diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..9dee993e2e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,19 @@ +*.py linguist-language=Python +*.cpp linguist-language=C++ +*.hpp linguist-language=C++ +*.java linguist-language=Java +*.go linguist-language=Go +*.swift linguist-language=Swift +*.js linguist-language=JavaScript + +*.c linguist-language=Other +*.h linguist-language=Other +*.cs linguist-language=Other +*.zig linguist-language=Other +*.rs linguist-language=Other +*.ts linguist-language=Other +*.dart linguist-language=Other +*.kt linguist-language=Other + +*.html linguist-detectable=false +*.css linguist-detectable=false diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 68be3e70b7..afb60dd02b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,10 @@ -If this PR is related to coding or code translation, please fill out the checklist. +If this pull request (PR) pertains to **Chinese-to-English translation**, please confirm that you have read the contribution guidelines and complete the checklist below: -- [ ] I've tested the code and ensured the outputs are the same as the outputs of reference codes. -- [ ] I've checked the codes (formatting, comments, indentation, file header, etc) carefully. -- [ ] The code does not rely on a particular environment or IDE and can be executed on a standard system (Win, macOS, Ubuntu). +- [ ] This PR represents the translation of a single, complete document, or contains only bug fixes. +- [ ] The translation accurately conveys the original meaning and intent of the Chinese version. If deviations exist, I have provided explanatory comments to clarify the reasons. + +If this pull request (PR) is associated with **coding or code transpilation**, please attach the relevant console outputs to the PR and complete the following checklist: + +- [ ] I have thoroughly reviewed the code, focusing on its formatting, comments, indentation, and file headers. +- [ ] I have confirmed that the code execution outputs are consistent with those produced by the reference code (Python or Java). +- [ ] The code is designed to be compatible on standard operating systems, including Windows, macOS, and Ubuntu. diff --git a/.github/workflows/c.yml b/.github/workflows/c.yml new file mode 100644 index 0000000000..8259cd2b12 --- /dev/null +++ b/.github/workflows/c.yml @@ -0,0 +1,72 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml + +name: C + +on: + push: + branches: ["main"] + paths: ["codes/c/**/*.c", "codes/c/**/*.h"] + pull_request: + branches: ["main"] + paths: ["codes/c/**/*.c", "codes/c/**/*.h"] + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: true + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v4 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ github.workspace }}/codes/c/build + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }}/codes/c + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ github.workspace }}/codes/c/build --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ github.workspace }}/codes/c/build + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml new file mode 100644 index 0000000000..ebf3a3e979 --- /dev/null +++ b/.github/workflows/cpp.yml @@ -0,0 +1,72 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml + +name: C++ + +on: + push: + branches: ["main"] + paths: ["codes/cpp/**/*.cpp", "codes/cpp/**/*.hpp"] + pull_request: + branches: ["main"] + paths: ["codes/cpp/**/*.cpp", "codes/cpp/**/*.hpp"] + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: true + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v4 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ github.workspace }}/codes/cpp/build + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }}/codes/cpp + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ github.workspace }}/codes/cpp/build --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ github.workspace }}/codes/cpp/build + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml new file mode 100644 index 0000000000..a6e7bab2b9 --- /dev/null +++ b/.github/workflows/dart.yml @@ -0,0 +1,36 @@ +# This workflow will install Dart SDK, run format, analyze and build with Dart + +name: Dart + +on: + push: + branches: ["main"] + paths: ["codes/dart/**/*.dart"] + pull_request: + branches: ["main"] + paths: ["codes/dart/**/*.dart"] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Dart ${{ matrix.dart-sdk }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + dart-sdk: [stable] + steps: + - uses: actions/checkout@v4 + - name: Set up Dart ${{ matrix.dart-sdk }} + uses: dart-lang/setup-dart@v1 + with: + sdk: ${{ matrix.dart-sdk}} + - name: Run format + run: dart format codes/dart + - name: Run analyze + run: dart analyze codes/dart + - name: Run build + run: dart codes/dart/build.dart diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000000..7001048c1c --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,39 @@ +# 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: ["codes/csharp/**/*.cs"] + pull_request: + branches: [ "main" ] + paths: ["codes/csharp/**/*.cs"] + workflow_dispatch: + +jobs: + build: + name: .NET ${{ matrix.dotnet-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: codes/csharp/ + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + dotnet-version: ["8.0.x"] + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET ${{ matrix.dotnet-version }} + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ matrix.dotnet-version }} + - name: Restore dependencies + run: dotnet restore hello-algo.csproj + - name: Build + run: dotnet build --no-restore hello-algo.csproj + - name: Test with dotnet + run: dotnet test hello-algo.csproj diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000000..6b615969d2 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,36 @@ +name: Go + +on: + push: + branches: [ "main" ] + paths: ["codes/go/**/*.go"] + pull_request: + branches: [ "main" ] + paths: ["codes/go/**/*.go"] + workflow_dispatch: + +jobs: + build: + name: Go ${{ matrix.go-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: codes/go/ + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + go-version: ["1.19.x"] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + - name: Check out code into the Go module directory + run: go get -v -t -d ./... + - name: Build + run: go build -v ./... + - name: Test with Go + run: go test -v ./... diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml new file mode 100644 index 0000000000..233b4292b4 --- /dev/null +++ b/.github/workflows/java.yml @@ -0,0 +1,29 @@ +# # This workflow will install OpenJDK and build the Java project +# For more information see: https://github.com/actions/setup-java + +name: Java + +on: + push: + branches: [ "main" ] + paths: ["codes/java/**/*.java"] + pull_request: + branches: [ "main" ] + paths: ["codes/java/**/*.java"] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + java: [ '11', '17' ] + name: Java ${{ matrix.Java }} sample + steps: + - uses: actions/checkout@v4 + - name: Setup java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - run: javac -d codes/java/build codes/java/**/*.java diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml new file mode 100644 index 0000000000..3a37d19e77 --- /dev/null +++ b/.github/workflows/javascript.yml @@ -0,0 +1,27 @@ +name: JavaScript + +on: + push: + branches: ['main'] + paths: ['codes/javascript/**/*.js'] + pull_request: + branches: ['main'] + paths: ['codes/javascript/**/*.js'] + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20.x + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - name: Run JavaScript Code + run: deno run -A codes/javascript/test_all.js diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml new file mode 100644 index 0000000000..aedeae6f8e --- /dev/null +++ b/.github/workflows/kotlin.yml @@ -0,0 +1,24 @@ +name: Kotlin + +on: + push: + branches: [ "main" ] + paths: ["codes/kotlin/**/*.kt"] + pull_request: + branches: [ "main" ] + paths: ["codes/kotlin/**/*.kt"] + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest, macos-latest ] + + name: Kotlin on ${{ matrix.os }} + steps: + - uses: actions/checkout@v4.1.2 + + - name: Build JAR + run: kotlinc codes/kotlin/**/*.kt -include-runtime -d codes/kotlin/build/test.jar diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000000..e178e2e737 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,41 @@ +# This workflow will install Python dependencies, run tests and lint with Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python + +on: + push: + branches: ["main"] + paths: ["codes/python/**/*.py"] + pull_request: + branches: ["main"] + paths: ["codes/python/**/*.py"] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.10", "3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black + - name: Lint with black + run: | + black codes/python + - name: Test python code + run: | + python codes/python/test_all.py diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml new file mode 100644 index 0000000000..2083cc0860 --- /dev/null +++ b/.github/workflows/ruby.yml @@ -0,0 +1,37 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by separate terms of service, privacy policy, and support documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake。 +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby + +name: Ruby + +on: + push: + branches: [ "main" ] + paths: ["codes/ruby/**/*.rb"] + pull_request: + branches: [ "main" ] + paths: ["codes/ruby/**/*.rb"] + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + + name: Ruby ${{ matrix.ruby-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + ruby-version: ['3.3'] + + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + - name: Run tests + run: ruby codes/ruby/test_all.rb diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000000..fada28651b --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,27 @@ +name: Rust + +on: + push: + branches: ["main"] + paths: ["codes/rust/**/*.rs", "codes/rust/Cargo.toml"] + pull_request: + branches: ["main"] + paths: ["codes/rust/**/*.rs", "codes/rust/Cargo.toml"] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: brndnmtthws/rust-action-rustup@v1 + with: + toolchain: nightly + + - uses: actions/checkout@v4 + + - name: Build + run: cargo build --manifest-path=codes/rust/Cargo.toml && cargo build --manifest-path=codes/rust/Cargo.toml --release diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000000..f8bfb6bee9 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,25 @@ +# This workflow will build a Swift project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift + +name: Swift + +on: + push: + branches: ["main"] + paths: ["codes/swift/**/*.swift"] + pull_request: + branches: ["main"] + paths: ["codes/swift/**/*.swift"] + workflow_dispatch: + +jobs: + build: + name: Swift on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-22.04", "macos-14"] + steps: + - uses: actions/checkout@v4 + - name: Build + run: swift build --package-path codes/swift diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml new file mode 100644 index 0000000000..1b6172101d --- /dev/null +++ b/.github/workflows/typescript.yml @@ -0,0 +1,26 @@ +name: TypeScript + +on: + push: + branches: ['main'] + paths: ['codes/typescript/**/*.ts'] + pull_request: + branches: ['main'] + paths: ['codes/typescript/**/*.ts'] + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20.x + - name: Install dependencies + run: cd codes/typescript && npm install + - name: Check TypeScript code + run: cd codes/typescript && npm run check diff --git a/.gitignore b/.gitignore index 52026b0663..04ea34de34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,14 @@ -# MacOS Desktop Services Store +# macOS .DS_Store -# Editor +# editors .vscode/ -.idea/ -cmake-build-debug/ -hello-algo.iml -*.dSYM/ +**/.idea # mkdocs files -site/ .cache/ -codes/scripts -docs/overrides/ -# python files -__pycache__ +# build +/build +/site +/utils diff --git a/Dockerfile b/Dockerfile index f699e33ea6..7531d6f916 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,25 @@ -FROM python:3.9.0-alpine +FROM python:3.10.0-alpine -RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade pip -RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple mkdocs-material==9.0.2 +ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple +RUN pip install --upgrade pip +RUN pip install mkdocs-material==9.5.5 mkdocs-glightbox -WORKDIR /app +WORKDIR /hello-algo -COPY codes /app/codes -COPY docs /app/docs -COPY mkdocs.yml /app/mkdocs.yml +COPY overrides ./build/overrides -RUN mkdir ./docs/overrides && mkdocs build +COPY docs ./build/docs +COPY mkdocs.yml mkdocs.yml +RUN mkdocs build -f mkdocs.yml -EXPOSE 8000 +COPY zh-hant/docs ./build/zh-hant/docs +COPY zh-hant/mkdocs.yml ./zh-hant/mkdocs.yml +RUN mkdocs build -f ./zh-hant/mkdocs.yml + +COPY en/docs ./build/en/docs +COPY en/mkdocs.yml ./en/mkdocs.yml +RUN mkdocs build -f ./en/mkdocs.yml -CMD ["mkdocs", "serve", "-a", "0.0.0.0:8000"] +WORKDIR /hello-algo/site +EXPOSE 8000 +CMD ["python", "-m", "http.server", "8000"] diff --git a/README.md b/README.md index 560e739536..bccf539d2c 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,59 @@

- - +

-

- 《 Hello,算法 》 -

+

+ hello-algo-typing-svg +
+ 动画图解、一键运行的数据结构与算法教程 +

-

- 动画图解、能运行、可讨论的
数据结构与算法快速入门教程 +

+ + + +

-
+

+ + +

- -   - + + + + + + + + + + + + +

- - 前往阅读 > - - hello-algo.com - - + 简体中文 + | + 繁體中文 + | + English

## 关于本书 -本书面向数据结构与算法初学者,致力于达成以下目标: +本项目旨在打造一本开源免费、新手友好的数据结构与算法入门教程。 -- 开源免费,所有同学都可在网上获取本书; -- 新手友好,适合算法初学者自主学习入门; -- 动画讲解,尽可能地保证平滑的学习曲线; -- 代码导向,提供可一键运行的算法源代码; -- 讨论学习,提问一般能在三日内得到回复; +- 全书采用动画图解,内容清晰易懂、学习曲线平滑,引导初学者探索数据结构与算法的知识地图。 +- 源代码可一键运行,帮助读者在练习中提升编程技能,了解算法工作原理和数据结构底层实现。 +- 提倡读者互助学习,欢迎大家在评论区提出问题与分享见解,在交流讨论中共同进步。 -如果感觉本书对你有所帮助,请点个 Star :star: 支持一下,谢谢! +若本书对您有所帮助,请在页面右上角点个 Star :star: 支持一下,谢谢! ## 推荐语 @@ -47,22 +61,28 @@ > > **—— 邓俊辉,清华大学计算机系教授** +> “如果我当年学数据结构与算法的时候有《Hello 算法》,学起来应该会简单 10 倍!” +> +> **—— 李沐,亚马逊资深首席科学家** + ## 贡献 -我们正在加速更新本书,期待您来[一起参与创作](https://www.hello-algo.com/chapter_preface/contribution/),以帮助其他读者获取更优质的学习内容: +本开源书仍在持续更新之中,欢迎您参与本项目,一同为读者提供更优质的学习内容。 -- 如果发现笔误、无效链接、内容缺失、文字歧义、解释不清晰等问题,烦请您帮忙修正; -- [代码翻译](https://github.com/krahets/hello-algo/issues/15) C++, Python, Go, JavaScript, TypeScript 正在进行中,期望您前来挑大梁; -- 欢迎您通过提交 Pull Request 来增添新内容,包括重写章节、新增章节等; +- [内容修正](https://www.hello-algo.com/chapter_appendix/contribution/):请您协助修正或在评论区指出语法错误、内容缺失、文字歧义、无效链接或代码 bug 等问题。 +- [代码转译](https://github.com/krahets/hello-algo/issues/15):期待您贡献各种语言代码,已支持 Python、Java、C++、Go、JavaScript 等 12 门编程语言。 +- [中译英](https://github.com/krahets/hello-algo/issues/914):诚邀您加入我们的翻译小组,成员主要来自计算机相关专业、英语专业和英文母语者。 -> 有任何问题请与我联系 WeChat: krahets-jyd +欢迎您提出宝贵意见和建议,如有任何问题请提交 Issues 或微信联系 `krahets-jyd` 。 感谢本开源书的每一位撰稿人,是他们的无私奉献让这本书变得更好,他们是: - - - +

+ + + +

## License -The texts, codes, images, photos, and videos in this repository are licensed under [CC BY-NC-SA-4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). +The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/codes/Dockerfile b/codes/Dockerfile new file mode 100644 index 0000000000..97d37bbcff --- /dev/null +++ b/codes/Dockerfile @@ -0,0 +1,35 @@ +FROM ubuntu:latest + +# Use Ubuntu image from Aliyun +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 + +# Install languages environment +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 ;; \ + # More languages... + *) \ + echo "Warning: No installation workflow for $LANG" ;; \ + esac \ + done + +WORKDIR /codes +COPY ./ ./ + +CMD ["/bin/bash"] diff --git a/codes/c/.gitignore b/codes/c/.gitignore new file mode 100644 index 0000000000..698ee4e212 --- /dev/null +++ b/codes/c/.gitignore @@ -0,0 +1,9 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ +*.dSYM/ + +build/ diff --git a/codes/c/CMakeLists.txt b/codes/c/CMakeLists.txt index 1a208dd9d0..bb5f8f6a9a 100644 --- a/codes/c/CMakeLists.txt +++ b/codes/c/CMakeLists.txt @@ -5,8 +5,16 @@ set(CMAKE_C_STANDARD 11) include_directories(./include) -add_subdirectory(include) add_subdirectory(chapter_computational_complexity) add_subdirectory(chapter_array_and_linkedlist) -add_subdirectory(chapter_sorting) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/codes/c/chapter_array_and_linkedlist/CMakeLists.txt b/codes/c/chapter_array_and_linkedlist/CMakeLists.txt index 24658b3012..29677a0be6 100644 --- a/codes/c/chapter_array_and_linkedlist/CMakeLists.txt +++ b/codes/c/chapter_array_and_linkedlist/CMakeLists.txt @@ -1 +1,3 @@ -add_executable(array array.c) \ No newline at end of file +add_executable(array array.c) +add_executable(linked_list linked_list.c) +add_executable(my_list my_list.c) \ No newline at end of file diff --git a/codes/c/chapter_array_and_linkedlist/array.c b/codes/c/chapter_array_and_linkedlist/array.c index 33dc999032..a826cbb32a 100644 --- a/codes/c/chapter_array_and_linkedlist/array.c +++ b/codes/c/chapter_array_and_linkedlist/array.c @@ -4,10 +4,10 @@ * Author: MolDuM (moldum@163.com) */ -#include "../include/include.h" +#include "../utils/common.h" -/* 随机返回一个数组元素 */ -int randomAccess(int* nums, int size) { +/* 随机访问元素 */ +int randomAccess(int *nums, int size) { // 在区间 [0, size) 中随机抽取一个数字 int randomIndex = rand() % size; // 获取并返回随机元素 @@ -16,9 +16,9 @@ int randomAccess(int* nums, int size) { } /* 扩展数组长度 */ -int* extend(int* nums, int size, int enlarge) { +int *extend(int *nums, int size, int enlarge) { // 初始化一个扩展长度后的数组 - int* res = (int *)malloc(sizeof(int) * (size + enlarge)); + int *res = (int *)malloc(sizeof(int) * (size + enlarge)); // 将原数组中的所有元素复制到新数组 for (int i = 0; i < size; i++) { res[i] = nums[i]; @@ -32,17 +32,18 @@ int* extend(int* nums, int size, int enlarge) { } /* 在数组的索引 index 处插入元素 num */ -void insert(int* nums, int size, int num, int index) { +void insert(int *nums, int size, int num, int index) { // 把索引 index 以及之后的所有元素向后移动一位 for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num; } -/* 删除索引 index 处元素 */ -void removeItem(int* nums, int size, int index) { +/* 删除索引 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]; @@ -50,16 +51,16 @@ void removeItem(int* nums, int size, int index) { } /* 遍历数组 */ -void traverse(int* nums, int size) { +void traverse(int *nums, int size) { int count = 0; // 通过索引遍历数组 for (int i = 0; i < size; i++) { - count++; + count += nums[i]; } } /* 在数组中查找指定元素 */ -int find(int* nums, int size, int target) { +int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; @@ -67,7 +68,6 @@ int find(int* nums, int size, int target) { return -1; } - /* Driver Code */ int main() { /* 初始化数组 */ @@ -76,21 +76,21 @@ int main() { printf("数组 arr = "); printArray(arr, size); - int nums[5] = { 1, 3, 2, 5, 4 }; + 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); + int *res = extend(nums, size, enlarge); size += enlarge; printf("将数组长度扩展至 8 ,得到 nums = "); printArray(res, size); - + /* 插入元素 */ insert(res, size, 6, 3); printf("在索引 3 处插入数字 6 ,得到 nums = "); @@ -100,13 +100,15 @@ int main() { removeItem(res, size, 2); printf("删除索引 2 处的元素,得到 nums = "); printArray(res, size); - + /* 遍历数组 */ traverse(res, size); - + /* 查找元素 */ int index = find(res, size, 3); printf("在 res 中查找元素 3 ,得到索引 = %d\n", index); + /* 释放内存 */ + free(res); return 0; } diff --git a/codes/c/chapter_array_and_linkedlist/linked_list.c b/codes/c/chapter_array_and_linkedlist/linked_list.c new file mode 100644 index 0000000000..d8275bcceb --- /dev/null +++ b/codes/c/chapter_array_and_linkedlist/linked_list.c @@ -0,0 +1,89 @@ +/** + * File: linked_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 在链表的节点 n0 之后插入节点 P */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* 删除链表的节点 n0 之后的首个节点 */ +// 注意:stdio.h 占用了 remove 关键词 +void removeItem(ListNode *n0) { + if (!n0->next) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 释放内存 + free(P); +} + +/* 访问链表中索引为 index 的节点 */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == NULL) + return NULL; + head = head->next; + } + return head; +} + +/* 在链表中查找值为 target 的首个节点 */ +int find(ListNode *head, int target) { + int index = 0; + while (head) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化链表 */ + // 初始化各个节点 + ListNode *n0 = newListNode(1); + ListNode *n1 = newListNode(3); + ListNode *n2 = newListNode(2); + ListNode *n3 = newListNode(5); + ListNode *n4 = newListNode(4); + // 构建节点之间的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + printf("初始化的链表为\r\n"); + printLinkedList(n0); + + /* 插入节点 */ + insert(n0, newListNode(0)); + printf("插入节点后的链表为\r\n"); + printLinkedList(n0); + + /* 删除节点 */ + removeItem(n0); + printf("删除节点后的链表为\r\n"); + printLinkedList(n0); + + /* 访问节点 */ + ListNode *node = access(n0, 3); + printf("链表中索引 3 处的节点的值 = %d\r\n", node->val); + + /* 查找节点 */ + int index = find(n0, 2); + printf("链表中值为 2 的节点的索引 = %d\r\n", index); + + // 释放内存 + freeMemoryLinkedList(n0); + return 0; +} diff --git a/codes/c/chapter_array_and_linkedlist/my_list.c b/codes/c/chapter_array_and_linkedlist/my_list.c new file mode 100644 index 0000000000..01b37ea266 --- /dev/null +++ b/codes/c/chapter_array_and_linkedlist/my_list.c @@ -0,0 +1,163 @@ +/** + * File: my_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 列表类 */ +typedef struct { + int *arr; // 数组(存储列表元素) + int capacity; // 列表容量 + int size; // 列表大小 + int extendRatio; // 列表每次扩容的倍数 +} MyList; + +void extendCapacity(MyList *nums); + +/* 构造函数 */ +MyList *newMyList() { + MyList *nums = malloc(sizeof(MyList)); + nums->capacity = 10; + nums->arr = malloc(sizeof(int) * nums->capacity); + nums->size = 0; + nums->extendRatio = 2; + return nums; +} + +/* 析构函数 */ +void delMyList(MyList *nums) { + free(nums->arr); + free(nums); +} + +/* 获取列表长度 */ +int size(MyList *nums) { + return nums->size; +} + +/* 获取列表容量 */ +int capacity(MyList *nums) { + return nums->capacity; +} + +/* 访问元素 */ +int get(MyList *nums, int index) { + assert(index >= 0 && index < nums->size); + return nums->arr[index]; +} + +/* 更新元素 */ +void set(MyList *nums, int index, int num) { + assert(index >= 0 && index < nums->size); + nums->arr[index] = num; +} + +/* 在尾部添加元素 */ +void add(MyList *nums, int num) { + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 扩容 + } + nums->arr[size(nums)] = num; + nums->size++; +} + +/* 在中间插入元素 */ +void insert(MyList *nums, int index, int num) { + assert(index >= 0 && index < size(nums)); + // 元素数量超出容量时,触发扩容机制 + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 扩容 + } + for (int i = size(nums); i > index; --i) { + nums->arr[i] = nums->arr[i - 1]; + } + nums->arr[index] = num; + nums->size++; +} + +/* 删除元素 */ +// 注意:stdio.h 占用了 remove 关键词 +int removeItem(MyList *nums, int index) { + assert(index >= 0 && index < size(nums)); + int num = nums->arr[index]; + for (int i = index; i < size(nums) - 1; i++) { + nums->arr[i] = nums->arr[i + 1]; + } + nums->size--; + return num; +} + +/* 列表扩容 */ +void extendCapacity(MyList *nums) { + // 先分配空间 + int newCapacity = capacity(nums) * nums->extendRatio; + int *extend = (int *)malloc(sizeof(int) * newCapacity); + int *temp = nums->arr; + + // 拷贝旧数据到新数据 + for (int i = 0; i < size(nums); i++) + extend[i] = nums->arr[i]; + + // 释放旧数据 + free(temp); + + // 更新新数据 + nums->arr = extend; + nums->capacity = newCapacity; +} + +/* 将列表转换为 Array 用于打印 */ +int *toArray(MyList *nums) { + return nums->arr; +} + +/* Driver Code */ +int main() { + /* 初始化列表 */ + MyList *nums = newMyList(); + /* 在尾部添加元素 */ + add(nums, 1); + add(nums, 3); + add(nums, 2); + add(nums, 5); + add(nums, 4); + printf("列表 nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,长度 = %d\n", capacity(nums), size(nums)); + + /* 在中间插入元素 */ + insert(nums, 3, 6); + printf("在索引 3 处插入数字 6 ,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 删除元素 */ + removeItem(nums, 3); + printf("删除索引 3 处的元素,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 访问元素 */ + int num = get(nums, 1); + printf("访问索引 1 处的元素,得到 num = %d\n", num); + + /* 更新元素 */ + set(nums, 1, 0); + printf("将索引 1 处的元素更新为 0 ,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 测试扩容机制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + add(nums, i); + } + + printf("扩容后的列表 nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,长度 = %d\n", capacity(nums), size(nums)); + + /* 释放分配内存 */ + delMyList(nums); + + return 0; +} diff --git a/codes/c/chapter_backtracking/CMakeLists.txt b/codes/c/chapter_backtracking/CMakeLists.txt new file mode 100644 index 0000000000..70161b6dd8 --- /dev/null +++ b/codes/c/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(permutations_i permutations_i.c) +add_executable(permutations_ii permutations_ii.c) +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) +add_executable(subset_sum_i_naive subset_sum_i_naive.c) +add_executable(subset_sum_i subset_sum_i.c) +add_executable(subset_sum_ii subset_sum_ii.c) +add_executable(n_queens n_queens.c) \ No newline at end of file diff --git a/codes/c/chapter_backtracking/n_queens.c b/codes/c/chapter_backtracking/n_queens.c new file mode 100644 index 0000000000..f538a5d119 --- /dev/null +++ b/codes/c/chapter_backtracking/n_queens.c @@ -0,0 +1,95 @@ +/** + * File : n_queens.c + * Created Time: 2023-09-25 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* 回溯算法:n 皇后 */ +void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], + bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { + // 当放置完所有行时,记录解 + if (row == n) { + res[*resSize] = (char **)malloc(sizeof(char *) * n); + for (int i = 0; i < n; ++i) { + res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); + strcpy(res[*resSize][i], state[i]); + } + (*resSize)++; + return; + } + // 遍历所有列 + for (int col = 0; col < n; col++) { + // 计算该格子对应的主对角线和次对角线 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 尝试:将皇后放置在该格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +char ***nQueens(int n, int *returnSize) { + char state[MAX_SIZE][MAX_SIZE]; + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + state[i][j] = '#'; + } + state[i][n] = '\0'; + } + bool cols[MAX_SIZE] = {false}; // 记录列是否有皇后 + bool diags1[2 * MAX_SIZE - 1] = {false}; // 记录主对角线上是否有皇后 + bool diags2[2 * MAX_SIZE - 1] = {false}; // 记录次对角线上是否有皇后 + + char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); + *returnSize = 0; + backtrack(0, n, state, res, returnSize, cols, diags1, diags2); + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + int returnSize; + char ***res = nQueens(n, &returnSize); + + printf("输入棋盘长宽为%d\n", n); + printf("皇后放置方案共有 %d 种\n", returnSize); + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + printf("["); + for (int k = 0; res[i][j][k] != '\0'; ++k) { + printf("%c", res[i][j][k]); + if (res[i][j][k + 1] != '\0') { + printf(", "); + } + } + printf("]\n"); + } + printf("---------------------\n"); + } + + // 释放内存 + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + free(res[i][j]); + } + free(res[i]); + } + free(res); + + return 0; +} diff --git a/codes/c/chapter_backtracking/permutations_i.c b/codes/c/chapter_backtracking/permutations_i.c new file mode 100644 index 0000000000..2e4b571ffd --- /dev/null +++ b/codes/c/chapter_backtracking/permutations_i.c @@ -0,0 +1,79 @@ +/** + * File: permutations_i.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 假设最多有 1000 个排列 +#define MAX_SIZE 1000 + +/* 回溯算法:全排列 I */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 当状态长度等于元素数量时,记录解 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // 遍历所有选择 + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state[stateSize] = choice; + // 进行下一轮选择 + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + } + } +} + +/* 全排列 I */ +int **permutationsI(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 2, 3}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsI(nums, numsSize, &returnSize); + + printf("输入数组 nums = "); + printArray(nums, numsSize); + printf("\n所有排列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // 释放内存 + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/codes/c/chapter_backtracking/permutations_ii.c b/codes/c/chapter_backtracking/permutations_ii.c new file mode 100644 index 0000000000..c6513c77cf --- /dev/null +++ b/codes/c/chapter_backtracking/permutations_ii.c @@ -0,0 +1,81 @@ +/** + * File: permutations_ii.c + * Created Time: 2023-10-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 假设最多有 1000 个排列,元素最大为 1000 +#define MAX_SIZE 1000 + +/* 回溯算法:全排列 II */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 当状态长度等于元素数量时,记录解 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // 遍历所有选择 + bool duplicated[MAX_SIZE] = {false}; + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated[choice]) { + // 尝试:做出选择,更新状态 + duplicated[choice] = true; // 记录选择过的元素值 + selected[i] = true; + state[stateSize] = choice; + // 进行下一轮选择 + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + } + } +} + +/* 全排列 II */ +int **permutationsII(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 1, 2}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsII(nums, numsSize, &returnSize); + + printf("输入数组 nums = "); + printArray(nums, numsSize); + printf("\n所有排列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // 释放内存 + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/codes/c/chapter_backtracking/preorder_traversal_i_compact.c b/codes/c/chapter_backtracking/preorder_traversal_i_compact.c new file mode 100644 index 0000000000..d6ef1f1515 --- /dev/null +++ b/codes/c/chapter_backtracking/preorder_traversal_i_compact.c @@ -0,0 +1,49 @@ +/** + * File: preorder_traversal_i_compact.c + * Created Time: 2023-05-10 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假设结果长度不超过 100 +#define MAX_SIZE 100 + +TreeNode *res[MAX_SIZE]; +int resSize = 0; + +/* 前序遍历:例题一 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + if (root->val == 7) { + // 记录解 + res[resSize++] = root; + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二叉树\n"); + printTree(root); + + // 前序遍历 + preOrder(root); + + printf("\n输出所有值为 7 的节点\n"); + int *vals = malloc(resSize * sizeof(int)); + for (int i = 0; i < resSize; i++) { + vals[i] = res[i]->val; + } + printArray(vals, resSize); + + // 释放内存 + freeMemoryTree(root); + free(vals); + return 0; +} diff --git a/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c b/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c new file mode 100644 index 0000000000..824911d66d --- /dev/null +++ b/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c @@ -0,0 +1,61 @@ +/** + * File: preorder_traversal_ii_compact.c + * Created Time: 2023-05-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假设路径和结果长度不超过 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前序遍历:例题二 */ +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--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二叉树\n"); + printTree(root); + + // 前序遍历 + preOrder(root); + + printf("\n输出所有根节点到节点 7 的路径\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 释放内存 + freeMemoryTree(root); + return 0; +} diff --git a/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c b/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c new file mode 100644 index 0000000000..d5269ff28c --- /dev/null +++ b/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c @@ -0,0 +1,62 @@ +/** + * File: preorder_traversal_iii_compact.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假设路径和结果长度不超过 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前序遍历:例题三 */ +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--; +} + +/* Driver Code */ +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 的路径,要求路径中不包含值为 3 的节点\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 释放内存 + freeMemoryTree(root); + return 0; +} diff --git a/codes/c/chapter_backtracking/preorder_traversal_iii_template.c b/codes/c/chapter_backtracking/preorder_traversal_iii_template.c new file mode 100644 index 0000000000..b02930afcb --- /dev/null +++ b/codes/c/chapter_backtracking/preorder_traversal_iii_template.c @@ -0,0 +1,93 @@ +/** + * File: preorder_traversal_iii_template.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假设路径和结果长度不超过 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 判断当前状态是否为解 */ +bool isSolution(void) { + return pathSize > 0 && path[pathSize - 1]->val == 7; +} + +/* 记录解 */ +void recordSolution(void) { + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; +} + +/* 判断在当前状态下,该选择是否合法 */ +bool isValid(TreeNode *choice) { + return choice != NULL && choice->val != 3; +} + +/* 更新状态 */ +void makeChoice(TreeNode *choice) { + path[pathSize++] = choice; +} + +/* 恢复状态 */ +void undoChoice(void) { + pathSize--; +} + +/* 回溯算法:例题三 */ +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(); + } + } +} + +/* Driver Code */ +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输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 释放内存 + freeMemoryTree(root); + return 0; +} diff --git a/codes/c/chapter_backtracking/subset_sum_i.c b/codes/c/chapter_backtracking/subset_sum_i.c new file mode 100644 index 0000000000..5fdbb81345 --- /dev/null +++ b/codes/c/chapter_backtracking/subset_sum_i.c @@ -0,0 +1,78 @@ +/** + * File: subset_sum_i.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 状态(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 结果列表(子集列表) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯算法:子集和 I */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 子集和等于 target 时,记录解 + if (target == 0) { + for (int i = 0; i < stateSize; ++i) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choicesSize; i++) { + // 剪枝一:若子集和超过 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); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumI(nums, numsSize, target); + + printf("输入数组 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等于 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/codes/c/chapter_backtracking/subset_sum_i_naive.c b/codes/c/chapter_backtracking/subset_sum_i_naive.c new file mode 100644 index 0000000000..145a80e5f9 --- /dev/null +++ b/codes/c/chapter_backtracking/subset_sum_i_naive.c @@ -0,0 +1,69 @@ +/** + * File: subset_sum_i_naive.c + * Created Time: 2023-07-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 状态(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 结果列表(子集列表) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯算法:子集和 I */ +void backtrack(int target, int total, int *choices, int choicesSize) { + // 子集和等于 target 时,记录解 + if (total == target) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 遍历所有选择 + for (int i = 0; i < choicesSize; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state[stateSize++] = choices[i]; + // 进行下一轮选择 + backtrack(target, total + choices[i], choices, choicesSize); + // 回退:撤销选择,恢复到之前的状态 + stateSize--; + } +} + +/* 求解子集和 I(包含重复子集) */ +void subsetSumINaive(int *nums, int numsSize, int target) { + resSize = 0; // 初始化解的数量为0 + backtrack(target, 0, nums, numsSize); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumINaive(nums, numsSize, target); + + printf("输入数组 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等于 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; i++) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/codes/c/chapter_backtracking/subset_sum_ii.c b/codes/c/chapter_backtracking/subset_sum_ii.c new file mode 100644 index 0000000000..9d87dc2f01 --- /dev/null +++ b/codes/c/chapter_backtracking/subset_sum_ii.c @@ -0,0 +1,83 @@ +/** + * File: subset_sum_ii.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 状态(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 结果列表(子集列表) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯算法:子集和 II */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 子集和等于 target 时,记录解 + if (target == 0) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choicesSize; i++) { + // 剪枝一:若子集和超过 target ,则直接跳过 + if (target - choices[i] < 0) { + continue; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + 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); +} + +/* Driver Code */ +int main() { + int nums[] = {4, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumII(nums, numsSize, target); + + printf("输入数组 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等于 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/codes/c/chapter_computational_complexity/CMakeLists.txt b/codes/c/chapter_computational_complexity/CMakeLists.txt index d9e55dcf16..dcfa063c3d 100644 --- a/codes/c/chapter_computational_complexity/CMakeLists.txt +++ b/codes/c/chapter_computational_complexity/CMakeLists.txt @@ -1,2 +1,5 @@ -add_executable(time_complexity time_complexity.c ) -add_executable(worst_best_time_complexity worst_best_time_complexity.c) \ No newline at end of file +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) diff --git a/codes/c/chapter_computational_complexity/iteration.c b/codes/c/chapter_computational_complexity/iteration.c new file mode 100644 index 0000000000..95594354da --- /dev/null +++ b/codes/c/chapter_computational_complexity/iteration.c @@ -0,0 +1,81 @@ +/** + * File: iteration.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) + */ + +#include "../utils/common.h" + +/* for 循环 */ +int forLoop(int n) { + int res = 0; + // 循环求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 循环 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; +} + +/* while 循环(两次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; +} + +/* 双层 for 循环 */ +char *nestedForLoop(int n) { + // n * n 为对应点数量,"(i, j), " 对应字符串长最大为 6+10*2,加上最后一个空字符 \0 的额外空间 + int size = n * n * 26 + 1; + char *res = malloc(size * sizeof(char)); + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + char tmp[26]; + snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); + strncat(res, tmp, size - strlen(res) - 1); + } + } + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + printf("\nfor 循环的求和结果 res = %d\n", res); + + res = whileLoop(n); + printf("\nwhile 循环的求和结果 res = %d\n", res); + + res = whileLoopII(n); + printf("\nwhile 循环(两次更新)求和结果 res = %d\n", res); + + char *resStr = nestedForLoop(n); + printf("\n双层 for 循环的遍历结果 %s\r\n", resStr); + free(resStr); + + return 0; +} diff --git a/codes/c/chapter_computational_complexity/recursion.c b/codes/c/chapter_computational_complexity/recursion.c new file mode 100644 index 0000000000..1469270042 --- /dev/null +++ b/codes/c/chapter_computational_complexity/recursion.c @@ -0,0 +1,77 @@ +/** + * File: recursion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 递归 */ +int recur(int n) { + // 终止条件 + if (n == 1) + return 1; + // 递:递归调用 + int res = recur(n - 1); + // 归:返回结果 + return n + res; +} + +/* 使用迭代模拟递归 */ +int forLoopRecur(int n) { + int stack[1000]; // 借助一个大数组来模拟栈 + int top = -1; // 栈顶索引 + int res = 0; + // 递:递归调用 + for (int i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack[1 + top++] = i; + } + // 归:返回结果 + while (top >= 0) { + // 通过“出栈操作”模拟“归” + res += stack[top--]; + } + // res = 1+2+3+...+n + return res; +} + +/* 尾递归 */ +int tailRecur(int n, int res) { + // 终止条件 + if (n == 0) + return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); +} + +/* 斐波那契数列:递归 */ +int fib(int n) { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + printf("\n递归函数的求和结果 res = %d\n", res); + + res = forLoopRecur(n); + printf("\n使用迭代模拟递归求和结果 res = %d\n", res); + + res = tailRecur(n, 0); + printf("\n尾递归函数的求和结果 res = %d\n", res); + + res = fib(n); + printf("\n斐波那契数列的第 %d 项为 %d\n", n, res); + + return 0; +} diff --git a/codes/c/chapter_computational_complexity/space_complexity.c b/codes/c/chapter_computational_complexity/space_complexity.c new file mode 100644 index 0000000000..5706f40364 --- /dev/null +++ b/codes/c/chapter_computational_complexity/space_complexity.c @@ -0,0 +1,141 @@ +/** + * File: space_complexity.c + * Created Time: 2023-04-15 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 函数 */ +int func() { + // 执行某些操作 + return 0; +} + +/* 常数阶 */ +void constant(int n) { + // 常量、变量、对象占用 O(1) 空间 + const int a = 0; + int b = 0; + int nums[1000]; + ListNode *node = newListNode(0); + free(node); + // 循环中的变量占用 O(1) 空间 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (int i = 0; i < n; i++) { + func(); + } +} + +/* 哈希表 */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // 基于 uthash.h 实现 +} HashTable; + +/* 线性阶 */ +void linear(int n) { + // 长度为 n 的数组占用 O(n) 空间 + int *nums = malloc(sizeof(int) * n); + free(nums); + + // 长度为 n 的列表占用 O(n) 空间 + ListNode **nodes = malloc(sizeof(ListNode *) * n); + for (int i = 0; i < n; i++) { + nodes[i] = newListNode(i); + } + // 内存释放 + for (int i = 0; i < n; i++) { + free(nodes[i]); + } + free(nodes); + + // 长度为 n 的哈希表占用 O(n) 空间 + HashTable *h = NULL; + for (int i = 0; i < n; i++) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = i; + tmp->val = i; + HASH_ADD_INT(h, key, tmp); + } + + // 内存释放 + HashTable *curr, *tmp; + HASH_ITER(hh, h, curr, tmp) { + HASH_DEL(h, curr); + free(curr); + } +} + +/* 线性阶(递归实现) */ +void linearRecur(int n) { + printf("递归 n = %d\r\n", n); + if (n == 1) + return; + linearRecur(n - 1); +} + +/* 平方阶 */ +void quadratic(int n) { + // 二维列表占用 O(n^2) 空间 + int **numMatrix = malloc(sizeof(int *) * n); + for (int i = 0; i < n; i++) { + int *tmp = malloc(sizeof(int) * n); + for (int j = 0; j < n; j++) { + tmp[j] = 0; + } + numMatrix[i] = tmp; + } + + // 内存释放 + for (int i = 0; i < n; i++) { + free(numMatrix[i]); + } + free(numMatrix); +} + +/* 平方阶(递归实现) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + int *nums = malloc(sizeof(int) * n); + printf("递归 n = %d 中的 nums 长度 = %d\r\n", n, n); + int res = quadraticRecur(n - 1); + free(nums); + return res; +} + +/* 指数阶(建立满二叉树) */ +TreeNode *buildTree(int n) { + if (n == 0) + return NULL; + TreeNode *root = newTreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // 常数阶 + constant(n); + // 线性阶 + linear(n); + linearRecur(n); + // 平方阶 + quadratic(n); + quadraticRecur(n); + // 指数阶 + TreeNode *root = buildTree(n); + printTree(root); + + // 释放内存 + freeMemoryTree(root); + + return 0; +} diff --git a/codes/c/chapter_computational_complexity/time_complexity.c b/codes/c/chapter_computational_complexity/time_complexity.c index ee827ed14f..d7ffe90214 100644 --- a/codes/c/chapter_computational_complexity/time_complexity.c +++ b/codes/c/chapter_computational_complexity/time_complexity.c @@ -1,10 +1,10 @@ /** * File: time_complexity.c * Created Time: 2023-01-03 - * Author: sjinzh (sjinzh@gmail.com) + * Author: codingonion (coderonion@gmail.com) */ -#include "../include/include.h" +#include "../utils/common.h" /* 常数阶 */ int constant(int n) { @@ -12,7 +12,7 @@ int constant(int n) { int size = 100000; int i = 0; for (int i = 0; i < size; i++) { - count ++; + count++; } return count; } @@ -21,7 +21,7 @@ int constant(int n) { int linear(int n) { int count = 0; for (int i = 0; i < n; i++) { - count ++; + count++; } return count; } @@ -31,19 +31,18 @@ int arrayTraversal(int *nums, int n) { int count = 0; // 循环次数与数组长度成正比 for (int i = 0; i < n; i++) { - count ++; + count++; } return count; } /* 平方阶 */ -int quadratic(int n) -{ +int quadratic(int n) { int count = 0; - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { - count ++; + count++; } } return count; @@ -51,17 +50,17 @@ int quadratic(int n) /* 平方阶(冒泡排序) */ int bubbleSort(int *nums, int n) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + 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 个单元操作 + count += 3; // 元素交换包含 3 个单元操作 } } } @@ -72,7 +71,7 @@ int bubbleSort(int *nums, int n) { int exponential(int n) { int count = 0; int bas = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < bas; j++) { count++; @@ -85,12 +84,13 @@ int exponential(int n) { /* 指数阶(递归实现) */ int expRecur(int n) { - if (n == 1) return 1; + if (n == 1) + return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 对数阶(循环实现) */ -int logarithmic(float n) { +int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; @@ -100,25 +100,27 @@ int logarithmic(float n) { } /* 对数阶(递归实现) */ -int logRecur(float n) { - if (n <= 1) return 0; +int logRecur(int n) { + if (n <= 1) + return 0; return logRecur(n / 2) + 1; } /* 线性对数阶 */ -int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); +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 ++; + count++; } return count; } /* 阶乘阶(递归实现) */ int factorialRecur(int n) { - if (n == 0) return 1; + if (n == 0) + return 1; int count = 0; for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); @@ -133,38 +135,38 @@ int main(int argc, char *argv[]) { printf("输入数据大小 n = %d\n", n); int count = constant(n); - printf("常数阶的计算操作数量 = %d\n", count); + printf("常数阶的操作数量 = %d\n", count); count = linear(n); - printf("线性阶的计算操作数量 = %d\n", count); - // 分配堆区内存(创建一维可变长数组:数组中元素数量为n,元素类型为int) + printf("线性阶的操作数量 = %d\n", count); + // 分配堆区内存(创建一维可变长数组:数组中元素数量为 n ,元素类型为 int ) int *nums = (int *)malloc(n * sizeof(int)); count = arrayTraversal(nums, n); - printf("线性阶(遍历数组)的计算操作数量 = %d\n", count); + printf("线性阶(遍历数组)的操作数量 = %d\n", count); count = quadratic(n); - printf("平方阶的计算操作数量 = %d\n", count); + printf("平方阶的操作数量 = %d\n", count); for (int i = 0; i < n; i++) { - nums[i] = n - i; // [n,n-1,...,2,1] + nums[i] = n - i; // [n,n-1,...,2,1] } count = bubbleSort(nums, n); - printf("平方阶(冒泡排序)的计算操作数量 = %d\n", count); + printf("平方阶(冒泡排序)的操作数量 = %d\n", count); count = exponential(n); - printf("指数阶(循环实现)的计算操作数量 = %d\n", count); + printf("指数阶(循环实现)的操作数量 = %d\n", count); count = expRecur(n); - printf("指数阶(递归实现)的计算操作数量 = %d\n", count); + printf("指数阶(递归实现)的操作数量 = %d\n", count); count = logarithmic(n); - printf("对数阶(循环实现)的计算操作数量 = %d\n", count); + printf("对数阶(循环实现)的操作数量 = %d\n", count); count = logRecur(n); - printf("对数阶(递归实现)的计算操作数量 = %d\n", count); + printf("对数阶(递归实现)的操作数量 = %d\n", count); count = linearLogRecur(n); - printf("线性对数阶(递归实现)的计算操作数量 = %d\n", count); + printf("线性对数阶(递归实现)的操作数量 = %d\n", count); count = factorialRecur(n); - printf("阶乘阶(递归实现)的计算操作数量 = %d\n", count); + printf("阶乘阶(递归实现)的操作数量 = %d\n", count); // 释放堆区内存 if (nums != NULL) { @@ -172,5 +174,6 @@ int main(int argc, char *argv[]) { nums = NULL; } getchar(); + return 0; } diff --git a/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/codes/c/chapter_computational_complexity/worst_best_time_complexity.c index e5cee821f3..7e8b6a8235 100644 --- a/codes/c/chapter_computational_complexity/worst_best_time_complexity.c +++ b/codes/c/chapter_computational_complexity/worst_best_time_complexity.c @@ -1,20 +1,20 @@ /** * File: worst_best_time_complexity.c * Created Time: 2023-01-03 - * Author: sjinzh (sjinzh@gmail.com) + * Author: codingonion (coderonion@gmail.com) */ -#include "../include/include.h" +#include "../utils/common.h" /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ int *randomNumbers(int n) { - // 分配堆区内存(创建一维可变长数组:数组中元素数量为n,元素类型为int) + // 分配堆区内存(创建一维可变长数组:数组中元素数量为 n ,元素类型为 int ) 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]; @@ -27,7 +27,10 @@ int *randomNumbers(int n) { /* 查找数组 nums 中数字 1 所在索引 */ int findOne(int *nums, int n) { for (int i = 0; i < n; i++) { - if (nums[i] == 1) return i; + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if (nums[i] == 1) + return i; } return -1; } @@ -49,5 +52,6 @@ int main(int argc, char *argv[]) { nums = NULL; } } + return 0; } diff --git a/codes/c/chapter_divide_and_conquer/CMakeLists.txt b/codes/c/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 0000000000..e03b1c5881 --- /dev/null +++ b/codes/c/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.c) +add_executable(build_tree build_tree.c) +add_executable(hanota hanota.c) diff --git a/codes/c/chapter_divide_and_conquer/binary_search_recur.c b/codes/c/chapter_divide_and_conquer/binary_search_recur.c new file mode 100644 index 0000000000..2a468a195e --- /dev/null +++ b/codes/c/chapter_divide_and_conquer/binary_search_recur.c @@ -0,0 +1,47 @@ +/** + * File: binary_search_recur.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 二分查找:问题 f(i, j) */ +int dfs(int nums[], int target, int i, int j) { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目标元素,返回其索引 + return m; + } +} + +/* 二分查找 */ +int binarySearch(int nums[], int target, int numsSize) { + int n = numsSize; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + + // 二分查找(双闭区间) + int index = binarySearch(nums, target, numsSize); + printf("目标元素 6 的索引 = %d\n", index); + + return 0; +} diff --git a/codes/c/chapter_divide_and_conquer/build_tree.c b/codes/c/chapter_divide_and_conquer/build_tree.c new file mode 100644 index 0000000000..fc6ef05e23 --- /dev/null +++ b/codes/c/chapter_divide_and_conquer/build_tree.c @@ -0,0 +1,61 @@ +/** + * File : build_tree.c + * Created Time: 2023-10-16 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// 假设所有元素都小于 1000 +#define MAX_SIZE 1000 + +/* 构建二叉树:分治 */ +TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { + // 子树区间为空时终止 + if (r - l < 0) + return NULL; + // 初始化根节点 + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = preorder[i]; + root->left = NULL; + root->right = NULL; + // 查询 m ,从而划分左右子树 + int m = inorderMap[preorder[i]]; + // 子问题:构建左子树 + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); + // 子问题:构建右子树 + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); + // 返回根节点 + return root; +} + +/* 构建二叉树 */ +TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); + for (int i = 0; i < inorderSize; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); + free(inorderMap); + return root; +} + +/* Driver Code */ +int main() { + int preorder[] = {3, 9, 2, 1, 7}; + int inorder[] = {9, 3, 1, 2, 7}; + int preorderSize = sizeof(preorder) / sizeof(preorder[0]); + int inorderSize = sizeof(inorder) / sizeof(inorder[0]); + printf("前序遍历 = "); + printArray(preorder, preorderSize); + printf("中序遍历 = "); + printArray(inorder, inorderSize); + + TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); + printf("构建的二叉树为:\n"); + printTree(root); + + freeMemoryTree(root); + return 0; +} diff --git a/codes/c/chapter_divide_and_conquer/hanota.c b/codes/c/chapter_divide_and_conquer/hanota.c new file mode 100644 index 0000000000..6979720b13 --- /dev/null +++ b/codes/c/chapter_divide_and_conquer/hanota.c @@ -0,0 +1,74 @@ +/** + * File: hanota.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// 假设最多有 1000 个排列 +#define MAX_SIZE 1000 + +/* 移动一个圆盘 */ +void move(int *src, int *srcSize, int *tar, int *tarSize) { + // 从 src 顶部拿出一个圆盘 + int pan = src[*srcSize - 1]; + src[*srcSize - 1] = 0; + (*srcSize)--; + // 将圆盘放入 tar 顶部 + tar[*tarSize] = pan; + (*tarSize)++; +} + +/* 求解汉诺塔问题 f(i) */ +void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, srcSize, tar, tarSize); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, srcSize, tar, tarSize); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); +} + +/* 求解汉诺塔问题 */ +void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(*ASize, A, ASize, B, BSize, C, CSize); +} + +/* Driver Code */ +int main() { + // 列表尾部是柱子顶部 + int a[] = {5, 4, 3, 2, 1}; + int b[MAX_SIZE] = {0}; + int c[MAX_SIZE] = {0}; + + int ASize = sizeof(a) / sizeof(a[0]); + int BSize = 0; + int CSize = 0; + + printf("\n初始状态下:"); + printf("\nA = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + solveHanota(a, &ASize, b, &BSize, c, &CSize); + + printf("\n圆盘移动完成后:"); + printf("A = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/CMakeLists.txt b/codes/c/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 0000000000..dd769ebd59 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) +add_executable(min_path_sum min_path_sum.c) +add_executable(knapsack knapsack.c) +add_executable(unbounded_knapsack unbounded_knapsack.c) +add_executable(coin_change coin_change.c) +add_executable(coin_change_ii coin_change_ii.c) +add_executable(edit_distance edit_distance.c) diff --git a/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c b/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c new file mode 100644 index 0000000000..4673ce2613 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c @@ -0,0 +1,47 @@ +/** + * File: climbing_stairs_backtrack.c + * Created Time: 2023-09-22 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 回溯 */ +void backtrack(int *choices, int state, int n, int *res, int len) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res[0]++; + // 遍历所有选择 + for (int i = 0; i < len; i++) { + int choice = choices[i]; + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) + continue; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res, len); + // 回退 + } +} + +/* 爬楼梯:回溯 */ +int climbingStairsBacktrack(int n) { + int choices[2] = {1, 2}; // 可选择向上爬 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; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c b/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c new file mode 100644 index 0000000000..b5356933b7 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_constraint_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 带约束爬楼梯:动态规划 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(3, sizeof(int)); + } + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + int res = dp[n][1] + dp[n][2]; + // 释放内存 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c b/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c new file mode 100644 index 0000000000..d62b7df20c --- /dev/null +++ b/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 搜索 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬楼梯:搜索 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c b/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c new file mode 100644 index 0000000000..09bc1e8bf1 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_dfs_mem.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 记忆化搜索 */ +int dfs(int i, int *mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在记录 dp[i] ,则直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; +} + +/* 爬楼梯:记忆化搜索 */ +int climbingStairsDFSMem(int n) { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + int *mem = (int *)malloc((n + 1) * sizeof(int)); + for (int i = 0; i <= n; i++) { + mem[i] = -1; + } + int result = dfs(n, mem); + free(mem); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c b/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c new file mode 100644 index 0000000000..53be19a253 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c @@ -0,0 +1,51 @@ +/** + * File: climbing_stairs_dp.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 爬楼梯:动态规划 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用于存储子问题的解 + int *dp = (int *)malloc((n + 1) * sizeof(int)); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + int result = dp[n]; + free(dp); + return result; +} + +/* 爬楼梯:空间优化后的动态规划 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); + + res = climbingStairsDPComp(n); + printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_dynamic_programming/coin_change.c b/codes/c/chapter_dynamic_programming/coin_change.c new file mode 100644 index 0000000000..0bd9d1ae35 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/coin_change.c @@ -0,0 +1,92 @@ +/** + * File: coin_change.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 零钱兑换:动态规划 */ +int coinChangeDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 状态转移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + int res = dp[n][amt] != MAX ? dp[n][amt] : -1; + // 释放内存 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* 零钱兑换:空间优化后的动态规划 */ +int coinChangeDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // 初始化 dp 表 + int *dp = malloc((amt + 1) * sizeof(int)); + for (int j = 1; j <= amt; j++) { + dp[j] = MAX; + } + dp[0] = 0; + + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + int res = dp[amt] != MAX ? dp[amt] : -1; + // 释放内存 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 4; + + // 动态规划 + int res = coinChangeDP(coins, amt, coinsSize); + printf("凑到目标金额所需的最少硬币数量为 %d\n", res); + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins, amt, coinsSize); + printf("凑到目标金额所需的最少硬币数量为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/coin_change_ii.c b/codes/c/chapter_dynamic_programming/coin_change_ii.c new file mode 100644 index 0000000000..1747028595 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/coin_change_ii.c @@ -0,0 +1,81 @@ +/** + * File: coin_change_ii.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 零钱兑换 II:动态规划 */ +int coinChangeIIDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + int res = dp[n][amt]; + // 释放内存 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // 初始化 dp 表 + int *dp = calloc(amt + 1, sizeof(int)); + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + int res = dp[amt]; + // 释放内存 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 5; + + // 动态规划 + int res = coinChangeIIDP(coins, amt, coinsSize); + printf("凑出目标金额的硬币组合数量为 %d\n", res); + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(coins, amt, coinsSize); + printf("凑出目标金额的硬币组合数量为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/edit_distance.c b/codes/c/chapter_dynamic_programming/edit_distance.c new file mode 100644 index 0000000000..8c1255c400 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/edit_distance.c @@ -0,0 +1,159 @@ +/** + * File: edit_distance.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 编辑距离:暴力搜索 */ +int editDistanceDFS(char *s, char *t, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return myMin(myMin(insert, del), replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若已有记录,则直接返回之 + if (mem[i][j] != -1) + return mem[i][j]; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = myMin(myMin(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +int editDistanceDP(char *s, char *t, int n, int m) { + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(m + 1, sizeof(int)); + } + // 状态转移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + int res = dp[n][m]; + // 释放内存 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 编辑距离:空间优化后的动态规划 */ +int editDistanceDPComp(char *s, char *t, int n, int m) { + int *dp = calloc(m + 1, sizeof(int)); + // 状态转移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (int i = 1; i <= n; i++) { + // 状态转移:首列 + int leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + int res = dp[m]; + // 释放内存 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + char *s = "bag"; + char *t = "pack"; + int n = strlen(s), m = strlen(t); + + // 暴力搜索 + int res = editDistanceDFS(s, t, n, m); + printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); + + // 记忆化搜索 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((m + 1) * sizeof(int)); + memset(mem[i], -1, (m + 1) * sizeof(int)); + } + res = editDistanceDFSMem(s, t, m + 1, mem, n, m); + printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); + // 释放内存 + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 动态规划 + res = editDistanceDP(s, t, n, m); + printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t, n, m); + printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/knapsack.c b/codes/c/chapter_dynamic_programming/knapsack.c new file mode 100644 index 0000000000..886faedc1f --- /dev/null +++ b/codes/c/chapter_dynamic_programming/knapsack.c @@ -0,0 +1,137 @@ +/** + * File: knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最大值 */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 0-1 背包:暴力搜索 */ +int knapsackDFS(int wgt[], int val[], int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return myMax(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = myMax(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // 释放内存 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 0-1 背包:空间优化后的动态规划 */ +int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int *dp = calloc(cap + 1, sizeof(int)); + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // 释放内存 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int cap = 50; + int n = sizeof(wgt) / sizeof(wgt[0]); + int wgtSize = n; + + // 暴力搜索 + int res = knapsackDFS(wgt, val, n, cap); + printf("不超过背包容量的最大物品价值为 %d\n", res); + + // 记忆化搜索 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((cap + 1) * sizeof(int)); + memset(mem[i], -1, (cap + 1) * sizeof(int)); + } + res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); + printf("不超过背包容量的最大物品价值为 %d\n", res); + // 释放内存 + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 动态规划 + res = knapsackDP(wgt, val, cap, wgtSize); + printf("不超过背包容量的最大物品价值为 %d\n", res); + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt, val, cap, wgtSize); + printf("不超过背包容量的最大物品价值为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c b/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c new file mode 100644 index 0000000000..48ec845289 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c @@ -0,0 +1,62 @@ +/** + * File: min_cost_climbing_stairs_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 爬楼梯最小代价:动态规划 */ +int minCostClimbingStairsDP(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + int *dp = calloc(n + 1, sizeof(int)); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; + } + int res = dp[n]; + // 释放内存 + free(dp); + return res; +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +int minCostClimbingStairsDPComp(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = myMin(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + int costSize = sizeof(cost) / sizeof(cost[0]); + printf("输入楼梯的代价列表为:"); + printArray(cost, costSize); + + int res = minCostClimbingStairsDP(cost, costSize); + printf("爬完楼梯的最低代价为 %d\n", res); + + res = minCostClimbingStairsDPComp(cost, costSize); + printf("爬完楼梯的最低代价为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/min_path_sum.c b/codes/c/chapter_dynamic_programming/min_path_sum.c new file mode 100644 index 0000000000..be19b7578c --- /dev/null +++ b/codes/c/chapter_dynamic_programming/min_path_sum.c @@ -0,0 +1,134 @@ +/** + * File: min_path_sum.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +// 假设矩阵最大行列数为 100 +#define MAX_SIZE 100 + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 最小路径和:暴力搜索 */ +int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小路径和:记忆化搜索 */ +int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // 初始化 dp 表 + int **dp = malloc(n * sizeof(int *)); + for (int i = 0; i < n; i++) { + dp[i] = calloc(m, sizeof(int)); + } + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + int res = dp[n - 1][m - 1]; + // 释放内存 + for (int i = 0; i < n; i++) { + free(dp[i]); + } + return res; +} + +/* 最小路径和:空间优化后的动态规划 */ +int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // 初始化 dp 表 + int *dp = calloc(m, sizeof(int)); + // 状态转移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; + } + } + int res = dp[m - 1]; + // 释放内存 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = 4, m = 4; // 矩阵容量为 MAX_SIZE * MAX_SIZE ,有效行列数为 n * m + + // 暴力搜索 + int res = minPathSumDFS(grid, n - 1, m - 1); + printf("从左上角到右下角的最小路径和为 %d\n", res); + + // 记忆化搜索 + int mem[MAX_SIZE][MAX_SIZE]; + memset(mem, -1, sizeof(mem)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + printf("从左上角到右下角的最小路径和为 %d\n", res); + + // 动态规划 + res = minPathSumDP(grid, n, m); + printf("从左上角到右下角的最小路径和为 %d\n", res); + + // 空间优化后的动态规划 + res = minPathSumDPComp(grid, n, m); + printf("从左上角到右下角的最小路径和为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_dynamic_programming/unbounded_knapsack.c b/codes/c/chapter_dynamic_programming/unbounded_knapsack.c new file mode 100644 index 0000000000..9dbde41278 --- /dev/null +++ b/codes/c/chapter_dynamic_programming/unbounded_knapsack.c @@ -0,0 +1,81 @@ +/** + * File: unbounded_knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最大值 */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 完全背包:动态规划 */ +int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // 释放内存 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 完全背包:空间优化后的动态规划 */ +int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int *dp = calloc(cap + 1, sizeof(int)); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // 释放内存 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int wgt[] = {1, 2, 3}; + int val[] = {5, 11, 15}; + int wgtSize = sizeof(wgt) / sizeof(wgt[0]); + int cap = 4; + + // 动态规划 + int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); + printf("不超过背包容量的最大物品价值为 %d\n", res); + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); + printf("不超过背包容量的最大物品价值为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_graph/CMakeLists.txt b/codes/c/chapter_graph/CMakeLists.txt new file mode 100644 index 0000000000..28f8470f4b --- /dev/null +++ b/codes/c/chapter_graph/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) +add_executable(graph_bfs graph_bfs.c) +add_executable(graph_dfs graph_dfs.c) diff --git a/codes/c/chapter_graph/graph_adjacency_list.c b/codes/c/chapter_graph/graph_adjacency_list.c new file mode 100644 index 0000000000..98584544da --- /dev/null +++ b/codes/c/chapter_graph/graph_adjacency_list.c @@ -0,0 +1,171 @@ +/** + * File: graph_adjacency_list.c + * Created Time: 2023-07-07 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// 假设节点最大数量为 100 +#define MAX_SIZE 100 + +/* 节点结构体 */ +typedef struct AdjListNode { + Vertex *vertex; // 顶点 + struct AdjListNode *next; // 后继节点 +} AdjListNode; + +/* 基于邻接表实现的无向图类 */ +typedef struct { + AdjListNode *heads[MAX_SIZE]; // 节点数组 + int size; // 节点数量 +} GraphAdjList; + +/* 构造函数 */ +GraphAdjList *newGraphAdjList() { + GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); + if (!graph) { + return NULL; + } + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + graph->heads[i] = NULL; + } + return graph; +} + +/* 析构函数 */ +void delGraphAdjList(GraphAdjList *graph) { + for (int i = 0; i < graph->size; i++) { + AdjListNode *cur = graph->heads[i]; + while (cur != NULL) { + AdjListNode *next = cur->next; + if (cur != graph->heads[i]) { + free(cur); + } + cur = next; + } + free(graph->heads[i]->vertex); + free(graph->heads[i]); + } + free(graph); +} + +/* 查找顶点对应的节点 */ +AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { + for (int i = 0; i < graph->size; i++) { + if (graph->heads[i]->vertex == vet) { + return graph->heads[i]; + } + } + return NULL; +} + +/* 添加边辅助函数 */ +void addEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); + node->vertex = vet; + // 头插法 + node->next = head->next; + head->next = node; +} + +/* 添加边 */ +void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL && head1 != head2); + // 添加边 vet1 - vet2 + addEdgeHelper(head1, vet2); + addEdgeHelper(head2, vet1); +} + +/* 删除边辅助函数 */ +void removeEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *pre = head; + AdjListNode *cur = head->next; + // 在链表中搜索 vet 对应节点 + while (cur != NULL && cur->vertex != vet) { + pre = cur; + cur = cur->next; + } + if (cur == NULL) + return; + // 将 vet 对应节点从链表中删除 + pre->next = cur->next; + // 释放内存 + free(cur); +} + +/* 删除边 */ +void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL); + // 删除边 vet1 - vet2 + removeEdgeHelper(head1, head2->vertex); + removeEdgeHelper(head2, head1->vertex); +} + +/* 添加顶点 */ +void addVertex(GraphAdjList *graph, Vertex *vet) { + assert(graph != NULL && graph->size < MAX_SIZE); + AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); + head->vertex = vet; + head->next = NULL; + // 在邻接表中添加一个新链表 + graph->heads[graph->size++] = head; +} + +/* 删除顶点 */ +void removeVertex(GraphAdjList *graph, Vertex *vet) { + AdjListNode *node = findNode(graph, vet); + assert(node != NULL); + // 在邻接表中删除顶点 vet 对应的链表 + AdjListNode *cur = node, *pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + free(pre); + } + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (int i = 0; i < graph->size; i++) { + cur = graph->heads[i]; + pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + if (cur && cur->vertex == vet) { + pre->next = cur->next; + free(cur); + break; + } + } + } + // 将该顶点之后的顶点向前移动,以填补空缺 + int i; + for (i = 0; i < graph->size; i++) { + if (graph->heads[i] == node) + break; + } + for (int j = i; j < graph->size - 1; j++) { + graph->heads[j] = graph->heads[j + 1]; + } + graph->size--; + free(vet); +} + +/* 打印邻接表 */ +void printGraph(const GraphAdjList *graph) { + printf("邻接表 =\n"); + for (int i = 0; i < graph->size; ++i) { + AdjListNode *node = graph->heads[i]; + printf("%d: [", node->vertex->val); + node = node->next; + while (node) { + printf("%d, ", node->vertex->val); + node = node->next; + } + printf("]\n"); + } +} diff --git a/codes/c/chapter_graph/graph_adjacency_list_test.c b/codes/c/chapter_graph/graph_adjacency_list_test.c new file mode 100644 index 0000000000..2995bca3a4 --- /dev/null +++ b/codes/c/chapter_graph/graph_adjacency_list_test.c @@ -0,0 +1,55 @@ +/** + * File: graph_adjacency_list_test.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +/* Driver Code */ +int main() { + int vals[] = {1, 3, 2, 5, 4}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 添加所有顶点和边 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化后,图为\n"); + printGraph(graph); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + addEdge(graph, v[0], v[2]); + printf("\n添加边 1-2 后,图为\n"); + printGraph(graph); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + removeEdge(graph, v[0], v[1]); + printf("\n删除边 1-3 后,图为\n"); + printGraph(graph); + + /* 添加顶点 */ + Vertex *v5 = newVertex(6); + addVertex(graph, v5); + printf("\n添加顶点 6 后,图为\n"); + printGraph(graph); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + removeVertex(graph, v[1]); + printf("\n删除顶点 3 后,图为:\n"); + printGraph(graph); + + // 释放内存 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/codes/c/chapter_graph/graph_adjacency_matrix.c b/codes/c/chapter_graph/graph_adjacency_matrix.c new file mode 100644 index 0000000000..7d621d960b --- /dev/null +++ b/codes/c/chapter_graph/graph_adjacency_matrix.c @@ -0,0 +1,150 @@ +/** + * File: graph_adjacency_matrix.c + * Created Time: 2023-07-06 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// 假设顶点数量最大为 100 +#define MAX_SIZE 100 + +/* 基于邻接矩阵实现的无向图结构体 */ +typedef struct { + int vertices[MAX_SIZE]; + int adjMat[MAX_SIZE][MAX_SIZE]; + int size; +} GraphAdjMat; + +/* 构造函数 */ +GraphAdjMat *newGraphAdjMat() { + GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + for (int j = 0; j < MAX_SIZE; j++) { + graph->adjMat[i][j] = 0; + } + } + return graph; +} + +/* 析构函数 */ +void delGraphAdjMat(GraphAdjMat *graph) { + free(graph); +} + +/* 添加顶点 */ +void addVertex(GraphAdjMat *graph, int val) { + if (graph->size == MAX_SIZE) { + fprintf(stderr, "图的顶点数量已达最大值\n"); + return; + } + // 添加第 n 个顶点,并将第 n 行和列置零 + 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); + } +} + +/* Driver Code */ +int main() { + // 初始化无向图 + GraphAdjMat *graph = newGraphAdjMat(); + int vertices[] = {1, 3, 2, 5, 4}; + for (int i = 0; i < 5; i++) { + addVertex(graph, vertices[i]); + } + int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + for (int i = 0; i < 6; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化后,图为\n"); + printGraphAdjMat(graph); + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + addEdge(graph, 0, 2); + printf("\n添加边 1-2 后,图为\n"); + printGraphAdjMat(graph); + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + removeEdge(graph, 0, 1); + printf("\n删除边 1-3 后,图为\n"); + printGraphAdjMat(graph); + + /* 添加顶点 */ + addVertex(graph, 6); + printf("\n添加顶点 6 后,图为\n"); + printGraphAdjMat(graph); + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + removeVertex(graph, 1); + printf("\n删除顶点 3 后,图为\n"); + printGraphAdjMat(graph); + + // 释放内存 + delGraphAdjMat(graph); + + return 0; +} diff --git a/codes/c/chapter_graph/graph_bfs.c b/codes/c/chapter_graph/graph_bfs.c new file mode 100644 index 0000000000..719cd886b8 --- /dev/null +++ b/codes/c/chapter_graph/graph_bfs.c @@ -0,0 +1,116 @@ +/** + * File: graph_bfs.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// 假设节点最大数量为 100 +#define MAX_SIZE 100 + +/* 节点队列结构体 */ +typedef struct { + Vertex *vertices[MAX_SIZE]; + int front, rear, size; +} Queue; + +/* 构造函数 */ +Queue *newQueue() { + Queue *q = (Queue *)malloc(sizeof(Queue)); + q->front = q->rear = q->size = 0; + return q; +} + +/* 判断队列是否为空 */ +int isEmpty(Queue *q) { + return q->size == 0; +} + +/* 入队操作 */ +void enqueue(Queue *q, Vertex *vet) { + q->vertices[q->rear] = vet; + q->rear = (q->rear + 1) % MAX_SIZE; + q->size++; +} + +/* 出队操作 */ +Vertex *dequeue(Queue *q) { + Vertex *vet = q->vertices[q->front]; + q->front = (q->front + 1) % MAX_SIZE; + q->size--; + return vet; +} + +/* 检查顶点是否已被访问 */ +int isVisited(Vertex **visited, int size, Vertex *vet) { + // 遍历查找节点,使用 O(n) 时间 + for (int i = 0; i < size; i++) { + if (visited[i] == vet) + return 1; + } + return 0; +} + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { + // 队列用于实现 BFS + Queue *queue = newQueue(); + enqueue(queue, startVet); + visited[(*visitedSize)++] = startVet; + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!isEmpty(queue)) { + Vertex *vet = dequeue(queue); // 队首顶点出队 + res[(*resSize)++] = vet; // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳过已被访问的顶点 + if (!isVisited(visited, *visitedSize, node->vertex)) { + enqueue(queue, node->vertex); // 只入队未访问的顶点 + visited[(*visitedSize)++] = node->vertex; // 标记该顶点已被访问 + } + node = node->next; + } + } + // 释放内存 + free(queue); +} + +/* Driver Code */ +int main() { + // 初始化无向图 + int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, + {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 添加所有顶点和边 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化后,图为\n"); + printGraph(graph); + + // 广度优先遍历 + // 顶点遍历序列 + Vertex *res[MAX_SIZE]; + int resSize = 0; + // 用于记录已被访问过的顶点 + Vertex *visited[MAX_SIZE]; + int visitedSize = 0; + graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); + printf("\n广度优先遍历(BFS)顶点序列为\n"); + printArray(vetsToVals(res, resSize), resSize); + + // 释放内存 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/codes/c/chapter_graph/graph_dfs.c b/codes/c/chapter_graph/graph_dfs.c new file mode 100644 index 0000000000..465f76b7af --- /dev/null +++ b/codes/c/chapter_graph/graph_dfs.c @@ -0,0 +1,75 @@ +/** + * File: graph_dfs.c + * Created Time: 2023-07-13 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// 假设节点最大数量为 100 +#define MAX_SIZE 100 + +/* 检查顶点是否已被访问 */ +int isVisited(Vertex **res, int size, Vertex *vet) { + // 遍历查找节点,使用 O(n) 时间 + for (int i = 0; i < size; i++) { + if (res[i] == vet) { + return 1; + } + } + return 0; +} + +/* 深度优先遍历辅助函数 */ +void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { + // 记录访问顶点 + res[(*resSize)++] = vet; + // 遍历该顶点的所有邻接顶点 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳过已被访问的顶点 + if (!isVisited(res, *resSize, node->vertex)) { + // 递归访问邻接顶点 + dfs(graph, res, resSize, node->vertex); + } + node = node->next; + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { + dfs(graph, res, resSize, startVet); +} + +/* Driver Code */ +int main() { + // 初始化无向图 + int vals[] = {0, 1, 2, 3, 4, 5, 6}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 添加所有顶点和边 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化后,图为\n"); + printGraph(graph); + + // 深度优先遍历 + Vertex *res[MAX_SIZE]; + int resSize = 0; + graphDFS(graph, v[0], res, &resSize); + printf("\n深度优先遍历(DFS)顶点序列为\n"); + printArray(vetsToVals(res, resSize), resSize); + + // 释放内存 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/codes/c/chapter_greedy/CMakeLists.txt b/codes/c/chapter_greedy/CMakeLists.txt new file mode 100644 index 0000000000..b8e6ca4250 --- /dev/null +++ b/codes/c/chapter_greedy/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(coin_change_greedy coin_change_greedy.c) +add_executable(fractional_knapsack fractional_knapsack.c) +add_executable(max_capacity max_capacity.c) +add_executable(max_product_cutting max_product_cutting.c) + +if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_link_libraries(max_product_cutting m) +endif() diff --git a/codes/c/chapter_greedy/coin_change_greedy.c b/codes/c/chapter_greedy/coin_change_greedy.c new file mode 100644 index 0000000000..b4bd2748e7 --- /dev/null +++ b/codes/c/chapter_greedy/coin_change_greedy.c @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.c + * Created Time: 2023-09-07 + * Author: lwbaptx (lwbaptx@gmail.com) + */ + +#include "../utils/common.h" + +/* 零钱兑换:贪心 */ +int coinChangeGreedy(int *coins, int size, int amt) { + // 假设 coins 列表有序 + int i = size - 1; + int count = 0; + // 循环进行贪心选择,直到无剩余金额 + while (amt > 0) { + // 找到小于且最接近剩余金额的硬币 + while (i > 0 && coins[i] > amt) { + i--; + } + // 选择 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,则返回 -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // 贪心:能够保证找到全局最优解 + int coins1[6] = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins1, 6, amt); + printf("\ncoins = "); + printArray(coins1, 6); + printf("amt = %d\n", amt); + printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res); + + // 贪心:无法保证找到全局最优解 + int coins2[3] = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins2, 3, amt); + printf("\ncoins = "); + printArray(coins2, 3); + printf("amt = %d\n", amt); + printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res); + printf("实际上需要的最少数量为 3 ,即 20 + 20 + 20\n"); + + // 贪心:无法保证找到全局最优解 + int coins3[3] = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins3, 3, amt); + printf("\ncoins = "); + printArray(coins3, 3); + printf("amt = %d\n", amt); + printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res); + printf("实际上需要的最少数量为 2 ,即 49 + 49\n"); + + return 0; +} diff --git a/codes/c/chapter_greedy/fractional_knapsack.c b/codes/c/chapter_greedy/fractional_knapsack.c new file mode 100644 index 0000000000..d98a04e421 --- /dev/null +++ b/codes/c/chapter_greedy/fractional_knapsack.c @@ -0,0 +1,60 @@ +/** + * File: fractional_knapsack.c + * Created Time: 2023-09-14 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 物品 */ +typedef struct { + int w; // 物品重量 + int v; // 物品价值 +} Item; + +/* 按照价值密度排序 */ +int sortByValueDensity(const void *a, const void *b) { + Item *t1 = (Item *)a; + Item *t2 = (Item *)b; + return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; +} + +/* 分数背包:贪心 */ +float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + Item *items = malloc(sizeof(Item) * itemCount); + for (int i = 0; i < itemCount; i++) { + items[i] = (Item){.w = wgt[i], .v = val[i]}; + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); + // 循环贪心选择 + float res = 0.0; + for (int i = 0; i < itemCount; i++) { + if (items[i].w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += items[i].v; + cap -= items[i].w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (float)cap / items[i].w * items[i].v; + cap = 0; + break; + } + } + free(items); + return res; +} + +/* Driver Code */ +int main(void) { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int capacity = 50; + + // 贪心算法 + float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); + printf("不超过背包容量的最大物品价值为 %0.2f\n", res); + + return 0; +} diff --git a/codes/c/chapter_greedy/max_capacity.c b/codes/c/chapter_greedy/max_capacity.c new file mode 100644 index 0000000000..023819cfff --- /dev/null +++ b/codes/c/chapter_greedy/max_capacity.c @@ -0,0 +1,49 @@ +/** + * File: max_capacity.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} +/* 求最大值 */ +int myMax(int a, int b) { + return a < b ? a : b; +} + +/* 最大容量:贪心 */ +int maxCapacity(int ht[], int htLength) { + // 初始化 i, j,使其分列数组两端 + int i = 0; + int j = htLength - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int capacity = myMin(ht[i], ht[j]) * (j - i); + res = myMax(res, capacity); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main(void) { + int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 贪心算法 + int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); + printf("最大容量为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_greedy/max_product_cutting.c b/codes/c/chapter_greedy/max_product_cutting.c new file mode 100644 index 0000000000..0278143c46 --- /dev/null +++ b/codes/c/chapter_greedy/max_product_cutting.c @@ -0,0 +1,38 @@ +/** + * File: max_product_cutting.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 最大切分乘积:贪心 */ +int maxProductCutting(int n) { + // 当 n <= 3 时,必须切分出一个 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return pow(3, a); +} + +/* Driver Code */ +int main(void) { + int n = 58; + // 贪心算法 + int res = maxProductCutting(n); + printf("最大切分乘积为 %d\n", res); + + return 0; +} diff --git a/codes/c/chapter_hashing/CMakeLists.txt b/codes/c/chapter_hashing/CMakeLists.txt new file mode 100644 index 0000000000..9ac951ae43 --- /dev/null +++ b/codes/c/chapter_hashing/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array_hash_map array_hash_map.c) +add_executable(hash_map_chaining hash_map_chaining.c) +add_executable(hash_map_open_addressing hash_map_open_addressing.c) +add_executable(simple_hash simple_hash.c) diff --git a/codes/c/chapter_hashing/array_hash_map.c b/codes/c/chapter_hashing/array_hash_map.c new file mode 100644 index 0000000000..17044934f6 --- /dev/null +++ b/codes/c/chapter_hashing/array_hash_map.c @@ -0,0 +1,215 @@ +/** + * File: array_hash_map.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 哈希表默认大小 */ +#define MAX_SIZE 100 + +/* 键值对 int->string */ +typedef struct { + int key; + char *val; +} Pair; + +/* 键值对的集合 */ +typedef struct { + void *set; + int len; +} MapSet; + +/* 基于数组实现的哈希表 */ +typedef struct { + Pair *buckets[MAX_SIZE]; +} ArrayHashMap; + +/* 构造函数 */ +ArrayHashMap *newArrayHashMap() { + ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); + for (int i=0; i < MAX_SIZE; i++) { + hmap->buckets[i] = NULL; + } + return hmap; +} + +/* 析构函数 */ +void delArrayHashMap(ArrayHashMap *hmap) { + for (int i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + free(hmap->buckets[i]->val); + free(hmap->buckets[i]); + } + } + free(hmap); +} + +/* 哈希函数 */ +int hashFunc(int key) { + int index = key % MAX_SIZE; + return index; +} + +/* 查询操作 */ +const char *get(const ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + const Pair *Pair = hmap->buckets[index]; + if (Pair == NULL) + return NULL; + return Pair->val; +} + +/* 添加操作 */ +void put(ArrayHashMap *hmap, const int key, const char *val) { + Pair *Pair = malloc(sizeof(Pair)); + Pair->key = key; + Pair->val = malloc(strlen(val) + 1); + strcpy(Pair->val, val); + + int index = hashFunc(key); + hmap->buckets[index] = Pair; +} + +/* 删除操作 */ +void removeItem(ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + free(hmap->buckets[index]->val); + free(hmap->buckets[index]); + hmap->buckets[index] = NULL; +} + +/* 获取所有键值对 */ +void pairSet(ArrayHashMap *hmap, MapSet *set) { + Pair *entries; + int i = 0, index = 0; + int total = 0; + /* 统计有效键值对数量 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + entries = malloc(sizeof(Pair) * total); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + entries[index].key = hmap->buckets[i]->key; + entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); + strcpy(entries[index].val, hmap->buckets[i]->val); + index++; + } + } + set->set = entries; + set->len = total; +} + +/* 获取所有键 */ +void keySet(ArrayHashMap *hmap, MapSet *set) { + int *keys; + int i = 0, index = 0; + int total = 0; + /* 统计有效键值对数量 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + keys = malloc(total * sizeof(int)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + keys[index] = hmap->buckets[i]->key; + index++; + } + } + set->set = keys; + set->len = total; +} + +/* 获取所有值 */ +void valueSet(ArrayHashMap *hmap, MapSet *set) { + char **vals; + int i = 0, index = 0; + int total = 0; + /* 统计有效键值对数量 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + vals = malloc(total * sizeof(char *)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + vals[index] = hmap->buckets[i]->val; + index++; + } + } + set->set = vals; + set->len = total; +} + +/* 打印哈希表 */ +void print(ArrayHashMap *hmap) { + int i; + MapSet set; + pairSet(hmap, &set); + Pair *entries = (Pair *)set.set; + for (i = 0; i < set.len; i++) { + printf("%d -> %s\n", entries[i].key, entries[i].val); + } + free(set.set); +} + +/* Driver Code */ +int main() { + /* 初始化哈希表 */ + ArrayHashMap *hmap = newArrayHashMap(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + put(hmap, 12836, "小哈"); + put(hmap, 15937, "小啰"); + put(hmap, 16750, "小算"); + put(hmap, 13276, "小法"); + put(hmap, 10583, "小鸭"); + printf("\n添加完成后,哈希表为\nKey -> Value\n"); + print(hmap); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + const char *name = get(hmap, 15937); + printf("\n输入学号 15937 ,查询到姓名 %s\n", name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + removeItem(hmap, 10583); + printf("\n删除 10583 后,哈希表为\nKey -> Value\n"); + print(hmap); + + /* 遍历哈希表 */ + int i; + + printf("\n遍历键值对 Key->Value\n"); + print(hmap); + + MapSet set; + + keySet(hmap, &set); + int *keys = (int *)set.set; + printf("\n单独遍历键 Key\n"); + for (i = 0; i < set.len; i++) { + printf("%d\n", keys[i]); + } + free(set.set); + + valueSet(hmap, &set); + char **vals = (char **)set.set; + printf("\n单独遍历键 Value\n"); + for (i = 0; i < set.len; i++) { + printf("%s\n", vals[i]); + } + free(set.set); + + delArrayHashMap(hmap); + return 0; +} diff --git a/codes/c/chapter_hashing/hash_map_chaining.c b/codes/c/chapter_hashing/hash_map_chaining.c new file mode 100644 index 0000000000..de87cc8bc2 --- /dev/null +++ b/codes/c/chapter_hashing/hash_map_chaining.c @@ -0,0 +1,213 @@ +/** + * File: hash_map_chaining.c + * Created Time: 2023-10-13 + * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) + */ + +#include +#include +#include + +// 假设 val 最大长度为 100 +#define MAX_SIZE 100 + +/* 键值对 */ +typedef struct { + int key; + char val[MAX_SIZE]; +} Pair; + +/* 链表节点 */ +typedef struct Node { + Pair *pair; + struct Node *next; +} Node; + +/* 链式地址哈希表 */ +typedef struct { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + Node **buckets; // 桶数组 +} HashMapChaining; + +/* 构造函数 */ +HashMapChaining *newHashMapChaining() { + HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + return hashMap; +} + +/* 析构函数 */ +void delHashMapChaining(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + while (cur) { + Node *tmp = cur; + cur = cur->next; + free(tmp->pair); + free(tmp); + } + } + free(hashMap->buckets); + free(hashMap); +} + +/* 哈希函数 */ +int hashFunc(HashMapChaining *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 负载因子 */ +double loadFactor(HashMapChaining *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* 查询操作 */ +char *get(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + // 遍历桶,若找到 key ,则返回对应 val + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + return cur->pair->val; + } + cur = cur->next; + } + return ""; // 若未找到 key ,则返回空字符串 +} + +/* 添加操作 */ +void put(HashMapChaining *hashMap, int key, const char *val); + +/* 扩容哈希表 */ +void extend(HashMapChaining *hashMap) { + // 暂存原哈希表 + int oldCapacity = hashMap->capacity; + Node **oldBuckets = hashMap->buckets; + // 初始化扩容后的新哈希表 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + hashMap->size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (int i = 0; i < oldCapacity; i++) { + Node *cur = oldBuckets[i]; + while (cur) { + put(hashMap, cur->pair->key, cur->pair->val); + Node *temp = cur; + cur = cur->next; + // 释放内存 + free(temp->pair); + free(temp); + } + } + + free(oldBuckets); +} + +/* 添加操作 */ +void put(HashMapChaining *hashMap, int key, const char *val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + int index = hashFunc(hashMap, key); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + strcpy(cur->pair->val, val); // 若遇到指定 key ,则更新对应 val 并返回 + return; + } + cur = cur->next; + } + // 若无该 key ,则将键值对添加至链表头部 + Pair *newPair = (Pair *)malloc(sizeof(Pair)); + newPair->key = key; + strcpy(newPair->val, val); + Node *newNode = (Node *)malloc(sizeof(Node)); + newNode->pair = newPair; + newNode->next = hashMap->buckets[index]; + hashMap->buckets[index] = newNode; + hashMap->size++; +} + +/* 删除操作 */ +void removeItem(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + Node *cur = hashMap->buckets[index]; + Node *pre = NULL; + while (cur) { + if (cur->pair->key == key) { + // 从中删除键值对 + if (pre) { + pre->next = cur->next; + } else { + hashMap->buckets[index] = cur->next; + } + // 释放内存 + free(cur->pair); + free(cur); + hashMap->size--; + return; + } + pre = cur; + cur = cur->next; + } +} + +/* 打印哈希表 */ +void print(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + printf("["); + while (cur) { + printf("%d -> %s, ", cur->pair->key, cur->pair->val); + cur = cur->next; + } + printf("]\n"); + } +} + +/* Driver Code */ +int main() { + /* 初始化哈希表 */ + HashMapChaining *hashMap = newHashMapChaining(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + put(hashMap, 12836, "小哈"); + put(hashMap, 15937, "小啰"); + put(hashMap, 16750, "小算"); + put(hashMap, 13276, "小法"); + put(hashMap, 10583, "小鸭"); + printf("\n添加完成后,哈希表为\nKey -> Value\n"); + print(hashMap); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + char *name = get(hashMap, 13276); + printf("\n输入学号 13276 ,查询到姓名 %s\n", name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + removeItem(hashMap, 12836); + printf("\n删除学号 12836 后,哈希表为\nKey -> Value\n"); + print(hashMap); + + /* 释放哈希表空间 */ + delHashMapChaining(hashMap); + + return 0; +} diff --git a/codes/c/chapter_hashing/hash_map_open_addressing.c b/codes/c/chapter_hashing/hash_map_open_addressing.c new file mode 100644 index 0000000000..e4e00fb08d --- /dev/null +++ b/codes/c/chapter_hashing/hash_map_open_addressing.c @@ -0,0 +1,211 @@ +/** + * File: hash_map_open_addressing.c + * Created Time: 2023-10-6 + * Author: lclc6 (w1929522410@163.com) + */ + +#include "../utils/common.h" + +/* 开放寻址哈希表 */ +typedef struct { + int key; + char *val; +} Pair; + +/* 开放寻址哈希表 */ +typedef struct { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + Pair **buckets; // 桶数组 + Pair *TOMBSTONE; // 删除标记 +} HashMapOpenAddressing; + +// 函数声明 +void extend(HashMapOpenAddressing *hashMap); + +/* 构造函数 */ +HashMapOpenAddressing *newHashMapOpenAddressing() { + HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); + hashMap->TOMBSTONE->key = -1; + hashMap->TOMBSTONE->val = "-1"; + + return hashMap; +} + +/* 析构函数 */ +void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + free(pair->val); + free(pair); + } + } + free(hashMap->buckets); + free(hashMap->TOMBSTONE); + free(hashMap); +} + +/* 哈希函数 */ +int hashFunc(HashMapOpenAddressing *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 负载因子 */ +double loadFactor(HashMapOpenAddressing *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* 搜索 key 对应的桶索引 */ +int findBucket(HashMapOpenAddressing *hashMap, int key) { + int index = hashFunc(hashMap, key); + int firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (hashMap->buckets[index] != NULL) { + // 若遇到 key ,返回对应的桶索引 + if (hashMap->buckets[index]->key == key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone != -1) { + hashMap->buckets[firstTombstone] = hashMap->buckets[index]; + hashMap->buckets[index] = hashMap->TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % hashMap->capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone == -1 ? index : firstTombstone; +} + +/* 查询操作 */ +char *get(HashMapOpenAddressing *hashMap, int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(hashMap, key); + // 若找到键值对,则返回对应 val + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + return hashMap->buckets[index]->val; + } + // 若键值对不存在,则返回空字符串 + return ""; +} + +/* 添加操作 */ +void put(HashMapOpenAddressing *hashMap, int key, char *val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + // 搜索 key 对应的桶索引 + int index = findBucket(hashMap, key); + // 若找到键值对,则覆盖 val 并返回 + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + free(hashMap->buckets[index]->val); + hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(hashMap->buckets[index]->val, val); + hashMap->buckets[index]->val[strlen(val)] = '\0'; + return; + } + // 若键值对不存在,则添加该键值对 + Pair *pair = (Pair *)malloc(sizeof(Pair)); + pair->key = key; + pair->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(pair->val, val); + pair->val[strlen(val)] = '\0'; + + hashMap->buckets[index] = pair; + hashMap->size++; +} + +/* 删除操作 */ +void removeItem(HashMapOpenAddressing *hashMap, int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(hashMap, key); + // 若找到键值对,则用删除标记覆盖它 + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + Pair *pair = hashMap->buckets[index]; + free(pair->val); + free(pair); + hashMap->buckets[index] = hashMap->TOMBSTONE; + hashMap->size--; + } +} + +/* 扩容哈希表 */ +void extend(HashMapOpenAddressing *hashMap) { + // 暂存原哈希表 + Pair **bucketsTmp = hashMap->buckets; + int oldCapacity = hashMap->capacity; + // 初始化扩容后的新哈希表 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (int i = 0; i < oldCapacity; i++) { + Pair *pair = bucketsTmp[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + put(hashMap, pair->key, pair->val); + free(pair->val); + free(pair); + } + } + free(bucketsTmp); +} + +/* 打印哈希表 */ +void print(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair == NULL) { + printf("NULL\n"); + } else if (pair == hashMap->TOMBSTONE) { + printf("TOMBSTONE\n"); + } else { + printf("%d -> %s\n", pair->key, pair->val); + } + } +} + +/* Driver Code */ +int main() { + // 初始化哈希表 + HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); + + // 添加操作 + // 在哈希表中添加键值对 (key, val) + put(hashmap, 12836, "小哈"); + put(hashmap, 15937, "小啰"); + put(hashmap, 16750, "小算"); + put(hashmap, 13276, "小法"); + put(hashmap, 10583, "小鸭"); + printf("\n添加完成后,哈希表为\nKey -> Value\n"); + print(hashmap); + + // 查询操作 + // 向哈希表中输入键 key ,得到值 val + char *name = get(hashmap, 13276); + printf("\n输入学号 13276 ,查询到姓名 %s\n", name); + + // 删除操作 + // 在哈希表中删除键值对 (key, val) + removeItem(hashmap, 16750); + printf("\n删除 16750 后,哈希表为\nKey -> Value\n"); + print(hashmap); + + // 销毁哈希表 + delHashMapOpenAddressing(hashmap); + return 0; +} diff --git a/codes/c/chapter_hashing/simple_hash.c b/codes/c/chapter_hashing/simple_hash.c new file mode 100644 index 0000000000..a4ee0737f5 --- /dev/null +++ b/codes/c/chapter_hashing/simple_hash.c @@ -0,0 +1,68 @@ +/** + * File: simple_hash.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 加法哈希 */ +int addHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* 乘法哈希 */ +int mulHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (31 * hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* 异或哈希 */ +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; +} + +/* Driver Code */ +int main() { + char *key = "Hello 算法"; + + int hash = addHash(key); + printf("加法哈希值为 %d\n", hash); + + hash = mulHash(key); + printf("乘法哈希值为 %d\n", hash); + + hash = xorHash(key); + printf("异或哈希值为 %d\n", hash); + + hash = rotHash(key); + printf("旋转哈希值为 %d\n", hash); + + return 0; +} diff --git a/codes/c/chapter_heap/CMakeLists.txt b/codes/c/chapter_heap/CMakeLists.txt new file mode 100644 index 0000000000..357ec702c3 --- /dev/null +++ b/codes/c/chapter_heap/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(my_heap_test my_heap_test.c) +add_executable(top_k top_k.c) diff --git a/codes/c/chapter_heap/my_heap.c b/codes/c/chapter_heap/my_heap.c new file mode 100644 index 0000000000..308a3cf1cc --- /dev/null +++ b/codes/c/chapter_heap/my_heap.c @@ -0,0 +1,152 @@ +/** + * File: my_heap.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 大顶堆 */ +typedef struct { + // size 代表的是实际元素的个数 + int size; + // 使用预先分配内存的数组,避免扩容 + int data[MAX_SIZE]; +} MaxHeap; + +// 函数声明 +void siftDown(MaxHeap *maxHeap, int i); +void siftUp(MaxHeap *maxHeap, int i); +int parent(MaxHeap *maxHeap, int i); + +/* 构造函数,根据切片建堆 */ +MaxHeap *newMaxHeap(int nums[], int size) { + // 所有元素入堆 + MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); + maxHeap->size = size; + memcpy(maxHeap->data, nums, size * sizeof(int)); + for (int i = parent(maxHeap, size - 1); i >= 0; i--) { + // 堆化除叶节点以外的其他所有节点 + siftDown(maxHeap, i); + } + return maxHeap; +} + +/* 析构函数 */ +void delMaxHeap(MaxHeap *maxHeap) { + // 释放内存 + free(maxHeap); +} + +/* 获取左子节点的索引 */ +int left(MaxHeap *maxHeap, int i) { + return 2 * i + 1; +} + +/* 获取右子节点的索引 */ +int right(MaxHeap *maxHeap, int i) { + return 2 * i + 2; +} + +/* 获取父节点的索引 */ +int parent(MaxHeap *maxHeap, int i) { + return (i - 1) / 2; // 向下取整 +} + +/* 交换元素 */ +void swap(MaxHeap *maxHeap, int i, int j) { + int temp = maxHeap->data[i]; + maxHeap->data[i] = maxHeap->data[j]; + maxHeap->data[j] = temp; +} + +/* 获取堆大小 */ +int size(MaxHeap *maxHeap) { + return maxHeap->size; +} + +/* 判断堆是否为空 */ +int isEmpty(MaxHeap *maxHeap) { + return maxHeap->size == 0; +} + +/* 访问堆顶元素 */ +int peek(MaxHeap *maxHeap) { + return maxHeap->data[0]; +} + +/* 元素入堆 */ +void push(MaxHeap *maxHeap, int val) { + // 默认情况下,不应该添加这么多节点 + if (maxHeap->size == MAX_SIZE) { + printf("heap is full!"); + return; + } + // 添加节点 + maxHeap->data[maxHeap->size] = val; + maxHeap->size++; + + // 从底至顶堆化 + siftUp(maxHeap, maxHeap->size - 1); +} + +/* 元素出堆 */ +int pop(MaxHeap *maxHeap) { + // 判空处理 + if (isEmpty(maxHeap)) { + printf("heap is empty!"); + return INT_MAX; + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(maxHeap, 0, size(maxHeap) - 1); + // 删除节点 + int val = maxHeap->data[maxHeap->size - 1]; + maxHeap->size--; + // 从顶至底堆化 + siftDown(maxHeap, 0); + + // 返回堆顶元素 + return val; +} + +/* 从节点 i 开始,从顶至底堆化 */ +void siftDown(MaxHeap *maxHeap, int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 max + int l = left(maxHeap, i); + int r = right(maxHeap, i); + int max = i; + if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { + max = l; + } + if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { + max = r; + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (max == i) { + break; + } + // 交换两节点 + swap(maxHeap, i, max); + // 循环向下堆化 + i = max; + } +} + +/* 从节点 i 开始,从底至顶堆化 */ +void siftUp(MaxHeap *maxHeap, int i) { + while (true) { + // 获取节点 i 的父节点 + int p = parent(maxHeap, i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { + break; + } + // 交换两节点 + swap(maxHeap, i, p); + // 循环向上堆化 + i = p; + } +} diff --git a/codes/c/chapter_heap/my_heap_test.c b/codes/c/chapter_heap/my_heap_test.c new file mode 100644 index 0000000000..902c15fde2 --- /dev/null +++ b/codes/c/chapter_heap/my_heap_test.c @@ -0,0 +1,41 @@ +/** + * File: my_heap_test.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "my_heap.c" + +/* Driver Code */ +int main() { + /* 初始化堆 */ + // 初始化大顶堆 + int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); + printf("输入数组并建堆后\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* 获取堆顶元素 */ + printf("\n堆顶元素为 %d\n", peek(maxHeap)); + + /* 元素入堆 */ + push(maxHeap, 7); + printf("\n元素 7 入堆后\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* 堆顶元素出堆 */ + int top = pop(maxHeap); + printf("\n堆顶元素 %d 出堆后\n", top); + printHeap(maxHeap->data, maxHeap->size); + + /* 获取堆大小 */ + printf("\n堆元素数量为 %d\n", size(maxHeap)); + + /* 判断堆是否为空 */ + printf("\n堆是否为空 %d\n", isEmpty(maxHeap)); + + // 释放内存 + delMaxHeap(maxHeap); + + return 0; +} diff --git a/codes/c/chapter_heap/top_k.c b/codes/c/chapter_heap/top_k.c new file mode 100644 index 0000000000..2374b22e01 --- /dev/null +++ b/codes/c/chapter_heap/top_k.c @@ -0,0 +1,73 @@ +/** + * File: top_k.c + * Created Time: 2023-10-26 + * Author: krahets (krahets163.com) + */ + +#include "my_heap.c" + +/* 元素入堆 */ +void pushMinHeap(MaxHeap *maxHeap, int val) { + // 元素取反 + push(maxHeap, -val); +} + +/* 元素出堆 */ +int popMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -pop(maxHeap); +} + +/* 访问堆顶元素 */ +int peekMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -peek(maxHeap); +} + +/* 取出堆中元素 */ +int *getMinHeap(MaxHeap *maxHeap) { + // 将堆中所有元素取反并存入 res 数组 + int *res = (int *)malloc(maxHeap->size * sizeof(int)); + for (int i = 0; i < maxHeap->size; i++) { + res[i] = -maxHeap->data[i]; + } + return res; +} + +// 基于堆查找数组中最大的 k 个元素的函数 +int *topKHeap(int *nums, int sizeNums, int k) { + // 初始化小顶堆 + // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 + int *empty = (int *)malloc(0); + MaxHeap *maxHeap = newMaxHeap(empty, 0); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < sizeNums; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + int *res = getMinHeap(maxHeap); + // 释放内存 + delMaxHeap(maxHeap); + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 7, 6, 3, 2}; + int k = 3; + int sizeNums = sizeof(nums) / sizeof(nums[0]); + + int *res = topKHeap(nums, sizeNums, k); + printf("最大的 %d 个元素为: ", k); + printArray(res, k); + + free(res); + return 0; +} diff --git a/codes/c/chapter_searching/CMakeLists.txt b/codes/c/chapter_searching/CMakeLists.txt new file mode 100644 index 0000000000..7b2a152d50 --- /dev/null +++ b/codes/c/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.c) +add_executable(two_sum two_sum.c) +add_executable(binary_search_edge binary_search_edge.c) +add_executable(binary_search_insertion binary_search_insertion.c) diff --git a/codes/c/chapter_searching/binary_search.c b/codes/c/chapter_searching/binary_search.c new file mode 100644 index 0000000000..c09aa3f215 --- /dev/null +++ b/codes/c/chapter_searching/binary_search.c @@ -0,0 +1,59 @@ +/** + * File: binary_search.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 二分查找(双闭区间) */ +int binarySearch(int *nums, int len, int target) { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + int i = 0, j = len - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 + j = m - 1; + else // 找到目标元素,返回其索引 + return m; + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* 二分查找(左闭右开区间) */ +int binarySearchLCRO(int *nums, int len, int target) { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + 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; +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* 二分查找(双闭区间) */ + int index = binarySearch(nums, 10, target); + printf("目标元素 6 的索引 = %d\n", index); + + /* 二分查找(左闭右开区间) */ + index = binarySearchLCRO(nums, 10, target); + printf("目标元素 6 的索引 = %d\n", index); + + return 0; +} diff --git a/codes/c/chapter_searching/binary_search_edge.c b/codes/c/chapter_searching/binary_search_edge.c new file mode 100644 index 0000000000..994f2a913f --- /dev/null +++ b/codes/c/chapter_searching/binary_search_edge.c @@ -0,0 +1,67 @@ +/** + * File: binary_search_edge.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分查找插入点(存在重复元素) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i; +} + +/* 二分查找最左一个 target */ +int binarySearchLeftEdge(int *nums, int numSize, int target) { + // 等价于查找 target 的插入点 + int i = binarySearchInsertion(nums, numSize, target); + // 未找到 target ,返回 -1 + if (i == numSize || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分查找最右一个 target */ +int binarySearchRightEdge(int *nums, int numSize, int target) { + // 转化为查找最左一个 target + 1 + int i = binarySearchInsertion(nums, numSize, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +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; +} \ No newline at end of file diff --git a/codes/c/chapter_searching/binary_search_insertion.c b/codes/c/chapter_searching/binary_search_insertion.c new file mode 100644 index 0000000000..48d2025d35 --- /dev/null +++ b/codes/c/chapter_searching/binary_search_insertion.c @@ -0,0 +1,68 @@ +/** + * File: binary_search_insertion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分查找插入点(无重复元素) */ +int binarySearchInsertionSimple(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入点 m + } + } + // 未找到 target ,返回插入点 i + return i; +} + +/* 二分查找插入点(存在重复元素) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i; +} + +/* Driver Code */ +int main() { + // 无重复元素的数组 + int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + printf("\n数组 nums = "); + printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); + // 二分查找插入点 + int targets1[] = {6, 9}; + for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { + int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); + printf("元素 %d 的插入点的索引为 %d\n", targets1[i], index); + } + + // 包含重复元素的数组 + int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\n数组 nums = "); + printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); + // 二分查找插入点 + int targets2[] = {2, 6, 20}; + for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { + int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); + printf("元素 %d 的插入点的索引为 %d\n", targets2[i], index); + } + + return 0; +} diff --git a/codes/c/chapter_searching/two_sum.c b/codes/c/chapter_searching/two_sum.c new file mode 100644 index 0000000000..953515d990 --- /dev/null +++ b/codes/c/chapter_searching/two_sum.c @@ -0,0 +1,86 @@ +/** + * File: two_sum.c + * Created Time: 2023-01-19 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 方法一:暴力枚举 */ +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; + } +} + +/* 方法二:辅助哈希表 */ +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; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + int nums[] = {2, 7, 11, 15}; + int target = 13; + // ====== Driver Code ====== + int returnSize; + int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); + // 方法一 + printf("方法一 res = "); + printArray(res, returnSize); + + // 方法二 + res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); + printf("方法二 res = "); + printArray(res, returnSize); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_sorting/CMakeLists.txt b/codes/c/chapter_sorting/CMakeLists.txt index 192e1cb974..88756b4c94 100644 --- a/codes/c/chapter_sorting/CMakeLists.txt +++ b/codes/c/chapter_sorting/CMakeLists.txt @@ -1,2 +1,9 @@ add_executable(bubble_sort bubble_sort.c) -add_executable(insertion_sort insertion_sort.c) \ No newline at end of file +add_executable(insertion_sort insertion_sort.c) +add_executable(quick_sort quick_sort.c) +add_executable(counting_sort counting_sort.c) +add_executable(radix_sort radix_sort.c) +add_executable(merge_sort merge_sort.c) +add_executable(heap_sort heap_sort.c) +add_executable(bucket_sort bucket_sort.c) +add_executable(selection_sort selection_sort.c) diff --git a/codes/c/chapter_sorting/bubble_sort.c b/codes/c/chapter_sorting/bubble_sort.c index 51bab6c64a..223c2d016e 100644 --- a/codes/c/chapter_sorting/bubble_sort.c +++ b/codes/c/chapter_sorting/bubble_sort.c @@ -4,18 +4,15 @@ * Author: Listening (https://github.com/L-Super) */ -#include "../include/include.h" +#include "../utils/common.h" /* 冒泡排序 */ void bubbleSort(int nums[], int size) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = 0; i < size - 1; i++) - { - // 内循环:冒泡操作 - for (int j = 0; j < size - 1 - i; j++) - { - if (nums[j] > nums[j + 1]) - { + // 外循环:未排序区间为 [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; @@ -26,15 +23,12 @@ void bubbleSort(int nums[], int size) { /* 冒泡排序(标志优化)*/ void bubbleSortWithFlag(int nums[], int size) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = 0; i < size - 1; i++) - { + // 外循环:未排序区间为 [0, i] + for (int i = size - 1; i > 0; i--) { bool flag = false; - // 内循环:冒泡操作 - for (int j = 0; j < size - 1 - i; j++) - { - if (nums[j] > nums[j + 1]) - { + // 内循环:将未排序区间 [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; @@ -46,22 +40,20 @@ void bubbleSortWithFlag(int nums[], int size) { } } - /* Driver Code */ int main() { int nums[6] = {4, 1, 3, 1, 5, 2}; printf("冒泡排序后: "); bubbleSort(nums, 6); - for (int i = 0; i < 6; i++) - { + for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } + int nums1[6] = {4, 1, 3, 1, 5, 2}; printf("\n优化版冒泡排序后: "); - bubbleSortWithFlag(nums, 6); - for (int i = 0; i < 6; i++) - { - printf("%d ", nums[i]); + bubbleSortWithFlag(nums1, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums1[i]); } printf("\n"); diff --git a/codes/c/chapter_sorting/bucket_sort.c b/codes/c/chapter_sorting/bucket_sort.c new file mode 100644 index 0000000000..b9b65de16b --- /dev/null +++ b/codes/c/chapter_sorting/bucket_sort.c @@ -0,0 +1,57 @@ +/** + * File: bucket_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define SIZE 10 + +/* 用于 qsort 的比较函数 */ +int compare(const void *a, const void *b) { + float fa = *(const float *)a; + float fb = *(const float *)b; + return (fa > fb) - (fa < fb); +} + +/* 桶排序 */ +void bucketSort(float nums[], int n) { + int k = n / 2; // 初始化 k = n/2 个桶 + int *sizes = malloc(k * sizeof(int)); // 记录每个桶的大小 + float **buckets = malloc(k * sizeof(float *)); // 动态数组的数组(桶) + // 为每个桶预分配足够的空间 + for (int i = 0; i < k; ++i) { + buckets[i] = (float *)malloc(n * sizeof(float)); + sizes[i] = 0; + } + // 1. 将数组元素分配到各个桶中 + for (int i = 0; i < n; ++i) { + int idx = (int)(nums[i] * k); + buckets[idx][sizes[idx]++] = nums[i]; + } + // 2. 对各个桶执行排序 + for (int i = 0; i < k; ++i) { + qsort(buckets[i], sizes[i], sizeof(float), compare); + } + // 3. 合并排序后的桶 + int idx = 0; + for (int i = 0; i < k; ++i) { + for (int j = 0; j < sizes[i]; ++j) { + nums[idx++] = buckets[i][j]; + } + // 释放内存 + free(buckets[i]); + } +} + +/* Driver Code */ +int main() { + // 设输入数据为浮点数,范围为 [0, 1) + float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums, SIZE); + printf("桶排序完成后 nums = "); + printArrayFloat(nums, SIZE); + + return 0; +} diff --git a/codes/c/chapter_sorting/counting_sort.c b/codes/c/chapter_sorting/counting_sort.c new file mode 100644 index 0000000000..aa77d1c827 --- /dev/null +++ b/codes/c/chapter_sorting/counting_sort.c @@ -0,0 +1,87 @@ +/** + * File: counting_sort.c + * Created Time: 2023-03-20 + * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +void countingSortNaive(int nums[], int size) { + // 1. 统计数组最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int *counter = calloc(m + 1, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + // 4. 释放内存 + free(counter); +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +void countingSort(int nums[], int size) { + // 1. 统计数组最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int *counter = calloc(m, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + 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); +} + +/* Driver Code */ +int main() { + int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size = sizeof(nums) / sizeof(int); + countingSortNaive(nums, size); + printf("计数排序(无法排序对象)完成后 nums = "); + printArray(nums, size); + + int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size1 = sizeof(nums1) / sizeof(int); + countingSort(nums1, size1); + printf("计数排序完成后 nums1 = "); + printArray(nums1, size1); + + return 0; +} diff --git a/codes/c/chapter_sorting/heap_sort.c b/codes/c/chapter_sorting/heap_sort.c new file mode 100644 index 0000000000..baa65f24fe --- /dev/null +++ b/codes/c/chapter_sorting/heap_sort.c @@ -0,0 +1,60 @@ +/** + * File: heap_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +void siftDown(int nums[], int n, int i) { + while (1) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) { + break; + } + // 交换两节点 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // 循环向下堆化 + i = ma; + } +} + +/* 堆排序 */ +void heapSort(int nums[], int n) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = n / 2 - 1; i >= 0; --i) { + siftDown(nums, n, i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = n - 1; i > 0; --i) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + heapSort(nums, n); + printf("堆排序完成后 nums = "); + printArray(nums, n); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_sorting/insertion_sort.c b/codes/c/chapter_sorting/insertion_sort.c index f81b8d43e7..d8e9fe1c26 100644 --- a/codes/c/chapter_sorting/insertion_sort.c +++ b/codes/c/chapter_sorting/insertion_sort.c @@ -4,23 +4,21 @@ * Author: Listening (https://github.com/L-Super) */ -#include "../include/include.h" +#include "../utils/common.h" /* 插入排序 */ void insertionSort(int nums[], int size) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < size; i++) - { + // 外循环:已排序区间为 [0, i-1] + for (int i = 1; i < size; i++) { int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) - { - // 1. 将 nums[j] 向右移动一位 - nums[j + 1] = nums[j]; + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while (j >= 0 && nums[j] > base) { + // 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; j--; } - // 2. 将 base 赋值到正确位置 - nums[j + 1] = base; + // 将 base 赋值到正确位置 + nums[j + 1] = base; } } @@ -29,8 +27,7 @@ int main() { int nums[] = {4, 1, 3, 1, 5, 2}; insertionSort(nums, 6); printf("插入排序完成后 nums = "); - for (int i = 0; i < 6; i++) - { + for (int i = 0; i < 6; i++) { printf("%d ", nums[i]); } printf("\n"); diff --git a/codes/c/chapter_sorting/merge_sort.c b/codes/c/chapter_sorting/merge_sort.c new file mode 100644 index 0000000000..e4bc254a55 --- /dev/null +++ b/codes/c/chapter_sorting/merge_sort.c @@ -0,0 +1,63 @@ +/** + * File: merge_sort.c + * Created Time: 2022-03-21 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 合并左子数组和右子数组 */ +void merge(int *nums, int left, int mid, int right) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + int tmpSize = right - left + 1; + int *tmp = (int *)malloc(tmpSize * sizeof(int)); + // 初始化左子数组和右子数组的起始索引 + int i = left, j = mid + 1, k = 0; + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for (k = 0; k < tmpSize; ++k) { + nums[left + k] = tmp[k]; + } + // 释放内存 + free(tmp); +} + +/* 归并排序 */ +void mergeSort(int *nums, int left, int right) { + // 终止条件 + if (left >= right) + return; // 当子数组长度为 1 时终止递归 + // 划分阶段 + int mid = left + (right - left) / 2; // 计算中点 + mergeSort(nums, left, mid); // 递归左子数组 + mergeSort(nums, mid + 1, right); // 递归右子数组 + // 合并阶段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* 归并排序 */ + int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; + int size = sizeof(nums) / sizeof(int); + mergeSort(nums, 0, size - 1); + printf("归并排序完成后 nums = "); + printArray(nums, size); + + return 0; +} diff --git a/codes/c/chapter_sorting/quick_sort.c b/codes/c/chapter_sorting/quick_sort.c new file mode 100644 index 0000000000..6d3986ee5c --- /dev/null +++ b/codes/c/chapter_sorting/quick_sort.c @@ -0,0 +1,137 @@ +/** + * File: quick_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 元素交换 */ +void swap(int nums[], int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; +} + +/* 哨兵划分 */ +int partition(int nums[], int left, int right) { + // 以 nums[left] 为基准数 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 从右向左找首个小于基准数的元素 + } + while (i < j && nums[i] <= nums[left]) { + i++; // 从左向右找首个大于基准数的元素 + } + // 交换这两个元素 + swap(nums, i, j); + } + // 将基准数交换至两子数组的分界线 + swap(nums, i, left); + // 返回基准数的索引 + return i; +} + +/* 快速排序 */ +void quickSort(int nums[], int left, int right) { + // 子数组长度为 1 时终止递归 + if (left >= right) { + return; + } + // 哨兵划分 + int pivot = partition(nums, left, right); + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); +} + +// 以下为中位数优化的快速排序 + +/* 选取三个候选元素的中位数 */ +int medianThree(int nums[], int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之间 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之间 + return right; +} + +/* 哨兵划分(三数取中值) */ +int partitionMedian(int nums[], int left, int right) { + // 选取三个候选元素的中位数 + int med = medianThree(nums, left, (left + right) / 2, right); + // 将中位数交换至数组最左端 + swap(nums, left, med); + // 以 nums[left] 为基准数 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 +} + +/* 快速排序(三数取中值) */ +void quickSortMedian(int nums[], int left, int right) { + // 子数组长度为 1 时终止递归 + if (left >= right) + return; + // 哨兵划分 + int pivot = partitionMedian(nums, left, right); + // 递归左子数组、右子数组 + quickSortMedian(nums, left, pivot - 1); + quickSortMedian(nums, pivot + 1, right); +} + +// 以下为尾递归优化的快速排序 + +/* 快速排序(尾递归优化) */ +void quickSortTailCall(int nums[], int left, int right) { + // 子数组长度为 1 时终止 + while (left < right) { + // 哨兵划分操作 + int pivot = partition(nums, left, right); + // 对两个子数组中较短的那个执行快速排序 + if (pivot - left < right - pivot) { + // 递归排序左子数组 + quickSortTailCall(nums, left, pivot - 1); + // 剩余未排序区间为 [pivot + 1, right] + left = pivot + 1; + } else { + // 递归排序右子数组 + quickSortTailCall(nums, pivot + 1, right); + // 剩余未排序区间为 [left, pivot - 1] + right = pivot - 1; + } + } +} + +/* Driver Code */ +int main() { + /* 快速排序 */ + int nums[] = {2, 4, 1, 0, 3, 5}; + int size = sizeof(nums) / sizeof(int); + quickSort(nums, 0, size - 1); + printf("快速排序完成后 nums = "); + printArray(nums, size); + + /* 快速排序(中位基准数优化) */ + int nums1[] = {2, 4, 1, 0, 3, 5}; + quickSortMedian(nums1, 0, size - 1); + printf("快速排序(中位基准数优化)完成后 nums = "); + printArray(nums1, size); + + /* 快速排序(尾递归优化) */ + int nums2[] = {2, 4, 1, 0, 3, 5}; + quickSortTailCall(nums2, 0, size - 1); + printf("快速排序(尾递归优化)完成后 nums = "); + printArray(nums1, size); + + return 0; +} diff --git a/codes/c/chapter_sorting/radix_sort.c b/codes/c/chapter_sorting/radix_sort.c new file mode 100644 index 0000000000..d50f9e29de --- /dev/null +++ b/codes/c/chapter_sorting/radix_sort.c @@ -0,0 +1,75 @@ +/** + * File: radix_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + 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); +} + +/* Driver Code */ +int main() { + // 基数排序 + int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + int size = sizeof(nums) / sizeof(int); + radixSort(nums, size); + printf("基数排序完成后 nums = "); + printArray(nums, size); +} diff --git a/codes/c/chapter_sorting/selection_sort.c b/codes/c/chapter_sorting/selection_sort.c new file mode 100644 index 0000000000..9c29b146f8 --- /dev/null +++ b/codes/c/chapter_sorting/selection_sort.c @@ -0,0 +1,37 @@ +/** + * File: selection_sort.c + * Created Time: 2023-05-31 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 选择排序 */ +void selectionSort(int nums[], int n) { + // 外循环:未排序区间为 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内循环:找到未排序区间内的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 记录最小元素的索引 + } + // 将该最小元素与未排序区间的首个元素交换 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + selectionSort(nums, n); + + printf("选择排序完成后 nums = "); + printArray(nums, n); + + return 0; +} diff --git a/codes/c/chapter_stack_and_queue/CMakeLists.txt b/codes/c/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 0000000000..ed3ba840c9 --- /dev/null +++ b/codes/c/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(array_stack array_stack.c) +add_executable(linkedlist_stack linkedlist_stack.c) +add_executable(array_queue array_queue.c) +add_executable(linkedlist_queue linkedlist_queue.c) +add_executable(array_deque array_deque.c) +add_executable(linkedlist_deque linkedlist_deque.c) diff --git a/codes/c/chapter_stack_and_queue/array_deque.c b/codes/c/chapter_stack_and_queue/array_deque.c new file mode 100644 index 0000000000..07adcbbfda --- /dev/null +++ b/codes/c/chapter_stack_and_queue/array_deque.c @@ -0,0 +1,172 @@ +/** + * File: array_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基于环形数组实现的双向队列 */ +typedef struct { + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 尾指针,指向队尾 + 1 + int queCapacity; // 队列容量 +} ArrayDeque; + +/* 构造函数 */ +ArrayDeque *newArrayDeque(int capacity) { + ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); + // 初始化数组 + deque->queCapacity = capacity; + deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); + deque->front = deque->queSize = 0; + return deque; +} + +/* 析构函数 */ +void delArrayDeque(ArrayDeque *deque) { + free(deque->nums); + free(deque); +} + +/* 获取双向队列的容量 */ +int capacity(ArrayDeque *deque) { + return deque->queCapacity; +} + +/* 获取双向队列的长度 */ +int size(ArrayDeque *deque) { + return deque->queSize; +} + +/* 判断双向队列是否为空 */ +bool empty(ArrayDeque *deque) { + return deque->queSize == 0; +} + +/* 计算环形数组索引 */ +int dequeIndex(ArrayDeque *deque, int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部时,回到头部 + // 当 i 越过数组头部后,回到尾部 + return ((i + capacity(deque)) % capacity(deque)); +} + +/* 队首入队 */ +void pushFirst(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("双向队列已满\r\n"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部回到尾部 + deque->front = dequeIndex(deque, deque->front - 1); + // 将 num 添加到队首 + deque->nums[deque->front] = num; + deque->queSize++; +} + +/* 队尾入队 */ +void pushLast(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("双向队列已满\r\n"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + int rear = dequeIndex(deque, deque->front + deque->queSize); + // 将 num 添加至队尾 + deque->nums[rear] = num; + deque->queSize++; +} + +/* 访问队首元素 */ +int peekFirst(ArrayDeque *deque) { + // 访问异常:双向队列为空 + assert(empty(deque) == 0); + return deque->nums[deque->front]; +} + +/* 访问队尾元素 */ +int peekLast(ArrayDeque *deque) { + // 访问异常:双向队列为空 + assert(empty(deque) == 0); + int last = dequeIndex(deque, deque->front + deque->queSize - 1); + return deque->nums[last]; +} + +/* 队首出队 */ +int popFirst(ArrayDeque *deque) { + int num = peekFirst(deque); + // 队首指针向后移动一位 + deque->front = dequeIndex(deque, deque->front + 1); + deque->queSize--; + return num; +} + +/* 队尾出队 */ +int popLast(ArrayDeque *deque) { + int num = peekLast(deque); + deque->queSize--; + return num; +} + +/* 返回数组用于打印 */ +int *toArray(ArrayDeque *deque, int *queSize) { + *queSize = deque->queSize; + int *res = (int *)calloc(deque->queSize, sizeof(int)); + int j = deque->front; + for (int i = 0; i < deque->queSize; i++) { + res[i] = deque->nums[j % deque->queCapacity]; + j++; + } + return res; +} + +/* Driver Code */ +int main() { + /* 初始化队列 */ + int capacity = 10; + int queSize; + ArrayDeque *deque = newArrayDeque(capacity); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("双向队列 deque = "); + printArray(toArray(deque, &queSize), queSize); + + /* 访问元素 */ + int peekFirstNum = peekFirst(deque); + printf("队首元素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("队尾元素 peekLast = %d\r\n", peekLastNum); + + /* 元素入队 */ + pushLast(deque, 4); + printf("元素 4 队尾入队后 deque = "); + printArray(toArray(deque, &queSize), queSize); + pushFirst(deque, 1); + printf("元素 1 队首入队后 deque = "); + printArray(toArray(deque, &queSize), queSize); + + /* 元素出队 */ + int popLastNum = popLast(deque); + printf("队尾出队元素 = %d ,队尾出队后 deque= ", popLastNum); + printArray(toArray(deque, &queSize), queSize); + int popFirstNum = popFirst(deque); + printf("队首出队元素 = %d ,队首出队后 deque= ", popFirstNum); + printArray(toArray(deque, &queSize), queSize); + + /* 获取队列的长度 */ + int dequeSize = size(deque); + printf("双向队列长度 size = %d\r\n", dequeSize); + + /* 判断队列是否为空 */ + bool isEmpty = empty(deque); + printf("队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); + + // 释放内存 + delArrayDeque(deque); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_stack_and_queue/array_queue.c b/codes/c/chapter_stack_and_queue/array_queue.c new file mode 100644 index 0000000000..ad6f4fb271 --- /dev/null +++ b/codes/c/chapter_stack_and_queue/array_queue.c @@ -0,0 +1,134 @@ +/** + * File: array_queue.c + * Created Time: 2023-01-28 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基于环形数组实现的队列 */ +typedef struct { + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 尾指针,指向队尾 + 1 + 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; +} + +/* Driver Code */ +int main() { + /* 初始化队列 */ + int capacity = 10; + int queSize; + ArrayQueue *queue = newArrayQueue(capacity); + + /* 元素入队 */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("队列 queue = "); + printArray(toArray(queue, &queSize), queSize); + + /* 访问队首元素 */ + int peekNum = peek(queue); + printf("队首元素 peek = %d\r\n", peekNum); + + /* 元素出队 */ + peekNum = pop(queue); + printf("出队元素 pop = %d ,出队后 queue = ", peekNum); + printArray(toArray(queue, &queSize), queSize); + + /* 获取队列的长度 */ + int queueSize = size(queue); + printf("队列长度 size = %d\r\n", queueSize); + + /* 判断队列是否为空 */ + bool isEmpty = empty(queue); + printf("队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); + + /* 测试环形数组 */ + for (int i = 0; i < 10; i++) { + push(queue, i); + pop(queue); + printf("第 %d 轮入队 + 出队后 queue = ", i); + printArray(toArray(queue, &queSize), queSize); + } + + // 释放内存 + delArrayQueue(queue); + + return 0; +} \ No newline at end of file diff --git a/codes/c/chapter_stack_and_queue/array_stack.c b/codes/c/chapter_stack_and_queue/array_stack.c new file mode 100644 index 0000000000..39e1cff4d7 --- /dev/null +++ b/codes/c/chapter_stack_and_queue/array_stack.c @@ -0,0 +1,103 @@ +/** + * File: array_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 基于数组实现的栈 */ +typedef struct { + int *data; + int size; +} ArrayStack; + +/* 构造函数 */ +ArrayStack *newArrayStack() { + ArrayStack *stack = malloc(sizeof(ArrayStack)); + // 初始化一个大容量,避免扩容 + stack->data = malloc(sizeof(int) * MAX_SIZE); + stack->size = 0; + return stack; +} + +/* 析构函数 */ +void delArrayStack(ArrayStack *stack) { + free(stack->data); + free(stack); +} + +/* 获取栈的长度 */ +int size(ArrayStack *stack) { + return stack->size; +} + +/* 判断栈是否为空 */ +bool isEmpty(ArrayStack *stack) { + return stack->size == 0; +} + +/* 入栈 */ +void push(ArrayStack *stack, int num) { + if (stack->size == MAX_SIZE) { + printf("栈已满\n"); + return; + } + stack->data[stack->size] = num; + stack->size++; +} + +/* 访问栈顶元素 */ +int peek(ArrayStack *stack) { + if (stack->size == 0) { + printf("栈为空\n"); + return INT_MAX; + } + return stack->data[stack->size - 1]; +} + +/* 出栈 */ +int pop(ArrayStack *stack) { + int val = peek(stack); + stack->size--; + return val; +} + +/* Driver Code */ +int main() { + /* 初始化栈 */ + ArrayStack *stack = newArrayStack(); + + /* 元素入栈 */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + printf("栈 stack = "); + printArray(stack->data, stack->size); + + /* 访问栈顶元素 */ + int val = peek(stack); + printf("栈顶元素 top = %d\n", val); + + /* 元素出栈 */ + val = pop(stack); + printf("出栈元素 pop = %d ,出栈后 stack = ", val); + printArray(stack->data, stack->size); + + /* 获取栈的长度 */ + int size = stack->size; + printf("栈的长度 size = %d\n", size); + + /* 判断是否为空 */ + bool empty = isEmpty(stack); + printf("栈是否为空 = %s\n", empty ? "true" : "false"); + + // 释放内存 + delArrayStack(stack); + + return 0; +} diff --git a/codes/c/chapter_stack_and_queue/linkedlist_deque.c b/codes/c/chapter_stack_and_queue/linkedlist_deque.c new file mode 100644 index 0000000000..f28671683c --- /dev/null +++ b/codes/c/chapter_stack_and_queue/linkedlist_deque.c @@ -0,0 +1,212 @@ +/** + * File: linkedlist_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 双向链表节点 */ +typedef struct DoublyListNode { + int val; // 节点值 + struct DoublyListNode *next; // 后继节点 + struct DoublyListNode *prev; // 前驱节点 +} DoublyListNode; + +/* 构造函数 */ +DoublyListNode *newDoublyListNode(int num) { + DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); + new->val = num; + new->next = NULL; + new->prev = NULL; + return new; +} + +/* 析构函数 */ +void delDoublyListNode(DoublyListNode *node) { + free(node); +} + +/* 基于双向链表实现的双向队列 */ +typedef struct { + DoublyListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize; // 双向队列的长度 +} LinkedListDeque; + +/* 构造函数 */ +LinkedListDeque *newLinkedListDeque() { + LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); + deque->front = NULL; + deque->rear = NULL; + deque->queSize = 0; + return deque; +} + +/* 析构函数 */ +void delLinkedListdeque(LinkedListDeque *deque) { + // 释放所有节点 + for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { + DoublyListNode *tmp = deque->front; + deque->front = deque->front->next; + free(tmp); + } + // 释放 deque 结构体 + free(deque); +} + +/* 获取队列的长度 */ +int size(LinkedListDeque *deque) { + return deque->queSize; +} + +/* 判断队列是否为空 */ +bool empty(LinkedListDeque *deque) { + return (size(deque) == 0); +} + +/* 入队 */ +void push(LinkedListDeque *deque, int num, bool isFront) { + DoublyListNode *node = newDoublyListNode(num); + // 若链表为空,则令 front 和 rear 都指向node + if (empty(deque)) { + deque->front = deque->rear = node; + } + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + deque->front->prev = node; + node->next = deque->front; + deque->front = node; // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + deque->rear->next = node; + node->prev = deque->rear; + deque->rear = node; + } + deque->queSize++; // 更新队列长度 +} + +/* 队首入队 */ +void pushFirst(LinkedListDeque *deque, int num) { + push(deque, num, true); +} + +/* 队尾入队 */ +void pushLast(LinkedListDeque *deque, int num) { + push(deque, num, false); +} + +/* 访问队首元素 */ +int peekFirst(LinkedListDeque *deque) { + assert(size(deque) && deque->front); + return deque->front->val; +} + +/* 访问队尾元素 */ +int peekLast(LinkedListDeque *deque) { + assert(size(deque) && deque->rear); + return deque->rear->val; +} + +/* 出队 */ +int pop(LinkedListDeque *deque, bool isFront) { + if (empty(deque)) + return -1; + int val; + // 队首出队操作 + if (isFront) { + val = peekFirst(deque); // 暂存头节点值 + DoublyListNode *fNext = deque->front->next; + if (fNext) { + fNext->prev = NULL; + deque->front->next = NULL; + } + delDoublyListNode(deque->front); + deque->front = fNext; // 更新头节点 + } + // 队尾出队操作 + else { + val = peekLast(deque); // 暂存尾节点值 + DoublyListNode *rPrev = deque->rear->prev; + if (rPrev) { + rPrev->next = NULL; + deque->rear->prev = NULL; + } + delDoublyListNode(deque->rear); + deque->rear = rPrev; // 更新尾节点 + } + deque->queSize--; // 更新队列长度 + return val; +} + +/* 队首出队 */ +int popFirst(LinkedListDeque *deque) { + return pop(deque, true); +} + +/* 队尾出队 */ +int popLast(LinkedListDeque *deque) { + return pop(deque, false); +} + +/* 打印队列 */ +void printLinkedListDeque(LinkedListDeque *deque) { + int *arr = malloc(sizeof(int) * deque->queSize); + // 拷贝链表中的数据到数组 + int i; + DoublyListNode *node; + for (i = 0, node = deque->front; i < deque->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, deque->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* 初始化双向队列 */ + LinkedListDeque *deque = newLinkedListDeque(); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("双向队列 deque = "); + printLinkedListDeque(deque); + + /* 访问元素 */ + int peekFirstNum = peekFirst(deque); + printf("队首元素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("队首元素 peekLast = %d\r\n", peekLastNum); + + /* 元素入队 */ + pushLast(deque, 4); + printf("元素 4 队尾入队后 deque ="); + printLinkedListDeque(deque); + pushFirst(deque, 1); + printf("元素 1 队首入队后 deque ="); + printLinkedListDeque(deque); + + /* 元素出队 */ + int popLastNum = popLast(deque); + printf("队尾出队元素 popLast = %d ,队尾出队后 deque = ", popLastNum); + printLinkedListDeque(deque); + int popFirstNum = popFirst(deque); + printf("队首出队元素 popFirst = %d ,队首出队后 deque = ", popFirstNum); + printLinkedListDeque(deque); + + /* 获取队列的长度 */ + int dequeSize = size(deque); + printf("双向队列长度 size = %d\r\n", dequeSize); + + /* 判断队列是否为空 */ + bool isEmpty = empty(deque); + printf("双向队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); + + // 释放内存 + delLinkedListdeque(deque); + + return 0; +} diff --git a/codes/c/chapter_stack_and_queue/linkedlist_queue.c b/codes/c/chapter_stack_and_queue/linkedlist_queue.c new file mode 100644 index 0000000000..60437c2cf2 --- /dev/null +++ b/codes/c/chapter_stack_and_queue/linkedlist_queue.c @@ -0,0 +1,128 @@ +/** + * File: linkedlist_queue.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基于链表实现的队列 */ +typedef struct { + ListNode *front, *rear; + int queSize; +} LinkedListQueue; + +/* 构造函数 */ +LinkedListQueue *newLinkedListQueue() { + LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); + queue->front = NULL; + queue->rear = NULL; + queue->queSize = 0; + return queue; +} + +/* 析构函数 */ +void delLinkedListQueue(LinkedListQueue *queue) { + // 释放所有节点 + while (queue->front != NULL) { + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + } + // 释放 queue 结构体 + free(queue); +} + +/* 获取队列的长度 */ +int size(LinkedListQueue *queue) { + return queue->queSize; +} + +/* 判断队列是否为空 */ +bool empty(LinkedListQueue *queue) { + return (size(queue) == 0); +} + +/* 入队 */ +void push(LinkedListQueue *queue, int num) { + // 尾节点处添加 node + ListNode *node = newListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (queue->front == NULL) { + queue->front = node; + queue->rear = node; + } + // 如果队列不为空,则将该节点添加到尾节点后 + else { + queue->rear->next = node; + queue->rear = node; + } + queue->queSize++; +} + +/* 访问队首元素 */ +int peek(LinkedListQueue *queue) { + assert(size(queue) && queue->front); + return queue->front->val; +} + +/* 出队 */ +int pop(LinkedListQueue *queue) { + int num = peek(queue); + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + queue->queSize--; + return num; +} + +/* 打印队列 */ +void printLinkedListQueue(LinkedListQueue *queue) { + int *arr = malloc(sizeof(int) * queue->queSize); + // 拷贝链表中的数据到数组 + int i; + ListNode *node; + for (i = 0, node = queue->front; i < queue->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, queue->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* 初始化队列 */ + LinkedListQueue *queue = newLinkedListQueue(); + + /* 元素入队 */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("队列 queue = "); + printLinkedListQueue(queue); + + /* 访问队首元素 */ + int peekNum = peek(queue); + printf("队首元素 peek = %d\r\n", peekNum); + + /* 元素出队 */ + peekNum = pop(queue); + printf("出队元素 pop = %d ,出队后 queue = ", peekNum); + printLinkedListQueue(queue); + + /* 获取队列的长度 */ + int queueSize = size(queue); + printf("队列长度 size = %d\r\n", queueSize); + + /* 判断队列是否为空 */ + bool isEmpty = empty(queue); + printf("队列是否为空 = %s\r\n", isEmpty ? "true" : "false"); + + // 释放内存 + delLinkedListQueue(queue); + + return 0; +} diff --git a/codes/c/chapter_stack_and_queue/linkedlist_stack.c b/codes/c/chapter_stack_and_queue/linkedlist_stack.c new file mode 100644 index 0000000000..25795b2862 --- /dev/null +++ b/codes/c/chapter_stack_and_queue/linkedlist_stack.c @@ -0,0 +1,107 @@ +/** + * File: linkedlist_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基于链表实现的栈 */ +typedef struct { + ListNode *top; // 将头节点作为栈顶 + int size; // 栈的长度 +} LinkedListStack; + +/* 构造函数 */ +LinkedListStack *newLinkedListStack() { + LinkedListStack *s = malloc(sizeof(LinkedListStack)); + s->top = NULL; + s->size = 0; + return s; +} + +/* 析构函数 */ +void delLinkedListStack(LinkedListStack *s) { + while (s->top) { + ListNode *n = s->top->next; + free(s->top); + s->top = n; + } + free(s); +} + +/* 获取栈的长度 */ +int size(LinkedListStack *s) { + return s->size; +} + +/* 判断栈是否为空 */ +bool isEmpty(LinkedListStack *s) { + return size(s) == 0; +} + +/* 入栈 */ +void push(LinkedListStack *s, int num) { + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->next = s->top; // 更新新加节点指针域 + node->val = num; // 更新新加节点数据域 + s->top = node; // 更新栈顶 + s->size++; // 更新栈大小 +} + +/* 访问栈顶元素 */ +int peek(LinkedListStack *s) { + if (s->size == 0) { + printf("栈为空\n"); + return INT_MAX; + } + return s->top->val; +} + +/* 出栈 */ +int pop(LinkedListStack *s) { + int val = peek(s); + ListNode *tmp = s->top; + s->top = s->top->next; + // 释放内存 + free(tmp); + s->size--; + return val; +} + +/* Driver Code */ +int main() { + /* 初始化栈 */ + LinkedListStack *stack = newLinkedListStack(); + + /* 元素入栈 */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + + printf("栈 stack = "); + printLinkedList(stack->top); + + /* 访问栈顶元素 */ + int val = peek(stack); + printf("栈顶元素 top = %d\r\n", val); + + /* 元素出栈 */ + val = pop(stack); + printf("出栈元素 pop = %d, 出栈后 stack = ", val); + printLinkedList(stack->top); + + /* 获取栈的长度 */ + printf("栈的长度 size = %d\n", size(stack)); + + /* 判断是否为空 */ + bool empty = isEmpty(stack); + printf("栈是否为空 = %s\n", empty ? "true" : "false"); + + // 释放内存 + delLinkedListStack(stack); + + return 0; +} diff --git a/codes/c/chapter_tree/CMakeLists.txt b/codes/c/chapter_tree/CMakeLists.txt index 779315b7be..9b4e825ff1 100644 --- a/codes/c/chapter_tree/CMakeLists.txt +++ b/codes/c/chapter_tree/CMakeLists.txt @@ -1,4 +1,6 @@ -add_executable(binary_search binary_tree.c) +add_executable(avl_tree avl_tree.c) +add_executable(binary_tree binary_tree.c) add_executable(binary_tree_bfs binary_tree_bfs.c) add_executable(binary_tree_dfs binary_tree_dfs.c) add_executable(binary_search_tree binary_search_tree.c) +add_executable(array_binary_tree array_binary_tree.c) diff --git a/codes/c/chapter_tree/array_binary_tree.c b/codes/c/chapter_tree/array_binary_tree.c new file mode 100644 index 0000000000..d715215dda --- /dev/null +++ b/codes/c/chapter_tree/array_binary_tree.c @@ -0,0 +1,166 @@ +/** + * File: array_binary_tree.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 数组表示下的二叉树结构体 */ +typedef struct { + int *tree; + int size; +} ArrayBinaryTree; + +/* 构造函数 */ +ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { + ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); + abt->tree = malloc(sizeof(int) * arrSize); + memcpy(abt->tree, arr, sizeof(int) * arrSize); + abt->size = arrSize; + return abt; +} + +/* 析构函数 */ +void delArrayBinaryTree(ArrayBinaryTree *abt) { + free(abt->tree); + free(abt); +} + +/* 列表容量 */ +int size(ArrayBinaryTree *abt) { + return abt->size; +} + +/* 获取索引为 i 节点的值 */ +int val(ArrayBinaryTree *abt, int i) { + // 若索引越界,则返回 INT_MAX ,代表空位 + if (i < 0 || i >= size(abt)) + return INT_MAX; + return abt->tree[i]; +} + +/* 获取索引为 i 节点的左子节点的索引 */ +int left(int i) { + return 2 * i + 1; +} + +/* 获取索引为 i 节点的右子节点的索引 */ +int right(int i) { + return 2 * i + 2; +} + +/* 获取索引为 i 节点的父节点的索引 */ +int parent(int i) { + return (i - 1) / 2; +} + +/* 层序遍历 */ +int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + // 直接遍历数组 + for (int i = 0; i < size(abt); i++) { + if (val(abt, i) != INT_MAX) + res[index++] = val(abt, i); + } + *returnSize = index; + return res; +} + +/* 深度优先遍历 */ +void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { + // 若为空位,则返回 + if (val(abt, i) == INT_MAX) + return; + // 前序遍历 + if (strcmp(order, "pre") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, left(i), order, res, index); + // 中序遍历 + if (strcmp(order, "in") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, right(i), order, res, index); + // 后序遍历 + if (strcmp(order, "post") == 0) + res[(*index)++] = val(abt, i); +} + +/* 前序遍历 */ +int *preOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "pre", res, &index); + *returnSize = index; + return res; +} + +/* 中序遍历 */ +int *inOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "in", res, &index); + *returnSize = index; + return res; +} + +/* 后序遍历 */ +int *postOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "post", res, &index); + *returnSize = index; + return res; +} + +/* Driver Code */ +int main() { + // 初始化二叉树 + // 使用 INT_MAX 代表空位 NULL + int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + int arrSize = sizeof(arr) / sizeof(arr[0]); + TreeNode *root = arrayToTree(arr, arrSize); + printf("\n初始化二叉树\n"); + printf("二叉树的数组表示:\n"); + printArray(arr, arrSize); + printf("二叉树的链表表示:\n"); + printTree(root); + + ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); + + // 访问节点 + int i = 1; + int l = left(i), r = right(i), p = parent(i); + printf("\n当前节点的索引为 %d,值为 %d\n", i, val(abt, i)); + printf("其左子节点的索引为 %d,值为 %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); + printf("其右子节点的索引为 %d,值为 %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); + printf("其父节点的索引为 %d,值为 %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); + + // 遍历树 + int returnSize; + int *res; + + res = levelOrder(abt, &returnSize); + printf("\n层序遍历为: "); + printArray(res, returnSize); + free(res); + + res = preOrder(abt, &returnSize); + printf("前序遍历为: "); + printArray(res, returnSize); + free(res); + + res = inOrder(abt, &returnSize); + printf("中序遍历为: "); + printArray(res, returnSize); + free(res); + + res = postOrder(abt, &returnSize); + printf("后序遍历为: "); + printArray(res, returnSize); + free(res); + + // 释放内存 + delArrayBinaryTree(abt); + return 0; +} diff --git a/codes/c/chapter_tree/avl_tree.c b/codes/c/chapter_tree/avl_tree.c new file mode 100644 index 0000000000..5cbddbb7bf --- /dev/null +++ b/codes/c/chapter_tree/avl_tree.c @@ -0,0 +1,259 @@ +/** + * File: avl_tree.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* AVL 树结构体 */ +typedef struct { + TreeNode *root; +} AVLTree; + +/* 构造函数 */ +AVLTree *newAVLTree() { + AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); + tree->root = NULL; + return tree; +} + +/* 析构函数 */ +void delAVLTree(AVLTree *tree) { + freeMemoryTree(tree->root); + free(tree); +} + +/* 获取节点高度 */ +int height(TreeNode *node) { + // 空节点高度为 -1 ,叶节点高度为 0 + if (node != NULL) { + return node->height; + } + return -1; +} + +/* 更新节点高度 */ +void updateHeight(TreeNode *node) { + int lh = height(node->left); + int rh = height(node->right); + // 节点高度等于最高子树高度 + 1 + if (lh > rh) { + node->height = lh + 1; + } else { + node->height = rh + 1; + } +} + +/* 获取平衡因子 */ +int balanceFactor(TreeNode *node) { + // 空节点平衡因子为 0 + if (node == NULL) { + return 0; + } + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node->left) - height(node->right); +} + +/* 右旋操作 */ +TreeNode *rightRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->left; + grandChild = child->right; + // 以 child 为原点,将 node 向右旋转 + child->right = node; + node->left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; +} + +/* 左旋操作 */ +TreeNode *leftRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->right; + grandChild = child->left; + // 以 child 为原点,将 node 向左旋转 + child->left = node; + node->right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; +} + +/* 执行旋转操作,使该子树重新恢复平衡 */ +TreeNode *rotate(TreeNode *node) { + // 获取节点 node 的平衡因子 + int bf = balanceFactor(node); + // 左偏树 + if (bf > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏树 + if (bf < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; +} + +/* 递归插入节点(辅助函数) */ +TreeNode *insertHelper(TreeNode *node, int val) { + if (node == NULL) { + return newTreeNode(val); + } + /* 1. 查找插入位置并插入节点 */ + if (val < node->val) { + node->left = insertHelper(node->left, val); + } else if (val > node->val) { + node->right = insertHelper(node->right, val); + } else { + // 重复节点不插入,直接返回 + return node; + } + // 更新节点高度 + updateHeight(node); + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; +} + +/* 插入节点 */ +void insert(AVLTree *tree, int val) { + tree->root = insertHelper(tree->root, val); +} + +/* 递归删除节点(辅助函数) */ +TreeNode *removeHelper(TreeNode *node, int val) { + TreeNode *child, *grandChild; + if (node == NULL) { + return NULL; + } + /* 1. 查找节点并删除 */ + if (val < node->val) { + node->left = removeHelper(node->left, val); + } else if (val > node->val) { + node->right = removeHelper(node->right, val); + } else { + if (node->left == NULL || node->right == NULL) { + child = node->left; + if (node->right != NULL) { + child = node->right; + } + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == NULL) { + return NULL; + } else { + // 子节点数量 = 1 ,直接删除 node + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode *temp = node->right; + while (temp->left != NULL) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + // 更新节点高度 + updateHeight(node); + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; +} + +/* 删除节点 */ +// 由于引入了 stdio.h ,此处无法使用 remove 关键词 +void removeItem(AVLTree *tree, int val) { + TreeNode *root = removeHelper(tree->root, val); +} + +/* 查找节点 */ +TreeNode *search(AVLTree *tree, int val) { + TreeNode *cur = tree->root; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + if (cur->val < val) { + // 目标节点在 cur 的右子树中 + cur = cur->right; + } else if (cur->val > val) { + // 目标节点在 cur 的左子树中 + cur = cur->left; + } else { + // 找到目标节点,跳出循环 + break; + } + } + // 找到目标节点,跳出循环 + return cur; +} + +void testInsert(AVLTree *tree, int val) { + insert(tree, val); + printf("\n插入节点 %d 后,AVL 树为 \n", val); + printTree(tree->root); +} + +void testRemove(AVLTree *tree, int val) { + removeItem(tree, val); + printf("\n删除节点 %d 后,AVL 树为 \n", val); + printTree(tree->root); +} + +/* Driver Code */ +int main() { + /* 初始化空 AVL 树 */ + AVLTree *tree = (AVLTree *)newAVLTree(); + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + testInsert(tree, 1); + testInsert(tree, 2); + testInsert(tree, 3); + testInsert(tree, 4); + testInsert(tree, 5); + testInsert(tree, 8); + testInsert(tree, 7); + testInsert(tree, 9); + testInsert(tree, 10); + testInsert(tree, 6); + + /* 插入重复节点 */ + testInsert(tree, 7); + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(tree, 8); // 删除度为 0 的节点 + testRemove(tree, 5); // 删除度为 1 的节点 + testRemove(tree, 4); // 删除度为 2 的节点 + + /* 查询节点 */ + TreeNode *node = search(tree, 7); + printf("\n查找到的节点对象节点值 = %d \n", node->val); + + // 释放内存 + delAVLTree(tree); + return 0; +} diff --git a/codes/c/chapter_tree/binary_search_tree.c b/codes/c/chapter_tree/binary_search_tree.c index f993faec31..751d63b9e6 100644 --- a/codes/c/chapter_tree/binary_search_tree.c +++ b/codes/c/chapter_tree/binary_search_tree.c @@ -4,5 +4,168 @@ * Author: Reanon (793584285@qq.com) */ -#include "../include/include.h" +#include "../utils/common.h" +/* 二叉搜索树结构体 */ +typedef struct { + TreeNode *root; +} BinarySearchTree; + +/* 构造函数 */ +BinarySearchTree *newBinarySearchTree() { + // 初始化空树 + BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); + bst->root = NULL; + return bst; +} + +/* 析构函数 */ +void delBinarySearchTree(BinarySearchTree *bst) { + freeMemoryTree(bst->root); + free(bst); +} + +/* 获取二叉树根节点 */ +TreeNode *getRoot(BinarySearchTree *bst) { + return bst->root; +} + +/* 查找节点 */ +TreeNode *search(BinarySearchTree *bst, int num) { + TreeNode *cur = bst->root; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + if (cur->val < num) { + // 目标节点在 cur 的右子树中 + cur = cur->right; + } else if (cur->val > num) { + // 目标节点在 cur 的左子树中 + cur = cur->left; + } else { + // 找到目标节点,跳出循环 + break; + } + } + // 返回目标节点 + return cur; +} + +/* 插入节点 */ +void insert(BinarySearchTree *bst, int num) { + // 若树为空,则初始化根节点 + if (bst->root == NULL) { + bst->root = newTreeNode(num); + return; + } + TreeNode *cur = bst->root, *pre = NULL; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + // 找到重复节点,直接返回 + if (cur->val == num) { + return; + } + pre = cur; + if (cur->val < num) { + // 插入位置在 cur 的右子树中 + cur = cur->right; + } else { + // 插入位置在 cur 的左子树中 + cur = cur->left; + } + } + // 插入节点 + TreeNode *node = newTreeNode(num); + if (pre->val < num) { + pre->right = node; + } else { + pre->left = node; + } +} + +/* 删除节点 */ +// 由于引入了 stdio.h ,此处无法使用 remove 关键词 +void removeItem(BinarySearchTree *bst, int num) { + // 若树为空,直接提前返回 + if (bst->root == NULL) + return; + TreeNode *cur = bst->root, *pre = NULL; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + // 找到待删除节点,跳出循环 + if (cur->val == num) + break; + pre = cur; + if (cur->val < num) { + // 待删除节点在 root 的右子树中 + cur = cur->right; + } else { + // 待删除节点在 root 的左子树中 + cur = cur->left; + } + } + // 若无待删除节点,则直接返回 + if (cur == NULL) + return; + // 判断待删除节点是否存在子节点 + if (cur->left == NULL || cur->right == NULL) { + /* 子节点数量 = 0 or 1 */ + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + TreeNode *child = cur->left != NULL ? cur->left : cur->right; + // 删除节点 cur + if (pre->left == cur) { + pre->left = child; + } else { + pre->right = child; + } + // 释放内存 + free(cur); + } else { + /* 子节点数量 = 2 */ + // 获取中序遍历中 cur 的下一个节点 + TreeNode *tmp = cur->right; + while (tmp->left != NULL) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 递归删除节点 tmp + removeItem(bst, tmp->val); + // 用 tmp 覆盖 cur + cur->val = tmpVal; + } +} + +/* Driver Code */ +int main() { + /* 初始化二叉搜索树 */ + int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + BinarySearchTree *bst = newBinarySearchTree(); + for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { + insert(bst, nums[i]); + } + printf("初始化的二叉树为\n"); + printTree(getRoot(bst)); + + /* 查找节点 */ + TreeNode *node = search(bst, 7); + printf("查找到的节点对象的节点值 = %d\n", node->val); + + /* 插入节点 */ + insert(bst, 16); + printf("插入节点 16 后,二叉树为\n"); + printTree(getRoot(bst)); + + /* 删除节点 */ + removeItem(bst, 1); + printf("删除节点 1 后,二叉树为\n"); + printTree(getRoot(bst)); + removeItem(bst, 2); + printf("删除节点 2 后,二叉树为\n"); + printTree(getRoot(bst)); + removeItem(bst, 4); + printf("删除节点 4 后,二叉树为\n"); + printTree(getRoot(bst)); + + // 释放内存 + delBinarySearchTree(bst); + return 0; +} diff --git a/codes/c/chapter_tree/binary_tree.c b/codes/c/chapter_tree/binary_tree.c index 1eb8e846a9..3472391dc2 100644 --- a/codes/c/chapter_tree/binary_tree.c +++ b/codes/c/chapter_tree/binary_tree.c @@ -4,18 +4,18 @@ * Author: Reanon (793584285@qq.com) */ -#include "../include/include.h" +#include "../utils/common.h" /* Driver Code */ int main() { /* 初始化二叉树 */ - // 初始化结点 - TreeNode* n1 = newTreeNode(1); - TreeNode* n2 = newTreeNode(2); - TreeNode* n3 = newTreeNode(3); - TreeNode* n4 = newTreeNode(4); - TreeNode* n5 = newTreeNode(5); - // 构建引用指向(即指针) + // 初始化节点 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // 构建节点之间的引用(指针) n1->left = n2; n1->right = n3; n2->left = n4; @@ -23,20 +23,21 @@ int main() { printf("初始化二叉树\n"); printTree(n1); - /* 插入与删除结点 */ - TreeNode* P = newTreeNode(0); - // 在 n1 -> n2 中间插入结点 P + /* 插入与删除节点 */ + TreeNode *P = newTreeNode(0); + // 在 n1 -> n2 中间插入节点 P n1->left = P; P->left = n2; - printf("插入结点 P 后\n"); + printf("插入节点 P 后\n"); printTree(n1); - // 删除结点 P + // 删除节点 P n1->left = n2; // 释放内存 free(P); - printf("删除结点 P 后\n"); + printf("删除节点 P 后\n"); printTree(n1); + freeMemoryTree(n1); return 0; } diff --git a/codes/c/chapter_tree/binary_tree_bfs.c b/codes/c/chapter_tree/binary_tree_bfs.c index c7b83b9125..cd9e59ac0a 100644 --- a/codes/c/chapter_tree/binary_tree_bfs.c +++ b/codes/c/chapter_tree/binary_tree_bfs.c @@ -4,7 +4,9 @@ * Author: Reanon (793584285@qq.com) */ -#include "../include/include.h" +#include "../utils/common.h" + +#define MAX_SIZE 100 /* 层序遍历 */ int *levelOrder(TreeNode *root, int *size) { @@ -15,52 +17,57 @@ int *levelOrder(TreeNode *root, int *size) { TreeNode **queue; /* 辅助队列 */ - queue = (TreeNode **) malloc(sizeof(TreeNode) * MAX_NODE_SIZE); + queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); // 队列指针 front = 0, rear = 0; - // 加入根结点 + // 加入根节点 queue[rear++] = root; // 初始化一个列表,用于保存遍历序列 /* 辅助数组 */ - arr = (int *) malloc(sizeof(int) * MAX_NODE_SIZE); + arr = (int *)malloc(sizeof(int) * MAX_SIZE); // 数组指针 index = 0; while (front < rear) { // 队列出队 node = queue[front++]; - // 保存结点 + // 保存节点值 arr[index++] = node->val; if (node->left != NULL) { - // 左子结点入队 + // 左子节点入队 queue[rear++] = node->left; } if (node->right != NULL) { - // 右子结点入队 + // 右子节点入队 queue[rear++] = node->right; } } // 更新数组长度的值 *size = index; arr = realloc(arr, sizeof(int) * (*size)); + + // 释放辅助数组空间 + free(queue); return arr; } - /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - int nums[] = {1, 2, 3, NIL, 5, 6, NIL}; + int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); - TreeNode *root = arrToTree(nums, size); + TreeNode *root = arrayToTree(nums, size); printf("初始化二叉树\n"); printTree(root); /* 层序遍历 */ // 需要传入数组的长度 int *arr = levelOrder(root, &size); - printf("层序遍历的结点打印序列 = "); + printf("层序遍历的节点打印序列 = "); printArray(arr, size); + // 释放内存 + freeMemoryTree(root); + free(arr); return 0; -} \ No newline at end of file +} diff --git a/codes/c/chapter_tree/binary_tree_dfs.c b/codes/c/chapter_tree/binary_tree_dfs.c index bafc90fd19..3e14d7fafc 100644 --- a/codes/c/chapter_tree/binary_tree_dfs.c +++ b/codes/c/chapter_tree/binary_tree_dfs.c @@ -4,16 +4,18 @@ * Author: Reanon (793584285@qq.com) */ -#include "../include/include.h" +#include "../utils/common.h" -/* 辅助数组,用于存储遍历序列 */ -int *arr; +#define MAX_SIZE 100 + +// 辅助数组,用于存储遍历序列 +int arr[MAX_SIZE]; /* 前序遍历 */ void preOrder(TreeNode *root, int *size) { - - if (root == NULL) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 + if (root == NULL) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 arr[(*size)++] = root->val; preOrder(root->left, size); preOrder(root->right, size); @@ -21,8 +23,9 @@ void preOrder(TreeNode *root, int *size) { /* 中序遍历 */ void inOrder(TreeNode *root, int *size) { - if (root == NULL) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 + if (root == NULL) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root->left, size); arr[(*size)++] = root->val; inOrder(root->right, size); @@ -30,43 +33,43 @@ void inOrder(TreeNode *root, int *size) { /* 后序遍历 */ void postOrder(TreeNode *root, int *size) { - if (root == NULL) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 + if (root == NULL) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root->left, size); postOrder(root->right, size); arr[(*size)++] = root->val; } - /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 int nums[] = {1, 2, 3, 4, 5, 6, 7}; int size = sizeof(nums) / sizeof(int); - TreeNode *root = arrToTree(nums, size); + TreeNode *root = arrayToTree(nums, size); printf("初始化二叉树\n"); printTree(root); /* 前序遍历 */ // 初始化辅助数组 - arr = (int *) malloc(sizeof(int) * MAX_NODE_SIZE); size = 0; preOrder(root, &size); - printf("前序遍历的结点打印序列 = "); + printf("前序遍历的节点打印序列 = "); printArray(arr, size); /* 中序遍历 */ size = 0; inOrder(root, &size); - printf("中序遍历的结点打印序列 = "); + printf("中序遍历的节点打印序列 = "); printArray(arr, size); /* 后序遍历 */ size = 0; postOrder(root, &size); - printf("后序遍历的结点打印序列 = "); + printf("后序遍历的节点打印序列 = "); printArray(arr, size); + freeMemoryTree(root); return 0; } diff --git a/codes/c/include/CMakeLists.txt b/codes/c/include/CMakeLists.txt deleted file mode 100644 index 4189ae334a..0000000000 --- a/codes/c/include/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_executable(include - include_test.c - include.h print_util.h - list_node.h tree_node.h) \ No newline at end of file diff --git a/codes/c/include/include.h b/codes/c/include/include.h deleted file mode 100644 index 9f30b05298..0000000000 --- a/codes/c/include/include.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * File: include.h - * Created Time: 2022-12-20 - * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) - */ - -#ifndef C_INCLUDE_H -#define C_INCLUDE_H - -#include -#include -#include -#include -#include - -#include "list_node.h" -#include "tree_node.h" -#include "print_util.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __cplusplus -} -#endif - -#endif // C_INCLUDE_H diff --git a/codes/c/include/include_test.c b/codes/c/include/include_test.c deleted file mode 100644 index 602670d686..0000000000 --- a/codes/c/include/include_test.c +++ /dev/null @@ -1,38 +0,0 @@ -/** - * File: include_test.c - * Created Time: 2023-01-10 - * Author: Reanon (793584285@qq.com) - */ - -#include "include.h" - -void testListNode() { - int nums[] = {2, 3, 5, 6, 7}; - int size = sizeof(nums) / sizeof(int); - ListNode *head = arrToLinkedList(nums, size); - printLinkedList(head); - - ListNode *node = getListNode(head, 5); - printf("find node: %d\n", node->val); -} - -void testTreeNode() { - int nums[] = {1, 2, 3, NIL, 5, 6, NIL}; - int size = sizeof(nums) / sizeof(int); - TreeNode *root = arrToTree(nums, size); - - // print tree - printTree(root); - - // tree to arr - int *arr = treeToArr(root); - printArray(arr, size); -} - -int main(int argc, char *argv[]) { - printf("==testListNode==\n"); - testListNode(); - printf("==testTreeNode==\n"); - testTreeNode(); - return 0; -} diff --git a/codes/c/include/list_node.h b/codes/c/include/list_node.h deleted file mode 100644 index 1845ffafdf..0000000000 --- a/codes/c/include/list_node.h +++ /dev/null @@ -1,72 +0,0 @@ -/** - * File: list_node.h - * Created Time: 2023-01-09 - * Author: Reanon (793584285@qq.com) - */ -#ifndef LIST_NODE_H -#define LIST_NODE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Definition for a singly-linked list node - * - */ -struct ListNode { - int val; // 结点值 - struct ListNode *next; // 指向下一结点的指针(引用) -}; - -// typedef 为 C 语言的关键字,作用是为一种数据类型定义一个新名字 -typedef struct ListNode ListNode; - -ListNode *newListNode(int val) { - ListNode *node, *next; - node = (ListNode *) malloc(sizeof(ListNode)); - node->val = val; - node->next = NULL; - return node; -} - -/** - * @brief Generate a linked list with a vector - * - * @param list - * @return ListNode* - */ - -ListNode *arrToLinkedList(const int *arr, size_t size) { - if (size <= 0) { - return NULL; - } - - ListNode *dummy = newListNode(0); - ListNode *node = dummy; - for (int i = 0; i < size; i++) { - node->next = newListNode(arr[i]); - node = node->next; - } - return dummy->next; -} - -/** - * @brief Get a list node with specific value from a linked list - * - * @param head - * @param val - * @return ListNode* - */ -ListNode *getListNode(ListNode *head, int val) { - while (head != NULL && head->val != val) { - head = head->next; - } - return head; -} - -#ifdef __cplusplus -} -#endif - -#endif // LIST_NODE_H \ No newline at end of file diff --git a/codes/c/include/print_util.h b/codes/c/include/print_util.h deleted file mode 100644 index a0c3a1500e..0000000000 --- a/codes/c/include/print_util.h +++ /dev/null @@ -1,135 +0,0 @@ -/** - * File: print_util.h - * Created Time: 2022-12-21 - * Author: MolDum (moldum@163.com)、Reanon (793584285@qq.com) - */ - -#ifndef PRINT_UTIL_H -#define PRINT_UTIL_H - -#include -#include -#include - -#include "list_node.h" -#include "tree_node.h" - -#ifdef __cplusplus -extern "C" { -#endif - - -/** - * @brief Print an Array - * - * @param arr - * @param size - */ -static void printArray(int *arr, int size) { - printf("["); - for (int i = 0; i < size - 1; i++) { - if (arr[i] != NIL) { - printf("%d, ", arr[i]); - } else { - printf("NULL, "); - } - } - if (arr[size - 1] != NIL) { - printf("%d]\n", arr[size - 1]); - }else{ - printf("NULL]\n"); - } -} - -/** - * @brief Print a linked list - * - * @param head - */ -static void printLinkedList(ListNode *node) { - if (node == NULL) { - return; - } - while (node->next != NULL) { - printf("%d -> ", node->val); - node = node->next; - } - printf("%d\n", node->val); -} - -struct Trunk { - struct Trunk *prev; - char *str; -}; - -typedef struct Trunk Trunk; - -Trunk *newTrunk(Trunk *prev, char *str) { - Trunk *trunk = (Trunk *) malloc(sizeof(Trunk)); - trunk->prev = prev; - trunk->str = (char *) malloc(sizeof(char) * 10); - strcpy(trunk->str, str); - return trunk; -} - -/** - * @brief Helper function to print branches of the binary tree - * - * @param trunk - */ -void showTrunks(Trunk *trunk) { - if (trunk == NULL) { - return; - } - showTrunks(trunk->prev); - printf("%s", trunk->str); -} - -/** - * Help to print a binary tree, hide more details - * @param node - * @param prev - * @param isLeft - */ -static void printTreeHelper(TreeNode *node, Trunk *prev, bool isLeft) { - if (node == NULL) { - return; - } - char *prev_str = " "; - Trunk *trunk = newTrunk(prev, prev_str); - printTreeHelper(node->right, trunk, true); - if (prev == NULL) { - trunk->str = "———"; - } else if (isLeft) { - trunk->str = "/———"; - prev_str = " |"; - } else { - trunk->str = "\\———"; - prev->str = prev_str; - } - showTrunks(trunk); - printf("%d\n", node->val); - - if (prev != NULL) { - prev->str = prev_str; - } - trunk->str = " |"; - - printTreeHelper(node->left, trunk, false); -} - -/** - * @brief Print a binary tree - * - * @param head - */ -static void printTree(TreeNode *root) { - printTreeHelper(root, NULL, false); -} - - -#ifdef __cplusplus -} -#endif - -#endif // PRINT_UTIL_H diff --git a/codes/c/include/tree_node.h b/codes/c/include/tree_node.h deleted file mode 100644 index c0438c437c..0000000000 --- a/codes/c/include/tree_node.h +++ /dev/null @@ -1,131 +0,0 @@ -/** - * File: tree_node.h - * Created Time: 2023-01-09 - * Author: Reanon (793584285@qq.com) - */ - - -#ifndef TREE_NODE_H -#define TREE_NODE_H - -#ifdef __cplusplus -extern "C" { -#endif - -#define NIL ('#') -#define MAX_NODE_SIZE 5000 - -struct TreeNode { - int val; - int height; - struct TreeNode *left; - struct TreeNode *right; -}; - -typedef struct TreeNode TreeNode; - - -TreeNode *newTreeNode(int val) { - TreeNode *node; - - node = (TreeNode *) malloc(sizeof(TreeNode)); - node->val = val; - node->height = 0; - node->left = NULL; - node->right = NULL; - return node; -} - -/** - * @brief Generate a binary tree with an array - * - * @param arr - * @param size - * @return TreeNode * - */ -TreeNode *arrToTree(const int *arr, size_t size) { - if (size <= 0) { - return NULL; - } - - int front, rear, index; - TreeNode *root, *node; - TreeNode **queue; - - /* 根结点 */ - root = newTreeNode(arr[0]); - /* 辅助队列 */ - queue = (TreeNode **) malloc(sizeof(TreeNode) * MAX_NODE_SIZE); - // 队列指针 - front = 0, rear = 0; - // 将根结点放入队尾 - queue[rear++] = root; - // 记录遍历数组的索引 - index = 0; - while (front < rear) { - // 取队列中的头结点,并让头结点出队 - node = queue[front++]; - index++; - if (index < size) { - if (arr[index] != NIL) { - node->left = newTreeNode(arr[index]); - queue[rear++] = node->left; - } - } - index++; - if (index < size) { - if (arr[index] != NIL) { - node->right = newTreeNode(arr[index]); - queue[rear++] = node->right; - } - } - } - return root; -} - - -/** - * @brief Generate a binary tree with an array - * - * @param arr - * @param size - * @return TreeNode * - */ -int *treeToArr(TreeNode *root) { - if (root == NULL) { - return NULL; - } - int front, rear; - int index, *arr; - TreeNode *node; - TreeNode **queue; - /* 辅助队列 */ - queue = (TreeNode **) malloc(sizeof(TreeNode) * MAX_NODE_SIZE); - // 队列指针 - front = 0, rear = 0; - // 将根结点放入队尾 - queue[rear++] = root; - /* 辅助数组 */ - arr = (int *) malloc(sizeof(int) * MAX_NODE_SIZE); - // 数组指针 - index = 0; - while (front < rear) { - // 取队列中的头结点,并让头结点出队 - node = queue[front++]; - if (node != NULL) { - arr[index] = node->val; - queue[rear++] = node->left; - queue[rear++] = node->right; - } else { - arr[index] = NIL; - } - index++; - } - return arr; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_NODE_H diff --git a/codes/c/utils/CMakeLists.txt b/codes/c/utils/CMakeLists.txt new file mode 100644 index 0000000000..c1ece2e38b --- /dev/null +++ b/codes/c/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(utils + common_test.c + common.h print_util.h + list_node.h tree_node.h + uthash.h) \ No newline at end of file diff --git a/codes/c/utils/common.h b/codes/c/utils/common.h new file mode 100644 index 0000000000..8b9adeff7a --- /dev/null +++ b/codes/c/utils/common.h @@ -0,0 +1,36 @@ +/** + * File: common.h + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.h" +#include "print_util.h" +#include "tree_node.h" +#include "vertex.h" + +// hash table lib +#include "uthash.h" + +#include "vector.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_H diff --git a/codes/c/utils/common_test.c b/codes/c/utils/common_test.c new file mode 100644 index 0000000000..a889b423b8 --- /dev/null +++ b/codes/c/utils/common_test.c @@ -0,0 +1,35 @@ +/** + * File: include_test.c + * Created Time: 2023-01-10 + * Author: Reanon (793584285@qq.com) + */ + +#include "common.h" + +void testListNode() { + int nums[] = {2, 3, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + ListNode *head = arrToLinkedList(nums, size); + printLinkedList(head); +} + +void testTreeNode() { + int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + + // print tree + printTree(root); + + // tree to arr + int *arr = treeToArray(root, &size); + printArray(arr, size); +} + +int main(int argc, char *argv[]) { + printf("==testListNode==\n"); + testListNode(); + printf("==testTreeNode==\n"); + testTreeNode(); + return 0; +} diff --git a/codes/c/utils/list_node.h b/codes/c/utils/list_node.h new file mode 100644 index 0000000000..78db687843 --- /dev/null +++ b/codes/c/utils/list_node.h @@ -0,0 +1,59 @@ +/** + * File: list_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef LIST_NODE_H +#define LIST_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 链表节点结构体 */ +typedef struct ListNode { + int val; // 节点值 + struct ListNode *next; // 指向下一节点的引用 +} ListNode; + +/* 构造函数,初始化一个新节点 */ +ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *)malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; +} + +/* 将数组反序列化为链表 */ +ListNode *arrToLinkedList(const int *arr, size_t size) { + if (size <= 0) { + return NULL; + } + + ListNode *dummy = newListNode(0); + ListNode *node = dummy; + for (int i = 0; i < size; i++) { + node->next = newListNode(arr[i]); + node = node->next; + } + return dummy->next; +} + +/* 释放分配给链表的内存空间 */ +void freeMemoryLinkedList(ListNode *cur) { + // 释放内存 + ListNode *pre; + while (cur != NULL) { + pre = cur; + cur = cur->next; + free(pre); + } +} + +#ifdef __cplusplus +} +#endif + +#endif // LIST_NODE_H diff --git a/codes/c/utils/print_util.h b/codes/c/utils/print_util.h new file mode 100644 index 0000000000..7a71a3ce84 --- /dev/null +++ b/codes/c/utils/print_util.h @@ -0,0 +1,131 @@ +/** + * File: print_util.h + * Created Time: 2022-12-21 + * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) + */ + +#ifndef PRINT_UTIL_H +#define PRINT_UTIL_H + +#include +#include +#include + +#include "list_node.h" +#include "tree_node.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 打印数组 */ +void printArray(int arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%d, ", arr[i]); + } + printf("%d]\n", arr[size - 1]); +} + +/* 打印数组 */ +void printArrayFloat(float arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%.2f, ", arr[i]); + } + printf("%.2f]\n", arr[size - 1]); +} + +/* 打印链表 */ +void printLinkedList(ListNode *node) { + if (node == NULL) { + return; + } + while (node->next != NULL) { + printf("%d -> ", node->val); + node = node->next; + } + printf("%d\n", node->val); +} + +typedef struct Trunk { + struct Trunk *prev; + char *str; +} Trunk; + +Trunk *newTrunk(Trunk *prev, char *str) { + Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); + trunk->prev = prev; + trunk->str = (char *)malloc(sizeof(char) * 10); + strcpy(trunk->str, str); + return trunk; +} + +void showTrunks(Trunk *trunk) { + if (trunk == NULL) { + return; + } + showTrunks(trunk->prev); + printf("%s", trunk->str); +} + +/** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { + if (node == NULL) { + return; + } + char *prev_str = " "; + Trunk *trunk = newTrunk(prev, prev_str); + printTreeHelper(node->right, trunk, true); + if (prev == NULL) { + trunk->str = "———"; + } else if (isRight) { + trunk->str = "/———"; + prev_str = " |"; + } else { + trunk->str = "\\———"; + prev->str = prev_str; + } + showTrunks(trunk); + printf("%d\n", node->val); + + if (prev != NULL) { + prev->str = prev_str; + } + trunk->str = " |"; + + printTreeHelper(node->left, trunk, false); +} + +/* 打印二叉树 */ +void printTree(TreeNode *root) { + printTreeHelper(root, NULL, false); +} + +/* 打印堆 */ +void printHeap(int arr[], int size) { + TreeNode *root; + printf("堆的数组表示:"); + printArray(arr, size); + printf("堆的树状表示:\n"); + root = arrayToTree(arr, size); + printTree(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // PRINT_UTIL_H diff --git a/codes/c/utils/tree_node.h b/codes/c/utils/tree_node.h new file mode 100644 index 0000000000..a0ab67fe28 --- /dev/null +++ b/codes/c/utils/tree_node.h @@ -0,0 +1,107 @@ +/** + * File: tree_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef TREE_NODE_H +#define TREE_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define MAX_NODE_SIZE 5000 + +/* 二叉树节点结构体 */ +typedef struct TreeNode { + int val; // 节点值 + int height; // 节点高度 + struct TreeNode *left; // 左子节点指针 + struct TreeNode *right; // 右子节点指针 +} TreeNode; + +/* 构造函数 */ +TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; +} + +// 序列化编码规则请参考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二叉树的数组表示: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二叉树的链表表示: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* 将列表反序列化为二叉树:递归 */ +TreeNode *arrayToTreeDFS(int *arr, int size, int i) { + if (i < 0 || i >= size || arr[i] == INT_MAX) { + return NULL; + } + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = arr[i]; + root->left = arrayToTreeDFS(arr, size, 2 * i + 1); + root->right = arrayToTreeDFS(arr, size, 2 * i + 2); + return root; +} + +/* 将列表反序列化为二叉树 */ +TreeNode *arrayToTree(int *arr, int size) { + return arrayToTreeDFS(arr, size, 0); +} + +/* 将二叉树序列化为列表:递归 */ +void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { + if (root == NULL) { + return; + } + while (i >= *size) { + res = realloc(res, (*size + 1) * sizeof(int)); + res[*size] = INT_MAX; + (*size)++; + } + res[i] = root->val; + treeToArrayDFS(root->left, 2 * i + 1, res, size); + treeToArrayDFS(root->right, 2 * i + 2, res, size); +} + +/* 将二叉树序列化为列表 */ +int *treeToArray(TreeNode *root, int *size) { + *size = 0; + int *res = NULL; + treeToArrayDFS(root, 0, res, size); + return res; +} + +/* 释放二叉树内存 */ +void freeMemoryTree(TreeNode *root) { + if (root == NULL) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + free(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_NODE_H diff --git a/codes/c/utils/uthash.h b/codes/c/utils/uthash.h new file mode 100644 index 0000000000..68693bf396 --- /dev/null +++ b/codes/c/utils/uthash.h @@ -0,0 +1,1140 @@ +/* +Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/codes/c/utils/vector.h b/codes/c/utils/vector.h new file mode 100644 index 0000000000..511b92a9b3 --- /dev/null +++ b/codes/c/utils/vector.h @@ -0,0 +1,259 @@ +/** + * File: vector.h + * Created Time: 2023-07-13 + * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 定义向量类型 */ +typedef struct vector { + int size; // 当前向量的大小 + int capacity; // 当前向量的容量 + int depth; // 当前向量的深度 + void **data; // 指向数据的指针数组 +} vector; + +/* 构造向量 */ +vector *newVector() { + vector *v = malloc(sizeof(vector)); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + return v; +} + +/* 构造向量,指定大小、元素默认值 */ +vector *_newVector(int size, void *elem, int elemSize) { + vector *v = malloc(sizeof(vector)); + v->size = size; + v->capacity = size; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + for (int i = 0; i < size; i++) { + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[i] = tmp; + } + return v; +} + +/* 析构向量 */ +void delVector(vector *v) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + for (int i = 0; i < v->size; i++) { + free(v->data[i]); + } + free(v); + } else { + for (int i = 0; i < v->size; i++) { + delVector(v->data[i]); + } + v->depth--; + } + } +} + +/* 添加元素(拷贝方式)到向量尾部 */ +void vectorPushback(vector *v, void *elem, int elemSize) { + if (v->size == v->capacity) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[v->size++] = tmp; +} + +/* 从向量尾部弹出元素 */ +void vectorPopback(vector *v) { + if (v->size != 0) { + free(v->data[v->size - 1]); + v->size--; + } +} + +/* 清空向量 */ +void vectorClear(vector *v) { + delVector(v); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); +} + +/* 获取向量的大小 */ +int vectorSize(vector *v) { + return v->size; +} + +/* 获取向量的尾元素 */ +void *vectorBack(vector *v) { + int n = v->size; + return n > 0 ? v->data[n - 1] : NULL; +} + +/* 获取向量的头元素 */ +void *vectorFront(vector *v) { + return v->size > 0 ? v->data[0] : NULL; +} + +/* 获取向量下标 pos 的元素 */ +void *vectorAt(vector *v, int pos) { + if (pos < 0 || pos >= v->size) { + printf("vectorAt: out of range\n"); + return NULL; + } + return v->data[pos]; +} + +/* 设置向量下标 pos 的元素 */ +void vectorSet(vector *v, int pos, void *elem, int elemSize) { + if (pos < 0 || pos >= v->size) { + printf("vectorSet: out of range\n"); + return; + } + free(v->data[pos]); + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; +} + +/* 向量扩容 */ +void vectorExpand(vector *v) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* 向量缩容 */ +void vectorShrink(vector *v) { + v->capacity /= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* 在向量下标 pos 处插入元素 */ +void vectorInsert(vector *v, int pos, void *elem, int elemSize) { + if (v->size == v->capacity) { + vectorExpand(v); + } + for (int j = v->size; j > pos; j--) { + v->data[j] = v->data[j - 1]; + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; + v->size++; +} + +/* 删除向量下标 pos 处的元素 */ +void vectorErase(vector *v, int pos) { + if (v->size != 0) { + free(v->data[pos]); + for (int j = pos; j < v->size - 1; j++) { + v->data[j] = v->data[j + 1]; + } + v->size--; + } +} + +/* 向量交换元素 */ +void vectorSwap(vector *v, int i, int j) { + void *tmp = v->data[i]; + v->data[i] = v->data[j]; + v->data[j] = tmp; +} + +/* 向量是否为空 */ +bool vectorEmpty(vector *v) { + return v->size == 0; +} + +/* 向量是否已满 */ +bool vectorFull(vector *v) { + return v->size == v->capacity; +} + +/* 向量是否相等 */ +bool vectorEqual(vector *v1, vector *v2) { + if (v1->size != v2->size) { + printf("size not equal\n"); + return false; + } + for (int i = 0; i < v1->size; i++) { + void *a = v1->data[i]; + void *b = v2->data[i]; + if (memcmp(a, b, sizeof(a)) != 0) { + printf("data %d not equal\n", i); + return false; + } + } + return true; +} + +/* 对向量内部进行排序 */ +void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { + qsort(v->data, v->size, sizeof(void *), cmp); +} + +/* 打印函数, 需传递一个打印变量的函数进来 */ +/* 当前仅支持打印深度为 1 的 vector */ +void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + if(v->size == 0) { + printf("\n"); + return; + } + for (int i = 0; i < v->size; i++) { + if (i == 0) { + printf("["); + } else if (i == v->size - 1) { + printFunc(v, v->data[i]); + printf("]\r\n"); + break; + } + printFunc(v, v->data[i]); + printf(","); + } + } else { + for (int i = 0; i < v->size; i++) { + printVector(v->data[i], printFunc); + } + v->depth--; + } + } +} + +/* 当前仅支持打印深度为 2 的 vector */ +void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { + printf("[\n"); + for (int i = 0; i < vv->size; i++) { + vector *v = (vector *)vv->data[i]; + printf(" ["); + for (int j = 0; j < v->size; j++) { + printFunc(v, v->data[j]); + if (j != v->size - 1) + printf(","); + } + printf("],"); + printf("\n"); + } + printf("]\n"); +} + +#ifdef __cplusplus +} +#endif + +#endif // VECTOR_H diff --git a/codes/c/utils/vertex.h b/codes/c/utils/vertex.h new file mode 100644 index 0000000000..ff4a60e5a4 --- /dev/null +++ b/codes/c/utils/vertex.h @@ -0,0 +1,49 @@ +/** + * File: vertex.h + * Created Time: 2023-10-28 + * Author: krahets (krahets@163.com) + */ + +#ifndef VERTEX_H +#define VERTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 顶点结构体 */ +typedef struct { + int val; +} Vertex; + +/* 构造函数,初始化一个新节点 */ +Vertex *newVertex(int val) { + Vertex *vet; + vet = (Vertex *)malloc(sizeof(Vertex)); + vet->val = val; + return vet; +} + +/* 将值数组转换为顶点数组 */ +Vertex **valsToVets(int *vals, int size) { + Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); + for (int i = 0; i < size; ++i) { + vertices[i] = newVertex(vals[i]); + } + return vertices; +} + +/* 将顶点数组转换为值数组 */ +int *vetsToVals(Vertex **vertices, int size) { + int *vals = (int *)malloc(size * sizeof(int)); + for (int i = 0; i < size; ++i) { + vals[i] = vertices[i]->val; + } + return vals; +} + +#ifdef __cplusplus +} +#endif + +#endif // VERTEX_H diff --git a/codes/cpp/.gitignore b/codes/cpp/.gitignore index 08b67f32cc..dc1ffacf49 100644 --- a/codes/cpp/.gitignore +++ b/codes/cpp/.gitignore @@ -5,4 +5,6 @@ # Unignore all dirs !*/ -*.dSYM/ \ No newline at end of file +*.dSYM/ + +build/ diff --git a/codes/cpp/CMakeLists.txt b/codes/cpp/CMakeLists.txt new file mode 100644 index 0000000000..1e80bc4d78 --- /dev/null +++ b/codes/cpp/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt b/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 0000000000..2e933e0163 --- /dev/null +++ b/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array array.cpp) +add_executable(linked_list linked_list.cpp) +add_executable(list list.cpp) +add_executable(my_list my_list.cpp) diff --git a/codes/cpp/chapter_array_and_linkedlist/array.cpp b/codes/cpp/chapter_array_and_linkedlist/array.cpp index 6cf4919be7..dd87b92405 100644 --- a/codes/cpp/chapter_array_and_linkedlist/array.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -1,13 +1,13 @@ /** * File: array.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" -/* 随机返回一个数组元素 */ -int randomAccess(int* nums, int size) { +/* 随机访问元素 */ +int randomAccess(int *nums, int size) { // 在区间 [0, size) 中随机抽取一个数字 int randomIndex = rand() % size; // 获取并返回随机元素 @@ -16,29 +16,31 @@ int randomAccess(int* nums, int size) { } /* 扩展数组长度 */ -int* extend(int* nums, int size, int enlarge) { +int *extend(int *nums, int size, int enlarge) { // 初始化一个扩展长度后的数组 - int* res = new int[size + enlarge]; + int *res = new int[size + enlarge]; // 将原数组中的所有元素复制到新数组 for (int i = 0; i < size; i++) { res[i] = nums[i]; } + // 释放内存 + delete[] nums; // 返回扩展后的新数组 return res; } /* 在数组的索引 index 处插入元素 num */ -void insert(int* nums, int size, int num, int index) { +void insert(int *nums, int size, int num, int index) { // 把索引 index 以及之后的所有元素向后移动一位 for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num; } -/* 删除索引 index 处元素 */ -void remove(int* nums, int size, int index) { +/* 删除索引 index 处的元素 */ +void remove(int *nums, int size, int index) { // 把索引 index 之后的所有元素向前移动一位 for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; @@ -46,16 +48,16 @@ void remove(int* nums, int size, int index) { } /* 遍历数组 */ -void traverse(int* nums, int size) { +void traverse(int *nums, int size) { int count = 0; // 通过索引遍历数组 for (int i = 0; i < size; i++) { - count++; + count += nums[i]; } } /* 在数组中查找指定元素 */ -int find(int* nums, int size, int target) { +int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) return i; @@ -63,49 +65,49 @@ int find(int* nums, int size, int target) { return -1; } - /* Driver Code */ int main() { /* 初始化数组 */ int size = 5; - int* arr = new int[size]; + int *arr = new int[size]; cout << "数组 arr = "; - PrintUtil::printArray(arr, size); + printArray(arr, size); - int* nums = new int[size] { 1, 3, 2, 5, 4 }; + int *nums = new int[size]{1, 3, 2, 5, 4}; cout << "数组 nums = "; - PrintUtil::printArray(nums, size); - + printArray(nums, size); + /* 随机访问 */ int randomNum = randomAccess(nums, size); cout << "在 nums 中获取随机元素 " << randomNum << endl; - + /* 长度扩展 */ int enlarge = 3; - int* res = extend(nums, size, enlarge); - int* temp = nums; - nums = res; - delete[] temp; + nums = extend(nums, size, enlarge); size += enlarge; cout << "将数组长度扩展至 8 ,得到 nums = "; - PrintUtil::printArray(nums, size); - + printArray(nums, size); + /* 插入元素 */ insert(nums, size, 6, 3); cout << "在索引 3 处插入数字 6 ,得到 nums = "; - PrintUtil::printArray(nums, size); + printArray(nums, size); /* 删除元素 */ remove(nums, size, 2); cout << "删除索引 2 处的元素,得到 nums = "; - PrintUtil::printArray(nums, size); - + printArray(nums, size); + /* 遍历数组 */ traverse(nums, size); - + /* 查找元素 */ int index = find(nums, size, 3); cout << "在 nums 中查找元素 3 ,得到索引 = " << index << endl; + // 释放内存 + delete[] arr; + delete[] nums; + return 0; } diff --git a/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp index 5e976a89aa..092226e0cf 100644 --- a/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -1,32 +1,32 @@ /** * File: linked_list.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" -/* 在链表的结点 n0 之后插入结点 P */ -void insert(ListNode* n0, ListNode* P) { - ListNode* n1 = n0->next; - n0->next = P; +/* 在链表的节点 n0 之后插入节点 P */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; P->next = n1; + n0->next = P; } -/* 删除链表的结点 n0 之后的首个结点 */ -void remove(ListNode* n0) { +/* 删除链表的节点 n0 之后的首个节点 */ +void remove(ListNode *n0) { if (n0->next == nullptr) return; // n0 -> P -> n1 - ListNode* P = n0->next; - ListNode* n1 = P->next; + ListNode *P = n0->next; + ListNode *n1 = P->next; n0->next = n1; // 释放内存 delete P; } -/* 访问链表中索引为 index 的结点 */ -ListNode* access(ListNode* head, int index) { +/* 访问链表中索引为 index 的节点 */ +ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == nullptr) return nullptr; @@ -35,8 +35,8 @@ ListNode* access(ListNode* head, int index) { return head; } -/* 在链表中查找值为 target 的首个结点 */ -int find(ListNode* head, int target) { +/* 在链表中查找值为 target 的首个节点 */ +int find(ListNode *head, int target) { int index = 0; while (head != nullptr) { if (head->val == target) @@ -47,41 +47,43 @@ int find(ListNode* head, int target) { return -1; } - /* Driver Code */ int main() { /* 初始化链表 */ - // 初始化各个结点 - ListNode* n0 = new ListNode(1); - ListNode* n1 = new ListNode(3); - ListNode* n2 = new ListNode(2); - ListNode* n3 = new ListNode(5); - ListNode* n4 = new ListNode(4); - // 构建引用指向 + // 初始化各个节点 + ListNode *n0 = new ListNode(1); + ListNode *n1 = new ListNode(3); + ListNode *n2 = new ListNode(2); + ListNode *n3 = new ListNode(5); + ListNode *n4 = new ListNode(4); + // 构建节点之间的引用 n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; cout << "初始化的链表为" << endl; - PrintUtil::printLinkedList(n0); + printLinkedList(n0); - /* 插入结点 */ + /* 插入节点 */ insert(n0, new ListNode(0)); - cout << "插入结点后的链表为" << endl; - PrintUtil::printLinkedList(n0); + cout << "插入节点后的链表为" << endl; + printLinkedList(n0); - /* 删除结点 */ + /* 删除节点 */ remove(n0); - cout << "删除结点后的链表为" << endl; - PrintUtil::printLinkedList(n0); + cout << "删除节点后的链表为" << endl; + printLinkedList(n0); - /* 访问结点 */ - ListNode* node = access(n0, 3); - cout << "链表中索引 3 处的结点的值 = " << node->val << endl; + /* 访问节点 */ + ListNode *node = access(n0, 3); + cout << "链表中索引 3 处的节点的值 = " << node->val << endl; - /* 查找结点 */ + /* 查找节点 */ int index = find(n0, 2); - cout << "链表中值为 2 的结点的索引 = " << index << endl; + cout << "链表中值为 2 的节点的索引 = " << index << endl; + + // 释放内存 + freeMemoryLinkedList(n0); return 0; } diff --git a/codes/cpp/chapter_array_and_linkedlist/list.cpp b/codes/cpp/chapter_array_and_linkedlist/list.cpp index 44bbf88c86..e0502cc17d 100644 --- a/codes/cpp/chapter_array_and_linkedlist/list.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -1,74 +1,72 @@ /** * File: list.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" - +#include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化列表 */ - vector list = { 1, 3, 2, 5, 4 }; - cout << "列表 list = "; - PrintUtil::printVector(list); + vector nums = {1, 3, 2, 5, 4}; + cout << "列表 nums = "; + printVector(nums); /* 访问元素 */ - int num = list[1]; + int num = nums[1]; cout << "访问索引 1 处的元素,得到 num = " << num << endl; /* 更新元素 */ - list[1] = 0; - cout << "将索引 1 处的元素更新为 0 ,得到 list = "; - PrintUtil::printVector(list); + nums[1] = 0; + cout << "将索引 1 处的元素更新为 0 ,得到 nums = "; + printVector(nums); /* 清空列表 */ - list.clear(); - cout << "清空列表后 list = "; - PrintUtil::printVector(list); + nums.clear(); + cout << "清空列表后 nums = "; + printVector(nums); - /* 尾部添加元素 */ - list.push_back(1); - list.push_back(3); - list.push_back(2); - list.push_back(5); - list.push_back(4); - cout << "添加元素后 list = "; - PrintUtil::printVector(list); + /* 在尾部添加元素 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + cout << "添加元素后 nums = "; + printVector(nums); - /* 中间插入元素 */ - list.insert(list.begin() + 3, 6); - cout << "在索引 3 处插入数字 6 ,得到 list = "; - PrintUtil::printVector(list); + /* 在中间插入元素 */ + nums.insert(nums.begin() + 3, 6); + cout << "在索引 3 处插入数字 6 ,得到 nums = "; + printVector(nums); /* 删除元素 */ - list.erase(list.begin() + 3); - cout << "删除索引 3 处的元素,得到 list = "; - PrintUtil::printVector(list); + nums.erase(nums.begin() + 3); + cout << "删除索引 3 处的元素,得到 nums = "; + printVector(nums); /* 通过索引遍历列表 */ int count = 0; - for (int i = 0; i < list.size(); i++) { - count++; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; } - /* 直接遍历列表元素 */ count = 0; - for (int n : list) { - count++; + for (int x : nums) { + count += x; } /* 拼接两个列表 */ - vector list1 = { 6, 8, 7, 10, 9 }; - list.insert(list.end(), list1.begin(), list1.end()); - cout << "将列表 list1 拼接到 list 之后,得到 list = "; - PrintUtil::printVector(list); + vector nums1 = {6, 8, 7, 10, 9}; + nums.insert(nums.end(), nums1.begin(), nums1.end()); + cout << "将列表 nums1 拼接到 nums 之后,得到 nums = "; + printVector(nums); /* 排序列表 */ - sort(list.begin(), list.end()); - cout << "排序列表后 list = "; - PrintUtil::printVector(list); - + sort(nums.begin(), nums.end()); + cout << "排序列表后 nums = "; + printVector(nums); + return 0; } diff --git a/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/codes/cpp/chapter_array_and_linkedlist/my_list.cpp index 0b550475e6..4fff94b112 100644 --- a/codes/cpp/chapter_array_and_linkedlist/my_list.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -1,104 +1,109 @@ /** * File: my_list.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" -/* 列表类简易实现 */ +/* 列表类 */ class MyList { -private: - int* nums; // 数组(存储列表元素) - int numsCapacity = 10; // 列表容量 - int numsSize = 0; // 列表长度(即当前元素数量) - int extendRatio = 2; // 每次列表扩容的倍数 - -public: - /* 构造函数 */ + private: + int *arr; // 数组(存储列表元素) + int arrCapacity = 10; // 列表容量 + int arrSize = 0; // 列表长度(当前元素数量) + int extendRatio = 2; // 每次列表扩容的倍数 + + public: + /* 构造方法 */ MyList() { - nums = new int[numsCapacity]; + arr = new int[arrCapacity]; } - /* 获取列表长度(即当前元素数量)*/ + /* 析构方法 */ + ~MyList() { + delete[] arr; + } + + /* 获取列表长度(当前元素数量)*/ int size() { - return numsSize; + return arrSize; } /* 获取列表容量 */ int capacity() { - return numsCapacity; + return arrCapacity; } /* 访问元素 */ int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index >= size()) + // 索引如果越界,则抛出异常,下同 + if (index < 0 || index >= size()) throw out_of_range("索引越界"); - return nums[index]; + return arr[index]; } /* 更新元素 */ void set(int index, int num) { - if (index >= size()) + if (index < 0 || index >= size()) throw out_of_range("索引越界"); - nums[index] = num; + arr[index] = num; } - /* 尾部添加元素 */ + /* 在尾部添加元素 */ void add(int num) { // 元素数量超出容量时,触发扩容机制 if (size() == capacity()) extendCapacity(); - nums[size()] = num; + arr[size()] = num; // 更新元素数量 - numsSize++; + arrSize++; } - /* 中间插入元素 */ + /* 在中间插入元素 */ void insert(int index, int num) { - if (index >= size()) + if (index < 0 || index >= size()) throw out_of_range("索引越界"); // 元素数量超出容量时,触发扩容机制 if (size() == capacity()) extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 + // 将索引 index 以及之后的元素都向后移动一位 for (int j = size() - 1; j >= index; j--) { - nums[j + 1] = nums[j]; + arr[j + 1] = arr[j]; } - nums[index] = num; + arr[index] = num; // 更新元素数量 - numsSize++; + arrSize++; } /* 删除元素 */ int remove(int index) { - if (index >= size()) + if (index < 0 || index >= size()) throw out_of_range("索引越界"); - int num = nums[index]; - // 索引 i 之后的元素都向前移动一位 + int num = arr[index]; + // 将索引 index 之后的元素都向前移动一位 for (int j = index; j < size() - 1; j++) { - nums[j] = nums[j + 1]; + arr[j] = arr[j + 1]; } // 更新元素数量 - numsSize--; - // 返回被删除元素 + arrSize--; + // 返回被删除的元素 return num; } /* 列表扩容 */ void extendCapacity() { - // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组 + // 新建一个长度为原数组 extendRatio 倍的新数组 int newCapacity = capacity() * extendRatio; - int* extend = new int[newCapacity]; + int *tmp = arr; + arr = new int[newCapacity]; // 将原数组中的所有元素复制到新数组 for (int i = 0; i < size(); i++) { - extend[i] = nums[i]; + arr[i] = tmp[i]; } - int* temp = nums; - nums = extend; - delete[] temp; - numsCapacity = newCapacity; + // 释放内存 + delete[] tmp; + arrCapacity = newCapacity; } /* 将列表转换为 Vector 用于打印 */ @@ -106,59 +111,61 @@ class MyList { // 仅转换有效长度范围内的列表元素 vector vec(size()); for (int i = 0; i < size(); i++) { - vec[i] = nums[i]; + vec[i] = arr[i]; } return vec; } }; - /* Driver Code */ int main() { /* 初始化列表 */ - MyList *list = new MyList(); - /* 尾部添加元素 */ - list->add(1); - list->add(3); - list->add(2); - list->add(5); - list->add(4); - cout << "列表 list = "; - vector vec = list->toVector(); - PrintUtil::printVector(vec); - cout << "容量 = " << list->capacity() << " ,长度 = " << list->size() << endl; - - /* 中间插入元素 */ - list->insert(3, 6); - cout << "在索引 3 处插入数字 6 ,得到 list = "; - vec = list->toVector(); - PrintUtil::printVector(vec); + MyList *nums = new MyList(); + /* 在尾部添加元素 */ + nums->add(1); + nums->add(3); + nums->add(2); + nums->add(5); + nums->add(4); + cout << "列表 nums = "; + vector vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,长度 = " << nums->size() << endl; + + /* 在中间插入元素 */ + nums->insert(3, 6); + cout << "在索引 3 处插入数字 6 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); /* 删除元素 */ - list->remove(3); - cout << "删除索引 3 处的元素,得到 list = "; - vec = list->toVector(); - PrintUtil::printVector(vec); + nums->remove(3); + cout << "删除索引 3 处的元素,得到 nums = "; + vec = nums->toVector(); + printVector(vec); /* 访问元素 */ - int num = list->get(1); + int num = nums->get(1); cout << "访问索引 1 处的元素,得到 num = " << num << endl; /* 更新元素 */ - list->set(1, 0); - cout << "将索引 1 处的元素更新为 0 ,得到 list = "; - vec = list->toVector(); - PrintUtil::printVector(vec); + nums->set(1, 0); + cout << "将索引 1 处的元素更新为 0 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); /* 测试扩容机制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list->add(i); + nums->add(i); } - cout << "扩容后的列表 list = "; - vec = list->toVector(); - PrintUtil::printVector(vec); - cout << "容量 = " << list->capacity() << " ,长度 = " << list->size() << endl; + cout << "扩容后的列表 nums = "; + vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,长度 = " << nums->size() << endl; + + // 释放内存 + delete nums; return 0; } diff --git a/codes/cpp/chapter_backtracking/CMakeLists.txt b/codes/cpp/chapter_backtracking/CMakeLists.txt new file mode 100644 index 0000000000..6c271e330b --- /dev/null +++ b/codes/cpp/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) +add_executable(permutations_i permutations_i.cpp) +add_executable(permutations_ii permutations_ii.cpp) +add_executable(n_queens n_queens.cpp) +add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) +add_executable(subset_sum_i subset_sum_i.cpp) +add_executable(subset_sum_ii subset_sum_ii.cpp) diff --git a/codes/cpp/chapter_backtracking/n_queens.cpp b/codes/cpp/chapter_backtracking/n_queens.cpp new file mode 100644 index 0000000000..9f759d75e5 --- /dev/null +++ b/codes/cpp/chapter_backtracking/n_queens.cpp @@ -0,0 +1,65 @@ +/** + * File: n_queens.cpp + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:n 皇后 */ +void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, + vector &diags1, vector &diags2) { + // 当放置完所有行时,记录解 + if (row == n) { + res.push_back(state); + return; + } + // 遍历所有列 + for (int col = 0; col < n; col++) { + // 计算该格子对应的主对角线和次对角线 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 尝试:将皇后放置在该格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +vector>> nQueens(int n) { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + vector> state(n, vector(n, "#")); + vector cols(n, false); // 记录列是否有皇后 + vector diags1(2 * n - 1, false); // 记录主对角线上是否有皇后 + vector diags2(2 * n - 1, false); // 记录次对角线上是否有皇后 + vector>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + vector>> res = nQueens(n); + + cout << "输入棋盘长宽为 " << n << endl; + cout << "皇后放置方案共有 " << res.size() << " 种" << endl; + for (const vector> &state : res) { + cout << "--------------------" << endl; + for (const vector &row : state) { + printVector(row); + } + } + + return 0; +} diff --git a/codes/cpp/chapter_backtracking/permutations_i.cpp b/codes/cpp/chapter_backtracking/permutations_i.cpp new file mode 100644 index 0000000000..a72fa6ce9d --- /dev/null +++ b/codes/cpp/chapter_backtracking/permutations_i.cpp @@ -0,0 +1,54 @@ +/** + * File: permutations_i.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:全排列 I */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.push_back(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 I */ +vector> permutationsI(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 2, 3}; + + vector> res = permutationsI(nums); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/codes/cpp/chapter_backtracking/permutations_ii.cpp b/codes/cpp/chapter_backtracking/permutations_ii.cpp new file mode 100644 index 0000000000..e3e3e7ec88 --- /dev/null +++ b/codes/cpp/chapter_backtracking/permutations_ii.cpp @@ -0,0 +1,56 @@ +/** + * File: permutations_ii.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:全排列 II */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 遍历所有选择 + unordered_set duplicated; + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && duplicated.find(choice) == duplicated.end()) { + // 尝试:做出选择,更新状态 + duplicated.emplace(choice); // 记录选择过的元素值 + selected[i] = true; + state.push_back(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 II */ +vector> permutationsII(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 1, 2}; + + vector> res = permutationsII(nums); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp b/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp new file mode 100644 index 0000000000..d1d80bdfad --- /dev/null +++ b/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -0,0 +1,39 @@ +/** + * File: preorder_traversal_i_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector res; + +/* 前序遍历:例题一 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // 记录解 + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 前序遍历 + preOrder(root); + + cout << "\n输出所有值为 7 的节点" << endl; + vector vals; + for (TreeNode *node : res) { + vals.push_back(node->val); + } + printVector(vals); +} diff --git a/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp b/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp new file mode 100644 index 0000000000..3d75ae83d1 --- /dev/null +++ b/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -0,0 +1,46 @@ +/** + * File: preorder_traversal_ii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序遍历:例题二 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // 尝试 + path.push_back(root); + if (root->val == 7) { + // 记录解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 前序遍历 + preOrder(root); + + cout << "\n输出所有根节点到节点 7 的路径" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp b/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp new file mode 100644 index 0000000000..28a73bde7f --- /dev/null +++ b/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序遍历:例题三 */ +void preOrder(TreeNode *root) { + // 剪枝 + if (root == nullptr || root->val == 3) { + return; + } + // 尝试 + path.push_back(root); + if (root->val == 7) { + // 记录解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 前序遍历 + preOrder(root); + + cout << "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp new file mode 100644 index 0000000000..c48fb3d9f2 --- /dev/null +++ b/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 判断当前状态是否为解 */ +bool isSolution(vector &state) { + return !state.empty() && state.back()->val == 7; +} + +/* 记录解 */ +void recordSolution(vector &state, vector> &res) { + res.push_back(state); +} + +/* 判断在当前状态下,该选择是否合法 */ +bool isValid(vector &state, TreeNode *choice) { + return choice != nullptr && choice->val != 3; +} + +/* 更新状态 */ +void makeChoice(vector &state, TreeNode *choice) { + state.push_back(choice); +} + +/* 恢复状态 */ +void undoChoice(vector &state, TreeNode *choice) { + state.pop_back(); +} + +/* 回溯算法:例题三 */ +void backtrack(vector &state, vector &choices, vector> &res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + for (TreeNode *choice : choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + vector nextChoices{choice->left, choice->right}; + backtrack(state, nextChoices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 回溯算法 + vector state; + vector choices = {root}; + vector> res; + backtrack(state, choices, res); + + cout << "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/codes/cpp/chapter_backtracking/subset_sum_i.cpp b/codes/cpp/chapter_backtracking/subset_sum_i.cpp new file mode 100644 index 0000000000..fe4999c43d --- /dev/null +++ b/codes/cpp/chapter_backtracking/subset_sum_i.cpp @@ -0,0 +1,57 @@ +/** + * File: subset_sum_i.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:子集和 I */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.push_back(state); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.push_back(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop_back(); + } +} + +/* 求解子集和 I */ +vector> subsetSumI(vector &nums, int target) { + vector state; // 状态(子集) + sort(nums.begin(), nums.end()); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + vector> res; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumI(nums, target); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等于 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp b/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp new file mode 100644 index 0000000000..64647bd9ae --- /dev/null +++ b/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:子集和 I */ +void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.push_back(state); + return; + } + // 遍历所有选择 + for (size_t i = 0; i < choices.size(); i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.push_back(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop_back(); + } +} + +/* 求解子集和 I(包含重复子集) */ +vector> subsetSumINaive(vector &nums, int target) { + vector state; // 状态(子集) + int total = 0; // 子集和 + vector> res; // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumINaive(nums, target); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等于 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/codes/cpp/chapter_backtracking/subset_sum_ii.cpp b/codes/cpp/chapter_backtracking/subset_sum_ii.cpp new file mode 100644 index 0000000000..b233581a8d --- /dev/null +++ b/codes/cpp/chapter_backtracking/subset_sum_ii.cpp @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:子集和 II */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.push_back(state); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.push_back(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop_back(); + } +} + +/* 求解子集和 II */ +vector> subsetSumII(vector &nums, int target) { + vector state; // 状态(子集) + sort(nums.begin(), nums.end()); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + vector> res; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {4, 4, 5}; + int target = 9; + + vector> res = subsetSumII(nums, target); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等于 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/codes/cpp/chapter_computational_complexity/CMakeLists.txt b/codes/cpp/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 0000000000..ea2845b751 --- /dev/null +++ b/codes/cpp/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.cpp) +add_executable(recursion recursion.cpp) +add_executable(space_complexity space_complexity.cpp) +add_executable(time_complexity time_complexity.cpp) +add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) \ No newline at end of file diff --git a/codes/cpp/chapter_computational_complexity/iteration.cpp b/codes/cpp/chapter_computational_complexity/iteration.cpp new file mode 100644 index 0000000000..ef262b75dc --- /dev/null +++ b/codes/cpp/chapter_computational_complexity/iteration.cpp @@ -0,0 +1,76 @@ +/** + * File: iteration.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* for 循环 */ +int forLoop(int n) { + int res = 0; + // 循环求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + res += i; + } + return res; +} + +/* while 循环 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; +} + +/* while 循环(两次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; +} + +/* 双层 for 循环 */ +string nestedForLoop(int n) { + ostringstream res; + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; ++j) { + res << "(" << i << ", " << j << "), "; + } + } + return res.str(); +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + cout << "\nfor 循环的求和结果 res = " << res << endl; + + res = whileLoop(n); + cout << "\nwhile 循环的求和结果 res = " << res << endl; + + res = whileLoopII(n); + cout << "\nwhile 循环(两次更新)求和结果 res = " << res << endl; + + string resStr = nestedForLoop(n); + cout << "\n双层 for 循环的遍历结果 " << resStr << endl; + + return 0; +} diff --git a/codes/cpp/chapter_computational_complexity/leetcode_two_sum.cpp b/codes/cpp/chapter_computational_complexity/leetcode_two_sum.cpp deleted file mode 100644 index dd561d5442..0000000000 --- a/codes/cpp/chapter_computational_complexity/leetcode_two_sum.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/** - * File: leetcode_two_sum.cpp - * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) - */ - -#include "../include/include.hpp" - -class SolutionBruteForce { -public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return { i, j }; - } - } - return {}; - } -}; - -class SolutionHashMap { -public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 辅助哈希表,空间复杂度 O(n) - unordered_map dic; - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.find(target - nums[i]) != dic.end()) { - return { dic[target - nums[i]], i }; - } - dic.emplace(nums[i], i); - } - return {}; - } -}; - - -int main() { - // ======= Test Case ======= - vector nums = { 2,7,11,15 }; - int target = 9; - - // ====== Driver Code ====== - // 方法一 - SolutionBruteForce* slt1 = new SolutionBruteForce(); - vector res = slt1->twoSum(nums, target); - cout << "方法一 res = "; - PrintUtil::printVector(res); - // 方法二 - SolutionHashMap* slt2 = new SolutionHashMap(); - res = slt2->twoSum(nums, target); - cout << "方法二 res = "; - PrintUtil::printVector(res); - - return 0; -} diff --git a/codes/cpp/chapter_computational_complexity/recursion.cpp b/codes/cpp/chapter_computational_complexity/recursion.cpp new file mode 100644 index 0000000000..3781556a87 --- /dev/null +++ b/codes/cpp/chapter_computational_complexity/recursion.cpp @@ -0,0 +1,78 @@ +/** + * File: recursion.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 递归 */ +int recur(int n) { + // 终止条件 + if (n == 1) + return 1; + // 递:递归调用 + int res = recur(n - 1); + // 归:返回结果 + return n + res; +} + +/* 使用迭代模拟递归 */ +int forLoopRecur(int n) { + // 使用一个显式的栈来模拟系统调用栈 + stack stack; + int res = 0; + // 递:递归调用 + for (int i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack.push(i); + } + // 归:返回结果 + while (!stack.empty()) { + // 通过“出栈操作”模拟“归” + res += stack.top(); + stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾递归 */ +int tailRecur(int n, int res) { + // 终止条件 + if (n == 0) + return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); +} + +/* 斐波那契数列:递归 */ +int fib(int n) { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + cout << "\n递归函数的求和结果 res = " << res << endl; + + res = forLoopRecur(n); + cout << "\n使用迭代模拟递归求和结果 res = " << res << endl; + + res = tailRecur(n, 0); + cout << "\n尾递归函数的求和结果 res = " << res << endl; + + res = fib(n); + cout << "\n斐波那契数列的第 " << n << " 项为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/codes/cpp/chapter_computational_complexity/space_complexity.cpp index 6352b9d47e..a41e7e1068 100644 --- a/codes/cpp/chapter_computational_complexity/space_complexity.cpp +++ b/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -1,14 +1,14 @@ /** * File: space_complexity.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 函数 */ int func() { - // do something + // 执行某些操作 return 0; } @@ -18,7 +18,7 @@ void constant(int n) { const int a = 0; int b = 0; vector nums(10000); - ListNode* node = new ListNode(0); + ListNode node(0); // 循环中的变量占用 O(1) 空间 for (int i = 0; i < n; i++) { int c = 0; @@ -34,9 +34,9 @@ void linear(int n) { // 长度为 n 的数组占用 O(n) 空间 vector nums(n); // 长度为 n 的列表占用 O(n) 空间 - vector nodes; + vector nodes; for (int i = 0; i < n; i++) { - nodes.push_back(new ListNode(i)); + nodes.push_back(ListNode(i)); } // 长度为 n 的哈希表占用 O(n) 空间 unordered_map map; @@ -48,7 +48,8 @@ void linear(int n) { /* 线性阶(递归实现) */ void linearRecur(int n) { cout << "递归 n = " << n << endl; - if (n == 1) return; + if (n == 1) + return; linearRecur(n - 1); } @@ -67,22 +68,23 @@ void quadratic(int n) { /* 平方阶(递归实现) */ int quadraticRecur(int n) { - if (n <= 0) return 0; + if (n <= 0) + return 0; vector nums(n); cout << "递归 n = " << n << " 中的 nums 长度 = " << nums.size() << endl; return quadraticRecur(n - 1); } /* 指数阶(建立满二叉树) */ -TreeNode* buildTree(int n) { - if (n == 0) return nullptr; - TreeNode* root = new TreeNode(0); +TreeNode *buildTree(int n) { + if (n == 0) + return nullptr; + TreeNode *root = new TreeNode(0); root->left = buildTree(n - 1); root->right = buildTree(n - 1); return root; } - /* Driver Code */ int main() { int n = 5; @@ -95,8 +97,11 @@ int main() { quadratic(n); quadraticRecur(n); // 指数阶 - TreeNode* root = buildTree(n); - PrintUtil::printTree(root); + TreeNode *root = buildTree(n); + printTree(root); + + // 释放内存 + freeMemoryTree(root); return 0; } diff --git a/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/codes/cpp/chapter_computational_complexity/time_complexity.cpp index cb1a207795..5e783edb82 100644 --- a/codes/cpp/chapter_computational_complexity/time_complexity.cpp +++ b/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -1,10 +1,10 @@ /** * File: time_complexity.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 常数阶 */ int constant(int n) { @@ -24,7 +24,7 @@ int linear(int n) { } /* 线性阶(遍历数组) */ -int arrayTraversal(vector& nums) { +int arrayTraversal(vector &nums) { int count = 0; // 循环次数与数组长度成正比 for (int num : nums) { @@ -36,7 +36,7 @@ int arrayTraversal(vector& nums) { /* 平方阶 */ int quadratic(int n) { int count = 0; - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; @@ -46,18 +46,18 @@ int quadratic(int n) { } /* 平方阶(冒泡排序) */ -int bubbleSort(vector& nums) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 +int bubbleSort(vector &nums) { + int count = 0; // 计数器 + // 外循环:未排序区间为 [0, i] for (int i = nums.size() - 1; i > 0; i--) { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 + count += 3; // 元素交换包含 3 个单元操作 } } } @@ -67,7 +67,7 @@ int bubbleSort(vector& nums) { /* 指数阶(循环实现) */ int exponential(int n) { int count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < base; j++) { count++; @@ -80,12 +80,13 @@ int exponential(int n) { /* 指数阶(递归实现) */ int expRecur(int n) { - if (n == 1) return 1; + if (n == 1) + return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 对数阶(循环实现) */ -int logarithmic(float n) { +int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; @@ -95,16 +96,17 @@ int logarithmic(float n) { } /* 对数阶(递归实现) */ -int logRecur(float n) { - if (n <= 1) return 0; +int logRecur(int n) { + if (n <= 1) + return 0; return logRecur(n / 2) + 1; } /* 线性对数阶 */ -int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); +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++; } @@ -113,7 +115,8 @@ int linearLogRecur(float n) { /* 阶乘阶(递归实现) */ int factorialRecur(int n) { - if (n == 0) return 1; + if (n == 0) + return 1; int count = 0; // 从 1 个分裂出 n 个 for (int i = 0; i < n; i++) { @@ -122,7 +125,6 @@ int factorialRecur(int n) { return count; } - /* Driver Code */ int main() { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 @@ -130,37 +132,37 @@ int main() { cout << "输入数据大小 n = " << n << endl; int count = constant(n); - cout << "常数阶的计算操作数量 = " << count << endl; + cout << "常数阶的操作数量 = " << count << endl; count = linear(n); - cout << "线性阶的计算操作数量 = " << count << endl; + cout << "线性阶的操作数量 = " << count << endl; vector arr(n); count = arrayTraversal(arr); - cout << "线性阶(遍历数组)的计算操作数量 = " << count << endl; + cout << "线性阶(遍历数组)的操作数量 = " << count << endl; count = quadratic(n); - cout << "平方阶的计算操作数量 = " << count << endl; + cout << "平方阶的操作数量 = " << count << endl; vector nums(n); for (int i = 0; i < n; i++) - nums[i] = n - i; // [n,n-1,...,2,1] + nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); - cout << "平方阶(冒泡排序)的计算操作数量 = " << count << endl; + cout << "平方阶(冒泡排序)的操作数量 = " << count << endl; count = exponential(n); - cout << "指数阶(循环实现)的计算操作数量 = " << count << endl; + cout << "指数阶(循环实现)的操作数量 = " << count << endl; count = expRecur(n); - cout << "指数阶(递归实现)的计算操作数量 = " << count << endl; + cout << "指数阶(递归实现)的操作数量 = " << count << endl; - count = logarithmic((float) n); - cout << "对数阶(循环实现)的计算操作数量 = " << count << endl; - count = logRecur((float) n); - cout << "对数阶(递归实现)的计算操作数量 = " << count << endl; + count = logarithmic(n); + cout << "对数阶(循环实现)的操作数量 = " << count << endl; + count = logRecur(n); + cout << "对数阶(递归实现)的操作数量 = " << count << endl; - count = linearLogRecur((float) n); - cout << "线性对数阶(递归实现)的计算操作数量 = " << count << endl; + count = linearLogRecur(n); + cout << "线性对数阶(递归实现)的操作数量 = " << count << endl; count = factorialRecur(n); - cout << "阶乘阶(递归实现)的计算操作数量 = " << count << endl; + cout << "阶乘阶(递归实现)的操作数量 = " << count << endl; return 0; } diff --git a/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp index c2916cb407..f2ce76b895 100644 --- a/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp +++ b/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -1,10 +1,10 @@ /** * File: worst_best_time_complexity.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ vector randomNumbers(int n) { @@ -21,15 +21,16 @@ vector randomNumbers(int n) { } /* 查找数组 nums 中数字 1 所在索引 */ -int findOne(vector& nums) { +int findOne(vector &nums) { for (int i = 0; i < nums.size(); i++) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] == 1) return i; } return -1; } - /* Driver Code */ int main() { for (int i = 0; i < 1000; i++) { @@ -37,7 +38,7 @@ int main() { vector nums = randomNumbers(n); int index = findOne(nums); cout << "\n数组 [ 1, 2, ..., n ] 被打乱后 = "; - PrintUtil::printVector(nums); + printVector(nums); cout << "数字 1 的索引为 " << index << endl; } return 0; diff --git a/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt b/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 0000000000..38dfff7107 --- /dev/null +++ b/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.cpp) +add_executable(build_tree build_tree.cpp) +add_executable(hanota hanota.cpp) \ No newline at end of file diff --git a/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp b/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp new file mode 100644 index 0000000000..5bb3b248f3 --- /dev/null +++ b/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp @@ -0,0 +1,46 @@ +/** + * File: binary_search_recur.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分查找:问题 f(i, j) */ +int dfs(vector &nums, int target, int i, int j) { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目标元素,返回其索引 + return m; + } +} + +/* 二分查找 */ +int binarySearch(vector &nums, int target) { + int n = nums.size(); + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + // 二分查找(双闭区间) + int index = binarySearch(nums, target); + cout << "目标元素 6 的索引 = " << index << endl; + + return 0; +} \ No newline at end of file diff --git a/codes/cpp/chapter_divide_and_conquer/build_tree.cpp b/codes/cpp/chapter_divide_and_conquer/build_tree.cpp new file mode 100644 index 0000000000..ca5389e1b0 --- /dev/null +++ b/codes/cpp/chapter_divide_and_conquer/build_tree.cpp @@ -0,0 +1,51 @@ +/** + * File: build_tree.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 构建二叉树:分治 */ +TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return NULL; + // 初始化根节点 + TreeNode *root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = inorderMap[preorder[i]]; + // 子问题:构建左子树 + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子问题:构建右子树 + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; +} + +/* 构建二叉树 */ +TreeNode *buildTree(vector &preorder, vector &inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + unordered_map inorderMap; + for (int i = 0; i < inorder.size(); i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1); + return root; +} + +/* Driver Code */ +int main() { + vector preorder = {3, 9, 2, 1, 7}; + vector inorder = {9, 3, 1, 2, 7}; + cout << "前序遍历 = "; + printVector(preorder); + cout << "中序遍历 = "; + printVector(inorder); + + TreeNode *root = buildTree(preorder, inorder); + cout << "构建的二叉树为:\n"; + printTree(root); + + return 0; +} diff --git a/codes/cpp/chapter_divide_and_conquer/hanota.cpp b/codes/cpp/chapter_divide_and_conquer/hanota.cpp new file mode 100644 index 0000000000..7a2bc5ac15 --- /dev/null +++ b/codes/cpp/chapter_divide_and_conquer/hanota.cpp @@ -0,0 +1,66 @@ +/** + * File: hanota.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 移动一个圆盘 */ +void move(vector &src, vector &tar) { + // 从 src 顶部拿出一个圆盘 + int pan = src.back(); + src.pop_back(); + // 将圆盘放入 tar 顶部 + tar.push_back(pan); +} + +/* 求解汉诺塔问题 f(i) */ +void dfs(int i, vector &src, vector &buf, vector &tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解汉诺塔问题 */ +void solveHanota(vector &A, vector &B, vector &C) { + int n = A.size(); + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +int main() { + // 列表尾部是柱子顶部 + vector A = {5, 4, 3, 2, 1}; + vector B = {}; + vector C = {}; + + cout << "初始状态下:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + solveHanota(A, B, C); + + cout << "圆盘移动完成后:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/CMakeLists.txt b/codes/cpp/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 0000000000..ed185458a4 --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) +add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) +add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) +add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) +add_executable(min_path_sum min_path_sum.cpp) +add_executable(unbounded_knapsack unbounded_knapsack.cpp) +add_executable(coin_change coin_change.cpp) +add_executable(coin_change_ii coin_change_ii.cpp) +add_executable(edit_distance edit_distance.cpp) \ No newline at end of file diff --git a/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp new file mode 100644 index 0000000000..4080f2881c --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -0,0 +1,43 @@ + +/** + * File: climbing_stairs_backtrack.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯 */ +void backtrack(vector &choices, int state, int n, vector &res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res[0]++; + // 遍历所有选择 + for (auto &choice : choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) + continue; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬楼梯:回溯 */ +int climbingStairsBacktrack(int n) { + vector choices = {1, 2}; // 可选择向上爬 1 阶或 2 阶 + int state = 0; // 从第 0 阶开始爬 + vector res = {0}; // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp new file mode 100644 index 0000000000..eb5fc75d03 --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -0,0 +1,37 @@ +/** + * File: climbing_stairs_constraint_dp.cpp + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 带约束爬楼梯:动态规划 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + vector> dp(n + 1, vector(3, 0)); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp new file mode 100644 index 0000000000..ffcff183f2 --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 搜索 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬楼梯:搜索 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp new file mode 100644 index 0000000000..7ae62cb9d5 --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_dfs_mem.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 记忆化搜索 */ +int dfs(int i, vector &mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在记录 dp[i] ,则直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; +} + +/* 爬楼梯:记忆化搜索 */ +int climbingStairsDFSMem(int n) { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + vector mem(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp b/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp new file mode 100644 index 0000000000..ce822e470f --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬楼梯:动态规划 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用于存储子问题的解 + vector dp(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬楼梯:空间优化后的动态规划 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + res = climbingStairsDPComp(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/coin_change.cpp b/codes/cpp/chapter_dynamic_programming/coin_change.cpp new file mode 100644 index 0000000000..c59b83a63d --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/coin_change.cpp @@ -0,0 +1,70 @@ +/** + * File: coin_change.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零钱兑换:动态规划 */ +int coinChangeDP(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 状态转移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* 零钱兑换:空间优化后的动态规划 */ +int coinChangeDPComp(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector dp(amt + 1, MAX); + dp[0] = 0; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 4; + + // 动态规划 + int res = coinChangeDP(coins, amt); + cout << "凑到目标金额所需的最少硬币数量为 " << res << endl; + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins, amt); + cout << "凑到目标金额所需的最少硬币数量为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp b/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp new file mode 100644 index 0000000000..7364074f5f --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp @@ -0,0 +1,68 @@ +/** + * File: coin_change_ii.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零钱兑换 II:动态规划 */ +int coinChangeIIDP(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +int coinChangeIIDPComp(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector dp(amt + 1, 0); + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 5; + + // 动态规划 + int res = coinChangeIIDP(coins, amt); + cout << "凑出目标金额的硬币组合数量为 " << res << endl; + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(coins, amt); + cout << "凑出目标金额的硬币组合数量为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/edit_distance.cpp b/codes/cpp/chapter_dynamic_programming/edit_distance.cpp new file mode 100644 index 0000000000..21a04b0992 --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/edit_distance.cpp @@ -0,0 +1,136 @@ +/** + * File: edit_distance.cpp + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 编辑距离:暴力搜索 */ +int editDistanceDFS(string s, string t, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return min(min(insert, del), replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若已有记录,则直接返回之 + if (mem[i][j] != -1) + return mem[i][j]; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = min(min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +int editDistanceDP(string s, string t) { + int n = s.length(), m = t.length(); + vector> dp(n + 1, vector(m + 1, 0)); + // 状态转移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 编辑距离:空间优化后的动态规划 */ +int editDistanceDPComp(string s, string t) { + int n = s.length(), m = t.length(); + vector dp(m + 1, 0); + // 状态转移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (int i = 1; i <= n; i++) { + // 状态转移:首列 + int leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +int main() { + string s = "bag"; + string t = "pack"; + int n = s.length(), m = t.length(); + + // 暴力搜索 + int res = editDistanceDFS(s, t, n, m); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + // 记忆化搜索 + vector> mem(n + 1, vector(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + // 动态规划 + res = editDistanceDP(s, t); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/knapsack.cpp b/codes/cpp/chapter_dynamic_programming/knapsack.cpp new file mode 100644 index 0000000000..a0848c8eab --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/knapsack.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +using namespace std; + +/* 0-1 背包:暴力搜索 */ +int knapsackDFS(vector &wgt, vector &val, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return max(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +int knapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空间优化后的动态规划 */ +int knapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + int n = wgt.size(); + + // 暴力搜索 + int res = knapsackDFS(wgt, val, n, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 记忆化搜索 + vector> mem(n + 1, vector(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 动态规划 + res = knapsackDP(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp new file mode 100644 index 0000000000..7736940de3 --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬楼梯最小代价:动态规划 */ +int minCostClimbingStairsDP(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + vector dp(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +int minCostClimbingStairsDPComp(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + cout << "输入楼梯的代价列表为 "; + printVector(cost); + + int res = minCostClimbingStairsDP(cost); + cout << "爬完楼梯的最低代价为 " << res << endl; + + res = minCostClimbingStairsDPComp(cost); + cout << "爬完楼梯的最低代价为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp b/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp new file mode 100644 index 0000000000..a59f6bc6fc --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp @@ -0,0 +1,116 @@ +/** + * File: min_path_sum.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最小路径和:暴力搜索 */ +int minPathSumDFS(vector> &grid, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小路径和:记忆化搜索 */ +int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +int minPathSumDP(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector> dp(n, vector(m)); + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路径和:空间优化后的动态规划 */ +int minPathSumDPComp(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector dp(m); + // 状态转移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +int main() { + vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = grid.size(), m = grid[0].size(); + + // 暴力搜索 + int res = minPathSumDFS(grid, n - 1, m - 1); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + // 记忆化搜索 + vector> mem(n, vector(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + // 动态规划 + res = minPathSumDP(grid); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + // 空间优化后的动态规划 + res = minPathSumDPComp(grid); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp b/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp new file mode 100644 index 0000000000..9e1b6dddcf --- /dev/null +++ b/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -0,0 +1,64 @@ +/** + * File: unbounded_knapsack.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 完全背包:动态规划 */ +int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空间优化后的动态规划 */ +int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver code */ +int main() { + vector wgt = {1, 2, 3}; + vector val = {5, 11, 15}; + int cap = 4; + + // 动态规划 + int res = unboundedKnapsackDP(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_graph/CMakeLists.txt b/codes/cpp/chapter_graph/CMakeLists.txt new file mode 100644 index 0000000000..4a56ce35ba --- /dev/null +++ b/codes/cpp/chapter_graph/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(graph_bfs graph_bfs.cpp) +add_executable(graph_dfs graph_dfs.cpp) +# add_executable(graph_adjacency_list graph_adjacency_list.cpp) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) +add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) diff --git a/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/codes/cpp/chapter_graph/graph_adjacency_list.cpp new file mode 100644 index 0000000000..e23026feb7 --- /dev/null +++ b/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -0,0 +1,90 @@ +/** + * File: graph_adjacency_list.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于邻接表实现的无向图类 */ +class GraphAdjList { + public: + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + unordered_map> adjList; + + /* 在 vector 中删除指定节点 */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* 构造方法 */ + GraphAdjList(const vector> &edges) { + // 添加所有顶点和边 + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() { + return adjList.size(); + } + + /* 添加边 */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在顶点"); + // 添加边 vet1 - vet2 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* 删除边 */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在顶点"); + // 删除边 vet1 - vet2 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* 添加顶点 */ + void addVertex(Vertex *vet) { + if (adjList.count(vet)) + return; + // 在邻接表中添加一个新链表 + adjList[vet] = vector(); + } + + /* 删除顶点 */ + void removeVertex(Vertex *vet) { + if (!adjList.count(vet)) + throw invalid_argument("不存在顶点"); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.erase(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* 打印邻接表 */ + void print() { + cout << "邻接表 =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } +}; + +// 测试样例请见 graph_adjacency_list_test.cpp diff --git a/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 0000000000..39ea794f4e --- /dev/null +++ b/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets(vector{1, 3, 2, 5, 4}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + GraphAdjList graph(edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + cout << "\n添加边 1-2 后,图为" << endl; + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + cout << "\n删除边 1-3 后,图为" << endl; + graph.print(); + + /* 添加顶点 */ + Vertex *v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\n添加顶点 6 后,图为" << endl; + graph.print(); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(v[1]); + cout << "\n删除顶点 3 后,图为" << endl; + graph.print(); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp new file mode 100644 index 0000000000..91d51a6bfd --- /dev/null +++ b/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -0,0 +1,127 @@ +/** + * File: graph_adjacency_matrix.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + vector vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vector> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + public: + /* 构造方法 */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // 添加顶点 + for (int val : vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() const { + return vertices.size(); + } + + /* 添加顶点 */ + void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.push_back(val); + // 在邻接矩阵中添加一行 + adjMat.emplace_back(vector(n, 0)); + // 在邻接矩阵中添加一列 + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* 删除顶点 */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("顶点不存在"); + } + // 在顶点列表中移除索引 index 的顶点 + vertices.erase(vertices.begin() + index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.erase(adjMat.begin() + index); + // 在邻接矩阵中删除索引 index 的列 + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + void print() { + cout << "顶点列表 = "; + printVector(vertices); + cout << "邻接矩阵 =" << endl; + printVectorMatrix(adjMat); + } +}; + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + vector vertices = {1, 3, 2, 5, 4}; + vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + GraphAdjMat graph(vertices, edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.addEdge(0, 2); + cout << "\n添加边 1-2 后,图为" << endl; + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.removeEdge(0, 1); + cout << "\n删除边 1-3 后,图为" << endl; + graph.print(); + + /* 添加顶点 */ + graph.addVertex(6); + cout << "\n添加顶点 6 后,图为" << endl; + graph.print(); + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.removeVertex(1); + cout << "\n删除顶点 3 后,图为" << endl; + graph.print(); + + return 0; +} diff --git a/codes/cpp/chapter_graph/graph_bfs.cpp b/codes/cpp/chapter_graph/graph_bfs.cpp new file mode 100644 index 0000000000..7b18fcec0c --- /dev/null +++ b/codes/cpp/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // 顶点遍历序列 + vector res; + // 哈希集合,用于记录已被访问过的顶点 + unordered_set visited = {startVet}; + // 队列用于实现 BFS + queue que; + que.push(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // 队首顶点出队 + res.push_back(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问的顶点 + que.push(adjVet); // 只入队未访问的顶点 + visited.emplace(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; +} + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + vector> edges = {{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]}}; + GraphAdjList graph(edges); + cout << "\n初始化后,图为\\n"; + graph.print(); + + /* 广度优先遍历 */ + vector res = graphBFS(graph, v[0]); + cout << "\n广度优先遍历(BFS)顶点序列为" << endl; + printVector(vetsToVals(res)); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/codes/cpp/chapter_graph/graph_dfs.cpp b/codes/cpp/chapter_graph/graph_dfs.cpp new file mode 100644 index 0000000000..dd8aef0924 --- /dev/null +++ b/codes/cpp/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 深度优先遍历辅助函数 */ +void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { + res.push_back(vet); // 记录访问顶点 + visited.emplace(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // 顶点遍历序列 + vector res; + // 哈希集合,用于记录已被访问过的顶点 + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + GraphAdjList graph(edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 深度优先遍历 */ + vector res = graphDFS(graph, v[0]); + cout << "\n深度优先遍历(DFS)顶点序列为" << endl; + printVector(vetsToVals(res)); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/codes/cpp/chapter_greedy/CMakeLists.txt b/codes/cpp/chapter_greedy/CMakeLists.txt new file mode 100644 index 0000000000..91788668d4 --- /dev/null +++ b/codes/cpp/chapter_greedy/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(coin_change_greedy coin_change_greedy.cpp) +add_executable(fractional_knapsack fractional_knapsack.cpp) +add_executable(max_capacity max_capacity.cpp) \ No newline at end of file diff --git a/codes/cpp/chapter_greedy/coin_change_greedy.cpp b/codes/cpp/chapter_greedy/coin_change_greedy.cpp new file mode 100644 index 0000000000..5f1db7cfc9 --- /dev/null +++ b/codes/cpp/chapter_greedy/coin_change_greedy.cpp @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零钱兑换:贪心 */ +int coinChangeGreedy(vector &coins, int amt) { + // 假设 coins 列表有序 + int i = coins.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; +} + +/* Driver Code */ +int main() { + // 贪心:能够保证找到全局最优解 + vector coins = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "凑到 " << amt << " 所需的最少硬币数量为 " << res << endl; + + // 贪心:无法保证找到全局最优解 + coins = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "凑到 " << amt << " 所需的最少硬币数量为 " << res << endl; + cout << "实际上需要的最少数量为 3 ,即 20 + 20 + 20" << endl; + + // 贪心:无法保证找到全局最优解 + coins = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "凑到 " << amt << " 所需的最少硬币数量为 " << res << endl; + cout << "实际上需要的最少数量为 2 ,即 49 + 49" << endl; + + return 0; +} diff --git a/codes/cpp/chapter_greedy/fractional_knapsack.cpp b/codes/cpp/chapter_greedy/fractional_knapsack.cpp new file mode 100644 index 0000000000..a9cdc4f4e6 --- /dev/null +++ b/codes/cpp/chapter_greedy/fractional_knapsack.cpp @@ -0,0 +1,56 @@ +/** + * File: fractional_knapsack.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 物品 */ +class Item { + public: + int w; // 物品重量 + int v; // 物品价值 + + Item(int w, int v) : w(w), v(v) { + } +}; + +/* 分数背包:贪心 */ +double fractionalKnapsack(vector &wgt, vector &val, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + vector items; + for (int i = 0; i < wgt.size(); i++) { + items.push_back(Item(wgt[i], val[i])); + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); + // 循环贪心选择 + double res = 0; + for (auto &item : items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (double)item.v / item.w * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + + // 贪心算法 + double res = fractionalKnapsack(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_greedy/max_capacity.cpp b/codes/cpp/chapter_greedy/max_capacity.cpp new file mode 100644 index 0000000000..eff2f6cf22 --- /dev/null +++ b/codes/cpp/chapter_greedy/max_capacity.cpp @@ -0,0 +1,39 @@ +/** + * File: max_capacity.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大容量:贪心 */ +int maxCapacity(vector &ht) { + // 初始化 i, j,使其分列数组两端 + int i = 0, j = ht.size() - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 贪心算法 + int res = maxCapacity(ht); + cout << "最大容量为 " << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_greedy/max_product_cutting.cpp b/codes/cpp/chapter_greedy/max_product_cutting.cpp new file mode 100644 index 0000000000..880d828239 --- /dev/null +++ b/codes/cpp/chapter_greedy/max_product_cutting.cpp @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大切分乘积:贪心 */ +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 (int)pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return (int)pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return (int)pow(3, a); +} + +/* Driver Code */ +int main() { + int n = 58; + + // 贪心算法 + int res = maxProductCutting(n); + cout << "最大切分乘积为" << res << endl; + + return 0; +} diff --git a/codes/cpp/chapter_hashing/CMakeLists.txt b/codes/cpp/chapter_hashing/CMakeLists.txt new file mode 100644 index 0000000000..6b583ef556 --- /dev/null +++ b/codes/cpp/chapter_hashing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(hash_map hash_map.cpp) +add_executable(array_hash_map_test array_hash_map_test.cpp) +add_executable(hash_map_chaining hash_map_chaining.cpp) +add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) +add_executable(simple_hash simple_hash.cpp) +add_executable(built_in_hash built_in_hash.cpp) \ No newline at end of file diff --git a/codes/cpp/chapter_hashing/array_hash_map.cpp b/codes/cpp/chapter_hashing/array_hash_map.cpp index 8b92174e2e..96a1a140d6 100644 --- a/codes/cpp/chapter_hashing/array_hash_map.cpp +++ b/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -4,27 +4,36 @@ * Author: msk397 (machangxinq@gmail.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" -/* 键值对 int->String */ -struct Entry { -public: +/* 键值对 */ +struct Pair { + public: int key; string val; - Entry(int key, string val) { + Pair(int key, string val) { this->key = key; this->val = val; } }; -/* 基于数组简易实现的哈希表 */ +/* 基于数组实现的哈希表 */ class ArrayHashMap { -private: - vector bucket; -public: + private: + vector buckets; + + public: ArrayHashMap() { - // 初始化一个长度为 100 的桶(数组) - bucket= vector(100); + // 初始化数组,包含 100 个桶 + buckets = vector(100); + } + + ~ArrayHashMap() { + // 释放内存 + for (const auto &bucket : buckets) { + delete bucket; + } + buckets.clear(); } /* 哈希函数 */ @@ -36,42 +45,42 @@ class ArrayHashMap { /* 查询操作 */ string get(int key) { int index = hashFunc(key); - Entry* pair = bucket[index]; - if (pair == nullptr) { - return "Not Found"; - } + Pair *pair = buckets[index]; + if (pair == nullptr) + return ""; return pair->val; } /* 添加操作 */ void put(int key, string val) { - Entry* pair = new Entry(key, val); + Pair *pair = new Pair(key, val); int index = hashFunc(key); - bucket[index] = pair; + buckets[index] = pair; } /* 删除操作 */ void remove(int key) { int index = hashFunc(key); - // 置为 nullptr ,代表删除 - bucket[index] = nullptr; + // 释放内存并置为 nullptr + delete buckets[index]; + buckets[index] = nullptr; } /* 获取所有键值对 */ - vector entrySet() { - vector entrySet; - for (Entry* pair: bucket) { + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { if (pair != nullptr) { - entrySet.push_back(pair); + pairSet.push_back(pair); } } - return entrySet; + return pairSet; } /* 获取所有键 */ vector keySet() { vector keySet; - for (Entry* pair: bucket) { + for (Pair *pair : buckets) { if (pair != nullptr) { keySet.push_back(pair->key); } @@ -82,8 +91,8 @@ class ArrayHashMap { /* 获取所有值 */ vector valueSet() { vector valueSet; - for (Entry* pair: bucket) { - if (pair != nullptr){ + for (Pair *pair : buckets) { + if (pair != nullptr) { valueSet.push_back(pair->val); } } @@ -92,53 +101,10 @@ class ArrayHashMap { /* 打印哈希表 */ void print() { - for (Entry* kv: entrySet()) { + for (Pair *kv : pairSet()) { cout << kv->key << " -> " << kv->val << endl; } } }; -/* Driver Code */ -int main() { - /* 初始化哈希表 */ - ArrayHashMap map = ArrayHashMap(); - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); - map.put(13276, "小法"); - map.put(10583, "小鸭"); - cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; - map.print(); - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - string name = map.get(15937); - cout << "\n输入学号 15937 ,查询到姓名 " << name << endl; - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.remove(10583); - cout << "\n删除 10583 后,哈希表为\nKey -> Value" << endl; - map.print(); - - /* 遍历哈希表 */ - cout << "\n遍历键值对 Key->Value" << endl; - for (auto kv: map.entrySet()) { - cout << kv->key << " -> " << kv->val << endl; - } - - cout << "\n单独遍历键 Key" << endl; - for (auto key: map.keySet()) { - cout << key << endl; - } - - cout << "\n单独遍历值 Value" << endl; - for (auto val: map.valueSet()) { - cout << val << endl; - } - - return 0; -} +// 测试样例请见 array_hash_map_test.cpp diff --git a/codes/cpp/chapter_hashing/array_hash_map_test.cpp b/codes/cpp/chapter_hashing/array_hash_map_test.cpp new file mode 100644 index 0000000000..4b3ccc8ecb --- /dev/null +++ b/codes/cpp/chapter_hashing/array_hash_map_test.cpp @@ -0,0 +1,52 @@ +/** + * File: array_hash_map_test.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "./array_hash_map.cpp" + +/* Driver Code */ +int main() { + /* 初始化哈希表 */ + ArrayHashMap map = ArrayHashMap(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; + map.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string name = map.get(15937); + cout << "\n输入学号 15937 ,查询到姓名 " << name << endl; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + cout << "\n删除 10583 后,哈希表为\nKey -> Value" << endl; + map.print(); + + /* 遍历哈希表 */ + cout << "\n遍历键值对 Key->Value" << endl; + for (auto kv : map.pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + + cout << "\n单独遍历键 Key" << endl; + for (auto key : map.keySet()) { + cout << key << endl; + } + + cout << "\n单独遍历值 Value" << endl; + for (auto val : map.valueSet()) { + cout << val << endl; + } + + return 0; +} diff --git a/codes/cpp/chapter_hashing/built_in_hash.cpp b/codes/cpp/chapter_hashing/built_in_hash.cpp new file mode 100644 index 0000000000..da3f90c756 --- /dev/null +++ b/codes/cpp/chapter_hashing/built_in_hash.cpp @@ -0,0 +1,29 @@ +/** + * File: built_in_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + int num = 3; + size_t hashNum = hash()(num); + cout << "整数 " << num << " 的哈希值为 " << hashNum << "\n"; + + bool bol = true; + size_t hashBol = hash()(bol); + cout << "布尔量 " << bol << " 的哈希值为 " << hashBol << "\n"; + + double dec = 3.14159; + size_t hashDec = hash()(dec); + cout << "小数 " << dec << " 的哈希值为 " << hashDec << "\n"; + + string str = "Hello 算法"; + size_t hashStr = hash()(str); + cout << "字符串 " << str << " 的哈希值为 " << hashStr << "\n"; + + // 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算 + // 数组、对象的哈希值计算需要自行实现 +} diff --git a/codes/cpp/chapter_hashing/hash_map.cpp b/codes/cpp/chapter_hashing/hash_map.cpp index 829265d69a..8875032286 100644 --- a/codes/cpp/chapter_hashing/hash_map.cpp +++ b/codes/cpp/chapter_hashing/hash_map.cpp @@ -4,8 +4,7 @@ * Author: msk397 (machangxinq@gmail.com) */ -#include "../include/include.hpp" - +#include "../utils/common.hpp" /* Driver Code */ int main() { @@ -20,10 +19,10 @@ int main() { map[13276] = "小法"; map[10583] = "小鸭"; cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; - PrintUtil::printHashMap(map); + printHashMap(map); /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value + // 向哈希表中输入键 key ,得到值 value string name = map[15937]; cout << "\n输入学号 15937 ,查询到姓名 " << name << endl; @@ -31,22 +30,16 @@ int main() { // 在哈希表中删除键值对 (key, value) map.erase(10583); cout << "\n删除 10583 后,哈希表为\nKey -> Value" << endl; - PrintUtil::printHashMap(map); + printHashMap(map); /* 遍历哈希表 */ cout << "\n遍历键值对 Key->Value" << endl; - for (auto kv: map) { + for (auto kv : map) { cout << kv.first << " -> " << kv.second << endl; } - - cout << "\n单独遍历键 Key" << endl; - for (auto key: map) { - cout << key.first << endl; - } - - cout << "\n单独遍历值 Value" << endl; - for (auto val: map) { - cout << val.second << endl; + cout << "\n使用迭代器遍历 Key->Value" << endl; + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; } return 0; diff --git a/codes/cpp/chapter_hashing/hash_map_chaining.cpp b/codes/cpp/chapter_hashing/hash_map_chaining.cpp new file mode 100644 index 0000000000..4fc373e3d0 --- /dev/null +++ b/codes/cpp/chapter_hashing/hash_map_chaining.cpp @@ -0,0 +1,150 @@ +/** + * File: hash_map_chaining.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 链式地址哈希表 */ +class HashMapChaining { + private: + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + vector> buckets; // 桶数组 + + public: + /* 构造方法 */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { + buckets.resize(capacity); + } + + /* 析构方法 */ + ~HashMapChaining() { + for (auto &bucket : buckets) { + for (Pair *pair : bucket) { + // 释放内存 + delete pair; + } + } + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* 查询操作 */ + string get(int key) { + int index = hashFunc(key); + // 遍历桶,若找到 key ,则返回对应 val + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // 若未找到 key ,则返回空字符串 + return ""; + } + + /* 添加操作 */ + void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + auto &bucket = buckets[index]; + // 遍历桶,从中删除键值对 + for (int i = 0; i < bucket.size(); i++) { + if (bucket[i]->key == key) { + Pair *tmp = bucket[i]; + bucket.erase(bucket.begin() + i); // 从中删除键值对 + delete tmp; // 释放内存 + size--; + return; + } + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + vector> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets.clear(); + buckets.resize(capacity); + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (auto &bucket : bucketsTmp) { + for (Pair *pair : bucket) { + put(pair->key, pair->val); + // 释放内存 + delete pair; + } + } + } + + /* 打印哈希表 */ + void print() { + for (auto &bucket : buckets) { + cout << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } +}; + +/* Driver Code */ +int main() { + /* 初始化哈希表 */ + HashMapChaining map = HashMapChaining(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; + map.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string name = map.get(13276); + cout << "\n输入学号 13276 ,查询到姓名 " << name << endl; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(12836); + cout << "\n删除 12836 后,哈希表为\nKey -> Value" << endl; + map.print(); + + return 0; +} diff --git a/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp b/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp new file mode 100644 index 0000000000..7f9bf5ab28 --- /dev/null +++ b/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -0,0 +1,171 @@ +/** + * File: hash_map_open_addressing.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + private: + int size; // 键值对数量 + int capacity = 4; // 哈希表容量 + const double loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + const int extendRatio = 2; // 扩容倍数 + vector buckets; // 桶数组 + Pair *TOMBSTONE = new Pair(-1, "-1"); // 删除标记 + + public: + /* 构造方法 */ + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* 析构方法 */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return (double)size / capacity; + } + + /* 搜索 key 对应的桶索引 */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (buckets[index] != nullptr) { + // 若遇到 key ,返回对应的桶索引 + if (buckets[index]->key == key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查询操作 */ + string get(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则返回对应 val + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + return buckets[index]->val; + } + // 若键值对不存在,则返回空字符串 + return ""; + } + + /* 添加操作 */ + void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + buckets[index]->val = val; + return; + } + // 若键值对不存在,则添加该键值对 + buckets[index] = new Pair(key, val); + size++; + } + + /* 删除操作 */ + void remove(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + vector bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = vector(capacity, nullptr); + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (Pair *pair : bucketsTmp) { + if (pair != nullptr && pair != TOMBSTONE) { + put(pair->key, pair->val); + delete pair; + } + } + } + + /* 打印哈希表 */ + void print() { + for (Pair *pair : buckets) { + if (pair == nullptr) { + cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; + } + } + } +}; + +/* Driver Code */ +int main() { + // 初始化哈希表 + HashMapOpenAddressing hashmap; + + // 添加操作 + // 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小啰"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鸭"); + cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; + hashmap.print(); + + // 查询操作 + // 向哈希表中输入键 key ,得到值 val + string name = hashmap.get(13276); + cout << "\n输入学号 13276 ,查询到姓名 " << name << endl; + + // 删除操作 + // 在哈希表中删除键值对 (key, val) + hashmap.remove(16750); + cout << "\n删除 16750 后,哈希表为\nKey -> Value" << endl; + hashmap.print(); + + return 0; +} diff --git a/codes/cpp/chapter_hashing/simple_hash.cpp b/codes/cpp/chapter_hashing/simple_hash.cpp new file mode 100644 index 0000000000..9e0f8a07fc --- /dev/null +++ b/codes/cpp/chapter_hashing/simple_hash.cpp @@ -0,0 +1,66 @@ +/** + * File: simple_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 加法哈希 */ +int addHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 乘法哈希 */ +int mulHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (31 * hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 异或哈希 */ +int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash ^= (int)c; + } + return hash & MODULUS; +} + +/* 旋转哈希 */ +int rotHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS; + } + return (int)hash; +} + +/* Driver Code */ +int main() { + string key = "Hello 算法"; + + int hash = addHash(key); + cout << "加法哈希值为 " << hash << endl; + + hash = mulHash(key); + cout << "乘法哈希值为 " << hash << endl; + + hash = xorHash(key); + cout << "异或哈希值为 " << hash << endl; + + hash = rotHash(key); + cout << "旋转哈希值为 " << hash << endl; + + return 0; +} diff --git a/codes/cpp/chapter_heap/CMakeLists.txt b/codes/cpp/chapter_heap/CMakeLists.txt new file mode 100644 index 0000000000..1ac33a44fe --- /dev/null +++ b/codes/cpp/chapter_heap/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(heap heap.cpp) +add_executable(my_heap my_heap.cpp) +add_executable(top_k top_k.cpp) diff --git a/codes/cpp/chapter_heap/heap.cpp b/codes/cpp/chapter_heap/heap.cpp new file mode 100644 index 0000000000..84f70bb49a --- /dev/null +++ b/codes/cpp/chapter_heap/heap.cpp @@ -0,0 +1,66 @@ +/** + * File: heap.cpp + * Created Time: 2023-01-19 + * Author: LoneRanger(836253168@qq.com) + */ + +#include "../utils/common.hpp" + +void testPush(priority_queue &heap, int val) { + heap.push(val); // 元素入堆 + cout << "\n元素 " << val << " 入堆后" << endl; + printHeap(heap); +} + +void testPop(priority_queue &heap) { + int val = heap.top(); + heap.pop(); + cout << "\n堆顶元素 " << val << " 出堆后" << endl; + printHeap(heap); +} + +/* Driver Code */ +int main() { + /* 初始化堆 */ + // 初始化小顶堆 + // priority_queue, greater> minHeap; + // 初始化大顶堆 + priority_queue, less> maxHeap; + + cout << "\n以下测试样例为大顶堆" << endl; + + /* 元素入堆 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 获取堆顶元素 */ + int peek = maxHeap.top(); + cout << "\n堆顶元素为 " << peek << endl; + + /* 堆顶元素出堆 */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* 获取堆大小 */ + int size = maxHeap.size(); + cout << "\n堆元素数量为 " << size << endl; + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.empty(); + cout << "\n堆是否为空 " << isEmpty << endl; + + /* 输入列表并建堆 */ + // 时间复杂度为 O(n) ,而非 O(nlogn) + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + cout << "输入列表并建立小顶堆后" << endl; + printHeap(minHeap); + + return 0; +} diff --git a/codes/cpp/chapter_heap/my_heap.cpp b/codes/cpp/chapter_heap/my_heap.cpp new file mode 100644 index 0000000000..5e6299ee8d --- /dev/null +++ b/codes/cpp/chapter_heap/my_heap.cpp @@ -0,0 +1,155 @@ +/** + * File: my_heap.cpp + * Created Time: 2023-02-04 + * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 大顶堆 */ +class MaxHeap { + private: + // 使用动态数组,这样无须考虑扩容问题 + vector maxHeap; + + /* 获取左子节点的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交换两节点 + swap(maxHeap[i], maxHeap[p]); + // 循环向上堆化 + i = p; + } + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) + break; + swap(maxHeap[i], maxHeap[ma]); + // 循环向下堆化 + i = ma; + } + } + + public: + /* 构造方法,根据输入列表建堆 */ + MaxHeap(vector nums) { + // 将列表元素原封不动添加进堆 + maxHeap = nums; + // 堆化除叶节点以外的其他所有节点 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 获取堆大小 */ + int size() { + return maxHeap.size(); + } + + /* 判断堆是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 访问堆顶元素 */ + int peek() { + return maxHeap[0]; + } + + /* 元素入堆 */ + void push(int val) { + // 添加节点 + maxHeap.push_back(val); + // 从底至顶堆化 + siftUp(size() - 1); + } + + /* 元素出堆 */ + void pop() { + // 判空处理 + if (isEmpty()) { + throw out_of_range("堆为空"); + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(maxHeap[0], maxHeap[size() - 1]); + // 删除节点 + maxHeap.pop_back(); + // 从顶至底堆化 + siftDown(0); + } + + /* 打印堆(二叉树)*/ + void print() { + cout << "堆的数组表示:"; + printVector(maxHeap); + cout << "堆的树状表示:" << endl; + TreeNode *root = vectorToTree(maxHeap); + printTree(root); + freeMemoryTree(root); + } +}; + +/* Driver Code */ +int main() { + /* 初始化大顶堆 */ + vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap maxHeap(vec); + cout << "\n输入列表并建堆后" << endl; + maxHeap.print(); + + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); + cout << "\n堆顶元素为 " << peek << endl; + + /* 元素入堆 */ + int val = 7; + maxHeap.push(val); + cout << "\n元素 " << val << " 入堆后" << endl; + maxHeap.print(); + + /* 堆顶元素出堆 */ + peek = maxHeap.peek(); + maxHeap.pop(); + cout << "\n堆顶元素 " << peek << " 出堆后" << endl; + maxHeap.print(); + + /* 获取堆大小 */ + int size = maxHeap.size(); + cout << "\n堆元素数量为 " << size << endl; + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.isEmpty(); + cout << "\n堆是否为空 " << isEmpty << endl; + + return 0; +} diff --git a/codes/cpp/chapter_heap/top_k.cpp b/codes/cpp/chapter_heap/top_k.cpp new file mode 100644 index 0000000000..0b4b11a628 --- /dev/null +++ b/codes/cpp/chapter_heap/top_k.cpp @@ -0,0 +1,38 @@ +/** + * File: top_k.cpp + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于堆查找数组中最大的 k 个元素 */ +priority_queue, greater> topKHeap(vector &nums, int k) { + // 初始化小顶堆 + priority_queue, greater> heap; + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.size(); i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +// Driver Code +int main() { + vector nums = {1, 7, 6, 3, 2}; + int k = 3; + + priority_queue, greater> res = topKHeap(nums, k); + cout << "最大的 " << k << " 个元素为: "; + printHeap(res); + + return 0; +} diff --git a/codes/cpp/chapter_searching/CMakeLists.txt b/codes/cpp/chapter_searching/CMakeLists.txt new file mode 100644 index 0000000000..60a223d839 --- /dev/null +++ b/codes/cpp/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.cpp) +add_executable(binary_search_insertion binary_search_insertion.cpp) +add_executable(binary_search_edge binary_search_edge.cpp) +add_executable(two_sum two_sum.cpp) diff --git a/codes/cpp/chapter_searching/binary_search.cpp b/codes/cpp/chapter_searching/binary_search.cpp index 8d727ab40b..a36670ee17 100644 --- a/codes/cpp/chapter_searching/binary_search.cpp +++ b/codes/cpp/chapter_searching/binary_search.cpp @@ -1,60 +1,59 @@ /** * File: binary_search.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 二分查找(双闭区间) */ -int binarySearch(vector& nums, int target) { +int binarySearch(vector &nums, int target) { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 int i = 0, j = nums.size() - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, 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 // 找到目标元素,返回其索引 + else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } -/* 二分查找(左闭右开) */ -int binarySearch1(vector& nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 +/* 二分查找(左闭右开区间) */ +int binarySearchLCRO(vector &nums, int target) { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 int i = 0, j = nums.size(); // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, 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 // 找到目标元素,返回其索引 + else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } - /* Driver Code */ int main() { int target = 6; - vector nums = { 1, 3, 6, 8, 12, 15, 23, 67, 70, 92 }; - + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + /* 二分查找(双闭区间) */ int index = binarySearch(nums, target); cout << "目标元素 6 的索引 = " << index << endl; - /* 二分查找(左闭右开) */ - index = binarySearch1(nums, target); + /* 二分查找(左闭右开区间) */ + index = binarySearchLCRO(nums, target); cout << "目标元素 6 的索引 = " << index << endl; - + return 0; } diff --git a/codes/cpp/chapter_searching/binary_search_edge.cpp b/codes/cpp/chapter_searching/binary_search_edge.cpp new file mode 100644 index 0000000000..6a536c2321 --- /dev/null +++ b/codes/cpp/chapter_searching/binary_search_edge.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_edge.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分查找插入点(存在重复元素) */ +int binarySearchInsertion(const vector &nums, int target) { + int i = 0, j = nums.size() - 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(vector &nums, int target) { + // 等价于查找 target 的插入点 + int i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.size() || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分查找最右一个 target */ +int binarySearchRightEdge(vector &nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +int main() { + // 包含重复元素的数组 + vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n数组 nums = "; + printVector(nums); + + // 二分查找左边界和右边界 + for (int target : {6, 7}) { + int index = binarySearchLeftEdge(nums, target); + cout << "最左一个元素 " << target << " 的索引为 " << index << endl; + index = binarySearchRightEdge(nums, target); + cout << "最右一个元素 " << target << " 的索引为 " << index << endl; + } + + return 0; +} diff --git a/codes/cpp/chapter_searching/binary_search_insertion.cpp b/codes/cpp/chapter_searching/binary_search_insertion.cpp new file mode 100644 index 0000000000..f2fd15a387 --- /dev/null +++ b/codes/cpp/chapter_searching/binary_search_insertion.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_insertion.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分查找插入点(无重复元素) */ +int binarySearchInsertionSimple(vector &nums, int target) { + int i = 0, j = nums.size() - 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(vector &nums, int target) { + int i = 0, j = nums.size() - 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; +} + +/* Driver Code */ +int main() { + // 无重复元素的数组 + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + cout << "\n数组 nums = "; + printVector(nums); + // 二分查找插入点 + for (int target : {6, 9}) { + int index = binarySearchInsertionSimple(nums, target); + cout << "元素 " << target << " 的插入点的索引为 " << index << endl; + } + + // 包含重复元素的数组 + nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n数组 nums = "; + printVector(nums); + // 二分查找插入点 + for (int target : {2, 6, 20}) { + int index = binarySearchInsertion(nums, target); + cout << "元素 " << target << " 的插入点的索引为 " << index << endl; + } + + return 0; +} diff --git a/codes/cpp/chapter_searching/hashing_search.cpp b/codes/cpp/chapter_searching/hashing_search.cpp index ebc2fb01ca..4bd6bd5966 100644 --- a/codes/cpp/chapter_searching/hashing_search.cpp +++ b/codes/cpp/chapter_searching/hashing_search.cpp @@ -1,13 +1,13 @@ /** * File: hashing_search.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 哈希查找(数组) */ -int hashingSearch(unordered_map map, int target) { +int hashingSearchArray(unordered_map map, int target) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 if (map.find(target) == map.end()) @@ -16,39 +16,38 @@ int hashingSearch(unordered_map map, int target) { } /* 哈希查找(链表) */ -ListNode* hashingSearch1(unordered_map map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 +ListNode *hashingSearchLinkedList(unordered_map map, int target) { + // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 nullptr if (map.find(target) == map.end()) return nullptr; return map[target]; } - /* Driver Code */ int main() { int target = 3; /* 哈希查找(数组) */ - vector nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; // 初始化哈希表 unordered_map map; for (int i = 0; i < nums.size(); i++) { - map[nums[i]] = i; // key: 元素,value: 索引 + map[nums[i]] = i; // key: 元素,value: 索引 } - int index = hashingSearch(map, target); + int index = hashingSearchArray(map, target); cout << "目标元素 3 的索引 = " << index << endl; /* 哈希查找(链表) */ - ListNode* head = vecToLinkedList(nums); + ListNode *head = vecToLinkedList(nums); // 初始化哈希表 - unordered_map map1; + unordered_map map1; while (head != nullptr) { - map1[head->val] = head; // key: 结点值,value: 结点 + map1[head->val] = head; // key: 节点值,value: 节点 head = head->next; } - ListNode* node = hashingSearch1(map1, target); - cout << "目标结点值 3 的对应结点对象为 " << node << endl; + ListNode *node = hashingSearchLinkedList(map1, target); + cout << "目标节点值 3 的对应节点对象为 " << node << endl; return 0; } diff --git a/codes/cpp/chapter_searching/linear_search.cpp b/codes/cpp/chapter_searching/linear_search.cpp index 7b5092762f..fb4aece0a1 100644 --- a/codes/cpp/chapter_searching/linear_search.cpp +++ b/codes/cpp/chapter_searching/linear_search.cpp @@ -1,13 +1,13 @@ /** * File: linear_search.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 线性查找(数组) */ -int linearSearch(vector& nums, int target) { +int linearSearchArray(vector &nums, int target) { // 遍历数组 for (int i = 0; i < nums.size(); i++) { // 找到目标元素,返回其索引 @@ -19,32 +19,31 @@ int linearSearch(vector& nums, int target) { } /* 线性查找(链表) */ -ListNode* linearSearch(ListNode* head, int target) { +ListNode *linearSearchLinkedList(ListNode *head, int target) { // 遍历链表 while (head != nullptr) { - // 找到目标结点,返回之 + // 找到目标节点,返回之 if (head->val == target) return head; head = head->next; } - // 未找到目标结点,返回 nullptr + // 未找到目标节点,返回 nullptr return nullptr; } - /* Driver Code */ int main() { int target = 3; /* 在数组中执行线性查找 */ - vector nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; - int index = linearSearch(nums, target); + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + int index = linearSearchArray(nums, target); cout << "目标元素 3 的索引 = " << index << endl; /* 在链表中执行线性查找 */ - ListNode* head = vecToLinkedList(nums); - ListNode* node = linearSearch(head, target); - cout << "目标结点值 3 的对应结点对象为 " << node << endl; - + ListNode *head = vecToLinkedList(nums); + ListNode *node = linearSearchLinkedList(head, target); + cout << "目标节点值 3 的对应节点对象为 " << node << endl; + return 0; } diff --git a/codes/cpp/chapter_searching/two_sum.cpp b/codes/cpp/chapter_searching/two_sum.cpp new file mode 100644 index 0000000000..eba46c18a3 --- /dev/null +++ b/codes/cpp/chapter_searching/two_sum.cpp @@ -0,0 +1,54 @@ +/** + * File: two_sum.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 方法一:暴力枚举 */ +vector twoSumBruteForce(vector &nums, int target) { + int size = nums.size(); + // 两层循环,时间复杂度为 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return {i, j}; + } + } + return {}; +} + +/* 方法二:辅助哈希表 */ +vector twoSumHashTable(vector &nums, int target) { + int size = nums.size(); + // 辅助哈希表,空间复杂度为 O(n) + unordered_map dic; + // 单层循环,时间复杂度为 O(n) + for (int i = 0; i < size; i++) { + if (dic.find(target - nums[i]) != dic.end()) { + return {dic[target - nums[i]], i}; + } + dic.emplace(nums[i], i); + } + return {}; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + vector nums = {2, 7, 11, 15}; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + vector res = twoSumBruteForce(nums, target); + cout << "方法一 res = "; + printVector(res); + // 方法二 + res = twoSumHashTable(nums, target); + cout << "方法二 res = "; + printVector(res); + + return 0; +} diff --git a/codes/cpp/chapter_sorting/CMakeLists.txt b/codes/cpp/chapter_sorting/CMakeLists.txt new file mode 100644 index 0000000000..e6347cf9f3 --- /dev/null +++ b/codes/cpp/chapter_sorting/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(selection_sort selection_sort.cpp) +add_executable(bubble_sort bubble_sort.cpp) +add_executable(insertion_sort insertion_sort.cpp) +add_executable(merge_sort merge_sort.cpp) +add_executable(quick_sort quick_sort.cpp) +add_executable(heap_sort heap_sort.cpp) \ No newline at end of file diff --git a/codes/cpp/chapter_sorting/bubble_sort.cpp b/codes/cpp/chapter_sorting/bubble_sort.cpp index 27427cae34..a64d239ed7 100644 --- a/codes/cpp/chapter_sorting/bubble_sort.cpp +++ b/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -1,16 +1,16 @@ /** * File: bubble_sort.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 冒泡排序 */ -void bubbleSort(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 +void bubbleSort(vector &nums) { + // 外循环:未排序区间为 [0, i] for (int i = nums.size() - 1; i > 0; i--) { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -22,35 +22,35 @@ void bubbleSort(vector& nums) { } /* 冒泡排序(标志优化)*/ -void bubbleSortWithFlag(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 +void bubbleSortWithFlag(vector &nums) { + // 外循环:未排序区间为 [0, i] for (int i = nums.size() - 1; i > 0; i--) { bool flag = false; // 初始化标志位 - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] // 这里使用了 std::swap() 函数 swap(nums[j], nums[j + 1]); - flag = true; // 记录交换元素 + flag = true; // 记录交换元素 } } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + if (!flag) + break; // 此轮“冒泡”未交换任何元素,直接跳出 } } - /* Driver Code */ int main() { - vector nums = { 4, 1, 3, 1, 5, 2 }; + vector nums = {4, 1, 3, 1, 5, 2}; bubbleSort(nums); cout << "冒泡排序完成后 nums = "; - PrintUtil::printVector(nums); + printVector(nums); - vector nums1 = { 4, 1, 3, 1, 5, 2 }; + vector nums1 = {4, 1, 3, 1, 5, 2}; bubbleSortWithFlag(nums1); cout << "冒泡排序完成后 nums1 = "; - PrintUtil::printVector(nums1); + printVector(nums1); return 0; } diff --git a/codes/cpp/chapter_sorting/bucket_sort.cpp b/codes/cpp/chapter_sorting/bucket_sort.cpp new file mode 100644 index 0000000000..42be5c3b27 --- /dev/null +++ b/codes/cpp/chapter_sorting/bucket_sort.cpp @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.cpp + * Created Time: 2023-03-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 桶排序 */ +void bucketSort(vector &nums) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + int k = nums.size() / 2; + vector> buckets(k); + // 1. 将数组元素分配到各个桶中 + for (float num : nums) { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + int i = num * k; + // 将 num 添加进桶 bucket_idx + buckets[i].push_back(num); + } + // 2. 对各个桶执行排序 + for (vector &bucket : buckets) { + // 使用内置排序函数,也可以替换成其他排序算法 + sort(bucket.begin(), bucket.end()); + } + // 3. 遍历桶合并结果 + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +int main() { + // 设输入数据为浮点数,范围为 [0, 1) + vector nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums); + cout << "桶排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/codes/cpp/chapter_sorting/counting_sort.cpp b/codes/cpp/chapter_sorting/counting_sort.cpp new file mode 100644 index 0000000000..824ef0af77 --- /dev/null +++ b/codes/cpp/chapter_sorting/counting_sort.cpp @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cpp + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +void countingSortNaive(vector &nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 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; + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +void countingSort(vector &nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int n = nums.size(); + vector res(n); + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + nums = res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSortNaive(nums); + cout << "计数排序(无法排序对象)完成后 nums = "; + printVector(nums); + + vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSort(nums1); + cout << "计数排序完成后 nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/codes/cpp/chapter_sorting/heap_sort.cpp b/codes/cpp/chapter_sorting/heap_sort.cpp new file mode 100644 index 0000000000..1e568b8833 --- /dev/null +++ b/codes/cpp/chapter_sorting/heap_sort.cpp @@ -0,0 +1,54 @@ +/** + * File: heap_sort.cpp + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +void siftDown(vector &nums, int n, int i) { + while (true) { + // 判断节点 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; + } + // 交换两节点 + swap(nums[i], nums[ma]); + // 循环向下堆化 + i = ma; + } +} + +/* 堆排序 */ +void heapSort(vector &nums) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = nums.size() / 2 - 1; i >= 0; --i) { + siftDown(nums, nums.size(), i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = nums.size() - 1; i > 0; --i) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(nums[0], nums[i]); + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + heapSort(nums); + cout << "堆排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/codes/cpp/chapter_sorting/insertion_sort.cpp b/codes/cpp/chapter_sorting/insertion_sort.cpp index a40e4a5f72..d56b967af8 100644 --- a/codes/cpp/chapter_sorting/insertion_sort.cpp +++ b/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -1,32 +1,31 @@ /** * File: insertion_sort.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 插入排序 */ -void insertionSort(vector& nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] +void insertionSort(vector &nums) { + // 外循环:已排序区间为 [0, i-1] for (int i = 1; i < nums.size(); i++) { int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 + nums[j + 1] = base; // 将 base 赋值到正确位置 } } - /* Driver Code */ int main() { - vector nums = { 4, 1, 3, 1, 5, 2 }; + vector nums = {4, 1, 3, 1, 5, 2}; insertionSort(nums); cout << "插入排序完成后 nums = "; - PrintUtil::printVector(nums); + printVector(nums); return 0; } diff --git a/codes/cpp/chapter_sorting/merge_sort.cpp b/codes/cpp/chapter_sorting/merge_sort.cpp index 89b5b09cda..827b9f1f47 100644 --- a/codes/cpp/chapter_sorting/merge_sort.cpp +++ b/codes/cpp/chapter_sorting/merge_sort.cpp @@ -1,59 +1,58 @@ /** * File: merge_sort.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" -/** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ -void merge(vector& nums, int left, int mid, int right) { - // 初始化辅助数组 - vector tmp(nums.begin() + left, nums.begin() + right + 1); - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ +/* 合并左子数组和右子数组 */ +void merge(vector &nums, int left, int mid, int right) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + vector tmp(right - left + 1); + // 初始化左子数组和右子数组的起始索引 + int i = left, j = mid + 1, k = 0; + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; else - nums[k] = tmp[j++]; + tmp[k++] = nums[j++]; + } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for (k = 0; k < tmp.size(); k++) { + nums[left + k] = tmp[k]; } } /* 归并排序 */ -void mergeSort(vector& nums, int left, int right) { +void mergeSort(vector &nums, int left, int right) { // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 + if (left >= right) + return; // 当子数组长度为 1 时终止递归 // 划分阶段 - int mid = (left + right) / 2; // 计算中点 + int mid = left + (right - left) / 2; // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } - /* Driver Code */ int main() { /* 归并排序 */ - vector nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; + vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; mergeSort(nums, 0, nums.size() - 1); cout << "归并排序完成后 nums = "; - PrintUtil::printVector(nums); + printVector(nums); return 0; } diff --git a/codes/cpp/chapter_sorting/quick_sort.cpp b/codes/cpp/chapter_sorting/quick_sort.cpp index c298f5be88..90d6775429 100644 --- a/codes/cpp/chapter_sorting/quick_sort.cpp +++ b/codes/cpp/chapter_sorting/quick_sort.cpp @@ -1,39 +1,32 @@ /** * File: quick_sort.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 快速排序类 */ class QuickSort { -private: - /* 元素交换 */ - static void swap(vector& nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - + private: /* 哨兵划分 */ - static int partition(vector& nums, int left, int right) { - // 以 nums[left] 作为基准数 + static int partition(vector &nums, int left, int right) { + // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 + j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 + i++; // 从左向右找首个大于基准数的元素 + swap(nums[i], nums[j]); // 交换这两个元素 } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 + swap(nums[i], nums[left]); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 } -public: + public: /* 快速排序 */ - static void quickSort(vector& nums, int left, int right) { + static void quickSort(vector &nums, int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; @@ -47,48 +40,39 @@ class QuickSort { /* 快速排序类(中位基准数优化) */ class QuickSortMedian { -private: - /* 元素交换 */ - static void swap(vector& nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 选取三个元素的中位数 */ - static int medianThree(vector& nums, int left, int mid, int right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; + private: + /* 选取三个候选元素的中位数 */ + static int medianThree(vector &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; } /* 哨兵划分(三数取中值) */ - static int partition(vector& nums, int left, int right) { + static int partition(vector &nums, int left, int right) { // 选取三个候选元素的中位数 int med = medianThree(nums, left, (left + right) / 2, right); // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 + swap(nums[left], nums[med]); + // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 + j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 + i++; // 从左向右找首个大于基准数的元素 + swap(nums[i], nums[j]); // 交换这两个元素 } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 + swap(nums[i], nums[left]); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 } -public: + public: /* 快速排序 */ - static void quickSort(vector& nums, int left, int right) { + static void quickSort(vector &nums, int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; @@ -102,68 +86,60 @@ class QuickSortMedian { /* 快速排序类(尾递归优化) */ class QuickSortTailCall { -private: - /* 元素交换 */ - static void swap(vector& nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - + private: /* 哨兵划分 */ - static int partition(vector& nums, int left, int right) { - // 以 nums[left] 作为基准数 + static int partition(vector &nums, int left, int right) { + // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 + j--; // 从右向左找首个小于基准数的元素 while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 + i++; // 从左向右找首个大于基准数的元素 + swap(nums[i], nums[j]); // 交换这两个元素 } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 + swap(nums[i], nums[left]); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 } -public: + public: /* 快速排序(尾递归优化) */ - static void quickSort(vector& nums, int left, int right) { + static void quickSort(vector &nums, int left, int right) { // 子数组长度为 1 时终止 while (left < right) { // 哨兵划分操作 int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 + // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] + quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } }; - /* Driver Code */ int main() { /* 快速排序 */ - vector nums { 2, 4, 1, 0, 3, 5 }; + vector nums{2, 4, 1, 0, 3, 5}; QuickSort::quickSort(nums, 0, nums.size() - 1); cout << "快速排序完成后 nums = "; - PrintUtil::printVector(nums); + printVector(nums); /* 快速排序(中位基准数优化) */ - vector nums1 = { 2, 4, 1, 0, 3, 5 }; + vector nums1 = {2, 4, 1, 0, 3, 5}; QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); cout << "快速排序(中位基准数优化)完成后 nums = "; - PrintUtil::printVector(nums); + printVector(nums1); /* 快速排序(尾递归优化) */ - vector nums2 = { 2, 4, 1, 0, 3, 5 }; + vector nums2 = {2, 4, 1, 0, 3, 5}; QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); cout << "快速排序(尾递归优化)完成后 nums = "; - PrintUtil::printVector(nums); + printVector(nums2); return 0; } diff --git a/codes/cpp/chapter_sorting/radix_sort.cpp b/codes/cpp/chapter_sorting/radix_sort.cpp new file mode 100644 index 0000000000..ef4ce40c85 --- /dev/null +++ b/codes/cpp/chapter_sorting/radix_sort.cpp @@ -0,0 +1,65 @@ +/** + * File: radix_sort.cpp + * Created Time: 2023-03-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10; +} + +/* 计数排序(根据 nums 第 k 位排序) */ +void countingSortDigit(vector &nums, int exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + vector counter(10, 0); + int n = nums.size(); + // 统计 0~9 各数字的出现次数 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + vector res(n, 0); + for (int i = n - 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 < n; i++) + nums[i] = res[i]; +} + +/* 基数排序 */ +void radixSort(vector &nums) { + // 获取数组的最大元素,用于判断最大位数 + int m = *max_element(nums.begin(), nums.end()); + // 按照从低位到高位的顺序遍历 + for (int exp = 1; exp <= m; exp *= 10) + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +int main() { + // 基数排序 + vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + radixSort(nums); + cout << "基数排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/codes/cpp/chapter_sorting/selection_sort.cpp b/codes/cpp/chapter_sorting/selection_sort.cpp new file mode 100644 index 0000000000..027d2ace43 --- /dev/null +++ b/codes/cpp/chapter_sorting/selection_sort.cpp @@ -0,0 +1,34 @@ +/** + * File: selection_sort.cpp + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 选择排序 */ +void selectionSort(vector &nums) { + int n = nums.size(); + // 外循环:未排序区间为 [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; // 记录最小元素的索引 + } + // 将该最小元素与未排序区间的首个元素交换 + swap(nums[i], nums[k]); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + selectionSort(nums); + + cout << "选择排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/codes/cpp/chapter_stack_and_queue/CMakeLists.txt b/codes/cpp/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 0000000000..b55878a174 --- /dev/null +++ b/codes/cpp/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(array_deque array_deque.cpp) +add_executable(array_queue array_queue.cpp) +add_executable(array_stack array_stack.cpp) +add_executable(deque deque.cpp) +add_executable(linkedlist_deque linkedlist_deque.cpp) +add_executable(linkedlist_queue linkedlist_queue.cpp) +add_executable(linkedlist_stack linkedlist_stack.cpp) +add_executable(queue queue.cpp) +add_executable(stack stack.cpp) diff --git a/codes/cpp/chapter_stack_and_queue/array_deque.cpp b/codes/cpp/chapter_stack_and_queue/array_deque.cpp new file mode 100644 index 0000000000..00bf7769f6 --- /dev/null +++ b/codes/cpp/chapter_stack_and_queue/array_deque.cpp @@ -0,0 +1,156 @@ +/** + * File: array_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于环形数组实现的双向队列 */ +class ArrayDeque { + private: + vector nums; // 用于存储双向队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 双向队列长度 + + public: + /* 构造方法 */ + ArrayDeque(int capacity) { + nums.resize(capacity); + front = queSize = 0; + } + + /* 获取双向队列的容量 */ + int capacity() { + return nums.size(); + } + + /* 获取双向队列的长度 */ + int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 计算环形数组索引 */ + int index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 队首入队 */ + void pushFirst(int num) { + if (queSize == capacity()) { + cout << "双向队列已满" << endl; + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + front = index(front - 1); + // 将 num 添加至队首 + nums[front] = num; + queSize++; + } + + /* 队尾入队 */ + void pushLast(int num) { + if (queSize == capacity()) { + cout << "双向队列已满" << endl; + return; + } + // 计算队尾指针,指向队尾索引 + 1 + int rear = index(front + queSize); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 队首出队 */ + int popFirst() { + int num = peekFirst(); + // 队首指针向后移动一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 队尾出队 */ + int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 访问队首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return nums[front]; + } + + /* 访问队尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + // 计算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回数组用于打印 */ + vector toVector() { + // 仅转换有效长度范围内的列表元素 + vector res(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化双向队列 */ + ArrayDeque *deque = new ArrayDeque(10); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "双向队列 deque = "; + printVector(deque->toVector()); + + /* 访问元素 */ + int peekFirst = deque->peekFirst(); + cout << "队首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "队尾元素 peekLast = " << peekLast << endl; + + /* 元素入队 */ + deque->pushLast(4); + cout << "元素 4 队尾入队后 deque = "; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 队首入队后 deque = "; + printVector(deque->toVector()); + + /* 元素出队 */ + int popLast = deque->popLast(); + cout << "队尾出队元素 = " << popLast << ",队尾出队后 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "队首出队元素 = " << popFirst << ",队首出队后 deque = "; + printVector(deque->toVector()); + + /* 获取双向队列的长度 */ + int size = deque->size(); + cout << "双向队列长度 size = " << size << endl; + + /* 判断双向队列是否为空 */ + bool isEmpty = deque->isEmpty(); + cout << "双向队列是否为空 = " << boolalpha << isEmpty << endl; + return 0; +} diff --git a/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/codes/cpp/chapter_stack_and_queue/array_queue.cpp index de5e780f61..e6def1331c 100644 --- a/codes/cpp/chapter_stack_and_queue/array_queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -1,121 +1,129 @@ /** * File: array_queue.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 基于环形数组实现的队列 */ class ArrayQueue { -private: - int *nums; // 用于存储队列元素的数组 - int cap; // 队列容量 - int front = 0; // 头指针,指向队首 - int rear = 0; // 尾指针,指向队尾 + 1 + private: + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 队列长度 + int queCapacity; // 队列容量 -public: + public: ArrayQueue(int capacity) { // 初始化数组 - cap = capacity; nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; } /* 获取队列的容量 */ int capacity() { - return cap; + return queCapacity; } /* 获取队列的长度 */ int size() { - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity() + rear - front) % capacity(); + return queSize; } /* 判断队列是否为空 */ - bool empty() { - return rear - front == 0; + bool isEmpty() { + return size() == 0; } /* 入队 */ - void offer(int num) { - if (size() == capacity()) { + void push(int num) { + if (queSize == queCapacity) { cout << "队列已满" << endl; return; } - // 尾结点后添加 num + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % queCapacity; + // 将 num 添加至队尾 nums[rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity(); + queSize++; } /* 出队 */ - void poll() { + int pop() { int num = peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + front = (front + 1) % queCapacity; + queSize--; + return num; } /* 访问队首元素 */ int peek() { - if (empty()) + if (isEmpty()) throw out_of_range("队列为空"); return nums[front]; } /* 将数组转化为 Vector 并返回 */ vector toVector() { - int siz = size(); - int cap = capacity(); // 仅转换有效长度范围内的列表元素 - vector arr(siz); - for (int i = 0, j = front; i < siz; i++, j++) { - arr[i] = nums[j % cap]; + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; } return arr; } }; - /* Driver Code */ int main() { /* 初始化队列 */ int capacity = 10; - ArrayQueue* queue = new ArrayQueue(capacity); + ArrayQueue *queue = new ArrayQueue(capacity); /* 元素入队 */ - queue->offer(1); - queue->offer(3); - queue->offer(2); - queue->offer(5); - queue->offer(4); + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); cout << "队列 queue = "; - PrintUtil::printVector(queue->toVector()); + printVector(queue->toVector()); /* 访问队首元素 */ int peek = queue->peek(); cout << "队首元素 peek = " << peek << endl; - + /* 元素出队 */ - queue->poll(); - cout << "出队元素 poll = " << peek << ",出队后 queue = "; - PrintUtil::printVector(queue->toVector()); + peek = queue->pop(); + cout << "出队元素 pop = " << peek << ",出队后 queue = "; + printVector(queue->toVector()); /* 获取队列的长度 */ int size = queue->size(); cout << "队列长度 size = " << size << endl; /* 判断队列是否为空 */ - bool empty = queue->empty(); + bool empty = queue->isEmpty(); cout << "队列是否为空 = " << empty << endl; /* 测试环形数组 */ for (int i = 0; i < 10; i++) { - queue->offer(i); - queue->poll(); + queue->push(i); + queue->pop(); cout << "第 " << i << " 轮入队 + 出队后 queue = "; - PrintUtil::printVector(queue->toVector()); + printVector(queue->toVector()); } + // 释放内存 + delete queue; + return 0; } diff --git a/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/codes/cpp/chapter_stack_and_queue/array_stack.cpp index e6159e8cf5..c59e076b39 100644 --- a/codes/cpp/chapter_stack_and_queue/array_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -4,22 +4,22 @@ * Author: qualifier1024 (2539244001@qq.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 基于数组实现的栈 */ class ArrayStack { -private: + private: vector stack; - -public: + + public: /* 获取栈的长度 */ int size() { return stack.size(); } /* 判断栈是否为空 */ - bool empty() { - return stack.empty(); + bool isEmpty() { + return stack.size() == 0; } /* 入栈 */ @@ -28,14 +28,15 @@ class ArrayStack { } /* 出栈 */ - void pop() { - int oldTop = top(); + int pop() { + int num = top(); stack.pop_back(); + return num; } /* 访问栈顶元素 */ int top() { - if(empty()) + if (isEmpty()) throw out_of_range("栈为空"); return stack.back(); } @@ -46,11 +47,10 @@ class ArrayStack { } }; - /* Driver Code */ int main() { /* 初始化栈 */ - ArrayStack* stack = new ArrayStack(); + ArrayStack *stack = new ArrayStack(); /* 元素入栈 */ stack->push(1); @@ -59,24 +59,27 @@ int main() { stack->push(5); stack->push(4); cout << "栈 stack = "; - PrintUtil::printVector(stack->toVector()); + printVector(stack->toVector()); /* 访问栈顶元素 */ int top = stack->top(); cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ - stack->pop(); + top = stack->pop(); cout << "出栈元素 pop = " << top << ",出栈后 stack = "; - PrintUtil::printVector(stack->toVector()); + printVector(stack->toVector()); /* 获取栈的长度 */ int size = stack->size(); cout << "栈的长度 size = " << size << endl; /* 判断是否为空 */ - bool empty = stack->empty(); + bool empty = stack->isEmpty(); cout << "栈是否为空 = " << empty << endl; + // 释放内存 + delete stack; + return 0; } diff --git a/codes/cpp/chapter_stack_and_queue/deque.cpp b/codes/cpp/chapter_stack_and_queue/deque.cpp index e60af11e1d..16da9ae22c 100644 --- a/codes/cpp/chapter_stack_and_queue/deque.cpp +++ b/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -1,11 +1,10 @@ /** * File: deque.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" - +#include "../utils/common.hpp" /* Driver Code */ int main() { @@ -19,7 +18,7 @@ int main() { deque.push_front(3); deque.push_front(1); cout << "双向队列 deque = "; - PrintUtil::printDeque(deque); + printDeque(deque); /* 访问元素 */ int front = deque.front(); @@ -30,10 +29,10 @@ int main() { /* 元素出队 */ deque.pop_front(); cout << "队首出队元素 popFront = " << front << ",队首出队后 deque = "; - PrintUtil::printDeque(deque); + printDeque(deque); deque.pop_back(); cout << "队尾出队元素 popLast = " << back << ",队尾出队后 deque = "; - PrintUtil::printDeque(deque); + printDeque(deque); /* 获取双向队列的长度 */ int size = deque.size(); diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp new file mode 100644 index 0000000000..7eea86cee7 --- /dev/null +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -0,0 +1,194 @@ +/** + * File: linkedlist_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 双向链表节点 */ +struct DoublyListNode { + int val; // 节点值 + DoublyListNode *next; // 后继节点指针 + DoublyListNode *prev; // 前驱节点指针 + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } +}; + +/* 基于双向链表实现的双向队列 */ +class LinkedListDeque { + private: + DoublyListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize = 0; // 双向队列的长度 + + public: + /* 构造方法 */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* 析构方法 */ + ~LinkedListDeque() { + // 遍历链表删除节点,释放内存 + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* 获取双向队列的长度 */ + int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front->prev = node; + node->next = front; + front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + rear->next = node; + node->prev = rear; + rear = node; // 更新尾节点 + } + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + void pushFirst(int num) { + push(num, true); + } + + /* 队尾入队 */ + void pushLast(int num) { + push(num, false); + } + + /* 出队操作 */ + int pop(bool isFront) { + if (isEmpty()) + throw out_of_range("队列为空"); + int val; + // 队首出队操作 + if (isFront) { + val = front->val; // 暂存头节点值 + // 删除头节点 + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + } + delete front; + front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = rear->val; // 暂存尾节点值 + // 删除尾节点 + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + } + delete rear; + rear = rPrev; // 更新尾节点 + } + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + int popFirst() { + return pop(true); + } + + /* 队尾出队 */ + int popLast() { + return pop(false); + } + + /* 访问队首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return front->val; + } + + /* 访问队尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return rear->val; + } + + /* 返回数组用于打印 */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化双向队列 */ + LinkedListDeque *deque = new LinkedListDeque(); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "双向队列 deque = "; + printVector(deque->toVector()); + + /* 访问元素 */ + int peekFirst = deque->peekFirst(); + cout << "队首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "队尾元素 peekLast = " << peekLast << endl; + + /* 元素入队 */ + deque->pushLast(4); + cout << "元素 4 队尾入队后 deque ="; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 队首入队后 deque = "; + printVector(deque->toVector()); + + /* 元素出队 */ + int popLast = deque->popLast(); + cout << "队尾出队元素 = " << popLast << ",队尾出队后 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "队首出队元素 = " << popFirst << ",队首出队后 deque = "; + printVector(deque->toVector()); + + /* 获取双向队列的长度 */ + int size = deque->size(); + cout << "双向队列长度 size = " << size << endl; + + /* 判断双向队列是否为空 */ + bool isEmpty = deque->isEmpty(); + cout << "双向队列是否为空 = " << boolalpha << isEmpty << endl; + + // 释放内存 + delete deque; + + return 0; +} diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp index ae22740d6a..687f1361e4 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -1,44 +1,49 @@ /** * File: linkedlist_queue.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 基于链表实现的队列 */ class LinkedListQueue { -private: - ListNode *front, *rear; // 头结点 front ,尾结点 rear + private: + ListNode *front, *rear; // 头节点 front ,尾节点 rear int queSize; -public: + public: LinkedListQueue() { front = nullptr; rear = nullptr; queSize = 0; } + ~LinkedListQueue() { + // 遍历链表删除节点,释放内存 + freeMemoryLinkedList(front); + } + /* 获取队列的长度 */ int size() { return queSize; } /* 判断队列是否为空 */ - bool empty() { + bool isEmpty() { return queSize == 0; } /* 入队 */ - void offer(int num) { - // 尾结点后添加 num - ListNode* node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 + void push(int num) { + // 在尾节点后添加 num + ListNode *node = new ListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 if (front == nullptr) { front = node; rear = node; } - // 如果队列不为空,则将该结点添加到尾结点后 + // 如果队列不为空,则将该节点添加到尾节点后 else { rear->next = node; rear = node; @@ -47,14 +52,15 @@ class LinkedListQueue { } /* 出队 */ - void poll() { + int pop() { int num = peek(); - // 删除头结点 + // 删除头节点 ListNode *tmp = front; front = front->next; // 释放内存 - delete tmp; + delete tmp; queSize--; + return num; } /* 访问队首元素 */ @@ -66,7 +72,7 @@ class LinkedListQueue { /* 将链表转化为 Vector 并返回 */ vector toVector() { - ListNode* node = front; + ListNode *node = front; vector res(size()); for (int i = 0; i < res.size(); i++) { res[i] = node->val; @@ -76,37 +82,39 @@ class LinkedListQueue { } }; - /* Driver Code */ int main() { /* 初始化队列 */ - LinkedListQueue* queue = new LinkedListQueue(); + LinkedListQueue *queue = new LinkedListQueue(); /* 元素入队 */ - queue->offer(1); - queue->offer(3); - queue->offer(2); - queue->offer(5); - queue->offer(4); + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); cout << "队列 queue = "; - PrintUtil::printVector(queue->toVector()); + printVector(queue->toVector()); /* 访问队首元素 */ int peek = queue->peek(); cout << "队首元素 peek = " << peek << endl; /* 元素出队 */ - queue->poll(); - cout << "出队元素 poll = " << peek << ",出队后 queue = "; - PrintUtil::printVector(queue->toVector()); + peek = queue->pop(); + cout << "出队元素 pop = " << peek << ",出队后 queue = "; + printVector(queue->toVector()); /* 获取队列的长度 */ int size = queue->size(); cout << "队列长度 size = " << size << endl; /* 判断队列是否为空 */ - bool empty = queue->empty(); + bool empty = queue->isEmpty(); cout << "队列是否为空 = " << empty << endl; + // 释放内存 + delete queue; + return 0; } diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp index 495f2660ff..dbf1f67244 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -4,58 +4,64 @@ * Author: qualifier1024 (2539244001@qq.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 基于链表实现的栈 */ class LinkedListStack { -private: - ListNode* stackTop; // 将头结点作为栈顶 - int stkSize; // 栈的长度 + private: + ListNode *stackTop; // 将头节点作为栈顶 + int stkSize; // 栈的长度 -public: + public: LinkedListStack() { stackTop = nullptr; stkSize = 0; } + ~LinkedListStack() { + // 遍历链表删除节点,释放内存 + freeMemoryLinkedList(stackTop); + } + /* 获取栈的长度 */ int size() { return stkSize; } /* 判断栈是否为空 */ - bool empty() { + bool isEmpty() { return size() == 0; } /* 入栈 */ void push(int num) { - ListNode* node = new ListNode(num); + ListNode *node = new ListNode(num); node->next = stackTop; stackTop = node; stkSize++; } /* 出栈 */ - void pop() { + int pop() { int num = top(); ListNode *tmp = stackTop; stackTop = stackTop->next; // 释放内存 delete tmp; stkSize--; + return num; } /* 访问栈顶元素 */ int top() { - if (size() == 0) + if (isEmpty()) throw out_of_range("栈为空"); return stackTop->val; } /* 将 List 转化为 Array 并返回 */ vector toVector() { - ListNode* node = stackTop; + ListNode *node = stackTop; vector res(size()); for (int i = res.size() - 1; i >= 0; i--) { res[i] = node->val; @@ -65,11 +71,10 @@ class LinkedListStack { } }; - /* Driver Code */ int main() { /* 初始化栈 */ - LinkedListStack* stack = new LinkedListStack(); + LinkedListStack *stack = new LinkedListStack(); /* 元素入栈 */ stack->push(1); @@ -78,24 +83,27 @@ int main() { stack->push(5); stack->push(4); cout << "栈 stack = "; - PrintUtil::printVector(stack->toVector()); + printVector(stack->toVector()); /* 访问栈顶元素 */ int top = stack->top(); cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ - stack->pop(); + top = stack->pop(); cout << "出栈元素 pop = " << top << ",出栈后 stack = "; - PrintUtil::printVector(stack->toVector()); + printVector(stack->toVector()); /* 获取栈的长度 */ int size = stack->size(); cout << "栈的长度 size = " << size << endl; /* 判断是否为空 */ - bool empty = stack->empty(); + bool empty = stack->isEmpty(); cout << "栈是否为空 = " << empty << endl; + // 释放内存 + delete stack; + return 0; } diff --git a/codes/cpp/chapter_stack_and_queue/queue.cpp b/codes/cpp/chapter_stack_and_queue/queue.cpp index 6bc37302c7..b94ba40ddd 100644 --- a/codes/cpp/chapter_stack_and_queue/queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -4,14 +4,13 @@ * Author: qualifier1024 (2539244001@qq.com) */ -#include "../include/include.hpp" - +#include "../utils/common.hpp" /* Driver Code */ -int main(){ +int main() { /* 初始化队列 */ queue queue; - + /* 元素入队 */ queue.push(1); queue.push(3); @@ -19,24 +18,24 @@ int main(){ queue.push(5); queue.push(4); cout << "队列 queue = "; - PrintUtil::printQueue(queue); - + printQueue(queue); + /* 访问队首元素 */ int front = queue.front(); cout << "队首元素 front = " << front << endl; - + /* 元素出队 */ queue.pop(); cout << "出队元素 front = " << front << ",出队后 queue = "; - PrintUtil::printQueue(queue); - + printQueue(queue); + /* 获取队列的长度 */ int size = queue.size(); cout << "队列长度 size = " << size << endl; - + /* 判断队列是否为空 */ bool empty = queue.empty(); cout << "队列是否为空 = " << empty << endl; - + return 0; } diff --git a/codes/cpp/chapter_stack_and_queue/stack.cpp b/codes/cpp/chapter_stack_and_queue/stack.cpp index 7bbfd6b9fe..18743bfbbf 100644 --- a/codes/cpp/chapter_stack_and_queue/stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -4,8 +4,7 @@ * Author: qualifier1024 (2539244001@qq.com) */ -#include "../include/include.hpp" - +#include "../utils/common.hpp" /* Driver Code */ int main() { @@ -19,16 +18,16 @@ int main() { stack.push(5); stack.push(4); cout << "栈 stack = "; - PrintUtil::printStack(stack); + printStack(stack); /* 访问栈顶元素 */ int top = stack.top(); cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ - stack.pop(); + stack.pop(); // 无返回值 cout << "出栈元素 pop = " << top << ",出栈后 stack = "; - PrintUtil::printStack(stack); + printStack(stack); /* 获取栈的长度 */ int size = stack.size(); diff --git a/codes/cpp/chapter_tree/CMakeLists.txt b/codes/cpp/chapter_tree/CMakeLists.txt new file mode 100644 index 0000000000..fa7009bcb3 --- /dev/null +++ b/codes/cpp/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.cpp) +add_executable(binary_search_tree binary_search_tree.cpp) +add_executable(binary_tree binary_tree.cpp) +add_executable(binary_tree_bfs binary_tree_bfs.cpp) +add_executable(binary_tree_dfs binary_tree_dfs.cpp) +add_executable(array_binary_tree array_binary_tree.cpp) \ No newline at end of file diff --git a/codes/cpp/chapter_tree/array_binary_tree.cpp b/codes/cpp/chapter_tree/array_binary_tree.cpp new file mode 100644 index 0000000000..0b76df48a8 --- /dev/null +++ b/codes/cpp/chapter_tree/array_binary_tree.cpp @@ -0,0 +1,137 @@ +/** + * File: array_binary_tree.cpp + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree { + public: + /* 构造方法 */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* 列表容量 */ + int size() { + return tree.size(); + } + + /* 获取索引为 i 节点的值 */ + int val(int i) { + // 若索引越界,则返回 INT_MAX ,代表空位 + if (i < 0 || i >= size()) + return INT_MAX; + return tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + int parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + vector levelOrder() { + vector res; + // 直接遍历数组 + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* 前序遍历 */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* 中序遍历 */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* 后序遍历 */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + private: + vector tree; + + /* 深度优先遍历 */ + void dfs(int i, string order, vector &res) { + // 若为空位,则返回 + if (val(i) == INT_MAX) + return; + // 前序遍历 + if (order == "pre") + res.push_back(val(i)); + dfs(left(i), order, res); + // 中序遍历 + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // 后序遍历 + if (order == "post") + res.push_back(val(i)); + } +}; + +/* Driver Code */ +int main() { + // 初始化二叉树 + // 使用 INT_MAX 代表空位 nullptr + vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + TreeNode *root = vectorToTree(arr); + cout << "\n初始化二叉树\n"; + cout << "二叉树的数组表示:\n"; + printVector(arr); + cout << "二叉树的链表表示:\n"; + printTree(root); + + // 数组表示下的二叉树类 + ArrayBinaryTree abt(arr); + + // 访问节点 + int i = 1; + int l = abt.left(i), r = abt.right(i), p = abt.parent(i); + cout << "\n当前节点的索引为 " << i << ",值为 " << abt.val(i) << "\n"; + cout << "其左子节点的索引为 " << l << ",值为 " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; + cout << "其右子节点的索引为 " << r << ",值为 " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; + cout << "其父节点的索引为 " << p << ",值为 " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; + + // 遍历树 + vector res = abt.levelOrder(); + cout << "\n层序遍历为: "; + printVector(res); + res = abt.preOrder(); + cout << "前序遍历为: "; + printVector(res); + res = abt.inOrder(); + cout << "中序遍历为: "; + printVector(res); + res = abt.postOrder(); + cout << "后序遍历为: "; + printVector(res); + + return 0; +} diff --git a/codes/cpp/chapter_tree/avl_tree.cpp b/codes/cpp/chapter_tree/avl_tree.cpp new file mode 100644 index 0000000000..46ab54b230 --- /dev/null +++ b/codes/cpp/chapter_tree/avl_tree.cpp @@ -0,0 +1,233 @@ +/** + * File: avl_tree.cpp + * Created Time: 2023-02-03 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* AVL 树 */ +class AVLTree { + private: + /* 更新节点高度 */ + void updateHeight(TreeNode *node) { + // 节点高度等于最高子树高度 + 1 + node->height = max(height(node->left), height(node->right)) + 1; + } + + /* 右旋操作 */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child = node->left; + TreeNode *grandChild = child->right; + // 以 child 为原点,将 node 向右旋转 + child->right = node; + node->left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 左旋操作 */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child = node->right; + TreeNode *grandChild = child->left; + // 以 child 为原点,将 node 向左旋转 + child->left = node; + node->right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode *rotate(TreeNode *node) { + // 获取节点 node 的平衡因子 + int _balanceFactor = balanceFactor(node); + // 左偏树 + if (_balanceFactor > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏树 + if (_balanceFactor < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + + /* 递归插入节点(辅助方法) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == nullptr) + return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node->val) + node->left = insertHelper(node->left, val); + else if (val > node->val) + node->right = insertHelper(node->right, val); + else + return node; // 重复节点不插入,直接返回 + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + /* 递归删除节点(辅助方法) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 1. 查找节点并删除 */ + if (val < node->val) + node->left = removeHelper(node->left, val); + else if (val > node->val) + node->right = removeHelper(node->right, val); + else { + if (node->left == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == nullptr) { + delete node; + return nullptr; + } + // 子节点数量 = 1 ,直接删除 node + else { + delete node; + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode *temp = node->right; + while (temp->left != nullptr) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + public: + TreeNode *root; // 根节点 + + /* 获取节点高度 */ + int height(TreeNode *node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == nullptr ? -1 : node->height; + } + + /* 获取平衡因子 */ + int balanceFactor(TreeNode *node) { + // 空节点平衡因子为 0 + if (node == nullptr) + return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node->left) - height(node->right); + } + + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 查找节点 */ + TreeNode *search(int val) { + TreeNode *cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 目标节点在 cur 的右子树中 + if (cur->val < val) + cur = cur->right; + // 目标节点在 cur 的左子树中 + else if (cur->val > val) + cur = cur->left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + + /*构造方法*/ + AVLTree() : root(nullptr) { + } + + /*析构方法*/ + ~AVLTree() { + freeMemoryTree(root); + } +}; + +void testInsert(AVLTree &tree, int val) { + tree.insert(val); + cout << "\n插入节点 " << val << " 后,AVL 树为" << endl; + printTree(tree.root); +} + +void testRemove(AVLTree &tree, int val) { + tree.remove(val); + cout << "\n删除节点 " << val << " 后,AVL 树为" << endl; + printTree(tree.root); +} + +/* Driver Code */ +int main() { + /* 初始化空 AVL 树 */ + AVLTree avlTree; + + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重复节点 */ + testInsert(avlTree, 7); + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(avlTree, 8); // 删除度为 0 的节点 + testRemove(avlTree, 5); // 删除度为 1 的节点 + testRemove(avlTree, 4); // 删除度为 2 的节点 + + /* 查询节点 */ + TreeNode *node = avlTree.search(7); + cout << "\n查找到的节点对象为 " << node << ",节点值 = " << node->val << endl; +} diff --git a/codes/cpp/chapter_tree/binary_search_tree.cpp b/codes/cpp/chapter_tree/binary_search_tree.cpp index 006d7d3084..420d0d159d 100644 --- a/codes/cpp/chapter_tree/binary_search_tree.cpp +++ b/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -1,156 +1,170 @@ /** * File: binary_search_tree.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 二叉搜索树 */ class BinarySearchTree { -private: - TreeNode* root; + private: + TreeNode *root; -public: - BinarySearchTree(vector nums) { - sort(nums.begin(), nums.end()); // 排序数组 - root = buildTree(nums, 0, nums.size() - 1); // 构建二叉搜索树 + public: + /* 构造方法 */ + BinarySearchTree() { + // 初始化空树 + root = nullptr; } - /* 获取二叉树根结点 */ - TreeNode* getRoot() { - return root; + /* 析构方法 */ + ~BinarySearchTree() { + freeMemoryTree(root); } - /* 构建二叉搜索树 */ - TreeNode* buildTree(vector nums, int i, int j) { - if (i > j) return nullptr; - // 将数组中间结点作为根结点 - int mid = (i + j) / 2; - TreeNode* root = new TreeNode(nums[mid]); - // 递归建立左子树和右子树 - root->left = buildTree(nums, i, mid - 1); - root->right = buildTree(nums, mid + 1, j); + /* 获取二叉树根节点 */ + TreeNode *getRoot() { return root; } - /* 查找结点 */ - TreeNode* search(int num) { - TreeNode* cur = root; - // 循环查找,越过叶结点后跳出 + /* 查找节点 */ + TreeNode *search(int num) { + TreeNode *cur = root; + // 循环查找,越过叶节点后跳出 while (cur != nullptr) { - // 目标结点在 root 的右子树中 - if (cur->val < num) cur = cur->right; - // 目标结点在 root 的左子树中 - else if (cur->val > num) cur = cur->left; - // 找到目标结点,跳出循环 - else break; + // 目标节点在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 目标节点在 cur 的左子树中 + else if (cur->val > num) + cur = cur->left; + // 找到目标节点,跳出循环 + else + break; } - // 返回目标结点 + // 返回目标节点 return cur; } - /* 插入结点 */ - TreeNode* insert(int num) { - // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; + /* 插入节点 */ + void insert(int num) { + // 若树为空,则初始化根节点 + if (root == nullptr) { + root = new TreeNode(num); + return; + } TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != nullptr) { - // 找到重复结点,直接返回 - if (cur->val == num) return nullptr; + // 找到重复节点,直接返回 + if (cur->val == num) + return; pre = cur; - // 插入位置在 root 的右子树中 - if (cur->val < num) cur = cur->right; - // 插入位置在 root 的左子树中 - else cur = cur->left; + // 插入位置在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 插入位置在 cur 的左子树中 + else + cur = cur->left; } - // 插入结点 val - TreeNode* node = new TreeNode(num); - if (pre->val < num) pre->right = node; - else pre->left = node; - return node; + // 插入节点 + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; } - /* 删除结点 */ - TreeNode* remove(int num) { + /* 删除节点 */ + void remove(int num) { // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; + if (root == nullptr) + return; TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != nullptr) { - // 找到待删除结点,跳出循环 - if (cur->val == num) break; + // 找到待删除节点,跳出循环 + if (cur->val == num) + break; pre = cur; - // 待删除结点在 root 的右子树中 - if (cur->val < num) cur = cur->right; - // 待删除结点在 root 的左子树中 - else cur = cur->left; + // 待删除节点在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 待删除节点在 cur 的左子树中 + else + cur = cur->left; } - // 若无待删除结点,则直接返回 - if (cur == nullptr) return nullptr; - // 子结点数量 = 0 or 1 + // 若无待删除节点,则直接返回 + if (cur == nullptr) + return; + // 子节点数量 = 0 or 1 if (cur->left == nullptr || cur->right == nullptr) { - // 当子结点数量 = 0 / 1 时, child = nullptr / 该子结点 - TreeNode* child = cur->left != nullptr ? cur->left : cur->right; - // 删除结点 cur - if (pre->left == cur) pre->left = child; - else pre->right = child; + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // 删除节点 cur + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } // 释放内存 delete cur; } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 - TreeNode* nex = getInOrderNext(cur->right); - int tmp = nex->val; - // 递归删除结点 nex - remove(nex->val); - // 将 nex 的值复制给 cur - cur->val = tmp; + // 获取中序遍历中 cur 的下一个节点 + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 递归删除节点 tmp + remove(tmp->val); + // 用 tmp 覆盖 cur + cur->val = tmpVal; } - return cur; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - TreeNode* getInOrderNext(TreeNode* root) { - if (root == nullptr) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root->left != nullptr) { - root = root->left; - } - return root; } }; - /* Driver Code */ int main() { /* 初始化二叉搜索树 */ - vector nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - BinarySearchTree* bst = new BinarySearchTree(nums); + BinarySearchTree *bst = new BinarySearchTree(); + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + for (int num : nums) { + bst->insert(num); + } cout << endl << "初始化的二叉树为\n" << endl; - PrintUtil::printTree(bst->getRoot()); + printTree(bst->getRoot()); - /* 查找结点 */ - TreeNode* node = bst->search(7); - cout << endl << "查找到的结点对象为 " << node << ",结点值 = " << node->val << endl; + /* 查找节点 */ + TreeNode *node = bst->search(7); + cout << endl << "查找到的节点对象为 " << node << ",节点值 = " << node->val << endl; - /* 插入结点 */ - node = bst->insert(16); - cout << endl << "插入结点 16 后,二叉树为\n" << endl; - PrintUtil::printTree(bst->getRoot()); + /* 插入节点 */ + bst->insert(16); + cout << endl << "插入节点 16 后,二叉树为\n" << endl; + printTree(bst->getRoot()); - /* 删除结点 */ + /* 删除节点 */ bst->remove(1); - cout << endl << "删除结点 1 后,二叉树为\n" << endl; - PrintUtil::printTree(bst->getRoot()); + cout << endl << "删除节点 1 后,二叉树为\n" << endl; + printTree(bst->getRoot()); bst->remove(2); - cout << endl << "删除结点 2 后,二叉树为\n" << endl; - PrintUtil::printTree(bst->getRoot()); + cout << endl << "删除节点 2 后,二叉树为\n" << endl; + printTree(bst->getRoot()); bst->remove(4); - cout << endl << "删除结点 4 后,二叉树为\n" << endl; - PrintUtil::printTree(bst->getRoot()); + cout << endl << "删除节点 4 后,二叉树为\n" << endl; + printTree(bst->getRoot()); + + // 释放内存 + delete bst; return 0; } diff --git a/codes/cpp/chapter_tree/binary_tree.cpp b/codes/cpp/chapter_tree/binary_tree.cpp index cd06778ad5..8146d6a625 100644 --- a/codes/cpp/chapter_tree/binary_tree.cpp +++ b/codes/cpp/chapter_tree/binary_tree.cpp @@ -1,41 +1,43 @@ /** * File: binary_tree.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" - +#include "../utils/common.hpp" /* Driver Code */ int main() { /* 初始化二叉树 */ - // 初始化结点 - TreeNode* n1 = new TreeNode(1); - TreeNode* n2 = new TreeNode(2); - TreeNode* n3 = new TreeNode(3); - TreeNode* n4 = new TreeNode(4); - TreeNode* n5 = new TreeNode(5); - // 构建引用指向(即指针) + // 初始化节点 + TreeNode *n1 = new TreeNode(1); + TreeNode *n2 = new TreeNode(2); + TreeNode *n3 = new TreeNode(3); + TreeNode *n4 = new TreeNode(4); + TreeNode *n5 = new TreeNode(5); + // 构建节点之间的引用(指针) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; cout << endl << "初始化二叉树\n" << endl; - PrintUtil::printTree(n1); + printTree(n1); - /* 插入与删除结点 */ - TreeNode* P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + /* 插入与删除节点 */ + TreeNode *P = new TreeNode(0); + // 在 n1 -> n2 中间插入节点 P n1->left = P; P->left = n2; - cout << endl << "插入结点 P 后\n" << endl; - PrintUtil::printTree(n1); - // 删除结点 P + cout << endl << "插入节点 P 后\n" << endl; + printTree(n1); + // 删除节点 P n1->left = n2; - delete P; // 释放内存 - cout << endl << "删除结点 P 后\n" << endl; - PrintUtil::printTree(n1); + delete P; // 释放内存 + cout << endl << "删除节点 P 后\n" << endl; + printTree(n1); + + // 释放内存 + freeMemoryTree(n1); return 0; } diff --git a/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/codes/cpp/chapter_tree/binary_tree_bfs.cpp index ffc2e372c7..587514b8b0 100644 --- a/codes/cpp/chapter_tree/binary_tree_bfs.cpp +++ b/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -1,43 +1,42 @@ /** * File: binary_tree_bfs.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" /* 层序遍历 */ -vector hierOrder(TreeNode* root) { - // 初始化队列,加入根结点 - queue queue; +vector levelOrder(TreeNode *root) { + // 初始化队列,加入根节点 + queue queue; queue.push(root); // 初始化一个列表,用于保存遍历序列 vector vec; while (!queue.empty()) { - TreeNode* node = queue.front(); - queue.pop(); // 队列出队 - vec.push_back(node->val); // 保存结点 + TreeNode *node = queue.front(); + queue.pop(); // 队列出队 + vec.push_back(node->val); // 保存节点值 if (node->left != nullptr) - queue.push(node->left); // 左子结点入队 + queue.push(node->left); // 左子节点入队 if (node->right != nullptr) - queue.push(node->right); // 右子结点入队 + queue.push(node->right); // 右子节点入队 } return vec; } - /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode* root = vecToTree(vector { 1, 2, 3, 4, 5, 6, 7 }); + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "初始化二叉树\n" << endl; - PrintUtil::printTree(root); + printTree(root); /* 层序遍历 */ - vector vec = hierOrder(root); - cout << endl << "层序遍历的结点打印序列 = "; - PrintUtil::printVector(vec); + vector vec = levelOrder(root); + cout << endl << "层序遍历的节点打印序列 = "; + printVector(vec); return 0; } diff --git a/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/codes/cpp/chapter_tree/binary_tree_dfs.cpp index 5ed5b78fe4..778c2a06c7 100644 --- a/codes/cpp/chapter_tree/binary_tree_dfs.cpp +++ b/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -1,67 +1,69 @@ /** * File: binary_tree_dfs.cpp * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ -#include "../include/include.hpp" +#include "../utils/common.hpp" // 初始化列表,用于存储遍历序列 vector vec; /* 前序遍历 */ -void preOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 +void preOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 vec.push_back(root->val); preOrder(root->left); preOrder(root->right); } /* 中序遍历 */ -void inOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 +void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root->left); vec.push_back(root->val); inOrder(root->right); } /* 后序遍历 */ -void postOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 +void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root->left); postOrder(root->right); vec.push_back(root->val); } - /* Driver Code */ int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode* root = vecToTree(vector { 1, 2, 3, 4, 5, 6, 7 }); + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "初始化二叉树\n" << endl; - PrintUtil::printTree(root); + printTree(root); /* 前序遍历 */ vec.clear(); preOrder(root); - cout << endl << "前序遍历的结点打印序列 = "; - PrintUtil::printVector(vec); + cout << endl << "前序遍历的节点打印序列 = "; + printVector(vec); /* 中序遍历 */ vec.clear(); inOrder(root); - cout << endl << "中序遍历的结点打印序列 = "; - PrintUtil::printVector(vec); + cout << endl << "中序遍历的节点打印序列 = "; + printVector(vec); /* 后序遍历 */ vec.clear(); postOrder(root); - cout << endl << "后序遍历的结点打印序列 = "; - PrintUtil::printVector(vec); - + cout << endl << "后序遍历的节点打印序列 = "; + printVector(vec); + return 0; } diff --git a/codes/cpp/include/ListNode.hpp b/codes/cpp/include/ListNode.hpp deleted file mode 100644 index 9e6bd06921..0000000000 --- a/codes/cpp/include/ListNode.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/** - * File: PrintUtil.hpp - * Created Time: 2021-12-19 - * Author: Krahets (krahets@163.com) - */ - -#pragma once - -#include -using namespace std; - -/** - * @brief Definition for a singly-linked list node - * - */ -struct ListNode { - int val; - ListNode *next; - ListNode(int x) : val(x), next(nullptr) {} -}; - -/** - * @brief Generate a linked list with a vector - * - * @param list - * @return ListNode* - */ -ListNode* vecToLinkedList(vector list) { - ListNode *dum = new ListNode(0); - ListNode *head = dum; - for (int val : list) { - head->next = new ListNode(val); - head = head->next; - } - return dum->next; -} - -/** - * @brief Get a list node with specific value from a linked list - * - * @param head - * @param val - * @return ListNode* - */ -ListNode* getListNode(ListNode *head, int val) { - while (head != nullptr && head->val != val) { - head = head->next; - } - return head; -} diff --git a/codes/cpp/include/PrintUtil.hpp b/codes/cpp/include/PrintUtil.hpp deleted file mode 100644 index e788d16503..0000000000 --- a/codes/cpp/include/PrintUtil.hpp +++ /dev/null @@ -1,296 +0,0 @@ -/** - * File: PrintUtil.hpp - * Created Time: 2021-12-19 - * Author: Krahets (krahets@163.com), msk397 (machangxinq@gmail.com) - */ - -#pragma once - -#include -#include -#include -#include -#include "ListNode.hpp" -#include "TreeNode.hpp" - -class PrintUtil { - public: - /** - * @brief Find an element in a vector - * - * @tparam T - * @param vec - * @param ele - * @return int - */ - template - static int vecFind(const vector& vec, T ele) { - int j = INT_MAX; - for (int i = 0; i < vec.size(); i++) { - if (vec[i] == ele) { - j = i; - } - } - return j; - } - - /** - * @brief Concatenate a vector with a delim - * - * @tparam T - * @param delim - * @param vec - * @return string - */ - template - static string strJoin(const string& delim, const T& vec) { - ostringstream s; - for (const auto& i : vec) { - if (&i != &vec[0]) { - s << delim; - } - s << i; - } - return s.str(); - } - - /** - * @brief Repeat a string for n times - * - * @param str - * @param n - * @return string - */ - static string strRepeat(string str, int n) { - ostringstream os; - for(int i = 0; i < n; i++) - os << str; - return os.str(); - } - - /** - * @brief Print an Array - * - * @tparam T - * @tparam n - */ - template - static void printArray(T* arr, int n) - { - cout << "["; - for (size_t i = 0; i < n - 1; i++) { - cout << arr[i] << ", "; - } - cout << arr[n - 1] << "]" << '\n'; - } - - /** - * @brief Get the Vector String object - * - * @tparam T - * @param list - * @return string - */ - template - static string getVectorString(vector &list) { - return "[" + strJoin(", ", list) + "]"; - } - - /** - * @brief Print a vector - * - * @tparam T - * @param list - */ - template - static void printVector(vector list) { - cout << getVectorString(list) << '\n'; - } - - /** - * @brief Print a vector matrix - * - * @tparam T - * @param matrix - */ - template - static void printVectorMatrix(vector> &matrix) { - cout << "[" << '\n'; - for (vector &list : matrix) - cout << " " + getVectorString(list) + "," << '\n'; - cout << "]" << '\n'; - } - - /** - * @brief Print a linked list - * - * @param head - */ - static void printLinkedList(ListNode *head) { - vector list; - while (head != nullptr) { - list.push_back(head->val); - head = head->next; - } - - cout << strJoin(" -> ", list) << '\n'; - } - - /** - * @brief This tree printer is borrowed from TECHIE DELIGHT - * https://www.techiedelight.com/c-program-print-binary-tree/ - */ - struct Trunk { - Trunk *prev; - string str; - Trunk(Trunk *prev, string str) { - this->prev = prev; - this->str = str; - } - }; - - /** - * @brief Helper function to print branches of the binary tree - * - * @param p - */ - static void showTrunks(Trunk *p) { - if (p == nullptr) { - return; - } - - showTrunks(p->prev); - cout << p->str; - } - - /** - * @brief The interface of the tree printer - * - * @param root - */ - static void printTree(TreeNode *root) { - printTree(root, nullptr, false); - } - - /** - * @brief Print a binary tree - * - * @param root - * @param prev - * @param isLeft - */ - static void printTree(TreeNode *root, Trunk *prev, bool isLeft) { - if (root == nullptr) { - return; - } - - string prev_str = " "; - Trunk *trunk = new Trunk(prev, prev_str); - - printTree(root->right, trunk, true); - - if (!prev) { - trunk->str = "———"; - } - else if (isLeft) { - trunk->str = "/———"; - prev_str = " |"; - } - else { - trunk->str = "\\———"; - prev->str = prev_str; - } - - showTrunks(trunk); - cout << " " << root->val << endl; - - if (prev) { - prev->str = prev_str; - } - trunk->str = " |"; - - printTree(root->left, trunk, false); - } - - /** - * @brief Print a stack - * - * @tparam T - * @param stk - */ - template - static void printStack(stack stk) { - // Reverse the input stack - stack tmp; - while(!stk.empty()) { - tmp.push(stk.top()); - stk.pop(); - } - // Generate the string to print - ostringstream s; - bool flag = true; - while(!tmp.empty()) { - if (flag) { - s << tmp.top(); - flag = false; - } - else s << ", " << tmp.top(); - tmp.pop(); - } - cout << "[" + s.str() + "]" << '\n'; - } - - /** - * @brief - * - * @tparam T - * @param queue - */ - template - static void printQueue(queue queue) - { - // Generate the string to print - ostringstream s; - bool flag = true; - while(!queue.empty()) { - if (flag) { - s << queue.front(); - flag = false; - } - else s << ", " << queue.front(); - queue.pop(); - } - cout << "[" + s.str() + "]" << '\n'; - } - - template - static void printDeque(deque deque) { - // Generate the string to print - ostringstream s; - bool flag = true; - while(!deque.empty()) { - if (flag) { - s << deque.front(); - flag = false; - } - else s << ", " << deque.front(); - deque.pop_front(); - } - cout << "[" + s.str() + "]" << '\n'; - } - - /** - * @brief Print a HashMap - * - * @tparam TKey - * @tparam TValue - * @param map - */ - // 定义模板参数 TKey 和 TValue,用于指定键值对的类型 - template - static void printHashMap(unordered_map map) { - for (auto kv : map) { - cout << kv.first << " -> " << kv.second << '\n'; - } - } -}; diff --git a/codes/cpp/include/TreeNode.hpp b/codes/cpp/include/TreeNode.hpp deleted file mode 100644 index 82f2ce9812..0000000000 --- a/codes/cpp/include/TreeNode.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * File: PrintUtil.hpp - * Created Time: 2021-12-19 - * Author: Krahets (krahets@163.com) - */ - -#pragma once - -/** - * @brief Definition for a binary tree node - * - */ -struct TreeNode { - int val{}; - int height = 0; - TreeNode *parent{}; - TreeNode *left{}; - TreeNode *right{}; - TreeNode() = default; - explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) {} -}; - -/** - * @brief Generate a binary tree with a vector - * - * @param list - * @return TreeNode* - */ -TreeNode *vecToTree(vector list) { - if (list.empty()) - return nullptr; - - auto *root = new TreeNode(list[0]); - queue que; - que.emplace(root); - size_t n = list.size(), index = 0; - while (!que.empty()) { - auto node = que.front(); - que.pop(); - if (++index >= n) break; - if (index < n) { - node->left = new TreeNode(list[index]); - que.emplace(node->left); - } - if (++index >= n) break; - if (index < n) { - node->right = new TreeNode(list[index]); - que.emplace(node->right); - } - } - - return root; -} - -/** - * @brief Get a tree node with specific value in a binary tree - * - * @param root - * @param val - * @return TreeNode* - */ -TreeNode *getTreeNode(TreeNode *root, int val) { - if (root == nullptr) - return nullptr; - if (root->val == val) - return root; - TreeNode *left = getTreeNode(root->left, val); - TreeNode *right = getTreeNode(root->right, val); - return left != nullptr ? left : right; -} diff --git a/codes/cpp/include/include.hpp b/codes/cpp/include/include.hpp deleted file mode 100644 index 03bf9b5036..0000000000 --- a/codes/cpp/include/include.hpp +++ /dev/null @@ -1,27 +0,0 @@ -/** - * File: PrintUtil.hpp - * Created Time: 2021-12-19 - * Author: Krahets (krahets@163.com) - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ListNode.hpp" -#include "TreeNode.hpp" -#include "PrintUtil.hpp" - -using namespace std; diff --git a/codes/cpp/utils/CMakeLists.txt b/codes/cpp/utils/CMakeLists.txt new file mode 100644 index 0000000000..775a558690 --- /dev/null +++ b/codes/cpp/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(utils + common.hpp print_utils.hpp + list_node.hpp tree_node.hpp + vertex.hpp) \ No newline at end of file diff --git a/codes/cpp/utils/common.hpp b/codes/cpp/utils/common.hpp new file mode 100644 index 0000000000..c72dabd882 --- /dev/null +++ b/codes/cpp/utils/common.hpp @@ -0,0 +1,28 @@ +/** + * File: common.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.hpp" +#include "print_utils.hpp" +#include "tree_node.hpp" +#include "vertex.hpp" + +using namespace std; diff --git a/codes/cpp/utils/list_node.hpp b/codes/cpp/utils/list_node.hpp new file mode 100644 index 0000000000..7526fe608d --- /dev/null +++ b/codes/cpp/utils/list_node.hpp @@ -0,0 +1,42 @@ +/** + * File: list_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 链表节点 */ +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(nullptr) { + } +}; + +/* 将列表反序列化为链表 */ +ListNode *vecToLinkedList(vector list) { + ListNode *dum = new ListNode(0); + ListNode *head = dum; + for (int val : list) { + head->next = new ListNode(val); + head = head->next; + } + return dum->next; +} + +/* 释放分配给链表的内存空间 */ +void freeMemoryLinkedList(ListNode *cur) { + // 释放内存 + ListNode *pre; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } +} diff --git a/codes/cpp/utils/print_utils.hpp b/codes/cpp/utils/print_utils.hpp new file mode 100644 index 0000000000..ff66fb2044 --- /dev/null +++ b/codes/cpp/utils/print_utils.hpp @@ -0,0 +1,228 @@ +/** + * File: print_utils.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com) + */ + +#pragma once + +#include "list_node.hpp" +#include "tree_node.hpp" +#include +#include +#include +#include + +/* Find an element in a vector */ +template int vecFind(const vector &vec, T ele) { + int j = INT_MAX; + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == ele) { + j = i; + } + } + return j; +} + +/* Concatenate a vector with a delim */ +template string strJoin(const string &delim, const T &vec) { + ostringstream s; + for (const auto &i : vec) { + if (&i != &vec[0]) { + s << delim; + } + s << i; + } + return s.str(); +} + +/* Repeat a string for n times */ +string strRepeat(string str, int n) { + ostringstream os; + for (int i = 0; i < n; i++) + os << str; + return os.str(); +} + +/* 打印数组 */ +template void printArray(T *arr, int n) { + cout << "["; + for (int i = 0; i < n - 1; i++) { + cout << arr[i] << ", "; + } + if (n >= 1) + cout << arr[n - 1] << "]" << endl; + else + cout << "]" << endl; +} + +/* Get the Vector String object */ +template string getVectorString(vector &list) { + return "[" + strJoin(", ", list) + "]"; +} + +/* 打印列表 */ +template void printVector(vector list) { + cout << getVectorString(list) << '\n'; +} + +/* 打印矩阵 */ +template void printVectorMatrix(vector> &matrix) { + cout << "[" << '\n'; + for (vector &list : matrix) + cout << " " + getVectorString(list) + "," << '\n'; + cout << "]" << '\n'; +} + +/* 打印链表 */ +void printLinkedList(ListNode *head) { + vector list; + while (head != nullptr) { + list.push_back(head->val); + head = head->next; + } + + cout << strJoin(" -> ", list) << '\n'; +} + +struct Trunk { + Trunk *prev; + string str; + Trunk(Trunk *prev, string str) { + this->prev = prev; + this->str = str; + } +}; + +void showTrunks(Trunk *p) { + if (p == nullptr) { + return; + } + + showTrunks(p->prev); + cout << p->str; +} + +/** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode *root, Trunk *prev, bool isRight) { + if (root == nullptr) { + return; + } + + string prev_str = " "; + Trunk trunk(prev, prev_str); + + printTree(root->right, &trunk, true); + + if (!prev) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev->str = prev_str; + } + + showTrunks(&trunk); + cout << " " << root->val << endl; + + if (prev) { + prev->str = prev_str; + } + trunk.str = " |"; + + printTree(root->left, &trunk, false); +} + +/* 打印二叉树 */ +void printTree(TreeNode *root) { + printTree(root, nullptr, false); +} + +/* 打印栈 */ +template void printStack(stack stk) { + // Reverse the input stack + stack tmp; + while (!stk.empty()) { + tmp.push(stk.top()); + stk.pop(); + } + // Generate the string to print + ostringstream s; + bool flag = true; + while (!tmp.empty()) { + if (flag) { + s << tmp.top(); + flag = false; + } else + s << ", " << tmp.top(); + tmp.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 打印队列 */ +template void printQueue(queue queue) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!queue.empty()) { + if (flag) { + s << queue.front(); + flag = false; + } else + s << ", " << queue.front(); + queue.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 打印双向队列 */ +template void printDeque(deque deque) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!deque.empty()) { + if (flag) { + s << deque.front(); + flag = false; + } else + s << ", " << deque.front(); + deque.pop_front(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 打印哈希表 */ +// 定义模板参数 TKey 和 TValue ,用于指定键值对的类型 +template void printHashMap(unordered_map map) { + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << '\n'; + } +} + +/* Expose the underlying storage of the priority_queue container */ +template S &Container(priority_queue &pq) { + struct HackedQueue : private priority_queue { + static S &Container(priority_queue &pq) { + return pq.*&HackedQueue::c; + } + }; + return HackedQueue::Container(pq); +} + +/* 打印堆(优先队列) */ +template void printHeap(priority_queue &heap) { + vector vec = Container(heap); + cout << "堆的数组表示:"; + printVector(vec); + cout << "堆的树状表示:" << endl; + TreeNode *root = vectorToTree(vec); + printTree(root); + freeMemoryTree(root); +} diff --git a/codes/cpp/utils/tree_node.hpp b/codes/cpp/utils/tree_node.hpp new file mode 100644 index 0000000000..c919ea722c --- /dev/null +++ b/codes/cpp/utils/tree_node.hpp @@ -0,0 +1,84 @@ +/** + * File: tree_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 二叉树节点结构体 */ +struct TreeNode { + int val{}; + int height = 0; + TreeNode *parent{}; + TreeNode *left{}; + TreeNode *right{}; + TreeNode() = default; + explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) { + } +}; + +// 序列化编码规则请参考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二叉树的数组表示: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二叉树的链表表示: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* 将列表反序列化为二叉树:递归 */ +TreeNode *vectorToTreeDFS(vector &arr, int i) { + if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) { + return nullptr; + } + TreeNode *root = new TreeNode(arr[i]); + root->left = vectorToTreeDFS(arr, 2 * i + 1); + root->right = vectorToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* 将列表反序列化为二叉树 */ +TreeNode *vectorToTree(vector arr) { + return vectorToTreeDFS(arr, 0); +} + +/* 将二叉树序列化为列表:递归 */ +void treeToVecorDFS(TreeNode *root, int i, vector &res) { + if (root == nullptr) + return; + while (i >= res.size()) { + res.push_back(INT_MAX); + } + res[i] = root->val; + treeToVecorDFS(root->left, 2 * i + 1, res); + treeToVecorDFS(root->right, 2 * i + 2, res); +} + +/* 将二叉树序列化为列表 */ +vector treeToVecor(TreeNode *root) { + vector res; + treeToVecorDFS(root, 0, res); + return res; +} + +/* 释放二叉树内存 */ +void freeMemoryTree(TreeNode *root) { + if (root == nullptr) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + delete root; +} diff --git a/codes/cpp/utils/vertex.hpp b/codes/cpp/utils/vertex.hpp new file mode 100644 index 0000000000..42917ba392 --- /dev/null +++ b/codes/cpp/utils/vertex.hpp @@ -0,0 +1,36 @@ +/** + * File: vertex.hpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include + +using namespace std; + +/* 顶点类 */ +struct Vertex { + int val; + Vertex(int x) : val(x) { + } +}; + +/* 输入值列表 vals ,返回顶点列表 vets */ +vector valsToVets(vector vals) { + vector vets; + for (int val : vals) { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* 输入顶点列表 vets ,返回值列表 vals */ +vector vetsToVals(vector vets) { + vector vals; + for (Vertex *vet : vets) { + vals.push_back(vet->val); + } + return vals; +} diff --git a/codes/csharp/.editorconfig b/codes/csharp/.editorconfig new file mode 100644 index 0000000000..0a2c1df585 --- /dev/null +++ b/codes/csharp/.editorconfig @@ -0,0 +1,88 @@ +# CSharp formatting rules +[*.cs] +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent + +# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. +dotnet_diagnostic.CS8981.severity = silent + +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = silent + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = silent + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = silent + +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = silent diff --git a/codes/csharp/GlobalUsing.cs b/codes/csharp/GlobalUsing.cs new file mode 100644 index 0000000000..402066ff43 --- /dev/null +++ b/codes/csharp/GlobalUsing.cs @@ -0,0 +1,3 @@ +global using NUnit.Framework; +global using hello_algo.utils; +global using System.Text; \ No newline at end of file diff --git a/codes/csharp/chapter_array_and_linkedlist/array.cs b/codes/csharp/chapter_array_and_linkedlist/array.cs index 97a37f0466..ec8487f89b 100644 --- a/codes/csharp/chapter_array_and_linkedlist/array.cs +++ b/codes/csharp/chapter_array_and_linkedlist/array.cs @@ -2,122 +2,106 @@ // Created Time: 2022-12-14 // Author: mingXta (1195669834@qq.com) -using NUnit.Framework; - -namespace hello_algo.chapter_array_and_linkedlist -{ - public class Array - { - /* 随机返回一个数组元素 */ - public static int RandomAccess(int[] nums) - { - Random random = new(); - int randomIndex = random.Next(nums.Length); - int randomNum = nums[randomIndex]; - return randomNum; - } +namespace hello_algo.chapter_array_and_linkedlist; - /* 扩展数组长度 */ - public static int[] Extend(int[] nums, int enlarge) - { - // 初始化一个扩展长度后的数组 - int[] res = new int[nums.Length + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < nums.Length; i++) - { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } +public class array { + /* 随机访问元素 */ + int RandomAccess(int[] nums) { + Random random = new(); + // 在区间 [0, nums.Length) 中随机抽取一个数字 + int randomIndex = random.Next(nums.Length); + // 获取并返回随机元素 + int randomNum = nums[randomIndex]; + return randomNum; + } - /* 在数组的索引 index 处插入元素 num */ - public static void Insert(int[] nums, int num, int index) - { - // 把索引 index 以及之后的所有元素向后移动一位 - for (int i = nums.Length - 1; i > index; i--) - { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; + /* 扩展数组长度 */ + int[] Extend(int[] nums, int enlarge) { + // 初始化一个扩展长度后的数组 + int[] res = new int[nums.Length + enlarge]; + // 将原数组中的所有元素复制到新数组 + for (int i = 0; i < nums.Length; i++) { + res[i] = nums[i]; } + // 返回扩展后的新数组 + return res; + } - /* 删除索引 index 处元素 */ - public static void Remove(int[] nums, int index) - { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < nums.Length - 1; i++) - { - nums[i] = nums[i + 1]; - } + /* 在数组的索引 index 处插入元素 num */ + void Insert(int[] nums, int num, int index) { + // 把索引 index 以及之后的所有元素向后移动一位 + for (int i = nums.Length - 1; i > index; i--) { + nums[i] = nums[i - 1]; } + // 将 num 赋给 index 处的元素 + nums[index] = num; + } - /* 遍历数组 */ - public static void Traverse(int[] nums) - { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < nums.Length; i++) - { - count++; - } - // 直接遍历数组 - foreach (int num in nums) - { - count++; - } + /* 删除索引 index 处的元素 */ + void Remove(int[] nums, int index) { + // 把索引 index 之后的所有元素向前移动一位 + for (int i = index; i < nums.Length - 1; i++) { + nums[i] = nums[i + 1]; } + } - /* 在数组中查找指定元素 */ - public static int Find(int[] nums, int target) - { - for (int i = 0; i < nums.Length; i++) - { - if (nums[i] == target) - return i; - } - return -1; + /* 遍历数组 */ + void Traverse(int[] nums) { + int count = 0; + // 通过索引遍历数组 + for (int i = 0; i < nums.Length; i++) { + count += nums[i]; } - - /* 辅助函数,数组转字符串 */ - public static string ToString(int[] nums) - { - return string.Join(",", nums); + // 直接遍历数组元素 + foreach (int num in nums) { + count += num; } - - - [Test] - public static void Test() - { - // 初始化数组 - int[] arr = new int[5]; - Console.WriteLine("数组 arr = " + ToString(arr)); - int[] nums = { 1, 3, 2, 5, 4 }; - Console.WriteLine("数组 nums = " + ToString(nums)); - - // 随机访问 - int randomNum = RandomAccess(nums); - Console.WriteLine("在 nums 中获取随机元素 " + randomNum); - - // 长度扩展 - nums = Extend(nums, 3); - Console.WriteLine("将数组长度扩展至 8 ,得到 nums = " + ToString(nums)); - - // 插入元素 - Insert(nums, 6, 3); - Console.WriteLine("在索引 3 处插入数字 6 ,得到 nums = " + ToString(nums)); - - // 删除元素 - Remove(nums, 2); - Console.WriteLine("删除索引 2 处的元素,得到 nums = " + ToString(nums)); - - // 遍历数组 - Traverse(nums); - - // 查找元素 - int index = Find(nums, 3); - Console.WriteLine("在 nums 中查找元素 3 ,得到索引 = " + index); + } + + /* 在数组中查找指定元素 */ + int Find(int[] nums, int target) { + for (int i = 0; i < nums.Length; i++) { + if (nums[i] == target) + return i; } + return -1; + } + + /* 辅助函数,数组转字符串 */ + string ToString(int[] nums) { + return string.Join(",", nums); + } + + + [Test] + public void Test() { + // 初始化数组 + int[] arr = new int[5]; + Console.WriteLine("数组 arr = " + ToString(arr)); + int[] nums = [1, 3, 2, 5, 4]; + Console.WriteLine("数组 nums = " + ToString(nums)); + + // 随机访问 + int randomNum = RandomAccess(nums); + Console.WriteLine("在 nums 中获取随机元素 " + randomNum); + + // 长度扩展 + nums = Extend(nums, 3); + Console.WriteLine("将数组长度扩展至 8 ,得到 nums = " + ToString(nums)); + + // 插入元素 + Insert(nums, 6, 3); + Console.WriteLine("在索引 3 处插入数字 6 ,得到 nums = " + ToString(nums)); + + // 删除元素 + Remove(nums, 2); + Console.WriteLine("删除索引 2 处的元素,得到 nums = " + ToString(nums)); + + // 遍历数组 + Traverse(nums); + + // 查找元素 + int index = Find(nums, 3); + Console.WriteLine("在 nums 中查找元素 3 ,得到索引 = " + index); } } diff --git a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs index 81e1499cbf..65db5ed23c 100644 --- a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs +++ b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -2,91 +2,79 @@ // Created Time: 2022-12-16 // Author: mingXta (1195669834@qq.com) -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_array_and_linkedlist; -namespace hello_algo.chapter_array_and_linkedlist -{ - public class linked_list - { - /* 在链表的结点 n0 之后插入结点 P */ - public static void Insert(ListNode n0, ListNode P) - { - ListNode? n1 = n0.next; - n0.next = P; - P.next = n1; - } +public class linked_list { + /* 在链表的节点 n0 之后插入节点 P */ + void Insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } - /* 删除链表的结点 n0 之后的首个结点 */ - public static void Remove(ListNode n0) - { - if (n0.next == null) - return; - // n0 -> P -> n1 - ListNode P = n0.next; - ListNode? n1 = P.next; - n0.next = n1; - } + /* 删除链表的节点 n0 之后的首个节点 */ + void Remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode? n1 = P.next; + n0.next = n1; + } - /* 访问链表中索引为 index 的结点 */ - public static ListNode? Access(ListNode head, int index) - { - for (int i = 0; i < index; i++) - { - if (head == null) - return null; - head = head.next; - } - return head; + /* 访问链表中索引为 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 的首个结点 */ - public static int Find(ListNode head, int target) - { - int index = 0; - while (head != null) - { - if (head.val == target) - return index; - head = head.next; - index++; - } - return -1; + /* 在链表中查找值为 target 的首个节点 */ + int Find(ListNode? head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; } + return -1; + } - [Test] - public void Test() - { - // 初始化链表 - // 初始化各个结点 - ListNode n0 = new ListNode(1); - ListNode n1 = new ListNode(3); - ListNode n2 = new ListNode(2); - ListNode n3 = new ListNode(5); - ListNode n4 = new ListNode(4); - // 构建引用指向 - n0.next = n1; - n1.next = n2; - n2.next = n3; - n3.next = n4; - Console.WriteLine($"初始化的链表为{n0}"); + [Test] + public void Test() { + // 初始化链表 + // 初始化各个节点 + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // 构建节点之间的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + Console.WriteLine($"初始化的链表为{n0}"); - // 插入结点 - Insert(n0, new ListNode(0)); - Console.WriteLine($"插入结点后的链表为{n0}"); + // 插入节点 + Insert(n0, new ListNode(0)); + Console.WriteLine($"插入节点后的链表为{n0}"); - // 删除结点 - Remove(n0); - Console.WriteLine($"删除结点后的链表为{n0}"); + // 删除节点 + Remove(n0); + Console.WriteLine($"删除节点后的链表为{n0}"); - // 访问结点 - ListNode? node = Access(n0, 3); - Console.WriteLine($"链表中索引 3 处的结点的值 = {node?.val}"); + // 访问节点 + ListNode? node = Access(n0, 3); + Console.WriteLine($"链表中索引 3 处的节点的值 = {node?.val}"); - // 查找结点 - int index = Find(n0, 2); - Console.WriteLine($"链表中值为 2 的结点的索引 = {index}"); - } + // 查找节点 + int index = Find(n0, 2); + Console.WriteLine($"链表中值为 2 的节点的索引 = {index}"); } } diff --git a/codes/csharp/chapter_array_and_linkedlist/list.cs b/codes/csharp/chapter_array_and_linkedlist/list.cs index 4f702163fa..1c11ac17e9 100644 --- a/codes/csharp/chapter_array_and_linkedlist/list.cs +++ b/codes/csharp/chapter_array_and_linkedlist/list.cs @@ -4,72 +4,63 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_array_and_linkedlist; -namespace hello_algo.chapter_array_and_linkedlist -{ - public class list - { - [Test] - public void Test() - { +public class list { + [Test] + public void Test() { - /* 初始化列表 */ - // 注意数组的元素类型是 int[] 的包装类 int[] - int[] numbers = new int[] { 1, 3, 2, 5, 4 }; - List list = numbers.ToList(); - Console.WriteLine("列表 list = " + string.Join(",",list)); + /* 初始化列表 */ + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + Console.WriteLine("列表 nums = " + string.Join(",", nums)); - /* 访问元素 */ - int num = list[1]; - Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); + /* 访问元素 */ + int num = nums[1]; + Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); - /* 更新元素 */ - list[1] = 0; - Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 list = " + string.Join(",", list)); + /* 更新元素 */ + nums[1] = 0; + Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 nums = " + string.Join(",", nums)); - /* 清空列表 */ - list.Clear(); - Console.WriteLine("清空列表后 list = " + string.Join(",", list)); + /* 清空列表 */ + nums.Clear(); + Console.WriteLine("清空列表后 nums = " + string.Join(",", nums)); - /* 尾部添加元素 */ - list.Add(1); - list.Add(3); - list.Add(2); - list.Add(5); - list.Add(4); - Console.WriteLine("添加元素后 list = " + string.Join(",", list)); + /* 在尾部添加元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("添加元素后 nums = " + string.Join(",", nums)); - /* 中间插入元素 */ - list.Insert(3, 6); - Console.WriteLine("在索引 3 处插入数字 6 ,得到 list = " + string.Join(",", list)); + /* 在中间插入元素 */ + nums.Insert(3, 6); + Console.WriteLine("在索引 3 处插入数字 6 ,得到 nums = " + string.Join(",", nums)); - /* 删除元素 */ - list.RemoveAt(3); - Console.WriteLine("删除索引 3 处的元素,得到 list = " + string.Join(",", list)); + /* 删除元素 */ + nums.RemoveAt(3); + Console.WriteLine("删除索引 3 处的元素,得到 nums = " + string.Join(",", nums)); - /* 通过索引遍历列表 */ - int count = 0; - for (int i = 0; i < list.Count(); i++) - { - count++; - } - - /* 直接遍历列表元素 */ - count = 0; - foreach (int n in list) - { - count++; - } + /* 通过索引遍历列表 */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + /* 直接遍历列表元素 */ + count = 0; + foreach (int x in nums) { + count += x; + } - /* 拼接两个列表 */ - List list1 = new() { 6, 8, 7, 10, 9 }; - list.AddRange(list1); - Console.WriteLine("将列表 list1 拼接到 list 之后,得到 list = " + string.Join(",", list)); + /* 拼接两个列表 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); + Console.WriteLine("将列表 nums1 拼接到 nums 之后,得到 nums = " + string.Join(",", nums)); - /* 排序列表 */ - list.Sort(); // 排序后,列表元素从小到大排列 - Console.WriteLine("排序列表后 list = " + string.Join(",", list)); - } + /* 排序列表 */ + nums.Sort(); // 排序后,列表元素从小到大排列 + Console.WriteLine("排序列表后 nums = " + string.Join(",", nums)); } } diff --git a/codes/csharp/chapter_array_and_linkedlist/my_list.cs b/codes/csharp/chapter_array_and_linkedlist/my_list.cs index 327180625e..9d695584cf 100644 --- a/codes/csharp/chapter_array_and_linkedlist/my_list.cs +++ b/codes/csharp/chapter_array_and_linkedlist/my_list.cs @@ -4,161 +4,141 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; - -namespace hello_algo.chapter_array_and_linkedlist -{ - class MyList - { - private int[] nums; // 数组(存储列表元素) - private int capacity = 10; // 列表容量 - private int size = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - public MyList() - { - nums = new int[capacity]; - } +namespace hello_algo.chapter_array_and_linkedlist; + +/* 列表类 */ +class MyList { + private int[] arr; // 数组(存储列表元素) + private int arrCapacity = 10; // 列表容量 + private int arrSize = 0; // 列表长度(当前元素数量) + private readonly int extendRatio = 2; // 每次列表扩容的倍数 + + /* 构造方法 */ + public MyList() { + arr = new int[arrCapacity]; + } - /* 获取列表长度(即当前元素数量)*/ - public int Size() - { - return size; - } + /* 获取列表长度(当前元素数量)*/ + public int Size() { + return arrSize; + } - /* 获取列表容量 */ - public int Capacity() - { - return capacity; - } + /* 获取列表容量 */ + public int Capacity() { + return arrCapacity; + } - /* 访问元素 */ - public int Get(int index) - { - // 索引如果越界则抛出异常,下同 - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - return nums[index]; - } + /* 访问元素 */ + public int Get(int index) { + // 索引如果越界,则抛出异常,下同 + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + return arr[index]; + } - /* 更新元素 */ - public void Set(int index, int num) - { - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - nums[index] = num; - } + /* 更新元素 */ + public void Set(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + arr[index] = num; + } - /* 尾部添加元素 */ - public void Add(int num) - { - // 元素数量超出容量时,触发扩容机制 - if (size == Capacity()) - ExtendCapacity(); - nums[size] = num; - // 更新元素数量 - size++; - } + /* 在尾部添加元素 */ + public void Add(int num) { + // 元素数量超出容量时,触发扩容机制 + if (arrSize == arrCapacity) + ExtendCapacity(); + arr[arrSize] = num; + // 更新元素数量 + arrSize++; + } - /* 中间插入元素 */ - public void Insert(int index, int num) - { - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size == Capacity()) - ExtendCapacity(); - // 将索引 index 以及之后的元素都向后移动一位 - for (int j = size - 1; j >= index; j--) - { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - size++; + /* 在中间插入元素 */ + public void Insert(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + // 元素数量超出容量时,触发扩容机制 + if (arrSize == arrCapacity) + ExtendCapacity(); + // 将索引 index 以及之后的元素都向后移动一位 + for (int j = arrSize - 1; j >= index; j--) { + arr[j + 1] = arr[j]; } + arr[index] = num; + // 更新元素数量 + arrSize++; + } - /* 删除元素 */ - public int Remove(int index) - { - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - int num = nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (int j = index; j < size - 1; j++) - { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - size--; - // 返回被删除元素 - return num; + /* 删除元素 */ + public int Remove(int index) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + int num = arr[index]; + // 将将索引 index 之后的元素都向前移动一位 + for (int j = index; j < arrSize - 1; j++) { + arr[j] = arr[j + 1]; } + // 更新元素数量 + arrSize--; + // 返回被删除的元素 + return num; + } - /* 列表扩容 */ - public void ExtendCapacity() - { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - System.Array.Resize(ref nums, Capacity() * extendRatio); - // 更新列表容量 - capacity = nums.Length; - } + /* 列表扩容 */ + public void ExtendCapacity() { + // 新建一个长度为 arrCapacity * extendRatio 的数组,并将原数组复制到新数组 + Array.Resize(ref arr, arrCapacity * extendRatio); + // 更新列表容量 + arrCapacity = arr.Length; + } - /* 将列表转换为数组 */ - public int[] ToArray() - { - int size = Size(); - // 仅转换有效长度范围内的列表元素 - int[] nums = new int[size]; - for (int i = 0; i < size; i++) - { - nums[i] = Get(i); - } - return nums; + /* 将列表转换为数组 */ + public int[] ToArray() { + // 仅转换有效长度范围内的列表元素 + int[] arr = new int[arrSize]; + for (int i = 0; i < arrSize; i++) { + arr[i] = Get(i); } + return arr; } +} + +public class my_list { + [Test] + public void Test() { + /* 初始化列表 */ + MyList nums = new(); + /* 在尾部添加元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("列表 nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,长度 = " + nums.Size()); + + /* 在中间插入元素 */ + nums.Insert(3, 6); + Console.WriteLine("在索引 3 处插入数字 6 ,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 删除元素 */ + nums.Remove(3); + Console.WriteLine("删除索引 3 处的元素,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 访问元素 */ + int num = nums.Get(1); + Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); + + /* 更新元素 */ + nums.Set(1, 0); + Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 nums = " + string.Join(",", nums.ToArray())); - public class my_list - { - [Test] - public void Test() - { - /* 初始化列表 */ - MyList list = new MyList(); - /* 尾部添加元素 */ - list.Add(1); - list.Add(3); - list.Add(2); - list.Add(5); - list.Add(4); - Console.WriteLine("列表 list = " + string.Join(",", list.ToArray()) + - " ,容量 = " + list.Capacity() + " ,长度 = " + list.Size()); - - /* 中间插入元素 */ - list.Insert(3, 6); - Console.WriteLine("在索引 3 处插入数字 6 ,得到 list = " + string.Join(",", list.ToArray())); - - /* 删除元素 */ - list.Remove(3); - Console.WriteLine("删除索引 3 处的元素,得到 list = " + string.Join(",", list.ToArray())); - - /* 访问元素 */ - int num = list.Get(1); - Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); - - /* 更新元素 */ - list.Set(1, 0); - Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 list = " + string.Join(",", list.ToArray())); - - /* 测试扩容机制 */ - for (int i = 0; i < 10; i++) - { - // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list.Add(i); - } - Console.WriteLine("扩容后的列表 list = " + string.Join(",", list.ToArray()) + - " ,容量 = " + list.Capacity() + " ,长度 = " + list.Size()); + /* 测试扩容机制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + nums.Add(i); } + Console.WriteLine("扩容后的列表 nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,长度 = " + nums.Size()); } } diff --git a/codes/csharp/chapter_backtracking/n_queens.cs b/codes/csharp/chapter_backtracking/n_queens.cs new file mode 100644 index 0000000000..b64ebd4a40 --- /dev/null +++ b/codes/csharp/chapter_backtracking/n_queens.cs @@ -0,0 +1,76 @@ +/** + * File: n_queens.cs + * Created Time: 2023-05-04 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class n_queens { + /* 回溯算法:n 皇后 */ + void Backtrack(int row, int n, List> state, List>> res, + bool[] cols, bool[] diags1, bool[] diags2) { + // 当放置完所有行时,记录解 + if (row == n) { + List> copyState = []; + foreach (List sRow in state) { + copyState.Add(new List(sRow)); + } + res.Add(copyState); + return; + } + // 遍历所有列 + for (int col = 0; col < n; col++) { + // 计算该格子对应的主对角线和次对角线 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 尝试:将皇后放置在该格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + Backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* 求解 n 皇后 */ + List>> NQueens(int n) { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + List> state = []; + for (int i = 0; i < n; i++) { + List row = []; + for (int j = 0; j < n; j++) { + row.Add("#"); + } + state.Add(row); + } + bool[] cols = new bool[n]; // 记录列是否有皇后 + bool[] diags1 = new bool[2 * n - 1]; // 记录主对角线上是否有皇后 + bool[] diags2 = new bool[2 * n - 1]; // 记录次对角线上是否有皇后 + List>> res = []; + + Backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + [Test] + public void Test() { + int n = 4; + List>> res = NQueens(n); + + Console.WriteLine("输入棋盘长宽为 " + n); + Console.WriteLine("皇后放置方案共有 " + res.Count + " 种"); + foreach (List> state in res) { + Console.WriteLine("--------------------"); + foreach (List row in state) { + PrintUtil.PrintList(row); + } + } + } +} diff --git a/codes/csharp/chapter_backtracking/permutations_i.cs b/codes/csharp/chapter_backtracking/permutations_i.cs new file mode 100644 index 0000000000..8096973731 --- /dev/null +++ b/codes/csharp/chapter_backtracking/permutations_i.cs @@ -0,0 +1,53 @@ +/** + * File: permutations_i.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_i { + /* 回溯算法:全排列 I */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.Add(choice); + // 进行下一轮选择 + Backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 I */ + List> PermutationsI(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 3]; + + List> res = PermutationsI(nums); + + Console.WriteLine("输入数组 nums = " + string.Join(", ", nums)); + Console.WriteLine("所有排列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/codes/csharp/chapter_backtracking/permutations_ii.cs b/codes/csharp/chapter_backtracking/permutations_ii.cs new file mode 100644 index 0000000000..68bda7c203 --- /dev/null +++ b/codes/csharp/chapter_backtracking/permutations_ii.cs @@ -0,0 +1,55 @@ +/** + * File: permutations_ii.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_ii { + /* 回溯算法:全排列 II */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + HashSet duplicated = []; + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.Contains(choice)) { + // 尝试:做出选择,更新状态 + duplicated.Add(choice); // 记录选择过的元素值 + selected[i] = true; + state.Add(choice); + // 进行下一轮选择 + Backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 II */ + List> PermutationsII(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 2]; + + List> res = PermutationsII(nums); + + Console.WriteLine("输入数组 nums = " + string.Join(", ", nums)); + Console.WriteLine("所有排列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs b/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs new file mode 100644 index 0000000000..58e46e8042 --- /dev/null +++ b/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs @@ -0,0 +1,37 @@ +/** + * File: preorder_traversal_i_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_i_compact { + List res = []; + + /* 前序遍历:例题一 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 记录解 + res.Add(root); + } + PreOrder(root.left); + PreOrder(root.right); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二叉树"); + PrintUtil.PrintTree(root); + + // 前序遍历 + PreOrder(root); + + Console.WriteLine("\n输出所有值为 7 的节点"); + PrintUtil.PrintList(res.Select(p => p.val).ToList()); + } +} diff --git a/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs b/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs new file mode 100644 index 0000000000..1bd8e686c0 --- /dev/null +++ b/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_ii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_ii_compact { + List path = []; + List> res = []; + + /* 前序遍历:例题二 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + // 尝试 + path.Add(root); + if (root.val == 7) { + // 记录解 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二叉树"); + PrintUtil.PrintTree(root); + + // 前序遍历 + PreOrder(root); + + Console.WriteLine("\n输出所有根节点到节点 7 的路径"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs b/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs new file mode 100644 index 0000000000..ac60b792ca --- /dev/null +++ b/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs @@ -0,0 +1,45 @@ +/** + * File: preorder_traversal_iii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_compact { + List path = []; + List> res = []; + + /* 前序遍历:例题三 */ + void PreOrder(TreeNode? root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 尝试 + path.Add(root); + if (root.val == 7) { + // 记录解 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二叉树"); + PrintUtil.PrintTree(root); + + // 前序遍历 + PreOrder(root); + + Console.WriteLine("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs b/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs new file mode 100644 index 0000000000..be4b2b1ea0 --- /dev/null +++ b/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs @@ -0,0 +1,72 @@ +/** + * File: preorder_traversal_iii_template.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_template { + /* 判断当前状态是否为解 */ + bool IsSolution(List state) { + return state.Count != 0 && state[^1].val == 7; + } + + /* 记录解 */ + void RecordSolution(List state, List> res) { + res.Add(new List(state)); + } + + /* 判断在当前状态下,该选择是否合法 */ + bool IsValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 更新状态 */ + void MakeChoice(List state, TreeNode choice) { + state.Add(choice); + } + + /* 恢复状态 */ + void UndoChoice(List state, TreeNode choice) { + state.RemoveAt(state.Count - 1); + } + + /* 回溯算法:例题三 */ + void Backtrack(List state, List choices, List> res) { + // 检查是否为解 + if (IsSolution(state)) { + // 记录解 + RecordSolution(state, res); + } + // 遍历所有选择 + foreach (TreeNode choice in choices) { + // 剪枝:检查选择是否合法 + if (IsValid(state, choice)) { + // 尝试:做出选择,更新状态 + MakeChoice(state, choice); + // 进行下一轮选择 + Backtrack(state, [choice.left!, choice.right!], res); + // 回退:撤销选择,恢复到之前的状态 + UndoChoice(state, choice); + } + } + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二叉树"); + PrintUtil.PrintTree(root); + + // 回溯算法 + List> res = []; + List choices = [root!]; + Backtrack([], choices, res); + + Console.WriteLine("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/codes/csharp/chapter_backtracking/subset_sum_i.cs b/codes/csharp/chapter_backtracking/subset_sum_i.cs new file mode 100644 index 0000000000..fe81e32048 --- /dev/null +++ b/codes/csharp/chapter_backtracking/subset_sum_i.cs @@ -0,0 +1,55 @@ +/** +* File: subset_sum_i.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i { + /* 回溯算法:子集和 I */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.Add(choices[i]); + // 进行下一轮选择 + Backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I */ + List> SubsetSumI(int[] nums, int target) { + List state = []; // 状态(子集) + Array.Sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = []; // 结果列表(子集列表) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumI(nums, target); + Console.WriteLine("输入数组 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等于 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs b/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs new file mode 100644 index 0000000000..54c8a02e23 --- /dev/null +++ b/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs @@ -0,0 +1,53 @@ +/** +* File: subset_sum_i_naive.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i_naive { + /* 回溯算法:子集和 I */ + void Backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.Length; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.Add(choices[i]); + // 进行下一轮选择 + Backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I(包含重复子集) */ + List> SubsetSumINaive(int[] nums, int target) { + List state = []; // 状态(子集) + int total = 0; // 子集和 + List> res = []; // 结果列表(子集列表) + Backtrack(state, target, total, nums, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumINaive(nums, target); + Console.WriteLine("输入数组 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等于 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + Console.WriteLine("请注意,该方法输出的结果包含重复集合"); + } +} diff --git a/codes/csharp/chapter_backtracking/subset_sum_ii.cs b/codes/csharp/chapter_backtracking/subset_sum_ii.cs new file mode 100644 index 0000000000..a3daf0cd21 --- /dev/null +++ b/codes/csharp/chapter_backtracking/subset_sum_ii.cs @@ -0,0 +1,60 @@ +/** +* File: subset_sum_ii.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_ii { + /* 回溯算法:子集和 II */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.Add(choices[i]); + // 进行下一轮选择 + Backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 II */ + List> SubsetSumII(int[] nums, int target) { + List state = []; // 状态(子集) + Array.Sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = []; // 结果列表(子集列表) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [4, 4, 5]; + int target = 9; + List> res = SubsetSumII(nums, target); + Console.WriteLine("输入数组 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等于 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/codes/csharp/chapter_computational_complexity/iteration.cs b/codes/csharp/chapter_computational_complexity/iteration.cs new file mode 100644 index 0000000000..0505fb0606 --- /dev/null +++ b/codes/csharp/chapter_computational_complexity/iteration.cs @@ -0,0 +1,77 @@ +/** +* File: iteration.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class iteration { + /* 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 += 1; // 更新条件变量 + } + return res; + } + + /* while 循环(两次更新) */ + int WhileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i += 1; + i *= 2; + } + return res; + } + + /* 双层 for 循环 */ + string NestedForLoop(int n) { + StringBuilder res = new(); + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.Append($"({i}, {j}), "); + } + } + return res.ToString(); + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = ForLoop(n); + Console.WriteLine("\nfor 循环的求和结果 res = " + res); + + res = WhileLoop(n); + Console.WriteLine("\nwhile 循环的求和结果 res = " + res); + + res = WhileLoopII(n); + Console.WriteLine("\nwhile 循环(两次更新)求和结果 res = " + res); + + string resStr = NestedForLoop(n); + Console.WriteLine("\n双层 for 循环的遍历结果 " + resStr); + } +} diff --git a/codes/csharp/chapter_computational_complexity/leetcode_two_sum.cs b/codes/csharp/chapter_computational_complexity/leetcode_two_sum.cs deleted file mode 100644 index 6d45f99b34..0000000000 --- a/codes/csharp/chapter_computational_complexity/leetcode_two_sum.cs +++ /dev/null @@ -1,69 +0,0 @@ -/** - * File: leetcode_two_sum.cs - * Created Time: 2022-12-23 - * Author: haptear (haptear@hotmail.com) - */ - -using NUnit.Framework; - -namespace hello_algo.chapter_computational_complexity -{ - class SolutionBruteForce - { - public int[] twoSum(int[] nums, int target) - { - int size = nums.Length; - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) - { - for (int j = i + 1; j < size; j++) - { - if (nums[i] + nums[j] == target) - return new int[] { i, j }; - } - } - return new int[0]; - } - } - - class SolutionHashMap - { - public int[] twoSum(int[] nums, int target) - { - int size = nums.Length; - // 辅助哈希表,空间复杂度 O(n) - Dictionary dic = new(); - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) - { - if (dic.ContainsKey(target - nums[i])) - { - return new int[] { dic[target - nums[i]], i }; - } - dic.Add(nums[i], i); - } - return new int[0]; - } - } - - public class leetcode_two_sum - { - [Test] - public void Test() - { - // ======= Test Case ======= - int[] nums = { 2, 7, 11, 15 }; - int target = 9; - - // ====== Driver Code ====== - // 方法一 - SolutionBruteForce slt1 = new SolutionBruteForce(); - int[] res = slt1.twoSum(nums, target); - Console.WriteLine("方法一 res = " + string.Join(",", res)); - // 方法二 - SolutionHashMap slt2 = new SolutionHashMap(); - res = slt2.twoSum(nums, target); - Console.WriteLine("方法二 res = " + string.Join(",", res)); - } - } -} diff --git a/codes/csharp/chapter_computational_complexity/recursion.cs b/codes/csharp/chapter_computational_complexity/recursion.cs new file mode 100644 index 0000000000..c0638b9cc2 --- /dev/null +++ b/codes/csharp/chapter_computational_complexity/recursion.cs @@ -0,0 +1,78 @@ +/** +* File: recursion.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class recursion { + /* 递归 */ + int Recur(int n) { + // 终止条件 + if (n == 1) + return 1; + // 递:递归调用 + int res = Recur(n - 1); + // 归:返回结果 + return n + res; + } + + /* 使用迭代模拟递归 */ + int ForLoopRecur(int n) { + // 使用一个显式的栈来模拟系统调用栈 + Stack stack = new(); + int res = 0; + // 递:递归调用 + for (int i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack.Push(i); + } + // 归:返回结果 + while (stack.Count > 0) { + // 通过“出栈操作”模拟“归” + res += stack.Pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* 尾递归 */ + int TailRecur(int n, int res) { + // 终止条件 + if (n == 0) + return res; + // 尾递归调用 + return TailRecur(n - 1, res + n); + } + + /* 斐波那契数列:递归 */ + int Fib(int n) { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + int res = Fib(n - 1) + Fib(n - 2); + // 返回结果 f(n) + return res; + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = Recur(n); + Console.WriteLine("\n递归函数的求和结果 res = " + res); + + res = ForLoopRecur(n); + Console.WriteLine("\n使用迭代模拟递归求和结果 res = " + res); + + res = TailRecur(n, 0); + Console.WriteLine("\n尾递归函数的求和结果 res = " + res); + + res = Fib(n); + Console.WriteLine("\n斐波那契数列的第 " + n + " 项为 " + res); + } +} diff --git a/codes/csharp/chapter_computational_complexity/space_complexity.cs b/codes/csharp/chapter_computational_complexity/space_complexity.cs index c534c81b29..44645cc2d0 100644 --- a/codes/csharp/chapter_computational_complexity/space_complexity.cs +++ b/codes/csharp/chapter_computational_complexity/space_complexity.cs @@ -4,119 +4,101 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_computational_complexity; -namespace hello_algo.chapter_computational_complexity -{ - public class space_complexity - { - /* 函数 */ - static int function() - { - // do something - return 0; - } +public class space_complexity { + /* 函数 */ + int Function() { + // 执行某些操作 + return 0; + } - /* 常数阶 */ - static void constant(int n) - { - // 常量、变量、对象占用 O(1) 空间 - int a = 0; - int b = 0; - int[] nums = new int[10000]; - ListNode node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) - { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) - { - function(); - } + /* 常数阶 */ + void Constant(int n) { + // 常量、变量、对象占用 O(1) 空间 + int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new(0); + // 循环中的变量占用 O(1) 空间 + for (int i = 0; i < n; i++) { + int c = 0; } - - /* 线性阶 */ - static void linear(int n) - { - // 长度为 n 的数组占用 O(n) 空间 - int[] nums = new int[n]; - // 长度为 n 的列表占用 O(n) 空间 - List nodes = new(); - for (int i = 0; i < n; i++) - { - nodes.Add(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - Dictionary map = new(); - for (int i = 0; i < n; i++) - { - map.Add(i, i.ToString()); - } + // 循环中的函数占用 O(1) 空间 + for (int i = 0; i < n; i++) { + Function(); } + } - /* 线性阶(递归实现) */ - static void linearRecur(int n) - { - Console.WriteLine("递归 n = " + n); - if (n == 1) return; - linearRecur(n - 1); + /* 线性阶 */ + void Linear(int n) { + // 长度为 n 的数组占用 O(n) 空间 + int[] nums = new int[n]; + // 长度为 n 的列表占用 O(n) 空间 + List nodes = []; + for (int i = 0; i < n; i++) { + nodes.Add(new ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + Dictionary map = []; + for (int i = 0; i < n; i++) { + map.Add(i, i.ToString()); } + } + + /* 线性阶(递归实现) */ + void LinearRecur(int n) { + Console.WriteLine("递归 n = " + n); + if (n == 1) return; + LinearRecur(n - 1); + } - /* 平方阶 */ - static void quadratic(int n) - { - // 矩阵占用 O(n^2) 空间 - int[,] numMatrix = new int[n, n]; - // 二维列表占用 O(n^2) 空间 - List> numList = new(); - for (int i = 0; i < n; i++) - { - List tmp = new(); - for (int j = 0; j < n; j++) - { - tmp.Add(0); - } - numList.Add(tmp); + /* 平方阶 */ + void Quadratic(int n) { + // 矩阵占用 O(n^2) 空间 + int[,] numMatrix = new int[n, n]; + // 二维列表占用 O(n^2) 空间 + List> numList = []; + for (int i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.Add(0); } + numList.Add(tmp); } + } - /* 平方阶(递归实现) */ - static int quadraticRecur(int n) - { - if (n <= 0) return 0; - int[] nums = new int[n]; - Console.WriteLine("递归 n = " + n + " 中的 nums 长度 = " + nums.Length); - return quadraticRecur(n - 1); - } + /* 平方阶(递归实现) */ + int QuadraticRecur(int n) { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("递归 n = " + n + " 中的 nums 长度 = " + nums.Length); + return QuadraticRecur(n - 1); + } - /* 指数阶(建立满二叉树) */ - static TreeNode? buildTree(int n) - { - if (n == 0) return null; - TreeNode root = new TreeNode(0); - root.left = buildTree(n - 1); - root.right = buildTree(n - 1); - return root; - } + /* 指数阶(建立满二叉树) */ + TreeNode? BuildTree(int n) { + if (n == 0) return null; + TreeNode root = new(0) { + left = BuildTree(n - 1), + right = BuildTree(n - 1) + }; + return root; + } - [Test] - public void Test() - { - int n = 5; - // 常数阶 - constant(n); - // 线性阶 - linear(n); - linearRecur(n); - // 平方阶 - quadratic(n); - quadraticRecur(n); - // 指数阶 - TreeNode? root = buildTree(n); - PrintUtil.PrintTree(root); - } + [Test] + public void Test() { + int n = 5; + // 常数阶 + Constant(n); + // 线性阶 + Linear(n); + LinearRecur(n); + // 平方阶 + Quadratic(n); + QuadraticRecur(n); + // 指数阶 + TreeNode? root = BuildTree(n); + PrintUtil.PrintTree(root); } } diff --git a/codes/csharp/chapter_computational_complexity/time_complexity.cs b/codes/csharp/chapter_computational_complexity/time_complexity.cs index ac7b405a9d..e39c5abf0c 100644 --- a/codes/csharp/chapter_computational_complexity/time_complexity.cs +++ b/codes/csharp/chapter_computational_complexity/time_complexity.cs @@ -4,229 +4,192 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; - -namespace hello_algo.chapter_computational_complexity -{ - public class time_complexity - { - void algorithm(int n) - { - int a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) - // +n(技巧 2) - for (int i = 0; i < 5 * n + 1; i++) - { - Console.WriteLine(0); - } - // +n*n(技巧 3) - for (int i = 0; i < 2 * n; i++) - { - for (int j = 0; j < n + 1; j++) - { - Console.WriteLine(0); - } - } - } - - // 算法 A 时间复杂度:常数阶 - void algorithm_A(int n) - { +namespace hello_algo.chapter_computational_complexity; + +public class time_complexity { + void Algorithm(int n) { + int a = 1; // +0(技巧 1) + a += n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } - // 算法 B 时间复杂度:线性阶 - void algorithm_B(int n) - { - for (int i = 0; i < n; i++) - { - Console.WriteLine(0); - } - } - // 算法 C 时间复杂度:常数阶 - void algorithm_C(int n) - { - for (int i = 0; i < 1000000; i++) - { + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); } } + } - /* 常数阶 */ - static int constant(int n) - { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } + // 算法 A 时间复杂度:常数阶 + void AlgorithmA(int n) { + Console.WriteLine(0); + } - /* 线性阶 */ - static int linear(int n) - { - int count = 0; - for (int i = 0; i < n; i++) - count++; - return count; + // 算法 B 时间复杂度:线性阶 + void AlgorithmB(int n) { + for (int i = 0; i < n; i++) { + Console.WriteLine(0); } + } - /* 线性阶(遍历数组) */ - static int arrayTraversal(int[] nums) - { - int count = 0; - // 循环次数与数组长度成正比 - foreach (int num in nums) - { - count++; - } - return count; + // 算法 C 时间复杂度:常数阶 + void AlgorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + Console.WriteLine(0); } + } - /* 平方阶 */ - static int quadratic(int n) - { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) - { - for (int j = 0; j < n; j++) - { - count++; - } - } - return count; + /* 常数阶 */ + 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 count = 0; + // 循环次数与数组长度成正比 + foreach (int num in nums) { + count++; } + return count; + } - /* 平方阶(冒泡排序) */ - static int bubbleSort(int[] nums) - { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; 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 个单元操作 - } - } + /* 平方阶 */ + 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; } + return count; + } - /* 指数阶(循环实现) */ - static int exponential(int n) - { - int count = 0, bas = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) - { - for (int j = 0; j < bas; j++) - { - count++; + /* 平方阶(冒泡排序) */ + int BubbleSort(int[] nums) { + int count = 0; // 计数器 + // 外循环:未排序区间为 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + count += 3; // 元素交换包含 3 个单元操作 } - bas *= 2; } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - - /* 指数阶(递归实现) */ - static int expRecur(int n) - { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; } + return count; + } - /* 对数阶(循环实现) */ - static int logarithmic(float n) - { - int count = 0; - while (n > 1) - { - n = n / 2; + /* 指数阶(循环实现) */ + int Exponential(int n) { + int count = 0, bas = 1; + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < bas; j++) { count++; } - return count; + bas *= 2; } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } - /* 对数阶(递归实现) */ - static int logRecur(float n) - { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } + /* 指数阶(递归实现) */ + int ExpRecur(int n) { + if (n == 1) return 1; + return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; + } - /* 线性对数阶 */ - static int linearLogRecur(float 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 Logarithmic(int n) { + int count = 0; + while (n > 1) { + n /= 2; + count++; } + return count; + } - /* 阶乘阶(递归实现) */ - static int factorialRecur(int n) - { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) - { - count += factorialRecur(n - 1); - } - 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; + } - [Test] - public void Test() - { - // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 - int n = 8; - Console.WriteLine("输入数据大小 n = " + n); - - int count = constant(n); - Console.WriteLine("常数阶的计算操作数量 = " + count); - - count = linear(n); - Console.WriteLine("线性阶的计算操作数量 = " + count); - count = arrayTraversal(new int[n]); - Console.WriteLine("线性阶(遍历数组)的计算操作数量 = " + count); - - count = quadratic(n); - Console.WriteLine("平方阶的计算操作数量 = " + count); - int[] nums = new int[n]; - for (int i = 0; i < n; i++) - nums[i] = n - i; // [n,n-1,...,2,1] - count = bubbleSort(nums); - Console.WriteLine("平方阶(冒泡排序)的计算操作数量 = " + count); - - count = exponential(n); - Console.WriteLine("指数阶(循环实现)的计算操作数量 = " + count); - count = expRecur(n); - Console.WriteLine("指数阶(递归实现)的计算操作数量 = " + count); - - count = logarithmic((float)n); - Console.WriteLine("对数阶(循环实现)的计算操作数量 = " + count); - count = logRecur((float)n); - Console.WriteLine("对数阶(递归实现)的计算操作数量 = " + count); - - count = linearLogRecur((float)n); - Console.WriteLine("线性对数阶(递归实现)的计算操作数量 = " + count); - - count = factorialRecur(n); - Console.WriteLine("阶乘阶(递归实现)的计算操作数量 = " + count); + /* 阶乘阶(递归实现) */ + int FactorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // 从 1 个分裂出 n 个 + for (int i = 0; i < n; i++) { + count += FactorialRecur(n - 1); } + return count; + } + + [Test] + public void Test() { + // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + int n = 8; + Console.WriteLine("输入数据大小 n = " + n); + + int count = Constant(n); + Console.WriteLine("常数阶的操作数量 = " + count); + + count = Linear(n); + Console.WriteLine("线性阶的操作数量 = " + count); + count = ArrayTraversal(new int[n]); + Console.WriteLine("线性阶(遍历数组)的操作数量 = " + count); + + count = Quadratic(n); + Console.WriteLine("平方阶的操作数量 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = BubbleSort(nums); + Console.WriteLine("平方阶(冒泡排序)的操作数量 = " + count); + + count = Exponential(n); + Console.WriteLine("指数阶(循环实现)的操作数量 = " + count); + count = ExpRecur(n); + Console.WriteLine("指数阶(递归实现)的操作数量 = " + count); + + count = Logarithmic(n); + Console.WriteLine("对数阶(循环实现)的操作数量 = " + count); + count = LogRecur(n); + Console.WriteLine("对数阶(递归实现)的操作数量 = " + count); + + count = LinearLogRecur(n); + Console.WriteLine("线性对数阶(递归实现)的操作数量 = " + count); + + count = FactorialRecur(n); + Console.WriteLine("阶乘阶(递归实现)的操作数量 = " + count); } } diff --git a/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs b/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs index 9759cd584a..c6770718b8 100644 --- a/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs +++ b/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs @@ -4,58 +4,46 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_computational_complexity; -namespace hello_algo.chapter_computational_complexity -{ - public class worst_best_time_complexity - { - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - static int[] randomNumbers(int n) - { - int[] nums = new int[n]; - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) - { - nums[i] = i + 1; - } +public class worst_best_time_complexity { + /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ + int[] RandomNumbers(int n) { + int[] nums = new int[n]; + // 生成数组 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } - // 随机打乱数组元素 - for (int i = 0; i < nums.Length; i++) - { - var index = new Random().Next(i, nums.Length); - var tmp = nums[i]; - var ran = nums[index]; - nums[i] = ran; - nums[index] = tmp; - } - return nums; + // 随机打乱数组元素 + for (int i = 0; i < nums.Length; i++) { + int index = new Random().Next(i, nums.Length); + (nums[i], nums[index]) = (nums[index], nums[i]); } + return nums; + } - /* 查找数组 nums 中数字 1 所在索引 */ - static int findOne(int[] nums) - { - for (int i = 0; i < nums.Length; i++) - { - if (nums[i] == 1) - return i; - } - return -1; + /* 查找数组 nums 中数字 1 所在索引 */ + int FindOne(int[] nums) { + for (int i = 0; i < nums.Length; i++) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if (nums[i] == 1) + return i; } + return -1; + } - /* Driver Code */ - [Test] - public void Test() - { - for (int i = 0; i < 10; i++) - { - int n = 100; - int[] nums = randomNumbers(n); - int index = findOne(nums); - Console.WriteLine("\n数组 [ 1, 2, ..., n ] 被打乱后 = " + string.Join(",", nums)); - Console.WriteLine("数字 1 的索引为 " + index); - } + /* Driver Code */ + [Test] + public void Test() { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = RandomNumbers(n); + int index = FindOne(nums); + Console.WriteLine("\n数组 [ 1, 2, ..., n ] 被打乱后 = " + string.Join(",", nums)); + Console.WriteLine("数字 1 的索引为 " + index); } } } diff --git a/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs b/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs new file mode 100644 index 0000000000..5e604aa38e --- /dev/null +++ b/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs @@ -0,0 +1,46 @@ +/** +* File: binary_search_recur.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class binary_search_recur { + /* 二分查找:问题 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 n = nums.Length; + // 求解问题 f(0, n-1) + return DFS(nums, target, 0, n - 1); + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分查找(双闭区间) + int index = BinarySearch(nums, target); + Console.WriteLine("目标元素 6 的索引 = " + index); + } +} diff --git a/codes/csharp/chapter_divide_and_conquer/build_tree.cs b/codes/csharp/chapter_divide_and_conquer/build_tree.cs new file mode 100644 index 0000000000..c85c90f47c --- /dev/null +++ b/codes/csharp/chapter_divide_and_conquer/build_tree.cs @@ -0,0 +1,49 @@ +/** +* File: build_tree.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class build_tree { + /* 构建二叉树:分治 */ + TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return null; + // 初始化根节点 + TreeNode root = new(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = inorderMap[preorder[i]]; + // 子问题:构建左子树 + root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; + } + + /* 构建二叉树 */ + TreeNode? BuildTree(int[] preorder, int[] inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + Dictionary inorderMap = []; + for (int i = 0; i < inorder.Length; i++) { + inorderMap.TryAdd(inorder[i], i); + } + TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); + return root; + } + + [Test] + public void Test() { + int[] preorder = [3, 9, 2, 1, 7]; + int[] inorder = [9, 3, 1, 2, 7]; + Console.WriteLine("前序遍历 = " + string.Join(", ", preorder)); + Console.WriteLine("中序遍历 = " + string.Join(", ", inorder)); + + TreeNode? root = BuildTree(preorder, inorder); + Console.WriteLine("构建的二叉树为:"); + PrintUtil.PrintTree(root); + } +} diff --git a/codes/csharp/chapter_divide_and_conquer/hanota.cs b/codes/csharp/chapter_divide_and_conquer/hanota.cs new file mode 100644 index 0000000000..230d6c2be5 --- /dev/null +++ b/codes/csharp/chapter_divide_and_conquer/hanota.cs @@ -0,0 +1,59 @@ +/** +* File: hanota.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class hanota { + /* 移动一个圆盘 */ + void Move(List src, List tar) { + // 从 src 顶部拿出一个圆盘 + int pan = src[^1]; + src.RemoveAt(src.Count - 1); + // 将圆盘放入 tar 顶部 + tar.Add(pan); + } + + /* 求解汉诺塔问题 f(i) */ + void DFS(int i, List src, List buf, List tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + Move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + DFS(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + Move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + DFS(i - 1, buf, src, tar); + } + + /* 求解汉诺塔问题 */ + void SolveHanota(List A, List B, List C) { + int n = A.Count; + // 将 A 顶部 n 个圆盘借助 B 移到 C + DFS(n, A, B, C); + } + + [Test] + public void Test() { + // 列表尾部是柱子顶部 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + Console.WriteLine("初始状态下:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + + SolveHanota(A, B, C); + + Console.WriteLine("圆盘移动完成后:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs b/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs new file mode 100644 index 0000000000..41e4f14a23 --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs @@ -0,0 +1,41 @@ +/** +* File: climbing_stairs_backtrack.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_backtrack { + /* 回溯 */ + void Backtrack(List choices, int state, int n, List res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res[0]++; + // 遍历所有选择 + foreach (int choice in choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) + continue; + // 尝试:做出选择,更新状态 + Backtrack(choices, state + choice, n, res); + // 回退 + } + } + + /* 爬楼梯:回溯 */ + int ClimbingStairsBacktrack(int n) { + List choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 + int state = 0; // 从第 0 阶开始爬 + List res = [0]; // 使用 res[0] 记录方案数量 + Backtrack(choices, state, n, res); + return res[0]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsBacktrack(n); + Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs b/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs new file mode 100644 index 0000000000..a972e442ee --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs @@ -0,0 +1,36 @@ +/** +* File: climbing_stairs_constraint_dp.cs +* Created Time: 2023-07-03 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 带约束爬楼梯:动态规划 */ + int ClimbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + int[,] dp = new int[n + 1, 3]; + // 初始状态:预设最小子问题的解 + dp[1, 1] = 1; + dp[1, 2] = 0; + dp[2, 1] = 0; + dp[2, 2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i, 1] = dp[i - 1, 2]; + dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; + } + return dp[n, 1] + dp[n, 2]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsConstraintDP(n); + Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs b/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs new file mode 100644 index 0000000000..868532ac2a --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs @@ -0,0 +1,31 @@ +/** +* File: climbing_stairs_dfs.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 搜索 */ + 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); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFS(n); + Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs b/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs new file mode 100644 index 0000000000..8bb40a675e --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs @@ -0,0 +1,39 @@ +/** +* File: climbing_stairs_dfs_mem.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs_mem { + /* 记忆化搜索 */ + 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 = new int[n + 1]; + Array.Fill(mem, -1); + return DFS(n, mem); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFSMem(n); + Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs b/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs new file mode 100644 index 0000000000..8bcb2e6376 --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs @@ -0,0 +1,49 @@ +/** +* File: climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 爬楼梯:动态规划 */ + int ClimbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* 爬楼梯:空间优化后的动态规划 */ + int ClimbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int n = 9; + + int res = ClimbingStairsDP(n); + Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); + + res = ClimbingStairsDPComp(n); + Console.WriteLine($"爬 {n} 阶楼梯共有 {res} 种方案"); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/coin_change.cs b/codes/csharp/chapter_dynamic_programming/coin_change.cs new file mode 100644 index 0000000000..7810f39ca8 --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/coin_change.cs @@ -0,0 +1,71 @@ +/** +* File: coin_change.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change { + /* 零钱兑换:动态规划 */ + int CoinChangeDP(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 状态转移:首行首列 + 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] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); + } + } + } + return dp[n, amt] != MAX ? dp[n, amt] : -1; + } + + /* 零钱兑换:空间优化后的动态规划 */ + int CoinChangeDPComp(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Array.Fill(dp, 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] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 4; + + // 动态规划 + int res = CoinChangeDP(coins, amt); + Console.WriteLine("凑到目标金额所需的最少硬币数量为 " + res); + + // 空间优化后的动态规划 + res = CoinChangeDPComp(coins, amt); + Console.WriteLine("凑到目标金额所需的最少硬币数量为 " + res); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs b/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs new file mode 100644 index 0000000000..0b53b2015c --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs @@ -0,0 +1,68 @@ +/** +* File: coin_change_ii.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change_ii { + /* 零钱兑换 II:动态规划 */ + int CoinChangeIIDP(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i, 0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i, a] = dp[i - 1, a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; + } + } + } + return dp[n, amt]; + } + + /* 零钱兑换 II:空间优化后的动态规划 */ + int CoinChangeIIDPComp(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 5; + + // 动态规划 + int res = CoinChangeIIDP(coins, amt); + Console.WriteLine("凑出目标金额的硬币组合数量为 " + res); + + // 空间优化后的动态规划 + res = CoinChangeIIDPComp(coins, amt); + Console.WriteLine("凑出目标金额的硬币组合数量为 " + res); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/edit_distance.cs b/codes/csharp/chapter_dynamic_programming/edit_distance.cs new file mode 100644 index 0000000000..243811a23e --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/edit_distance.cs @@ -0,0 +1,141 @@ +/** +* File: edit_distance.cs +* Created Time: 2023-07-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class edit_distance { + /* 编辑距离:暴力搜索 */ + int EditDistanceDFS(string s, string t, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) + return EditDistanceDFS(s, t, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = EditDistanceDFS(s, t, i, j - 1); + int delete = EditDistanceDFS(s, t, i - 1, j); + int replace = EditDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return Math.Min(Math.Min(insert, delete), replace) + 1; + } + + /* 编辑距离:记忆化搜索 */ + int EditDistanceDFSMem(string s, string t, 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, mem, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); + int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); + int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* 编辑距离:动态规划 */ + int EditDistanceDP(string s, string t) { + int n = s.Length, m = t.Length; + int[,] dp = new int[n + 1, m + 1]; + // 状态转移:首行首列 + 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] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; + } + } + } + return dp[n, m]; + } + + /* 编辑距离:空间优化后的动态规划 */ + int EditDistanceDPComp(string s, string t) { + int n = s.Length, m = t.Length; + int[] dp = new int[m + 1]; + // 状态转移:首行 + 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] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; + } + + [Test] + public void Test() { + string s = "bag"; + string t = "pack"; + int n = s.Length, m = t.Length; + + // 暴力搜索 + int res = EditDistanceDFS(s, t, n, m); + Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + + // 记忆化搜索 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[m + 1]; + Array.Fill(mem[i], -1); + } + + res = EditDistanceDFSMem(s, t, mem, n, m); + Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + + // 动态规划 + res = EditDistanceDP(s, t); + Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + + // 空间优化后的动态规划 + res = EditDistanceDPComp(s, t); + Console.WriteLine("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/knapsack.cs b/codes/csharp/chapter_dynamic_programming/knapsack.cs new file mode 100644 index 0000000000..fabb81e49f --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/knapsack.cs @@ -0,0 +1,118 @@ +/** +* File: knapsack.cs +* Created Time: 2023-07-07 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class knapsack { + /* 0-1 背包:暴力搜索 */ + int KnapsackDFS(int[] weight, int[] val, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (weight[i - 1] > c) { + return KnapsackDFS(weight, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = KnapsackDFS(weight, val, i - 1, c); + int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return Math.Max(no, yes); + } + + /* 0-1 背包:记忆化搜索 */ + int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (weight[i - 1] > c) { + return KnapsackDFSMem(weight, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = KnapsackDFSMem(weight, val, mem, i - 1, c); + int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.Max(no, yes); + return mem[i][c]; + } + + /* 0-1 背包:动态规划 */ + int KnapsackDP(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (weight[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i, c] = dp[i - 1, c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); + } + } + } + return dp[n, cap]; + } + + /* 0-1 背包:空间优化后的动态规划 */ + int KnapsackDPComp(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c > 0; c--) { + if (weight[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] weight = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = weight.Length; + + // 暴力搜索 + int res = KnapsackDFS(weight, val, n, cap); + Console.WriteLine("不超过背包容量的最大物品价值为 " + res); + + // 记忆化搜索 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[cap + 1]; + Array.Fill(mem[i], -1); + } + res = KnapsackDFSMem(weight, val, mem, n, cap); + Console.WriteLine("不超过背包容量的最大物品价值为 " + res); + + // 动态规划 + res = KnapsackDP(weight, val, cap); + Console.WriteLine("不超过背包容量的最大物品价值为 " + res); + + // 空间优化后的动态规划 + res = KnapsackDPComp(weight, val, cap); + Console.WriteLine("不超过背包容量的最大物品价值为 " + res); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs b/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs new file mode 100644 index 0000000000..f54d9a8afc --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs @@ -0,0 +1,53 @@ +/** +* File: min_cost_climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_cost_climbing_stairs_dp { + /* 爬楼梯最小代价:动态规划 */ + int MinCostClimbingStairsDP(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* 爬楼梯最小代价:空间优化后的动态规划 */ + int MinCostClimbingStairsDPComp(int[] cost) { + int n = cost.Length - 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 = Math.Min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + Console.WriteLine("输入楼梯的代价列表为"); + PrintUtil.PrintList(cost); + + int res = MinCostClimbingStairsDP(cost); + Console.WriteLine($"爬完楼梯的最低代价为 {res}"); + + res = MinCostClimbingStairsDPComp(cost); + Console.WriteLine($"爬完楼梯的最低代价为 {res}"); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/min_path_sum.cs b/codes/csharp/chapter_dynamic_programming/min_path_sum.cs new file mode 100644 index 0000000000..a02f655f47 --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/min_path_sum.cs @@ -0,0 +1,127 @@ +/** +* File: min_path_sum.cs +* Created Time: 2023-07-10 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_path_sum { + /* 最小路径和:暴力搜索 */ + int MinPathSumDFS(int[][] grid, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int up = MinPathSumDFS(grid, i - 1, j); + int left = MinPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.Min(left, up) + grid[i][j]; + } + + /* 最小路径和:记忆化搜索 */ + int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 若已有记录,则直接返回 + 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] = Math.Min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* 最小路径和:动态规划 */ + int MinPathSumDP(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[,] dp = new int[n, m]; + dp[0, 0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0, j] = dp[0, j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i, 0] = dp[i - 1, 0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; + } + } + return dp[n - 1, m - 1]; + } + + /* 最小路径和:空间优化后的动态规划 */ + int MinPathSumDPComp(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[] dp = new int[m]; + dp[0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + [Test] + public void Test() { + int[][] grid = + [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2] + ]; + + int n = grid.Length, m = grid[0].Length; + + // 暴力搜索 + int res = MinPathSumDFS(grid, n - 1, m - 1); + Console.WriteLine("从左上角到右下角的最小路径和为 " + res); + + // 记忆化搜索 + int[][] mem = new int[n][]; + for (int i = 0; i < n; i++) { + mem[i] = new int[m]; + Array.Fill(mem[i], -1); + } + res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); + Console.WriteLine("从左上角到右下角的最小路径和为 " + res); + + // 动态规划 + res = MinPathSumDP(grid); + Console.WriteLine("从左上角到右下角的最小路径和为 " + res); + + // 空间优化后的动态规划 + res = MinPathSumDPComp(grid); + Console.WriteLine("从左上角到右下角的最小路径和为 " + res); + } +} diff --git a/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs b/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs new file mode 100644 index 0000000000..25131cc599 --- /dev/null +++ b/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs @@ -0,0 +1,64 @@ +/** +* File: unbounded_knapsack.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全背包:动态规划 */ + int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 状态转移 + 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] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n, cap]; + } + + /* 完全背包:空间优化后的动态规划 */ + int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + 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] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] wgt = [1, 2, 3]; + int[] val = [5, 11, 15]; + int cap = 4; + + // 动态规划 + int res = UnboundedKnapsackDP(wgt, val, cap); + Console.WriteLine("不超过背包容量的最大物品价值为 " + res); + + // 空间优化后的动态规划 + res = UnboundedKnapsackDPComp(wgt, val, cap); + Console.WriteLine("不超过背包容量的最大物品价值为 " + res); + } +} diff --git a/codes/csharp/chapter_graph/graph_adjacency_list.cs b/codes/csharp/chapter_graph/graph_adjacency_list.cs new file mode 100644 index 0000000000..89e0b16525 --- /dev/null +++ b/codes/csharp/chapter_graph/graph_adjacency_list.cs @@ -0,0 +1,122 @@ +/** + * File: graph_adjacency_list.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 基于邻接表实现的无向图类 */ +public class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + public Dictionary> adjList; + + /* 构造函数 */ + public GraphAdjList(Vertex[][] edges) { + adjList = []; + // 添加所有顶点和边 + foreach (Vertex[] edge in edges) { + AddVertex(edge[0]); + AddVertex(edge[1]); + AddEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int Size() { + return adjList.Count; + } + + /* 添加边 */ + public void AddEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 添加边 vet1 - vet2 + adjList[vet1].Add(vet2); + adjList[vet2].Add(vet1); + } + + /* 删除边 */ + public void RemoveEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 删除边 vet1 - vet2 + adjList[vet1].Remove(vet2); + adjList[vet2].Remove(vet1); + } + + /* 添加顶点 */ + public void AddVertex(Vertex vet) { + if (adjList.ContainsKey(vet)) + return; + // 在邻接表中添加一个新链表 + adjList.Add(vet, []); + } + + /* 删除顶点 */ + public void RemoveVertex(Vertex vet) { + if (!adjList.ContainsKey(vet)) + throw new InvalidOperationException(); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.Remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + foreach (List list in adjList.Values) { + list.Remove(vet); + } + } + + /* 打印邻接表 */ + public void Print() { + Console.WriteLine("邻接表 ="); + foreach (KeyValuePair> pair in adjList) { + List tmp = []; + foreach (Vertex vertex in pair.Value) + tmp.Add(vertex.val); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); + } + } +} + +public class graph_adjacency_list { + [Test] + public void Test() { + /* 初始化无向图 */ + Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); + Vertex[][] edges = + [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]] + ]; + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化后,图为"); + graph.Print(); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.AddEdge(v[0], v[2]); + Console.WriteLine("\n添加边 1-2 后,图为"); + graph.Print(); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.RemoveEdge(v[0], v[1]); + Console.WriteLine("\n删除边 1-3 后,图为"); + graph.Print(); + + /* 添加顶点 */ + Vertex v5 = new(6); + graph.AddVertex(v5); + Console.WriteLine("\n添加顶点 6 后,图为"); + graph.Print(); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.RemoveVertex(v[1]); + Console.WriteLine("\n删除顶点 3 后,图为"); + graph.Print(); + } +} diff --git a/codes/csharp/chapter_graph/graph_adjacency_matrix.cs b/codes/csharp/chapter_graph/graph_adjacency_matrix.cs new file mode 100644 index 0000000000..871cd9c054 --- /dev/null +++ b/codes/csharp/chapter_graph/graph_adjacency_matrix.cs @@ -0,0 +1,137 @@ +/** + * File: graph_adjacency_matrix.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + foreach (int val in vertices) { + AddVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + foreach (int[] e in edges) { + AddEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + int Size() { + return vertices.Count; + } + + /* 添加顶点 */ + public void AddVertex(int val) { + int n = Size(); + // 向顶点列表中添加新顶点的值 + vertices.Add(val); + // 在邻接矩阵中添加一行 + List newRow = new(n); + for (int j = 0; j < n; j++) { + newRow.Add(0); + } + adjMat.Add(newRow); + // 在邻接矩阵中添加一列 + foreach (List row in adjMat) { + row.Add(0); + } + } + + /* 删除顶点 */ + public void RemoveVertex(int index) { + if (index >= Size()) + throw new IndexOutOfRangeException(); + // 在顶点列表中移除索引 index 的顶点 + vertices.RemoveAt(index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.RemoveAt(index); + // 在邻接矩阵中删除索引 index 的列 + foreach (List row in adjMat) { + row.RemoveAt(index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + public void AddEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + public void RemoveEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + public void Print() { + Console.Write("顶点列表 = "); + PrintUtil.PrintList(vertices); + Console.WriteLine("邻接矩阵 ="); + PrintUtil.PrintMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + [Test] + public void Test() { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + int[] vertices = [1, 3, 2, 5, 4]; + int[][] edges = + [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4] + ]; + GraphAdjMat graph = new(vertices, edges); + Console.WriteLine("\n初始化后,图为"); + graph.Print(); + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.AddEdge(0, 2); + Console.WriteLine("\n添加边 1-2 后,图为"); + graph.Print(); + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.RemoveEdge(0, 1); + Console.WriteLine("\n删除边 1-3 后,图为"); + graph.Print(); + + /* 添加顶点 */ + graph.AddVertex(6); + Console.WriteLine("\n添加顶点 6 后,图为"); + graph.Print(); + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.RemoveVertex(1); + Console.WriteLine("\n删除顶点 3 后,图为"); + graph.Print(); + } +} diff --git a/codes/csharp/chapter_graph/graph_bfs.cs b/codes/csharp/chapter_graph/graph_bfs.cs new file mode 100644 index 0000000000..8fb84700d4 --- /dev/null +++ b/codes/csharp/chapter_graph/graph_bfs.cs @@ -0,0 +1,58 @@ +/** + * File: graph_bfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_bfs { + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List GraphBFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = []; + // 哈希集合,用于记录已被访问过的顶点 + HashSet visited = [startVet]; + // 队列用于实现 BFS + Queue que = new(); + que.Enqueue(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.Count > 0) { + Vertex vet = que.Dequeue(); // 队首顶点出队 + res.Add(vet); // 记录访问顶点 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.Enqueue(adjVet); // 只入队未访问的顶点 + visited.Add(adjVet); // 标记该顶点已被访问 + } + } + + // 返回顶点遍历序列 + return res; + } + + [Test] + public void Test() { + /* 初始化无向图 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + Vertex[][] edges = + [ + [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]] + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化后,图为"); + graph.Print(); + + /* 广度优先遍历 */ + List res = GraphBFS(graph, v[0]); + Console.WriteLine("\n广度优先遍历(BFS)顶点序列为"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/codes/csharp/chapter_graph/graph_dfs.cs b/codes/csharp/chapter_graph/graph_dfs.cs new file mode 100644 index 0000000000..ea4ce5ddce --- /dev/null +++ b/codes/csharp/chapter_graph/graph_dfs.cs @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_dfs { + /* 深度优先遍历辅助函数 */ + void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { + res.Add(vet); // 记录访问顶点 + visited.Add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + DFS(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List GraphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = []; + // 哈希集合,用于记录已被访问过的顶点 + HashSet visited = []; + DFS(graph, visited, res, startVet); + return res; + } + + [Test] + public void Test() { + /* 初始化无向图 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); + Vertex[][] edges = + [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化后,图为"); + graph.Print(); + + /* 深度优先遍历 */ + List res = GraphDFS(graph, v[0]); + Console.WriteLine("\n深度优先遍历(DFS)顶点序列为"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/codes/csharp/chapter_greedy/coin_change_greedy.cs b/codes/csharp/chapter_greedy/coin_change_greedy.cs new file mode 100644 index 0000000000..693cbeb28c --- /dev/null +++ b/codes/csharp/chapter_greedy/coin_change_greedy.cs @@ -0,0 +1,54 @@ +/** +* File: coin_change_greedy.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class coin_change_greedy { + /* 零钱兑换:贪心 */ + int CoinChangeGreedy(int[] coins, int amt) { + // 假设 coins 列表有序 + int i = coins.Length - 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; + } + + [Test] + public void Test() { + // 贪心:能够保证找到全局最优解 + int[] coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("凑到 " + amt + " 所需的最少硬币数量为 " + res); + + // 贪心:无法保证找到全局最优解 + coins = [1, 20, 50]; + amt = 60; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("凑到 " + amt + " 所需的最少硬币数量为 " + res); + Console.WriteLine("实际上需要的最少数量为 3 ,即 20 + 20 + 20"); + + // 贪心:无法保证找到全局最优解 + coins = [1, 49, 50]; + amt = 98; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("凑到 " + amt + " 所需的最少硬币数量为 " + res); + Console.WriteLine("实际上需要的最少数量为 2 ,即 49 + 49"); + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_greedy/fractional_knapsack.cs b/codes/csharp/chapter_greedy/fractional_knapsack.cs new file mode 100644 index 0000000000..e2f477b286 --- /dev/null +++ b/codes/csharp/chapter_greedy/fractional_knapsack.cs @@ -0,0 +1,52 @@ +/** +* File: fractional_knapsack.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +/* 物品 */ +class Item(int w, int v) { + public int w = w; // 物品重量 + public int v = v; // 物品价值 +} + +public class fractional_knapsack { + /* 分数背包:贪心 */ + double FractionalKnapsack(int[] wgt, int[] val, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + Item[] items = new Item[wgt.Length]; + for (int i = 0; i < wgt.Length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); + // 循环贪心选择 + double res = 0; + foreach (Item item in items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (double)item.v / item.w * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; + } + + [Test] + public void Test() { + int[] wgt = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 贪心算法 + double res = FractionalKnapsack(wgt, val, cap); + Console.WriteLine("不超过背包容量的最大物品价值为 " + res); + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_greedy/max_capacity.cs b/codes/csharp/chapter_greedy/max_capacity.cs new file mode 100644 index 0000000000..10d4e1c303 --- /dev/null +++ b/codes/csharp/chapter_greedy/max_capacity.cs @@ -0,0 +1,39 @@ +/** +* File: max_capacity.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_capacity { + /* 最大容量:贪心 */ + int MaxCapacity(int[] ht) { + // 初始化 i, j,使其分列数组两端 + int i = 0, j = ht.Length - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.Min(ht[i], ht[j]) * (j - i); + res = Math.Max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + [Test] + public void Test() { + int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 贪心算法 + int res = MaxCapacity(ht); + Console.WriteLine("最大容量为 " + res); + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_greedy/max_product_cutting.cs b/codes/csharp/chapter_greedy/max_product_cutting.cs new file mode 100644 index 0000000000..f57d9b498a --- /dev/null +++ b/codes/csharp/chapter_greedy/max_product_cutting.cs @@ -0,0 +1,39 @@ +/** +* File: max_product_cutting.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_product_cutting { + /* 最大切分乘积:贪心 */ + 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 (int)Math.Pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return (int)Math.Pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return (int)Math.Pow(3, a); + } + + [Test] + public void Test() { + int n = 58; + + // 贪心算法 + int res = MaxProductCutting(n); + Console.WriteLine("最大切分乘积为" + res); + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_hashing/array_hash_map.cs b/codes/csharp/chapter_hashing/array_hash_map.cs index 3320fa8562..7b76237282 100644 --- a/codes/csharp/chapter_hashing/array_hash_map.cs +++ b/codes/csharp/chapter_hashing/array_hash_map.cs @@ -4,161 +4,131 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; - -namespace hello_algo.chapter_hashing -{ - - /* 键值对 int->String */ - class Entry - { - public int key; - public String val; - public Entry(int key, String val) - { - this.key = key; - this.val = val; +namespace hello_algo.chapter_hashing; + +/* 键值对 int->string */ +class Pair(int key, string val) { + public int key = key; + public string val = val; +} + +/* 基于数组实现的哈希表 */ +class ArrayHashMap { + List buckets; + public ArrayHashMap() { + // 初始化数组,包含 100 个桶 + buckets = []; + for (int i = 0; i < 100; i++) { + buckets.Add(null); } } - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap - { - private List bucket; - public ArrayHashMap() - { - // 初始化一个长度为 100 的桶(数组) - bucket = new (); - for (int i = 0; i < 100; i++) - { - bucket.Add(null); - } - } - - /* 哈希函数 */ - private int hashFunc(int key) - { - int index = key % 100; - return index; - } + /* 哈希函数 */ + int HashFunc(int key) { + int index = key % 100; + return index; + } - /* 查询操作 */ - public String? get(int key) - { - int index = hashFunc(key); - Entry? pair = bucket[index]; - if (pair == null) return null; - return pair.val; - } + /* 查询操作 */ + public string? Get(int key) { + int index = HashFunc(key); + Pair? pair = buckets[index]; + if (pair == null) return null; + return pair.val; + } - /* 添加操作 */ - public void put(int key, String val) - { - Entry pair = new Entry(key, val); - int index = hashFunc(key); - bucket[index]=pair; - } + /* 添加操作 */ + public void Put(int key, string val) { + Pair pair = new(key, val); + int index = HashFunc(key); + buckets[index] = pair; + } - /* 删除操作 */ - public void remove(int key) - { - int index = hashFunc(key); - // 置为 null ,代表删除 - bucket[index]=null; - } + /* 删除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // 置为 null ,代表删除 + buckets[index] = null; + } - /* 获取所有键值对 */ - public List entrySet() - { - List entrySet = new (); - foreach (Entry? pair in bucket) - { - if (pair != null) - entrySet.Add(pair); - } - return entrySet; + /* 获取所有键值对 */ + public List PairSet() { + List pairSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + pairSet.Add(pair); } + return pairSet; + } - /* 获取所有键 */ - public List keySet() - { - List keySet = new (); - foreach (Entry? pair in bucket) - { - if (pair != null) - keySet.Add(pair.key); - } - return keySet; + /* 获取所有键 */ + public List KeySet() { + List keySet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + keySet.Add(pair.key); } + return keySet; + } - /* 获取所有值 */ - public List valueSet() - { - List valueSet = new (); - foreach (Entry? pair in bucket) - { - if (pair != null) - valueSet.Add(pair.val); - } - return valueSet; + /* 获取所有值 */ + public List ValueSet() { + List valueSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + valueSet.Add(pair.val); } + return valueSet; + } - /* 打印哈希表 */ - public void print() - { - foreach (Entry kv in entrySet()) - { - Console.WriteLine(kv.key + " -> " + kv.val); - } + /* 打印哈希表 */ + public void Print() { + foreach (Pair kv in PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); } } +} + + +public class array_hash_map { + [Test] + public void Test() { + /* 初始化哈希表 */ + ArrayHashMap map = new(); + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小啰"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鸭"); + Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); + map.Print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string? name = map.Get(15937); + Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); - public class array_hash_map - { - [Test] - public void Test() - { - /* 初始化哈希表 */ - ArrayHashMap map = new ArrayHashMap(); - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); - map.put(13276, "小法"); - map.put(10583, "小鸭"); - Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); - map.print(); - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - String? name = map.get(15937); - Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.remove(10583); - Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); - map.print(); - - /* 遍历哈希表 */ - Console.WriteLine("\n遍历键值对 Key->Value"); - foreach (Entry kv in map.entrySet()) - { - Console.WriteLine(kv.key + " -> " + kv.val); - } - Console.WriteLine("\n单独遍历键 Key"); - foreach (int key in map.keySet()) - { - Console.WriteLine(key); - } - Console.WriteLine("\n单独遍历值 Value"); - foreach (String val in map.valueSet()) - { - Console.WriteLine(val); - } + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.Remove(10583); + Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); + map.Print(); + + /* 遍历哈希表 */ + Console.WriteLine("\n遍历键值对 Key->Value"); + foreach (Pair kv in map.PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + Console.WriteLine("\n单独遍历键 Key"); + foreach (int key in map.KeySet()) { + Console.WriteLine(key); + } + Console.WriteLine("\n单独遍历值 Value"); + foreach (string val in map.ValueSet()) { + Console.WriteLine(val); } } -} \ No newline at end of file +} diff --git a/codes/csharp/chapter_hashing/built_in_hash.cs b/codes/csharp/chapter_hashing/built_in_hash.cs new file mode 100644 index 0000000000..2f7c0e7513 --- /dev/null +++ b/codes/csharp/chapter_hashing/built_in_hash.cs @@ -0,0 +1,36 @@ +/** +* File: built_in_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class built_in_hash { + [Test] + public void Test() { + int num = 3; + int hashNum = num.GetHashCode(); + Console.WriteLine("整数 " + num + " 的哈希值为 " + hashNum); + + bool bol = true; + int hashBol = bol.GetHashCode(); + Console.WriteLine("布尔量 " + bol + " 的哈希值为 " + hashBol); + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + Console.WriteLine("小数 " + dec + " 的哈希值为 " + hashDec); + + string str = "Hello 算法"; + int hashStr = str.GetHashCode(); + Console.WriteLine("字符串 " + str + " 的哈希值为 " + hashStr); + + object[] arr = [12836, "小哈"]; + int hashTup = arr.GetHashCode(); + Console.WriteLine("数组 [" + string.Join(", ", arr) + "] 的哈希值为 " + hashTup); + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + Console.WriteLine("节点对象 " + obj + " 的哈希值为 " + hashObj); + } +} diff --git a/codes/csharp/chapter_hashing/hash_map.cs b/codes/csharp/chapter_hashing/hash_map.cs index 6afcc82c88..93701d0872 100644 --- a/codes/csharp/chapter_hashing/hash_map.cs +++ b/codes/csharp/chapter_hashing/hash_map.cs @@ -5,53 +5,47 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; - -namespace hello_algo.chapter_hashing -{ - - public class hash_map { - [Test] - public void Test() - { - /* 初始化哈希表 */ - Dictionary map = new (); +namespace hello_algo.chapter_hashing; +public class hash_map { + [Test] + public void Test() { + /* 初始化哈希表 */ + Dictionary map = new() { /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - map.Add(12836, "小哈"); - map.Add(15937, "小啰"); - map.Add(16750, "小算"); - map.Add(13276, "小法"); - map.Add(10583, "小鸭"); - Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); - PrintUtil.printHashMap(map); - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - String name = map[15937]; - Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.Remove(10583); - Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); - PrintUtil.printHashMap(map); - - /* 遍历哈希表 */ - Console.WriteLine("\n遍历键值对 Key->Value"); - foreach (var kv in map) { - Console.WriteLine(kv.Key + " -> " + kv.Value); - } - Console.WriteLine("\n单独遍历键 Key"); - foreach (int key in map.Keys) { - Console.WriteLine(key); - } - Console.WriteLine("\n单独遍历值 Value"); - foreach (String val in map.Values) { - Console.WriteLine(val); - } + { 12836, "小哈" }, + { 15937, "小啰" }, + { 16750, "小算" }, + { 13276, "小法" }, + { 10583, "小鸭" } + }; + Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string name = map[15937]; + Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.Remove(10583); + Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* 遍历哈希表 */ + Console.WriteLine("\n遍历键值对 Key->Value"); + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + Console.WriteLine("\n单独遍历键 Key"); + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + Console.WriteLine("\n单独遍历值 Value"); + foreach (string val in map.Values) { + Console.WriteLine(val); } } } diff --git a/codes/csharp/chapter_hashing/hash_map_chaining.cs b/codes/csharp/chapter_hashing/hash_map_chaining.cs new file mode 100644 index 0000000000..3a7e252320 --- /dev/null +++ b/codes/csharp/chapter_hashing/hash_map_chaining.cs @@ -0,0 +1,144 @@ +/** +* File: hash_map_chaining.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* 链式地址哈希表 */ +class HashMapChaining { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + List> buckets; // 桶数组 + + /* 构造方法 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + } + + /* 哈希函数 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* 查询操作 */ + public string? Get(int key) { + int index = HashFunc(key); + // 遍历桶,若找到 key ,则返回对应 val + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,则返回 null + return null; + } + + /* 添加操作 */ + public void Put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (LoadFactor() > loadThres) { + Extend(); + } + int index = HashFunc(key); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + buckets[index].Add(new Pair(key, val)); + size++; + } + + /* 删除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // 遍历桶,从中删除键值对 + foreach (Pair pair in buckets[index].ToList()) { + if (pair.key == key) { + buckets[index].Remove(pair); + size--; + break; + } + } + } + + /* 扩容哈希表 */ + void Extend() { + // 暂存原哈希表 + List> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + foreach (List bucket in bucketsTmp) { + foreach (Pair pair in bucket) { + Put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + public void Print() { + foreach (List bucket in buckets) { + List res = []; + foreach (Pair pair in bucket) { + res.Add(pair.key + " -> " + pair.val); + } + foreach (string kv in res) { + Console.WriteLine(kv); + } + } + } +} + +public class hash_map_chaining { + [Test] + public void Test() { + /* 初始化哈希表 */ + HashMapChaining map = new(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小啰"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鸭"); + Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); + map.Print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string? name = map.Get(13276); + Console.WriteLine("\n输入学号 13276 ,查询到姓名 " + name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.Remove(12836); + Console.WriteLine("\n删除 12836 后,哈希表为\nKey -> Value"); + map.Print(); + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_hashing/hash_map_open_addressing.cs b/codes/csharp/chapter_hashing/hash_map_open_addressing.cs new file mode 100644 index 0000000000..ad6f2ee494 --- /dev/null +++ b/codes/csharp/chapter_hashing/hash_map_open_addressing.cs @@ -0,0 +1,159 @@ +/** +* File: hash_map_open_addressing.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + int size; // 键值对数量 + int capacity = 4; // 哈希表容量 + double loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + int extendRatio = 2; // 扩容倍数 + Pair[] buckets; // 桶数组 + Pair TOMBSTONE = new(-1, "-1"); // 删除标记 + + /* 构造方法 */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* 哈希函数 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* 搜索 key 对应的桶索引 */ + int FindBucket(int key) { + int index = HashFunc(key); + int firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回对应的桶索引 + if (buckets[index].key == key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查询操作 */ + public string? Get(int key) { + // 搜索 key 对应的桶索引 + int index = FindBucket(key); + // 若找到键值对,则返回对应 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // 若键值对不存在,则返回 null + return null; + } + + /* 添加操作 */ + public void Put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (LoadFactor() > loadThres) { + Extend(); + } + // 搜索 key 对应的桶索引 + int index = FindBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // 若键值对不存在,则添加该键值对 + buckets[index] = new Pair(key, val); + size++; + } + + /* 删除操作 */ + public void Remove(int key) { + // 搜索 key 对应的桶索引 + int index = FindBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 扩容哈希表 */ + void Extend() { + // 暂存原哈希表 + Pair[] bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + foreach (Pair pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + Put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + public void Print() { + foreach (Pair pair in buckets) { + if (pair == null) { + Console.WriteLine("null"); + } else if (pair == TOMBSTONE) { + Console.WriteLine("TOMBSTONE"); + } else { + Console.WriteLine(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + [Test] + public void Test() { + /* 初始化哈希表 */ + HashMapOpenAddressing map = new(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小啰"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鸭"); + Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); + map.Print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string? name = map.Get(13276); + Console.WriteLine("\n输入学号 13276 ,查询到姓名 " + name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.Remove(16750); + Console.WriteLine("\n删除 16750 后,哈希表为\nKey -> Value"); + map.Print(); + } +} diff --git a/codes/csharp/chapter_hashing/simple_hash.cs b/codes/csharp/chapter_hashing/simple_hash.cs new file mode 100644 index 0000000000..52ad0681a9 --- /dev/null +++ b/codes/csharp/chapter_hashing/simple_hash.cs @@ -0,0 +1,66 @@ +/** +* File: simple_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class simple_hash { + /* 加法哈希 */ + int AddHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (hash + c) % MODULUS; + } + return (int)hash; + } + + /* 乘法哈希 */ + int MulHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (31 * hash + c) % MODULUS; + } + return (int)hash; + } + + /* 异或哈希 */ + int XorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash ^= c; + } + return hash & MODULUS; + } + + /* 旋转哈希 */ + int RotHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; + } + return (int)hash; + } + + [Test] + public void Test() { + string key = "Hello 算法"; + + int hash = AddHash(key); + Console.WriteLine("加法哈希值为 " + hash); + + hash = MulHash(key); + Console.WriteLine("乘法哈希值为 " + hash); + + hash = XorHash(key); + Console.WriteLine("异或哈希值为 " + hash); + + hash = RotHash(key); + Console.WriteLine("旋转哈希值为 " + hash); + } +} diff --git a/codes/csharp/chapter_heap/heap.cs b/codes/csharp/chapter_heap/heap.cs new file mode 100644 index 0000000000..f50ceba3a8 --- /dev/null +++ b/codes/csharp/chapter_heap/heap.cs @@ -0,0 +1,64 @@ +/** + * File: heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +public class heap { + void TestPush(PriorityQueue heap, int val) { + heap.Enqueue(val, val); // 元素入堆 + Console.WriteLine($"\n元素 {val} 入堆后\n"); + PrintUtil.PrintHeap(heap); + } + + void TestPop(PriorityQueue heap) { + int val = heap.Dequeue(); // 堆顶元素出堆 + Console.WriteLine($"\n堆顶元素 {val} 出堆后\n"); + PrintUtil.PrintHeap(heap); + } + + [Test] + public void Test() { + /* 初始化堆 */ + // 初始化小顶堆 + PriorityQueue minHeap = new(); + // 初始化大顶堆(使用 lambda 表达式修改 Comparer 即可) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); + Console.WriteLine("以下测试样例为大顶堆"); + + /* 元素入堆 */ + TestPush(maxHeap, 1); + TestPush(maxHeap, 3); + TestPush(maxHeap, 2); + TestPush(maxHeap, 5); + TestPush(maxHeap, 4); + + /* 获取堆顶元素 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"堆顶元素为 {peek}"); + + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + + /* 获取堆大小 */ + int size = maxHeap.Count; + Console.WriteLine($"堆元素数量为 {size}"); + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.Count == 0; + Console.WriteLine($"堆是否为空 {isEmpty}"); + + /* 输入列表并建堆 */ + var list = new int[] { 1, 3, 2, 5, 4 }; + minHeap = new PriorityQueue(list.Select(x => (x, x))); + Console.WriteLine("输入列表并建立小顶堆后"); + PrintUtil.PrintHeap(minHeap); + } +} diff --git a/codes/csharp/chapter_heap/my_heap.cs b/codes/csharp/chapter_heap/my_heap.cs new file mode 100644 index 0000000000..f947210141 --- /dev/null +++ b/codes/csharp/chapter_heap/my_heap.cs @@ -0,0 +1,160 @@ +/** + * File: my_heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +/* 大顶堆 */ +class MaxHeap { + // 使用列表而非数组,这样无须考虑扩容问题 + List maxHeap; + + /* 构造函数,建立空堆 */ + public MaxHeap() { + maxHeap = []; + } + + /* 构造函数,根据输入列表建堆 */ + public MaxHeap(IEnumerable nums) { + // 将列表元素原封不动添加进堆 + maxHeap = new List(nums); + // 堆化除叶节点以外的其他所有节点 + var size = Parent(this.Size() - 1); + for (int i = size; i >= 0; i--) { + SiftDown(i); + } + } + + /* 获取左子节点的索引 */ + int Left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int Right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int Parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 访问堆顶元素 */ + public int Peek() { + return maxHeap[0]; + } + + /* 元素入堆 */ + public void Push(int val) { + // 添加节点 + maxHeap.Add(val); + // 从底至顶堆化 + SiftUp(Size() - 1); + } + + /* 获取堆大小 */ + public int Size() { + return maxHeap.Count; + } + + /* 判断堆是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 从节点 i 开始,从底至顶堆化 */ + void SiftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = Parent(i); + // 若“越过根节点”或“节点无须修复”,则结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交换两节点 + Swap(i, p); + // 循环向上堆化 + i = p; + } + } + + /* 元素出堆 */ + public int Pop() { + // 判空处理 + if (IsEmpty()) + throw new IndexOutOfRangeException(); + // 交换根节点与最右叶节点(交换首元素与尾元素) + Swap(0, Size() - 1); + // 删除节点 + int val = maxHeap.Last(); + maxHeap.RemoveAt(Size() - 1); + // 从顶至底堆化 + SiftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void SiftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = Left(i), r = Right(i), ma = i; + if (l < Size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < Size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若“节点 i 最大”或“越过叶节点”,则结束堆化 + if (ma == i) break; + // 交换两节点 + Swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + + /* 交换元素 */ + void Swap(int i, int p) { + (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); + } + + /* 打印堆(二叉树) */ + public void Print() { + var queue = new Queue(maxHeap); + PrintUtil.PrintHeap(queue); + } +} + +public class my_heap { + [Test] + public void Test() { + /* 初始化大顶堆 */ + MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + Console.WriteLine("\n输入列表并建堆后"); + maxHeap.Print(); + + /* 获取堆顶元素 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"堆顶元素为 {peek}"); + + /* 元素入堆 */ + int val = 7; + maxHeap.Push(val); + Console.WriteLine($"元素 {val} 入堆后"); + maxHeap.Print(); + + /* 堆顶元素出堆 */ + peek = maxHeap.Pop(); + Console.WriteLine($"堆顶元素 {peek} 出堆后"); + maxHeap.Print(); + + /* 获取堆大小 */ + int size = maxHeap.Size(); + Console.WriteLine($"堆元素数量为 {size}"); + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.IsEmpty(); + Console.WriteLine($"堆是否为空 {isEmpty}"); + } +} diff --git a/codes/csharp/chapter_heap/top_k.cs b/codes/csharp/chapter_heap/top_k.cs new file mode 100644 index 0000000000..72ecfe6f28 --- /dev/null +++ b/codes/csharp/chapter_heap/top_k.cs @@ -0,0 +1,37 @@ +/** +* File: top_k.cs +* Created Time: 2023-06-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_heap; + +public class top_k { + /* 基于堆查找数组中最大的 k 个元素 */ + PriorityQueue TopKHeap(int[] nums, int k) { + // 初始化小顶堆 + PriorityQueue heap = new(); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.Enqueue(nums[i], nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.Length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.Peek()) { + heap.Dequeue(); + heap.Enqueue(nums[i], nums[i]); + } + } + return heap; + } + + [Test] + public void Test() { + int[] nums = [1, 7, 6, 3, 2]; + int k = 3; + PriorityQueue res = TopKHeap(nums, k); + Console.WriteLine("最大的 " + k + " 个元素为"); + PrintUtil.PrintHeap(res); + } +} diff --git a/codes/csharp/chapter_searching/binary_search.cs b/codes/csharp/chapter_searching/binary_search.cs index 1bf8a5b2fa..f4cf8852ce 100644 --- a/codes/csharp/chapter_searching/binary_search.cs +++ b/codes/csharp/chapter_searching/binary_search.cs @@ -4,65 +4,56 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_searching; -namespace hello_algo.chapter_searching -{ - public class binary_search - { - /* 二分查找(双闭区间) */ - static int binarySearch(int[] nums, int target) - { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.Length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) - { - int m = (i + j) / 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; +public class binary_search { + /* 二分查找(双闭区间) */ + int BinarySearch(int[] nums, int target) { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + int i = 0, j = nums.Length - 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; + } - /* 二分查找(左闭右开) */ - static int binarySearch1(int[] nums, int target) - { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.Length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) - { - int m = (i + j) / 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 BinarySearchLCRO(int[] nums, int target) { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = nums.Length; + // 循环,当搜索区间为空时跳出(当 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; + } - [Test] - public void Test() - { - int target = 6; - int[] nums = { 1, 3, 6, 8, 12, 15, 23, 67, 70, 92 }; + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; - /* 二分查找(双闭区间) */ - int index = binarySearch(nums, target); - Console.WriteLine("目标元素 6 的索引 = " + index); + /* 二分查找(双闭区间) */ + int index = BinarySearch(nums, target); + Console.WriteLine("目标元素 6 的索引 = " + index); - /* 二分查找(左闭右开) */ - index = binarySearch1(nums, target); - Console.WriteLine("目标元素 6 的索引 = " + index); - } + /* 二分查找(左闭右开区间) */ + index = BinarySearchLCRO(nums, target); + Console.WriteLine("目标元素 6 的索引 = " + index); } } diff --git a/codes/csharp/chapter_searching/binary_search_edge.cs b/codes/csharp/chapter_searching/binary_search_edge.cs new file mode 100644 index 0000000000..609daa3b1a --- /dev/null +++ b/codes/csharp/chapter_searching/binary_search_edge.cs @@ -0,0 +1,50 @@ +/** +* File: binary_search_edge.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_edge { + /* 二分查找最左一个 target */ + int BinarySearchLeftEdge(int[] nums, int target) { + // 等价于查找 target 的插入点 + int i = binary_search_insertion.BinarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.Length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } + + /* 二分查找最右一个 target */ + int BinarySearchRightEdge(int[] nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } + + [Test] + public void Test() { + // 包含重复元素的数组 + int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n数组 nums = " + nums.PrintList()); + + // 二分查找左边界和右边界 + foreach (int target in new int[] { 6, 7 }) { + int index = BinarySearchLeftEdge(nums, target); + Console.WriteLine("最左一个元素 " + target + " 的索引为 " + index); + index = BinarySearchRightEdge(nums, target); + Console.WriteLine("最右一个元素 " + target + " 的索引为 " + index); + } + } +} diff --git a/codes/csharp/chapter_searching/binary_search_insertion.cs b/codes/csharp/chapter_searching/binary_search_insertion.cs new file mode 100644 index 0000000000..af4c1d21f8 --- /dev/null +++ b/codes/csharp/chapter_searching/binary_search_insertion.cs @@ -0,0 +1,64 @@ +/** +* File: binary_search_insertion.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_insertion { + /* 二分查找插入点(无重复元素) */ + public static int BinarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.Length - 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; + } + + /* 二分查找插入点(存在重复元素) */ + public static int BinarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.Length - 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; + } + + [Test] + public void Test() { + // 无重复元素的数组 + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + Console.WriteLine("\n数组 nums = " + nums.PrintList()); + // 二分查找插入点 + foreach (int target in new int[] { 6, 9 }) { + int index = BinarySearchInsertionSimple(nums, target); + Console.WriteLine("元素 " + target + " 的插入点的索引为 " + index); + } + + // 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n数组 nums = " + nums.PrintList()); + // 二分查找插入点 + foreach (int target in new int[] { 2, 6, 20 }) { + int index = BinarySearchInsertion(nums, target); + Console.WriteLine("元素 " + target + " 的插入点的索引为 " + index); + } + } +} diff --git a/codes/csharp/chapter_searching/hashing_search.cs b/codes/csharp/chapter_searching/hashing_search.cs index 33d5fe9dc8..6431f6964b 100644 --- a/codes/csharp/chapter_searching/hashing_search.cs +++ b/codes/csharp/chapter_searching/hashing_search.cs @@ -4,57 +4,47 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_searching; -namespace hello_algo.chapter_searching -{ - public class hashing_search - { - /* 哈希查找(数组) */ - static int hashingSearch(Dictionary map, int target) - { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.GetValueOrDefault(target, -1); - } +public class hashing_search { + /* 哈希查找(数组) */ + int HashingSearchArray(Dictionary map, int target) { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 -1 + return map.GetValueOrDefault(target, -1); + } - /* 哈希查找(链表) */ - static ListNode? hashingSearch1(Dictionary map, int target) - { + /* 哈希查找(链表) */ + ListNode? HashingSearchLinkedList(Dictionary map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.GetValueOrDefault(target); - } + // 哈希表的 key: 目标节点值,value: 节点对象 + // 若哈希表中无此 key ,返回 null + return map.GetValueOrDefault(target); + } - [Test] - public void Test() - { - int target = 3; + [Test] + public void Test() { + int target = 3; - /* 哈希查找(数组) */ - int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; - // 初始化哈希表 - Dictionary map = new(); - for (int i = 0; i < nums.Length; i++) - { - map[nums[i]] = i; // key: 元素,value: 索引 - } - int index = hashingSearch(map, target); - Console.WriteLine("目标元素 3 的索引 = " + index); + /* 哈希查找(数组) */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化哈希表 + Dictionary map = []; + for (int i = 0; i < nums.Length; i++) { + map[nums[i]] = i; // key: 元素,value: 索引 + } + int index = HashingSearchArray(map, target); + Console.WriteLine("目标元素 3 的索引 = " + index); - /* 哈希查找(链表) */ - ListNode? head = ListNode.ArrToLinkedList(nums); - // 初始化哈希表 - Dictionary map1 = new(); - while (head != null) - { - map1[head.val] = head; // key: 结点值,value: 结点 - head = head.next; - } - ListNode? node = hashingSearch1(map1, target); - Console.WriteLine("目标结点值 3 的对应结点对象为 " + node); + /* 哈希查找(链表) */ + ListNode? head = ListNode.ArrToLinkedList(nums); + // 初始化哈希表 + Dictionary map1 = []; + while (head != null) { + map1[head.val] = head; // key: 节点值,value: 节点 + head = head.next; } + ListNode? node = HashingSearchLinkedList(map1, target); + Console.WriteLine("目标节点值 3 的对应节点对象为 " + node); } } diff --git a/codes/csharp/chapter_searching/linear_search.cs b/codes/csharp/chapter_searching/linear_search.cs index 412eb37d35..f4e10d5f8a 100644 --- a/codes/csharp/chapter_searching/linear_search.cs +++ b/codes/csharp/chapter_searching/linear_search.cs @@ -4,56 +4,46 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_searching; -namespace hello_algo.chapter_searching -{ - public class linear_search - { - /* 线性查找(数组) */ - static int linearSearch(int[] nums, int target) - { - // 遍历数组 - for (int i = 0; i < nums.Length; i++) - { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; +public class linear_search { + /* 线性查找(数组) */ + int LinearSearchArray(int[] nums, int target) { + // 遍历数组 + for (int i = 0; i < nums.Length; i++) { + // 找到目标元素,返回其索引 + if (nums[i] == target) + return i; } + // 未找到目标元素,返回 -1 + return -1; + } - /* 线性查找(链表) */ - static ListNode? linearSearch(ListNode head, int target) - { - // 遍历链表 - while (head != null) - { - // 找到目标结点,返回之 - if (head.val == target) - return head; - head = head.next; - } - // 未找到目标结点,返回 null - return null; + /* 线性查找(链表) */ + ListNode? LinearSearchLinkedList(ListNode? head, int target) { + // 遍历链表 + while (head != null) { + // 找到目标节点,返回之 + if (head.val == target) + return head; + head = head.next; } + // 未找到目标节点,返回 null + return null; + } - [Test] - public void Test() - { - int target = 3; + [Test] + public void Test() { + int target = 3; - /* 在数组中执行线性查找 */ - int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; - int index = linearSearch(nums, target); - Console.WriteLine("目标元素 3 的索引 = " + index); + /* 在数组中执行线性查找 */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = LinearSearchArray(nums, target); + Console.WriteLine("目标元素 3 的索引 = " + index); - /* 在链表中执行线性查找 */ - ListNode head = ListNode.ArrToLinkedList(nums); - ListNode? node = linearSearch(head, target); - Console.WriteLine("目标结点值 3 的对应结点对象为 " + node); - } + /* 在链表中执行线性查找 */ + ListNode? head = ListNode.ArrToLinkedList(nums); + ListNode? node = LinearSearchLinkedList(head, target); + Console.WriteLine("目标节点值 3 的对应节点对象为 " + node); } } diff --git a/codes/csharp/chapter_searching/two_sum.cs b/codes/csharp/chapter_searching/two_sum.cs new file mode 100644 index 0000000000..d463eeb218 --- /dev/null +++ b/codes/csharp/chapter_searching/two_sum.cs @@ -0,0 +1,52 @@ +/** + * File: two_sum.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class two_sum { + /* 方法一:暴力枚举 */ + int[] TwoSumBruteForce(int[] nums, int target) { + int size = nums.Length; + // 两层循环,时间复杂度为 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return [i, j]; + } + } + return []; + } + + /* 方法二:辅助哈希表 */ + int[] TwoSumHashTable(int[] nums, int target) { + int size = nums.Length; + // 辅助哈希表,空间复杂度为 O(n) + Dictionary dic = []; + // 单层循环,时间复杂度为 O(n) + for (int i = 0; i < size; i++) { + if (dic.ContainsKey(target - nums[i])) { + return [dic[target - nums[i]], i]; + } + dic.Add(nums[i], i); + } + return []; + } + + [Test] + public void Test() { + // ======= Test Case ======= + int[] nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + int[] res = TwoSumBruteForce(nums, target); + Console.WriteLine("方法一 res = " + string.Join(",", res)); + // 方法二 + res = TwoSumHashTable(nums, target); + Console.WriteLine("方法二 res = " + string.Join(",", res)); + } +} diff --git a/codes/csharp/chapter_sorting/bubble_sort.cs b/codes/csharp/chapter_sorting/bubble_sort.cs index b3304df04e..97c5fa456b 100644 --- a/codes/csharp/chapter_sorting/bubble_sort.cs +++ b/codes/csharp/chapter_sorting/bubble_sort.cs @@ -4,65 +4,48 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_sorting; -namespace hello_algo.chapter_sorting -{ - public class bubble_sort - { - /* 冒泡排序 */ - static void bubbleSort(int[] nums) - { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; 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; - } +public class bubble_sort { + /* 冒泡排序 */ + void BubbleSort(int[] nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); } } } + } - /* 冒泡排序(标志优化)*/ - static void bubbleSortWithFlag(int[] nums) - { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; i > 0; i--) - { - bool flag = false; // 初始化标志位 - // 内循环:冒泡操作 - 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; - flag = true; // 记录交换元素 - } + /* 冒泡排序(标志优化)*/ + void BubbleSortWithFlag(int[] nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + bool flag = false; // 初始化标志位 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + flag = true; // 记录交换元素 } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 } + if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } + } - [Test] - public void Test() - { - int[] nums = { 4, 1, 3, 1, 5, 2 }; - bubbleSort(nums); - Console.WriteLine("冒泡排序完成后 nums = " + string.Join(",",nums)); + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + BubbleSort(nums); + Console.WriteLine("冒泡排序完成后 nums = " + string.Join(",", nums)); - int[] nums1 = { 4, 1, 3, 1, 5, 2 }; - bubbleSortWithFlag(nums1); - Console.WriteLine("冒泡排序完成后 nums1 = " + string.Join(",", nums)); - } + int[] nums1 = [4, 1, 3, 1, 5, 2]; + BubbleSortWithFlag(nums1); + Console.WriteLine("冒泡排序完成后 nums1 = " + string.Join(",", nums1)); } } diff --git a/codes/csharp/chapter_sorting/bucket_sort.cs b/codes/csharp/chapter_sorting/bucket_sort.cs new file mode 100644 index 0000000000..3fac83dbfb --- /dev/null +++ b/codes/csharp/chapter_sorting/bucket_sort.cs @@ -0,0 +1,46 @@ +/** + * File: bucket_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bucket_sort { + /* 桶排序 */ + void BucketSort(float[] nums) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + int k = nums.Length / 2; + List> buckets = []; + for (int i = 0; i < k; i++) { + buckets.Add([]); + } + // 1. 将数组元素分配到各个桶中 + foreach (float num in nums) { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + int i = (int)(num * k); + // 将 num 添加进桶 i + buckets[i].Add(num); + } + // 2. 对各个桶执行排序 + foreach (List bucket in buckets) { + // 使用内置排序函数,也可以替换成其他排序算法 + bucket.Sort(); + } + // 3. 遍历桶合并结果 + int j = 0; + foreach (List bucket in buckets) { + foreach (float num in bucket) { + nums[j++] = num; + } + } + } + + [Test] + public void Test() { + // 设输入数据为浮点数,范围为 [0, 1) + float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; + BucketSort(nums); + Console.WriteLine("桶排序完成后 nums = " + string.Join(" ", nums)); + } +} diff --git a/codes/csharp/chapter_sorting/counting_sort.cs b/codes/csharp/chapter_sorting/counting_sort.cs new file mode 100644 index 0000000000..d410288450 --- /dev/null +++ b/codes/csharp/chapter_sorting/counting_sort.cs @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class counting_sort { + /* 计数排序 */ + // 简单实现,无法用于排序对象 + void CountingSortNaive(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 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; + } + } + } + + /* 计数排序 */ + // 完整实现,可排序对象,并且是稳定排序 + void CountingSort(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int n = nums.Length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + [Test] + public void Test() { + int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSortNaive(nums); + Console.WriteLine("计数排序(无法排序对象)完成后 nums = " + string.Join(" ", nums)); + + int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSort(nums1); + Console.WriteLine("计数排序完成后 nums1 = " + string.Join(" ", nums)); + } +} diff --git a/codes/csharp/chapter_sorting/heap_sort.cs b/codes/csharp/chapter_sorting/heap_sort.cs new file mode 100644 index 0000000000..68df059f19 --- /dev/null +++ b/codes/csharp/chapter_sorting/heap_sort.cs @@ -0,0 +1,52 @@ +/** +* File: heap_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class heap_sort { + /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ + void SiftDown(int[] nums, int n, int i) { + while (true) { + // 判断节点 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; + // 交换两节点 + (nums[ma], nums[i]) = (nums[i], nums[ma]); + // 循环向下堆化 + i = ma; + } + } + + /* 堆排序 */ + void HeapSort(int[] nums) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = nums.Length / 2 - 1; i >= 0; i--) { + SiftDown(nums, nums.Length, i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = nums.Length - 1; i > 0; i--) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + (nums[i], nums[0]) = (nums[0], nums[i]); + // 以根节点为起点,从顶至底进行堆化 + SiftDown(nums, i, 0); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + HeapSort(nums); + Console.WriteLine("堆排序完成后 nums = " + string.Join(" ", nums)); + } +} diff --git a/codes/csharp/chapter_sorting/insertion_sort.cs b/codes/csharp/chapter_sorting/insertion_sort.cs index 0f674c9fcc..d3e974f62a 100644 --- a/codes/csharp/chapter_sorting/insertion_sort.cs +++ b/codes/csharp/chapter_sorting/insertion_sort.cs @@ -4,35 +4,27 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_sorting; -namespace hello_algo.chapter_sorting -{ - public class insertion_sort - { - /* 插入排序 */ - static void insertionSort(int[] nums) - { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.Length; i++) - { - int bas = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > bas) - { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = bas; // 2. 将 base 赋值到正确位置 +public class insertion_sort { + /* 插入排序 */ + void InsertionSort(int[] nums) { + // 外循环:已排序区间为 [0, i-1] + for (int i = 1; i < nums.Length; i++) { + int bas = nums[i], j = i - 1; + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while (j >= 0 && nums[j] > bas) { + nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 + j--; } + nums[j + 1] = bas; // 将 base 赋值到正确位置 } + } - [Test] - public void Test() - { - int[] nums = { 4, 1, 3, 1, 5, 2 }; - insertionSort(nums); - Console.WriteLine("插入排序完成后 nums = " + string.Join(",", nums)); - } + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + InsertionSort(nums); + Console.WriteLine("插入排序完成后 nums = " + string.Join(",", nums)); } } diff --git a/codes/csharp/chapter_sorting/merge_sort.cs b/codes/csharp/chapter_sorting/merge_sort.cs index e66ae5e230..051675d8d3 100644 --- a/codes/csharp/chapter_sorting/merge_sort.cs +++ b/codes/csharp/chapter_sorting/merge_sort.cs @@ -4,62 +4,53 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_sorting; -namespace hello_algo.chapter_sorting -{ - public class merge_sort - { - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ - static void merge(int[] nums, int left, int mid, int right) - { - // 初始化辅助数组 - int[] tmp = nums[left..(right + 1)]; - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) - { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else - nums[k] = tmp[j++]; - } +public class merge_sort { + /* 合并左子数组和右子数组 */ + void Merge(int[] nums, int left, int mid, int right) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + int[] tmp = new int[right - left + 1]; + // 初始化左子数组和右子数组的起始索引 + 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++]; } - - /* 归并排序 */ - static void mergeSort(int[] nums, int left, int right) - { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - int mid = (left + right) / 2; // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while (i <= mid) { + tmp[k++] = nums[i++]; } - - [Test] - public void Test() - { - /* 归并排序 */ - int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; - mergeSort(nums, 0, nums.Length - 1); - Console.WriteLine("归并排序完成后 nums = " + string.Join(",", nums)); + while (j <= right) { + tmp[k++] = nums[j++]; } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for (k = 0; k < tmp.Length; ++k) { + nums[left + k] = tmp[k]; + } + } + + /* 归并排序 */ + 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); + } + + [Test] + public void Test() { + /* 归并排序 */ + int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; + MergeSort(nums, 0, nums.Length - 1); + Console.WriteLine("归并排序完成后 nums = " + string.Join(",", nums)); } } diff --git a/codes/csharp/chapter_sorting/quick_sort.cs b/codes/csharp/chapter_sorting/quick_sort.cs index 020d4f342c..055c553b78 100644 --- a/codes/csharp/chapter_sorting/quick_sort.cs +++ b/codes/csharp/chapter_sorting/quick_sort.cs @@ -4,180 +4,147 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_sorting; -namespace hello_algo.chapter_sorting -{ - class QuickSort - { - /* 元素交换 */ - static void swap(int[] nums, int i, int j) - { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } +class quickSort { + /* 元素交换 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } - /* 哨兵划分 */ - static 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; // 返回基准数的索引 + /* 哨兵划分 */ + static 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; // 返回基准数的索引 + } - /* 快速排序 */ - public static 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); - } + /* 快速排序 */ + public static 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); } +} - /* 快速排序类(中位基准数优化) */ - class QuickSortMedian - { - /* 元素交换 */ - static void swap(int[] nums, int i, int j) - { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } +/* 快速排序类(中位基准数优化) */ +class QuickSortMedian { + /* 元素交换 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } - /* 选取三个元素的中位数 */ - static int medianThree(int[] nums, int left, int mid, int right) - { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } + /* 选取三个候选元素的中位数 */ + static 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; + } - /* 哨兵划分(三数取中值) */ - static int partition(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; // 返回基准数的索引 + /* 哨兵划分(三数取中值) */ + static int Partition(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; // 返回基准数的索引 + } - /* 快速排序 */ - public static 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); - } + /* 快速排序 */ + public static 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); } +} - /* 快速排序类(尾递归优化) */ - class QuickSortTailCall - { - /* 元素交换 */ - static void swap(int[] nums, int i, int j) - { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } +/* 快速排序类(尾递归优化) */ +class QuickSortTailCall { + /* 元素交换 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } - /* 哨兵划分 */ - static 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; // 返回基准数的索引 + /* 哨兵划分 */ + static 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; // 返回基准数的索引 + } - /* 快速排序(尾递归优化) */ - public static void quickSort(int[] nums, int left, int right) - { - // 子数组长度为 1 时终止 - while (left < right) - { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) - { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } - else - { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } + /* 快速排序(尾递归优化) */ + public static void QuickSort(int[] nums, int left, int right) { + // 子数组长度为 1 时终止 + while (left < right) { + // 哨兵划分操作 + int pivot = Partition(nums, left, right); + // 对两个子数组中较短的那个执行快速排序 + if (pivot - left < right - pivot) { + QuickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] + } else { + QuickSort(nums, pivot + 1, right); // 递归排序右子数组 + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } +} - public class quick_sort - { - [Test] - public void Test() - { - /* 快速排序 */ - int[] nums = { 2, 4, 1, 0, 3, 5 }; - QuickSort.quickSort(nums, 0, nums.Length - 1); - Console.WriteLine("快速排序完成后 nums = " + string.Join(",", nums)); +public class quick_sort { + [Test] + public void Test() { + /* 快速排序 */ + int[] nums = [2, 4, 1, 0, 3, 5]; + quickSort.QuickSort(nums, 0, nums.Length - 1); + Console.WriteLine("快速排序完成后 nums = " + string.Join(",", nums)); - /* 快速排序(中位基准数优化) */ - int[] nums1 = { 2, 4, 1, 0, 3, 5 }; - QuickSortMedian.quickSort(nums1, 0, nums1.Length - 1); - Console.WriteLine("快速排序(中位基准数优化)完成后 nums1 = " + string.Join(",", nums1)); + /* 快速排序(中位基准数优化) */ + int[] nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); + Console.WriteLine("快速排序(中位基准数优化)完成后 nums1 = " + string.Join(",", nums1)); - /* 快速排序(尾递归优化) */ - int[] nums2 = { 2, 4, 1, 0, 3, 5 }; - QuickSortTailCall.quickSort(nums2, 0, nums2.Length - 1); - Console.WriteLine("快速排序(尾递归优化)完成后 nums2 = " + string.Join(",", nums2)); - } + /* 快速排序(尾递归优化) */ + int[] nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); + Console.WriteLine("快速排序(尾递归优化)完成后 nums2 = " + string.Join(",", nums2)); } } diff --git a/codes/csharp/chapter_sorting/radix_sort.cs b/codes/csharp/chapter_sorting/radix_sort.cs new file mode 100644 index 0000000000..6b14e87ce4 --- /dev/null +++ b/codes/csharp/chapter_sorting/radix_sort.cs @@ -0,0 +1,69 @@ +/** + * File: radix_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class radix_sort { + /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + int Digit(int num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10; + } + + /* 计数排序(根据 nums 第 k 位排序) */ + void CountingSortDigit(int[] nums, int exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + int[] counter = new int[10]; + int n = nums.Length; + // 统计 0~9 各数字的出现次数 + for (int i = 0; i < n; i++) { + int d = Digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + int[] res = new int[n]; + for (int i = n - 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 < n; i++) { + nums[i] = res[i]; + } + } + + /* 基数排序 */ + void RadixSort(int[] nums) { + // 获取数组的最大元素,用于判断最大位数 + int m = int.MinValue; + foreach (int num in nums) { + if (num > m) m = num; + } + // 按照从低位到高位的顺序遍历 + for (int exp = 1; exp <= m; exp *= 10) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + CountingSortDigit(nums, exp); + } + } + + [Test] + public void Test() { + // 基数排序 + int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 ]; + RadixSort(nums); + Console.WriteLine("基数排序完成后 nums = " + string.Join(" ", nums)); + } +} diff --git a/codes/csharp/chapter_sorting/selection_sort.cs b/codes/csharp/chapter_sorting/selection_sort.cs new file mode 100644 index 0000000000..962660dec3 --- /dev/null +++ b/codes/csharp/chapter_sorting/selection_sort.cs @@ -0,0 +1,32 @@ +/** +* File: selection_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class selection_sort { + /* 选择排序 */ + void SelectionSort(int[] nums) { + int n = nums.Length; + // 外循环:未排序区间为 [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; // 记录最小元素的索引 + } + // 将该最小元素与未排序区间的首个元素交换 + (nums[k], nums[i]) = (nums[i], nums[k]); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + SelectionSort(nums); + Console.WriteLine("选择排序完成后 nums = " + string.Join(" ", nums)); + } +} diff --git a/codes/csharp/chapter_stack_and_queue/array_deque.cs b/codes/csharp/chapter_stack_and_queue/array_deque.cs new file mode 100644 index 0000000000..fbd87ef6ef --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/array_deque.cs @@ -0,0 +1,152 @@ +/** + * File: array_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基于环形数组实现的双向队列 */ +public class ArrayDeque { + int[] nums; // 用于存储双向队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 双向队列长度 + + /* 构造方法 */ + public ArrayDeque(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 获取双向队列的容量 */ + int Capacity() { + return nums.Length; + } + + /* 获取双向队列的长度 */ + public int Size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* 计算环形数组索引 */ + int Index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + Capacity()) % Capacity(); + } + + /* 队首入队 */ + public void PushFirst(int num) { + if (queSize == Capacity()) { + Console.WriteLine("双向队列已满"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + front = Index(front - 1); + // 将 num 添加至队首 + nums[front] = num; + queSize++; + } + + /* 队尾入队 */ + public void PushLast(int num) { + if (queSize == Capacity()) { + Console.WriteLine("双向队列已满"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + int rear = Index(front + queSize); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 队首出队 */ + public int PopFirst() { + int num = PeekFirst(); + // 队首指针向后移动一位 + front = Index(front + 1); + queSize--; + return num; + } + + /* 队尾出队 */ + public int PopLast() { + int num = PeekLast(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int PeekFirst() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + return nums[front]; + } + + /* 访问队尾元素 */ + public int PeekLast() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + // 计算尾元素索引 + int last = Index(front + queSize - 1); + return nums[last]; + } + + /* 返回数组用于打印 */ + public int[] ToArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[Index(j)]; + } + return res; + } +} + +public class array_deque { + [Test] + public void Test() { + /* 初始化双向队列 */ + ArrayDeque deque = new(10); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("双向队列 deque = " + string.Join(" ", deque.ToArray())); + + /* 访问元素 */ + int peekFirst = deque.PeekFirst(); + Console.WriteLine("队首元素 peekFirst = " + peekFirst); + int peekLast = deque.PeekLast(); + Console.WriteLine("队尾元素 peekLast = " + peekLast); + + /* 元素入队 */ + deque.PushLast(4); + Console.WriteLine("元素 4 队尾入队后 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("元素 1 队首入队后 deque = " + string.Join(" ", deque.ToArray())); + + /* 元素出队 */ + int popLast = deque.PopLast(); + Console.WriteLine("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + string.Join(" ", deque.ToArray())); + int popFirst = deque.PopFirst(); + Console.WriteLine("队首出队元素 = " + popFirst + ",队首出队后 deque = " + string.Join(" ", deque.ToArray())); + + /* 获取双向队列的长度 */ + int size = deque.Size(); + Console.WriteLine("双向队列长度 size = " + size); + + /* 判断双向队列是否为空 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("双向队列是否为空 = " + isEmpty); + } +} diff --git a/codes/csharp/chapter_stack_and_queue/array_queue.cs b/codes/csharp/chapter_stack_and_queue/array_queue.cs index 81085a3686..7be3d835e0 100644 --- a/codes/csharp/chapter_stack_and_queue/array_queue.cs +++ b/codes/csharp/chapter_stack_and_queue/array_queue.cs @@ -4,130 +4,111 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; - -namespace hello_algo.chapter_stack_and_queue -{ - - /* 基于环形数组实现的队列 */ - class ArrayQueue - { - private int[] nums; // 用于存储队列元素的数组 - private int front = 0; // 头指针,指向队首 - private int rear = 0; // 尾指针,指向队尾 + 1 - - public ArrayQueue(int capacity) - { - // 初始化数组 - nums = new int[capacity]; - } +namespace hello_algo.chapter_stack_and_queue; - /* 获取队列的容量 */ - public int capacity() - { - return nums.Length; - } +/* 基于环形数组实现的队列 */ +class ArrayQueue { + int[] nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 队列长度 - /* 获取队列的长度 */ - public int size() - { - int capacity = this.capacity(); - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity + rear - front) % capacity; - } + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } - /* 判断队列是否为空 */ - public bool isEmpty() - { - return rear - front == 0; - } + /* 获取队列的容量 */ + int Capacity() { + return nums.Length; + } - /* 入队 */ - public void offer(int num) - { - if (size() == capacity()) - { - Console.WriteLine("队列已满"); - return; - } - // 尾结点后添加 num - nums[rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity(); - } + /* 获取队列的长度 */ + public int Size() { + return queSize; + } - /* 出队 */ - public int poll() - { - int num = peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - return num; - } + /* 判断队列是否为空 */ + public bool IsEmpty() { + return queSize == 0; + } - /* 访问队首元素 */ - public int peek() - { - if (isEmpty()) - throw new Exception(); - return nums[front]; + /* 入队 */ + public void Push(int num) { + if (queSize == Capacity()) { + Console.WriteLine("队列已满"); + return; } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % Capacity(); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 出队 */ + public int Pop() { + int num = Peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + front = (front + 1) % Capacity(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return nums[front]; + } - /* 返回数组 */ - public int[] toArray() - { - int size = this.size(); - int capacity = this.capacity(); - // 仅转换有效长度范围内的列表元素 - int[] res = new int[size]; - for (int i = 0, j = front; i < size; i++, j++) - { - res[i] = nums[j % capacity]; - } - return res; + /* 返回数组 */ + public int[] ToArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % this.Capacity()]; } + return res; } +} + +public class array_queue { + [Test] + public void Test() { + /* 初始化队列 */ + int capacity = 10; + ArrayQueue queue = new(capacity); + + /* 元素入队 */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("队列 queue = " + string.Join(",", queue.ToArray())); - public class array_queue - { - [Test] - public void Test() - { - /* 初始化队列 */ - int capacity = 10; - ArrayQueue queue = new ArrayQueue(capacity); - - /* 元素入队 */ - queue.offer(1); - queue.offer(3); - queue.offer(2); - queue.offer(5); - queue.offer(4); - Console.WriteLine("队列 queue = " + string.Join(",", queue.toArray())); - - /* 访问队首元素 */ - int peek = queue.peek(); - Console.WriteLine("队首元素 peek = " + peek); - - /* 元素出队 */ - int poll = queue.poll(); - Console.WriteLine("出队元素 poll = " + poll + ",出队后 queue = " + string.Join(",", queue.toArray())); - - /* 获取队列的长度 */ - int size = queue.size(); - Console.WriteLine("队列长度 size = " + size); - - /* 判断队列是否为空 */ - bool isEmpty = queue.isEmpty(); - Console.WriteLine("队列是否为空 = " + isEmpty); - - /* 测试环形数组 */ - for (int i = 0; i < 10; i++) - { - queue.offer(i); - queue.poll(); - Console.WriteLine("第 " + i + " 轮入队 + 出队后 queue = " + string.Join(",", queue.toArray())); - } + /* 访问队首元素 */ + int peek = queue.Peek(); + Console.WriteLine("队首元素 peek = " + peek); + + /* 元素出队 */ + int pop = queue.Pop(); + Console.WriteLine("出队元素 pop = " + pop + ",出队后 queue = " + string.Join(",", queue.ToArray())); + + /* 获取队列的长度 */ + int size = queue.Size(); + Console.WriteLine("队列长度 size = " + size); + + /* 判断队列是否为空 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("队列是否为空 = " + isEmpty); + + /* 测试环形数组 */ + for (int i = 0; i < 10; i++) { + queue.Push(i); + queue.Pop(); + Console.WriteLine("第 " + i + " 轮入队 + 出队后 queue = " + string.Join(",", queue.ToArray())); } } -} \ No newline at end of file +} diff --git a/codes/csharp/chapter_stack_and_queue/array_stack.cs b/codes/csharp/chapter_stack_and_queue/array_stack.cs index b7ea6df768..4f89b65e27 100644 --- a/codes/csharp/chapter_stack_and_queue/array_stack.cs +++ b/codes/csharp/chapter_stack_and_queue/array_stack.cs @@ -4,95 +4,81 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_stack_and_queue; -namespace hello_algo.chapter_stack_and_queue -{ - - /* 基于数组实现的栈 */ - class ArrayStack - { - private List stack; - public ArrayStack() - { - // 初始化列表(动态数组) - stack = new(); - } +/* 基于数组实现的栈 */ +class ArrayStack { + List stack; + public ArrayStack() { + // 初始化列表(动态数组) + stack = []; + } - /* 获取栈的长度 */ - public int size() - { - return stack.Count(); - } + /* 获取栈的长度 */ + public int Size() { + return stack.Count; + } - /* 判断栈是否为空 */ - public bool isEmpty() - { - return size() == 0; - } + /* 判断栈是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } - /* 入栈 */ - public void push(int num) - { - stack.Add(num); - } + /* 入栈 */ + public void Push(int num) { + stack.Add(num); + } - /* 出栈 */ - public int pop() - { - if (isEmpty()) - throw new Exception(); - var val = peek(); - stack.RemoveAt(size() - 1); - return val; - } + /* 出栈 */ + public int Pop() { + if (IsEmpty()) + throw new Exception(); + var val = Peek(); + stack.RemoveAt(Size() - 1); + return val; + } - /* 访问栈顶元素 */ - public int peek() - { - if (isEmpty()) - throw new Exception(); - return stack[size() - 1]; - } + /* 访问栈顶元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stack[Size() - 1]; + } - /* 将 List 转化为 Array 并返回 */ - public int[] toArray() - { - return stack.ToArray(); - } + /* 将 List 转化为 Array 并返回 */ + public int[] ToArray() { + return [.. stack]; } +} - public class array_stack - { - [Test] - public void Test() - { - /* 初始化栈 */ - ArrayStack stack = new ArrayStack(); +public class array_stack { + [Test] + public void Test() { + /* 初始化栈 */ + ArrayStack stack = new(); - /* 元素入栈 */ - stack.push(1); - stack.push(3); - stack.push(2); - stack.push(5); - stack.push(4); - Console.WriteLine("栈 stack = " + String.Join(",", stack.toArray())); + /* 元素入栈 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("栈 stack = " + string.Join(",", stack.ToArray())); - /* 访问栈顶元素 */ - int peek = stack.peek(); - Console.WriteLine("栈顶元素 peek = " + peek); + /* 访问栈顶元素 */ + int peek = stack.Peek(); + Console.WriteLine("栈顶元素 peek = " + peek); - /* 元素出栈 */ - int pop = stack.pop(); - Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + String.Join(",", stack.toArray())); + /* 元素出栈 */ + int pop = stack.Pop(); + Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + string.Join(",", stack.ToArray())); - /* 获取栈的长度 */ - int size = stack.size(); - Console.WriteLine("栈的长度 size = " + size); + /* 获取栈的长度 */ + int size = stack.Size(); + Console.WriteLine("栈的长度 size = " + size); - /* 判断是否为空 */ - bool isEmpty = stack.isEmpty(); - Console.WriteLine("栈是否为空 = " + isEmpty); - } + /* 判断是否为空 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("栈是否为空 = " + isEmpty); } } diff --git a/codes/csharp/chapter_stack_and_queue/deque.cs b/codes/csharp/chapter_stack_and_queue/deque.cs index d9f7a50982..d9b9d8709b 100644 --- a/codes/csharp/chapter_stack_and_queue/deque.cs +++ b/codes/csharp/chapter_stack_and_queue/deque.cs @@ -4,46 +4,41 @@ * Author: moonache (microin1301@outlook.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_stack_and_queue; -namespace hello_algo.chapter_stack_and_queue -{ - public class deque - { - [Test] - public void Test() - { - /* 初始化双向队列 */ - // 在 C# 中,将链表 LinkedList 看作双向队列来使用 - LinkedList deque = new LinkedList(); +public class deque { + [Test] + public void Test() { + /* 初始化双向队列 */ + // 在 C# 中,将链表 LinkedList 看作双向队列来使用 + LinkedList deque = new(); - /* 元素入队 */ - deque.AddLast(2); // 添加至队尾 - deque.AddLast(5); - deque.AddLast(4); - deque.AddFirst(3); // 添加至队首 - deque.AddFirst(1); - Console.WriteLine("双向队列 deque = " + String.Join(",", deque.ToArray())); + /* 元素入队 */ + deque.AddLast(2); // 添加至队尾 + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 添加至队首 + deque.AddFirst(1); + Console.WriteLine("双向队列 deque = " + string.Join(",", deque)); - /* 访问元素 */ - int peekFirst = deque.First.Value; // 队首元素 - Console.WriteLine("队首元素 peekFirst = " + peekFirst); - int peekLast = deque.Last.Value; // 队尾元素 - Console.WriteLine("队尾元素 peekLast = " + peekLast); + /* 访问元素 */ + int? peekFirst = deque.First?.Value; // 队首元素 + Console.WriteLine("队首元素 peekFirst = " + peekFirst); + int? peekLast = deque.Last?.Value; // 队尾元素 + Console.WriteLine("队尾元素 peekLast = " + peekLast); - /* 元素出队 */ - deque.RemoveFirst(); // 队首元素出队 - Console.WriteLine("队首元素出队后 deque = " + String.Join(",", deque.ToArray())); - deque.RemoveLast(); // 队尾元素出队 - Console.WriteLine("队尾元素出队后 deque = " + String.Join(",", deque.ToArray())); + /* 元素出队 */ + deque.RemoveFirst(); // 队首元素出队 + Console.WriteLine("队首元素出队后 deque = " + string.Join(",", deque)); + deque.RemoveLast(); // 队尾元素出队 + Console.WriteLine("队尾元素出队后 deque = " + string.Join(",", deque)); - /* 获取双向队列的长度 */ - int size = deque.Count; - Console.WriteLine("双向队列长度 size = " + size); + /* 获取双向队列的长度 */ + int size = deque.Count; + Console.WriteLine("双向队列长度 size = " + size); - /* 判断双向队列是否为空 */ - bool isEmpty = deque.Count == 0; - Console.WriteLine("双向队列是否为空 = " + isEmpty); - } + /* 判断双向队列是否为空 */ + bool isEmpty = deque.Count == 0; + Console.WriteLine("双向队列是否为空 = " + isEmpty); } } diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs new file mode 100644 index 0000000000..d2c51c1a53 --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs @@ -0,0 +1,177 @@ +/** + * File: linkedlist_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 双向链表节点 */ +public class ListNode(int val) { + public int val = val; // 节点值 + public ListNode? next = null; // 后继节点引用 + public ListNode? prev = null; // 前驱节点引用 +} + +/* 基于双向链表实现的双向队列 */ +public class LinkedListDeque { + ListNode? front, rear; // 头节点 front, 尾节点 rear + int queSize = 0; // 双向队列的长度 + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* 获取双向队列的长度 */ + public int Size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入队操作 */ + void Push(int num, bool isFront) { + ListNode node = new(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (IsEmpty()) { + front = node; + rear = node; + } + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front!.prev = node; + node.next = front; + front = node; // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + rear!.next = node; + node.prev = rear; + rear = node; // 更新尾节点 + } + + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + public void PushFirst(int num) { + Push(num, true); + } + + /* 队尾入队 */ + public void PushLast(int num) { + Push(num, false); + } + + /* 出队操作 */ + int? Pop(bool isFront) { + if (IsEmpty()) + throw new Exception(); + int? val; + // 队首出队操作 + if (isFront) { + val = front?.val; // 暂存头节点值 + // 删除头节点 + ListNode? fNext = front?.next; + if (fNext != null) { + fNext.prev = null; + front!.next = null; + } + front = fNext; // 更新头节点 + } + // 队尾出队操作 + else { + val = rear?.val; // 暂存尾节点值 + // 删除尾节点 + ListNode? rPrev = rear?.prev; + if (rPrev != null) { + rPrev.next = null; + rear!.prev = null; + } + rear = rPrev; // 更新尾节点 + } + + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + public int? PopFirst() { + return Pop(true); + } + + /* 队尾出队 */ + public int? PopLast() { + return Pop(false); + } + + /* 访问队首元素 */ + public int? PeekFirst() { + if (IsEmpty()) + throw new Exception(); + return front?.val; + } + + /* 访问队尾元素 */ + public int? PeekLast() { + if (IsEmpty()) + throw new Exception(); + return rear?.val; + } + + /* 返回数组用于打印 */ + public int?[] ToArray() { + ListNode? node = front; + int?[] res = new int?[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node?.val; + node = node?.next; + } + + return res; + } +} + +public class linkedlist_deque { + [Test] + public void Test() { + /* 初始化双向队列 */ + LinkedListDeque deque = new(); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("双向队列 deque = " + string.Join(" ", deque.ToArray())); + + /* 访问元素 */ + int? peekFirst = deque.PeekFirst(); + Console.WriteLine("队首元素 peekFirst = " + peekFirst); + int? peekLast = deque.PeekLast(); + Console.WriteLine("队尾元素 peekLast = " + peekLast); + + /* 元素入队 */ + deque.PushLast(4); + Console.WriteLine("元素 4 队尾入队后 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("元素 1 队首入队后 deque = " + string.Join(" ", deque.ToArray())); + + /* 元素出队 */ + int? popLast = deque.PopLast(); + Console.WriteLine("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + string.Join(" ", deque.ToArray())); + int? popFirst = deque.PopFirst(); + Console.WriteLine("队首出队元素 = " + popFirst + ",队首出队后 deque = " + string.Join(" ", deque.ToArray())); + + /* 获取双向队列的长度 */ + int size = deque.Size(); + Console.WriteLine("双向队列长度 size = " + size); + + /* 判断双向队列是否为空 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("双向队列是否为空 = " + isEmpty); + } +} diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs index 70816c8f07..646c359e7a 100644 --- a/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs @@ -4,121 +4,103 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; - -namespace hello_algo.chapter_stack_and_queue -{ - /* 基于链表实现的队列 */ - class LinkedListQueue - { - private ListNode? front, rear; // 头结点 front ,尾结点 rear - private int queSize = 0; - - public LinkedListQueue() - { - front = null; - rear = null; - } +namespace hello_algo.chapter_stack_and_queue; - /* 获取队列的长度 */ - public int size() - { - return queSize; - } +/* 基于链表实现的队列 */ +class LinkedListQueue { + ListNode? front, rear; // 头节点 front ,尾节点 rear + int queSize = 0; - /* 判断队列是否为空 */ - public bool isEmpty() - { - return size() == 0; - } + public LinkedListQueue() { + front = null; + rear = null; + } - /* 入队 */ - public void offer(int num) - { - // 尾结点后添加 num - ListNode node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == null) - { - front = node; - rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } - else if (rear != null) - { - rear.next = node; - rear = node; - } - queSize++; - } + /* 获取队列的长度 */ + public int Size() { + return queSize; + } - /* 出队 */ - public int poll() - { - int num = peek(); - // 删除头结点 - front = front?.next; - queSize--; - return num; - } + /* 判断队列是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } - /* 访问队首元素 */ - public int peek() - { - if (size() == 0 || front == null) - throw new Exception(); - return front.val; + /* 入队 */ + public void Push(int num) { + // 在尾节点后添加 num + ListNode node = new(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (front == null) { + front = node; + rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else if (rear != null) { + rear.next = node; + rear = node; } + queSize++; + } - /* 将链表转化为 Array 并返回 */ - public int[] toArray() - { - if (front == null) - return Array.Empty(); - - ListNode node = front; - int[] res = new int[size()]; - for (int i = 0; i < res.Length; i++) - { - res[i] = node.val; - node = node.next; - } - return res; - } + /* 出队 */ + public int Pop() { + int num = Peek(); + // 删除头节点 + front = front?.next; + queSize--; + return num; + } + + /* 访问队首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return front!.val; } - public class linkedlist_queue - { - [Test] - public void Test() - { - /* 初始化队列 */ - LinkedListQueue queue = new LinkedListQueue(); - - /* 元素入队 */ - queue.offer(1); - queue.offer(3); - queue.offer(2); - queue.offer(5); - queue.offer(4); - Console.WriteLine("队列 queue = " + String.Join(",", queue.toArray())); - - /* 访问队首元素 */ - int peek = queue.peek(); - Console.WriteLine("队首元素 peek = " + peek); - - /* 元素出队 */ - int poll = queue.poll(); - Console.WriteLine("出队元素 poll = " + poll + ",出队后 queue = " + String.Join(",", queue.toArray())); - - /* 获取队列的长度 */ - int size = queue.size(); - Console.WriteLine("队列长度 size = " + size); - - /* 判断队列是否为空 */ - bool isEmpty = queue.isEmpty(); - Console.WriteLine("队列是否为空 = " + isEmpty); + /* 将链表转化为 Array 并返回 */ + public int[] ToArray() { + if (front == null) + return []; + + ListNode? node = front; + int[] res = new int[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node!.val; + node = node.next; } + return res; + } +} + +public class linkedlist_queue { + [Test] + public void Test() { + /* 初始化队列 */ + LinkedListQueue queue = new(); + + /* 元素入队 */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("队列 queue = " + string.Join(",", queue.ToArray())); + + /* 访问队首元素 */ + int peek = queue.Peek(); + Console.WriteLine("队首元素 peek = " + peek); + + /* 元素出队 */ + int pop = queue.Pop(); + Console.WriteLine("出队元素 pop = " + pop + ",出队后 queue = " + string.Join(",", queue.ToArray())); + + /* 获取队列的长度 */ + int size = queue.Size(); + Console.WriteLine("队列长度 size = " + size); + + /* 判断队列是否为空 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("队列是否为空 = " + isEmpty); } -} \ No newline at end of file +} diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs index 78a409449c..c1b36478bd 100644 --- a/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs @@ -4,110 +4,94 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; - -namespace hello_algo.chapter_stack_and_queue -{ - class LinkedListStack - { - private ListNode? stackPeek; // 将头结点作为栈顶 - private int stkSize = 0; // 栈的长度 - - public LinkedListStack() - { - stackPeek = null; - } +namespace hello_algo.chapter_stack_and_queue; - /* 获取栈的长度 */ - public int size() - { - return stkSize; - } +/* 基于链表实现的栈 */ +class LinkedListStack { + ListNode? stackPeek; // 将头节点作为栈顶 + int stkSize = 0; // 栈的长度 - /* 判断栈是否为空 */ - public bool isEmpty() - { - return size() == 0; - } + public LinkedListStack() { + stackPeek = null; + } - /* 入栈 */ - public void push(int num) - { - ListNode node = new ListNode(num); - node.next = stackPeek; - stackPeek = node; - stkSize++; - } + /* 获取栈的长度 */ + public int Size() { + return stkSize; + } - /* 出栈 */ - public int pop() - { - if (stackPeek == null) - throw new Exception(); + /* 判断栈是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } - int num = peek(); - stackPeek = stackPeek.next; - stkSize--; - return num; - } + /* 入栈 */ + public void Push(int num) { + ListNode node = new(num) { + next = stackPeek + }; + stackPeek = node; + stkSize++; + } - /* 访问栈顶元素 */ - public int peek() - { - if (size() == 0 || stackPeek==null) - throw new Exception(); - return stackPeek.val; - } + /* 出栈 */ + public int Pop() { + int num = Peek(); + stackPeek = stackPeek!.next; + stkSize--; + return num; + } - /* 将 List 转化为 Array 并返回 */ - public int[] toArray() - { - if (stackPeek == null) - return Array.Empty(); - - ListNode node = stackPeek; - int[] res = new int[size()]; - for (int i = res.Length - 1; i >= 0; i--) - { - res[i] = node.val; - node = node.next; - } - return res; - } + /* 访问栈顶元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stackPeek!.val; } - public class linkedlist_stack - { - [Test] - public void Test() - { - /* 初始化栈 */ - LinkedListStack stack = new LinkedListStack(); - - /* 元素入栈 */ - stack.push(1); - stack.push(3); - stack.push(2); - stack.push(5); - stack.push(4); - Console.WriteLine("栈 stack = " + String.Join(",",stack.toArray())); - - /* 访问栈顶元素 */ - int peek = stack.peek(); - Console.WriteLine("栈顶元素 peek = " + peek); - - /* 元素出栈 */ - int pop = stack.pop(); - Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + String.Join(",",stack.toArray())); - - /* 获取栈的长度 */ - int size = stack.size(); - Console.WriteLine("栈的长度 size = " + size); - - /* 判断是否为空 */ - bool isEmpty = stack.isEmpty(); - Console.WriteLine("栈是否为空 = " + isEmpty); + /* 将 List 转化为 Array 并返回 */ + public int[] ToArray() { + if (stackPeek == null) + return []; + + ListNode? node = stackPeek; + int[] res = new int[Size()]; + for (int i = res.Length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node.next; } + return res; + } +} + +public class linkedlist_stack { + [Test] + public void Test() { + /* 初始化栈 */ + LinkedListStack stack = new(); + + /* 元素入栈 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("栈 stack = " + string.Join(",", stack.ToArray())); + + /* 访问栈顶元素 */ + int peek = stack.Peek(); + Console.WriteLine("栈顶元素 peek = " + peek); + + /* 元素出栈 */ + int pop = stack.Pop(); + Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + string.Join(",", stack.ToArray())); + + /* 获取栈的长度 */ + int size = stack.Size(); + Console.WriteLine("栈的长度 size = " + size); + + /* 判断是否为空 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("栈是否为空 = " + isEmpty); } -} \ No newline at end of file +} diff --git a/codes/csharp/chapter_stack_and_queue/queue.cs b/codes/csharp/chapter_stack_and_queue/queue.cs index a5ce969532..b42ac8f783 100644 --- a/codes/csharp/chapter_stack_and_queue/queue.cs +++ b/codes/csharp/chapter_stack_and_queue/queue.cs @@ -4,42 +4,36 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; - -namespace hello_algo.chapter_stack_and_queue -{ - public class queue - { - [Test] - public void Test() - { - /* 初始化队列 */ - Queue queue = new(); - - /* 元素入队 */ - queue.Enqueue(1); - queue.Enqueue(3); - queue.Enqueue(2); - queue.Enqueue(5); - queue.Enqueue(4); - Console.WriteLine("队列 queue = " + String.Join(",", queue.ToArray())); - - /* 访问队首元素 */ - int peek = queue.Peek(); - Console.WriteLine("队首元素 peek = " + peek); - - /* 元素出队 */ - int poll = queue.Dequeue(); - Console.WriteLine("出队元素 poll = " + poll + ",出队后 queue = " + String.Join(",", queue.ToArray())); - - /* 获取队列的长度 */ - int size = queue.Count(); - Console.WriteLine("队列长度 size = " + size); - - /* 判断队列是否为空 */ - bool isEmpty = queue.Count() == 0; - Console.WriteLine("队列是否为空 = " + isEmpty); - } +namespace hello_algo.chapter_stack_and_queue; + +public class queue { + [Test] + public void Test() { + /* 初始化队列 */ + Queue queue = new(); + + /* 元素入队 */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + Console.WriteLine("队列 queue = " + string.Join(",", queue)); + + /* 访问队首元素 */ + int peek = queue.Peek(); + Console.WriteLine("队首元素 peek = " + peek); + + /* 元素出队 */ + int pop = queue.Dequeue(); + Console.WriteLine("出队元素 pop = " + pop + ",出队后 queue = " + string.Join(",", queue)); + + /* 获取队列的长度 */ + int size = queue.Count; + Console.WriteLine("队列长度 size = " + size); + + /* 判断队列是否为空 */ + bool isEmpty = queue.Count == 0; + Console.WriteLine("队列是否为空 = " + isEmpty); } - } diff --git a/codes/csharp/chapter_stack_and_queue/stack.cs b/codes/csharp/chapter_stack_and_queue/stack.cs index c19666b72d..7c2f827f0f 100644 --- a/codes/csharp/chapter_stack_and_queue/stack.cs +++ b/codes/csharp/chapter_stack_and_queue/stack.cs @@ -4,42 +4,37 @@ * Author: haptear (haptear@hotmail.com) */ -using NUnit.Framework; +namespace hello_algo.chapter_stack_and_queue; -namespace hello_algo.chapter_stack_and_queue -{ - public class stack - { - [Test] - public void Test() - { - /* 初始化栈 */ - Stack stack = new(); +public class stack { + [Test] + public void Test() { + /* 初始化栈 */ + Stack stack = new(); - /* 元素入栈 */ - stack.Push(1); - stack.Push(3); - stack.Push(2); - stack.Push(5); - stack.Push(4); - // 请注意,stack.ToArray() 得到的是倒序序列,即索引 0 为栈顶 - Console.WriteLine("栈 stack = " + string.Join(",", stack.ToArray())); + /* 元素入栈 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + // 请注意,stack.ToArray() 得到的是倒序序列,即索引 0 为栈顶 + Console.WriteLine("栈 stack = " + string.Join(",", stack)); - /* 访问栈顶元素 */ - int peek = stack.Peek(); - Console.WriteLine("栈顶元素 peek = " + peek); + /* 访问栈顶元素 */ + int peek = stack.Peek(); + Console.WriteLine("栈顶元素 peek = " + peek); - /* 元素出栈 */ - int pop = stack.Pop(); - Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + string.Join(",", stack.ToArray())); + /* 元素出栈 */ + int pop = stack.Pop(); + Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + string.Join(",", stack)); - /* 获取栈的长度 */ - int size = stack.Count(); - Console.WriteLine("栈的长度 size = " + size); + /* 获取栈的长度 */ + int size = stack.Count; + Console.WriteLine("栈的长度 size = " + size); - /* 判断是否为空 */ - bool isEmpty = stack.Count() == 0; - Console.WriteLine("栈是否为空 = " + isEmpty); - } + /* 判断是否为空 */ + bool isEmpty = stack.Count == 0; + Console.WriteLine("栈是否为空 = " + isEmpty); } -} \ No newline at end of file +} diff --git a/codes/csharp/chapter_tree/array_binary_tree.cs b/codes/csharp/chapter_tree/array_binary_tree.cs new file mode 100644 index 0000000000..91b7673d35 --- /dev/null +++ b/codes/csharp/chapter_tree/array_binary_tree.cs @@ -0,0 +1,129 @@ +/** +* File: array_binary_tree.cs +* Created Time: 2023-07-20 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_tree; + +/* 数组表示下的二叉树类 */ +public class ArrayBinaryTree(List arr) { + List tree = new(arr); + + /* 列表容量 */ + public int Size() { + return tree.Count; + } + + /* 获取索引为 i 节点的值 */ + public int? Val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= Size()) + return null; + return tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + public int Left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + public int Right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + public int Parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + public List LevelOrder() { + List res = []; + // 直接遍历数组 + for (int i = 0; i < Size(); i++) { + if (Val(i).HasValue) + res.Add(Val(i)!.Value); + } + return res; + } + + /* 深度优先遍历 */ + void DFS(int i, string order, List res) { + // 若为空位,则返回 + if (!Val(i).HasValue) + return; + // 前序遍历 + if (order == "pre") + res.Add(Val(i)!.Value); + DFS(Left(i), order, res); + // 中序遍历 + if (order == "in") + res.Add(Val(i)!.Value); + DFS(Right(i), order, res); + // 后序遍历 + if (order == "post") + res.Add(Val(i)!.Value); + } + + /* 前序遍历 */ + public List PreOrder() { + List res = []; + DFS(0, "pre", res); + return res; + } + + /* 中序遍历 */ + public List InOrder() { + List res = []; + DFS(0, "in", res); + return res; + } + + /* 后序遍历 */ + public List PostOrder() { + List res = []; + DFS(0, "post", res); + return res; + } +} + +public class array_binary_tree { + [Test] + public void Test() { + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + + TreeNode? root = TreeNode.ListToTree(arr); + Console.WriteLine("\n初始化二叉树\n"); + Console.WriteLine("二叉树的数组表示:"); + Console.WriteLine(arr.PrintList()); + Console.WriteLine("二叉树的链表表示:"); + PrintUtil.PrintTree(root); + + // 数组表示下的二叉树类 + ArrayBinaryTree abt = new(arr); + + // 访问节点 + int i = 1; + int l = abt.Left(i); + int r = abt.Right(i); + int p = abt.Parent(i); + Console.WriteLine("\n当前节点的索引为 " + i + " ,值为 " + abt.Val(i)); + Console.WriteLine("其左子节点的索引为 " + l + " ,值为 " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); + Console.WriteLine("其右子节点的索引为 " + r + " ,值为 " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); + Console.WriteLine("其父节点的索引为 " + p + " ,值为 " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); + + // 遍历树 + List res = abt.LevelOrder(); + Console.WriteLine("\n层序遍历为:" + res.PrintList()); + res = abt.PreOrder(); + Console.WriteLine("前序遍历为:" + res.PrintList()); + res = abt.InOrder(); + Console.WriteLine("中序遍历为:" + res.PrintList()); + res = abt.PostOrder(); + Console.WriteLine("后序遍历为:" + res.PrintList()); + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_tree/avl_tree.cs b/codes/csharp/chapter_tree/avl_tree.cs index 6bcb559b7a..6597889cae 100644 --- a/codes/csharp/chapter_tree/avl_tree.cs +++ b/codes/csharp/chapter_tree/avl_tree.cs @@ -4,257 +4,213 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_tree; -namespace hello_algo.chapter_tree -{ - // Tree class - class AVLTree - { - public TreeNode? root; // 根节点 +/* AVL 树 */ +class AVLTree { + public TreeNode? root; // 根节点 - /* 获取结点高度 */ - public int height(TreeNode? node) - { - // 空结点高度为 -1 ,叶结点高度为 0 - return node == null ? -1 : node.height; - } + /* 获取节点高度 */ + int Height(TreeNode? node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == null ? -1 : node.height; + } - /* 更新结点高度 */ - private void updateHeight(TreeNode node) - { - // 结点高度等于最高子树高度 + 1 - node.height = Math.Max(height(node.left), height(node.right)) + 1; - } + /* 更新节点高度 */ + void UpdateHeight(TreeNode node) { + // 节点高度等于最高子树高度 + 1 + node.height = Math.Max(Height(node.left), Height(node.right)) + 1; + } - /* 获取平衡因子 */ - public int balanceFactor(TreeNode? node) - { - // 空结点平衡因子为 0 - if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.left) - height(node.right); - } + /* 获取平衡因子 */ + public int BalanceFactor(TreeNode? node) { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return Height(node.left) - Height(node.right); + } - /* 右旋操作 */ - TreeNode? rightRotate(TreeNode? node) - { - TreeNode? child = node.left; - TreeNode? grandChild = child?.right; - // 以 child 为原点,将 node 向右旋转 - child.right = node; - node.left = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; - } + /* 右旋操作 */ + TreeNode? RightRotate(TreeNode? node) { + TreeNode? child = node?.left; + TreeNode? grandChild = child?.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋转后子树的根节点 + return child; + } - /* 左旋操作 */ - TreeNode? leftRotate(TreeNode? node) - { - TreeNode? child = node.right; - TreeNode? grandChild = child?.left; - // 以 child 为原点,将 node 向左旋转 - child.left = node; - node.right = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; - } + /* 左旋操作 */ + TreeNode? LeftRotate(TreeNode? node) { + TreeNode? child = node?.right; + TreeNode? grandChild = child?.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋转后子树的根节点 + return child; + } - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode? rotate(TreeNode? node) - { - // 获取结点 node 的平衡因子 - int balanceFactorInt = balanceFactor(node); - // 左偏树 - if (balanceFactorInt > 1) - { - if (balanceFactor(node.left) >= 0) - { - // 右旋 - return rightRotate(node); - } - else - { - // 先左旋后右旋 - node.left = leftRotate(node?.left); - return rightRotate(node); - } + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode? Rotate(TreeNode? node) { + // 获取节点 node 的平衡因子 + int balanceFactorInt = BalanceFactor(node); + // 左偏树 + if (balanceFactorInt > 1) { + if (BalanceFactor(node?.left) >= 0) { + // 右旋 + return RightRotate(node); + } else { + // 先左旋后右旋 + node!.left = LeftRotate(node!.left); + return RightRotate(node); } - // 右偏树 - if (balanceFactorInt < -1) - { - if (balanceFactor(node.right) <= 0) - { - // 左旋 - return leftRotate(node); - } - else - { - // 先右旋后左旋 - node.right = rightRotate(node?.right); - return leftRotate(node); - } + } + // 右偏树 + if (balanceFactorInt < -1) { + if (BalanceFactor(node?.right) <= 0) { + // 左旋 + return LeftRotate(node); + } else { + // 先右旋后左旋 + node!.right = RightRotate(node!.right); + return LeftRotate(node); } - // 平衡树,无需旋转,直接返回 - return node; } + // 平衡树,无须旋转,直接返回 + return node; + } - /* 插入结点 */ - public TreeNode? insert(int val) - { - root = insertHelper(root, val); - return root; - } + /* 插入节点 */ + public void Insert(int val) { + root = InsertHelper(root, val); + } - /* 递归插入结点(辅助函数) */ - private TreeNode? insertHelper(TreeNode? node, int val) - { - if (node == null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node.val) - node.left = insertHelper(node.left, val); - else if (val > node.val) - node.right = insertHelper(node.right, val); - else - return node; // 重复结点不插入,直接返回 - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根节点 - return node; - } + /* 递归插入节点(辅助方法) */ + TreeNode? InsertHelper(TreeNode? node, int val) { + if (node == null) return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) + node.left = InsertHelper(node.left, val); + else if (val > node.val) + node.right = InsertHelper(node.right, val); + else + return node; // 重复节点不插入,直接返回 + UpdateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Rotate(node); + // 返回子树的根节点 + return node; + } - /* 删除结点 */ - public TreeNode? remove(int val) - { - root = removeHelper(root, val); - return root; - } + /* 删除节点 */ + public void Remove(int val) { + root = RemoveHelper(root, val); + } - /* 递归删除结点(辅助函数) */ - private TreeNode? removeHelper(TreeNode? node, int val) - { - if (node == null) return null; - /* 1. 查找结点,并删除之 */ - if (val < node.val) - node.left = removeHelper(node.left, val); - else if (val > node.val) - node.right = removeHelper(node.right, val); - else - { - if (node.left == null || node.right == null) - { - TreeNode? child = node.left != null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child == null) - return null; - // 子结点数量 = 1 ,直接删除 node - else - node = child; - } + /* 递归删除节点(辅助方法) */ + TreeNode? RemoveHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) + node.left = RemoveHelper(node.left, val); + else if (val > node.val) + node.right = RemoveHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null; + // 子节点数量 = 1 ,直接删除 node else - { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode? temp = getInOrderNext(node.right); - node.right = removeHelper(node.right, temp.val); - node.val = temp.val; + node = child; + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode? temp = node.right; + while (temp.left != null) { + temp = temp.left; } + node.right = RemoveHelper(node.right, temp.val!.Value); + node.val = temp.val; } - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根节点 - return node; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - private TreeNode? getInOrderNext(TreeNode? node) - { - if (node == null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left != null) - { - node = node.left; - } - return node; - } - - /* 查找结点 */ - public TreeNode? search(int val) - { - TreeNode? cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 目标结点在 root 的右子树中 - if (cur.val < val) - cur = cur.right; - // 目标结点在 root 的左子树中 - else if (cur.val > val) - cur = cur.left; - // 找到目标结点,跳出循环 - else - break; - } - // 返回目标结点 - return cur; } + UpdateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Rotate(node); + // 返回子树的根节点 + return node; } - public class avl_tree - { - static void testInsert(AVLTree tree, int val) - { - tree.insert(val); - Console.WriteLine("\n插入结点 " + val + " 后,AVL 树为"); - PrintUtil.PrintTree(tree.root); - } - - static void testRemove(AVLTree tree, int val) - { - tree.remove(val); - Console.WriteLine("\n删除结点 " + val + " 后,AVL 树为"); - PrintUtil.PrintTree(tree.root); + /* 查找节点 */ + public TreeNode? Search(int val) { + TreeNode? cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.val < val) + cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > val) + cur = cur.left; + // 找到目标节点,跳出循环 + else + break; } + // 返回目标节点 + return cur; + } +} - [Test] - public void Test() - { - /* 初始化空 AVL 树 */ - AVLTree avlTree = new AVLTree(); - - /* 插入结点 */ - // 请关注插入结点后,AVL 树是如何保持平衡的 - testInsert(avlTree, 1); - testInsert(avlTree, 2); - testInsert(avlTree, 3); - testInsert(avlTree, 4); - testInsert(avlTree, 5); - testInsert(avlTree, 8); - testInsert(avlTree, 7); - testInsert(avlTree, 9); - testInsert(avlTree, 10); - testInsert(avlTree, 6); - - /* 插入重复结点 */ - testInsert(avlTree, 7); +public class avl_tree { + static void TestInsert(AVLTree tree, int val) { + tree.Insert(val); + Console.WriteLine("\n插入节点 " + val + " 后,AVL 树为"); + PrintUtil.PrintTree(tree.root); + } - /* 删除结点 */ - // 请关注删除结点后,AVL 树是如何保持平衡的 - testRemove(avlTree, 8); // 删除度为 0 的结点 - testRemove(avlTree, 5); // 删除度为 1 的结点 - testRemove(avlTree, 4); // 删除度为 2 的结点 + static void TestRemove(AVLTree tree, int val) { + tree.Remove(val); + Console.WriteLine("\n删除节点 " + val + " 后,AVL 树为"); + PrintUtil.PrintTree(tree.root); + } - /* 查询结点 */ - TreeNode? node = avlTree.search(7); - Console.WriteLine("\n查找到的结点对象为 " + node + ",结点值 = " + node?.val); - } + [Test] + public void Test() { + /* 初始化空 AVL 树 */ + AVLTree avlTree = new(); + + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + TestInsert(avlTree, 1); + TestInsert(avlTree, 2); + TestInsert(avlTree, 3); + TestInsert(avlTree, 4); + TestInsert(avlTree, 5); + TestInsert(avlTree, 8); + TestInsert(avlTree, 7); + TestInsert(avlTree, 9); + TestInsert(avlTree, 10); + TestInsert(avlTree, 6); + + /* 插入重复节点 */ + TestInsert(avlTree, 7); + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + TestRemove(avlTree, 8); // 删除度为 0 的节点 + TestRemove(avlTree, 5); // 删除度为 1 的节点 + TestRemove(avlTree, 4); // 删除度为 2 的节点 + + /* 查询节点 */ + TreeNode? node = avlTree.Search(7); + Console.WriteLine("\n查找到的节点对象为 " + node + ",节点值 = " + node?.val); } } diff --git a/codes/csharp/chapter_tree/binary_search_tree.cs b/codes/csharp/chapter_tree/binary_search_tree.cs index e12cdd425a..aa67605b84 100644 --- a/codes/csharp/chapter_tree/binary_search_tree.cs +++ b/codes/csharp/chapter_tree/binary_search_tree.cs @@ -4,179 +4,157 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_tree; -namespace hello_algo.chapter_tree -{ - class BinarySearchTree - { - TreeNode? root; +class BinarySearchTree { + TreeNode? root; - public BinarySearchTree(int[] nums) { - Array.Sort(nums); // 排序数组 - root = buildTree(nums, 0, nums.Length - 1); // 构建二叉搜索树 - } + public BinarySearchTree() { + // 初始化空树 + root = null; + } - /* 获取二叉树根结点 */ - public TreeNode? getRoot() { - return root; - } + /* 获取二叉树根节点 */ + public TreeNode? GetRoot() { + return root; + } - /* 构建二叉搜索树 */ - public TreeNode? buildTree(int[] nums, int i, int j) { - if (i > j) return null; - // 将数组中间结点作为根结点 - int mid = (i + j) / 2; - TreeNode root = new TreeNode(nums[mid]); - // 递归建立左子树和右子树 - root.left = buildTree(nums, i, mid - 1); - root.right = buildTree(nums, mid + 1, j); - return root; + /* 查找节点 */ + public TreeNode? Search(int num) { + TreeNode? cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.val < num) cur = + cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) + cur = cur.left; + // 找到目标节点,跳出循环 + else + break; } + // 返回目标节点 + return cur; + } - /* 查找结点 */ - public TreeNode? search(int num) - { - TreeNode? cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 目标结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 root 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; + /* 插入节点 */ + public void Insert(int num) { + // 若树为空,则初始化根节点 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode? cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; } - /* 插入结点 */ - public TreeNode? insert(int num) - { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 找到重复结点,直接返回 - if (cur.val == num) return null; - pre = cur; - // 插入位置在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 root 的左子树中 - else cur = cur.left; - } - - // 插入结点 val - TreeNode node = new TreeNode(num); - if (pre != null) - { - if (pre.val < num) pre.right = node; - else pre.left = node; - } - return node; + // 插入节点 + TreeNode node = new(num); + if (pre != null) { + if (pre.val < num) + pre.right = node; + else + pre.left = node; } + } - /* 删除结点 */ - public TreeNode? remove(int num) - { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 找到待删除结点,跳出循环 - if (cur.val == num) break; - pre = cur; - // 待删除结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 root 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur == null || pre == null) return null; - // 子结点数量 = 0 or 1 - if (cur.left == null || cur.right == null) - { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - TreeNode? child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left == cur) - { + /* 删除节点 */ + public void Remove(int num) { + // 若树为空,直接提前返回 + if (root == null) + return; + TreeNode? cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.val == num) + break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 待删除节点在 cur 的左子树中 + else + cur = cur.left; + } + // 若无待删除节点,则直接返回 + if (cur == null) + return; + // 子节点数量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + TreeNode? child = cur.left ?? cur.right; + // 删除节点 cur + if (cur != root) { + if (pre!.left == cur) pre.left = child; - } else - { pre.right = child; - } - - } - // 子结点数量 = 2 - else - { - // 获取中序遍历中 cur 的下一个结点 - TreeNode? nex = getInOrderNext(cur.right); - if (nex != null) - { - int tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; } - return cur; } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - private TreeNode? getInOrderNext(TreeNode? root) - { - if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left != null) - { - root = root.left; + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + TreeNode? tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; } - return root; + // 递归删除节点 tmp + Remove(tmp.val!.Value); + // 用 tmp 覆盖 cur + cur.val = tmp.val; } } +} - public class binary_search_tree - { - [Test] - public void Test() - { - /* 初始化二叉搜索树 */ - int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - BinarySearchTree bst = new BinarySearchTree(nums); - Console.WriteLine("\n初始化的二叉树为\n"); - PrintUtil.PrintTree(bst.getRoot()); - - /* 查找结点 */ - TreeNode? node = bst.search(7); - Console.WriteLine("\n查找到的结点对象为 " + node + ",结点值 = " + node.val); - - /* 插入结点 */ - node = bst.insert(16); - Console.WriteLine("\n插入结点 16 后,二叉树为\n"); - PrintUtil.PrintTree(bst.getRoot()); - - /* 删除结点 */ - bst.remove(1); - Console.WriteLine("\n删除结点 1 后,二叉树为\n"); - PrintUtil.PrintTree(bst.getRoot()); - bst.remove(2); - Console.WriteLine("\n删除结点 2 后,二叉树为\n"); - PrintUtil.PrintTree(bst.getRoot()); - bst.remove(4); - Console.WriteLine("\n删除结点 4 后,二叉树为\n"); - PrintUtil.PrintTree(bst.getRoot()); +public class binary_search_tree { + [Test] + public void Test() { + /* 初始化二叉搜索树 */ + BinarySearchTree bst = new(); + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + foreach (int num in nums) { + bst.Insert(num); } + + Console.WriteLine("\n初始化的二叉树为\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* 查找节点 */ + TreeNode? node = bst.Search(7); + Console.WriteLine("\n查找到的节点对象为 " + node + ",节点值 = " + node?.val); + + /* 插入节点 */ + bst.Insert(16); + Console.WriteLine("\n插入节点 16 后,二叉树为\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* 删除节点 */ + bst.Remove(1); + Console.WriteLine("\n删除节点 1 后,二叉树为\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(2); + Console.WriteLine("\n删除节点 2 后,二叉树为\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(4); + Console.WriteLine("\n删除节点 4 后,二叉树为\n"); + PrintUtil.PrintTree(bst.GetRoot()); } } diff --git a/codes/csharp/chapter_tree/binary_tree.cs b/codes/csharp/chapter_tree/binary_tree.cs index 275a69f934..08956de18a 100644 --- a/codes/csharp/chapter_tree/binary_tree.cs +++ b/codes/csharp/chapter_tree/binary_tree.cs @@ -4,43 +4,36 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_tree; -namespace hello_algo.chapter_tree -{ +public class binary_tree { + [Test] + public void Test() { + /* 初始化二叉树 */ + // 初始化节点 + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // 构建节点之间的引用(指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + Console.WriteLine("\n初始化二叉树\n"); + PrintUtil.PrintTree(n1); - public class binary_tree - { - [Test] - public void Test() - { - /* 初始化二叉树 */ - // 初始化结点 - TreeNode n1 = new TreeNode(1); - TreeNode n2 = new TreeNode(2); - TreeNode n3 = new TreeNode(3); - TreeNode n4 = new TreeNode(4); - TreeNode n5 = new TreeNode(5); - // 构建引用指向(即指针) - n1.left = n2; - n1.right = n3; - n2.left = n4; - n2.right = n5; - Console.WriteLine("\n初始化二叉树\n"); - PrintUtil.PrintTree(n1); - - /* 插入与删除结点 */ - TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1.left = P; - P.left = n2; - Console.WriteLine("\n插入结点 P 后\n"); - PrintUtil.PrintTree(n1); - // 删除结点 P - n1.left = n2; - Console.WriteLine("\n删除结点 P 后\n"); - PrintUtil.PrintTree(n1); - } + /* 插入与删除节点 */ + TreeNode P = new(0); + // 在 n1 -> n2 中间插入节点 P + n1.left = P; + P.left = n2; + Console.WriteLine("\n插入节点 P 后\n"); + PrintUtil.PrintTree(n1); + // 删除节点 P + n1.left = n2; + Console.WriteLine("\n删除节点 P 后\n"); + PrintUtil.PrintTree(n1); } -} \ No newline at end of file +} diff --git a/codes/csharp/chapter_tree/binary_tree_bfs.cs b/codes/csharp/chapter_tree/binary_tree_bfs.cs index 9a57dbc170..da1509802f 100644 --- a/codes/csharp/chapter_tree/binary_tree_bfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -4,45 +4,37 @@ * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_tree; -namespace hello_algo.chapter_tree -{ - public class binary_tree_bfs - { +public class binary_tree_bfs { - /* 层序遍历 */ - public List hierOrder(TreeNode root) - { - // 初始化队列,加入根结点 - Queue queue = new(); - queue.Enqueue(root); - // 初始化一个列表,用于保存遍历序列 - List list = new(); - while (queue.Count != 0) - { - TreeNode node = queue.Dequeue(); // 队列出队 - list.Add(node.val); // 保存结点值 - if (node.left != null) - queue.Enqueue(node.left); // 左子结点入队 - if (node.right != null) - queue.Enqueue(node.right); // 右子结点入队 - } - return list; + /* 层序遍历 */ + List LevelOrder(TreeNode root) { + // 初始化队列,加入根节点 + Queue queue = new(); + queue.Enqueue(root); + // 初始化一个列表,用于保存遍历序列 + List list = []; + while (queue.Count != 0) { + TreeNode node = queue.Dequeue(); // 队列出队 + list.Add(node.val!.Value); // 保存节点值 + if (node.left != null) + queue.Enqueue(node.left); // 左子节点入队 + if (node.right != null) + queue.Enqueue(node.right); // 右子节点入队 } + return list; + } - [Test] - public void Test() - { - /* 初始化二叉树 */ - // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 }); - Console.WriteLine("\n初始化二叉树\n"); - PrintUtil.PrintTree(root); + [Test] + public void Test() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二叉树\n"); + PrintUtil.PrintTree(root); - List list = hierOrder(root); - Console.WriteLine("\n层序遍历的结点打印序列 = " + string.Join(",", list.ToArray())); - } + List list = LevelOrder(root!); + Console.WriteLine("\n层序遍历的节点打印序列 = " + string.Join(",", list)); } } diff --git a/codes/csharp/chapter_tree/binary_tree_dfs.cs b/codes/csharp/chapter_tree/binary_tree_dfs.cs index f8669d7825..4c1a3d50a0 100644 --- a/codes/csharp/chapter_tree/binary_tree_dfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -1,68 +1,59 @@ /** - * File: binary_tree_bfs.cs + * File: binary_tree_dfs.cs * Created Time: 2022-12-23 * Author: haptear (haptear@hotmail.com) */ -using hello_algo.include; -using NUnit.Framework; +namespace hello_algo.chapter_tree; -namespace hello_algo.chapter_tree -{ - public class binary_tree_dfs - { - List list = new(); +public class binary_tree_dfs { + List list = []; - /* 前序遍历 */ - void preOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.Add(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - void inOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.Add(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - void postOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.Add(root.val); - } - - [Test] - public void Test() - { - /* 初始化二叉树 */ - // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 }); - Console.WriteLine("\n初始化二叉树\n"); - PrintUtil.PrintTree(root); + /* 前序遍历 */ + void PreOrder(TreeNode? root) { + if (root == null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.Add(root.val!.Value); + PreOrder(root.left); + PreOrder(root.right); + } - list.Clear(); - preOrder(root); - Console.WriteLine("\n前序遍历的结点打印序列 = " + string.Join(",", list.ToArray())); + /* 中序遍历 */ + void InOrder(TreeNode? root) { + if (root == null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + InOrder(root.left); + list.Add(root.val!.Value); + InOrder(root.right); + } - list.Clear(); - inOrder(root); - Console.WriteLine("\n中序遍历的结点打印序列 = " + string.Join(",", list.ToArray())); + /* 后序遍历 */ + void PostOrder(TreeNode? root) { + if (root == null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + PostOrder(root.left); + PostOrder(root.right); + list.Add(root.val!.Value); + } - list.Clear(); - postOrder(root); - Console.WriteLine("\n后序遍历的结点打印序列 = " + string.Join(",", list.ToArray())); - } + [Test] + public void Test() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二叉树\n"); + PrintUtil.PrintTree(root); + + list.Clear(); + PreOrder(root); + Console.WriteLine("\n前序遍历的节点打印序列 = " + string.Join(",", list)); + + list.Clear(); + InOrder(root); + Console.WriteLine("\n中序遍历的节点打印序列 = " + string.Join(",", list)); + + list.Clear(); + PostOrder(root); + Console.WriteLine("\n后序遍历的节点打印序列 = " + string.Join(",", list)); } } diff --git a/codes/csharp/csharp.sln b/codes/csharp/csharp.sln new file mode 100644 index 0000000000..0c74ccc535 --- /dev/null +++ b/codes/csharp/csharp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} + EndGlobalSection +EndGlobal diff --git a/codes/csharp/hello-algo.csproj b/codes/csharp/hello-algo.csproj index d96d728b17..43817cc382 100644 --- a/codes/csharp/hello-algo.csproj +++ b/codes/csharp/hello-algo.csproj @@ -2,17 +2,20 @@ Exe - net6.0 + net8.0 hello_algo enable enable - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/codes/csharp/include/ListNode.cs b/codes/csharp/include/ListNode.cs deleted file mode 100644 index 0fe2913eb1..0000000000 --- a/codes/csharp/include/ListNode.cs +++ /dev/null @@ -1,68 +0,0 @@ -// File: ListNode.cs -// Created Time: 2022-12-16 -// Author: mingXta (1195669834@qq.com) - -namespace hello_algo.include -{ - /// - /// Definition for a singly-linked list node - /// - public class ListNode - { - public int val; - public ListNode? next; - - /// - /// Generate a linked list with an array - /// - /// - public ListNode(int x) - { - val = x; - } - - /// - /// Generate a linked list with an array - /// - /// - /// - public static ListNode? ArrToLinkedList(int[] arr) - { - ListNode dum = new ListNode(0); - ListNode head = dum; - foreach (int val in arr) - { - head.next = new ListNode(val); - head = head.next; - } - return dum.next; - } - - /// - /// Get a list node with specific value from a linked list - /// - /// - /// - /// - public static ListNode? GetListNode(ListNode? head, int val) - { - while (head != null && head.val != val) - { - head = head.next; - } - return head; - } - - public override string? ToString() - { - List list = new(); - var head = this; - while (head != null) - { - list.Add(head.val.ToString()); - head = head.next; - } - return string.Join("->", list); - } - } -} \ No newline at end of file diff --git a/codes/csharp/include/PrintUtil.cs b/codes/csharp/include/PrintUtil.cs deleted file mode 100644 index 06e85a93c5..0000000000 --- a/codes/csharp/include/PrintUtil.cs +++ /dev/null @@ -1,124 +0,0 @@ -/** - * File: PrintUtil.cs - * Created Time: 2022-12-23 - * Author: haptear (haptear@hotmail.com) - */ - -namespace hello_algo.include -{ - public class Trunk - { - public Trunk? prev; - public String str; - - public Trunk(Trunk? prev, String str) - { - this.prev = prev; - this.str = str; - } - }; - - public class PrintUtil - { - /** - * Print a linked list - * @param head - */ - public static void PrintLinkedList(ListNode head) - { - List list = new(); - while (head != null) - { - list.Add(head.val.ToString()); - head = head.next; - } - Console.Write(String.Join(" -> ", list)); - } - - /** - * The interface of the tree printer - * This tree printer is borrowed from TECHIE DELIGHT - * https://www.techiedelight.com/c-program-print-binary-tree/ - * @param root - */ - public static void PrintTree(TreeNode? root) - { - PrintTree(root, null, false); - } - - /** - * Print a binary tree - * @param root - * @param prev - * @param isLeft - */ - public static void PrintTree(TreeNode? root, Trunk? prev, bool isLeft) - { - if (root == null) - { - return; - } - - String prev_str = " "; - Trunk trunk = new Trunk(prev, prev_str); - - PrintTree(root.right, trunk, true); - - if (prev == null) - { - trunk.str = "———"; - } - else if (isLeft) - { - trunk.str = "/———"; - prev_str = " |"; - } - else - { - trunk.str = "\\———"; - prev.str = prev_str; - } - - showTrunks(trunk); - Console.WriteLine(" " + root.val); - - if (prev != null) - { - prev.str = prev_str; - } - trunk.str = " |"; - - PrintTree(root.left, trunk, false); - } - - /** - * Helper function to print branches of the binary tree - * @param p - */ - public static void showTrunks(Trunk? p) - { - if (p == null) - { - return; - } - - showTrunks(p.prev); - Console.Write(p.str); - } - - /** - * Print a hash map - * @param - * @param - * @param map - */ - public static void printHashMap(Dictionary map) where K : notnull - { - foreach (var kv in map.Keys) - { - Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); - } - } - } - -} diff --git a/codes/csharp/include/TreeNode.cs b/codes/csharp/include/TreeNode.cs deleted file mode 100644 index 9cd0ac92c3..0000000000 --- a/codes/csharp/include/TreeNode.cs +++ /dev/null @@ -1,98 +0,0 @@ -/** - * File: TreeNode.cs - * Created Time: 2022-12-23 - * Author: haptear (haptear@hotmail.com) - */ - -namespace hello_algo.include -{ - public class TreeNode - { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode? left; // 左子结点引用 - public TreeNode? right; // 右子结点引用 - - public TreeNode(int x) - { - val = x; - } - - /** - * Generate a binary tree given an array - * @param arr - * @return - */ - public static TreeNode? ArrToTree(int?[] arr) - { - if (arr.Length == 0 || arr[0] == null) - return null; - - TreeNode root = new TreeNode((int) arr[0]); - Queue queue = new Queue(); - queue.Enqueue(root); - int i = 0; - while (queue.Count != 0) - { - TreeNode node = queue.Dequeue(); - if (++i >= arr.Length) break; - if (arr[i] != null) - { - node.left = new TreeNode((int) arr[i]); - queue.Enqueue(node.left); - } - if (++i >= arr.Length) break; - if (arr[i] != null) - { - node.right = new TreeNode((int) arr[i]); - queue.Enqueue(node.right); - } - } - return root; - } - - /** - * Serialize a binary tree to a list - * @param root - * @return - */ - public static List TreeToList(TreeNode root) - { - List list = new(); - if (root == null) return list; - Queue queue = new(); - while (queue.Count != 0) - { - TreeNode? node = queue.Dequeue(); - if (node != null) - { - list.Add(node.val); - queue.Enqueue(node.left); - queue.Enqueue(node.right); - } - else - { - list.Add(null); - } - } - return list; - } - - /** - * Get a tree node with specific value in a binary tree - * @param root - * @param val - * @return - */ - public static TreeNode? GetTreeNode(TreeNode? root, int val) - { - if (root == null) - return null; - if (root.val == val) - return root; - TreeNode? left = GetTreeNode(root.left, val); - TreeNode? right = GetTreeNode(root.right, val); - return left != null ? left : right; - } - } -} diff --git a/codes/csharp/utils/ListNode.cs b/codes/csharp/utils/ListNode.cs new file mode 100644 index 0000000000..3205b49ed0 --- /dev/null +++ b/codes/csharp/utils/ListNode.cs @@ -0,0 +1,32 @@ +// File: ListNode.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.utils; + +/* 链表节点 */ +public class ListNode(int x) { + public int val = x; + public ListNode? next; + + /* 将数组反序列化为链表 */ + public static ListNode? ArrToLinkedList(int[] arr) { + ListNode dum = new(0); + ListNode head = dum; + foreach (int val in arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } + + public override string? ToString() { + List list = []; + var head = this; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + return string.Join("->", list); + } +} diff --git a/codes/csharp/utils/PrintUtil.cs b/codes/csharp/utils/PrintUtil.cs new file mode 100644 index 0000000000..b91f013251 --- /dev/null +++ b/codes/csharp/utils/PrintUtil.cs @@ -0,0 +1,132 @@ +/** +* File: PrintUtil.cs +* Created Time: 2022-12-23 +* Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) +*/ + +namespace hello_algo.utils; + +public class Trunk(Trunk? prev, string str) { + public Trunk? prev = prev; + public string str = str; +}; + +public static class PrintUtil { + /* 打印列表 */ + public static void PrintList(IList list) { + Console.WriteLine("[" + string.Join(", ", list) + "]"); + } + + public static string PrintList(this IEnumerable list) { + return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; + } + + /* 打印矩阵 (Array) */ + public static void PrintMatrix(T[][] matrix) { + Console.WriteLine("["); + foreach (T[] row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 打印矩阵 (List) */ + public static void PrintMatrix(List> matrix) { + Console.WriteLine("["); + foreach (List row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 打印链表 */ + public static void PrintLinkedList(ListNode? head) { + List list = []; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + Console.Write(string.Join(" -> ", list)); + } + + /** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void PrintTree(TreeNode? root) { + PrintTree(root, null, false); + } + + /* 打印二叉树 */ + public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { + if (root == null) { + return; + } + + string prev_str = " "; + Trunk trunk = new(prev, prev_str); + + PrintTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + ShowTrunks(trunk); + Console.WriteLine(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + PrintTree(root.left, trunk, false); + } + + public static void ShowTrunks(Trunk? p) { + if (p == null) { + return; + } + + ShowTrunks(p.prev); + Console.Write(p.str); + } + + /* 打印哈希表 */ + public static void PrintHashMap(Dictionary map) where K : notnull { + foreach (var kv in map.Keys) { + Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); + } + } + + /* 打印堆 */ + public static void PrintHeap(Queue queue) { + Console.Write("堆的数组表示:"); + List list = [.. queue]; + Console.WriteLine(string.Join(',', list)); + Console.WriteLine("堆的树状表示:"); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } + + /* 打印优先队列 */ + public static void PrintHeap(PriorityQueue queue) { + var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); + Console.Write("堆的数组表示:"); + List list = []; + while (newQueue.TryDequeue(out int element, out _)) { + list.Add(element); + } + Console.WriteLine("堆的树状表示:"); + Console.WriteLine(string.Join(',', list.ToList())); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } +} \ No newline at end of file diff --git a/codes/csharp/utils/TreeNode.cs b/codes/csharp/utils/TreeNode.cs new file mode 100644 index 0000000000..6d63dfb110 --- /dev/null +++ b/codes/csharp/utils/TreeNode.cs @@ -0,0 +1,67 @@ +/** + * File: TreeNode.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.utils; + +/* 二叉树节点类 */ +public class TreeNode(int? x) { + public int? val = x; // 节点值 + public int height; // 节点高度 + public TreeNode? left; // 左子节点引用 + public TreeNode? right; // 右子节点引用 + + // 序列化编码规则请参考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二叉树的数组表示: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二叉树的链表表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 将列表反序列化为二叉树:递归 */ + static TreeNode? ListToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.Count || !arr[i].HasValue) { + return null; + } + TreeNode root = new(arr[i]) { + left = ListToTreeDFS(arr, 2 * i + 1), + right = ListToTreeDFS(arr, 2 * i + 2) + }; + return root; + } + + /* 将列表反序列化为二叉树 */ + public static TreeNode? ListToTree(List arr) { + return ListToTreeDFS(arr, 0); + } + + /* 将二叉树序列化为列表:递归 */ + static void TreeToListDFS(TreeNode? root, int i, List res) { + if (root == null) + return; + while (i >= res.Count) { + res.Add(null); + } + res[i] = root.val; + TreeToListDFS(root.left, 2 * i + 1, res); + TreeToListDFS(root.right, 2 * i + 2, res); + } + + /* 将二叉树序列化为列表 */ + public static List TreeToList(TreeNode root) { + List res = []; + TreeToListDFS(root, 0, res); + return res; + } +} diff --git a/codes/csharp/utils/Vertex.cs b/codes/csharp/utils/Vertex.cs new file mode 100644 index 0000000000..b9672ef64d --- /dev/null +++ b/codes/csharp/utils/Vertex.cs @@ -0,0 +1,30 @@ +/** + * File: Vertex.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) + */ + +namespace hello_algo.utils; + +/* 顶点类 */ +public class Vertex(int val) { + public int val = val; + + /* 输入值列表 vals ,返回顶点列表 vets */ + public static Vertex[] ValsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.Length]; + for (int i = 0; i < vals.Length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 输入顶点列表 vets ,返回值列表 vals */ + public static List VetsToVals(List vets) { + List vals = []; + foreach (Vertex vet in vets) { + vals.Add(vet.val); + } + return vals; + } +} diff --git a/codes/dart/build.dart b/codes/dart/build.dart new file mode 100644 index 0000000000..7bd5b51a14 --- /dev/null +++ b/codes/dart/build.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +void main() { + Directory foldPath = Directory('codes/dart/'); + List files = foldPath.listSync(); + int totalCount = 0; + int errorCount = 0; + for (var file in files) { + if (file.path.endsWith('build.dart')) continue; + if (file is File && file.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [file.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } else if (file is Directory) { + List subFiles = file.listSync(); + for (var subFile in subFiles) { + if (subFile is File && subFile.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [subFile.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } + } + } + } + + print('===== Build Complete ====='); + print('Total: $totalCount'); + print('Error: $errorCount'); +} diff --git a/codes/dart/chapter_array_and_linkedlist/array.dart b/codes/dart/chapter_array_and_linkedlist/array.dart new file mode 100644 index 0000000000..60e0652234 --- /dev/null +++ b/codes/dart/chapter_array_and_linkedlist/array.dart @@ -0,0 +1,105 @@ +/** + * File: array.dart + * Created Time: 2023-01-20 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:math'; + +/* 随机访问元素 */ +int randomAccess(List nums) { + // 在区间 [0, nums.length) 中随机抽取一个数字 + int randomIndex = Random().nextInt(nums.length); + // 获取并返回随机元素 + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 扩展数组长度 */ +List extend(List nums, int enlarge) { + // 初始化一个扩展长度后的数组 + List res = List.filled(nums.length + enlarge, 0); + // 将原数组中的所有元素复制到新数组 + for (var i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回扩展后的新数组 + return res; +} + +/* 在数组的索引 index 处插入元素 _num */ +void insert(List nums, int _num, int index) { + // 把索引 index 以及之后的所有元素向后移动一位 + for (var i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 将 _num 赋给 index 处元素 + nums[index] = _num; +} + +/* 删除索引 index 处的元素 */ +void remove(List nums, int index) { + // 把索引 index 之后的所有元素向前移动一位 + for (var i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 遍历数组元素 */ +void traverse(List nums) { + int count = 0; + // 通过索引遍历数组 + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接遍历数组元素 + for (int _num in nums) { + count += _num; + } + // 通过 forEach 方法遍历数组 + nums.forEach((_num) { + count += _num; + }); +} + +/* 在数组中查找指定元素 */ +int find(List nums, int target) { + for (var i = 0; i < nums.length; i++) { + if (nums[i] == target) return i; + } + return -1; +} + +/* Driver Code */ +void main() { + /* 初始化数组 */ + var arr = List.filled(5, 0); + print('数组 arr = $arr'); + List nums = [1, 3, 2, 5, 4]; + print('数组 nums = $nums'); + + /* 随机访问 */ + int randomNum = randomAccess(nums); + print('在 nums 中获取随机元素 $randomNum'); + + /* 长度扩展 */ + nums = extend(nums, 3); + print('将数组长度扩展至 8 ,得到 nums = $nums'); + + /* 插入元素 */ + insert(nums, 6, 3); + print("在索引 3 处插入数字 6 ,得到 nums = $nums"); + + /* 删除元素 */ + remove(nums, 2); + print("删除索引 2 处的元素,得到 nums = $nums"); + + /* 遍历数组 */ + traverse(nums); + + /* 查找元素 */ + int index = find(nums, 3); + print("在 nums 中查找元素 3 ,得到索引 = $index"); +} diff --git a/codes/dart/chapter_array_and_linkedlist/linked_list.dart b/codes/dart/chapter_array_and_linkedlist/linked_list.dart new file mode 100644 index 0000000000..fc304b363f --- /dev/null +++ b/codes/dart/chapter_array_and_linkedlist/linked_list.dart @@ -0,0 +1,83 @@ +/** + * File: linked_list.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; + +/* 在链表的节点 n0 之后插入节点 P */ +void insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 删除链表的节点 n0 之后的首个节点 */ +void remove(ListNode n0) { + if (n0.next == null) return; + // n0 -> P -> n1 + ListNode P = n0.next!; + ListNode? n1 = P.next; + n0.next = n1; +} + +/* 访问链表中索引为 index 的节点 */ +ListNode? access(ListNode? head, int index) { + for (var 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 != null) { + if (head.val == target) { + return index; + } + head = head.next; + index++; + } + return -1; +} + +/* Driver Code */ +void main() { + // 初始化链表 + // 初始化各个节点 + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // 构建节点之间的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + + print('初始化的链表为'); + printLinkedList(n0); + + /* 插入节点 */ + insert(n0, ListNode(0)); + print('插入节点后的链表为'); + printLinkedList(n0); + + /* 删除节点 */ + remove(n0); + print('删除节点后的链表为'); + printLinkedList(n0); + + /* 访问节点 */ + ListNode? node = access(n0, 3); + print('链表中索引 3 处的节点的值 = ${node!.val}'); + + /* 查找节点 */ + int index = find(n0, 2); + print('链表中值为 2 的节点的索引 = $index'); +} diff --git a/codes/dart/chapter_array_and_linkedlist/list.dart b/codes/dart/chapter_array_and_linkedlist/list.dart new file mode 100644 index 0000000000..bf80cee12e --- /dev/null +++ b/codes/dart/chapter_array_and_linkedlist/list.dart @@ -0,0 +1,62 @@ +/** + * File: list.dart + * Created Time: 2023-01-24 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* Driver Code */ +void main() { + /* 初始化列表 */ + List nums = [1, 3, 2, 5, 4]; + print('列表 nums = $nums'); + + /* 访问元素 */ + int _num = nums[1]; + print('访问索引 1 处的元素,得到 _num = $_num'); + + /* 更新元素 */ + nums[1] = 0; + print('将索引 1 处的元素更新为 0 ,得到 nums = $nums'); + + /* 清空列表 */ + nums.clear(); + print('清空列表后 nums = $nums'); + + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print('添加元素后 nums = $nums'); + + /* 在中间插入元素 */ + nums.insert(3, 6); + print('在索引 3 处插入数字 6 ,得到 nums = $nums'); + + /* 删除元素 */ + nums.removeAt(3); + print('删除索引 3 处的元素,得到 nums = $nums'); + + /* 通过索引遍历列表 */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + /* 直接遍历列表元素 */ + count = 0; + for (var x in nums) { + count += x; + } + + /* 拼接两个列表 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); + print('将列表 nums1 拼接到 nums 之后,得到 nums = $nums'); + + /* 排序列表 */ + nums.sort(); + print('排序列表后 nums = $nums'); +} diff --git a/codes/dart/chapter_array_and_linkedlist/my_list.dart b/codes/dart/chapter_array_and_linkedlist/my_list.dart new file mode 100644 index 0000000000..7bfa846937 --- /dev/null +++ b/codes/dart/chapter_array_and_linkedlist/my_list.dart @@ -0,0 +1,132 @@ +/** + * File: my_list.dart + * Created Time: 2023-02-05 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 列表类 */ +class MyList { + late List _arr; // 数组(存储列表元素) + int _capacity = 10; // 列表容量 + int _size = 0; // 列表长度(当前元素数量) + int _extendRatio = 2; // 每次列表扩容的倍数 + + /* 构造方法 */ + MyList() { + _arr = List.filled(_capacity, 0); + } + + /* 获取列表长度(当前元素数量)*/ + int size() => _size; + + /* 获取列表容量 */ + int capacity() => _capacity; + + /* 访问元素 */ + int get(int index) { + if (index >= _size) throw RangeError('索引越界'); + return _arr[index]; + } + + /* 更新元素 */ + void set(int index, int _num) { + if (index >= _size) throw RangeError('索引越界'); + _arr[index] = _num; + } + + /* 在尾部添加元素 */ + void add(int _num) { + // 元素数量超出容量时,触发扩容机制 + if (_size == _capacity) extendCapacity(); + _arr[_size] = _num; + // 更新元素数量 + _size++; + } + + /* 在中间插入元素 */ + void insert(int index, int _num) { + if (index >= _size) throw RangeError('索引越界'); + // 元素数量超出容量时,触发扩容机制 + if (_size == _capacity) extendCapacity(); + // 将索引 index 以及之后的元素都向后移动一位 + for (var j = _size - 1; j >= index; j--) { + _arr[j + 1] = _arr[j]; + } + _arr[index] = _num; + // 更新元素数量 + _size++; + } + + /* 删除元素 */ + int remove(int index) { + if (index >= _size) throw RangeError('索引越界'); + int _num = _arr[index]; + // 将将索引 index 之后的元素都向前移动一位 + for (var j = index; j < _size - 1; j++) { + _arr[j] = _arr[j + 1]; + } + // 更新元素数量 + _size--; + // 返回被删除的元素 + return _num; + } + + /* 列表扩容 */ + void extendCapacity() { + // 新建一个长度为原数组 _extendRatio 倍的新数组 + final _newNums = List.filled(_capacity * _extendRatio, 0); + // 将原数组复制到新数组 + List.copyRange(_newNums, 0, _arr); + // 更新 _arr 的引用 + _arr = _newNums; + // 更新列表容量 + _capacity = _arr.length; + } + + /* 将列表转换为数组 */ + List toArray() { + List arr = []; + for (var i = 0; i < _size; i++) { + arr.add(get(i)); + } + return arr; + } +} + +/* Driver Code */ +void main() { + /* 初始化列表 */ + MyList nums = MyList(); + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print( + '列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}'); + + /* 在中间插入元素 */ + nums.insert(3, 6); + print('在索引 3 处插入数字 6 ,得到 nums = ${nums.toArray()}'); + + /* 删除元素 */ + nums.remove(3); + print('删除索引 3 处的元素,得到 nums = ${nums.toArray()}'); + + /* 访问元素 */ + int _num = nums.get(1); + print('访问索引 1 处的元素,得到 _num = $_num'); + + /* 更新元素 */ + nums.set(1, 0); + print('将索引 1 处的元素更新为 0 ,得到 nums = ${nums.toArray()}'); + + /* 测试扩容机制 */ + for (var i = 0; i < 10; i++) { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + nums.add(i); + } + print( + '扩容后的列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}'); +} diff --git a/codes/dart/chapter_backtracking/n_queens.dart b/codes/dart/chapter_backtracking/n_queens.dart new file mode 100644 index 0000000000..6d4aa777d8 --- /dev/null +++ b/codes/dart/chapter_backtracking/n_queens.dart @@ -0,0 +1,75 @@ +/** + * File: n_queens.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯算法:n 皇后 */ +void backtrack( + int row, + int n, + List> state, + List>> res, + List cols, + List diags1, + List diags2, +) { + // 当放置完所有行时,记录解 + if (row == n) { + List> copyState = []; + for (List sRow in state) { + copyState.add(List.from(sRow)); + } + res.add(copyState); + 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] = true; + diags1[diag1] = true; + diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = "#"; + cols[col] = false; + diags1[diag1] = false; + diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +List>> nQueens(int n) { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + List> state = List.generate(n, (index) => List.filled(n, "#")); + List cols = List.filled(n, false); // 记录列是否有皇后 + List diags1 = List.filled(2 * n - 1, false); // 记录主对角线上是否有皇后 + List diags2 = List.filled(2 * n - 1, false); // 记录次对角线上是否有皇后 + List>> res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +void main() { + int n = 4; + List>> res = nQueens(n); + print("输入棋盘长宽为 $n"); + print("皇后放置方案共有 ${res.length} 种"); + for (List> state in res) { + print("--------------------"); + for (List row in state) { + print(row); + } + } +} diff --git a/codes/dart/chapter_backtracking/permutations_i.dart b/codes/dart/chapter_backtracking/permutations_i.dart new file mode 100644 index 0000000000..2987f32cb5 --- /dev/null +++ b/codes/dart/chapter_backtracking/permutations_i.dart @@ -0,0 +1,51 @@ +/** + * File: permutations_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯算法:全排列 I */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 当状态长度等于元素数量时,记录解 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全排列 I */ +List> permutationsI(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 3]; + + List> res = permutationsI(nums); + + print("输入数组 nums = $nums"); + print("所有排列 res = $res"); +} diff --git a/codes/dart/chapter_backtracking/permutations_ii.dart b/codes/dart/chapter_backtracking/permutations_ii.dart new file mode 100644 index 0000000000..18016cc270 --- /dev/null +++ b/codes/dart/chapter_backtracking/permutations_ii.dart @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯算法:全排列 II */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 当状态长度等于元素数量时,记录解 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // 遍历所有选择 + Set duplicated = {}; + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 尝试:做出选择,更新状态 + duplicated.add(choice); // 记录选择过的元素值 + selected[i] = true; + state.add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全排列 II */ +List> permutationsII(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 2]; + + List> res = permutationsII(nums); + + print("输入数组 nums = $nums"); + print("所有排列 res = $res"); +} diff --git a/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart b/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart new file mode 100644 index 0000000000..b4d76519aa --- /dev/null +++ b/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart @@ -0,0 +1,35 @@ +/** + * File: preorder_traversal_i_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序遍历:例题一 */ +void preOrder(TreeNode? root, List res) { + if (root == null) { + return; + } + if (root.val == 7) { + // 记录解 + res.add(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二叉树"); + printTree(root); + + // 前序遍历 + List res = []; + preOrder(root, res); + + print("\n输出所有值为 7 的节点"); + print(List.generate(res.length, (i) => res[i].val)); +} diff --git a/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart b/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart new file mode 100644 index 0000000000..c3ef81f22b --- /dev/null +++ b/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序遍历:例题二 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null) { + return; + } + + // 尝试 + path.add(root); + if (root.val == 7) { + // 记录解 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二叉树"); + printTree(root); + + // 前序遍历 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\n输出所有根节点到节点 7 的路径"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart b/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart new file mode 100644 index 0000000000..93cc684c9e --- /dev/null +++ b/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序遍历:例题三 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null || root.val == 3) { + return; + } + + // 尝试 + path.add(root); + if (root.val == 7) { + // 记录解 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二叉树"); + printTree(root); + + // 前序遍历 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\n输出所有根节点到节点 7 的路径"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart b/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart new file mode 100644 index 0000000000..a8ca0d8b01 --- /dev/null +++ b/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart @@ -0,0 +1,73 @@ +/** + * File: preorder_traversal_iii_template.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 判断当前状态是否为解 */ +bool isSolution(List state) { + return state.isNotEmpty && state.last.val == 7; +} + +/* 记录解 */ +void recordSolution(List state, List> res) { + res.add(List.from(state)); +} + +/* 判断在当前状态下,该选择是否合法 */ +bool isValid(List state, TreeNode? choice) { + return choice != null && choice.val != 3; +} + +/* 更新状态 */ +void makeChoice(List state, TreeNode? choice) { + state.add(choice!); +} + +/* 恢复状态 */ +void undoChoice(List state, TreeNode? choice) { + state.removeLast(); +} + +/* 回溯算法:例题三 */ +void backtrack( + List state, + List choices, + List> res, +) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + for (TreeNode? choice in choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + backtrack(state, [choice!.left, choice.right], res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二叉树"); + printTree(root); + + // 回溯算法 + List> res = []; + backtrack([], [root!], res); + print("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"); + for (List path in res) { + print(List.from(path.map((e) => e.val))); + } +} diff --git a/codes/dart/chapter_backtracking/subset_sum_i.dart b/codes/dart/chapter_backtracking/subset_sum_i.dart new file mode 100644 index 0000000000..aa71c0f09d --- /dev/null +++ b/codes/dart/chapter_backtracking/subset_sum_i.dart @@ -0,0 +1,56 @@ +/** + * File: subset_sum_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯算法:子集和 I */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(List.from(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.removeLast(); + } +} + +/* 求解子集和 I */ +List> subsetSumI(List nums, int target) { + List state = []; // 状态(子集) + nums.sort(); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = []; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumI(nums, target); + + print("输入数组 nums = $nums, target = $target"); + print("所有和等于 $target 的子集 res = $res"); +} diff --git a/codes/dart/chapter_backtracking/subset_sum_i_naive.dart b/codes/dart/chapter_backtracking/subset_sum_i_naive.dart new file mode 100644 index 0000000000..9f9d75f60c --- /dev/null +++ b/codes/dart/chapter_backtracking/subset_sum_i_naive.dart @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯算法:子集和 I */ +void backtrack( + List state, + int target, + int total, + List choices, + List> res, +) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.add(List.from(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.length; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.removeLast(); + } +} + +/* 求解子集和 I(包含重复子集) */ +List> subsetSumINaive(List nums, int target) { + List state = []; // 状态(子集) + int total = 0; // 元素和 + List> res = []; // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + print("输入数组 nums = $nums, target = $target"); + print("所有和等于 $target 的子集 res = $res"); + print("请注意,该方法输出的结果包含重复集合"); +} diff --git a/codes/dart/chapter_backtracking/subset_sum_ii.dart b/codes/dart/chapter_backtracking/subset_sum_ii.dart new file mode 100644 index 0000000000..877a4a6a5b --- /dev/null +++ b/codes/dart/chapter_backtracking/subset_sum_ii.dart @@ -0,0 +1,61 @@ +/** + * File: subset_sum_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯算法:子集和 II */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(List.from(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.removeLast(); + } +} + +/* 求解子集和 II */ +List> subsetSumII(List nums, int target) { + List state = []; // 状态(子集) + nums.sort(); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = []; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [4, 4, 5]; + int target = 9; + + List> res = subsetSumII(nums, target); + + print("输入数组 nums = $nums, target = $target"); + print("所有和等于 $target 的子集 res = $res"); +} diff --git a/codes/dart/chapter_computational_complexity/iteration.dart b/codes/dart/chapter_computational_complexity/iteration.dart new file mode 100644 index 0000000000..3022b6d57b --- /dev/null +++ b/codes/dart/chapter_computational_complexity/iteration.dart @@ -0,0 +1,72 @@ +/** + * File: iteration.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* for 循环 */ +int forLoop(int n) { + int res = 0; + // 循环求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 循环 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; +} + +/* while 循环(两次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; +} + +/* 双层 for 循环 */ +String nestedForLoop(int n) { + String res = ""; + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res += "($i, $j), "; + } + } + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = forLoop(n); + print("\nfor 循环的求和结果 res = $res"); + + res = whileLoop(n); + print("\nwhile 循环的求和结果 res = $res"); + + res = whileLoopII(n); + print("\nwhile 循环(两次更新)的求和结果 res = $res"); + + String resStr = nestedForLoop(n); + print("\n双层 for 循环的结果 $resStr"); +} diff --git a/codes/dart/chapter_computational_complexity/recursion.dart b/codes/dart/chapter_computational_complexity/recursion.dart new file mode 100644 index 0000000000..e1689c5119 --- /dev/null +++ b/codes/dart/chapter_computational_complexity/recursion.dart @@ -0,0 +1,70 @@ +/** + * File: recursion.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 递归 */ +int recur(int n) { + // 终止条件 + if (n == 1) return 1; + // 递:递归调用 + int res = recur(n - 1); + // 归:返回结果 + return n + res; +} + +/* 使用迭代模拟递归 */ +int forLoopRecur(int n) { + // 使用一个显式的栈来模拟系统调用栈 + List stack = []; + int res = 0; + // 递:递归调用 + for (int i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack.add(i); + } + // 归:返回结果 + while (!stack.isEmpty) { + // 通过“出栈操作”模拟“归” + res += stack.removeLast(); + } + // 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; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = recur(n); + print("\n递归函数的求和结果 res = $res"); + + res = tailRecur(n, 0); + print("\n尾递归函数的求和结果 res = $res"); + + res = forLoopRecur(n); + print("\n使用迭代模拟递归求和结果 res = $res"); + + res = fib(n); + print("\n斐波那契数列的第 $n 项为 $res"); +} diff --git a/codes/dart/chapter_computational_complexity/space_complexity.dart b/codes/dart/chapter_computational_complexity/space_complexity.dart new file mode 100644 index 0000000000..2155ea2ca1 --- /dev/null +++ b/codes/dart/chapter_computational_complexity/space_complexity.dart @@ -0,0 +1,106 @@ +/** + * File: space_complexity.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:collection'; +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 函数 */ +int function() { + // 执行某些操作 + return 0; +} + +/* 常数阶 */ +void constant(int n) { + // 常量、变量、对象占用 O(1) 空间 + final int a = 0; + int b = 0; + List nums = List.filled(10000, 0); + ListNode node = ListNode(0); + // 循环中的变量占用 O(1) 空间 + for (var i = 0; i < n; i++) { + int c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (var i = 0; i < n; i++) { + function(); + } +} + +/* 线性阶 */ +void linear(int n) { + // 长度为 n 的数组占用 O(n) 空间 + List nums = List.filled(n, 0); + // 长度为 n 的列表占用 O(n) 空间 + List nodes = []; + for (var i = 0; i < n; i++) { + nodes.add(ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + Map map = HashMap(); + for (var i = 0; i < n; i++) { + map.putIfAbsent(i, () => i.toString()); + } +} + +/* 线性阶(递归实现) */ +void linearRecur(int n) { + print('递归 n = $n'); + if (n == 1) return; + linearRecur(n - 1); +} + +/* 平方阶 */ +void quadratic(int n) { + // 矩阵占用 O(n^2) 空间 + List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); + // 二维列表占用 O(n^2) 空间 + List> numList = []; + for (var i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } +} + +/* 平方阶(递归实现) */ +int quadraticRecur(int n) { + if (n <= 0) return 0; + List nums = List.filled(n, 0); + print('递归 n = $n 中的 nums 长度 = ${nums.length}'); + return quadraticRecur(n - 1); +} + +/* 指数阶(建立满二叉树) */ +TreeNode? buildTree(int n) { + if (n == 0) return null; + TreeNode root = TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +void main() { + int n = 5; + // 常数阶 + constant(n); + // 线性阶 + linear(n); + linearRecur(n); + // 平方阶 + quadratic(n); + quadraticRecur(n); + // 指数阶 + TreeNode? root = buildTree(n); + printTree(root); +} diff --git a/codes/dart/chapter_computational_complexity/time_complexity.dart b/codes/dart/chapter_computational_complexity/time_complexity.dart new file mode 100644 index 0000000000..3bc3afc4b7 --- /dev/null +++ b/codes/dart/chapter_computational_complexity/time_complexity.dart @@ -0,0 +1,165 @@ +/** + * File: time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* 常数阶 */ +int constant(int n) { + int count = 0; + int size = 100000; + for (var i = 0; i < size; i++) { + count++; + } + return count; +} + +/* 线性阶 */ +int linear(int n) { + int count = 0; + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 线性阶(遍历数组) */ +int arrayTraversal(List nums) { + int count = 0; + // 循环次数与数组长度成正比 + for (var _num in nums) { + count++; + } + return count; +} + +/* 平方阶 */ +int quadratic(int n) { + int count = 0; + // 循环次数与数据大小 n 成平方关系 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方阶(冒泡排序) */ +int bubbleSort(List nums) { + int count = 0; // 计数器 + // 外循环:未排序区间为 [0, i] + for (var i = nums.length - 1; i > 0; i--) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (var j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交换包含 3 个单元操作 + } + } + } + return count; +} + +/* 指数阶(循环实现) */ +int exponential(int n) { + int count = 0, base = 1; + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for (var i = 0; i < n; i++) { + for (var j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指数阶(递归实现) */ +int expRecur(int n) { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 对数阶(循环实现) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n ~/ 2; + count++; + } + return count; +} + +/* 对数阶(递归实现) */ +int logRecur(int n) { + if (n <= 1) return 0; + return logRecur(n ~/ 2) + 1; +} + +/* 线性对数阶 */ +int linearLogRecur(int n) { + if (n <= 1) return 1; + int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 阶乘阶(递归实现) */ +int factorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // 从 1 个分裂出 n 个 + for (var i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +void main() { + // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + int n = 8; + print('输入数据大小 n = $n'); + + int count = constant(n); + print('常数阶的操作数量 = $count'); + + count = linear(n); + print('线性阶的操作数量 = $count'); + + count = arrayTraversal(List.filled(n, 0)); + print('线性阶(遍历数组)的操作数量 = $count'); + + count = quadratic(n); + print('平方阶的操作数量 = $count'); + final nums = List.filled(n, 0); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums); + print('平方阶(冒泡排序)的操作数量 = $count'); + + count = exponential(n); + print('指数阶(循环实现)的操作数量 = $count'); + count = expRecur(n); + print('指数阶(递归实现)的操作数量 = $count'); + + count = logarithmic(n); + print('对数阶(循环实现)的操作数量 = $count'); + count = logRecur(n); + print('对数阶(递归实现)的操作数量 = $count'); + + count = linearLogRecur(n); + print('线性对数阶(递归实现)的操作数量 = $count'); + + count = factorialRecur(n); + print('阶乘阶(递归实现)的操作数量 = $count'); +} diff --git a/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart b/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart new file mode 100644 index 0000000000..541d3e4d9b --- /dev/null +++ b/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ +List randomNumbers(int n) { + final nums = List.filled(n, 0); + // 生成数组 nums = { 1, 2, 3, ..., n } + for (var i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 随机打乱数组元素 + nums.shuffle(); + + return nums; +} + +/* 查找数组 nums 中数字 1 所在索引 */ +int findOne(List nums) { + for (var i = 0; i < nums.length; i++) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if (nums[i] == 1) return i; + } + + return -1; +} + +/* Driver Code */ +void main() { + for (var i = 0; i < 10; i++) { + int n = 100; + final nums = randomNumbers(n); + int index = findOne(nums); + print('\n数组 [ 1, 2, ..., n ] 被打乱后 = $nums'); + print('数字 1 的索引为 + $index'); + } +} diff --git a/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart b/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart new file mode 100644 index 0000000000..d86d9de1bf --- /dev/null +++ b/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart @@ -0,0 +1,42 @@ +/** + * File: binary_search_recur.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分查找:问题 f(i, j) */ +int dfs(List 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(List nums, int target) { + int n = nums.length; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +void main() { + int target = 6; + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分查找(双闭区间) + int index = binarySearch(nums, target); + print("目标元素 6 的索引 = $index"); +} diff --git a/codes/dart/chapter_divide_and_conquer/build_tree.dart b/codes/dart/chapter_divide_and_conquer/build_tree.dart new file mode 100644 index 0000000000..f31cd09068 --- /dev/null +++ b/codes/dart/chapter_divide_and_conquer/build_tree.dart @@ -0,0 +1,55 @@ +/** + * File: build_tree.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 构建二叉树:分治 */ +TreeNode? dfs( + List preorder, + Map inorderMap, + int i, + int l, + int r, +) { + // 子树区间为空时终止 + if (r - l < 0) { + return null; + } + // 初始化根节点 + TreeNode? root = TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = inorderMap[preorder[i]]!; + // 子问题:构建左子树 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; +} + +/* 构建二叉树 */ +TreeNode? buildTree(List preorder, List inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + Map inorderMap = {}; + for (int i = 0; i < inorder.length; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +void main() { + List preorder = [3, 9, 2, 1, 7]; + List inorder = [9, 3, 1, 2, 7]; + print("前序遍历 = $preorder"); + print("中序遍历 = $inorder"); + + TreeNode? root = buildTree(preorder, inorder); + print("构建的二叉树为:"); + printTree(root!); +} diff --git a/codes/dart/chapter_divide_and_conquer/hanota.dart b/codes/dart/chapter_divide_and_conquer/hanota.dart new file mode 100644 index 0000000000..a22a5feaaf --- /dev/null +++ b/codes/dart/chapter_divide_and_conquer/hanota.dart @@ -0,0 +1,54 @@ +/** + * File: hanota.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 移动一个圆盘 */ +void move(List src, List tar) { + // 从 src 顶部拿出一个圆盘 + int pan = src.removeLast(); + // 将圆盘放入 tar 顶部 + tar.add(pan); +} + +/* 求解汉诺塔问题 f(i) */ +void dfs(int i, List src, List buf, List tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解汉诺塔问题 */ +void solveHanota(List A, List B, List C) { + int n = A.length; + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +void main() { + // 列表尾部是柱子顶部 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + print("初始状态下:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); + + solveHanota(A, B, C); + + print("圆盘移动完成后:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); +} diff --git a/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart b/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart new file mode 100644 index 0000000000..198a07c6ef --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_backtrack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯 */ +void backtrack(List choices, int state, int n, List res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) { + res[0]++; + } + // 遍历所有选择 + for (int choice in choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) continue; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬楼梯:回溯 */ +int climbingStairsBacktrack(int n) { + List choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 + int state = 0; // 从第 0 阶开始爬 + List res = []; + res.add(0); // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + print("爬 $n 阶楼梯共有 $res 种方案"); +} diff --git a/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart b/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart new file mode 100644 index 0000000000..0d07c00be1 --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_constraint_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 带约束爬楼梯:动态规划 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + print("爬 $n 阶楼梯共有 $res 种方案"); +} diff --git a/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart b/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart new file mode 100644 index 0000000000..cba4028763 --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart @@ -0,0 +1,27 @@ +/** + * File: climbing_stairs_dfs.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 搜索 */ +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); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFS(n); + print("爬 $n 阶楼梯共有 $res 种方案"); +} diff --git a/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart b/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart new file mode 100644 index 0000000000..0b64b9b27d --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_dfs_mem.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 记忆化搜索 */ +int dfs(int i, List 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 代表无记录 + List mem = List.filled(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + print("爬 $n 阶楼梯共有 $res 种方案"); +} diff --git a/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart b/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart new file mode 100644 index 0000000000..d07d080214 --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart @@ -0,0 +1,43 @@ +/** + * File: climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 爬楼梯:动态规划 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) return n; + // 初始化 dp 表,用于存储子问题的解 + List dp = List.filled(n + 1, 0); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬楼梯:空间优化后的动态规划 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDP(n); + print("爬 $n 阶楼梯共有 $res 种方案"); + + res = climbingStairsDPComp(n); + print("爬 $n 阶楼梯共有 $res 种方案"); +} diff --git a/codes/dart/chapter_dynamic_programming/coin_change.dart b/codes/dart/chapter_dynamic_programming/coin_change.dart new file mode 100644 index 0000000000..3660728136 --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/coin_change.dart @@ -0,0 +1,68 @@ +/** + * File: coin_change.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 零钱兑换:动态规划 */ +int coinChangeDP(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 状态转移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* 零钱兑换:空间优化后的动态规划 */ +int coinChangeDPComp(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + List dp = List.filled(amt + 1, MAX); + dp[0] = 0; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 4; + + // 动态规划 + int res = coinChangeDP(coins, amt); + print("凑到目标金额所需的最少硬币数量为 $res"); + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins, amt); + print("凑到目标金额所需的最少硬币数量为 $res"); +} diff --git a/codes/dart/chapter_dynamic_programming/coin_change_ii.dart b/codes/dart/chapter_dynamic_programming/coin_change_ii.dart new file mode 100644 index 0000000000..e2efb555e2 --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/coin_change_ii.dart @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 零钱兑换 II:动态规划 */ +int coinChangeIIDP(List coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +int coinChangeIIDPComp(List coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + List dp = List.filled(amt + 1, 0); + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 5; + + // 动态规划 + int res = coinChangeIIDP(coins, amt); + print("凑出目标金额的硬币组合数量为 $res"); + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(coins, amt); + print("凑出目标金额的硬币组合数量为 $res"); +} diff --git a/codes/dart/chapter_dynamic_programming/edit_distance.dart b/codes/dart/chapter_dynamic_programming/edit_distance.dart new file mode 100644 index 0000000000..72d97319be --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/edit_distance.dart @@ -0,0 +1,125 @@ +/** + * File: edit_distance.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 编辑距离:暴力搜索 */ +int editDistanceDFS(String s, String t, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) return i; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return min(min(insert, delete), replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) return i; + // 若已有记录,则直接返回之 + if (mem[i][j] != -1) return mem[i][j]; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = min(min(insert, delete), replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +int editDistanceDP(String s, String t) { + int n = s.length, m = t.length; + List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); + // 状态转移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 编辑距离:空间优化后的动态规划 */ +int editDistanceDPComp(String s, String t) { + int n = s.length, m = t.length; + List dp = List.filled(m + 1, 0); + // 状态转移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (int i = 1; i <= n; i++) { + // 状态转移:首列 + int leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +void main() { + String s = "bag"; + String t = "pack"; + int n = s.length, m = t.length; + + // 暴力搜索 + int res = editDistanceDFS(s, t, n, m); + print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); + + // 记忆化搜索 + List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); + + // 动态规划 + res = editDistanceDP(s, t); + print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t); + print("将 " + s + " 更改为 " + t + " 最少需要编辑 $res 步"); +} diff --git a/codes/dart/chapter_dynamic_programming/knapsack.dart b/codes/dart/chapter_dynamic_programming/knapsack.dart new file mode 100644 index 0000000000..86113c1153 --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/knapsack.dart @@ -0,0 +1,116 @@ +/** + * File: knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 0-1 背包:暴力搜索 */ +int knapsackDFS(List wgt, List val, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return max(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +int knapsackDFSMem( + List wgt, + List val, + List> mem, + int i, + int c, +) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +int knapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空间优化后的动态规划 */ +int knapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List dp = List.filled(cap + 1, 0); + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = wgt.length; + + // 暴力搜索 + int res = knapsackDFS(wgt, val, n, cap); + print("不超过背包容量的最大物品价值为 $res"); + + // 记忆化搜索 + List> mem = + List.generate(n + 1, (index) => List.filled(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + print("不超过背包容量的最大物品价值为 $res"); + + // 动态规划 + res = knapsackDP(wgt, val, cap); + print("不超过背包容量的最大物品价值为 $res"); + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt, val, cap); + print("不超过背包容量的最大物品价值为 $res"); +} diff --git a/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart b/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart new file mode 100644 index 0000000000..d6526e158e --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart @@ -0,0 +1,48 @@ +/** + * File: min_cost_climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 爬楼梯最小代价:动态规划 */ +int minCostClimbingStairsDP(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + List dp = List.filled(n + 1, 0); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +int minCostClimbingStairsDPComp(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + print("输入楼梯的代价列表为 $cost"); + + int res = minCostClimbingStairsDP(cost); + print("爬完楼梯的最低代价为 $res"); + + res = minCostClimbingStairsDPComp(cost); + print("爬完楼梯的最低代价为 $res"); +} diff --git a/codes/dart/chapter_dynamic_programming/min_path_sum.dart b/codes/dart/chapter_dynamic_programming/min_path_sum.dart new file mode 100644 index 0000000000..5dca91952c --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/min_path_sum.dart @@ -0,0 +1,120 @@ +/** + * File: min_path_sum.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最小路径和:暴力搜索 */ +int minPathSumDFS(List> grid, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + // 在 Dart 中,int 类型是固定范围的整数,不存在表示“无穷大”的值 + return BigInt.from(2).pow(31).toInt(); + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j]; +} + +/* 最小路径和:记忆化搜索 */ +int minPathSumDFSMem(List> grid, List> mem, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + // 在 Dart 中,int 类型是固定范围的整数,不存在表示“无穷大”的值 + return BigInt.from(2).pow(31).toInt(); + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +int minPathSumDP(List> grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + List> dp = List.generate(n, (i) => List.filled(m, 0)); + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路径和:空间优化后的动态规划 */ +int minPathSumDPComp(List> grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + List dp = List.filled(m, 0); + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +void main() { + List> grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ]; + int n = grid.length, m = grid[0].length; + +// 暴力搜索 + int res = minPathSumDFS(grid, n - 1, m - 1); + print("从左上角到右下角的最小路径和为 $res"); + +// 记忆化搜索 + List> mem = List.generate(n, (i) => List.filled(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + print("从左上角到右下角的最小路径和为 $res"); + +// 动态规划 + res = minPathSumDP(grid); + print("从左上角到右下角的最小路径和为 $res"); + +// 空间优化后的动态规划 + res = minPathSumDPComp(grid); + print("从左上角到右下角的最小路径和为 $res"); +} diff --git a/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart b/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart new file mode 100644 index 0000000000..7a77e6a719 --- /dev/null +++ b/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart @@ -0,0 +1,62 @@ +/** + * File: unbounded_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 完全背包:动态规划 */ +int unboundedKnapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空间优化后的动态规划 */ +int unboundedKnapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List dp = List.filled(cap + 1, 0); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [1, 2, 3]; + List val = [5, 11, 15]; + int cap = 4; + + // 动态规划 + int res = unboundedKnapsackDP(wgt, val, cap); + print("不超过背包容量的最大物品价值为 $res"); + + // 空间优化后的动态规划 + int resComp = unboundedKnapsackDPComp(wgt, val, cap); + print("不超过背包容量的最大物品价值为 $resComp"); +} diff --git a/codes/dart/chapter_graph/graph_adjacency_list.dart b/codes/dart/chapter_graph/graph_adjacency_list.dart new file mode 100644 index 0000000000..c563f82221 --- /dev/null +++ b/codes/dart/chapter_graph/graph_adjacency_list.dart @@ -0,0 +1,124 @@ +/** + * File: graph_adjacency_list.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; + +/* 基于邻接表实现的无向图类 */ +class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + Map> adjList = {}; + + /* 构造方法 */ + GraphAdjList(List> edges) { + for (List edge in edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() { + return adjList.length; + } + + /* 添加边 */ + void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 添加边 vet1 - vet2 + adjList[vet1]!.add(vet2); + adjList[vet2]!.add(vet1); + } + + /* 删除边 */ + void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 删除边 vet1 - vet2 + adjList[vet1]!.remove(vet2); + adjList[vet2]!.remove(vet1); + } + + /* 添加顶点 */ + void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) return; + // 在邻接表中添加一个新链表 + adjList[vet] = []; + } + + /* 删除顶点 */ + void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) { + throw ArgumentError; + } + // 在邻接表中删除顶点 vet 对应的链表 + adjList.remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + adjList.forEach((key, value) { + value.remove(vet); + }); + } + + /* 打印邻接表 */ + void printAdjList() { + print("邻接表 ="); + adjList.forEach((key, value) { + List tmp = []; + for (Vertex vertex in value) { + tmp.add(vertex.val); + } + print("${key.val}: $tmp,"); + }); + } +} + +/* Driver Code */ +void main() { + /* 初始化无向图 */ + List v = Vertex.valsToVets([1, 3, 2, 5, 4]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化后,图为"); + graph.printAdjList(); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + print("\n添加边 1-2 后,图为"); + graph.printAdjList(); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + print("\n删除边 1-3 后,图为"); + graph.printAdjList(); + + /* 添加顶点 */ + Vertex v5 = Vertex(6); + graph.addVertex(v5); + print("\n添加顶点 6 后,图为"); + graph.printAdjList(); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(v[1]); + print("\n删除顶点 3 后,图为"); + graph.printAdjList(); +} diff --git a/codes/dart/chapter_graph/graph_adjacency_matrix.dart b/codes/dart/chapter_graph/graph_adjacency_matrix.dart new file mode 100644 index 0000000000..7a90b3c379 --- /dev/null +++ b/codes/dart/chapter_graph/graph_adjacency_matrix.dart @@ -0,0 +1,133 @@ +/** + * File: graph_adjacency_matrix.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + List vertices = []; // 顶点元素,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat = []; //邻接矩阵,行列索引对应“顶点索引” + + /* 构造方法 */ + GraphAdjMat(List vertices, List> edges) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + for (int val in vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (List e in edges) { + addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + int size() { + return vertices.length; + } + + /* 添加顶点 */ + void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.add(val); + // 在邻接矩阵中添加一行 + List newRow = List.filled(n, 0, growable: true); + adjMat.add(newRow); + // 在邻接矩阵中添加一列 + for (List row in adjMat) { + row.add(0); + } + } + + /* 删除顶点 */ + void removeVertex(int index) { + if (index >= size()) { + throw IndexError; + } + // 在顶点列表中移除索引 index 的顶点 + vertices.removeAt(index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.removeAt(index); + // 在邻接矩阵中删除索引 index 的列 + for (List row in adjMat) { + row.removeAt(index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + void printAdjMat() { + print("顶点列表 = $vertices"); + print("邻接矩阵 = "); + printMatrix(adjMat); + } +} + +/* Driver Code */ +void main() { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + List vertices = [1, 3, 2, 5, 4]; + List> edges = [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4], + ]; + GraphAdjMat graph = GraphAdjMat(vertices, edges); + print("\n初始化后,图为"); + graph.printAdjMat(); + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.addEdge(0, 2); + print("\n添加边 1-2 后,图为"); + graph.printAdjMat(); + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.removeEdge(0, 1); + print("\n删除边 1-3 后,图为"); + graph.printAdjMat(); + + /* 添加顶点 */ + graph.addVertex(6); + print("\n添加顶点 6 后,图为"); + graph.printAdjMat(); + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.removeVertex(1); + print("\n删除顶点 3 后,图为"); + graph.printAdjMat(); +} diff --git a/codes/dart/chapter_graph/graph_bfs.dart b/codes/dart/chapter_graph/graph_bfs.dart new file mode 100644 index 0000000000..76fc50725e --- /dev/null +++ b/codes/dart/chapter_graph/graph_bfs.dart @@ -0,0 +1,66 @@ +/** + * File: graph_bfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 广度优先遍历 */ +List graphBFS(GraphAdjList graph, Vertex startVet) { + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + // 顶点遍历序列 + List res = []; + // 哈希集合,用于记录已被访问过的顶点 + Set visited = {}; + visited.add(startVet); + // 队列用于实现 BFS + Queue que = Queue(); + que.add(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.isNotEmpty) { + Vertex vet = que.removeFirst(); // 队首顶点出队 + res.add(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.add(adjVet); // 只入队未访问的顶点 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; +} + +/* Dirver Code */ +void main() { + /* 初始化无向图 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + List> edges = [ + [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]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化后,图为"); + graph.printAdjList(); + + /* 广度优先遍历 */ + List res = graphBFS(graph, v[0]); + print("\n广度优先遍历(BFS)顶点序列为"); + print(Vertex.vetsToVals(res)); +} diff --git a/codes/dart/chapter_graph/graph_dfs.dart b/codes/dart/chapter_graph/graph_dfs.dart new file mode 100644 index 0000000000..a4e892775d --- /dev/null +++ b/codes/dart/chapter_graph/graph_dfs.dart @@ -0,0 +1,59 @@ +/** + * File: graph_dfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 深度优先遍历辅助函数 */ +void dfs( + GraphAdjList graph, + Set visited, + List res, + Vertex vet, +) { + res.add(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度优先遍历 */ +List graphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = []; + // 哈希集合,用于记录已被访问过的顶点 + Set visited = {}; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +void main() { + /* 初始化无向图 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化后,图为"); + graph.printAdjList(); + + /* 深度优先遍历 */ + List res = graphDFS(graph, v[0]); + print("\n深度优先遍历(DFS)顶点序列为"); + print(Vertex.vetsToVals(res)); +} diff --git a/codes/dart/chapter_greedy/coin_change_greedy.dart b/codes/dart/chapter_greedy/coin_change_greedy.dart new file mode 100644 index 0000000000..7b739f3982 --- /dev/null +++ b/codes/dart/chapter_greedy/coin_change_greedy.dart @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 零钱兑换:贪心 */ +int coinChangeGreedy(List coins, int amt) { + // 假设 coins 列表有序 + int i = coins.length - 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; +} + +/* Driver Code */ +void main() { + // 贪心:能够保证找到全局最优解 + List coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("凑到 $amt 所需的最少硬币数量为 $res"); + + // 贪心:无法保证找到全局最优解 + coins = [1, 20, 50]; + amt = 60; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("凑到 $amt 所需的最少硬币数量为 $res"); + print("实际上需要的最少数量为 3 ,即 20 + 20 + 20"); + + // 贪心:无法保证找到全局最优解 + coins = [1, 49, 50]; + amt = 98; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("凑到 $amt 所需的最少硬币数量为 $res"); + print("实际上需要的最少数量为 2 ,即 49 + 49"); +} diff --git a/codes/dart/chapter_greedy/fractional_knapsack.dart b/codes/dart/chapter_greedy/fractional_knapsack.dart new file mode 100644 index 0000000000..61c5d501f1 --- /dev/null +++ b/codes/dart/chapter_greedy/fractional_knapsack.dart @@ -0,0 +1,47 @@ +/** + * File: fractional_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 物品 */ +class Item { + int w; // 物品重量 + int v; // 物品价值 + + Item(this.w, this.v); +} + +/* 分数背包:贪心 */ +double fractionalKnapsack(List wgt, List val, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); + // 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); + // 循环贪心选择 + double res = 0; + for (Item item in items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += item.v / item.w * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 贪心算法 + double res = fractionalKnapsack(wgt, val, cap); + print("不超过背包容量的最大物品价值为 $res"); +} diff --git a/codes/dart/chapter_greedy/max_capacity.dart b/codes/dart/chapter_greedy/max_capacity.dart new file mode 100644 index 0000000000..1ed464a6e1 --- /dev/null +++ b/codes/dart/chapter_greedy/max_capacity.dart @@ -0,0 +1,37 @@ +/** + * File: max_capacity.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大容量:贪心 */ +int maxCapacity(List ht) { + // 初始化 i, j,使其分列数组两端 + int i = 0, j = ht.length - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +void main() { + List ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 贪心算法 + int res = maxCapacity(ht); + print("最大容量为 $res"); +} diff --git a/codes/dart/chapter_greedy/max_product_cutting.dart b/codes/dart/chapter_greedy/max_product_cutting.dart new file mode 100644 index 0000000000..2df5c8e18a --- /dev/null +++ b/codes/dart/chapter_greedy/max_product_cutting.dart @@ -0,0 +1,37 @@ +/** + * File: max_product_cutting.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大切分乘积:贪心 */ +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).toInt(); + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return (pow(3, a) * 2).toInt(); + } + // 当余数为 0 时,不做处理 + return pow(3, a).toInt(); +} + +/* Driver Code */ +void main() { + int n = 58; + + // 贪心算法 + int res = maxProductCutting(n); + print("最大切分乘积为 $res"); +} diff --git a/codes/dart/chapter_hashing/array_hash_map.dart b/codes/dart/chapter_hashing/array_hash_map.dart new file mode 100644 index 0000000000..45b46310fa --- /dev/null +++ b/codes/dart/chapter_hashing/array_hash_map.dart @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 键值对 */ +class Pair { + int key; + String val; + Pair(this.key, this.val); +} + +/* 基于数组实现的哈希表 */ +class ArrayHashMap { + late List _buckets; + + ArrayHashMap() { + // 初始化数组,包含 100 个桶 + _buckets = List.filled(100, null); + } + + /* 哈希函数 */ + int _hashFunc(int key) { + final int index = key % 100; + return index; + } + + /* 查询操作 */ + String? get(int key) { + final int index = _hashFunc(key); + final Pair? pair = _buckets[index]; + if (pair == null) { + return null; + } + return pair.val; + } + + /* 添加操作 */ + void put(int key, String val) { + final Pair pair = Pair(key, val); + final int index = _hashFunc(key); + _buckets[index] = pair; + } + + /* 删除操作 */ + void remove(int key) { + final int index = _hashFunc(key); + _buckets[index] = null; + } + + /* 获取所有键值对 */ + List pairSet() { + List pairSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + pairSet.add(pair); + } + } + return pairSet; + } + + /* 获取所有键 */ + List keySet() { + List keySet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + keySet.add(pair.key); + } + } + return keySet; + } + + /* 获取所有值 */ + List values() { + List valueSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + valueSet.add(pair.val); + } + } + return valueSet; + } + + /* 打印哈希表 */ + void printHashMap() { + for (final Pair kv in pairSet()) { + print("${kv.key} -> ${kv.val}"); + } + } +} + +/* Driver Code */ +void main() { + /* 初始化哈希表 */ + final ArrayHashMap map = ArrayHashMap(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + print("\n添加完成后,哈希表为\nKey -> Value"); + map.printHashMap(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + String? name = map.get(15937); + print("\n输入学号 15937 ,查询到姓名 $name"); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + print("\n删除 10583 后,哈希表为\nKey -> Value"); + map.printHashMap(); + + /* 遍历哈希表 */ + print("\n遍历键值对 Key->Value"); + map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); + print("\n单独遍历键 Key"); + map.keySet().forEach((key) => print("$key")); + print("\n单独遍历值 Value"); + map.values().forEach((val) => print("$val")); +} diff --git a/codes/dart/chapter_hashing/built_in_hash.dart b/codes/dart/chapter_hashing/built_in_hash.dart new file mode 100644 index 0000000000..4926e2812b --- /dev/null +++ b/codes/dart/chapter_hashing/built_in_hash.dart @@ -0,0 +1,34 @@ +/** + * File: built_in_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../chapter_stack_and_queue/linkedlist_deque.dart'; + +/* Driver Code */ +void main() { + int _num = 3; + int hashNum = _num.hashCode; + print("整数 $_num 的哈希值为 $hashNum"); + + bool bol = true; + int hashBol = bol.hashCode; + print("布尔值 $bol 的哈希值为 $hashBol"); + + double dec = 3.14159; + int hashDec = dec.hashCode; + print("小数 $dec 的哈希值为 $hashDec"); + + String str = "Hello 算法"; + int hashStr = str.hashCode; + print("字符串 $str 的哈希值为 $hashStr"); + + List arr = [12836, "小哈"]; + int hashArr = arr.hashCode; + print("数组 $arr 的哈希值为 $hashArr"); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + print("节点对象 $obj 的哈希值为 $hashObj"); +} diff --git a/codes/dart/chapter_hashing/hash_map.dart b/codes/dart/chapter_hashing/hash_map.dart new file mode 100644 index 0000000000..811d3dac2d --- /dev/null +++ b/codes/dart/chapter_hashing/hash_map.dart @@ -0,0 +1,41 @@ +/** + * File: hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Driver Code */ +void main() { + /* 初始化哈希表 */ + final Map map = {}; + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map[12836] = "小哈"; + map[15937] = "小啰"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鸭"; + print("\n添加完成后,哈希表为\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + final String? name = map[15937]; + print("\n输入学号 15937 ,查询到姓名 $name"); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + print("\n删除 10583 后,哈希表为\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* 遍历哈希表 */ + print("\n遍历键值对 Key->Value"); + map.forEach((key, value) => print("$key -> $value")); + print("\n单独遍历键 Key"); + map.keys.forEach((key) => print(key)); + print("\n单独遍历值 Value"); + map.forEach((key, value) => print("$value")); + map.values.forEach((value) => print(value)); +} diff --git a/codes/dart/chapter_hashing/hash_map_chaining.dart b/codes/dart/chapter_hashing/hash_map_chaining.dart new file mode 100644 index 0000000000..a8ed9d5ab7 --- /dev/null +++ b/codes/dart/chapter_hashing/hash_map_chaining.dart @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.dart + * Created Time: 2023-06-24 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* 链式地址哈希表 */ +class HashMapChaining { + late int size; // 键值对数量 + late int capacity; // 哈希表容量 + late double loadThres; // 触发扩容的负载因子阈值 + late int extendRatio; // 扩容倍数 + late List> buckets; // 桶数组 + + /* 构造方法 */ + HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = List.generate(capacity, (_) => []); + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return size / capacity; + } + + /* 查询操作 */ + String? get(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // 遍历桶,若找到 key ,则返回对应 val + for (Pair pair in bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,则返回 null + return null; + } + + /* 添加操作 */ + void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets[index]; + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (Pair pair in bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + Pair pair = Pair(key, val); + bucket.add(pair); + size++; + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // 遍历桶,从中删除键值对 + for (Pair pair in bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + List> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = List.generate(capacity, (_) => []); + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (List bucket in bucketsTmp) { + for (Pair pair in bucket) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + void printHashMap() { + for (List bucket in buckets) { + List res = []; + for (Pair pair in bucket) { + res.add("${pair.key} -> ${pair.val}"); + } + print(res); + } + } +} + +/* Driver Code */ +void main() { + /* 初始化哈希表 */ + HashMapChaining map = HashMapChaining(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + print("\n添加完成后,哈希表为\nKey -> Value"); + map.printHashMap(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + String? name = map.get(13276); + print("\n输入学号 13276 ,查询到姓名 ${name}"); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(12836); + print("\n删除 12836 后,哈希表为\nKey -> Value"); + map.printHashMap(); +} diff --git a/codes/dart/chapter_hashing/hash_map_open_addressing.dart b/codes/dart/chapter_hashing/hash_map_open_addressing.dart new file mode 100644 index 0000000000..618031234c --- /dev/null +++ b/codes/dart/chapter_hashing/hash_map_open_addressing.dart @@ -0,0 +1,157 @@ +/** + * File: hash_map_open_addressing.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + late int _size; // 键值对数量 + int _capacity = 4; // 哈希表容量 + double _loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + int _extendRatio = 2; // 扩容倍数 + late List _buckets; // 桶数组 + Pair _TOMBSTONE = Pair(-1, "-1"); // 删除标记 + + /* 构造方法 */ + HashMapOpenAddressing() { + _size = 0; + _buckets = List.generate(_capacity, (index) => null); + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % _capacity; + } + + /* 负载因子 */ + double loadFactor() { + return _size / _capacity; + } + + /* 搜索 key 对应的桶索引 */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (_buckets[index] != null) { + // 若遇到 key ,返回对应的桶索引 + if (_buckets[index]!.key == key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone != -1) { + _buckets[firstTombstone] = _buckets[index]; + _buckets[index] = _TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % _capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查询操作 */ + String? get(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则返回对应 val + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + return _buckets[index]!.val; + } + // 若键值对不存在,则返回 null + return null; + } + + /* 添加操作 */ + void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > _loadThres) { + extend(); + } + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index]!.val = val; + return; + } + // 若键值对不存在,则添加该键值对 + _buckets[index] = new Pair(key, val); + _size++; + } + + /* 删除操作 */ + void remove(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index] = _TOMBSTONE; + _size--; + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + List bucketsTmp = _buckets; + // 初始化扩容后的新哈希表 + _capacity *= _extendRatio; + _buckets = List.generate(_capacity, (index) => null); + _size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (Pair? pair in bucketsTmp) { + if (pair != null && pair != _TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + void printHashMap() { + for (Pair? pair in _buckets) { + if (pair == null) { + print("null"); + } else if (pair == _TOMBSTONE) { + print("TOMBSTONE"); + } else { + print("${pair.key} -> ${pair.val}"); + } + } + } +} + +/* Driver Code */ +void main() { + /* 初始化哈希表 */ + HashMapOpenAddressing map = HashMapOpenAddressing(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + print("\n添加完成后,哈希表为\nKey -> Value"); + map.printHashMap(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + String? name = map.get(13276); + print("\n输入学号 13276 ,查询到姓名 $name"); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(16750); + print("\n删除 16750 后,哈希表为\nKey -> Value"); + map.printHashMap(); +} diff --git a/codes/dart/chapter_hashing/simple_hash.dart b/codes/dart/chapter_hashing/simple_hash.dart new file mode 100644 index 0000000000..3c92841703 --- /dev/null +++ b/codes/dart/chapter_hashing/simple_hash.dart @@ -0,0 +1,62 @@ +/** + * File: simple_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 加法哈希 */ +int addHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* 乘法哈希 */ +int mulHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* 异或哈希 */ +int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash ^= key.codeUnitAt(i); + } + return hash & MODULUS; +} + +/* 旋转哈希 */ +int rotHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* Dirver Code */ +void main() { + String key = "Hello 算法"; + + int hash = addHash(key); + print("加法哈希值为 $hash"); + + hash = mulHash(key); + print("乘法哈希值为 $hash"); + + hash = xorHash(key); + print("异或哈希值为 $hash"); + + hash = rotHash(key); + print("旋转哈希值为 $hash"); +} diff --git a/codes/dart/chapter_heap/my_heap.dart b/codes/dart/chapter_heap/my_heap.dart new file mode 100644 index 0000000000..7264ed78d7 --- /dev/null +++ b/codes/dart/chapter_heap/my_heap.dart @@ -0,0 +1,151 @@ +/** + * File: my_heap.dart + * Created Time: 2023-04-09 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 大顶堆 */ +class MaxHeap { + late List _maxHeap; + + /* 构造方法,根据输入列表建堆 */ + MaxHeap(List nums) { + // 将列表元素原封不动添加进堆 + _maxHeap = nums; + // 堆化除叶节点以外的其他所有节点 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 获取左子节点的索引 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 向下整除 + } + + /* 交换元素 */ + void _swap(int i, int j) { + int tmp = _maxHeap[i]; + _maxHeap[i] = _maxHeap[j]; + _maxHeap[j] = tmp; + } + + /* 获取堆大小 */ + int size() { + return _maxHeap.length; + } + + /* 判断堆是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 访问堆顶元素 */ + int peek() { + return _maxHeap[0]; + } + + /* 元素入堆 */ + void push(int val) { + // 添加节点 + _maxHeap.add(val); + // 从底至顶堆化 + siftUp(size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = _parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { + break; + } + // 交换两节点 + _swap(i, p); + // 循环向上堆化 + i = p; + } + } + + /* 元素出堆 */ + int pop() { + // 判空处理 + if (isEmpty()) throw Exception('堆为空'); + // 交换根节点与最右叶节点(交换首元素与尾元素) + _swap(0, size() - 1); + // 删除节点 + int val = _maxHeap.removeLast(); + // 从顶至底堆化 + siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = _left(i); + int r = _right(i); + int ma = i; + if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; + if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break; + // 交换两节点 + _swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + + /* 打印堆(二叉树) */ + void print() { + printHeap(_maxHeap); + } +} + +/* Driver Code */ +void main() { + /* 初始化大顶堆 */ + MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + print("\n输入列表并建堆后"); + maxHeap.print(); + + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); + print("\n堆顶元素为 $peek"); + + /* 元素入堆 */ + int val = 7; + maxHeap.push(val); + print("\n元素 $val 入堆后"); + maxHeap.print(); + + /* 堆顶元素出堆 */ + peek = maxHeap.pop(); + print("\n堆顶元素 $peek 出堆后"); + maxHeap.print(); + + /* 获取堆大小 */ + int size = maxHeap.size(); + print("\n堆元素数量为 $size"); + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.isEmpty(); + print("\n堆是否为空 $isEmpty"); +} diff --git a/codes/dart/chapter_heap/top_k.dart b/codes/dart/chapter_heap/top_k.dart new file mode 100644 index 0000000000..011d079b4d --- /dev/null +++ b/codes/dart/chapter_heap/top_k.dart @@ -0,0 +1,150 @@ +/** + * File: top_k.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 基于堆查找数组中最大的 k 个元素 */ +MinHeap topKHeap(List nums, int k) { + // 初始化小顶堆,将数组的前 k 个元素入堆 + MinHeap heap = MinHeap(nums.sublist(0, k)); + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.peek()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +/* Driver Code */ +void main() { + List nums = [1, 7, 6, 3, 2]; + int k = 3; + + MinHeap res = topKHeap(nums, k); + print("最大的 $k 个元素为"); + res.print(); +} + +/* 小顶堆 */ +class MinHeap { + late List _minHeap; + + /* 构造方法,根据输入列表建堆 */ + MinHeap(List nums) { + // 将列表元素原封不动添加进堆 + _minHeap = nums; + // 堆化除叶节点以外的其他所有节点 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 返回堆中的元素 */ + List getHeap() { + return _minHeap; + } + + /* 获取左子节点的索引 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 向下整除 + } + + /* 交换元素 */ + void _swap(int i, int j) { + int tmp = _minHeap[i]; + _minHeap[i] = _minHeap[j]; + _minHeap[j] = tmp; + } + + /* 获取堆大小 */ + int size() { + return _minHeap.length; + } + + /* 判断堆是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 访问堆顶元素 */ + int peek() { + return _minHeap[0]; + } + + /* 元素入堆 */ + void push(int val) { + // 添加节点 + _minHeap.add(val); + // 从底至顶堆化 + siftUp(size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = _parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || _minHeap[i] >= _minHeap[p]) { + break; + } + // 交换两节点 + _swap(i, p); + // 循环向上堆化 + i = p; + } + } + + /* 元素出堆 */ + int pop() { + // 判空处理 + if (isEmpty()) throw Exception('堆为空'); + // 交换根节点与最右叶节点(交换首元素与尾元素) + _swap(0, size() - 1); + // 删除节点 + int val = _minHeap.removeLast(); + // 从顶至底堆化 + siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = _left(i); + int r = _right(i); + int mi = i; + if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; + if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (mi == i) break; + // 交换两节点 + _swap(i, mi); + // 循环向下堆化 + i = mi; + } + } + + /* 打印堆(二叉树) */ + void print() { + printHeap(_minHeap); + } +} diff --git a/codes/dart/chapter_searching/binary_search.dart b/codes/dart/chapter_searching/binary_search.dart new file mode 100644 index 0000000000..34c557d7fc --- /dev/null +++ b/codes/dart/chapter_searching/binary_search.dart @@ -0,0 +1,63 @@ +/** + * File: binary_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二分查找(双闭区间) */ +int binarySearch(List nums, int target) { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + int i = 0, j = nums.length - 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(List nums, int target) { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = nums.length; + // 循环,当搜索区间为空时跳出(当 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; +} + +/* Driver Code*/ +void main() { + int target = 6; + final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* 二分查找 (双闭区间) */ + int index = binarySearch(nums, target); + print('目标元素 6 的索引 = $index'); + + /* 二分查找(左闭右开区间) */ + index = binarySearchLCRO(nums, target); + print('目标元素 6 的索引 = $index'); +} diff --git a/codes/dart/chapter_searching/binary_search_edge.dart b/codes/dart/chapter_searching/binary_search_edge.dart new file mode 100644 index 0000000000..c98912e074 --- /dev/null +++ b/codes/dart/chapter_searching/binary_search_edge.dart @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'binary_search_insertion.dart'; + +/* 二分查找最左一个 target */ +int binarySearchLeftEdge(List nums, int target) { + // 等价于查找 target 的插入点 + int i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分查找最右一个 target */ +int binarySearchRightEdge(List nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +void main() { + // 包含重复元素的数组 + List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n数组 nums = $nums"); + + // 二分查找左边界和右边界 + for (int target in [6, 7]) { + int index = binarySearchLeftEdge(nums, target); + print("最左一个元素 $target 的索引为 $index"); + index = binarySearchRightEdge(nums, target); + print("最右一个元素 $target 的索引为 $index"); + } +} diff --git a/codes/dart/chapter_searching/binary_search_insertion.dart b/codes/dart/chapter_searching/binary_search_insertion.dart new file mode 100644 index 0000000000..141c80b62e --- /dev/null +++ b/codes/dart/chapter_searching/binary_search_insertion.dart @@ -0,0 +1,60 @@ +/** + * File: binary_search_insertion.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分查找插入点(无重复元素) */ +int binarySearchInsertionSimple(List nums, int target) { + int i = 0, j = nums.length - 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(List nums, int target) { + int i = 0, j = nums.length - 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; +} + +/* Driver Code */ +void main() { + // 无重复元素的数组 + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + print("\n数组 nums = $nums"); + // 二分查找插入点 + for (int target in [6, 9]) { + int index = binarySearchInsertionSimple(nums, target); + print("元素 $target 的插入点的索引为 $index"); + } + + // 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n数组 nums = $nums"); + // 二分查找插入点 + for (int target in [2, 6, 20]) { + int index = binarySearchInsertion(nums, target); + print("元素 $target 的插入点的索引为 $index"); + } +} diff --git a/codes/dart/chapter_searching/hashing_search.dart b/codes/dart/chapter_searching/hashing_search.dart new file mode 100644 index 0000000000..6bd73de1ab --- /dev/null +++ b/codes/dart/chapter_searching/hashing_search.dart @@ -0,0 +1,54 @@ +/** + * File: hashing_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; +import '../utils/list_node.dart'; + +/* 哈希查找(数组) */ +int hashingSearchArray(Map map, int target) { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 -1 + if (!map.containsKey(target)) { + return -1; + } + return map[target]!; +} + +/* 哈希查找(链表) */ +ListNode? hashingSearchLinkedList(Map map, int target) { + // 哈希表的 key: 目标节点值,value: 节点对象 + // 若哈希表中无此 key ,返回 null + if (!map.containsKey(target)) { + return null; + } + return map[target]!; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* 哈希查找(数组) */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化哈希表 + Map map = HashMap(); + for (int i = 0; i < nums.length; i++) { + map.putIfAbsent(nums[i], () => i); // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + print('目标元素 3 的索引 = $index'); + + /* 哈希查找(链表) */ + ListNode? head = listToLinkedList(nums); + // 初始化哈希表 + Map map1 = HashMap(); + while (head != null) { + map1.putIfAbsent(head.val, () => head!); // key: 节点值,value: 节点 + head = head.next; + } + ListNode? node = hashingSearchLinkedList(map1, target); + print('目标节点值 3 的对应节点对象为 $node'); +} diff --git a/codes/dart/chapter_searching/linear_search.dart b/codes/dart/chapter_searching/linear_search.dart new file mode 100644 index 0000000000..55b38cbbf7 --- /dev/null +++ b/codes/dart/chapter_searching/linear_search.dart @@ -0,0 +1,47 @@ +/** + * File: linear_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 线性查找(数组) */ +int linearSearchArray(List nums, int target) { + // 遍历数组 + for (int i = 0; i < nums.length; i++) { + // 找到目标元素,返回其索引 + if (nums[i] == target) { + return i; + } + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* 线性查找(链表) */ +ListNode? linearSearchList(ListNode? head, int target) { + // 遍历链表 + while (head != null) { + // 找到目标节点,返回之 + if (head.val == target) return head; + head = head.next; + } + // 未找到目标元素,返回 null + return null; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* 在数组中执行线性查找 */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = linearSearchArray(nums, target); + print('目标元素 3 的索引 = $index'); + + /* 在链表中执行线性查找 */ + ListNode? head = listToLinkedList(nums); + ListNode? node = linearSearchList(head, target); + print('目标节点值 3 的对应节点对象为 $node'); +} diff --git a/codes/dart/chapter_searching/two_sum.dart b/codes/dart/chapter_searching/two_sum.dart new file mode 100644 index 0000000000..c854cba2ff --- /dev/null +++ b/codes/dart/chapter_searching/two_sum.dart @@ -0,0 +1,49 @@ +/** + * File: two_sum.dart + * Created Time: 2023-2-11 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; + +/* 方法一: 暴力枚举 */ +List twoSumBruteForce(List nums, int target) { + int size = nums.length; + // 两层循环,时间复杂度为 O(n^2) + for (var i = 0; i < size - 1; i++) { + for (var j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) return [i, j]; + } + } + return [0]; +} + +/* 方法二: 辅助哈希表 */ +List twoSumHashTable(List nums, int target) { + int size = nums.length; + // 辅助哈希表,空间复杂度为 O(n) + Map dic = HashMap(); + // 单层循环,时间复杂度为 O(n) + for (var i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return [dic[target - nums[i]]!, i]; + } + dic.putIfAbsent(nums[i], () => i); + } + return [0]; +} + +/* Driver Code */ +void main() { + // ======= Test Case ======= + List nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + List res = twoSumBruteForce(nums, target); + print('方法一 res = $res'); + // 方法二 + res = twoSumHashTable(nums, target); + print('方法二 res = $res'); +} diff --git a/codes/dart/chapter_sorting/bubble_sort.dart b/codes/dart/chapter_sorting/bubble_sort.dart new file mode 100644 index 0000000000..1d3cb2b4e9 --- /dev/null +++ b/codes/dart/chapter_sorting/bubble_sort.dart @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 冒泡排序 */ +void bubbleSort(List nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.length - 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; + } + } + } +} + +/* 冒泡排序(标志优化)*/ +void bubbleSortWithFlag(List nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + bool flag = false; // 初始化标志位 + // 内循环:将未排序区间 [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; + flag = true; // 记录交换元素 + } + } + if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + bubbleSort(nums); + print("冒泡排序完成后 nums = $nums"); + + List nums1 = [4, 1, 3, 1, 5, 2]; + bubbleSortWithFlag(nums1); + print("冒泡排序完成后 nums1 = $nums1"); +} diff --git a/codes/dart/chapter_sorting/bucket_sort.dart b/codes/dart/chapter_sorting/bucket_sort.dart new file mode 100644 index 0000000000..d6c5acbd90 --- /dev/null +++ b/codes/dart/chapter_sorting/bucket_sort.dart @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 桶排序 */ +void bucketSort(List nums) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + int k = nums.length ~/ 2; + List> buckets = List.generate(k, (index) => []); + + // 1. 将数组元素分配到各个桶中 + for (double _num in nums) { + // 输入数据范围为 [0, 1),使用 _num * k 映射到索引范围 [0, k-1] + int i = (_num * k).toInt(); + // 将 _num 添加进桶 bucket_idx + buckets[i].add(_num); + } + // 2. 对各个桶执行排序 + for (List bucket in buckets) { + bucket.sort(); + } + // 3. 遍历桶合并结果 + int i = 0; + for (List bucket in buckets) { + for (double _num in bucket) { + nums[i++] = _num; + } + } +} + +/* Driver Code*/ +void main() { + // 设输入数据为浮点数,范围为 [0, 1) + final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucketSort(nums); + print('桶排序完成后 nums = $nums'); +} diff --git a/codes/dart/chapter_sorting/counting_sort.dart b/codes/dart/chapter_sorting/counting_sort.dart new file mode 100644 index 0000000000..92cf9b5467 --- /dev/null +++ b/codes/dart/chapter_sorting/counting_sort.dart @@ -0,0 +1,72 @@ +/** + * File: counting_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ +import 'dart:math'; + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +void countingSortNaive(List nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 统计各数字的出现次数 + // counter[_num] 代表 _num 的出现次数 + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 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; + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +void countingSort(List nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 统计各数字的出现次数 + // counter[_num] 代表 _num 的出现次数 + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[_num]-1 是 _num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int n = nums.length; + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int _num = nums[i]; + res[counter[_num] - 1] = _num; // 将 _num 放置到对应索引处 + counter[_num]--; // 令前缀和自减 1 ,得到下次放置 _num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + nums.setAll(0, res); +} + +/* Driver Code*/ +void main() { + final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSortNaive(nums); + print('计数排序(无法排序对象)完成后 nums = $nums'); + + final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSort(nums1); + print('计数排序完成后 nums1 = $nums1'); +} diff --git a/codes/dart/chapter_sorting/heap_sort.dart b/codes/dart/chapter_sorting/heap_sort.dart new file mode 100644 index 0000000000..76254e975c --- /dev/null +++ b/codes/dart/chapter_sorting/heap_sort.dart @@ -0,0 +1,49 @@ +/** + * File: heap_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +void siftDown(List nums, int n, int i) { + while (true) { + // 判断节点 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(List nums) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = nums.length - 1; i > 0; i--) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + heapSort(nums); + print("堆排序完成后 nums = $nums"); +} diff --git a/codes/dart/chapter_sorting/insertion_sort.dart b/codes/dart/chapter_sorting/insertion_sort.dart new file mode 100644 index 0000000000..41ab47ec2d --- /dev/null +++ b/codes/dart/chapter_sorting/insertion_sort.dart @@ -0,0 +1,26 @@ +/** + * File: insertion_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 插入排序 */ +void insertionSort(List nums) { + // 外循环:已排序区间为 [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 + j--; + } + nums[j + 1] = base; // 将 base 赋值到正确位置 + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + insertionSort(nums); + print("插入排序完成后 nums = $nums"); +} diff --git a/codes/dart/chapter_sorting/merge_sort.dart b/codes/dart/chapter_sorting/merge_sort.dart new file mode 100644 index 0000000000..68fc3b34b8 --- /dev/null +++ b/codes/dart/chapter_sorting/merge_sort.dart @@ -0,0 +1,52 @@ +/** + * File: merge_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 合并左子数组和右子数组 */ +void merge(List nums, int left, int mid, int right) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + List tmp = List.filled(right - left + 1, 0); + // 初始化左子数组和右子数组的起始索引 + 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 < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* 归并排序 */ +void mergeSort(List 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); +} + +/* Driver Code */ +void main() { + /* 归并排序 */ + List nums = [7, 3, 2, 6, 0, 1, 5, 4]; + mergeSort(nums, 0, nums.length - 1); + print("归并排序完成后 nums = $nums"); +} diff --git a/codes/dart/chapter_sorting/quick_sort.dart b/codes/dart/chapter_sorting/quick_sort.dart new file mode 100644 index 0000000000..5d635bf8ac --- /dev/null +++ b/codes/dart/chapter_sorting/quick_sort.dart @@ -0,0 +1,145 @@ +/** + * File: quick_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 快速排序类 */ +class QuickSort { + /* 元素交换 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵划分 */ + static int _partition(List 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; // 返回基准数的索引 + } + + /* 快速排序 */ + static void quickSort(List 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); + } +} + +/* 快速排序类(中位基准数优化) */ +class QuickSortMedian { + /* 元素交换 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 选取三个候选元素的中位数 */ + static int _medianThree(List 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; + } + + /* 哨兵划分(三数取中值) */ + static int _partition(List 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; // 返回基准数的索引 + } + + /* 快速排序 */ + static void quickSort(List 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); + } +} + +/* 快速排序类(尾递归优化) */ +class QuickSortTailCall { + /* 元素交换 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵划分 */ + static int _partition(List 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; // 返回基准数的索引 + } + + /* 快速排序(尾递归优化) */ + static void quickSort(List nums, int left, int right) { + // 子数组长度为 1 时终止 + while (left < right) { + // 哨兵划分操作 + int pivot = _partition(nums, left, right); + // 对两个子数组中较短的那个执行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 递归排序右子数组 + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +void main() { + /* 快速排序 */ + List nums = [2, 4, 1, 0, 3, 5]; + QuickSort.quickSort(nums, 0, nums.length - 1); + print("快速排序完成后 nums = $nums"); + + /* 快速排序(中位基准数优化) */ + List nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + print("快速排序(中位基准数优化)完成后 nums1 = $nums1"); + + /* 快速排序(尾递归优化) */ + List nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + print("快速排序(尾递归优化)完成后 nums2 = $nums2"); +} diff --git a/codes/dart/chapter_sorting/radix_sort.dart b/codes/dart/chapter_sorting/radix_sort.dart new file mode 100644 index 0000000000..24c7b68002 --- /dev/null +++ b/codes/dart/chapter_sorting/radix_sort.dart @@ -0,0 +1,71 @@ +/** + * File: radix_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 获取元素 _num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int _num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (_num ~/ exp) % 10; +} + +/* 计数排序(根据 nums 第 k 位排序) */ +void countingSortDigit(List nums, int exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + List counter = List.filled(10, 0); + int n = nums.length; + // 统计 0~9 各数字的出现次数 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + List res = List.filled(n, 0); + for (int i = n - 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 < n; i++) nums[i] = res[i]; +} + +/* 基数排序 */ +void radixSort(List nums) { + // 获取数组的最大元素,用于判断最大位数 + // dart 中 int 的长度是 64 位的 + int m = -1 << 63; + for (int _num in nums) if (_num > m) m = _num; + // 按照从低位到高位的顺序遍历 + for (int exp = 1; exp <= m; exp *= 10) + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +void main() { + // 基数排序 + List nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996 + ]; + radixSort(nums); + print("基数排序完成后 nums = $nums"); +} diff --git a/codes/dart/chapter_sorting/selection_sort.dart b/codes/dart/chapter_sorting/selection_sort.dart new file mode 100644 index 0000000000..df939d8944 --- /dev/null +++ b/codes/dart/chapter_sorting/selection_sort.dart @@ -0,0 +1,29 @@ +/** + * File: selection_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 选择排序 */ +void selectionSort(List nums) { + int n = nums.length; + // 外循环:未排序区间为 [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; + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + selectionSort(nums); + print("选择排序完成后 nums = $nums"); +} diff --git a/codes/dart/chapter_stack_and_queue/array_deque.dart b/codes/dart/chapter_stack_and_queue/array_deque.dart new file mode 100644 index 0000000000..56de478f22 --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/array_deque.dart @@ -0,0 +1,146 @@ +/** + * File: array_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基于环形数组实现的双向队列 */ +class ArrayDeque { + late List _nums; // 用于存储双向队列元素的数组 + late int _front; // 队首指针,指向队首元素 + late int _queSize; // 双向队列长度 + + /* 构造方法 */ + ArrayDeque(int capacity) { + this._nums = List.filled(capacity, 0); + this._front = this._queSize = 0; + } + + /* 获取双向队列的容量 */ + int capacity() { + return _nums.length; + } + + /* 获取双向队列的长度 */ + int size() { + return _queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 计算环形数组索引 */ + int index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 队首入队 */ + void pushFirst(int _num) { + if (_queSize == capacity()) { + throw Exception("双向队列已满"); + } + // 队首指针向左移动一位 + // 通过取余操作实现 _front 越过数组头部后回到尾部 + _front = index(_front - 1); + // 将 _num 添加至队首 + _nums[_front] = _num; + _queSize++; + } + + /* 队尾入队 */ + void pushLast(int _num) { + if (_queSize == capacity()) { + throw Exception("双向队列已满"); + } + // 计算队尾指针,指向队尾索引 + 1 + int rear = index(_front + _queSize); + // 将 _num 添加至队尾 + _nums[rear] = _num; + _queSize++; + } + + /* 队首出队 */ + int popFirst() { + int _num = peekFirst(); + // 队首指针向右移动一位 + _front = index(_front + 1); + _queSize--; + return _num; + } + + /* 队尾出队 */ + int popLast() { + int _num = peekLast(); + _queSize--; + return _num; + } + + /* 访问队首元素 */ + int peekFirst() { + if (isEmpty()) { + throw Exception("双向队列为空"); + } + return _nums[_front]; + } + + /* 访问队尾元素 */ + int peekLast() { + if (isEmpty()) { + throw Exception("双向队列为空"); + } + // 计算尾元素索引 + int last = index(_front + _queSize - 1); + return _nums[last]; + } + + /* 返回数组用于打印 */ + List toArray() { + // 仅转换有效长度范围内的列表元素 + List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[index(j)]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化双向队列 */ + final ArrayDeque deque = ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("双向队列 deque = ${deque.toArray()}"); + + /* 访问元素 */ + final int peekFirst = deque.peekFirst(); + print("队首元素 peekFirst = $peekFirst"); + final int peekLast = deque.peekLast(); + print("队尾元素 peekLast = $peekLast"); + + /* 元素入队 */ + deque.pushLast(4); + print("元素 4 队尾入队后 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("元素 1 队首入队后 deque = ${deque.toArray()}"); + + /* 元素出队 */ + final int popLast = deque.popLast(); + print("队尾出队元素 = $popLast ,队尾出队后 deque = ${deque.toArray()}"); + final int popFirst = deque.popFirst(); + print("队首出队元素 = $popFirst ,队首出队后 deque = ${deque.toArray()}"); + + /* 获取双向队列的长度 */ + final int size = deque.size(); + print("双向队列长度 size = $size"); + + /* 判断双向队列是否为空 */ + final bool isEmpty = deque.isEmpty(); + print("双向队列是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_stack_and_queue/array_queue.dart b/codes/dart/chapter_stack_and_queue/array_queue.dart new file mode 100644 index 0000000000..1d2aaf3e17 --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/array_queue.dart @@ -0,0 +1,110 @@ +/** + * File: array_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基于环形数组实现的队列 */ +class ArrayQueue { + late List _nums; // 用于储存队列元素的数组 + late int _front; // 队首指针,指向队首元素 + late int _queSize; // 队列长度 + + ArrayQueue(int capacity) { + _nums = List.filled(capacity, 0); + _front = _queSize = 0; + } + + /* 获取队列的容量 */ + int capaCity() { + return _nums.length; + } + + /* 获取队列的长度 */ + int size() { + return _queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入队 */ + void push(int _num) { + if (_queSize == capaCity()) { + throw Exception("队列已满"); + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (_front + _queSize) % capaCity(); + // 将 _num 添加至队尾 + _nums[rear] = _num; + _queSize++; + } + + /* 出队 */ + int pop() { + int _num = peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + _front = (_front + 1) % capaCity(); + _queSize--; + return _num; + } + + /* 访问队首元素 */ + int peek() { + if (isEmpty()) { + throw Exception("队列为空"); + } + return _nums[_front]; + } + + /* 返回 Array */ + List toArray() { + // 仅转换有效长度范围内的列表元素 + final List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[j % capaCity()]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化队列 */ + final int capacity = 10; + final ArrayQueue queue = ArrayQueue(capacity); + + /* 元素入队 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("队列 queue = ${queue.toArray()}"); + + /* 访问队首元素 */ + final int peek = queue.peek(); + print("队首元素 peek = $peek"); + + /* 元素出队 */ + final int pop = queue.pop(); + print("出队元素 pop = $pop ,出队后 queue = ${queue.toArray()}"); + + /* 获取队列长度 */ + final int size = queue.size(); + print("队列长度 size = $size"); + + /* 判断队列是否为空 */ + final bool isEmpty = queue.isEmpty(); + print("队列是否为空 = $isEmpty"); + + /* 测试环形数组 */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + print("第 $i 轮入队 + 出队后 queue = ${queue.toArray()}"); + } +} diff --git a/codes/dart/chapter_stack_and_queue/array_stack.dart b/codes/dart/chapter_stack_and_queue/array_stack.dart new file mode 100644 index 0000000000..bd6918f78e --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/array_stack.dart @@ -0,0 +1,77 @@ +/** + * File: array_stack.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基于数组实现的栈 */ +class ArrayStack { + late List _stack; + ArrayStack() { + _stack = []; + } + + /* 获取栈的长度 */ + int size() { + return _stack.length; + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return _stack.isEmpty; + } + + /* 入栈 */ + void push(int _num) { + _stack.add(_num); + } + + /* 出栈 */ + int pop() { + if (isEmpty()) { + throw Exception("栈为空"); + } + return _stack.removeLast(); + } + + /* 访问栈顶元素 */ + int peek() { + if (isEmpty()) { + throw Exception("栈为空"); + } + return _stack.last; + } + + /* 将栈转化为 Array 并返回 */ + List toArray() => _stack; +} + +/* Driver Code */ +void main() { + /* 初始化栈 */ + final ArrayStack stack = ArrayStack(); + + /* 元素入栈 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("栈 stack = ${stack.toArray()}"); + + /* 访问栈顶元素 */ + final int peek = stack.peek(); + print("栈顶元素 peek = $peek"); + + /* 元素出栈 */ + final int pop = stack.pop(); + print("出栈元素 pop = $pop ,出栈后 stack = ${stack.toArray()}"); + + /* 获取栈的长度 */ + final int size = stack.size(); + print("栈的长度 size = $size"); + + /* 判断是否为空 */ + final bool isEmpty = stack.isEmpty(); + print("栈是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_stack_and_queue/deque.dart b/codes/dart/chapter_stack_and_queue/deque.dart new file mode 100644 index 0000000000..9f9676d36e --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/deque.dart @@ -0,0 +1,42 @@ +/** + * File: deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* 初始化双向队列 */ + final Queue deque = Queue(); + deque.addFirst(3); + deque.addLast(2); + deque.addLast(5); + print("双向队列 deque = $deque"); + + /* 访问元素 */ + final int peekFirst = deque.first; + print("队首元素 peekFirst = $peekFirst"); + final int peekLast = deque.last; + print("队尾元素 peekLast = $peekLast"); + + /* 元素入队 */ + deque.addLast(4); + print("元素 4 队尾入队后 deque = $deque"); + deque.addFirst(1); + print("元素 1 队首入队后 deque = $deque"); + + /* 元素出队 */ + final int popLast = deque.removeLast(); + print("队尾出队元素 = $popLast ,队尾出队后 deque = $deque"); + final int popFirst = deque.removeFirst(); + print("队首出队元素 = $popFirst ,队首出队后 deque = $deque"); + + /* 获取双向队列的长度 */ + final int size = deque.length; + print("双向队列长度 size = $size"); + + /* 判断双向队列是否为空 */ + final bool isEmpty = deque.isEmpty; + print("双向队列是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart b/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart new file mode 100644 index 0000000000..08d0dd8071 --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 双向链表节点 */ +class ListNode { + int val; // 节点值 + ListNode? next; // 后继节点引用 + ListNode? prev; // 前驱节点引用 + + ListNode(this.val, {this.next, this.prev}); +} + +/* 基于双向链表实现的双向对列 */ +class LinkedListDeque { + late ListNode? _front; // 头节点 _front + late ListNode? _rear; // 尾节点 _rear + int _queSize = 0; // 双向队列的长度 + + LinkedListDeque() { + this._front = null; + this._rear = null; + } + + /* 获取双向队列长度 */ + int size() { + return this._queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + void push(int _num, bool isFront) { + final ListNode node = ListNode(_num); + if (isEmpty()) { + // 若链表为空,则令 _front 和 _rear 都指向 node + _front = _rear = node; + } else if (isFront) { + // 队首入队操作 + // 将 node 添加至链表头部 + _front!.prev = node; + node.next = _front; + _front = node; // 更新头节点 + } else { + // 队尾入队操作 + // 将 node 添加至链表尾部 + _rear!.next = node; + node.prev = _rear; + _rear = node; // 更新尾节点 + } + _queSize++; // 更新队列长度 + } + + /* 队首入队 */ + void pushFirst(int _num) { + push(_num, true); + } + + /* 队尾入队 */ + void pushLast(int _num) { + push(_num, false); + } + + /* 出队操作 */ + int? pop(bool isFront) { + // 若队列为空,直接返回 null + if (isEmpty()) { + return null; + } + final int val; + if (isFront) { + // 队首出队操作 + val = _front!.val; // 暂存头节点值 + // 删除头节点 + ListNode? fNext = _front!.next; + if (fNext != null) { + fNext.prev = null; + _front!.next = null; + } + _front = fNext; // 更新头节点 + } else { + // 队尾出队操作 + val = _rear!.val; // 暂存尾节点值 + // 删除尾节点 + ListNode? rPrev = _rear!.prev; + if (rPrev != null) { + rPrev.next = null; + _rear!.prev = null; + } + _rear = rPrev; // 更新尾节点 + } + _queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + int? popFirst() { + return pop(true); + } + + /* 队尾出队 */ + int? popLast() { + return pop(false); + } + + /* 访问队首元素 */ + int? peekFirst() { + return _front?.val; + } + + /* 访问队尾元素 */ + int? peekLast() { + return _rear?.val; + } + + /* 返回数组用于打印 */ + List toArray() { + ListNode? node = _front; + final List res = []; + for (int i = 0; i < _queSize; i++) { + res.add(node!.val); + node = node.next; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化双向队列 */ + final LinkedListDeque deque = LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("双向队列 deque = ${deque.toArray()}"); + + /* 访问元素 */ + int? peekFirst = deque.peekFirst(); + print("队首元素 peekFirst = $peekFirst"); + int? peekLast = deque.peekLast(); + print("队尾元素 peekLast = $peekLast"); + + /* 元素入队 */ + deque.pushLast(4); + print("元素 4 队尾入队后 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("元素 1 队首入队后 deque = ${deque.toArray()}"); + + /* 元素出队 */ + int? popLast = deque.popLast(); + print("队尾出队元素 = $popLast ,队尾出队后 deque = ${deque.toArray()}"); + int? popFirst = deque.popFirst(); + print("队首出队元素 = $popFirst ,队首出队后 deque = ${deque.toArray()}"); + + /* 获取双向队列的长度 */ + int size = deque.size(); + print("双向队列长度 size = $size"); + + /* 判断双向队列是否为空 */ + bool isEmpty = deque.isEmpty(); + print("双向队列是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart b/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart new file mode 100644 index 0000000000..50ceb7a08c --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart @@ -0,0 +1,103 @@ +/** + * File: linkedlist_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 基于链表实现的队列 */ +class LinkedListQueue { + ListNode? _front; // 头节点 _front + ListNode? _rear; // 尾节点 _rear + int _queSize = 0; // 队列长度 + + LinkedListQueue() { + _front = null; + _rear = null; + } + + /* 获取队列的长度 */ + int size() { + return _queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入队 */ + void push(int _num) { + // 在尾节点后添加 _num + final node = ListNode(_num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (_front == null) { + _front = node; + _rear = node; + } else { + // 如果队列不为空,则将该节点添加到尾节点后 + _rear!.next = node; + _rear = node; + } + _queSize++; + } + + /* 出队 */ + int pop() { + final int _num = peek(); + // 删除头节点 + _front = _front!.next; + _queSize--; + return _num; + } + + /* 访问队首元素 */ + int peek() { + if (_queSize == 0) { + throw Exception('队列为空'); + } + return _front!.val; + } + + /* 将链表转化为 Array 并返回 */ + List toArray() { + ListNode? node = _front; + final List queue = []; + while (node != null) { + queue.add(node.val); + node = node.next; + } + return queue; + } +} + +/* Driver Code */ +void main() { + /* 初始化队列 */ + final queue = LinkedListQueue(); + + /* 元素入队 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("队列 queue = ${queue.toArray()}"); + + /* 访问队首元素 */ + final int peek = queue.peek(); + print("队首元素 peek = $peek"); + + /* 元素出队 */ + final int pop = queue.pop(); + print("出队元素 pop = $pop ,出队后 queue = ${queue.toArray()}"); + + /* 获取队列的长度 */ + final int size = queue.size(); + print("队列长度 size = $size"); + + /* 判断队列是否为空 */ + final bool isEmpty = queue.isEmpty(); + print("队列是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart b/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart new file mode 100644 index 0000000000..1efaa54636 --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart @@ -0,0 +1,93 @@ +/** + * File: linkedlist_stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 基于链表类实现的栈 */ +class LinkedListStack { + ListNode? _stackPeek; // 将头节点作为栈顶 + int _stkSize = 0; // 栈的长度 + + LinkedListStack() { + _stackPeek = null; + } + + /* 获取栈的长度 */ + int size() { + return _stkSize; + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return _stkSize == 0; + } + + /* 入栈 */ + void push(int _num) { + final ListNode node = ListNode(_num); + node.next = _stackPeek; + _stackPeek = node; + _stkSize++; + } + + /* 出栈 */ + int pop() { + final int _num = peek(); + _stackPeek = _stackPeek!.next; + _stkSize--; + return _num; + } + + /* 访问栈顶元素 */ + int peek() { + if (_stackPeek == null) { + throw Exception("栈为空"); + } + return _stackPeek!.val; + } + + /* 将链表转化为 List 并返回 */ + List toList() { + ListNode? node = _stackPeek; + List list = []; + while (node != null) { + list.add(node.val); + node = node.next; + } + list = list.reversed.toList(); + return list; + } +} + +/* Driver Code */ +void main() { + /* 初始化栈 */ + final LinkedListStack stack = LinkedListStack(); + + /* 元素入栈 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("栈 stack = ${stack.toList()}"); + + /* 访问栈顶元素 */ + final int peek = stack.peek(); + print("栈顶元素 peek = $peek"); + + /* 元素出栈 */ + final int pop = stack.pop(); + print("出栈元素 pop = $pop ,出栈后 stack = ${stack.toList()}"); + + /* 获取栈的长度 */ + final int size = stack.size(); + print("栈的长度 size = $size"); + + /* 判断是否为空 */ + final bool isEmpty = stack.isEmpty(); + print("栈是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_stack_and_queue/queue.dart b/codes/dart/chapter_stack_and_queue/queue.dart new file mode 100644 index 0000000000..0d6d24f24e --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/queue.dart @@ -0,0 +1,37 @@ +/** + * File: queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* 初始化队列 */ + // 在 Dart 中,一般将双向队列类 Queue 看作队列使用 + final Queue queue = Queue(); + + /* 元素入队 */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + print("队列 queue = $queue"); + + /* 访问队首元素 */ + final int peek = queue.first; + print("队首元素 peek = $peek"); + + /* 元素出队 */ + final int pop = queue.removeFirst(); + print("出队元素 pop = $pop ,出队后 queue = $queue"); + + /* 获取队列长度 */ + final int size = queue.length; + print("队列长度 size = $size"); + + /* 判断队列是否为空 */ + final bool isEmpty = queue.isEmpty; + print("队列是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_stack_and_queue/stack.dart b/codes/dart/chapter_stack_and_queue/stack.dart new file mode 100644 index 0000000000..e98230653a --- /dev/null +++ b/codes/dart/chapter_stack_and_queue/stack.dart @@ -0,0 +1,35 @@ +/** + * File: stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +void main() { + /* 初始化栈 */ + // Dart 没有内置的栈类,可以把 List 当作栈来使用 + final List stack = []; + + /* 元素入栈 */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + print("栈 stack = $stack"); + + /* 访问栈顶元素 */ + final int peek = stack.last; + print("栈顶元素 peek = $peek"); + + /* 元素出栈 */ + final int pop = stack.removeLast(); + print("出栈元素 pop = $pop ,出栈后 stack = $stack"); + + /* 获取栈的长度 */ + final int size = stack.length; + print("栈的长度 size = $size"); + + /* 判断是否为空 */ + final bool isEmpty = stack.isEmpty; + print("栈是否为空 = $isEmpty"); +} diff --git a/codes/dart/chapter_tree/array_binary_tree.dart b/codes/dart/chapter_tree/array_binary_tree.dart new file mode 100644 index 0000000000..c37a3bcc65 --- /dev/null +++ b/codes/dart/chapter_tree/array_binary_tree.dart @@ -0,0 +1,152 @@ +/** + * File: array_binary_tree.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree { + late List _tree; + + /* 构造方法 */ + ArrayBinaryTree(this._tree); + + /* 列表容量 */ + int size() { + return _tree.length; + } + + /* 获取索引为 i 节点的值 */ + int? val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) { + return null; + } + return _tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + int? left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + int? right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + int? parent(int i) { + return (i - 1) ~/ 2; + } + + /* 层序遍历 */ + List levelOrder() { + List res = []; + for (int i = 0; i < size(); i++) { + if (val(i) != null) { + res.add(val(i)!); + } + } + return res; + } + + /* 深度优先遍历 */ + void dfs(int i, String order, List res) { + // 若为空位,则返回 + if (val(i) == null) { + return; + } + // 前序遍历 + if (order == 'pre') { + res.add(val(i)); + } + dfs(left(i)!, order, res); + // 中序遍历 + if (order == 'in') { + res.add(val(i)); + } + dfs(right(i)!, order, res); + // 后序遍历 + if (order == 'post') { + res.add(val(i)); + } + } + + /* 前序遍历 */ + List preOrder() { + List res = []; + dfs(0, 'pre', res); + return res; + } + + /* 中序遍历 */ + List inOrder() { + List res = []; + dfs(0, 'in', res); + return res; + } + + /* 后序遍历 */ + List postOrder() { + List res = []; + dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +void main() { + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + List arr = [ + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 + ]; + + TreeNode? root = listToTree(arr); + print("\n初始化二叉树\n"); + print("二叉树的数组表示:"); + print(arr); + print("二叉树的链表表示:"); + printTree(root); + + // 数组表示下的二叉树类 + ArrayBinaryTree abt = ArrayBinaryTree(arr); + + // 访问节点 + int i = 1; + int? l = abt.left(i); + int? r = abt.right(i); + int? p = abt.parent(i); + print("\n当前节点的索引为 $i ,值为 ${abt.val(i)}"); + print("其左子节点的索引为 $l ,值为 ${(l == null ? "null" : abt.val(l))}"); + print("其右子节点的索引为 $r ,值为 ${(r == null ? "null" : abt.val(r))}"); + print("其父节点的索引为 $p ,值为 ${(p == null ? "null" : abt.val(p))}"); + + // 遍历树 + List res = abt.levelOrder(); + print("\n层序遍历为:$res"); + res = abt.preOrder(); + print("前序遍历为 $res"); + res = abt.inOrder(); + print("中序遍历为 $res"); + res = abt.postOrder(); + print("后序遍历为 $res"); +} diff --git a/codes/dart/chapter_tree/avl_tree.dart b/codes/dart/chapter_tree/avl_tree.dart new file mode 100644 index 0000000000..0db64270ba --- /dev/null +++ b/codes/dart/chapter_tree/avl_tree.dart @@ -0,0 +1,218 @@ +/** + * File: avl_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +class AVLTree { + TreeNode? root; + + /* 构造方法 */ + AVLTree() { + root = null; + } + + /* 获取节点高度 */ + int height(TreeNode? node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == null ? -1 : node.height; + } + + /* 更新节点高度 */ + void updateHeight(TreeNode? node) { + // 节点高度等于最高子树高度 + 1 + node!.height = max(height(node.left), height(node.right)) + 1; + } + + /* 获取平衡因子 */ + int balanceFactor(TreeNode? node) { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right); + } + + /* 右旋操作 */ + TreeNode? rightRotate(TreeNode? node) { + TreeNode? child = node!.left; + TreeNode? grandChild = child!.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 左旋操作 */ + TreeNode? leftRotate(TreeNode? node) { + TreeNode? child = node!.right; + TreeNode? grandChild = child!.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode? rotate(TreeNode? node) { + // 获取节点 node 的平衡因子 + int factor = balanceFactor(node); + // 左偏树 + if (factor > 1) { + if (balanceFactor(node!.left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右偏树 + if (factor < -1) { + if (balanceFactor(node!.right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 递归插入节点(辅助方法) */ + TreeNode? insertHelper(TreeNode? node, int val) { + if (node == null) return TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重复节点不插入,直接返回 + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 递归删除节点(辅助方法) */ + TreeNode? removeHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null; + // 子节点数量 = 1 ,直接删除 node + else + node = child; + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode? temp = node.right; + while (temp!.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + /* 查找节点 */ + TreeNode? search(int val) { + TreeNode? cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (val < cur.val) + cur = cur.left; + // 目标节点在 cur 的左子树中 + else if (val > cur.val) + cur = cur.right; + // 目标节点与当前节点相等 + else + break; + } + return cur; + } +} + +void testInsert(AVLTree tree, int val) { + tree.insert(val); + print("\n插入节点 $val 后,AVL 树为"); + printTree(tree.root); +} + +void testRemove(AVLTree tree, int val) { + tree.remove(val); + print("\n删除节点 $val 后,AVL 树为"); + printTree(tree.root); +} + +/* Driver Code */ +void main() { + /* 初始化空 AVL 树 */ + AVLTree avlTree = AVLTree(); + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重复节点 */ + testInsert(avlTree, 7); + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(avlTree, 8); // 删除度为 0 的节点 + testRemove(avlTree, 5); // 删除度为 1 的节点 + testRemove(avlTree, 4); // 删除度为 2 的节点 + + /* 查询节点 */ + TreeNode? node = avlTree.search(7); + print("\n查找到的节点对象为 $node ,节点值 = ${node!.val}"); +} diff --git a/codes/dart/chapter_tree/binary_search_tree.dart b/codes/dart/chapter_tree/binary_search_tree.dart new file mode 100644 index 0000000000..4deaef3d42 --- /dev/null +++ b/codes/dart/chapter_tree/binary_search_tree.dart @@ -0,0 +1,153 @@ +/** + * File: binary_search_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 二叉搜索树 */ +class BinarySearchTree { + late TreeNode? _root; + + /* 构造方法 */ + BinarySearchTree() { + // 初始化空树 + _root = null; + } + + /* 获取二叉树的根节点 */ + TreeNode? getRoot() { + return _root; + } + + /* 查找节点 */ + TreeNode? search(int _num) { + TreeNode? cur = _root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.val < _num) + cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > _num) + cur = cur.left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + + /* 插入节点 */ + void insert(int _num) { + // 若树为空,则初始化根节点 + if (_root == null) { + _root = TreeNode(_num); + return; + } + TreeNode? cur = _root; + TreeNode? pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.val == _num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < _num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; + } + // 插入节点 + TreeNode? node = TreeNode(_num); + if (pre!.val < _num) + pre.right = node; + else + pre.left = node; + } + + /* 删除节点 */ + void remove(int _num) { + // 若树为空,直接提前返回 + if (_root == null) return; + TreeNode? cur = _root; + TreeNode? pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.val == _num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < _num) + cur = cur.right; + // 待删除节点在 cur 的左子树中 + else + cur = cur.left; + } + // 若无待删除节点,直接返回 + if (cur == null) return; + // 子节点数量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + TreeNode? child = cur.left ?? cur.right; + // 删除节点 cur + if (cur != _root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + _root = child; + } + } else { + // 子节点数量 = 2 + // 获取中序遍历中 cur 的下一个节点 + TreeNode? tmp = cur.right; + while (tmp!.left != null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +void main() { + /* 初始化二叉搜索树 */ + BinarySearchTree bst = BinarySearchTree(); + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for (int _num in nums) { + bst.insert(_num); + } + print("\n初始化的二叉树为\n"); + printTree(bst.getRoot()); + + /* 查找节点 */ + TreeNode? node = bst.search(7); + print("\n查找到的节点对象为 $node ,节点值 = ${node?.val}"); + + /* 插入节点 */ + bst.insert(16); + print("\n插入节点 16 后,二叉树为\n"); + printTree(bst.getRoot()); + + /* 删除节点 */ + bst.remove(1); + print("\n删除节点 1 后,二叉树为\n"); + printTree(bst.getRoot()); + bst.remove(2); + print("\n删除节点 2 后,二叉树为\n"); + printTree(bst.getRoot()); + bst.remove(4); + print("\n删除节点 4 后,二叉树为\n"); + printTree(bst.getRoot()); +} diff --git a/codes/dart/chapter_tree/binary_tree.dart b/codes/dart/chapter_tree/binary_tree.dart new file mode 100644 index 0000000000..757578b41d --- /dev/null +++ b/codes/dart/chapter_tree/binary_tree.dart @@ -0,0 +1,37 @@ +/** + * File: binary_tree.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +void main() { + /* 初始化二叉树 */ + // 舒适化节点 + TreeNode n1 = TreeNode(1); + TreeNode n2 = TreeNode(2); + TreeNode n3 = TreeNode(3); + TreeNode n4 = TreeNode(4); + TreeNode n5 = TreeNode(5); + // 构建节点之间的引用(指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + print("\n初始化二叉树\n"); + printTree(n1); + + /* 插入与删除节点 */ + TreeNode p = TreeNode(0); + // 在 n1 -> n2 中间插入节点 p + n1.left = p; + p.left = n2; + print("\n插入节点 P 后\n"); + printTree(n1); + // 删除节点 P + n1.left = n2; + print("\n删除节点 P 后\n"); + printTree(n1); +} diff --git a/codes/dart/chapter_tree/binary_tree_bfs.dart b/codes/dart/chapter_tree/binary_tree_bfs.dart new file mode 100644 index 0000000000..21bb0fa24b --- /dev/null +++ b/codes/dart/chapter_tree/binary_tree_bfs.dart @@ -0,0 +1,38 @@ +/** + * File: binary_tree_bfs.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmai.com) + */ + +import 'dart:collection'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 层序遍历 */ +List levelOrder(TreeNode? root) { + // 初始化队列,加入根节点 + Queue queue = Queue(); + queue.add(root); + // 初始化一个列表,用于保存遍历序列 + List res = []; + while (queue.isNotEmpty) { + TreeNode? node = queue.removeFirst(); // 队列出队 + res.add(node!.val); // 保存节点值 + if (node.left != null) queue.add(node.left); // 左子节点入队 + if (node.right != null) queue.add(node.right); // 右子节点入队 + } + return res; +} + +/* Driver Code */ +void main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n初始化二叉树\n"); + printTree(root); + + // 层序遍历 + List res = levelOrder(root); + print("\n层序遍历的节点打印序列 = $res"); +} diff --git a/codes/dart/chapter_tree/binary_tree_dfs.dart b/codes/dart/chapter_tree/binary_tree_dfs.dart new file mode 100644 index 0000000000..a59eb76b05 --- /dev/null +++ b/codes/dart/chapter_tree/binary_tree_dfs.dart @@ -0,0 +1,62 @@ +/** + * File: binary_tree_dfs.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +// 初始化列表,用于存储遍历序列 +List list = []; + +/* 前序遍历 */ +void preOrder(TreeNode? node) { + if (node == null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.add(node.val); + preOrder(node.left); + preOrder(node.right); +} + +/* 中序遍历 */ +void inOrder(TreeNode? node) { + if (node == null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(node.left); + list.add(node.val); + inOrder(node.right); +} + +/* 后序遍历 */ +void postOrder(TreeNode? node) { + if (node == null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(node.left); + postOrder(node.right); + list.add(node.val); +} + +/* Driver Code */ +void main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n初始化二叉树\n"); + printTree(root); + + /* 前序遍历 */ + list.clear(); + preOrder(root); + print("\n前序遍历的节点打印序列 = $list"); + + /* 中序遍历 */ + list.clear(); + inOrder(root); + print("\n中序遍历的节点打印序列 = $list"); + + /* 后序遍历 */ + list.clear(); + postOrder(root); + print("\n后序遍历的节点打印序列 = $list"); +} diff --git a/codes/dart/utils/list_node.dart b/codes/dart/utils/list_node.dart new file mode 100644 index 0000000000..b77f61d1c3 --- /dev/null +++ b/codes/dart/utils/list_node.dart @@ -0,0 +1,24 @@ +/** + * File: list_node.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 链表节点 */ +class ListNode { + int val; + ListNode? next; + + ListNode(this.val, [this.next]); +} + +/* 将列表反序列化为链表 */ +ListNode? listToLinkedList(List list) { + ListNode dum = ListNode(0); + ListNode? head = dum; + for (int val in list) { + head?.next = ListNode(val); + head = head?.next; + } + return dum.next; +} diff --git a/codes/dart/utils/print_util.dart b/codes/dart/utils/print_util.dart new file mode 100644 index 0000000000..49cccc2244 --- /dev/null +++ b/codes/dart/utils/print_util.dart @@ -0,0 +1,90 @@ +/** + * File: print_util.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:io'; + +import 'list_node.dart'; +import 'tree_node.dart'; + +class Trunk { + Trunk? prev; + String str; + + Trunk(this.prev, this.str); +} + +/* 打印矩阵 (Array) */ +void printMatrix(List> matrix) { + print("["); + for (List row in matrix) { + print(" $row,"); + } + print("]"); +} + +/* 打印链表 */ +void printLinkedList(ListNode? head) { + List list = []; + + while (head != null) { + list.add('${head.val}'); + head = head.next; + } + + print(list.join(' -> ')); +} + +/** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { + if (root == null) { + return; + } + + String prev_str = ' '; + Trunk trunk = Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + showTrunks(trunk); + print(' ${root.val}'); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +void showTrunks(Trunk? p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + stdout.write(p.str); +} + +/* 打印堆 */ +void printHeap(List heap) { + print("堆的数组表示:$heap"); + print("堆的树状表示:"); + TreeNode? root = listToTree(heap); + printTree(root); +} diff --git a/codes/dart/utils/tree_node.dart b/codes/dart/utils/tree_node.dart new file mode 100644 index 0000000000..dcf1fcfda8 --- /dev/null +++ b/codes/dart/utils/tree_node.dart @@ -0,0 +1,50 @@ +/** + * File: tree_node.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二叉树节点类 */ +class TreeNode { + int val; // 节点值 + int height; // 节点高度 + TreeNode? left; // 左子节点引用 + TreeNode? right; // 右子节点引用 + + /* 构造方法 */ + TreeNode(this.val, [this.height = 0, this.left, this.right]); +} + +/* 将列表反序列化为二叉树:递归 */ +TreeNode? listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.length || arr[i] == null) { + return null; + } + TreeNode? root = TreeNode(arr[i]!); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* 将列表反序列化为二叉树 */ +TreeNode? listToTree(List arr) { + return listToTreeDFS(arr, 0); +} + +/* 将二叉树序列化为列表:递归 */ +void treeToListDFS(TreeNode? root, int i, List res) { + if (root == null) return; + while (i >= res.length) { + res.add(null); + } + res[i] = root.val; + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); +} + +/* 将二叉树序列化为列表 */ +List treeToList(TreeNode? root) { + List res = []; + treeToListDFS(root, 0, res); + return res; +} diff --git a/codes/dart/utils/vertex.dart b/codes/dart/utils/vertex.dart new file mode 100644 index 0000000000..fe81e6631e --- /dev/null +++ b/codes/dart/utils/vertex.dart @@ -0,0 +1,29 @@ +/** + * File: Vertex.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 顶点类 */ +class Vertex { + int val; + Vertex(this.val); + + /* 输入值列表 vals ,返回顶点列表 vets */ + static List valsToVets(List vals) { + List vets = []; + for (int i in vals) { + vets.add(Vertex(i)); + } + return vets; + } + + /* 输入顶点列表 vets ,返回值列表 vals */ + static List vetsToVals(List vets) { + List vals = []; + for (Vertex vet in vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/codes/docker-compose.yml b/codes/docker-compose.yml new file mode 100644 index 0000000000..7c11b7c5b6 --- /dev/null +++ b/codes/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' +services: + hello-algo-code: + build: + context: . + args: + # 设置需要安装的语言,使用空格隔开 + # Set the languages to be installed, separated by spaces + LANGS: "python cpp java csharp" + image: hello-algo-code + container_name: hello-algo-code + stdin_open: true + tty: true diff --git a/codes/go/chapter_array_and_linkedlist/array.go b/codes/go/chapter_array_and_linkedlist/array.go index 0cf0d5d843..325276c588 100644 --- a/codes/go/chapter_array_and_linkedlist/array.go +++ b/codes/go/chapter_array_and_linkedlist/array.go @@ -8,7 +8,7 @@ import ( "math/rand" ) -/* 随机返回一个数组元素 */ +/* 随机访问元素 */ func randomAccess(nums []int) (randomNum int) { // 在区间 [0, nums.length) 中随机抽取一个数字 randomIndex := rand.Intn(len(nums)) @@ -35,11 +35,11 @@ func insert(nums []int, num int, index int) { for i := len(nums) - 1; i > index; i-- { nums[i] = nums[i-1] } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num } -/* 删除索引 index 处元素 */ +/* 删除索引 index 处的元素 */ func remove(nums []int, index int) { // 把索引 index 之后的所有元素向前移动一位 for i := index; i < len(nums)-1; i++ { @@ -52,12 +52,17 @@ func traverse(nums []int) { count := 0 // 通过索引遍历数组 for i := 0; i < len(nums); i++ { - count++ + count += nums[i] } count = 0 - // 直接遍历数组 - for range nums { - count++ + // 直接遍历数组元素 + for _, num := range nums { + count += num + } + // 同时遍历数据索引和元素 + for i, num := range nums { + count += nums[i] + count += num } } diff --git a/codes/go/chapter_array_and_linkedlist/array_test.go b/codes/go/chapter_array_and_linkedlist/array_test.go index 3d4e8f8870..d9b8b0f40a 100644 --- a/codes/go/chapter_array_and_linkedlist/array_test.go +++ b/codes/go/chapter_array_and_linkedlist/array_test.go @@ -21,7 +21,7 @@ func TestArray(t *testing.T) { fmt.Println("数组 arr =", arr) // 在 Go 中,指定长度时([5]int)为数组,不指定长度时([]int)为切片 // 由于 Go 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 - // 为了方便实现扩容 extend() 方法,以下将切片(Slice)看作数组(Array) + // 为了方便实现扩容 extend() 函数,以下将切片(Slice)看作数组(Array) nums := []int{1, 3, 2, 5, 4} fmt.Println("数组 nums =", nums) diff --git a/codes/go/chapter_array_and_linkedlist/linked_list.go b/codes/go/chapter_array_and_linkedlist/linked_list.go index 8a60fd18e7..f7158a653d 100644 --- a/codes/go/chapter_array_and_linkedlist/linked_list.go +++ b/codes/go/chapter_array_and_linkedlist/linked_list.go @@ -8,15 +8,15 @@ import ( . "github.com/krahets/hello-algo/pkg" ) -/* 在链表的结点 n0 之后插入结点 P */ +/* 在链表的节点 n0 之后插入节点 P */ func insertNode(n0 *ListNode, P *ListNode) { n1 := n0.Next - n0.Next = P P.Next = n1 + n0.Next = P } -/* 删除链表的结点 n0 之后的首个结点 */ -func removeNode(n0 *ListNode) { +/* 删除链表的节点 n0 之后的首个节点 */ +func removeItem(n0 *ListNode) { if n0.Next == nil { return } @@ -26,7 +26,7 @@ func removeNode(n0 *ListNode) { n0.Next = n1 } -/* 访问链表中索引为 index 的结点 */ +/* 访问链表中索引为 index 的节点 */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { if head == nil { @@ -37,7 +37,7 @@ func access(head *ListNode, index int) *ListNode { return head } -/* 在链表中查找值为 target 的首个结点 */ +/* 在链表中查找值为 target 的首个节点 */ func findNode(head *ListNode, target int) int { index := 0 for head != nil { diff --git a/codes/go/chapter_array_and_linkedlist/linked_list_test.go b/codes/go/chapter_array_and_linkedlist/linked_list_test.go index 466bde4f55..b12c725e62 100644 --- a/codes/go/chapter_array_and_linkedlist/linked_list_test.go +++ b/codes/go/chapter_array_and_linkedlist/linked_list_test.go @@ -11,16 +11,16 @@ import ( . "github.com/krahets/hello-algo/pkg" ) -func TestLikedList(t *testing.T) { +func TestLinkedList(t *testing.T) { /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) - // 构建引用指向 + // 构建节点之间的引用 n0.Next = n1 n1.Next = n2 n2.Next = n3 @@ -28,21 +28,21 @@ func TestLikedList(t *testing.T) { fmt.Println("初始化的链表为") PrintLinkedList(n0) - /* 插入结点 */ + /* 插入节点 */ insertNode(n0, NewListNode(0)) - fmt.Println("插入结点后的链表为") + fmt.Println("插入节点后的链表为") PrintLinkedList(n0) - /* 删除结点 */ - removeNode(n0) - fmt.Println("删除结点后的链表为") + /* 删除节点 */ + removeItem(n0) + fmt.Println("删除节点后的链表为") PrintLinkedList(n0) - /* 访问结点 */ + /* 访问节点 */ node := access(n0, 3) - fmt.Println("链表中索引 3 处的结点的值 =", node) + fmt.Println("链表中索引 3 处的节点的值 =", node) - /* 查找结点 */ + /* 查找节点 */ index := findNode(n0, 2) - fmt.Println("链表中值为 2 的结点的索引 =", index) + fmt.Println("链表中值为 2 的节点的索引 =", index) } diff --git a/codes/go/chapter_array_and_linkedlist/list_test.go b/codes/go/chapter_array_and_linkedlist/list_test.go index 141f83ad70..35e43a85ef 100644 --- a/codes/go/chapter_array_and_linkedlist/list_test.go +++ b/codes/go/chapter_array_and_linkedlist/list_test.go @@ -13,55 +13,54 @@ import ( /* Driver Code */ func TestList(t *testing.T) { /* 初始化列表 */ - list := []int{1, 3, 2, 5, 4} - fmt.Println("列表 list =", list) + nums := []int{1, 3, 2, 5, 4} + fmt.Println("列表 nums =", nums) /* 访问元素 */ - num := list[1] // 访问索引 1 处的元素 + num := nums[1] // 访问索引 1 处的元素 fmt.Println("访问索引 1 处的元素,得到 num =", num) /* 更新元素 */ - list[1] = 0 // 将索引 1 处的元素更新为 0 - fmt.Println("将索引 1 处的元素更新为 0 ,得到 list =", list) + nums[1] = 0 // 将索引 1 处的元素更新为 0 + fmt.Println("将索引 1 处的元素更新为 0 ,得到 nums =", nums) /* 清空列表 */ - list = nil - fmt.Println("清空列表后 list =", list) + nums = nil + fmt.Println("清空列表后 nums =", nums) - /* 尾部添加元素 */ - list = append(list, 1) - list = append(list, 3) - list = append(list, 2) - list = append(list, 5) - list = append(list, 4) - fmt.Println("添加元素后 list =", list) + /* 在尾部添加元素 */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + fmt.Println("添加元素后 nums =", nums) - /* 中间插入元素 */ - list = append(list[:3], append([]int{6}, list[3:]...)...) // 在索引 3 处插入数字 6 - fmt.Println("在索引 3 处插入数字 6 ,得到 list =", list) + /* 在中间插入元素 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 处插入数字 6 + fmt.Println("在索引 3 处插入数字 6 ,得到 nums =", nums) /* 删除元素 */ - list = append(list[:3], list[4:]...) // 删除索引 3 处的元素 - fmt.Println("删除索引 3 处的元素,得到 list =", list) + nums = append(nums[:3], nums[4:]...) // 删除索引 3 处的元素 + fmt.Println("删除索引 3 处的元素,得到 nums =", nums) /* 通过索引遍历列表 */ count := 0 - for i := 0; i < len(list); i++ { - count++ + for i := 0; i < len(nums); i++ { + count += nums[i] } - /* 直接遍历列表元素 */ count = 0 - for range list { - count++ + for _, x := range nums { + count += x } /* 拼接两个列表 */ - list1 := []int{6, 8, 7, 10, 9} - list = append(list, list1...) // 将列表 list1 拼接到 list 之后 - fmt.Println("将列表 list1 拼接到 list 之后,得到 list =", list) + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // 将列表 nums1 拼接到 nums 之后 + fmt.Println("将列表 nums1 拼接到 nums 之后,得到 nums =", nums) /* 排序列表 */ - sort.Ints(list) // 排序后,列表元素从小到大排列 - fmt.Println("排序列表后 list =", list) -} \ No newline at end of file + sort.Ints(nums) // 排序后,列表元素从小到大排列 + fmt.Println("排序列表后 nums =", nums) +} diff --git a/codes/go/chapter_array_and_linkedlist/my_list.go b/codes/go/chapter_array_and_linkedlist/my_list.go index 134096d0a8..c507275cd6 100644 --- a/codes/go/chapter_array_and_linkedlist/my_list.go +++ b/codes/go/chapter_array_and_linkedlist/my_list.go @@ -4,106 +4,106 @@ package chapter_array_and_linkedlist -/* 列表类简易实现 */ +/* 列表类 */ type myList struct { - numsCapacity int - nums []int - numsSize int - extendRatio int + arrCapacity int + arr []int + arrSize int + extendRatio int } /* 构造函数 */ func newMyList() *myList { return &myList{ - numsCapacity: 10, // 列表容量 - nums: make([]int, 10), // 数组(存储列表元素) - numsSize: 0, // 列表长度(即当前元素数量) - extendRatio: 2, // 每次列表扩容的倍数 + arrCapacity: 10, // 列表容量 + arr: make([]int, 10), // 数组(存储列表元素) + arrSize: 0, // 列表长度(当前元素数量) + extendRatio: 2, // 每次列表扩容的倍数 } } -/* 获取列表长度(即当前元素数量) */ +/* 获取列表长度(当前元素数量) */ func (l *myList) size() int { - return l.numsSize + return l.arrSize } /* 获取列表容量 */ func (l *myList) capacity() int { - return l.numsCapacity + return l.arrCapacity } /* 访问元素 */ func (l *myList) get(index int) int { - // 索引如果越界则抛出异常,下同 - if index >= l.numsSize { + // 索引如果越界,则抛出异常,下同 + if index < 0 || index >= l.arrSize { panic("索引越界") } - return l.nums[index] + return l.arr[index] } /* 更新元素 */ func (l *myList) set(num, index int) { - if index >= l.numsSize { + if index < 0 || index >= l.arrSize { panic("索引越界") } - l.nums[index] = num + l.arr[index] = num } -/* 尾部添加元素 */ +/* 在尾部添加元素 */ func (l *myList) add(num int) { // 元素数量超出容量时,触发扩容机制 - if l.numsSize == l.numsCapacity { + if l.arrSize == l.arrCapacity { l.extendCapacity() } - l.nums[l.numsSize] = num + l.arr[l.arrSize] = num // 更新元素数量 - l.numsSize++ + l.arrSize++ } -/* 中间插入元素 */ +/* 在中间插入元素 */ func (l *myList) insert(num, index int) { - if index >= l.numsSize { + if index < 0 || index >= l.arrSize { panic("索引越界") } // 元素数量超出容量时,触发扩容机制 - if l.numsSize == l.numsCapacity { + if l.arrSize == l.arrCapacity { l.extendCapacity() } - // 索引 i 以及之后的元素都向后移动一位 - for j := l.numsSize - 1; j >= index; j-- { - l.nums[j+1] = l.nums[j] + // 将索引 index 以及之后的元素都向后移动一位 + for j := l.arrSize - 1; j >= index; j-- { + l.arr[j+1] = l.arr[j] } - l.nums[index] = num + l.arr[index] = num // 更新元素数量 - l.numsSize++ + l.arrSize++ } /* 删除元素 */ func (l *myList) remove(index int) int { - if index >= l.numsSize { + if index < 0 || index >= l.arrSize { panic("索引越界") } - num := l.nums[index] - // 索引 i 之后的元素都向前移动一位 - for j := index; j < l.numsSize-1; j++ { - l.nums[j] = l.nums[j+1] + num := l.arr[index] + // 将索引 index 之后的元素都向前移动一位 + for j := index; j < l.arrSize-1; j++ { + l.arr[j] = l.arr[j+1] } // 更新元素数量 - l.numsSize-- - // 返回被删除元素 + l.arrSize-- + // 返回被删除的元素 return num } /* 列表扩容 */ func (l *myList) extendCapacity() { - // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 - l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...) + // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组复制到新数组 + l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) // 更新列表容量 - l.numsCapacity = len(l.nums) + l.arrCapacity = len(l.arr) } /* 返回有效长度的列表 */ func (l *myList) toArray() []int { // 仅转换有效长度范围内的列表元素 - return l.nums[:l.numsSize] + return l.arr[:l.arrSize] } diff --git a/codes/go/chapter_array_and_linkedlist/my_list_test.go b/codes/go/chapter_array_and_linkedlist/my_list_test.go index afe16e4dfe..755f5bbfae 100644 --- a/codes/go/chapter_array_and_linkedlist/my_list_test.go +++ b/codes/go/chapter_array_and_linkedlist/my_list_test.go @@ -12,35 +12,35 @@ import ( /* Driver Code */ func TestMyList(t *testing.T) { /* 初始化列表 */ - list := newMyList() - /* 尾部添加元素 */ - list.add(1) - list.add(3) - list.add(2) - list.add(5) - list.add(4) - fmt.Printf("列表 list = %v ,容量 = %v ,长度 = %v\n", list.toArray(), list.capacity(), list.size()) - - /* 中间插入元素 */ - list.insert(6, 3) - fmt.Printf("在索引 3 处插入数字 6 ,得到 list = %v\n", list.toArray()) + nums := newMyList() + /* 在尾部添加元素 */ + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + fmt.Printf("列表 nums = %v ,容量 = %v ,长度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) + + /* 在中间插入元素 */ + nums.insert(6, 3) + fmt.Printf("在索引 3 处插入数字 6 ,得到 nums = %v\n", nums.toArray()) /* 删除元素 */ - list.remove(3) - fmt.Printf("删除索引 3 处的元素,得到 list = %v\n", list.toArray()) + nums.remove(3) + fmt.Printf("删除索引 3 处的元素,得到 nums = %v\n", nums.toArray()) /* 访问元素 */ - num := list.get(1) + num := nums.get(1) fmt.Printf("访问索引 1 处的元素,得到 num = %v\n", num) /* 更新元素 */ - list.set(0, 1) - fmt.Printf("将索引 1 处的元素更新为 0 ,得到 list = %v\n", list.toArray()) + nums.set(0, 1) + fmt.Printf("将索引 1 处的元素更新为 0 ,得到 nums = %v\n", nums.toArray()) /* 测试扩容机制 */ for i := 0; i < 10; i++ { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list.add(i) + nums.add(i) } - fmt.Printf("扩容后的列表 list = %v ,容量 = %v ,长度 = %v\n", list.toArray(), list.capacity(), list.size()) -} \ No newline at end of file + fmt.Printf("扩容后的列表 nums = %v ,容量 = %v ,长度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) +} diff --git a/codes/go/chapter_backtracking/n_queens.go b/codes/go/chapter_backtracking/n_queens.go new file mode 100644 index 0000000000..5d7e7fb230 --- /dev/null +++ b/codes/go/chapter_backtracking/n_queens.go @@ -0,0 +1,57 @@ +// File: n_queens.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯算法:n 皇后 */ +func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { + // 当放置完所有行时,记录解 + if row == n { + newState := make([][]string, len(*state)) + for i, _ := range newState { + newState[i] = make([]string, len((*state)[0])) + copy(newState[i], (*state)[i]) + + } + *res = append(*res, newState) + return + } + // 遍历所有列 + for col := 0; col < n; col++ { + // 计算该格子对应的主对角线和次对角线 + diag1 := row - col + n - 1 + diag2 := row + col + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { + // 尝试:将皇后放置在该格子 + (*state)[row][col] = "Q" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true + // 放置下一行 + backtrack(row+1, n, state, res, cols, diags1, diags2) + // 回退:将该格子恢复为空位 + (*state)[row][col] = "#" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false + } + } +} + +/* 求解 n 皇后 */ +func nQueens(n int) [][][]string { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + state := make([][]string, n) + for i := 0; i < n; i++ { + row := make([]string, n) + for i := 0; i < n; i++ { + row[i] = "#" + } + state[i] = row + } + // 记录列是否有皇后 + cols := make([]bool, n) + diags1 := make([]bool, 2*n-1) + diags2 := make([]bool, 2*n-1) + res := make([][][]string, 0) + backtrack(0, n, &state, &res, &cols, &diags1, &diags2) + return res +} diff --git a/codes/go/chapter_backtracking/n_queens_test.go b/codes/go/chapter_backtracking/n_queens_test.go new file mode 100644 index 0000000000..5279a8280f --- /dev/null +++ b/codes/go/chapter_backtracking/n_queens_test.go @@ -0,0 +1,24 @@ +// File: n_queens_test.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" +) + +func TestNQueens(t *testing.T) { + n := 4 + res := nQueens(n) + + fmt.Println("输入棋盘长宽为 ", n) + fmt.Println("皇后放置方案共有 ", len(res), " 种") + for _, state := range res { + fmt.Println("--------------------") + for _, row := range state { + fmt.Println(row) + } + } +} diff --git a/codes/go/chapter_backtracking/permutation_test.go b/codes/go/chapter_backtracking/permutation_test.go new file mode 100644 index 0000000000..c810a8e782 --- /dev/null +++ b/codes/go/chapter_backtracking/permutation_test.go @@ -0,0 +1,33 @@ +// File: permutation_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPermutationI(t *testing.T) { + /* 全排列 I */ + nums := []int{1, 2, 3} + fmt.Printf("输入数组 nums = ") + PrintSlice(nums) + + res := permutationsI(nums) + fmt.Printf("所有排列 res = ") + fmt.Println(res) +} + +func TestPermutationII(t *testing.T) { + nums := []int{1, 2, 2} + fmt.Printf("输入数组 nums = ") + PrintSlice(nums) + + res := permutationsII(nums) + fmt.Printf("所有排列 res = ") + fmt.Println(res) +} diff --git a/codes/go/chapter_backtracking/permutations_i.go b/codes/go/chapter_backtracking/permutations_i.go new file mode 100644 index 0000000000..ed50ece9c8 --- /dev/null +++ b/codes/go/chapter_backtracking/permutations_i.go @@ -0,0 +1,38 @@ +// File: permutations_i.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯算法:全排列 I */ +func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 当状态长度等于元素数量时,记录解 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // 遍历所有选择 + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 剪枝:不允许重复选择元素 + if !(*selected)[i] { + // 尝试:做出选择,更新状态 + (*selected)[i] = true + *state = append(*state, choice) + // 进行下一轮选择 + backtrackI(state, choices, selected, res) + // 回退:撤销选择,恢复到之前的状态 + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全排列 I */ +func permutationsI(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackI(&state, &nums, &selected, &res) + return res +} diff --git a/codes/go/chapter_backtracking/permutations_ii.go b/codes/go/chapter_backtracking/permutations_ii.go new file mode 100644 index 0000000000..34a866c9ee --- /dev/null +++ b/codes/go/chapter_backtracking/permutations_ii.go @@ -0,0 +1,41 @@ +// File: permutations_ii.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯算法:全排列 II */ +func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 当状态长度等于元素数量时,记录解 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // 遍历所有选择 + duplicated := make(map[int]struct{}, 0) + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if _, ok := duplicated[choice]; !ok && !(*selected)[i] { + // 尝试:做出选择,更新状态 + // 记录选择过的元素值 + duplicated[choice] = struct{}{} + (*selected)[i] = true + *state = append(*state, choice) + // 进行下一轮选择 + backtrackII(state, choices, selected, res) + // 回退:撤销选择,恢复到之前的状态 + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全排列 II */ +func permutationsII(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackII(&state, &nums, &selected, &res) + return res +} diff --git a/codes/go/chapter_backtracking/preorder_traversal_i_compact.go b/codes/go/chapter_backtracking/preorder_traversal_i_compact.go new file mode 100644 index 0000000000..f091a3bfc1 --- /dev/null +++ b/codes/go/chapter_backtracking/preorder_traversal_i_compact.go @@ -0,0 +1,22 @@ +// File: preorder_traversal_i_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序遍历:例题一 */ +func preOrderI(root *TreeNode, res *[]*TreeNode) { + if root == nil { + return + } + if (root.Val).(int) == 7 { + // 记录解 + *res = append(*res, root) + } + preOrderI(root.Left, res) + preOrderI(root.Right, res) +} diff --git a/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go b/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go new file mode 100644 index 0000000000..b6064412c4 --- /dev/null +++ b/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go @@ -0,0 +1,26 @@ +// File: preorder_traversal_ii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序遍历:例题二 */ +func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + if root == nil { + return + } + // 尝试 + *path = append(*path, root) + if root.Val.(int) == 7 { + // 记录解 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderII(root.Left, res, path) + preOrderII(root.Right, res, path) + // 回退 + *path = (*path)[:len(*path)-1] +} diff --git a/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go b/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go new file mode 100644 index 0000000000..f9ce23bb8a --- /dev/null +++ b/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go @@ -0,0 +1,27 @@ +// File: preorder_traversal_iii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序遍历:例题三 */ +func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + // 剪枝 + if root == nil || root.Val == 3 { + return + } + // 尝试 + *path = append(*path, root) + if root.Val.(int) == 7 { + // 记录解 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderIII(root.Left, res, path) + preOrderIII(root.Right, res, path) + // 回退 + *path = (*path)[:len(*path)-1] +} diff --git a/codes/go/chapter_backtracking/preorder_traversal_iii_template.go b/codes/go/chapter_backtracking/preorder_traversal_iii_template.go new file mode 100644 index 0000000000..b7901df475 --- /dev/null +++ b/codes/go/chapter_backtracking/preorder_traversal_iii_template.go @@ -0,0 +1,57 @@ +// File: preorder_traversal_iii_template.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 判断当前状态是否为解 */ +func isSolution(state *[]*TreeNode) bool { + return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 +} + +/* 记录解 */ +func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { + *res = append(*res, append([]*TreeNode{}, *state...)) +} + +/* 判断在当前状态下,该选择是否合法 */ +func isValid(state *[]*TreeNode, choice *TreeNode) bool { + return choice != nil && choice.Val != 3 +} + +/* 更新状态 */ +func makeChoice(state *[]*TreeNode, choice *TreeNode) { + *state = append(*state, choice) +} + +/* 恢复状态 */ +func undoChoice(state *[]*TreeNode, choice *TreeNode) { + *state = (*state)[:len(*state)-1] +} + +/* 回溯算法:例题三 */ +func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { + // 检查是否为解 + if isSolution(state) { + // 记录解 + recordSolution(state, res) + } + // 遍历所有选择 + for _, choice := range *choices { + // 剪枝:检查选择是否合法 + if isValid(state, choice) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice) + // 进行下一轮选择 + temp := make([]*TreeNode, 0) + temp = append(temp, choice.Left, choice.Right) + backtrackIII(state, &temp, res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice) + } + } +} diff --git a/codes/go/chapter_backtracking/preorder_traversal_test.go b/codes/go/chapter_backtracking/preorder_traversal_test.go new file mode 100644 index 0000000000..32d8b9621e --- /dev/null +++ b/codes/go/chapter_backtracking/preorder_traversal_test.go @@ -0,0 +1,91 @@ +// File: preorder_traversal_i_compact_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreorderTraversalICompact(t *testing.T) { + /* 初始化二叉树 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树") + PrintTree(root) + + // 前序遍历 + res := make([]*TreeNode, 0) + preOrderI(root, &res) + + fmt.Println("\n输出所有值为 7 的节点") + for _, node := range res { + fmt.Printf("%v ", node.Val) + } + fmt.Println() +} + +func TestPreorderTraversalIICompact(t *testing.T) { + /* 初始化二叉树 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树") + PrintTree(root) + + // 前序遍历 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderII(root, &res, &path) + + fmt.Println("\n输出所有根节点到节点 7 的路径") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIICompact(t *testing.T) { + /* 初始化二叉树 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树") + PrintTree(root) + + // 前序遍历 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderIII(root, &res, &path) + + fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIITemplate(t *testing.T) { + /* 初始化二叉树 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树") + PrintTree(root) + + // 回溯算法 + res := make([][]*TreeNode, 0) + state := make([]*TreeNode, 0) + choices := make([]*TreeNode, 0) + choices = append(choices, root) + backtrackIII(&state, &choices, &res) + + fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} diff --git a/codes/go/chapter_backtracking/subset_sum_i.go b/codes/go/chapter_backtracking/subset_sum_i.go new file mode 100644 index 0000000000..c9b7d4bd5b --- /dev/null +++ b/codes/go/chapter_backtracking/subset_sum_i.go @@ -0,0 +1,42 @@ +// File: subset_sum_i.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* 回溯算法:子集和 I */ +func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { + // 子集和等于 target 时,记录解 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for i := start; i < len(*choices); i++ { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target-(*choices)[i] < 0 { + break + } + // 尝试:做出选择,更新 target, start + *state = append(*state, (*choices)[i]) + // 进行下一轮选择 + backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) + // 回退:撤销选择,恢复到之前的状态 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 I */ +func subsetSumI(nums []int, target int) [][]int { + state := make([]int, 0) // 状态(子集) + sort.Ints(nums) // 对 nums 进行排序 + start := 0 // 遍历起始点 + res := make([][]int, 0) // 结果列表(子集列表) + backtrackSubsetSumI(start, target, &state, &nums, &res) + return res +} diff --git a/codes/go/chapter_backtracking/subset_sum_i_naive.go b/codes/go/chapter_backtracking/subset_sum_i_naive.go new file mode 100644 index 0000000000..2a4747fd9d --- /dev/null +++ b/codes/go/chapter_backtracking/subset_sum_i_naive.go @@ -0,0 +1,37 @@ +// File: subset_sum_i_naive.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯算法:子集和 I */ +func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { + // 子集和等于 target 时,记录解 + if target == total { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 遍历所有选择 + for i := 0; i < len(*choices); i++ { + // 剪枝:若子集和超过 target ,则跳过该选择 + if total+(*choices)[i] > target { + continue + } + // 尝试:做出选择,更新元素和 total + *state = append(*state, (*choices)[i]) + // 进行下一轮选择 + backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) + // 回退:撤销选择,恢复到之前的状态 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 I(包含重复子集) */ +func subsetSumINaive(nums []int, target int) [][]int { + state := make([]int, 0) // 状态(子集) + total := 0 // 子集和 + res := make([][]int, 0) // 结果列表(子集列表) + backtrackSubsetSumINaive(total, target, &state, &nums, &res) + return res +} diff --git a/codes/go/chapter_backtracking/subset_sum_ii.go b/codes/go/chapter_backtracking/subset_sum_ii.go new file mode 100644 index 0000000000..4ecbfc118e --- /dev/null +++ b/codes/go/chapter_backtracking/subset_sum_ii.go @@ -0,0 +1,47 @@ +// File: subset_sum_ii.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* 回溯算法:子集和 II */ +func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { + // 子集和等于 target 时,记录解 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i := start; i < len(*choices); i++ { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target-(*choices)[i] < 0 { + break + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start && (*choices)[i] == (*choices)[i-1] { + continue + } + // 尝试:做出选择,更新 target, start + *state = append(*state, (*choices)[i]) + // 进行下一轮选择 + backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) + // 回退:撤销选择,恢复到之前的状态 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 II */ +func subsetSumII(nums []int, target int) [][]int { + state := make([]int, 0) // 状态(子集) + sort.Ints(nums) // 对 nums 进行排序 + start := 0 // 遍历起始点 + res := make([][]int, 0) // 结果列表(子集列表) + backtrackSubsetSumII(start, target, &state, &nums, &res) + return res +} diff --git a/codes/go/chapter_backtracking/subset_sum_test.go b/codes/go/chapter_backtracking/subset_sum_test.go new file mode 100644 index 0000000000..9cd681051d --- /dev/null +++ b/codes/go/chapter_backtracking/subset_sum_test.go @@ -0,0 +1,56 @@ +// File: subset_sum_test.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSubsetSumINaive(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumINaive(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 输入数组 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等于 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } + fmt.Println("请注意,该方法输出的结果包含重复集合") +} + +func TestSubsetSumI(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumI(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 输入数组 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等于 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } +} + +func TestSubsetSumII(t *testing.T) { + nums := []int{4, 4, 5} + target := 9 + res := subsetSumII(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 输入数组 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等于 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } +} diff --git a/codes/go/chapter_computational_complexity/iteration.go b/codes/go/chapter_computational_complexity/iteration.go new file mode 100644 index 0000000000..79470b8629 --- /dev/null +++ b/codes/go/chapter_computational_complexity/iteration.go @@ -0,0 +1,59 @@ +// File: iteration.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "fmt" + +/* for 循环 */ +func forLoop(n int) int { + res := 0 + // 循环求和 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + res += i + } + return res +} + +/* while 循环 */ +func whileLoop(n int) int { + res := 0 + // 初始化条件变量 + i := 1 + // 循环求和 1, 2, ..., n-1, n + for i <= n { + res += i + // 更新条件变量 + i++ + } + return res +} + +/* while 循环(两次更新) */ +func whileLoopII(n int) int { + res := 0 + // 初始化条件变量 + i := 1 + // 循环求和 1, 4, 10, ... + for i <= n { + res += i + // 更新条件变量 + i++ + i *= 2 + } + return res +} + +/* 双层 for 循环 */ +func nestedForLoop(n int) string { + res := "" + // 循环 i = 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + for j := 1; j <= n; j++ { + // 循环 j = 1, 2, ..., n-1, n + res += fmt.Sprintf("(%d, %d), ", i, j) + } + } + return res +} diff --git a/codes/go/chapter_computational_complexity/iteration_test.go b/codes/go/chapter_computational_complexity/iteration_test.go new file mode 100644 index 0000000000..abde1ba46d --- /dev/null +++ b/codes/go/chapter_computational_complexity/iteration_test.go @@ -0,0 +1,26 @@ +// File: iteration_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestIteration(t *testing.T) { + n := 5 + res := forLoop(n) + fmt.Println("\nfor 循环的求和结果 res = ", res) + + res = whileLoop(n) + fmt.Println("\nwhile 循环的求和结果 res = ", res) + + res = whileLoopII(n) + fmt.Println("\nwhile 循环(两次更新)求和结果 res = ", res) + + resStr := nestedForLoop(n) + fmt.Println("\n双层 for 循环的遍历结果 ", resStr) +} diff --git a/codes/go/chapter_computational_complexity/leetcode_two_sum.go b/codes/go/chapter_computational_complexity/leetcode_two_sum.go deleted file mode 100644 index 6712d97ecd..0000000000 --- a/codes/go/chapter_computational_complexity/leetcode_two_sum.go +++ /dev/null @@ -1,33 +0,0 @@ -// File: leetcode_two_sum.go -// Created Time: 2022-11-25 -// Author: reanon (793584285@qq.com) - -package chapter_computational_complexity - -// twoSumBruteForce -func twoSumBruteForce(nums []int, target int) []int { - size := len(nums) - // 两层循环,时间复杂度 O(n^2) - for i := 0; i < size-1; i++ { - for j := i + 1; i < size; j++ { - if nums[i]+nums[j] == target { - return []int{i, j} - } - } - } - return nil -} - -// twoSumHashTable -func twoSumHashTable(nums []int, target int) []int { - // 辅助哈希表,空间复杂度 O(n) - hashTable := map[int]int{} - // 单层循环,时间复杂度 O(n) - for idx, val := range nums { - if preIdx, ok := hashTable[target-val]; ok { - return []int{preIdx, idx} - } - hashTable[val] = idx - } - return nil -} diff --git a/codes/go/chapter_computational_complexity/leetcode_two_sum_test.go b/codes/go/chapter_computational_complexity/leetcode_two_sum_test.go deleted file mode 100644 index 6e44eef982..0000000000 --- a/codes/go/chapter_computational_complexity/leetcode_two_sum_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// File: leetcode_two_sum.go -// Created Time: 2022-11-25 -// Author: reanon (793584285@qq.com) - -package chapter_computational_complexity - -import ( - "fmt" - "testing" -) - -func TestTwoSum(t *testing.T) { - // ======= Test Case ======= - nums := []int{2, 7, 11, 15} - target := 9 - - // ====== Driver Code ====== - // 方法一:暴力解法 - res := twoSumBruteForce(nums, target) - fmt.Println("方法一 res =", res) - // 方法二:哈希表 - res = twoSumHashTable(nums, target) - fmt.Println("方法二 res =", res) -} diff --git a/codes/go/chapter_computational_complexity/recursion.go b/codes/go/chapter_computational_complexity/recursion.go new file mode 100644 index 0000000000..8dd5cd9809 --- /dev/null +++ b/codes/go/chapter_computational_complexity/recursion.go @@ -0,0 +1,61 @@ +// File: recursion.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "container/list" + +/* 递归 */ +func recur(n int) int { + // 终止条件 + if n == 1 { + return 1 + } + // 递:递归调用 + res := recur(n - 1) + // 归:返回结果 + return n + res +} + +/* 使用迭代模拟递归 */ +func forLoopRecur(n int) int { + // 使用一个显式的栈来模拟系统调用栈 + stack := list.New() + res := 0 + // 递:递归调用 + for i := n; i > 0; i-- { + // 通过“入栈操作”模拟“递” + stack.PushBack(i) + } + // 归:返回结果 + for stack.Len() != 0 { + // 通过“出栈操作”模拟“归” + res += stack.Back().Value.(int) + stack.Remove(stack.Back()) + } + // res = 1+2+3+...+n + return res +} + +/* 尾递归 */ +func tailRecur(n int, res int) int { + // 终止条件 + if n == 0 { + return res + } + // 尾递归调用 + return tailRecur(n-1, res+n) +} + +/* 斐波那契数列:递归 */ +func fib(n int) int { + // 终止条件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // 递归调用 f(n) = f(n-1) + f(n-2) + res := fib(n-1) + fib(n-2) + // 返回结果 f(n) + return res +} diff --git a/codes/go/chapter_computational_complexity/recursion_test.go b/codes/go/chapter_computational_complexity/recursion_test.go new file mode 100644 index 0000000000..40ad75365e --- /dev/null +++ b/codes/go/chapter_computational_complexity/recursion_test.go @@ -0,0 +1,26 @@ +// File: recursion_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestRecursion(t *testing.T) { + n := 5 + res := recur(n) + fmt.Println("\n递归函数的求和结果 res = ", res) + + res = forLoopRecur(n) + fmt.Println("\n使用迭代模拟递归求和结果 res = ", res) + + res = tailRecur(n, 0) + fmt.Println("\n尾递归函数的求和结果 res = ", res) + + res = fib(n) + fmt.Println("\n斐波那契数列的第", n, "项为", res) +} diff --git a/codes/go/chapter_computational_complexity/space_complexity.go b/codes/go/chapter_computational_complexity/space_complexity.go index 93f5b33800..3639c7912f 100644 --- a/codes/go/chapter_computational_complexity/space_complexity.go +++ b/codes/go/chapter_computational_complexity/space_complexity.go @@ -7,6 +7,8 @@ package chapter_computational_complexity import ( "fmt" "strconv" + + . "github.com/krahets/hello-algo/pkg" ) /* 结构体 */ @@ -15,36 +17,14 @@ type node struct { next *node } -/* treeNode 二叉树 */ -type treeNode struct { - val int - left *treeNode - right *treeNode -} - /* 创建 node 结构体 */ func newNode(val int) *node { return &node{val: val} } -/* 创建 treeNode 结构体 */ -func newTreeNode(val int) *treeNode { - return &treeNode{val: val} -} - -/* 输出二叉树 */ -func printTree(root *treeNode) { - if root == nil { - return - } - fmt.Println(root.val) - printTree(root.left) - printTree(root.right) -} - /* 函数 */ func function() int { - // do something... + // 执行某些操作... return 0 } @@ -54,7 +34,7 @@ func spaceConstant(n int) { const a = 0 b := 0 nums := make([]int, 10000) - ListNode := newNode(0) + node := newNode(0) // 循环中的变量占用 O(1) 空间 var c int for i := 0; i < n; i++ { @@ -64,7 +44,10 @@ func spaceConstant(n int) { for i := 0; i < n; i++ { function() } - fmt.Println(a, b, nums, c, ListNode) + b += 0 + c += 0 + nums[0] = 0 + node.val = 0 } /* 线性阶 */ @@ -112,12 +95,12 @@ func spaceQuadraticRecur(n int) int { } /* 指数阶(建立满二叉树) */ -func buildTree(n int) *treeNode { +func buildTree(n int) *TreeNode { if n == 0 { return nil } - root := newTreeNode(0) - root.left = buildTree(n - 1) - root.right = buildTree(n - 1) + root := NewTreeNode(0) + root.Left = buildTree(n - 1) + root.Right = buildTree(n - 1) return root } diff --git a/codes/go/chapter_computational_complexity/space_complexity_test.go b/codes/go/chapter_computational_complexity/space_complexity_test.go index 9e82372353..2c62fdb789 100644 --- a/codes/go/chapter_computational_complexity/space_complexity_test.go +++ b/codes/go/chapter_computational_complexity/space_complexity_test.go @@ -6,25 +6,21 @@ package chapter_computational_complexity import ( "testing" + + . "github.com/krahets/hello-algo/pkg" ) func TestSpaceComplexity(t *testing.T) { - /* ======= Test Case ======= */ n := 5 - - /* ====== Driver Code ====== */ // 常数阶 spaceConstant(n) - // 线性阶 spaceLinear(n) spaceLinearRecur(n) - // 平方阶 spaceQuadratic(n) spaceQuadraticRecur(n) - // 指数阶 root := buildTree(n) - printTree(root) + PrintTree(root) } diff --git a/codes/go/chapter_computational_complexity/time_complexity.go b/codes/go/chapter_computational_complexity/time_complexity.go index 15bb8f25c0..3cbe31af2f 100644 --- a/codes/go/chapter_computational_complexity/time_complexity.go +++ b/codes/go/chapter_computational_complexity/time_complexity.go @@ -36,7 +36,7 @@ func arrayTraversal(nums []int) int { /* 平方阶 */ func quadratic(n int) int { count := 0 - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for i := 0; i < n; i++ { for j := 0; j < n; j++ { count++ @@ -48,9 +48,9 @@ func quadratic(n int) int { /* 平方阶(冒泡排序) */ func bubbleSort(nums []int) int { count := 0 // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for i := len(nums) - 1; i > 0; i-- { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交换 nums[j] 与 nums[j + 1] @@ -67,7 +67,7 @@ func bubbleSort(nums []int) int { /* 指数阶(循环实现)*/ func exponential(n int) int { count, base := 0, 1 - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for i := 0; i < n; i++ { for j := 0; j < base; j++ { count++ @@ -87,7 +87,7 @@ func expRecur(n int) int { } /* 对数阶(循环实现)*/ -func logarithmic(n float64) int { +func logarithmic(n int) int { count := 0 for n > 1 { n = n / 2 @@ -97,7 +97,7 @@ func logarithmic(n float64) int { } /* 对数阶(递归实现)*/ -func logRecur(n float64) int { +func logRecur(n int) int { if n <= 1 { return 0 } @@ -105,13 +105,12 @@ func logRecur(n float64) int { } /* 线性对数阶 */ -func linearLogRecur(n float64) int { +func linearLogRecur(n int) int { if n <= 1 { return 1 } - count := linearLogRecur(n/2) + - linearLogRecur(n/2) - for i := 0.0; i < n; i++ { + count := linearLogRecur(n/2) + linearLogRecur(n/2) + for i := 0; i < n; i++ { count++ } return count diff --git a/codes/go/chapter_computational_complexity/time_complexity_test.go b/codes/go/chapter_computational_complexity/time_complexity_test.go index 6486a59ff2..819ce7095d 100644 --- a/codes/go/chapter_computational_complexity/time_complexity_test.go +++ b/codes/go/chapter_computational_complexity/time_complexity_test.go @@ -14,35 +14,35 @@ func TestTimeComplexity(t *testing.T) { fmt.Println("输入数据大小 n =", n) count := constant(n) - fmt.Println("常数阶的计算操作数量 =", count) + fmt.Println("常数阶的操作数量 =", count) count = linear(n) - fmt.Println("线性阶的计算操作数量 =", count) + fmt.Println("线性阶的操作数量 =", count) count = arrayTraversal(make([]int, n)) - fmt.Println("线性阶(遍历数组)的计算操作数量 =", count) + fmt.Println("线性阶(遍历数组)的操作数量 =", count) count = quadratic(n) - fmt.Println("平方阶的计算操作数量 =", count) + fmt.Println("平方阶的操作数量 =", count) nums := make([]int, n) for i := 0; i < n; i++ { nums[i] = n - i } count = bubbleSort(nums) - fmt.Println("平方阶(冒泡排序)的计算操作数量 =", count) + fmt.Println("平方阶(冒泡排序)的操作数量 =", count) count = exponential(n) - fmt.Println("指数阶(循环实现)的计算操作数量 =", count) + fmt.Println("指数阶(循环实现)的操作数量 =", count) count = expRecur(n) - fmt.Println("指数阶(递归实现)的计算操作数量 =", count) + fmt.Println("指数阶(递归实现)的操作数量 =", count) - count = logarithmic(float64(n)) - fmt.Println("对数阶(循环实现)的计算操作数量 =", count) - count = logRecur(float64(n)) - fmt.Println("对数阶(递归实现)的计算操作数量 =", count) + count = logarithmic(n) + fmt.Println("对数阶(循环实现)的操作数量 =", count) + count = logRecur(n) + fmt.Println("对数阶(递归实现)的操作数量 =", count) - count = linearLogRecur(float64(n)) - fmt.Println("线性对数阶(递归实现)的计算操作数量 =", count) + count = linearLogRecur(n) + fmt.Println("线性对数阶(递归实现)的操作数量 =", count) count = factorialRecur(n) - fmt.Println("阶乘阶(递归实现)的计算操作数量 =", count) + fmt.Println("阶乘阶(递归实现)的操作数量 =", count) } diff --git a/codes/go/chapter_computational_complexity/worst_best_time_complexity.go b/codes/go/chapter_computational_complexity/worst_best_time_complexity.go index 10e475d590..75b74c8e1d 100644 --- a/codes/go/chapter_computational_complexity/worst_best_time_complexity.go +++ b/codes/go/chapter_computational_complexity/worst_best_time_complexity.go @@ -5,7 +5,6 @@ package chapter_computational_complexity import ( - "fmt" "math/rand" ) @@ -26,20 +25,11 @@ func randomNumbers(n int) []int { /* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums []int) int { for i := 0; i < len(nums); i++ { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if nums[i] == 1 { return i } } return -1 } - -/* Driver Code */ -func main() { - for i := 0; i < 10; i++ { - n := 100 - nums := randomNumbers(n) - index := findOne(nums) - fmt.Println("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) - fmt.Println("数字 1 的索引为", index) - } -} diff --git a/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go b/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go index a7aa988f70..fed5f1836e 100644 --- a/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go +++ b/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go @@ -1,4 +1,4 @@ -// File: worst_best_time_complexity.go +// File: worst_best_time_complexity_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) @@ -14,7 +14,7 @@ func TestWorstBestTimeComplexity(t *testing.T) { n := 100 nums := randomNumbers(n) index := findOne(nums) - fmt.Println("打乱后的数组为", nums) + fmt.Println("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) fmt.Println("数字 1 的索引为", index) } } diff --git a/codes/go/chapter_divide_and_conquer/binary_search_recur.go b/codes/go/chapter_divide_and_conquer/binary_search_recur.go new file mode 100644 index 0000000000..df1946dc00 --- /dev/null +++ b/codes/go/chapter_divide_and_conquer/binary_search_recur.go @@ -0,0 +1,34 @@ +// File: binary_search_recur.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +/* 二分查找:问题 f(i, j) */ +func dfs(nums []int, target, i, j int) int { + // 如果区间为空,代表没有目标元素,则返回 -1 + if i > j { + return -1 + } + // 计算索引中点 + m := i + ((j - i) >> 1) + //判断中点与目标元素大小 + 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 + } +} + +/* 二分查找 */ +func binarySearch(nums []int, target int) int { + n := len(nums) + return dfs(nums, target, 0, n-1) +} diff --git a/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go b/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go new file mode 100644 index 0000000000..56aa940abc --- /dev/null +++ b/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go @@ -0,0 +1,20 @@ +// File: binary_search_recur_test.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + target := 6 + noTarget := 99 + targetIndex := binarySearch(nums, target) + fmt.Println("目标元素 6 的索引 = ", targetIndex) + noTargetIndex := binarySearch(nums, noTarget) + fmt.Println("不存在目标元素的索引 = ", noTargetIndex) +} diff --git a/codes/go/chapter_divide_and_conquer/build_tree.go b/codes/go/chapter_divide_and_conquer/build_tree.go new file mode 100644 index 0000000000..625040f7e8 --- /dev/null +++ b/codes/go/chapter_divide_and_conquer/build_tree.go @@ -0,0 +1,37 @@ +// File: build_tree.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import . "github.com/krahets/hello-algo/pkg" + +/* 构建二叉树:分治 */ +func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { + // 子树区间为空时终止 + if r-l < 0 { + return nil + } + // 初始化根节点 + root := NewTreeNode(preorder[i]) + // 查询 m ,从而划分左右子树 + m := inorderMap[preorder[i]] + // 子问题:构建左子树 + root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) + // 子问题:构建右子树 + root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) + // 返回根节点 + return root +} + +/* 构建二叉树 */ +func buildTree(preorder, inorder []int) *TreeNode { + // 初始化哈希表,存储 inorder 元素到索引的映射 + inorderMap := make(map[int]int, len(inorder)) + for i := 0; i < len(inorder); i++ { + inorderMap[inorder[i]] = i + } + + root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) + return root +} diff --git a/codes/go/chapter_divide_and_conquer/build_tree_test.go b/codes/go/chapter_divide_and_conquer/build_tree_test.go new file mode 100644 index 0000000000..ab61506344 --- /dev/null +++ b/codes/go/chapter_divide_and_conquer/build_tree_test.go @@ -0,0 +1,25 @@ +// File: build_tree_test.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBuildTree(t *testing.T) { + preorder := []int{3, 9, 2, 1, 7} + inorder := []int{9, 3, 1, 2, 7} + fmt.Print("前序遍历 = ") + PrintSlice(preorder) + fmt.Print("中序遍历 = ") + PrintSlice(inorder) + + root := buildTree(preorder, inorder) + fmt.Println("构建的二叉树为:") + PrintTree(root) +} diff --git a/codes/go/chapter_divide_and_conquer/hanota.go b/codes/go/chapter_divide_and_conquer/hanota.go new file mode 100644 index 0000000000..96e408093d --- /dev/null +++ b/codes/go/chapter_divide_and_conquer/hanota.go @@ -0,0 +1,39 @@ +// File: hanota.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import "container/list" + +/* 移动一个圆盘 */ +func move(src, tar *list.List) { + // 从 src 顶部拿出一个圆盘 + pan := src.Back() + // 将圆盘放入 tar 顶部 + tar.PushBack(pan.Value) + // 移除 src 顶部圆盘 + src.Remove(pan) +} + +/* 求解汉诺塔问题 f(i) */ +func dfsHanota(i int, src, buf, tar *list.List) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1 { + move(src, tar) + return + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfsHanota(i-1, src, tar, buf) + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar) + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfsHanota(i-1, buf, src, tar) +} + +/* 求解汉诺塔问题 */ +func solveHanota(A, B, C *list.List) { + n := A.Len() + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfsHanota(n, A, B, C) +} diff --git a/codes/go/chapter_divide_and_conquer/hanota_test.go b/codes/go/chapter_divide_and_conquer/hanota_test.go new file mode 100644 index 0000000000..1018ecb9aa --- /dev/null +++ b/codes/go/chapter_divide_and_conquer/hanota_test.go @@ -0,0 +1,40 @@ +// File: hanota_test.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHanota(t *testing.T) { + // 列表尾部是柱子顶部 + A := list.New() + for i := 5; i > 0; i-- { + A.PushBack(i) + } + B := list.New() + C := list.New() + fmt.Println("初始状态下:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) + + solveHanota(A, B, C) + + fmt.Println("圆盘移动完成后:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go b/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go new file mode 100644 index 0000000000..5030faab7d --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go @@ -0,0 +1,36 @@ +// File: climbing_stairs_backtrack.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 回溯 */ +func backtrack(choices []int, state, n int, res []int) { + // 当爬到第 n 阶时,方案数量加 1 + if state == n { + res[0] = res[0] + 1 + } + // 遍历所有选择 + for _, choice := range choices { + // 剪枝:不允许越过第 n 阶 + if state+choice > n { + continue + } + // 尝试:做出选择,更新状态 + backtrack(choices, state+choice, n, res) + // 回退 + } +} + +/* 爬楼梯:回溯 */ +func climbingStairsBacktrack(n int) int { + // 可选择向上爬 1 阶或 2 阶 + choices := []int{1, 2} + // 从第 0 阶开始爬 + state := 0 + res := make([]int, 1) + // 使用 res[0] 记录方案数量 + res[0] = 0 + backtrack(choices, state, n, res) + return res[0] +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go b/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go new file mode 100644 index 0000000000..1653d6939e --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go @@ -0,0 +1,25 @@ +// File: climbing_stairs_constraint_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 带约束爬楼梯:动态规划 */ +func climbingStairsConstraintDP(n int) int { + if n == 1 || n == 2 { + return 1 + } + // 初始化 dp 表,用于存储子问题的解 + dp := make([][3]int, n+1) + // 初始状态:预设最小子问题的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + dp[i][1] = dp[i-1][2] + dp[i][2] = dp[i-2][1] + dp[i-2][2] + } + return dp[n][1] + dp[n][2] +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go new file mode 100644 index 0000000000..62310483ed --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go @@ -0,0 +1,21 @@ +// File: climbing_stairs_dfs.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 搜索 */ +func dfs(i int) int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfs(i-1) + dfs(i-2) + return count +} + +/* 爬楼梯:搜索 */ +func climbingStairsDFS(n int) int { + return dfs(n) +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go new file mode 100644 index 0000000000..03d5158e52 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go @@ -0,0 +1,32 @@ +// File: climbing_stairs_dfs_mem.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 记忆化搜索 */ +func dfsMem(i int, mem []int) int { + // 已知 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] + count := dfsMem(i-1, mem) + dfsMem(i-2, mem) + // 记录 dp[i] + mem[i] = count + return count +} + +/* 爬楼梯:记忆化搜索 */ +func climbingStairsDFSMem(n int) int { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + mem := make([]int, n+1) + for i := range mem { + mem[i] = -1 + } + return dfsMem(n, mem) +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go b/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go new file mode 100644 index 0000000000..b54139e68f --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go @@ -0,0 +1,35 @@ +// File: climbing_stairs_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 爬楼梯:动态规划 */ +func climbingStairsDP(n int) int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用于存储子问题的解 + dp := make([]int, n+1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + return dp[n] +} + +/* 爬楼梯:空间优化后的动态规划 */ +func climbingStairsDPComp(n int) int { + if n == 1 || n == 2 { + return n + } + a, b := 1, 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + a, b = b, a+b + } + return b +} diff --git a/codes/go/chapter_dynamic_programming/climbing_stairs_test.go b/codes/go/chapter_dynamic_programming/climbing_stairs_test.go new file mode 100644 index 0000000000..7599e11ca9 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/climbing_stairs_test.go @@ -0,0 +1,57 @@ +// File: climbing_stairs_test.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestClimbingStairsBacktrack(t *testing.T) { + n := 9 + res := climbingStairsBacktrack(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDFS(t *testing.T) { + n := 9 + res := climbingStairsDFS(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDFSMem(t *testing.T) { + n := 9 + res := climbingStairsDFSMem(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDP(t *testing.T) { + n := 9 + res := climbingStairsDP(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsDPComp(t *testing.T) { + n := 9 + res := climbingStairsDPComp(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestClimbingStairsConstraintDP(t *testing.T) { + n := 9 + res := climbingStairsConstraintDP(n) + fmt.Printf("爬 %d 阶楼梯共有 %d 种方案\n", n, res) +} + +func TestMinCostClimbingStairsDPComp(t *testing.T) { + cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} + fmt.Printf("输入楼梯的代价列表为 %v\n", cost) + + res := minCostClimbingStairsDP(cost) + fmt.Printf("爬完楼梯的最低代价为 %d\n", res) + + res = minCostClimbingStairsDPComp(cost) + fmt.Printf("爬完楼梯的最低代价为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/coin_change.go b/codes/go/chapter_dynamic_programming/coin_change.go new file mode 100644 index 0000000000..de2a7a5c1d --- /dev/null +++ b/codes/go/chapter_dynamic_programming/coin_change.go @@ -0,0 +1,66 @@ +// File: coin_change.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 零钱兑换:动态规划 */ +func coinChangeDP(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 状态转移:首行首列 + for a := 1; a <= amt; a++ { + dp[0][a] = max + } + // 状态转移:其余行和列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i-1][a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) + } + } + } + if dp[n][amt] != max { + return dp[n][amt] + } + return -1 +} + +/* 零钱兑换:动态规划 */ +func coinChangeDPComp(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([]int, amt+1) + for i := 1; i <= amt; i++ { + dp[i] = max + } + // 状态转移 + for i := 1; i <= n; i++ { + // 正序遍历 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) + } + } + } + if dp[amt] != max { + return dp[amt] + } + return -1 +} diff --git a/codes/go/chapter_dynamic_programming/coin_change_ii.go b/codes/go/chapter_dynamic_programming/coin_change_ii.go new file mode 100644 index 0000000000..72031d4429 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/coin_change_ii.go @@ -0,0 +1,54 @@ +// File: coin_change_ii.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 零钱兑换 II:动态规划 */ +func coinChangeIIDP(coins []int, amt int) int { + n := len(coins) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 初始化首列 + for i := 0; i <= n; i++ { + dp[i][0] = 1 + } + // 状态转移:其余行和列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i-1][a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] + } + } + } + return dp[n][amt] +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +func coinChangeIIDPComp(coins []int, amt int) int { + n := len(coins) + // 初始化 dp 表 + dp := make([]int, amt+1) + dp[0] = 1 + // 状态转移 + for i := 1; i <= n; i++ { + // 正序遍历 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a-coins[i-1]] + } + } + } + return dp[amt] +} diff --git a/codes/go/chapter_dynamic_programming/coin_change_test.go b/codes/go/chapter_dynamic_programming/coin_change_test.go new file mode 100644 index 0000000000..28a13f4ea9 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/coin_change_test.go @@ -0,0 +1,23 @@ +// File: coin_change_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestCoinChange(t *testing.T) { + coins := []int{1, 2, 5} + amt := 4 + + // 动态规划 + res := coinChangeDP(coins, amt) + fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res) + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins, amt) + fmt.Printf("凑到目标金额所需的最少硬币数量为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/edit_distance.go b/codes/go/chapter_dynamic_programming/edit_distance.go new file mode 100644 index 0000000000..075b0a6b19 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/edit_distance.go @@ -0,0 +1,129 @@ +// File: edit_distance.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 编辑距离:暴力搜索 */ +func editDistanceDFS(s string, t string, i int, j int) int { + // 若 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 + insert := editDistanceDFS(s, t, i, j-1) + deleted := editDistanceDFS(s, t, i-1, j) + replace := editDistanceDFS(s, t, i-1, j-1) + // 返回最少编辑步数 + return MinInt(MinInt(insert, deleted), replace) + 1 +} + +/* 编辑距离:记忆化搜索 */ +func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { + // 若 s 和 t 都为空,则返回 0 + if i == 0 && j == 0 { + return 0 + } + // 若 s 为空,则返回 t 长度 + if i == 0 { + return j + } + // 若 t 为空,则返回 s 长度 + if j == 0 { + return i + } + // 若已有记录,则直接返回之 + if mem[i][j] != -1 { + return mem[i][j] + } + // 若两字符相等,则直接跳过此两字符 + if s[i-1] == t[j-1] { + return editDistanceDFSMem(s, t, mem, i-1, j-1) + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert := editDistanceDFSMem(s, t, mem, i, j-1) + deleted := editDistanceDFSMem(s, t, mem, i-1, j) + replace := editDistanceDFSMem(s, t, mem, i-1, j-1) + // 记录并返回最少编辑步数 + mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 + return mem[i][j] +} + +/* 编辑距离:动态规划 */ +func editDistanceDP(s string, t string) int { + n := len(s) + m := len(t) + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, m+1) + } + // 状态转移:首行首列 + for i := 1; i <= n; i++ { + dp[i][0] = i + } + for j := 1; j <= m; j++ { + dp[0][j] = j + } + // 状态转移:其余行和列 + for i := 1; i <= n; i++ { + for 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] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 + } + } + } + return dp[n][m] +} + +/* 编辑距离:空间优化后的动态规划 */ +func editDistanceDPComp(s string, t string) int { + n := len(s) + m := len(t) + dp := make([]int, m+1) + // 状态转移:首行 + for j := 1; j <= m; j++ { + dp[j] = j + } + // 状态转移:其余行 + for i := 1; i <= n; i++ { + // 状态转移:首列 + leftUp := dp[0] // 暂存 dp[i-1, j-1] + dp[0] = i + // 状态转移:其余列 + for j := 1; j <= m; j++ { + temp := dp[j] + if s[i-1] == t[j-1] { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftUp + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 + } + leftUp = temp // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m] +} + +func MinInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/codes/go/chapter_dynamic_programming/edit_distance_test.go b/codes/go/chapter_dynamic_programming/edit_distance_test.go new file mode 100644 index 0000000000..14e02222a9 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/edit_distance_test.go @@ -0,0 +1,40 @@ +// File: edit_distance_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestEditDistanceDFS(test *testing.T) { + s := "bag" + t := "pack" + n := len(s) + m := len(t) + + // 暴力搜索 + res := editDistanceDFS(s, t, n, m) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) + + // 记忆化搜索 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, m+1) + for j := 0; j <= m; j++ { + mem[i][j] = -1 + } + } + res = editDistanceDFSMem(s, t, mem, n, m) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) + + // 动态规划 + res = editDistanceDP(s, t) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t) + fmt.Printf("将 %s 更改为 %s 最少需要编辑 %d 步\n", s, t, res) +} diff --git a/codes/go/chapter_dynamic_programming/knapsack.go b/codes/go/chapter_dynamic_programming/knapsack.go new file mode 100644 index 0000000000..1445d09171 --- /dev/null +++ b/codes/go/chapter_dynamic_programming/knapsack.go @@ -0,0 +1,87 @@ +// File: knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 0-1 背包:暴力搜索 */ +func knapsackDFS(wgt, val []int, i, c int) int { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超过背包容量,则只能选择不放入背包 + if wgt[i-1] > c { + return knapsackDFS(wgt, val, i-1, c) + } + // 计算不放入和放入物品 i 的最大价值 + no := knapsackDFS(wgt, val, i-1, c) + yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] + // 返回两种方案中价值更大的那一个 + return int(math.Max(float64(no), float64(yes))) +} + +/* 0-1 背包:记忆化搜索 */ +func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若已有记录,则直接返回 + if mem[i][c] != -1 { + return mem[i][c] + } + // 若超过背包容量,则只能选择不放入背包 + if wgt[i-1] > c { + return knapsackDFSMem(wgt, val, mem, i-1, c) + } + // 计算不放入和放入物品 i 的最大价值 + no := knapsackDFSMem(wgt, val, mem, i-1, c) + yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] + // 返回两种方案中价值更大的那一个 + mem[i][c] = int(math.Max(float64(no), float64(yes))) + return mem[i][c] +} + +/* 0-1 背包:动态规划 */ +func knapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 状态转移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空间优化后的动态规划 */ +func knapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 状态转移 + for i := 1; i <= n; i++ { + // 倒序遍历 + for c := cap; c >= 1; c-- { + if wgt[i-1] <= c { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/codes/go/chapter_dynamic_programming/knapsack_test.go b/codes/go/chapter_dynamic_programming/knapsack_test.go new file mode 100644 index 0000000000..7834e847eb --- /dev/null +++ b/codes/go/chapter_dynamic_programming/knapsack_test.go @@ -0,0 +1,54 @@ +// File: knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + c := 50 + n := len(wgt) + + // 暴力搜索 + res := knapsackDFS(wgt, val, n, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 记忆化搜索 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, c+1) + for j := 0; j <= c; j++ { + mem[i][j] = -1 + } + } + res = knapsackDFSMem(wgt, val, mem, n, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 动态规划 + res = knapsackDP(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) +} + +func TestUnboundedKnapsack(t *testing.T) { + wgt := []int{1, 2, 3} + val := []int{5, 11, 15} + c := 4 + + // 动态规划 + res := unboundedKnapsackDP(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(wgt, val, c) + fmt.Printf("不超过背包容量的最大物品价值为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go b/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go new file mode 100644 index 0000000000..b99c58671d --- /dev/null +++ b/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go @@ -0,0 +1,52 @@ +// File: min_cost_climbing_stairs_dp.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 爬楼梯最小代价:动态规划 */ +func minCostClimbingStairsDP(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 初始化 dp 表,用于存储子问题的解 + dp := make([]int, n+1) + // 初始状态:预设最小子问题的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + dp[i] = min(dp[i-1], dp[i-2]) + cost[i] + } + return dp[n] +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +func minCostClimbingStairsDPComp(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 初始状态:预设最小子问题的解 + a, b := cost[1], cost[2] + // 状态转移:从较小子问题逐步求解较大子问题 + for i := 3; i <= n; i++ { + tmp := b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} diff --git a/codes/go/chapter_dynamic_programming/min_path_sum.go b/codes/go/chapter_dynamic_programming/min_path_sum.go new file mode 100644 index 0000000000..0b843d9c2a --- /dev/null +++ b/codes/go/chapter_dynamic_programming/min_path_sum.go @@ -0,0 +1,94 @@ +// File: min_path_sum.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 最小路径和:暴力搜索 */ +func minPathSumDFS(grid [][]int, i, j int) int { + // 若为左上角单元格,则终止搜索 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return math.MaxInt + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + up := minPathSumDFS(grid, i-1, j) + left := minPathSumDFS(grid, i, j-1) + // 返回从左上角到 (i, j) 的最小路径代价 + return int(math.Min(float64(left), float64(up))) + grid[i][j] +} + +/* 最小路径和:记忆化搜索 */ +func minPathSumDFSMem(grid, mem [][]int, i, j int) int { + // 若为左上角单元格,则终止搜索 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return math.MaxInt + } + // 若已有记录,则直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左边和上边单元格的最小路径代价 + up := minPathSumDFSMem(grid, mem, i-1, j) + left := minPathSumDFSMem(grid, mem, i, j-1) + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] + return mem[i][j] +} + +/* 最小路径和:动态规划 */ +func minPathSumDP(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([][]int, n) + for i := 0; i < n; i++ { + dp[i] = make([]int, m) + } + dp[0][0] = grid[0][0] + // 状态转移:首行 + for j := 1; j < m; j++ { + dp[0][j] = dp[0][j-1] + grid[0][j] + } + // 状态转移:首列 + for i := 1; i < n; i++ { + dp[i][0] = dp[i-1][0] + grid[i][0] + } + // 状态转移:其余行和列 + for i := 1; i < n; i++ { + for j := 1; j < m; j++ { + dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] + } + } + return dp[n-1][m-1] +} + +/* 最小路径和:空间优化后的动态规划 */ +func minPathSumDPComp(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([]int, m) + // 状态转移:首行 + dp[0] = grid[0][0] + for j := 1; j < m; j++ { + dp[j] = dp[j-1] + grid[0][j] + } + // 状态转移:其余行和列 + for i := 1; i < n; i++ { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + // 状态转移:其余列 + for j := 1; j < m; j++ { + dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] + } + } + return dp[m-1] +} diff --git a/codes/go/chapter_dynamic_programming/min_path_sum_test.go b/codes/go/chapter_dynamic_programming/min_path_sum_test.go new file mode 100644 index 0000000000..53ecb1a79e --- /dev/null +++ b/codes/go/chapter_dynamic_programming/min_path_sum_test.go @@ -0,0 +1,43 @@ +// File: min_path_sum_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestMinPathSum(t *testing.T) { + grid := [][]int{ + {1, 3, 1, 5}, + {2, 2, 4, 2}, + {5, 3, 2, 1}, + {4, 3, 5, 2}, + } + n, m := len(grid), len(grid[0]) + + // 暴力搜索 + res := minPathSumDFS(grid, n-1, m-1) + fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) + + // 记忆化搜索 + mem := make([][]int, n) + for i := 0; i < n; i++ { + mem[i] = make([]int, m) + for j := 0; j < m; j++ { + mem[i][j] = -1 + } + } + res = minPathSumDFSMem(grid, mem, n-1, m-1) + fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) + + // 动态规划 + res = minPathSumDP(grid) + fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) + + // 空间优化后的动态规划 + res = minPathSumDPComp(grid) + fmt.Printf("从左上角到右下角的最小路径和为 %d\n", res) +} diff --git a/codes/go/chapter_dynamic_programming/unbounded_knapsack.go b/codes/go/chapter_dynamic_programming/unbounded_knapsack.go new file mode 100644 index 0000000000..7be105ca7a --- /dev/null +++ b/codes/go/chapter_dynamic_programming/unbounded_knapsack.go @@ -0,0 +1,50 @@ +// File: unbounded_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 完全背包:动态规划 */ +func unboundedKnapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 状态转移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 完全背包:空间优化后的动态规划 */ +func unboundedKnapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 状态转移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/codes/go/chapter_graph/graph_adjacency_list.go b/codes/go/chapter_graph/graph_adjacency_list.go new file mode 100644 index 0000000000..49f94eb8ef --- /dev/null +++ b/codes/go/chapter_graph/graph_adjacency_list.go @@ -0,0 +1,100 @@ +// File: graph_adjacency_list.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "strconv" + "strings" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 基于邻接表实现的无向图类 */ +type graphAdjList struct { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + adjList map[Vertex][]Vertex +} + +/* 构造函数 */ +func newGraphAdjList(edges [][]Vertex) *graphAdjList { + g := &graphAdjList{ + adjList: make(map[Vertex][]Vertex), + } + // 添加所有顶点和边 + for _, edge := range edges { + g.addVertex(edge[0]) + g.addVertex(edge[1]) + g.addEdge(edge[0], edge[1]) + } + return g +} + +/* 获取顶点数量 */ +func (g *graphAdjList) size() int { + return len(g.adjList) +} + +/* 添加边 */ +func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 添加边 vet1 - vet2, 添加匿名 struct{}, + g.adjList[vet1] = append(g.adjList[vet1], vet2) + g.adjList[vet2] = append(g.adjList[vet2], vet1) +} + +/* 删除边 */ +func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 删除边 vet1 - vet2 + g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) + g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) +} + +/* 添加顶点 */ +func (g *graphAdjList) addVertex(vet Vertex) { + _, ok := g.adjList[vet] + if ok { + return + } + // 在邻接表中添加一个新链表 + g.adjList[vet] = make([]Vertex, 0) +} + +/* 删除顶点 */ +func (g *graphAdjList) removeVertex(vet Vertex) { + _, ok := g.adjList[vet] + if !ok { + panic("error") + } + // 在邻接表中删除顶点 vet 对应的链表 + delete(g.adjList, vet) + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for v, list := range g.adjList { + g.adjList[v] = DeleteSliceElms(list, vet) + } +} + +/* 打印邻接表 */ +func (g *graphAdjList) print() { + var builder strings.Builder + fmt.Printf("邻接表 = \n") + for k, v := range g.adjList { + builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") + for _, vet := range v { + builder.WriteString(strconv.Itoa(vet.Val) + " ") + } + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/codes/go/chapter_graph/graph_adjacency_list_test.go b/codes/go/chapter_graph/graph_adjacency_list_test.go new file mode 100644 index 0000000000..24e1b79e1f --- /dev/null +++ b/codes/go/chapter_graph/graph_adjacency_list_test.go @@ -0,0 +1,45 @@ +// File: graph_adjacency_list_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphAdjList(t *testing.T) { + /* 初始化无向图 */ + v := ValsToVets([]int{1, 3, 2, 5, 4}) + edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化后,图为:") + graph.print() + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]) + fmt.Println("\n添加边 1-2 后,图为") + graph.print() + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]) + fmt.Println("\n删除边 1-3 后,图为") + graph.print() + + /* 添加顶点 */ + v5 := NewVertex(6) + graph.addVertex(v5) + fmt.Println("\n添加顶点 6 后,图为") + graph.print() + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(v[1]) + fmt.Println("\n删除顶点 3 后,图为") + graph.print() +} diff --git a/codes/go/chapter_graph/graph_adjacency_matrix.go b/codes/go/chapter_graph/graph_adjacency_matrix.go new file mode 100644 index 0000000000..9050550c6e --- /dev/null +++ b/codes/go/chapter_graph/graph_adjacency_matrix.go @@ -0,0 +1,102 @@ +// File: graph_adjacency_matrix.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import "fmt" + +/* 基于邻接矩阵实现的无向图类 */ +type graphAdjMat struct { + // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vertices []int + // 邻接矩阵,行列索引对应“顶点索引” + adjMat [][]int +} + +/* 构造函数 */ +func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { + // 添加顶点 + n := len(vertices) + adjMat := make([][]int, n) + for i := range adjMat { + adjMat[i] = make([]int, n) + } + // 初始化图 + g := &graphAdjMat{ + vertices: vertices, + adjMat: adjMat, + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for i := range edges { + g.addEdge(edges[i][0], edges[i][1]) + } + return g +} + +/* 获取顶点数量 */ +func (g *graphAdjMat) size() int { + return len(g.vertices) +} + +/* 添加顶点 */ +func (g *graphAdjMat) addVertex(val int) { + n := g.size() + // 向顶点列表中添加新顶点的值 + g.vertices = append(g.vertices, val) + // 在邻接矩阵中添加一行 + newRow := make([]int, n) + g.adjMat = append(g.adjMat, newRow) + // 在邻接矩阵中添加一列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i], 0) + } +} + +/* 删除顶点 */ +func (g *graphAdjMat) removeVertex(index int) { + if index >= g.size() { + return + } + // 在顶点列表中移除索引 index 的顶点 + g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) + // 在邻接矩阵中删除索引 index 的行 + g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) + // 在邻接矩阵中删除索引 index 的列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) + } +} + +/* 添加边 */ +// 参数 i, j 对应 vertices 元素索引 +func (g *graphAdjMat) addEdge(i, j int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + g.adjMat[i][j] = 1 + g.adjMat[j][i] = 1 +} + +/* 删除边 */ +// 参数 i, j 对应 vertices 元素索引 +func (g *graphAdjMat) removeEdge(i, j int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + g.adjMat[i][j] = 0 + g.adjMat[j][i] = 0 +} + +/* 打印邻接矩阵 */ +func (g *graphAdjMat) print() { + fmt.Printf("\t顶点列表 = %v\n", g.vertices) + fmt.Printf("\t邻接矩阵 = \n") + for i := range g.adjMat { + fmt.Printf("\t\t\t%v\n", g.adjMat[i]) + } +} diff --git a/codes/go/chapter_graph/graph_adjacency_matrix_test.go b/codes/go/chapter_graph/graph_adjacency_matrix_test.go new file mode 100644 index 0000000000..90056ff266 --- /dev/null +++ b/codes/go/chapter_graph/graph_adjacency_matrix_test.go @@ -0,0 +1,43 @@ +// File: graph_adjacency_matrix_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" +) + +func TestGraphAdjMat(t *testing.T) { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + vertices := []int{1, 3, 2, 5, 4} + edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} + graph := newGraphAdjMat(vertices, edges) + fmt.Println("初始化后,图为:") + graph.print() + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.addEdge(0, 2) + fmt.Println("添加边 1-2 后,图为") + graph.print() + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.removeEdge(0, 1) + fmt.Println("删除边 1-3 后,图为") + graph.print() + + /* 添加顶点 */ + graph.addVertex(6) + fmt.Println("添加顶点 6 后,图为") + graph.print() + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.removeVertex(1) + fmt.Println("删除顶点 3 后,图为") + graph.print() +} diff --git a/codes/go/chapter_graph/graph_bfs.go b/codes/go/chapter_graph/graph_bfs.go new file mode 100644 index 0000000000..d5d9717f36 --- /dev/null +++ b/codes/go/chapter_graph/graph_bfs.go @@ -0,0 +1,41 @@ +// File: graph_bfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { + // 顶点遍历序列 + res := make([]Vertex, 0) + // 哈希集合,用于记录已被访问过的顶点 + visited := make(map[Vertex]struct{}) + visited[startVet] = struct{}{} + // 队列用于实现 BFS, 使用切片模拟队列 + queue := make([]Vertex, 0) + queue = append(queue, startVet) + // 以顶点 vet 为起点,循环直至访问完所有顶点 + for len(queue) > 0 { + // 队首顶点出队 + vet := queue[0] + queue = queue[1:] + // 记录访问顶点 + res = append(res, vet) + // 遍历该顶点的所有邻接顶点 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 只入队未访问的顶点 + if !isExist { + queue = append(queue, adjVet) + visited[adjVet] = struct{}{} + } + } + } + // 返回顶点遍历序列 + return res +} diff --git a/codes/go/chapter_graph/graph_bfs_test.go b/codes/go/chapter_graph/graph_bfs_test.go new file mode 100644 index 0000000000..fe81cb9d7c --- /dev/null +++ b/codes/go/chapter_graph/graph_bfs_test.go @@ -0,0 +1,29 @@ +// File: graph_bfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphBFS(t *testing.T) { + /* 初始化无向图 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, + {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, + {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化后,图为:") + graph.print() + + /* 广度优先遍历 */ + res := graphBFS(graph, vets[0]) + fmt.Println("广度优先遍历(BFS)顶点序列为:") + PrintSlice(VetsToVals(res)) +} diff --git a/codes/go/chapter_graph/graph_dfs.go b/codes/go/chapter_graph/graph_dfs.go new file mode 100644 index 0000000000..7fda99394a --- /dev/null +++ b/codes/go/chapter_graph/graph_dfs.go @@ -0,0 +1,36 @@ +// File: graph_dfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 深度优先遍历辅助函数 */ +func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { + // append 操作会返回新的的引用,必须让原引用重新赋值为新slice的引用 + *res = append(*res, vet) + visited[vet] = struct{}{} + // 遍历该顶点的所有邻接顶点 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 递归访问邻接顶点 + if !isExist { + dfs(g, visited, res, adjVet) + } + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { + // 顶点遍历序列 + res := make([]Vertex, 0) + // 哈希集合,用于记录已被访问过的顶点 + visited := make(map[Vertex]struct{}) + dfs(g, visited, &res, startVet) + // 返回顶点遍历序列 + return res +} diff --git a/codes/go/chapter_graph/graph_dfs_test.go b/codes/go/chapter_graph/graph_dfs_test.go new file mode 100644 index 0000000000..80dbb979f7 --- /dev/null +++ b/codes/go/chapter_graph/graph_dfs_test.go @@ -0,0 +1,28 @@ +// File: graph_dfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphDFS(t *testing.T) { + /* 初始化无向图 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, + {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化后,图为:") + graph.print() + + /* 深度优先遍历 */ + res := graphDFS(graph, vets[0]) + fmt.Println("深度优先遍历(DFS)顶点序列为:") + PrintSlice(VetsToVals(res)) +} diff --git a/codes/go/chapter_greedy/coin_change_greedy.go b/codes/go/chapter_greedy/coin_change_greedy.go new file mode 100644 index 0000000000..1ce5d3e0fc --- /dev/null +++ b/codes/go/chapter_greedy/coin_change_greedy.go @@ -0,0 +1,27 @@ +// File: coin_change_greedy.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +/* 零钱兑换:贪心 */ +func coinChangeGreedy(coins []int, amt int) int { + // 假设 coins 列表有序 + i := len(coins) - 1 + count := 0 + // 循环进行贪心选择,直到无剩余金额 + for amt > 0 { + // 找到小于且最接近剩余金额的硬币 + for i > 0 && coins[i] > amt { + i-- + } + // 选择 coins[i] + amt -= coins[i] + count++ + } + // 若未找到可行方案,则返回 -1 + if amt != 0 { + return -1 + } + return count +} diff --git a/codes/go/chapter_greedy/coin_change_greedy_test.go b/codes/go/chapter_greedy/coin_change_greedy_test.go new file mode 100644 index 0000000000..0c0bac32a3 --- /dev/null +++ b/codes/go/chapter_greedy/coin_change_greedy_test.go @@ -0,0 +1,35 @@ +// File: coin_change_greedy_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestCoinChangeGreedy(t *testing.T) { + // 贪心:能够保证找到全局最优解 + coins := []int{1, 5, 10, 20, 50, 100} + amt := 186 + res := coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res) + + // 贪心:无法保证找到全局最优解 + coins = []int{1, 20, 50} + amt = 60 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res) + fmt.Println("实际上需要的最少数量为 3 ,即 20 + 20 + 20") + + // 贪心:无法保证找到全局最优解 + coins = []int{1, 49, 50} + amt = 98 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("凑到 %d 所需的最少硬币数量为 %d\n", amt, res) + fmt.Println("实际上需要的最少数量为 2 ,即 49 + 49") +} diff --git a/codes/go/chapter_greedy/fractional_knapsack.go b/codes/go/chapter_greedy/fractional_knapsack.go new file mode 100644 index 0000000000..04ffb0f8fb --- /dev/null +++ b/codes/go/chapter_greedy/fractional_knapsack.go @@ -0,0 +1,41 @@ +// File: fractional_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "sort" + +/* 物品 */ +type Item struct { + w int // 物品重量 + v int // 物品价值 +} + +/* 分数背包:贪心 */ +func fractionalKnapsack(wgt []int, val []int, cap int) float64 { + // 创建物品列表,包含两个属性:重量、价值 + items := make([]Item, len(wgt)) + for i := 0; i < len(wgt); i++ { + items[i] = Item{wgt[i], val[i]} + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + sort.Slice(items, func(i, j int) bool { + return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) + }) + // 循环贪心选择 + res := 0.0 + for _, item := range items { + if item.w <= cap { + // 若剩余容量充足,则将当前物品整个装进背包 + res += float64(item.v) + cap -= item.w + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += float64(item.v) / float64(item.w) * float64(cap) + // 已无剩余容量,因此跳出循环 + break + } + } + return res +} diff --git a/codes/go/chapter_greedy/fractional_knapsack_test.go b/codes/go/chapter_greedy/fractional_knapsack_test.go new file mode 100644 index 0000000000..bb3b0f76ba --- /dev/null +++ b/codes/go/chapter_greedy/fractional_knapsack_test.go @@ -0,0 +1,20 @@ +// File: fractional_knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestFractionalKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + capacity := 50 + + // 贪心算法 + res := fractionalKnapsack(wgt, val, capacity) + fmt.Println("不超过背包容量的最大物品价值为", res) +} diff --git a/codes/go/chapter_greedy/max_capacity.go b/codes/go/chapter_greedy/max_capacity.go new file mode 100644 index 0000000000..178a4df12c --- /dev/null +++ b/codes/go/chapter_greedy/max_capacity.go @@ -0,0 +1,28 @@ +// File: max_capacity.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大容量:贪心 */ +func maxCapacity(ht []int) int { + // 初始化 i, j,使其分列数组两端 + i, j := 0, len(ht)-1 + // 初始最大容量为 0 + res := 0 + // 循环贪心选择,直至两板相遇 + for i < j { + // 更新最大容量 + capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) + res = int(math.Max(float64(res), float64(capacity))) + // 向内移动短板 + if ht[i] < ht[j] { + i++ + } else { + j-- + } + } + return res +} diff --git a/codes/go/chapter_greedy/max_capacity_test.go b/codes/go/chapter_greedy/max_capacity_test.go new file mode 100644 index 0000000000..9b14aff4a1 --- /dev/null +++ b/codes/go/chapter_greedy/max_capacity_test.go @@ -0,0 +1,18 @@ +// File: max_capacity_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxCapacity(t *testing.T) { + ht := []int{3, 8, 5, 2, 7, 7, 3, 4} + + // 贪心算法 + res := maxCapacity(ht) + fmt.Println("最大容量为", res) +} diff --git a/codes/go/chapter_greedy/max_product_cutting.go b/codes/go/chapter_greedy/max_product_cutting.go new file mode 100644 index 0000000000..35256707f0 --- /dev/null +++ b/codes/go/chapter_greedy/max_product_cutting.go @@ -0,0 +1,28 @@ +// File: max_product_cutting.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大切分乘积:贪心 */ +func maxProductCutting(n int) int { + // 当 n <= 3 时,必须切分出一个 1 + if n <= 3 { + return 1 * (n - 1) + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + a := n / 3 + b := n % 3 + if b == 1 { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return int(math.Pow(3, float64(a-1))) * 2 * 2 + } + if b == 2 { + // 当余数为 2 时,不做处理 + return int(math.Pow(3, float64(a))) * 2 + } + // 当余数为 0 时,不做处理 + return int(math.Pow(3, float64(a))) +} diff --git a/codes/go/chapter_greedy/max_product_cutting_test.go b/codes/go/chapter_greedy/max_product_cutting_test.go new file mode 100644 index 0000000000..e931d68547 --- /dev/null +++ b/codes/go/chapter_greedy/max_product_cutting_test.go @@ -0,0 +1,17 @@ +// File: max_product_cutting_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxProductCutting(t *testing.T) { + n := 58 + // 贪心算法 + res := maxProductCutting(n) + fmt.Println("最大切分乘积为", res) +} diff --git a/codes/go/chapter_hashing/array_hash_map.go b/codes/go/chapter_hashing/array_hash_map.go index 5fb1480cc0..8823a63c13 100644 --- a/codes/go/chapter_hashing/array_hash_map.go +++ b/codes/go/chapter_hashing/array_hash_map.go @@ -6,21 +6,22 @@ package chapter_hashing import "fmt" -/* 键值对 int->String */ -type entry struct { +/* 键值对 */ +type pair struct { key int val string } -/* 基于数组简易实现的哈希表 */ +/* 基于数组实现的哈希表 */ type arrayHashMap struct { - bucket []*entry + buckets []*pair } +/* 初始化哈希表 */ func newArrayHashMap() *arrayHashMap { - // 初始化一个长度为 100 的桶(数组) - bucket := make([]*entry, 100) - return &arrayHashMap{bucket: bucket} + // 初始化数组,包含 100 个桶 + buckets := make([]*pair, 100) + return &arrayHashMap{buckets: buckets} } /* 哈希函数 */ @@ -32,7 +33,7 @@ func (a *arrayHashMap) hashFunc(key int) int { /* 查询操作 */ func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) - pair := a.bucket[index] + pair := a.buckets[index] if pair == nil { return "Not Found" } @@ -41,22 +42,22 @@ func (a *arrayHashMap) get(key int) string { /* 添加操作 */ func (a *arrayHashMap) put(key int, val string) { - pair := &entry{key: key, val: val} + pair := &pair{key: key, val: val} index := a.hashFunc(key) - a.bucket[index] = pair + a.buckets[index] = pair } /* 删除操作 */ func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // 置为 nil ,代表删除 - a.bucket[index] = nil + a.buckets[index] = nil } /* 获取所有键对 */ -func (a *arrayHashMap) entrySet() []*entry { - var pairs []*entry - for _, pair := range a.bucket { +func (a *arrayHashMap) pairSet() []*pair { + var pairs []*pair + for _, pair := range a.buckets { if pair != nil { pairs = append(pairs, pair) } @@ -67,7 +68,7 @@ func (a *arrayHashMap) entrySet() []*entry { /* 获取所有键 */ func (a *arrayHashMap) keySet() []int { var keys []int - for _, pair := range a.bucket { + for _, pair := range a.buckets { if pair != nil { keys = append(keys, pair.key) } @@ -78,7 +79,7 @@ func (a *arrayHashMap) keySet() []int { /* 获取所有值 */ func (a *arrayHashMap) valueSet() []string { var values []string - for _, pair := range a.bucket { + for _, pair := range a.buckets { if pair != nil { values = append(values, pair.val) } @@ -88,7 +89,7 @@ func (a *arrayHashMap) valueSet() []string { /* 打印哈希表 */ func (a *arrayHashMap) print() { - for _, pair := range a.bucket { + for _, pair := range a.buckets { if pair != nil { fmt.Println(pair.key, "->", pair.val) } diff --git a/codes/go/chapter_hashing/array_hash_map_test.go b/codes/go/chapter_hashing/array_hash_map_test.go index 6925f0d578..ce976e5ebc 100644 --- a/codes/go/chapter_hashing/array_hash_map_test.go +++ b/codes/go/chapter_hashing/array_hash_map_test.go @@ -11,42 +11,42 @@ import ( func TestArrayHashMap(t *testing.T) { /* 初始化哈希表 */ - mapp := newArrayHashMap() + hmap := newArrayHashMap() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - mapp.put(12836, "小哈") - mapp.put(15937, "小啰") - mapp.put(16750, "小算") - mapp.put(13276, "小法") - mapp.put(10583, "小鸭") + hmap.put(12836, "小哈") + hmap.put(15937, "小啰") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鸭") fmt.Println("\n添加完成后,哈希表为\nKey -> Value") - mapp.print() + hmap.print() /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - name := mapp.get(15937) + // 向哈希表中输入键 key ,得到值 value + name := hmap.get(15937) fmt.Println("\n输入学号 15937 ,查询到姓名 " + name) /* 删除操作 */ // 在哈希表中删除键值对 (key, value) - mapp.remove(10583) + hmap.remove(10583) fmt.Println("\n删除 10583 后,哈希表为\nKey -> Value") - mapp.print() + hmap.print() /* 遍历哈希表 */ fmt.Println("\n遍历键值对 Key->Value") - for _, kv := range mapp.entrySet() { + for _, kv := range hmap.pairSet() { fmt.Println(kv.key, " -> ", kv.val) } fmt.Println("\n单独遍历键 Key") - for _, key := range mapp.keySet() { + for _, key := range hmap.keySet() { fmt.Println(key) } fmt.Println("\n单独遍历值 Value") - for _, val := range mapp.valueSet() { + for _, val := range hmap.valueSet() { fmt.Println(val) } } diff --git a/codes/go/chapter_hashing/hash_collision_test.go b/codes/go/chapter_hashing/hash_collision_test.go new file mode 100644 index 0000000000..4e59908d2f --- /dev/null +++ b/codes/go/chapter_hashing/hash_collision_test.go @@ -0,0 +1,62 @@ +// File: hash_collision_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestHashMapChaining(t *testing.T) { + /* 初始化哈希表 */ + hmap := newHashMapChaining() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小啰") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鸭") + fmt.Println("\n添加完成后,哈希表为\nKey -> Value") + hmap.print() + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + name := hmap.get(15937) + fmt.Println("\n输入学号 15937 ,查询到姓名", name) + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + hmap.remove(12836) + fmt.Println("\n删除 12836 后,哈希表为\nKey -> Value") + hmap.print() +} + +func TestHashMapOpenAddressing(t *testing.T) { + /* 初始化哈希表 */ + hmap := newHashMapOpenAddressing() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小啰") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鸭") + fmt.Println("\n添加完成后,哈希表为\nKey -> Value") + hmap.print() + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + name := hmap.get(13276) + fmt.Println("\n输入学号 13276 ,查询到姓名 ", name) + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + hmap.remove(16750) + fmt.Println("\n删除 16750 后,哈希表为\nKey -> Value") + hmap.print() +} diff --git a/codes/go/chapter_hashing/hash_map_chaining.go b/codes/go/chapter_hashing/hash_map_chaining.go new file mode 100644 index 0000000000..63874e1be7 --- /dev/null +++ b/codes/go/chapter_hashing/hash_map_chaining.go @@ -0,0 +1,134 @@ +// File: hash_map_chaining.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "strings" +) + +/* 链式地址哈希表 */ +type hashMapChaining struct { + size int // 键值对数量 + capacity int // 哈希表容量 + loadThres float64 // 触发扩容的负载因子阈值 + extendRatio int // 扩容倍数 + buckets [][]pair // 桶数组 +} + +/* 构造方法 */ +func newHashMapChaining() *hashMapChaining { + buckets := make([][]pair, 4) + for i := 0; i < 4; i++ { + buckets[i] = make([]pair, 0) + } + return &hashMapChaining{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: buckets, + } +} + +/* 哈希函数 */ +func (m *hashMapChaining) hashFunc(key int) int { + return key % m.capacity +} + +/* 负载因子 */ +func (m *hashMapChaining) loadFactor() float64 { + return float64(m.size) / float64(m.capacity) +} + +/* 查询操作 */ +func (m *hashMapChaining) get(key int) string { + idx := m.hashFunc(key) + bucket := m.buckets[idx] + // 遍历桶,若找到 key ,则返回对应 val + for _, p := range bucket { + if p.key == key { + return p.val + } + } + // 若未找到 key ,则返回空字符串 + return "" +} + +/* 添加操作 */ +func (m *hashMapChaining) put(key int, val string) { + // 当负载因子超过阈值时,执行扩容 + if m.loadFactor() > m.loadThres { + m.extend() + } + idx := m.hashFunc(key) + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for i := range m.buckets[idx] { + if m.buckets[idx][i].key == key { + m.buckets[idx][i].val = val + return + } + } + // 若无该 key ,则将键值对添加至尾部 + p := pair{ + key: key, + val: val, + } + m.buckets[idx] = append(m.buckets[idx], p) + m.size += 1 +} + +/* 删除操作 */ +func (m *hashMapChaining) remove(key int) { + idx := m.hashFunc(key) + // 遍历桶,从中删除键值对 + for i, p := range m.buckets[idx] { + if p.key == key { + // 切片删除 + m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) + m.size -= 1 + break + } + } +} + +/* 扩容哈希表 */ +func (m *hashMapChaining) extend() { + // 暂存原哈希表 + tmpBuckets := make([][]pair, len(m.buckets)) + for i := 0; i < len(m.buckets); i++ { + tmpBuckets[i] = make([]pair, len(m.buckets[i])) + copy(tmpBuckets[i], m.buckets[i]) + } + // 初始化扩容后的新哈希表 + m.capacity *= m.extendRatio + m.buckets = make([][]pair, m.capacity) + for i := 0; i < m.capacity; i++ { + m.buckets[i] = make([]pair, 0) + } + m.size = 0 + // 将键值对从原哈希表搬运至新哈希表 + for _, bucket := range tmpBuckets { + for _, p := range bucket { + m.put(p.key, p.val) + } + } +} + +/* 打印哈希表 */ +func (m *hashMapChaining) print() { + var builder strings.Builder + + for _, bucket := range m.buckets { + builder.WriteString("[") + for _, p := range bucket { + builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") + } + builder.WriteString("]") + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/codes/go/chapter_hashing/hash_map_open_addressing.go b/codes/go/chapter_hashing/hash_map_open_addressing.go new file mode 100644 index 0000000000..d4b80b9e3e --- /dev/null +++ b/codes/go/chapter_hashing/hash_map_open_addressing.go @@ -0,0 +1,126 @@ +// File: hash_map_open_addressing.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" +) + +/* 开放寻址哈希表 */ +type hashMapOpenAddressing struct { + size int // 键值对数量 + capacity int // 哈希表容量 + loadThres float64 // 触发扩容的负载因子阈值 + extendRatio int // 扩容倍数 + buckets []*pair // 桶数组 + TOMBSTONE *pair // 删除标记 +} + +/* 构造方法 */ +func newHashMapOpenAddressing() *hashMapOpenAddressing { + return &hashMapOpenAddressing{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: make([]*pair, 4), + TOMBSTONE: &pair{-1, "-1"}, + } +} + +/* 哈希函数 */ +func (h *hashMapOpenAddressing) hashFunc(key int) int { + return key % h.capacity // 根据键计算哈希值 +} + +/* 负载因子 */ +func (h *hashMapOpenAddressing) loadFactor() float64 { + return float64(h.size) / float64(h.capacity) // 计算当前负载因子 +} + +/* 搜索 key 对应的桶索引 */ +func (h *hashMapOpenAddressing) findBucket(key int) int { + index := h.hashFunc(key) // 获取初始索引 + firstTombstone := -1 // 记录遇到的第一个TOMBSTONE的位置 + for h.buckets[index] != nil { + if h.buckets[index].key == key { + if firstTombstone != -1 { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + h.buckets[firstTombstone] = h.buckets[index] + h.buckets[index] = h.TOMBSTONE + return firstTombstone // 返回移动后的桶索引 + } + return index // 返回找到的索引 + } + if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { + firstTombstone = index // 记录遇到的首个删除标记的位置 + } + index = (index + 1) % h.capacity // 线性探测,越过尾部则返回头部 + } + // 若 key 不存在,则返回添加点的索引 + if firstTombstone != -1 { + return firstTombstone + } + return index +} + +/* 查询操作 */ +func (h *hashMapOpenAddressing) get(key int) string { + index := h.findBucket(key) // 搜索 key 对应的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + return h.buckets[index].val // 若找到键值对,则返回对应 val + } + return "" // 若键值对不存在,则返回 "" +} + +/* 添加操作 */ +func (h *hashMapOpenAddressing) put(key int, val string) { + if h.loadFactor() > h.loadThres { + h.extend() // 当负载因子超过阈值时,执行扩容 + } + index := h.findBucket(key) // 搜索 key 对应的桶索引 + if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { + h.buckets[index] = &pair{key, val} // 若键值对不存在,则添加该键值对 + h.size++ + } else { + h.buckets[index].val = val // 若找到键值对,则覆盖 val + } +} + +/* 删除操作 */ +func (h *hashMapOpenAddressing) remove(key int) { + index := h.findBucket(key) // 搜索 key 对应的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + h.buckets[index] = h.TOMBSTONE // 若找到键值对,则用删除标记覆盖它 + h.size-- + } +} + +/* 扩容哈希表 */ +func (h *hashMapOpenAddressing) extend() { + oldBuckets := h.buckets // 暂存原哈希表 + h.capacity *= h.extendRatio // 更新容量 + h.buckets = make([]*pair, h.capacity) // 初始化扩容后的新哈希表 + h.size = 0 // 重置大小 + // 将键值对从原哈希表搬运至新哈希表 + for _, pair := range oldBuckets { + if pair != nil && pair != h.TOMBSTONE { + h.put(pair.key, pair.val) + } + } +} + +/* 打印哈希表 */ +func (h *hashMapOpenAddressing) print() { + for _, pair := range h.buckets { + if pair == nil { + fmt.Println("nil") + } else if pair == h.TOMBSTONE { + fmt.Println("TOMBSTONE") + } else { + fmt.Printf("%d -> %s\n", pair.key, pair.val) + } + } +} diff --git a/codes/go/chapter_hashing/hash_map_test.go b/codes/go/chapter_hashing/hash_map_test.go index c53d3991cd..1809ca4449 100644 --- a/codes/go/chapter_hashing/hash_map_test.go +++ b/codes/go/chapter_hashing/hash_map_test.go @@ -6,50 +6,69 @@ package chapter_hashing import ( "fmt" + "strconv" "testing" . "github.com/krahets/hello-algo/pkg" ) -func TestHashmap(t *testing.T) { +func TestHashMap(t *testing.T) { /* 初始化哈希表 */ - mapp := make(map[int]string) + hmap := make(map[int]string) /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" fmt.Println("\n添加完成后,哈希表为\nKey -> Value") - PrintMap(mapp) + PrintMap(hmap) /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - name := mapp[15937] + // 向哈希表中输入键 key ,得到值 value + name := hmap[15937] fmt.Println("\n输入学号 15937 ,查询到姓名 ", name) /* 删除操作 */ // 在哈希表中删除键值对 (key, value) - delete(mapp, 10583) + delete(hmap, 10583) fmt.Println("\n删除 10583 后,哈希表为\nKey -> Value") - PrintMap(mapp) + PrintMap(hmap) /* 遍历哈希表 */ // 遍历键值对 key->value fmt.Println("\n遍历键值对 Key->Value") - for key, value := range mapp { + for key, value := range hmap { fmt.Println(key, "->", value) } // 单独遍历键 key fmt.Println("\n单独遍历键 Key") - for key := range mapp { + for key := range hmap { fmt.Println(key) } // 单独遍历值 value fmt.Println("\n单独遍历值 Value") - for _, value := range mapp { + for _, value := range hmap { fmt.Println(value) } } + +func TestSimpleHash(t *testing.T) { + var hash int + + key := "Hello 算法" + + hash = addHash(key) + fmt.Println("加法哈希值为 " + strconv.Itoa(hash)) + + hash = mulHash(key) + fmt.Println("乘法哈希值为 " + strconv.Itoa(hash)) + + hash = xorHash(key) + fmt.Println("异或哈希值为 " + strconv.Itoa(hash)) + + hash = rotHash(key) + fmt.Println("旋转哈希值为 " + strconv.Itoa(hash)) +} diff --git a/codes/go/chapter_hashing/simple_hash.go b/codes/go/chapter_hashing/simple_hash.go new file mode 100644 index 0000000000..a843abaa02 --- /dev/null +++ b/codes/go/chapter_hashing/simple_hash.go @@ -0,0 +1,55 @@ +// File: simple_hash.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import "fmt" + +/* 加法哈希 */ +func addHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (hash + int64(b)) % modulus + } + return int(hash) +} + +/* 乘法哈希 */ +func mulHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (31*hash + int64(b)) % modulus + } + return int(hash) +} + +/* 异或哈希 */ +func xorHash(key string) int { + hash := 0 + modulus := 1000000007 + for _, b := range []byte(key) { + fmt.Println(int(b)) + hash ^= int(b) + hash = (31*hash + int(b)) % modulus + } + return hash & modulus +} + +/* 旋转哈希 */ +func rotHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus + } + return int(hash) +} diff --git a/codes/go/chapter_heap/heap.go b/codes/go/chapter_heap/heap.go new file mode 100644 index 0000000000..56a67f5cc4 --- /dev/null +++ b/codes/go/chapter_heap/heap.go @@ -0,0 +1,45 @@ +// File: heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +// Go 语言中可以通过实现 heap.Interface 来构建整数大顶堆 +// 实现 heap.Interface 需要同时实现 sort.Interface +type intHeap []any + +// Push heap.Interface 的函数,实现推入元素到堆 +func (h *intHeap) Push(x any) { + // Push 和 Pop 使用 pointer receiver 作为参数 + // 因为它们不仅会对切片的内容进行调整,还会修改切片的长度。 + *h = append(*h, x.(int)) +} + +// Pop heap.Interface 的函数,实现弹出堆顶元素 +func (h *intHeap) Pop() any { + // 待出堆元素存放在最后 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Len sort.Interface 的函数 +func (h *intHeap) Len() int { + return len(*h) +} + +// Less sort.Interface 的函数 +func (h *intHeap) Less(i, j int) bool { + // 如果实现小顶堆,则需要调整为小于号 + return (*h)[i].(int) > (*h)[j].(int) +} + +// Swap sort.Interface 的函数 +func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] +} + +// Top 获取堆顶元素 +func (h *intHeap) Top() any { + return (*h)[0] +} diff --git a/codes/go/chapter_heap/heap_test.go b/codes/go/chapter_heap/heap_test.go new file mode 100644 index 0000000000..4b84a05e55 --- /dev/null +++ b/codes/go/chapter_heap/heap_test.go @@ -0,0 +1,101 @@ +// File: heap_test.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "container/heap" + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func testPush(h *intHeap, val int) { + // 调用 heap.Interface 的函数,来添加元素 + heap.Push(h, val) + fmt.Printf("\n元素 %d 入堆后 \n", val) + PrintHeap(*h) +} + +func testPop(h *intHeap) { + // 调用 heap.Interface 的函数,来移除元素 + val := heap.Pop(h) + fmt.Printf("\n堆顶元素 %d 出堆后 \n", val) + PrintHeap(*h) +} + +func TestHeap(t *testing.T) { + /* 初始化堆 */ + // 初始化大顶堆 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 元素入堆 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* 获取堆顶元素 */ + top := maxHeap.Top() + fmt.Printf("堆顶元素为 %d\n", top) + + /* 堆顶元素出堆 */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* 获取堆大小 */ + size := len(*maxHeap) + fmt.Printf("堆元素数量为 %d\n", size) + + /* 判断堆是否为空 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("堆是否为空 %t\n", isEmpty) +} + +func TestMyHeap(t *testing.T) { + /* 初始化堆 */ + // 初始化大顶堆 + maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) + fmt.Printf("输入数组并建堆后\n") + maxHeap.print() + + /* 获取堆顶元素 */ + peek := maxHeap.peek() + fmt.Printf("\n堆顶元素为 %d\n", peek) + + /* 元素入堆 */ + val := 7 + maxHeap.push(val) + fmt.Printf("\n元素 %d 入堆后\n", val) + maxHeap.print() + + /* 堆顶元素出堆 */ + peek = maxHeap.pop() + fmt.Printf("\n堆顶元素 %d 出堆后\n", peek) + maxHeap.print() + + /* 获取堆大小 */ + size := maxHeap.size() + fmt.Printf("\n堆元素数量为 %d\n", size) + + /* 判断堆是否为空 */ + isEmpty := maxHeap.isEmpty() + fmt.Printf("\n堆是否为空 %t\n", isEmpty) +} + +func TestTopKHeap(t *testing.T) { + /* 初始化堆 */ + // 初始化大顶堆 + nums := []int{1, 7, 6, 3, 2} + k := 3 + res := topKHeap(nums, k) + fmt.Printf("最大的 " + strconv.Itoa(k) + " 个元素为") + PrintHeap(*res) +} diff --git a/codes/go/chapter_heap/my_heap.go b/codes/go/chapter_heap/my_heap.go new file mode 100644 index 0000000000..1cc091b2fd --- /dev/null +++ b/codes/go/chapter_heap/my_heap.go @@ -0,0 +1,140 @@ +// File: my_heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "fmt" + + . "github.com/krahets/hello-algo/pkg" +) + +type maxHeap struct { + // 使用切片而非数组,这样无须考虑扩容问题 + data []any +} + +/* 构造函数,建立空堆 */ +func newHeap() *maxHeap { + return &maxHeap{ + data: make([]any, 0), + } +} + +/* 构造函数,根据切片建堆 */ +func newMaxHeap(nums []any) *maxHeap { + // 将列表元素原封不动添加进堆 + h := &maxHeap{data: nums} + for i := h.parent(len(h.data) - 1); i >= 0; i-- { + // 堆化除叶节点以外的其他所有节点 + h.siftDown(i) + } + return h +} + +/* 获取左子节点的索引 */ +func (h *maxHeap) left(i int) int { + return 2*i + 1 +} + +/* 获取右子节点的索引 */ +func (h *maxHeap) right(i int) int { + return 2*i + 2 +} + +/* 获取父节点的索引 */ +func (h *maxHeap) parent(i int) int { + // 向下整除 + return (i - 1) / 2 +} + +/* 交换元素 */ +func (h *maxHeap) swap(i, j int) { + h.data[i], h.data[j] = h.data[j], h.data[i] +} + +/* 获取堆大小 */ +func (h *maxHeap) size() int { + return len(h.data) +} + +/* 判断堆是否为空 */ +func (h *maxHeap) isEmpty() bool { + return len(h.data) == 0 +} + +/* 访问堆顶元素 */ +func (h *maxHeap) peek() any { + return h.data[0] +} + +/* 元素入堆 */ +func (h *maxHeap) push(val any) { + // 添加节点 + h.data = append(h.data, val) + // 从底至顶堆化 + h.siftUp(len(h.data) - 1) +} + +/* 从节点 i 开始,从底至顶堆化 */ +func (h *maxHeap) siftUp(i int) { + for true { + // 获取节点 i 的父节点 + p := h.parent(i) + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 || h.data[i].(int) <= h.data[p].(int) { + break + } + // 交换两节点 + h.swap(i, p) + // 循环向上堆化 + i = p + } +} + +/* 元素出堆 */ +func (h *maxHeap) pop() any { + // 判空处理 + if h.isEmpty() { + fmt.Println("error") + return nil + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + h.swap(0, h.size()-1) + // 删除节点 + val := h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + // 从顶至底堆化 + h.siftDown(0) + + // 返回堆顶元素 + return val +} + +/* 从节点 i 开始,从顶至底堆化 */ +func (h *maxHeap) siftDown(i int) { + for true { + // 判断节点 i, l, r 中值最大的节点,记为 max + l, r, max := h.left(i), h.right(i), i + if l < h.size() && h.data[l].(int) > h.data[max].(int) { + max = l + } + if r < h.size() && h.data[r].(int) > h.data[max].(int) { + max = r + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if max == i { + break + } + // 交换两节点 + h.swap(i, max) + // 循环向下堆化 + i = max + } +} + +/* 打印堆(二叉树) */ +func (h *maxHeap) print() { + PrintHeap(h.data) +} diff --git a/codes/go/chapter_heap/top_k.go b/codes/go/chapter_heap/top_k.go new file mode 100644 index 0000000000..4874c3fb15 --- /dev/null +++ b/codes/go/chapter_heap/top_k.go @@ -0,0 +1,51 @@ +// File: top_k.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import "container/heap" + +type minHeap []any + +func (h *minHeap) Len() int { return len(*h) } +func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } +func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } + +// Push heap.Interface 的方法,实现推入元素到堆 +func (h *minHeap) Push(x any) { + *h = append(*h, x.(int)) +} + +// Pop heap.Interface 的方法,实现弹出堆顶元素 +func (h *minHeap) Pop() any { + // 待出堆元素存放在最后 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Top 获取堆顶元素 +func (h *minHeap) Top() any { + return (*h)[0] +} + +/* 基于堆查找数组中最大的 k 个元素 */ +func topKHeap(nums []int, k int) *minHeap { + // 初始化小顶堆 + h := &minHeap{} + heap.Init(h) + // 将数组的前 k 个元素入堆 + for i := 0; i < k; i++ { + heap.Push(h, nums[i]) + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for i := k; i < len(nums); i++ { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > h.Top().(int) { + heap.Pop(h) + heap.Push(h, nums[i]) + } + } + return h +} diff --git a/codes/go/chapter_searching/binary_search.go b/codes/go/chapter_searching/binary_search.go index cbaa85162e..699c653cc0 100644 --- a/codes/go/chapter_searching/binary_search.go +++ b/codes/go/chapter_searching/binary_search.go @@ -10,7 +10,7 @@ func binarySearch(nums []int, target int) int { i, j := 0, len(nums)-1 // 循环,当搜索区间为空时跳出(当 i > j 时为空) for i <= j { - m := (i + j) / 2 // 计算中点索引 m + 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] 中 @@ -23,13 +23,13 @@ func binarySearch(nums []int, target int) int { return -1 } -/* 二分查找(左闭右开) */ -func binarySearch1(nums []int, target int) int { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 +/* 二分查找(左闭右开区间) */ +func binarySearchLCRO(nums []int, target int) int { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 i, j := 0, len(nums) // 循环,当搜索区间为空时跳出(当 i = j 时为空) for i < j { - m := (i + j) / 2 // 计算中点索引 m + 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) 中 diff --git a/codes/go/chapter_searching/binary_search_edge.go b/codes/go/chapter_searching/binary_search_edge.go new file mode 100644 index 0000000000..c383adb010 --- /dev/null +++ b/codes/go/chapter_searching/binary_search_edge.go @@ -0,0 +1,31 @@ +// File: binary_search_edge.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 二分查找最左一个 target */ +func binarySearchLeftEdge(nums []int, target int) int { + // 等价于查找 target 的插入点 + i := binarySearchInsertion(nums, target) + // 未找到 target ,返回 -1 + if i == len(nums) || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分查找最右一个 target */ +func binarySearchRightEdge(nums []int, target int) int { + // 转化为查找最左一个 target + 1 + i := binarySearchInsertion(nums, target+1) + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + j := i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j +} diff --git a/codes/go/chapter_searching/binary_search_insertion.go b/codes/go/chapter_searching/binary_search_insertion.go new file mode 100644 index 0000000000..ffd4130324 --- /dev/null +++ b/codes/go/chapter_searching/binary_search_insertion.go @@ -0,0 +1,49 @@ +// File: binary_search_insertion.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 二分查找插入点(无重复元素) */ +func binarySearchInsertionSimple(nums []int, target int) int { + // 初始化双闭区间 [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // 计算中点索引 m + m := i + (j-i)/2 + if nums[m] < target { + // target 在区间 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { + // target 在区间 [i, m-1] 中 + j = m - 1 + } else { + // 找到 target ,返回插入点 m + return m + } + } + // 未找到 target ,返回插入点 i + return i +} + +/* 二分查找插入点(存在重复元素) */ +func binarySearchInsertion(nums []int, target int) int { + // 初始化双闭区间 [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // 计算中点索引 m + m := i + (j-i)/2 + if nums[m] < target { + // target 在区间 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { + // target 在区间 [i, m-1] 中 + j = m - 1 + } else { + // 首个小于 target 的元素在区间 [i, m-1] 中 + j = m - 1 + } + } + // 返回插入点 i + return i +} diff --git a/codes/go/chapter_searching/binary_search_test.go b/codes/go/chapter_searching/binary_search_test.go index 3dada9593f..eee9d3eb2c 100644 --- a/codes/go/chapter_searching/binary_search_test.go +++ b/codes/go/chapter_searching/binary_search_test.go @@ -11,14 +11,51 @@ import ( func TestBinarySearch(t *testing.T) { var ( - target = 3 - nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + target = 6 + nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} expected = 2 ) // 在数组中执行二分查找 actual := binarySearch(nums, target) - fmt.Println("目标元素 3 的索引 =", actual) + fmt.Println("目标元素 6 的索引 =", actual) if actual != expected { - t.Errorf("目标元素 3 的索引 = %d, 应该为 %d", actual, expected) + t.Errorf("目标元素 6 的索引 = %d, 应该为 %d", actual, expected) + } +} + +func TestBinarySearchEdge(t *testing.T) { + // 包含重复元素的数组 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("\n数组 nums = ", nums) + + // 二分查找左边界和右边界 + for _, target := range []int{6, 7} { + index := binarySearchLeftEdge(nums, target) + fmt.Println("最左一个元素", target, "的索引为", index) + + index = binarySearchRightEdge(nums, target) + fmt.Println("最右一个元素", target, "的索引为", index) + } +} + +func TestBinarySearchInsertion(t *testing.T) { + // 无重复元素的数组 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("数组 nums =", nums) + + // 二分查找插入点 + for _, target := range []int{6, 9} { + index := binarySearchInsertionSimple(nums, target) + fmt.Println("元素", target, "的插入点的索引为", index) + } + + // 包含重复元素的数组 + nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} + fmt.Println("\n数组 nums =", nums) + + // 二分查找插入点 + for _, target := range []int{2, 6, 20} { + index := binarySearchInsertion(nums, target) + fmt.Println("元素", target, "的插入点的索引为", index) } } diff --git a/codes/go/chapter_searching/hashing_search.go b/codes/go/chapter_searching/hashing_search.go index 22af449f0e..b3f9623d73 100644 --- a/codes/go/chapter_searching/hashing_search.go +++ b/codes/go/chapter_searching/hashing_search.go @@ -1,4 +1,4 @@ -// File: binary_search.go +// File: hashing_search.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) @@ -7,7 +7,7 @@ package chapter_searching import . "github.com/krahets/hello-algo/pkg" /* 哈希查找(数组) */ -func hashingSearch(m map[int]int, target int) int { +func hashingSearchArray(m map[int]int, target int) int { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 if index, ok := m[target]; ok { @@ -18,8 +18,8 @@ func hashingSearch(m map[int]int, target int) int { } /* 哈希查找(链表) */ -func hashingSearch1(m map[int]*ListNode, target int) *ListNode { - // 哈希表的 key: 目标结点值,value: 结点对象 +func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { + // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 nil if node, ok := m[target]; ok { return node diff --git a/codes/go/chapter_searching/hashing_search_test.go b/codes/go/chapter_searching/hashing_search_test.go index 18cf5c651c..8db3231fdf 100644 --- a/codes/go/chapter_searching/hashing_search_test.go +++ b/codes/go/chapter_searching/hashing_search_test.go @@ -1,4 +1,4 @@ -// File: binary_search.go +// File: hashing_search_test.go // Created Time: 2022-12-12 // Author: Slone123c (274325721@qq.com) @@ -20,7 +20,7 @@ func TestHashingSearch(t *testing.T) { for i := 0; i < len(nums); i++ { m[nums[i]] = i } - index := hashingSearch(m, target) + index := hashingSearchArray(m, target) fmt.Println("目标元素 3 的索引 = ", index) /* 哈希查找(链表) */ @@ -31,6 +31,6 @@ func TestHashingSearch(t *testing.T) { m1[head.Val] = head head = head.Next } - node := hashingSearch1(m1, target) - fmt.Println("目标结点值 3 的对应结点对象为 ", node) + node := hashingSearchLinkedList(m1, target) + fmt.Println("目标节点值 3 的对应节点对象为 ", node) } diff --git a/codes/go/chapter_searching/linear_search.go b/codes/go/chapter_searching/linear_search.go index 2560375197..f55978e636 100644 --- a/codes/go/chapter_searching/linear_search.go +++ b/codes/go/chapter_searching/linear_search.go @@ -9,7 +9,7 @@ import ( ) /* 线性查找(数组) */ -func linerSearchArray(nums []int, target int) int { +func linearSearchArray(nums []int, target int) int { // 遍历数组 for i := 0; i < len(nums); i++ { // 找到目标元素,返回其索引 @@ -22,10 +22,10 @@ func linerSearchArray(nums []int, target int) int { } /* 线性查找(链表) */ -func linerSearchLinkedList(node *ListNode, target int) *ListNode { +func linearSearchLinkedList(node *ListNode, target int) *ListNode { // 遍历链表 for node != nil { - // 找到目标元素,返回其索引 + // 找到目标节点,返回之 if node.Val == target { return node } diff --git a/codes/go/chapter_searching/linear_search_test.go b/codes/go/chapter_searching/linear_search_test.go index 79525aace0..21523bdfc1 100644 --- a/codes/go/chapter_searching/linear_search_test.go +++ b/codes/go/chapter_searching/linear_search_test.go @@ -16,11 +16,11 @@ func TestLinearSearch(t *testing.T) { nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} // 在数组中执行线性查找 - index := linerSearchArray(nums, target) + index := linearSearchArray(nums, target) fmt.Println("目标元素 3 的索引 =", index) // 在链表中执行线性查找 head := ArrayToLinkedList(nums) - node := linerSearchLinkedList(head, target) - fmt.Println("目标结点值 3 的对应结点对象为", node) + node := linearSearchLinkedList(head, target) + fmt.Println("目标节点值 3 的对应节点对象为", node) } diff --git a/codes/go/chapter_searching/two_sum.go b/codes/go/chapter_searching/two_sum.go new file mode 100644 index 0000000000..565540d4b7 --- /dev/null +++ b/codes/go/chapter_searching/two_sum.go @@ -0,0 +1,33 @@ +// File: two_sum.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +/* 方法一:暴力枚举 */ +func twoSumBruteForce(nums []int, target int) []int { + size := len(nums) + // 两层循环,时间复杂度为 O(n^2) + for i := 0; i < size-1; i++ { + for j := i + 1; j < size; j++ { + if nums[i]+nums[j] == target { + return []int{i, j} + } + } + } + return nil +} + +/* 方法二:辅助哈希表 */ +func twoSumHashTable(nums []int, target int) []int { + // 辅助哈希表,空间复杂度为 O(n) + hashTable := map[int]int{} + // 单层循环,时间复杂度为 O(n) + for idx, val := range nums { + if preIdx, ok := hashTable[target-val]; ok { + return []int{preIdx, idx} + } + hashTable[val] = idx + } + return nil +} diff --git a/codes/go/chapter_searching/two_sum_test.go b/codes/go/chapter_searching/two_sum_test.go new file mode 100644 index 0000000000..82581bc637 --- /dev/null +++ b/codes/go/chapter_searching/two_sum_test.go @@ -0,0 +1,24 @@ +// File: two_sum_test.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestTwoSum(t *testing.T) { + // ======= Test Case ======= + nums := []int{2, 7, 11, 15} + target := 13 + + // ====== Driver Code ====== + // 方法一:暴力解法 + res := twoSumBruteForce(nums, target) + fmt.Println("方法一 res =", res) + // 方法二:哈希表 + res = twoSumHashTable(nums, target) + fmt.Println("方法二 res =", res) +} diff --git a/codes/go/chapter_sorting/bubble_sort.go b/codes/go/chapter_sorting/bubble_sort.go index ff1ff9ee56..773611e477 100644 --- a/codes/go/chapter_sorting/bubble_sort.go +++ b/codes/go/chapter_sorting/bubble_sort.go @@ -6,9 +6,9 @@ package chapter_sorting /* 冒泡排序 */ func bubbleSort(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for i := len(nums) - 1; i > 0; i-- { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交换 nums[j] 与 nums[j + 1] @@ -20,10 +20,10 @@ func bubbleSort(nums []int) { /* 冒泡排序(标志优化)*/ func bubbleSortWithFlag(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for i := len(nums) - 1; i > 0; i-- { flag := false // 初始化标志位 - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j := 0; j < i; j++ { if nums[j] > nums[j+1] { // 交换 nums[j] 与 nums[j + 1] @@ -31,7 +31,7 @@ func bubbleSortWithFlag(nums []int) { flag = true // 记录交换元素 } } - if flag == false { // 此轮冒泡未交换任何元素,直接跳出 + if flag == false { // 此轮“冒泡”未交换任何元素,直接跳出 break } } diff --git a/codes/go/chapter_sorting/bubble_sort_test.go b/codes/go/chapter_sorting/bubble_sort_test.go index 53a7d055ac..0588ae57b3 100644 --- a/codes/go/chapter_sorting/bubble_sort_test.go +++ b/codes/go/chapter_sorting/bubble_sort_test.go @@ -16,5 +16,5 @@ func TestBubbleSort(t *testing.T) { nums1 := []int{4, 1, 3, 1, 5, 2} bubbleSortWithFlag(nums1) - fmt.Println("冒泡排序完成后 nums1 = ", nums) + fmt.Println("冒泡排序完成后 nums1 = ", nums1) } diff --git a/codes/go/chapter_sorting/bucket_sort.go b/codes/go/chapter_sorting/bucket_sort.go new file mode 100644 index 0000000000..3f8e05ea7d --- /dev/null +++ b/codes/go/chapter_sorting/bucket_sort.go @@ -0,0 +1,37 @@ +// File: bucket_sort.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "sort" + +/* 桶排序 */ +func bucketSort(nums []float64) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + k := len(nums) / 2 + buckets := make([][]float64, k) + for i := 0; i < k; i++ { + buckets[i] = make([]float64, 0) + } + // 1. 将数组元素分配到各个桶中 + for _, num := range nums { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + i := int(num * float64(k)) + // 将 num 添加进桶 i + buckets[i] = append(buckets[i], num) + } + // 2. 对各个桶执行排序 + for i := 0; i < k; i++ { + // 使用内置切片排序函数,也可以替换成其他排序算法 + sort.Float64s(buckets[i]) + } + // 3. 遍历桶合并结果 + i := 0 + for _, bucket := range buckets { + for _, num := range bucket { + nums[i] = num + i++ + } + } +} diff --git a/codes/go/chapter_sorting/bucket_sort_test.go b/codes/go/chapter_sorting/bucket_sort_test.go new file mode 100644 index 0000000000..49d56014df --- /dev/null +++ b/codes/go/chapter_sorting/bucket_sort_test.go @@ -0,0 +1,17 @@ +// File: bucket_sort_test.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBucketSort(t *testing.T) { + // 设输入数据为浮点数,范围为 [0, 1) + nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} + bucketSort(nums) + fmt.Println("桶排序完成后 nums = ", nums) +} diff --git a/codes/go/chapter_sorting/counting_sort.go b/codes/go/chapter_sorting/counting_sort.go new file mode 100644 index 0000000000..a1322347a2 --- /dev/null +++ b/codes/go/chapter_sorting/counting_sort.go @@ -0,0 +1,68 @@ +// File: counting_sort.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +type CountingSort struct{} + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +func countingSortNaive(nums []int) { + // 1. 统计数组最大元素 m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. 遍历 counter ,将各元素填入原数组 nums + for i, num := 0, 0; num < m+1; num++ { + for j := 0; j < counter[num]; j++ { + nums[i] = num + i++ + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +func countingSort(nums []int) { + // 1. 统计数组最大元素 m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i := 0; i < m; i++ { + counter[i+1] += counter[i] + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + n := len(nums) + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + num := nums[i] + // 将 num 放置到对应索引处 + res[counter[num]-1] = num + // 令前缀和自减 1 ,得到下次放置 num 的索引 + counter[num]-- + } + // 使用结果数组 res 覆盖原数组 nums + copy(nums, res) +} diff --git a/codes/go/chapter_sorting/counting_sort_test.go b/codes/go/chapter_sorting/counting_sort_test.go new file mode 100644 index 0000000000..5cae817fb9 --- /dev/null +++ b/codes/go/chapter_sorting/counting_sort_test.go @@ -0,0 +1,20 @@ +// File: counting_sort_test.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestCountingSort(t *testing.T) { + nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSortNaive(nums) + fmt.Println("计数排序(无法排序对象)完成后 nums = ", nums) + + nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSort(nums1) + fmt.Println("计数排序完成后 nums1 = ", nums1) +} diff --git a/codes/go/chapter_sorting/heap_sort.go b/codes/go/chapter_sorting/heap_sort.go new file mode 100644 index 0000000000..7ab866eaf2 --- /dev/null +++ b/codes/go/chapter_sorting/heap_sort.go @@ -0,0 +1,44 @@ +// File: heap_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +func siftDown(nums *[]int, n, i int) { + for true { + // 判断节点 i, l, r 中值最大的节点,记为 ma + l := 2*i + 1 + r := 2*i + 2 + 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 + } + // 交换两节点 + (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] + // 循环向下堆化 + i = ma + } +} + +/* 堆排序 */ +func heapSort(nums *[]int) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for i := len(*nums)/2 - 1; i >= 0; i-- { + siftDown(nums, len(*nums), i) + } + // 从堆中提取最大元素,循环 n-1 轮 + for i := len(*nums) - 1; i > 0; i-- { + // 交换根节点与最右叶节点(交换首元素与尾元素) + (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0) + } +} diff --git a/codes/go/chapter_sorting/heap_sort_test.go b/codes/go/chapter_sorting/heap_sort_test.go new file mode 100644 index 0000000000..9477d89ea7 --- /dev/null +++ b/codes/go/chapter_sorting/heap_sort_test.go @@ -0,0 +1,16 @@ +// File: heap_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestHeapSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + heapSort(&nums) + fmt.Println("堆排序完成后 nums = ", nums) +} diff --git a/codes/go/chapter_sorting/insertion_sort.go b/codes/go/chapter_sorting/insertion_sort.go index 24101e969a..de57e75299 100644 --- a/codes/go/chapter_sorting/insertion_sort.go +++ b/codes/go/chapter_sorting/insertion_sort.go @@ -4,16 +4,17 @@ package chapter_sorting +/* 插入排序 */ func insertionSort(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:已排序区间为 [0, i-1] for i := 1; i < len(nums); i++ { base := nums[i] j := i - 1 - // 内循环:将 base 插入到左边的正确位置 + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 for j >= 0 && nums[j] > base { - nums[j+1] = nums[j] // 1. 将 nums[j] 向右移动一位 + nums[j+1] = nums[j] // 将 nums[j] 向右移动一位 j-- } - nums[j+1] = base // 2. 将 base 赋值到正确位置 + nums[j+1] = base // 将 base 赋值到正确位置 } } diff --git a/codes/go/chapter_sorting/merge_sort.go b/codes/go/chapter_sorting/merge_sort.go index 43aff01a79..252618fbda 100644 --- a/codes/go/chapter_sorting/merge_sort.go +++ b/codes/go/chapter_sorting/merge_sort.go @@ -4,46 +4,49 @@ package chapter_sorting -// 合并左子数组和右子数组 -// 左子数组区间 [left, mid] -// 右子数组区间 [mid + 1, right] +/* 合并左子数组和右子数组 */ func merge(nums []int, left, mid, right int) { - // 初始化辅助数组 借助 copy 模块 + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 tmp := make([]int, right-left+1) - for i := left; i <= right; i++ { - tmp[i-left] = nums[i] - } - // 左子数组的起始索引和结束索引 - leftStart, leftEnd := left-left, mid-left - // 右子数组的起始索引和结束索引 - rightStart, rightEnd := mid+1-left, right-left - // i, j 分别指向左子数组、右子数组的首元素 - i, j := leftStart, rightStart - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for k := left; k <= right; k++ { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > leftEnd { - nums[k] = tmp[j] - j++ - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if j > rightEnd || tmp[i] <= tmp[j] { - nums[k] = tmp[i] + // 初始化左子数组和右子数组的起始索引 + i, j, k := left, mid+1, 0 + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + for i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] i++ - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { - nums[k] = tmp[j] + tmp[k] = nums[j] j++ } + k++ + } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + for i <= mid { + tmp[k] = nums[i] + i++ + k++ + } + for j <= right { + tmp[k] = nums[j] + j++ + k++ + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for k := 0; k < len(tmp); k++ { + nums[left+k] = tmp[k] } } +/* 归并排序 */ func mergeSort(nums []int, left, right int) { // 终止条件 if left >= right { return } // 划分阶段 - mid := (left + right) / 2 + mid := left + (right - left) / 2 mergeSort(nums, left, mid) mergeSort(nums, mid+1, right) // 合并阶段 diff --git a/codes/go/chapter_sorting/quick_sort.go b/codes/go/chapter_sorting/quick_sort.go index 853bce452a..f5cbf15245 100644 --- a/codes/go/chapter_sorting/quick_sort.go +++ b/codes/go/chapter_sorting/quick_sort.go @@ -15,7 +15,7 @@ type quickSortTailCall struct{} /* 哨兵划分 */ func (q *quickSort) partition(nums []int, left, right int) int { - // 以 nums[left] 作为基准数 + // 以 nums[left] 为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { @@ -45,23 +45,25 @@ func (q *quickSort) quickSort(nums []int, left, right int) { q.quickSort(nums, pivot+1, right) } -/* 选取三个元素的中位数 */ +/* 选取三个候选元素的中位数 */ func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { - if (nums[left] > nums[mid]) != (nums[left] > nums[right]) { - return left - } else if (nums[mid] < nums[left]) != (nums[mid] > nums[right]) { - return mid + l, m, r := nums[left], nums[mid], 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 } /* 哨兵划分(三数取中值)*/ func (q *quickSortMedian) partition(nums []int, left, right int) int { - // 以 nums[left] 作为基准数 + // 以 nums[left] 为基准数 med := q.medianThree(nums, left, (left+right)/2, right) // 将中位数交换至数组最左端 nums[left], nums[med] = nums[med], nums[left] - // 以 nums[left] 作为基准数 + // 以 nums[left] 为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { @@ -93,7 +95,7 @@ func (q *quickSortMedian) quickSort(nums []int, left, right int) { /* 哨兵划分 */ func (q *quickSortTailCall) partition(nums []int, left, right int) int { - // 以 nums[left] 作为基准数 + // 以 nums[left] 为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { @@ -116,13 +118,13 @@ func (q *quickSortTailCall) quickSort(nums []int, left, right int) { for left < right { // 哨兵划分操作 pivot := q.partition(nums, left, right) - // 对两个子数组中较短的那个执行快排 + // 对两个子数组中较短的那个执行快速排序 if pivot-left < right-pivot { q.quickSort(nums, left, pivot-1) // 递归排序左子数组 - left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] + left = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] } else { q.quickSort(nums, pivot+1, right) // 递归排序右子数组 - right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] } } } diff --git a/codes/go/chapter_sorting/radix_sort.go b/codes/go/chapter_sorting/radix_sort.go new file mode 100644 index 0000000000..43a0c8811e --- /dev/null +++ b/codes/go/chapter_sorting/radix_sort.go @@ -0,0 +1,60 @@ +// File: radix_sort.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "math" + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +func digit(num, exp int) int { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10 +} + +/* 计数排序(根据 nums 第 k 位排序) */ +func countingSortDigit(nums []int, exp int) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + counter := make([]int, 10) + n := len(nums) + // 统计 0~9 各数字的出现次数 + for i := 0; i < n; i++ { + d := digit(nums[i], exp) // 获取 nums[i] 第 k 位,记为 d + counter[d]++ // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for i := 1; i < 10; i++ { + counter[i] += counter[i-1] + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + d := digit(nums[i], exp) + j := counter[d] - 1 // 获取 d 在数组中的索引 j + res[j] = nums[i] // 将当前元素填入索引 j + counter[d]-- // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for i := 0; i < n; i++ { + nums[i] = res[i] + } +} + +/* 基数排序 */ +func radixSort(nums []int) { + // 获取数组的最大元素,用于判断最大位数 + max := math.MinInt + for _, num := range nums { + if num > max { + max = num + } + } + // 按照从低位到高位的顺序遍历 + for exp := 1; max >= exp; exp *= 10 { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp) + } +} diff --git a/codes/go/chapter_sorting/radix_sort_test.go b/codes/go/chapter_sorting/radix_sort_test.go new file mode 100644 index 0000000000..1fb5b12a95 --- /dev/null +++ b/codes/go/chapter_sorting/radix_sort_test.go @@ -0,0 +1,18 @@ +// File: radix_sort_test.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestRadixSort(t *testing.T) { + /* 基数排序 */ + nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996} + radixSort(nums) + fmt.Println("基数排序完成后 nums = ", nums) +} diff --git a/codes/go/chapter_sorting/selection_sort.go b/codes/go/chapter_sorting/selection_sort.go new file mode 100644 index 0000000000..021222aca3 --- /dev/null +++ b/codes/go/chapter_sorting/selection_sort.go @@ -0,0 +1,24 @@ +// File: selection_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* 选择排序 */ +func selectionSort(nums []int) { + n := len(nums) + // 外循环:未排序区间为 [i, n-1] + for i := 0; i < n-1; i++ { + // 内循环:找到未排序区间内的最小元素 + k := i + for j := i + 1; j < n; j++ { + if nums[j] < nums[k] { + // 记录最小元素的索引 + k = j + } + } + // 将该最小元素与未排序区间的首个元素交换 + nums[i], nums[k] = nums[k], nums[i] + + } +} diff --git a/codes/go/chapter_sorting/selection_sort_test.go b/codes/go/chapter_sorting/selection_sort_test.go new file mode 100644 index 0000000000..937f74503d --- /dev/null +++ b/codes/go/chapter_sorting/selection_sort_test.go @@ -0,0 +1,16 @@ +// File: selection_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestSelectionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + selectionSort(nums) + fmt.Println("选择排序完成后 nums = ", nums) +} diff --git a/codes/go/chapter_stack_and_queue/array_deque.go b/codes/go/chapter_stack_and_queue/array_deque.go new file mode 100644 index 0000000000..37b436f2bd --- /dev/null +++ b/codes/go/chapter_stack_and_queue/array_deque.go @@ -0,0 +1,121 @@ +// File: array_deque.go +// Created Time: 2023-03-13 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import "fmt" + +/* 基于环形数组实现的双向队列 */ +type arrayDeque struct { + nums []int // 用于存储双向队列元素的数组 + front int // 队首指针,指向队首元素 + queSize int // 双向队列长度 + queCapacity int // 队列容量(即最大容纳元素数量) +} + +/* 初始化队列 */ +func newArrayDeque(queCapacity int) *arrayDeque { + return &arrayDeque{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* 获取双向队列的长度 */ +func (q *arrayDeque) size() int { + return q.queSize +} + +/* 判断双向队列是否为空 */ +func (q *arrayDeque) isEmpty() bool { + return q.queSize == 0 +} + +/* 计算环形数组索引 */ +func (q *arrayDeque) index(i int) int { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + q.queCapacity) % q.queCapacity +} + +/* 队首入队 */ +func (q *arrayDeque) pushFirst(num int) { + if q.queSize == q.queCapacity { + fmt.Println("双向队列已满") + return + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + q.front = q.index(q.front - 1) + // 将 num 添加至队首 + q.nums[q.front] = num + q.queSize++ +} + +/* 队尾入队 */ +func (q *arrayDeque) pushLast(num int) { + if q.queSize == q.queCapacity { + fmt.Println("双向队列已满") + return + } + // 计算队尾指针,指向队尾索引 + 1 + rear := q.index(q.front + q.queSize) + // 将 num 添加至队尾 + q.nums[rear] = num + q.queSize++ +} + +/* 队首出队 */ +func (q *arrayDeque) popFirst() any { + num := q.peekFirst() + if num == nil { + return nil + } + // 队首指针向后移动一位 + q.front = q.index(q.front + 1) + q.queSize-- + return num +} + +/* 队尾出队 */ +func (q *arrayDeque) popLast() any { + num := q.peekLast() + if num == nil { + return nil + } + q.queSize-- + return num +} + +/* 访问队首元素 */ +func (q *arrayDeque) peekFirst() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* 访问队尾元素 */ +func (q *arrayDeque) peekLast() any { + if q.isEmpty() { + return nil + } + // 计算尾元素索引 + last := q.index(q.front + q.queSize - 1) + return q.nums[last] +} + +/* 获取 Slice 用于打印 */ +func (q *arrayDeque) toSlice() []int { + // 仅转换有效长度范围内的列表元素 + res := make([]int, q.queSize) + for i, j := 0, q.front; i < q.queSize; i++ { + res[i] = q.nums[q.index(j)] + j++ + } + return res +} diff --git a/codes/go/chapter_stack_and_queue/array_queue.go b/codes/go/chapter_stack_and_queue/array_queue.go index 7d0367df84..d8723e8e34 100644 --- a/codes/go/chapter_stack_and_queue/array_queue.go +++ b/codes/go/chapter_stack_and_queue/array_queue.go @@ -6,66 +6,73 @@ package chapter_stack_and_queue /* 基于环形数组实现的队列 */ type arrayQueue struct { - data []int // 用于存储队列元素的数组 - capacity int // 队列容量(即最多容量的元素个数) - front int // 头指针,指向队首 - rear int // 尾指针,指向队尾 + 1 + nums []int // 用于存储队列元素的数组 + front int // 队首指针,指向队首元素 + queSize int // 队列长度 + queCapacity int // 队列容量(即最大容纳元素数量) } -// newArrayQueue 基于环形数组实现的队列 -func newArrayQueue(capacity int) *arrayQueue { +/* 初始化队列 */ +func newArrayQueue(queCapacity int) *arrayQueue { return &arrayQueue{ - data: make([]int, capacity), - capacity: capacity, - front: 0, - rear: 0, + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, } } -// size 获取队列的长度 +/* 获取队列的长度 */ func (q *arrayQueue) size() int { - size := (q.capacity + q.rear - q.front) % q.capacity - return size + return q.queSize } -// isEmpty 判断队列是否为空 +/* 判断队列是否为空 */ func (q *arrayQueue) isEmpty() bool { - return q.rear-q.front == 0 + return q.queSize == 0 } -// offer 入队 -func (q *arrayQueue) offer(v int) { - // 当 rear == capacity 表示队列已满 - if q.size() == q.capacity { +/* 入队 */ +func (q *arrayQueue) push(num int) { + // 当 rear == queCapacity 表示队列已满 + if q.queSize == q.queCapacity { return } - // 尾结点后添加 - q.data[q.rear] = v - // 尾指针向后移动一位,越过尾部后返回到数组头部 - q.rear = (q.rear + 1) % q.capacity + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + rear := (q.front + q.queSize) % q.queCapacity + // 将 num 添加至队尾 + q.nums[rear] = num + q.queSize++ } -// poll 出队 -func (q *arrayQueue) poll() any { - if q.isEmpty() { +/* 出队 */ +func (q *arrayQueue) pop() any { + num := q.peek() + if num == nil { return nil } - v := q.data[q.front] - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - q.front = (q.front + 1) % q.capacity - return v + + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + q.front = (q.front + 1) % q.queCapacity + q.queSize-- + return num } -// peek 访问队首元素 +/* 访问队首元素 */ func (q *arrayQueue) peek() any { if q.isEmpty() { return nil } - v := q.data[q.front] - return v + return q.nums[q.front] } -// 获取 Slice 用于打印 +/* 获取 Slice 用于打印 */ func (q *arrayQueue) toSlice() []int { - return q.data[q.front:q.rear] + rear := (q.front + q.queSize) + if rear >= q.queCapacity { + rear %= q.queCapacity + return append(q.nums[q.front:], q.nums[:rear]...) + } + return q.nums[q.front:rear] } diff --git a/codes/go/chapter_stack_and_queue/array_stack.go b/codes/go/chapter_stack_and_queue/array_stack.go index ef32ebe4c5..ab644e76a3 100644 --- a/codes/go/chapter_stack_and_queue/array_stack.go +++ b/codes/go/chapter_stack_and_queue/array_stack.go @@ -9,6 +9,7 @@ type arrayStack struct { data []int // 数据 } +/* 初始化栈 */ func newArrayStack() *arrayStack { return &arrayStack{ // 设置栈的长度为 0,容量为 16 @@ -16,34 +17,30 @@ func newArrayStack() *arrayStack { } } -// size 栈的长度 +/* 栈的长度 */ func (s *arrayStack) size() int { return len(s.data) } -// isEmpty 栈是否为空 +/* 栈是否为空 */ func (s *arrayStack) isEmpty() bool { return s.size() == 0 } -// push 入栈 +/* 入栈 */ func (s *arrayStack) push(v int) { // 切片会自动扩容 s.data = append(s.data, v) } -// pop 出栈 +/* 出栈 */ func (s *arrayStack) pop() any { - // 弹出栈前,先判断是否为空 - if s.isEmpty() { - return nil - } val := s.peek() s.data = s.data[:len(s.data)-1] return val } -// peek 获取栈顶元素 +/* 获取栈顶元素 */ func (s *arrayStack) peek() any { if s.isEmpty() { return nil @@ -52,7 +49,7 @@ func (s *arrayStack) peek() any { return val } -// 获取 Slice 用于打印 +/* 获取 Slice 用于打印 */ func (s *arrayStack) toSlice() []int { return s.data } diff --git a/codes/go/chapter_stack_and_queue/deque_test.go b/codes/go/chapter_stack_and_queue/deque_test.go index 4c0f2f5338..f68a958d03 100644 --- a/codes/go/chapter_stack_and_queue/deque_test.go +++ b/codes/go/chapter_stack_and_queue/deque_test.go @@ -49,16 +49,59 @@ func TestDeque(t *testing.T) { fmt.Println("双向队列是否为空 =", isEmpty) } +func TestArrayDeque(t *testing.T) { + /* 初始化双向队列 */ + // 在 Go 中,将 list 作为双向队列使用 + deque := newArrayDeque(16) + + /* 元素入队 */ + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + fmt.Print("双向队列 deque = ") + PrintSlice(deque.toSlice()) + + /* 访问元素 */ + peekFirst := deque.peekFirst() + fmt.Println("队首元素 peekFirst =", peekFirst) + peekLast := deque.peekLast() + fmt.Println("队尾元素 peekLast =", peekLast) + + /* 元素入队 */ + deque.pushLast(4) + fmt.Print("元素 4 队尾入队后 deque = ") + PrintSlice(deque.toSlice()) + deque.pushFirst(1) + fmt.Print("元素 1 队首入队后 deque = ") + PrintSlice(deque.toSlice()) + + /* 元素出队 */ + popFirst := deque.popFirst() + fmt.Print("队首出队元素 popFirst = ", popFirst, ",队首出队后 deque = ") + PrintSlice(deque.toSlice()) + popLast := deque.popLast() + fmt.Print("队尾出队元素 popLast = ", popLast, ",队尾出队后 deque = ") + PrintSlice(deque.toSlice()) + + /* 获取双向队列的长度 */ + size := deque.size() + fmt.Println("双向队列长度 size =", size) + + /* 判断双向队列是否为空 */ + isEmpty := deque.isEmpty() + fmt.Println("双向队列是否为空 =", isEmpty) +} + func TestLinkedListDeque(t *testing.T) { // 初始化队列 deque := newLinkedListDeque() // 元素入队 - deque.offerLast(2) - deque.offerLast(5) - deque.offerLast(4) - deque.offerFirst(3) - deque.offerFirst(1) + deque.pushLast(2) + deque.pushLast(5) + deque.pushLast(4) + deque.pushFirst(3) + deque.pushFirst(1) fmt.Print("队列 deque = ") PrintList(deque.toList()) @@ -69,11 +112,11 @@ func TestLinkedListDeque(t *testing.T) { fmt.Println("队尾元素 rear =", rear) // 元素出队 - pollFirst := deque.pollFirst() - fmt.Print("队首出队元素 pollFirst = ", pollFirst, ",队首出队后 deque = ") + popFirst := deque.popFirst() + fmt.Print("队首出队元素 popFirst = ", popFirst, ",队首出队后 deque = ") PrintList(deque.toList()) - pollLast := deque.pollLast() - fmt.Print("队尾出队元素 pollLast = ", pollLast, ",队尾出队后 deque = ") + popLast := deque.popLast() + fmt.Print("队尾出队元素 popLast = ", popLast, ",队尾出队后 deque = ") PrintList(deque.toList()) // 获取队的长度 @@ -85,14 +128,14 @@ func TestLinkedListDeque(t *testing.T) { fmt.Println("队是否为空 =", isEmpty) } -// BenchmarkArrayQueue 67.92 ns/op in Mac M1 Pro +// BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { - stack := newLinkedListDeque() + deque := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { - stack.offerLast(777) + deque.pushLast(777) } for i := 0; i < b.N; i++ { - stack.pollFirst() + deque.popFirst() } } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_deque.go b/codes/go/chapter_stack_and_queue/linkedlist_deque.go index b1db77ca69..b1f21b8494 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_deque.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_deque.go @@ -8,30 +8,31 @@ import ( "container/list" ) -// linkedListDeque 基于链表实现的双端队列, 使用内置包 list 来实现栈 +/* 基于双向链表实现的双向队列 */ type linkedListDeque struct { + // 使用内置包 list data *list.List } -// newLinkedListDeque 初始化双端队列 +/* 初始化双端队列 */ func newLinkedListDeque() *linkedListDeque { return &linkedListDeque{ data: list.New(), } } -// offerFirst 队首元素入队 -func (s *linkedListDeque) offerFirst(value any) { +/* 队首元素入队 */ +func (s *linkedListDeque) pushFirst(value any) { s.data.PushFront(value) } -// offerLast 队尾元素入队 -func (s *linkedListDeque) offerLast(value any) { +/* 队尾元素入队 */ +func (s *linkedListDeque) pushLast(value any) { s.data.PushBack(value) } -// pollFirst 队首元素出队 -func (s *linkedListDeque) pollFirst() any { +/* 队首元素出队 */ +func (s *linkedListDeque) popFirst() any { if s.isEmpty() { return nil } @@ -40,8 +41,8 @@ func (s *linkedListDeque) pollFirst() any { return e.Value } -// pollLast 队尾元素出队 -func (s *linkedListDeque) pollLast() any { +/* 队尾元素出队 */ +func (s *linkedListDeque) popLast() any { if s.isEmpty() { return nil } @@ -50,7 +51,7 @@ func (s *linkedListDeque) pollLast() any { return e.Value } -// peekFirst 访问队首元素 +/* 访问队首元素 */ func (s *linkedListDeque) peekFirst() any { if s.isEmpty() { return nil @@ -59,7 +60,7 @@ func (s *linkedListDeque) peekFirst() any { return e.Value } -// peekLast 访问队尾元素 +/* 访问队尾元素 */ func (s *linkedListDeque) peekLast() any { if s.isEmpty() { return nil @@ -68,17 +69,17 @@ func (s *linkedListDeque) peekLast() any { return e.Value } -// size 获取队列的长度 +/* 获取队列的长度 */ func (s *linkedListDeque) size() int { return s.data.Len() } -// isEmpty 判断队列是否为空 +/* 判断队列是否为空 */ func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } -// 获取 List 用于打印 +/* 获取 List 用于打印 */ func (s *linkedListDeque) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_queue.go b/codes/go/chapter_stack_and_queue/linkedlist_queue.go index f5d164a826..7044d8217c 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_queue.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_queue.go @@ -14,20 +14,20 @@ type linkedListQueue struct { data *list.List } -// newLinkedListQueue 初始化链表 +/* 初始化队列 */ func newLinkedListQueue() *linkedListQueue { return &linkedListQueue{ data: list.New(), } } -// offer 入队 -func (s *linkedListQueue) offer(value any) { +/* 入队 */ +func (s *linkedListQueue) push(value any) { s.data.PushBack(value) } -// poll 出队 -func (s *linkedListQueue) poll() any { +/* 出队 */ +func (s *linkedListQueue) pop() any { if s.isEmpty() { return nil } @@ -36,7 +36,7 @@ func (s *linkedListQueue) poll() any { return e.Value } -// peek 访问队首元素 +/* 访问队首元素 */ func (s *linkedListQueue) peek() any { if s.isEmpty() { return nil @@ -45,17 +45,17 @@ func (s *linkedListQueue) peek() any { return e.Value } -// size 获取队列的长度 +/* 获取队列的长度 */ func (s *linkedListQueue) size() int { return s.data.Len() } -// isEmpty 判断队列是否为空 +/* 判断队列是否为空 */ func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } -// 获取 List 用于打印 +/* 获取 List 用于打印 */ func (s *linkedListQueue) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_stack.go b/codes/go/chapter_stack_and_queue/linkedlist_stack.go index 8509c2b83d..53fd711201 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_stack.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_stack.go @@ -14,19 +14,19 @@ type linkedListStack struct { data *list.List } -// newLinkedListStack 初始化链表 +/* 初始化栈 */ func newLinkedListStack() *linkedListStack { return &linkedListStack{ data: list.New(), } } -// push 入栈 +/* 入栈 */ func (s *linkedListStack) push(value int) { s.data.PushBack(value) } -// pop 出栈 +/* 出栈 */ func (s *linkedListStack) pop() any { if s.isEmpty() { return nil @@ -36,7 +36,7 @@ func (s *linkedListStack) pop() any { return e.Value } -// peek 访问栈顶元素 +/* 访问栈顶元素 */ func (s *linkedListStack) peek() any { if s.isEmpty() { return nil @@ -45,17 +45,17 @@ func (s *linkedListStack) peek() any { return e.Value } -// size 获取栈的长度 +/* 获取栈的长度 */ func (s *linkedListStack) size() int { return s.data.Len() } -// isEmpty 判断栈是否为空 +/* 判断栈是否为空 */ func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } -// 获取 List 用于打印 +/* 获取 List 用于打印 */ func (s *linkedListStack) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/queue_test.go b/codes/go/chapter_stack_and_queue/queue_test.go index b9ec79df2b..c25f0f12d2 100644 --- a/codes/go/chapter_stack_and_queue/queue_test.go +++ b/codes/go/chapter_stack_and_queue/queue_test.go @@ -31,9 +31,9 @@ func TestQueue(t *testing.T) { fmt.Println("队首元素 peek =", peek.Value) /* 元素出队 */ - poll := queue.Front() - queue.Remove(poll) - fmt.Print("出队元素 poll = ", poll.Value, ",出队后 queue = ") + pop := queue.Front() + queue.Remove(pop) + fmt.Print("出队元素 pop = ", pop.Value, ",出队后 queue = ") PrintList(queue) /* 获取队列的长度 */ @@ -46,16 +46,20 @@ func TestQueue(t *testing.T) { } func TestArrayQueue(t *testing.T) { + // 初始化队列,使用队列的通用接口 capacity := 10 queue := newArrayQueue(capacity) + if queue.pop() != nil { + t.Errorf("want:%v,got:%v", nil, queue.pop()) + } // 元素入队 - queue.offer(1) - queue.offer(3) - queue.offer(2) - queue.offer(5) - queue.offer(4) + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) fmt.Print("队列 queue = ") PrintSlice(queue.toSlice()) @@ -64,8 +68,8 @@ func TestArrayQueue(t *testing.T) { fmt.Println("队首元素 peek =", peek) // 元素出队 - poll := queue.poll() - fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ") + pop := queue.pop() + fmt.Print("出队元素 pop = ", pop, ", 出队后 queue = ") PrintSlice(queue.toSlice()) // 获取队的长度 @@ -75,6 +79,14 @@ func TestArrayQueue(t *testing.T) { // 判断是否为空 isEmpty := queue.isEmpty() fmt.Println("队是否为空 =", isEmpty) + + /* 测试环形数组 */ + for i := 0; i < 10; i++ { + queue.push(i) + queue.pop() + fmt.Print("第", i, "轮入队 + 出队后 queue =") + PrintSlice(queue.toSlice()) + } } func TestLinkedListQueue(t *testing.T) { @@ -82,11 +94,11 @@ func TestLinkedListQueue(t *testing.T) { queue := newLinkedListQueue() // 元素入队 - queue.offer(1) - queue.offer(3) - queue.offer(2) - queue.offer(5) - queue.offer(4) + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) fmt.Print("队列 queue = ") PrintList(queue.toList()) @@ -95,8 +107,8 @@ func TestLinkedListQueue(t *testing.T) { fmt.Println("队首元素 peek =", peek) // 元素出队 - poll := queue.poll() - fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ") + pop := queue.pop() + fmt.Print("出队元素 pop = ", pop, ", 出队后 queue = ") PrintList(queue.toList()) // 获取队的长度 @@ -111,24 +123,24 @@ func TestLinkedListQueue(t *testing.T) { // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 - stack := newArrayQueue(capacity) + queue := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { - stack.offer(777) + queue.push(777) } for i := 0; i < b.N; i++ { - stack.poll() + queue.pop() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { - stack := newLinkedListQueue() + queue := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { - stack.offer(777) + queue.push(777) } for i := 0; i < b.N; i++ { - stack.poll() + queue.pop() } } diff --git a/codes/go/chapter_stack_and_queue/stack_test.go b/codes/go/chapter_stack_and_queue/stack_test.go index a4cf338b7a..d881c5ec8f 100644 --- a/codes/go/chapter_stack_and_queue/stack_test.go +++ b/codes/go/chapter_stack_and_queue/stack_test.go @@ -22,7 +22,7 @@ func TestStack(t *testing.T) { stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) - fmt.Print("栈 = ") + fmt.Print("栈 stack = ") PrintSlice(stack) /* 访问栈顶元素 */ diff --git a/codes/go/chapter_tree/array_binary_tree.go b/codes/go/chapter_tree/array_binary_tree.go new file mode 100644 index 0000000000..f4db98f68b --- /dev/null +++ b/codes/go/chapter_tree/array_binary_tree.go @@ -0,0 +1,101 @@ +// File: array_binary_tree.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +/* 数组表示下的二叉树类 */ +type arrayBinaryTree struct { + tree []any +} + +/* 构造方法 */ +func newArrayBinaryTree(arr []any) *arrayBinaryTree { + return &arrayBinaryTree{ + tree: arr, + } +} + +/* 列表容量 */ +func (abt *arrayBinaryTree) size() int { + return len(abt.tree) +} + +/* 获取索引为 i 节点的值 */ +func (abt *arrayBinaryTree) val(i int) any { + // 若索引越界,则返回 null ,代表空位 + if i < 0 || i >= abt.size() { + return nil + } + return abt.tree[i] +} + +/* 获取索引为 i 节点的左子节点的索引 */ +func (abt *arrayBinaryTree) left(i int) int { + return 2*i + 1 +} + +/* 获取索引为 i 节点的右子节点的索引 */ +func (abt *arrayBinaryTree) right(i int) int { + return 2*i + 2 +} + +/* 获取索引为 i 节点的父节点的索引 */ +func (abt *arrayBinaryTree) parent(i int) int { + return (i - 1) / 2 +} + +/* 层序遍历 */ +func (abt *arrayBinaryTree) levelOrder() []any { + var res []any + // 直接遍历数组 + for i := 0; i < abt.size(); i++ { + if abt.val(i) != nil { + res = append(res, abt.val(i)) + } + } + return res +} + +/* 深度优先遍历 */ +func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { + // 若为空位,则返回 + if abt.val(i) == nil { + return + } + // 前序遍历 + if order == "pre" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.left(i), order, res) + // 中序遍历 + if order == "in" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.right(i), order, res) + // 后序遍历 + if order == "post" { + *res = append(*res, abt.val(i)) + } +} + +/* 前序遍历 */ +func (abt *arrayBinaryTree) preOrder() []any { + var res []any + abt.dfs(0, "pre", &res) + return res +} + +/* 中序遍历 */ +func (abt *arrayBinaryTree) inOrder() []any { + var res []any + abt.dfs(0, "in", &res) + return res +} + +/* 后序遍历 */ +func (abt *arrayBinaryTree) postOrder() []any { + var res []any + abt.dfs(0, "post", &res) + return res +} diff --git a/codes/go/chapter_tree/array_binary_tree_test.go b/codes/go/chapter_tree/array_binary_tree_test.go new file mode 100644 index 0000000000..6ee45a6ab1 --- /dev/null +++ b/codes/go/chapter_tree/array_binary_tree_test.go @@ -0,0 +1,47 @@ +// File: array_binary_tree_test.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestArrayBinaryTree(t *testing.T) { + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + root := SliceToTree(arr) + fmt.Println("\n初始化二叉树") + fmt.Println("二叉树的数组表示:") + fmt.Println(arr) + fmt.Println("二叉树的链表表示:") + PrintTree(root) + + // 数组表示下的二叉树类 + abt := newArrayBinaryTree(arr) + + // 访问节点 + i := 1 + l := abt.left(i) + r := abt.right(i) + p := abt.parent(i) + fmt.Println("\n当前节点的索引为", i, ",值为", abt.val(i)) + fmt.Println("其左子节点的索引为", l, ",值为", abt.val(l)) + fmt.Println("其右子节点的索引为", r, ",值为", abt.val(r)) + fmt.Println("其父节点的索引为", p, ",值为", abt.val(p)) + + // 遍历树 + res := abt.levelOrder() + fmt.Println("\n层序遍历为:", res) + res = abt.preOrder() + fmt.Println("前序遍历为:", res) + res = abt.inOrder() + fmt.Println("中序遍历为:", res) + res = abt.postOrder() + fmt.Println("后序遍历为:", res) +} diff --git a/codes/go/chapter_tree/avl_tree.go b/codes/go/chapter_tree/avl_tree.go index e83b2882bc..b54f6ba543 100644 --- a/codes/go/chapter_tree/avl_tree.go +++ b/codes/go/chapter_tree/avl_tree.go @@ -6,30 +6,30 @@ package chapter_tree import . "github.com/krahets/hello-algo/pkg" -/* AVL Tree*/ -type avlTree struct { +/* AVL 树 */ +type aVLTree struct { // 根节点 root *TreeNode } -func newAVLTree() *avlTree { - return &avlTree{root: nil} +func newAVLTree() *aVLTree { + return &aVLTree{root: nil} } -/* 获取结点高度 */ -func height(node *TreeNode) int { - // 空结点高度为 -1 ,叶结点高度为 0 +/* 获取节点高度 */ +func (t *aVLTree) height(node *TreeNode) int { + // 空节点高度为 -1 ,叶节点高度为 0 if node != nil { return node.Height } return -1 } -/* 更新结点高度 */ -func updateHeight(node *TreeNode) { - lh := height(node.Left) - rh := height(node.Right) - // 结点高度等于最高子树高度 + 1 +/* 更新节点高度 */ +func (t *aVLTree) updateHeight(node *TreeNode) { + lh := t.height(node.Left) + rh := t.height(node.Right) + // 节点高度等于最高子树高度 + 1 if lh > rh { node.Height = lh + 1 } else { @@ -38,174 +38,163 @@ func updateHeight(node *TreeNode) { } /* 获取平衡因子 */ -func balanceFactor(node *TreeNode) int { - // 空结点平衡因子为 0 +func (t *aVLTree) balanceFactor(node *TreeNode) int { + // 空节点平衡因子为 0 if node == nil { return 0 } - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.Left) - height(node.Right) + // 节点平衡因子 = 左子树高度 - 右子树高度 + return t.height(node.Left) - t.height(node.Right) } /* 右旋操作 */ -func rightRotate(node *TreeNode) *TreeNode { +func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { child := node.Left grandChild := child.Right // 以 child 为原点,将 node 向右旋转 child.Right = node node.Left = grandChild - // 更新结点高度 - updateHeight(node) - updateHeight(child) + // 更新节点高度 + t.updateHeight(node) + t.updateHeight(child) // 返回旋转后子树的根节点 return child } /* 左旋操作 */ -func leftRotate(node *TreeNode) *TreeNode { +func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { child := node.Right grandChild := child.Left // 以 child 为原点,将 node 向左旋转 child.Left = node node.Right = grandChild - // 更新结点高度 - updateHeight(node) - updateHeight(child) + // 更新节点高度 + t.updateHeight(node) + t.updateHeight(child) // 返回旋转后子树的根节点 return child } /* 执行旋转操作,使该子树重新恢复平衡 */ -func rotate(node *TreeNode) *TreeNode { - // 获取结点 node 的平衡因子 - // Go 推荐短变量,这里 bf 指代 balanceFactor - bf := balanceFactor(node) +func (t *aVLTree) rotate(node *TreeNode) *TreeNode { + // 获取节点 node 的平衡因子 + // Go 推荐短变量,这里 bf 指代 t.balanceFactor + bf := t.balanceFactor(node) // 左偏树 if bf > 1 { - if balanceFactor(node.Left) >= 0 { + if t.balanceFactor(node.Left) >= 0 { // 右旋 - return rightRotate(node) + return t.rightRotate(node) } else { // 先左旋后右旋 - node.Left = leftRotate(node.Left) - return rightRotate(node) + node.Left = t.leftRotate(node.Left) + return t.rightRotate(node) } } // 右偏树 if bf < -1 { - if balanceFactor(node.Right) <= 0 { + if t.balanceFactor(node.Right) <= 0 { // 左旋 - return leftRotate(node) + return t.leftRotate(node) } else { // 先右旋后左旋 - node.Right = rightRotate(node.Right) - return leftRotate(node) + node.Right = t.rightRotate(node.Right) + return t.leftRotate(node) } } - // 平衡树,无需旋转,直接返回 + // 平衡树,无须旋转,直接返回 return node } -/* 插入结点 */ -func (t *avlTree) insert(val int) *TreeNode { - t.root = insertHelper(t.root, val) - return t.root +/* 插入节点 */ +func (t *aVLTree) insert(val int) { + t.root = t.insertHelper(t.root, val) } -/* 递归插入结点(辅助函数) */ -func insertHelper(node *TreeNode, val int) *TreeNode { +/* 递归插入节点(辅助函数) */ +func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { if node == nil { return NewTreeNode(val) } - /* 1. 查找插入位置,并插入结点 */ - if val < node.Val { - node.Left = insertHelper(node.Left, val) - } else if val > node.Val { - node.Right = insertHelper(node.Right, val) + /* 1. 查找插入位置并插入节点 */ + if val < node.Val.(int) { + node.Left = t.insertHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.insertHelper(node.Right, val) } else { - // 重复结点不插入,直接返回 + // 重复节点不插入,直接返回 return node } - // 更新结点高度 - updateHeight(node) + // 更新节点高度 + t.updateHeight(node) /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node) + node = t.rotate(node) // 返回子树的根节点 return node } -/* 删除结点 */ -func (t *avlTree) remove(val int) *TreeNode { - root := removeHelper(t.root, val) - return root +/* 删除节点 */ +func (t *aVLTree) remove(val int) { + t.root = t.removeHelper(t.root, val) } -/* 递归删除结点(辅助函数) */ -func removeHelper(node *TreeNode, val int) *TreeNode { +/* 递归删除节点(辅助函数) */ +func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { if node == nil { return nil } - /* 1. 查找结点,并删除之 */ - if val < node.Val { - node.Left = removeHelper(node.Left, val) - } else if val > node.Val { - node.Right = removeHelper(node.Right, val) + /* 1. 查找节点并删除 */ + if val < node.Val.(int) { + node.Left = t.removeHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.removeHelper(node.Right, val) } else { if node.Left == nil || node.Right == nil { child := node.Left if node.Right != nil { child = node.Right } - // 子结点数量 = 0 ,直接删除 node 并返回 if child == nil { + // 子节点数量 = 0 ,直接删除 node 并返回 return nil } else { - // 子结点数量 = 1 ,直接删除 node + // 子节点数量 = 1 ,直接删除 node node = child } } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp := getInOrderNext(node.Right) - node.Right = removeHelper(node.Right, temp.Val) + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + temp := node.Right + for temp.Left != nil { + temp = temp.Left + } + node.Right = t.removeHelper(node.Right, temp.Val.(int)) node.Val = temp.Val } } - // 更新结点高度 - updateHeight(node) + // 更新节点高度 + t.updateHeight(node) /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node) + node = t.rotate(node) // 返回子树的根节点 return node } -/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ -func getInOrderNext(node *TreeNode) *TreeNode { - if node == nil { - return node - } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - for node.Left != nil { - node = node.Left - } - return node -} - -/* 查找结点 */ -func (t *avlTree) search(val int) *TreeNode { +/* 查找节点 */ +func (t *aVLTree) search(val int) *TreeNode { cur := t.root - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 for cur != nil { - // 目标结点在 root 的右子树中 - if cur.Val < val { + if cur.Val.(int) < val { + // 目标节点在 cur 的右子树中 cur = cur.Right - } else if cur.Val > val { - // 目标结点在 root 的左子树中 + } else if cur.Val.(int) > val { + // 目标节点在 cur 的左子树中 cur = cur.Left } else { - // 找到目标结点,跳出循环 + // 找到目标节点,跳出循环 break } } - // 返回目标结点 + // 返回目标节点 return cur } diff --git a/codes/go/chapter_tree/avl_tree_test.go b/codes/go/chapter_tree/avl_tree_test.go index c4fc6b719d..8dd03776a6 100644 --- a/codes/go/chapter_tree/avl_tree_test.go +++ b/codes/go/chapter_tree/avl_tree_test.go @@ -14,8 +14,8 @@ import ( func TestAVLTree(t *testing.T) { /* 初始化空 AVL 树 */ tree := newAVLTree() - /* 插入结点 */ - // 请关注插入结点后,AVL 树是如何保持平衡的 + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(tree, 1) testInsert(tree, 2) testInsert(tree, 3) @@ -27,28 +27,28 @@ func TestAVLTree(t *testing.T) { testInsert(tree, 10) testInsert(tree, 6) - /* 插入重复结点 */ + /* 插入重复节点 */ testInsert(tree, 7) - /* 删除结点 */ - // 请关注删除结点后,AVL 树是如何保持平衡的 - testRemove(tree, 8) // 删除度为 0 的结点 - testRemove(tree, 5) // 删除度为 1 的结点 - testRemove(tree, 4) // 删除度为 2 的结点 + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(tree, 8) // 删除度为 0 的节点 + testRemove(tree, 5) // 删除度为 1 的节点 + testRemove(tree, 4) // 删除度为 2 的节点 - /* 查询结点 */ + /* 查询节点 */ node := tree.search(7) - fmt.Printf("\n查找到的结点对象为 %#v ,结点值 = %d \n", node, node.Val) + fmt.Printf("\n查找到的节点对象为 %#v ,节点值 = %d \n", node, node.Val) } -func testInsert(tree *avlTree, val int) { +func testInsert(tree *aVLTree, val int) { tree.insert(val) - fmt.Printf("\n插入结点 %d 后,AVL 树为 \n", val) + fmt.Printf("\n插入节点 %d 后,AVL 树为 \n", val) PrintTree(tree.root) } -func testRemove(tree *avlTree, val int) { +func testRemove(tree *aVLTree, val int) { tree.remove(val) - fmt.Printf("\n删除结点 %d 后,AVL 树为 \n", val) + fmt.Printf("\n删除节点 %d 后,AVL 树为 \n", val) PrintTree(tree.root) } diff --git a/codes/go/chapter_tree/binary_search_tree.go b/codes/go/chapter_tree/binary_search_tree.go index 61ed400d5f..60471ca203 100644 --- a/codes/go/chapter_tree/binary_search_tree.go +++ b/codes/go/chapter_tree/binary_search_tree.go @@ -5,8 +5,6 @@ package chapter_tree import ( - "sort" - . "github.com/krahets/hello-algo/pkg" ) @@ -14,153 +12,131 @@ type binarySearchTree struct { root *TreeNode } -func newBinarySearchTree(nums []int) *binarySearchTree { - // sorting array - sort.Ints(nums) - root := buildBinarySearchTree(nums, 0, len(nums)-1) - return &binarySearchTree{ - root: root, - } +func newBinarySearchTree() *binarySearchTree { + bst := &binarySearchTree{} + // 初始化空树 + bst.root = nil + return bst } -/* 获取根结点 */ +/* 获取根节点 */ func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } -/* 获取中序遍历的下一个结点 */ -func (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode { - if node == nil { - return node - } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - for node.Left != nil { - node = node.Left - } - return node -} - -/* 查找结点 */ +/* 查找节点 */ func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 for node != nil { - if node.Val < num { - // 目标结点在 root 的右子树中 + if node.Val.(int) < num { + // 目标节点在 cur 的右子树中 node = node.Right - } else if node.Val > num { - // 目标结点在 root 的左子树中 + } else if node.Val.(int) > num { + // 目标节点在 cur 的左子树中 node = node.Left } else { - // 找到目标结点,跳出循环 + // 找到目标节点,跳出循环 break } } - // 返回目标结点 + // 返回目标节点 return node } -/* 插入结点 */ -func (bst *binarySearchTree) insert(num int) *TreeNode { +/* 插入节点 */ +func (bst *binarySearchTree) insert(num int) { cur := bst.root - // 若树为空,直接提前返回 + // 若树为空,则初始化根节点 if cur == nil { - return nil + bst.root = NewTreeNode(num) + return } - // 待插入结点之前的结点位置 - var prev *TreeNode = nil - // 循环查找,越过叶结点后跳出 + // 待插入节点之前的节点位置 + var pre *TreeNode = nil + // 循环查找,越过叶节点后跳出 for cur != nil { if cur.Val == num { - return nil + return } - prev = cur - if cur.Val < num { + pre = cur + if cur.Val.(int) < num { cur = cur.Right } else { cur = cur.Left } } - // 插入结点 + // 插入节点 node := NewTreeNode(num) - if prev.Val < num { - prev.Right = node + if pre.Val.(int) < num { + pre.Right = node } else { - prev.Left = node + pre.Left = node } - return cur } -/* 删除结点 */ -func (bst *binarySearchTree) remove(num int) *TreeNode { +/* 删除节点 */ +func (bst *binarySearchTree) remove(num int) { cur := bst.root // 若树为空,直接提前返回 if cur == nil { - return nil + return } - // 待删除结点之前的结点位置 - var prev *TreeNode = nil - // 循环查找,越过叶结点后跳出 + // 待删除节点之前的节点位置 + var pre *TreeNode = nil + // 循环查找,越过叶节点后跳出 for cur != nil { if cur.Val == num { break } - prev = cur - if cur.Val < num { - // 待删除结点在右子树中 + pre = cur + if cur.Val.(int) < num { + // 待删除节点在右子树中 cur = cur.Right } else { - // 待删除结点在左子树中 + // 待删除节点在左子树中 cur = cur.Left } } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if cur == nil { - return nil + return } - // 子结点数为 0 或 1 + // 子节点数为 0 或 1 if cur.Left == nil || cur.Right == nil { var child *TreeNode = nil - // 取出待删除结点的子结点 + // 取出待删除节点的子节点 if cur.Left != nil { child = cur.Left } else { child = cur.Right } - // 将子结点替换为待删除结点 - if prev.Left == cur { - prev.Left = child + // 删除节点 cur + if cur != bst.root { + if pre.Left == cur { + pre.Left = child + } else { + pre.Right = child + } } else { - prev.Right = child + // 若删除节点为根节点,则重新指定根节点 + bst.root = child } - // 子结点数为 2 + // 子节点数为 2 } else { - // 获取中序遍历中待删除结点 cur 的下一个结点 - next := bst.getInOrderNext(cur) - temp := next.Val - // 递归删除结点 next - bst.remove(next.Val) - // 将 next 的值复制给 cur - cur.Val = temp - } - return cur -} - -// buildBinarySearchTree Build a binary search tree from array. -func buildBinarySearchTree(nums []int, left, right int) *TreeNode { - if left > right { - return nil + // 获取中序遍历中待删除节点 cur 的下一个节点 + tmp := cur.Right + for tmp.Left != nil { + tmp = tmp.Left + } + // 递归删除节点 tmp + bst.remove(tmp.Val.(int)) + // 用 tmp 覆盖 cur + cur.Val = tmp.Val } - // 将数组中间结点作为根结点 - middle := left + (right-left)>>1 - root := NewTreeNode(nums[middle]) - // 递归构建左子树和右子树 - root.Left = buildBinarySearchTree(nums, left, middle-1) - root.Right = buildBinarySearchTree(nums, middle+1, right) - return root } -// print binary search tree +/* 打印二叉搜索树 */ func (bst *binarySearchTree) print() { PrintTree(bst.root) } diff --git a/codes/go/chapter_tree/binary_search_tree_test.go b/codes/go/chapter_tree/binary_search_tree_test.go index 2a864d1386..65b72840f2 100644 --- a/codes/go/chapter_tree/binary_search_tree_test.go +++ b/codes/go/chapter_tree/binary_search_tree_test.go @@ -10,32 +10,36 @@ import ( ) func TestBinarySearchTree(t *testing.T) { - nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - bst := newBinarySearchTree(nums) + bst := newBinarySearchTree() + nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + for _, num := range nums { + bst.insert(num) + } fmt.Println("\n初始化的二叉树为:") bst.print() - // 获取根结点 + // 获取根节点 node := bst.getRoot() - fmt.Println("\n二叉树的根结点为:", node.Val) + fmt.Println("\n二叉树的根节点为:", node.Val) - // 查找结点 + // 查找节点 node = bst.search(7) - fmt.Println("查找到的结点对象为", node, ",结点值 =", node.Val) + fmt.Println("查找到的节点对象为", node, ",节点值 =", node.Val) - // 插入结点 - node = bst.insert(16) - fmt.Println("\n插入结点后 16 的二叉树为:") + // 插入节点 + bst.insert(16) + fmt.Println("\n插入节点后 16 的二叉树为:") bst.print() - // 删除结点 + // 删除节点 bst.remove(1) - fmt.Println("\n删除结点 1 后的二叉树为:") + fmt.Println("\n删除节点 1 后的二叉树为:") bst.print() bst.remove(2) - fmt.Println("\n删除结点 2 后的二叉树为:") + fmt.Println("\n删除节点 2 后的二叉树为:") bst.print() bst.remove(4) - fmt.Println("\n删除结点 4 后的二叉树为:") + fmt.Println("\n删除节点 4 后的二叉树为:") bst.print() } diff --git a/codes/go/chapter_tree/binary_tree_bfs.go b/codes/go/chapter_tree/binary_tree_bfs.go index d77bc97b14..cf554d33e4 100644 --- a/codes/go/chapter_tree/binary_tree_bfs.go +++ b/codes/go/chapter_tree/binary_tree_bfs.go @@ -11,23 +11,23 @@ import ( ) /* 层序遍历 */ -func levelOrder(root *TreeNode) []int { - // 初始化队列,加入根结点 +func levelOrder(root *TreeNode) []any { + // 初始化队列,加入根节点 queue := list.New() queue.PushBack(root) // 初始化一个切片,用于保存遍历序列 - nums := make([]int, 0) + nums := make([]any, 0) for queue.Len() > 0 { - // poll + // 队列出队 node := queue.Remove(queue.Front()).(*TreeNode) - // 保存结点 + // 保存节点值 nums = append(nums, node.Val) if node.Left != nil { - // 左子结点入队 + // 左子节点入队 queue.PushBack(node.Left) } if node.Right != nil { - // 右子结点入队 + // 右子节点入队 queue.PushBack(node.Right) } } diff --git a/codes/go/chapter_tree/binary_tree_bfs_test.go b/codes/go/chapter_tree/binary_tree_bfs_test.go index 7b1bbf1f5e..8c3d17cc7d 100644 --- a/codes/go/chapter_tree/binary_tree_bfs_test.go +++ b/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -14,11 +14,11 @@ import ( func TestLevelOrder(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7}) + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树: ") PrintTree(root) // 层序遍历 nums := levelOrder(root) - fmt.Println("\n层序遍历的结点打印序列 =", nums) + fmt.Println("\n层序遍历的节点打印序列 =", nums) } diff --git a/codes/go/chapter_tree/binary_tree_dfs.go b/codes/go/chapter_tree/binary_tree_dfs.go index 1a857ebaeb..25211ce960 100644 --- a/codes/go/chapter_tree/binary_tree_dfs.go +++ b/codes/go/chapter_tree/binary_tree_dfs.go @@ -8,14 +8,14 @@ import ( . "github.com/krahets/hello-algo/pkg" ) -var nums []int +var nums []any /* 前序遍历 */ func preOrder(node *TreeNode) { if node == nil { return } - // 访问优先级:根结点 -> 左子树 -> 右子树 + // 访问优先级:根节点 -> 左子树 -> 右子树 nums = append(nums, node.Val) preOrder(node.Left) preOrder(node.Right) @@ -26,7 +26,7 @@ func inOrder(node *TreeNode) { if node == nil { return } - // 访问优先级:左子树 -> 根结点 -> 右子树 + // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(node.Left) nums = append(nums, node.Val) inOrder(node.Right) @@ -37,7 +37,7 @@ func postOrder(node *TreeNode) { if node == nil { return } - // 访问优先级:左子树 -> 右子树 -> 根结点 + // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(node.Left) postOrder(node.Right) nums = append(nums, node.Val) diff --git a/codes/go/chapter_tree/binary_tree_dfs_test.go b/codes/go/chapter_tree/binary_tree_dfs_test.go index 9e0dfe22f3..e8404c9406 100644 --- a/codes/go/chapter_tree/binary_tree_dfs_test.go +++ b/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -14,22 +14,22 @@ import ( func TestPreInPostOrderTraversal(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7}) + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树: ") PrintTree(root) // 前序遍历 nums = nil preOrder(root) - fmt.Println("\n前序遍历的结点打印序列 =", nums) + fmt.Println("\n前序遍历的节点打印序列 =", nums) // 中序遍历 nums = nil inOrder(root) - fmt.Println("\n中序遍历的结点打印序列 =", nums) + fmt.Println("\n中序遍历的节点打印序列 =", nums) // 后序遍历 nums = nil postOrder(root) - fmt.Println("\n后序遍历的结点打印序列 =", nums) + fmt.Println("\n后序遍历的节点打印序列 =", nums) } diff --git a/codes/go/chapter_tree/binary_tree_test.go b/codes/go/chapter_tree/binary_tree_test.go index f91e816fb9..7f29755594 100644 --- a/codes/go/chapter_tree/binary_tree_test.go +++ b/codes/go/chapter_tree/binary_tree_test.go @@ -13,13 +13,13 @@ import ( func TestBinaryTree(t *testing.T) { /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) - // 构建引用指向(即指针) + // 构建节点之间的引用(指针) n1.Left = n2 n1.Right = n3 n2.Left = n4 @@ -27,15 +27,15 @@ func TestBinaryTree(t *testing.T) { fmt.Println("初始化二叉树") PrintTree(n1) - /* 插入与删除结点 */ - // 插入结点 + /* 插入与删除节点 */ + // 插入节点 p := NewTreeNode(0) n1.Left = p p.Left = n2 - fmt.Println("插入结点 P 后") + fmt.Println("插入节点 P 后") PrintTree(n1) - // 删除结点 + // 删除节点 n1.Left = n2 - fmt.Println("删除结点 P 后") + fmt.Println("删除节点 P 后") PrintTree(n1) } diff --git a/codes/go/pkg/list_node.go b/codes/go/pkg/list_node.go index f1c152de1a..877f8563d3 100644 --- a/codes/go/pkg/list_node.go +++ b/codes/go/pkg/list_node.go @@ -4,13 +4,13 @@ package pkg -// ListNode Definition for a singly-linked list node +// ListNode 链表节点 type ListNode struct { Next *ListNode Val int } -// NewListNode Generate a list node with an val +// NewListNode 链表节点构造函数 func NewListNode(v int) *ListNode { return &ListNode{ Next: nil, @@ -18,7 +18,7 @@ func NewListNode(v int) *ListNode { } } -// ArrayToLinkedList Generate a linked list with an array +// ArrayToLinkedList 将数组反序列化为链表 func ArrayToLinkedList(arr []int) *ListNode { // dummy header of linked list dummy := NewListNode(0) @@ -29,11 +29,3 @@ func ArrayToLinkedList(arr []int) *ListNode { } return dummy.Next } - -// GetListNode Get a list node with specific value from a linked list -func GetListNode(node *ListNode, val int) *ListNode { - for node != nil && node.Val != val { - node = node.Next - } - return node -} diff --git a/codes/go/pkg/list_node_test.go b/codes/go/pkg/list_node_test.go index 31b99618f8..e61d8d5bff 100644 --- a/codes/go/pkg/list_node_test.go +++ b/codes/go/pkg/list_node_test.go @@ -5,7 +5,6 @@ package pkg import ( - "fmt" "testing" ) @@ -14,6 +13,4 @@ func TestListNode(t *testing.T) { head := ArrayToLinkedList(arr) PrintLinkedList(head) - node := GetListNode(head, 5) - fmt.Println("Find node: ", node.Val) } diff --git a/codes/go/pkg/print_utils.go b/codes/go/pkg/print_utils.go index 575ab6b944..ab952150ef 100644 --- a/codes/go/pkg/print_utils.go +++ b/codes/go/pkg/print_utils.go @@ -1,6 +1,6 @@ // File: print_utils.go // Created Time: 2022-12-03 -// Author: Reanon (793584285@qq.com), Krahets (krahets@163.com), msk397 (machangxinq@gmail.com) +// Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) package pkg @@ -11,14 +11,18 @@ import ( "strings" ) -// PrintSlice Print a slice +// PrintSlice 打印切片 func PrintSlice[T any](nums []T) { fmt.Printf("%v", nums) fmt.Println() } -// PrintList Print a list +// PrintList 打印列表 func PrintList(list *list.List) { + if list.Len() == 0 { + fmt.Print("[]\n") + return + } e := list.Front() // 强转为 string, 会影响效率 fmt.Print("[") @@ -29,7 +33,23 @@ func PrintList(list *list.List) { fmt.Print(e.Value, "]\n") } -// PrintLinkedList Print a linked list +// PrintMap 打印哈希表 +func PrintMap[K comparable, V any](m map[K]V) { + for key, value := range m { + fmt.Println(key, "->", value) + } +} + +// PrintHeap 打印堆 +func PrintHeap(h []any) { + fmt.Printf("堆的数组表示:") + fmt.Printf("%v", h) + fmt.Printf("\n堆的树状表示:\n") + root := SliceToTree(h) + PrintTree(root) +} + +// PrintLinkedList 打印链表 func PrintLinkedList(node *ListNode) { if node == nil { return @@ -43,15 +63,15 @@ func PrintLinkedList(node *ListNode) { fmt.Println(builder.String()) } -// PrintTree Print a binary tree +// PrintTree 打印二叉树 func PrintTree(root *TreeNode) { printTreeHelper(root, nil, false) } -// printTreeHelper Help to print a binary tree, hide more details +// printTreeHelper 打印二叉树 // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ -func printTreeHelper(root *TreeNode, prev *trunk, isLeft bool) { +func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { if root == nil { return } @@ -60,7 +80,7 @@ func printTreeHelper(root *TreeNode, prev *trunk, isLeft bool) { printTreeHelper(root.Right, trunk, true) if prev == nil { trunk.str = "———" - } else if isLeft { + } else if isRight { trunk.str = "/———" prevStr = " |" } else { @@ -76,7 +96,6 @@ func printTreeHelper(root *TreeNode, prev *trunk, isLeft bool) { printTreeHelper(root.Left, trunk, false) } -// trunk Help to print tree structure type trunk struct { prev *trunk str string @@ -97,10 +116,3 @@ func showTrunk(t *trunk) { showTrunk(t.prev) fmt.Print(t.str) } - -// PrintMap Print a hash map -func PrintMap[K comparable, V any](m map[K]V) { - for key, value := range m { - fmt.Println(key, "->", value) - } -} diff --git a/codes/go/pkg/tree_node.go b/codes/go/pkg/tree_node.go index 6baed50eb9..80c1eab358 100644 --- a/codes/go/pkg/tree_node.go +++ b/codes/go/pkg/tree_node.go @@ -4,18 +4,16 @@ package pkg -import ( - "container/list" -) - +// TreeNode 二叉树节点 type TreeNode struct { - Val int // 结点值 - Height int // 结点高度 - Left *TreeNode // 左子结点引用 - Right *TreeNode // 右子结点引用 + Val any // 节点值 + Height int // 节点高度 + Left *TreeNode // 左子节点引用 + Right *TreeNode // 右子节点引用 } -func NewTreeNode(v int) *TreeNode { +// NewTreeNode 二叉树节点构造函数 +func NewTreeNode(v any) *TreeNode { return &TreeNode{ Val: v, Height: 0, @@ -24,56 +22,57 @@ func NewTreeNode(v int) *TreeNode { } } -// ArrToTree Generate a binary tree given an array -func ArrToTree(arr []any) *TreeNode { - if len(arr) <= 0 { +// 序列化编码规则请参考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二叉树的数组表示: +// [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] +// 二叉树的链表表示: +// +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// +// ——— 1 +// +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +// SliceToTreeDFS 将列表反序列化为二叉树:递归 +func SliceToTreeDFS(arr []any, i int) *TreeNode { + if i < 0 || i >= len(arr) || arr[i] == nil { return nil } - // TreeNode only accept integer value for now. - root := NewTreeNode(arr[0].(int)) - // Let container.list as queue - queue := list.New() - queue.PushBack(root) - i := 0 - for queue.Len() > 0 { - // poll - node := queue.Remove(queue.Front()).(*TreeNode) - i++ - if i < len(arr) { - if arr[i] != nil { - node.Left = NewTreeNode(arr[i].(int)) - queue.PushBack(node.Left) - } - } - i++ - if i < len(arr) { - if arr[i] != nil { - node.Right = NewTreeNode(arr[i].(int)) - queue.PushBack(node.Right) - } - } - } + root := NewTreeNode(arr[i]) + root.Left = SliceToTreeDFS(arr, 2*i+1) + root.Right = SliceToTreeDFS(arr, 2*i+2) return root } -// TreeToArray Serialize a binary tree to a list -func TreeToArray(root *TreeNode) []any { +// SliceToTree 将切片反序列化为二叉树 +func SliceToTree(arr []any) *TreeNode { + return SliceToTreeDFS(arr, 0) +} + +// TreeToSliceDFS 将二叉树序列化为切片:递归 +func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { if root == nil { - return []any{} + return } - arr := make([]any, 0) - queue := list.New() - queue.PushBack(root) - for queue.Len() > 0 { - node := queue.Remove(queue.Front()).(*TreeNode) - if node != nil { - arr = append(arr, node.Val) - queue.PushBack(node.Left) - queue.PushBack(node.Right) - } else { - // node don't exist. - arr = append(arr, nil) - } + for i >= len(*res) { + *res = append(*res, nil) } - return arr + (*res)[i] = root.Val + TreeToSliceDFS(root.Left, 2*i+1, res) + TreeToSliceDFS(root.Right, 2*i+2, res) +} + +// TreeToSlice 将二叉树序列化为切片 +func TreeToSlice(root *TreeNode) []any { + var res []any + TreeToSliceDFS(root, 0, &res) + return res } diff --git a/codes/go/pkg/tree_node_test.go b/codes/go/pkg/tree_node_test.go index ec7831bf7c..043aab099a 100644 --- a/codes/go/pkg/tree_node_test.go +++ b/codes/go/pkg/tree_node_test.go @@ -11,11 +11,11 @@ import ( func TestTreeNode(t *testing.T) { arr := []any{1, 2, 3, nil, 5, 6, nil} - node := ArrToTree(arr) + node := SliceToTree(arr) // print tree PrintTree(node) // tree to arr - fmt.Println(TreeToArray(node)) + fmt.Println(TreeToSlice(node)) } diff --git a/codes/go/pkg/vertex.go b/codes/go/pkg/vertex.go new file mode 100644 index 0000000000..19190d7a31 --- /dev/null +++ b/codes/go/pkg/vertex.go @@ -0,0 +1,55 @@ +// File: vertex.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package pkg + +// Vertex 顶点类 +type Vertex struct { + Val int +} + +// NewVertex 顶点构造函数 +func NewVertex(val int) Vertex { + return Vertex{ + Val: val, + } +} + +// ValsToVets 将值列表反序列化为顶点列表 +func ValsToVets(vals []int) []Vertex { + vets := make([]Vertex, len(vals)) + for i := 0; i < len(vals); i++ { + vets[i] = NewVertex(vals[i]) + } + return vets +} + +// VetsToVals 将顶点列表序列化为值列表 +func VetsToVals(vets []Vertex) []int { + vals := make([]int, len(vets)) + for i := range vets { + vals[i] = vets[i].Val + } + return vals +} + +// DeleteSliceElms 删除切片指定元素 +func DeleteSliceElms[T any](a []T, elms ...T) []T { + if len(a) == 0 || len(elms) == 0 { + return a + } + // 先将元素转为 set + m := make(map[any]struct{}) + for _, v := range elms { + m[v] = struct{}{} + } + // 过滤掉指定元素 + res := make([]T, 0, len(a)) + for _, v := range a { + if _, ok := m[v]; !ok { + res = append(res, v) + } + } + return res +} diff --git a/codes/java/.gitignore b/codes/java/.gitignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/codes/java/.gitignore @@ -0,0 +1 @@ +build diff --git a/codes/java/chapter_array_and_linkedlist/array.java b/codes/java/chapter_array_and_linkedlist/array.java index d3cb176c6a..20ba8ebf35 100644 --- a/codes/java/chapter_array_and_linkedlist/array.java +++ b/codes/java/chapter_array_and_linkedlist/array.java @@ -1,7 +1,7 @@ /** * File: array.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_array_and_linkedlist; @@ -10,11 +10,10 @@ import java.util.concurrent.ThreadLocalRandom; public class array { - /* 随机返回一个数组元素 */ + /* 随机访问元素 */ static int randomAccess(int[] nums) { // 在区间 [0, nums.length) 中随机抽取一个数字 - int randomIndex = ThreadLocalRandom.current(). - nextInt(0, nums.length); + int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); // 获取并返回随机元素 int randomNum = nums[randomIndex]; return randomNum; @@ -38,11 +37,11 @@ static void insert(int[] nums, int num, int index) { for (int i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num; } - /* 删除索引 index 处元素 */ + /* 删除索引 index 处的元素 */ static void remove(int[] nums, int index) { // 把索引 index 之后的所有元素向前移动一位 for (int i = index; i < nums.length - 1; i++) { @@ -55,11 +54,11 @@ static void traverse(int[] nums) { int count = 0; // 通过索引遍历数组 for (int i = 0; i < nums.length; i++) { - count++; + count += nums[i]; } - // 直接遍历数组 + // 直接遍历数组元素 for (int num : nums) { - count++; + count += num; } } @@ -79,15 +78,15 @@ public static void main(String[] args) { System.out.println("数组 arr = " + Arrays.toString(arr)); int[] nums = { 1, 3, 2, 5, 4 }; System.out.println("数组 nums = " + Arrays.toString(nums)); - + /* 随机访问 */ int randomNum = randomAccess(nums); System.out.println("在 nums 中获取随机元素 " + randomNum); - + /* 长度扩展 */ nums = extend(nums, 3); System.out.println("将数组长度扩展至 8 ,得到 nums = " + Arrays.toString(nums)); - + /* 插入元素 */ insert(nums, 6, 3); System.out.println("在索引 3 处插入数字 6 ,得到 nums = " + Arrays.toString(nums)); @@ -95,10 +94,10 @@ public static void main(String[] args) { /* 删除元素 */ remove(nums, 2); System.out.println("删除索引 2 处的元素,得到 nums = " + Arrays.toString(nums)); - + /* 遍历数组 */ traverse(nums); - + /* 查找元素 */ int index = find(nums, 3); System.out.println("在 nums 中查找元素 3 ,得到索引 = " + index); diff --git a/codes/java/chapter_array_and_linkedlist/linked_list.java b/codes/java/chapter_array_and_linkedlist/linked_list.java index 0db8f6ae7b..bc75381717 100644 --- a/codes/java/chapter_array_and_linkedlist/linked_list.java +++ b/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -1,22 +1,22 @@ /** * File: linked_list.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_array_and_linkedlist; -import include.*; +import utils.*; public class linked_list { - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ static void insert(ListNode n0, ListNode P) { ListNode n1 = n0.next; - n0.next = P; P.next = n1; + n0.next = P; } - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ static void remove(ListNode n0) { if (n0.next == null) return; @@ -26,7 +26,7 @@ static void remove(ListNode n0) { n0.next = n1; } - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ static ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { if (head == null) @@ -36,7 +36,7 @@ static ListNode access(ListNode head, int index) { return head; } - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ static int find(ListNode head, int target) { int index = 0; while (head != null) { @@ -51,13 +51,13 @@ static int find(ListNode head, int target) { /* Driver Code */ public static void main(String[] args) { /* 初始化链表 */ - // 初始化各个结点 + // 初始化各个节点 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(5); ListNode n4 = new ListNode(4); - // 构建引用指向 + // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; @@ -65,22 +65,22 @@ public static void main(String[] args) { System.out.println("初始化的链表为"); PrintUtil.printLinkedList(n0); - /* 插入结点 */ + /* 插入节点 */ insert(n0, new ListNode(0)); - System.out.println("插入结点后的链表为"); + System.out.println("插入节点后的链表为"); PrintUtil.printLinkedList(n0); - /* 删除结点 */ + /* 删除节点 */ remove(n0); - System.out.println("删除结点后的链表为"); + System.out.println("删除节点后的链表为"); PrintUtil.printLinkedList(n0); - /* 访问结点 */ + /* 访问节点 */ ListNode node = access(n0, 3); - System.out.println("链表中索引 3 处的结点的值 = " + node.val); + System.out.println("链表中索引 3 处的节点的值 = " + node.val); - /* 查找结点 */ + /* 查找节点 */ int index = find(n0, 2); - System.out.println("链表中值为 2 的结点的索引 = " + index); + System.out.println("链表中值为 2 的节点的索引 = " + index); } } diff --git a/codes/java/chapter_array_and_linkedlist/list.java b/codes/java/chapter_array_and_linkedlist/list.java index dc4d897f7f..3178071532 100644 --- a/codes/java/chapter_array_and_linkedlist/list.java +++ b/codes/java/chapter_array_and_linkedlist/list.java @@ -1,7 +1,7 @@ /** * File: list.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_array_and_linkedlist; @@ -13,56 +13,54 @@ public static void main(String[] args) { /* 初始化列表 */ // 注意数组的元素类型是 int[] 的包装类 Integer[] Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; - List list = new ArrayList<>(Arrays.asList(numbers)); - System.out.println("列表 list = " + list); + List nums = new ArrayList<>(Arrays.asList(numbers)); + System.out.println("列表 nums = " + nums); /* 访问元素 */ - int num = list.get(1); + int num = nums.get(1); System.out.println("访问索引 1 处的元素,得到 num = " + num); /* 更新元素 */ - list.set(1, 0); - System.out.println("将索引 1 处的元素更新为 0 ,得到 list = " + list); + nums.set(1, 0); + System.out.println("将索引 1 处的元素更新为 0 ,得到 nums = " + nums); /* 清空列表 */ - list.clear(); - System.out.println("清空列表后 list = " + list); + nums.clear(); + System.out.println("清空列表后 nums = " + nums); - /* 尾部添加元素 */ - list.add(1); - list.add(3); - list.add(2); - list.add(5); - list.add(4); - System.out.println("添加元素后 list = " + list); + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("添加元素后 nums = " + nums); - /* 中间插入元素 */ - list.add(3, 6); - System.out.println("在索引 3 处插入数字 6 ,得到 list = " + list); + /* 在中间插入元素 */ + nums.add(3, 6); + System.out.println("在索引 3 处插入数字 6 ,得到 nums = " + nums); /* 删除元素 */ - list.remove(3); - System.out.println("删除索引 3 处的元素,得到 list = " + list); + nums.remove(3); + System.out.println("删除索引 3 处的元素,得到 nums = " + nums); /* 通过索引遍历列表 */ int count = 0; - for (int i = 0; i < list.size(); i++) { - count++; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); } - /* 直接遍历列表元素 */ - count = 0; - for (int n : list) { - count++; + for (int x : nums) { + count += x; } /* 拼接两个列表 */ - List list1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); - list.addAll(list1); - System.out.println("将列表 list1 拼接到 list 之后,得到 list = " + list); + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); + System.out.println("将列表 nums1 拼接到 nums 之后,得到 nums = " + nums); /* 排序列表 */ - Collections.sort(list); - System.out.println("排序列表后 list = " + list); + Collections.sort(nums); + System.out.println("排序列表后 nums = " + nums); } } diff --git a/codes/java/chapter_array_and_linkedlist/my_list.java b/codes/java/chapter_array_and_linkedlist/my_list.java index cce8da6839..12962d5493 100644 --- a/codes/java/chapter_array_and_linkedlist/my_list.java +++ b/codes/java/chapter_array_and_linkedlist/my_list.java @@ -1,26 +1,26 @@ /** * File: my_list.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_array_and_linkedlist; import java.util.*; -/* 列表类简易实现 */ +/* 列表类 */ class MyList { - private int[] nums; // 数组(存储列表元素) - private int capacity = 10; // 列表容量 - private int size = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 + private int[] arr; // 数组(存储列表元素) + private int capacity = 10; // 列表容量 + private int size = 0; // 列表长度(当前元素数量) + private int extendRatio = 2; // 每次列表扩容的倍数 - /* 构造函数 */ + /* 构造方法 */ public MyList() { - nums = new int[capacity]; + arr = new int[capacity]; } - /* 获取列表长度(即当前元素数量)*/ + /* 获取列表长度(当前元素数量) */ public int size() { return size; } @@ -32,77 +32,77 @@ public int capacity() { /* 访问元素 */ public int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index >= size) + // 索引如果越界,则抛出异常,下同 + if (index < 0 || index >= size) throw new IndexOutOfBoundsException("索引越界"); - return nums[index]; + return arr[index]; } /* 更新元素 */ public void set(int index, int num) { - if (index >= size) + if (index < 0 || index >= size) throw new IndexOutOfBoundsException("索引越界"); - nums[index] = num; + arr[index] = num; } - /* 尾部添加元素 */ + /* 在尾部添加元素 */ public void add(int num) { // 元素数量超出容量时,触发扩容机制 if (size == capacity()) extendCapacity(); - nums[size] = num; + arr[size] = num; // 更新元素数量 size++; } - /* 中间插入元素 */ + /* 在中间插入元素 */ public void insert(int index, int num) { - if (index >= size) + if (index < 0 || index >= size) throw new IndexOutOfBoundsException("索引越界"); // 元素数量超出容量时,触发扩容机制 if (size == capacity()) extendCapacity(); // 将索引 index 以及之后的元素都向后移动一位 for (int j = size - 1; j >= index; j--) { - nums[j + 1] = nums[j]; + arr[j + 1] = arr[j]; } - nums[index] = num; + arr[index] = num; // 更新元素数量 size++; } /* 删除元素 */ public int remove(int index) { - if (index >= size) + if (index < 0 || index >= size) throw new IndexOutOfBoundsException("索引越界"); - int num = nums[index]; - // 将索引 index 之后的元素都向前移动一位 + int num = arr[index]; + // 将将索引 index 之后的元素都向前移动一位 for (int j = index; j < size - 1; j++) { - nums[j] = nums[j + 1]; + arr[j] = arr[j + 1]; } // 更新元素数量 size--; - // 返回被删除元素 + // 返回被删除的元素 return num; } /* 列表扩容 */ public void extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - nums = Arrays.copyOf(nums, capacity() * extendRatio); + // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组复制到新数组 + arr = Arrays.copyOf(arr, capacity() * extendRatio); // 更新列表容量 - capacity = nums.length; + capacity = arr.length; } /* 将列表转换为数组 */ public int[] toArray() { int size = size(); // 仅转换有效长度范围内的列表元素 - int[] nums = new int[size]; + int[] arr = new int[size]; for (int i = 0; i < size; i++) { - nums[i] = get(i); + arr[i] = get(i); } - return nums; + return arr; } } @@ -110,38 +110,38 @@ public class my_list { /* Driver Code */ public static void main(String[] args) { /* 初始化列表 */ - MyList list = new MyList(); - /* 尾部添加元素 */ - list.add(1); - list.add(3); - list.add(2); - list.add(5); - list.add(4); - System.out.println("列表 list = " + Arrays.toString(list.toArray()) + - " ,容量 = " + list.capacity() + " ,长度 = " + list.size()); - - /* 中间插入元素 */ - list.insert(3, 6); - System.out.println("在索引 3 处插入数字 6 ,得到 list = " + Arrays.toString(list.toArray())); + MyList nums = new MyList(); + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("列表 nums = " + Arrays.toString(nums.toArray()) + + " ,容量 = " + nums.capacity() + " ,长度 = " + nums.size()); + + /* 在中间插入元素 */ + nums.insert(3, 6); + System.out.println("在索引 3 处插入数字 6 ,得到 nums = " + Arrays.toString(nums.toArray())); /* 删除元素 */ - list.remove(3); - System.out.println("删除索引 3 处的元素,得到 list = " + Arrays.toString(list.toArray())); + nums.remove(3); + System.out.println("删除索引 3 处的元素,得到 nums = " + Arrays.toString(nums.toArray())); /* 访问元素 */ - int num = list.get(1); + int num = nums.get(1); System.out.println("访问索引 1 处的元素,得到 num = " + num); /* 更新元素 */ - list.set(1, 0); - System.out.println("将索引 1 处的元素更新为 0 ,得到 list = " + Arrays.toString(list.toArray())); + nums.set(1, 0); + System.out.println("将索引 1 处的元素更新为 0 ,得到 nums = " + Arrays.toString(nums.toArray())); /* 测试扩容机制 */ for (int i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list.add(i); + nums.add(i); } - System.out.println("扩容后的列表 list = " + Arrays.toString(list.toArray()) + - " ,容量 = " + list.capacity() + " ,长度 = " + list.size()); + System.out.println("扩容后的列表 nums = " + Arrays.toString(nums.toArray()) + + " ,容量 = " + nums.capacity() + " ,长度 = " + nums.size()); } } diff --git a/codes/java/chapter_backtracking/n_queens.java b/codes/java/chapter_backtracking/n_queens.java new file mode 100644 index 0000000000..ecfcef14a4 --- /dev/null +++ b/codes/java/chapter_backtracking/n_queens.java @@ -0,0 +1,77 @@ +/** + * File: n_queens.java + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class n_queens { + /* 回溯算法:n 皇后 */ + public static void backtrack(int row, int n, List> state, List>> res, + boolean[] cols, boolean[] diags1, boolean[] diags2) { + // 当放置完所有行时,记录解 + if (row == n) { + List> copyState = new ArrayList<>(); + for (List sRow : state) { + copyState.add(new ArrayList<>(sRow)); + } + res.add(copyState); + 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.get(row).set(col, "Q"); + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state.get(row).set(col, "#"); + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* 求解 n 皇后 */ + public static List>> nQueens(int n) { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + List> state = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List row = new ArrayList<>(); + for (int j = 0; j < n; j++) { + row.add("#"); + } + state.add(row); + } + boolean[] cols = new boolean[n]; // 记录列是否有皇后 + boolean[] diags1 = new boolean[2 * n - 1]; // 记录主对角线上是否有皇后 + boolean[] diags2 = new boolean[2 * n - 1]; // 记录次对角线上是否有皇后 + List>> res = new ArrayList<>(); + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + public static void main(String[] args) { + int n = 4; + List>> res = nQueens(n); + + System.out.println("输入棋盘长宽为 " + n); + System.out.println("皇后放置方案共有 " + res.size() + " 种"); + for (List> state : res) { + System.out.println("--------------------"); + for (List row : state) { + System.out.println(row); + } + } + } +} diff --git a/codes/java/chapter_backtracking/permutations_i.java b/codes/java/chapter_backtracking/permutations_i.java new file mode 100644 index 0000000000..f302a43618 --- /dev/null +++ b/codes/java/chapter_backtracking/permutations_i.java @@ -0,0 +1,51 @@ +/** + * File: permutations_i.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_i { + /* 回溯算法:全排列 I */ + public static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* 全排列 I */ + static List> permutationsI(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3 }; + + List> res = permutationsI(nums); + + System.out.println("输入数组 nums = " + Arrays.toString(nums)); + System.out.println("所有排列 res = " + res); + } +} diff --git a/codes/java/chapter_backtracking/permutations_ii.java b/codes/java/chapter_backtracking/permutations_ii.java new file mode 100644 index 0000000000..4763db16d5 --- /dev/null +++ b/codes/java/chapter_backtracking/permutations_ii.java @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_ii { + /* 回溯算法:全排列 II */ + static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 遍历所有选择 + Set duplicated = new HashSet(); + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 尝试:做出选择,更新状态 + duplicated.add(choice); // 记录选择过的元素值 + selected[i] = true; + state.add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* 全排列 II */ + static List> permutationsII(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 2 }; + + List> res = permutationsII(nums); + + System.out.println("输入数组 nums = " + Arrays.toString(nums)); + System.out.println("所有排列 res = " + res); + } +} diff --git a/codes/java/chapter_backtracking/preorder_traversal_i_compact.java b/codes/java/chapter_backtracking/preorder_traversal_i_compact.java new file mode 100644 index 0000000000..821ffe7d01 --- /dev/null +++ b/codes/java/chapter_backtracking/preorder_traversal_i_compact.java @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_i_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_i_compact { + static List res; + + /* 前序遍历:例题一 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 记录解 + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二叉树"); + PrintUtil.printTree(root); + + // 前序遍历 + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n输出所有值为 7 的节点"); + List vals = new ArrayList<>(); + for (TreeNode node : res) { + vals.add(node.val); + } + System.out.println(vals); + } +} diff --git a/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java b/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java new file mode 100644 index 0000000000..2e2db81365 --- /dev/null +++ b/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_ii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_ii_compact { + static List path; + static List> res; + + /* 前序遍历:例题二 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + // 尝试 + path.add(root); + if (root.val == 7) { + // 记录解 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二叉树"); + PrintUtil.printTree(root); + + // 前序遍历 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n输出所有根节点到节点 7 的路径"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java b/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java new file mode 100644 index 0000000000..c19ccd454f --- /dev/null +++ b/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -0,0 +1,53 @@ +/** + * File: preorder_traversal_iii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_compact { + static List path; + static List> res; + + /* 前序遍历:例题三 */ + static void preOrder(TreeNode root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 尝试 + path.add(root); + if (root.val == 7) { + // 记录解 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二叉树"); + PrintUtil.printTree(root); + + // 前序遍历 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/codes/java/chapter_backtracking/preorder_traversal_iii_template.java b/codes/java/chapter_backtracking/preorder_traversal_iii_template.java new file mode 100644 index 0000000000..66e3ab211a --- /dev/null +++ b/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -0,0 +1,77 @@ +/** + * File: preorder_traversal_iii_template.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_template { + /* 判断当前状态是否为解 */ + static boolean isSolution(List state) { + return !state.isEmpty() && state.get(state.size() - 1).val == 7; + } + + /* 记录解 */ + static void recordSolution(List state, List> res) { + res.add(new ArrayList<>(state)); + } + + /* 判断在当前状态下,该选择是否合法 */ + static boolean isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 更新状态 */ + static void makeChoice(List state, TreeNode choice) { + state.add(choice); + } + + /* 恢复状态 */ + static void undoChoice(List state, TreeNode choice) { + state.remove(state.size() - 1); + } + + /* 回溯算法:例题三 */ + static void backtrack(List state, List choices, List> res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + for (TreeNode choice : choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + backtrack(state, Arrays.asList(choice.left, choice.right), res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二叉树"); + PrintUtil.printTree(root); + + // 回溯算法 + List> res = new ArrayList<>(); + backtrack(new ArrayList<>(), Arrays.asList(root), res); + + System.out.println("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/codes/java/chapter_backtracking/subset_sum_i.java b/codes/java/chapter_backtracking/subset_sum_i.java new file mode 100644 index 0000000000..904d8137bf --- /dev/null +++ b/codes/java/chapter_backtracking/subset_sum_i.java @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i { + /* 回溯算法:子集和 I */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 I */ + static List> subsetSumI(int[] nums, int target) { + List state = new ArrayList<>(); // 状态(子集) + Arrays.sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = new ArrayList<>(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumI(nums, target); + + System.out.println("输入数组 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等于 " + target + " 的子集 res = " + res); + } +} diff --git a/codes/java/chapter_backtracking/subset_sum_i_naive.java b/codes/java/chapter_backtracking/subset_sum_i_naive.java new file mode 100644 index 0000000000..70261ad87d --- /dev/null +++ b/codes/java/chapter_backtracking/subset_sum_i_naive.java @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i_naive.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i_naive { + /* 回溯算法:子集和 I */ + static void backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.add(new ArrayList<>(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.length; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 I(包含重复子集) */ + static List> subsetSumINaive(int[] nums, int target) { + List state = new ArrayList<>(); // 状态(子集) + int total = 0; // 子集和 + List> res = new ArrayList<>(); // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + System.out.println("输入数组 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等于 " + target + " 的子集 res = " + res); + System.out.println("请注意,该方法输出的结果包含重复集合"); + } +} diff --git a/codes/java/chapter_backtracking/subset_sum_ii.java b/codes/java/chapter_backtracking/subset_sum_ii.java new file mode 100644 index 0000000000..bc691e7f5e --- /dev/null +++ b/codes/java/chapter_backtracking/subset_sum_ii.java @@ -0,0 +1,60 @@ +/** + * File: subset_sum_ii.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_ii { + /* 回溯算法:子集和 II */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 II */ + static List> subsetSumII(int[] nums, int target) { + List state = new ArrayList<>(); // 状态(子集) + Arrays.sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = new ArrayList<>(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 4, 4, 5 }; + int target = 9; + + List> res = subsetSumII(nums, target); + + System.out.println("输入数组 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等于 " + target + " 的子集 res = " + res); + } +} diff --git a/codes/java/chapter_computational_complexity/iteration.java b/codes/java/chapter_computational_complexity/iteration.java new file mode 100644 index 0000000000..a491ea7cbd --- /dev/null +++ b/codes/java/chapter_computational_complexity/iteration.java @@ -0,0 +1,76 @@ +/** + * File: iteration.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class iteration { + /* for 循环 */ + static int forLoop(int n) { + int res = 0; + // 循环求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* while 循环 */ + static int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; + } + + /* while 循环(两次更新) */ + static int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; + } + + /* 双层 for 循环 */ + static String nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.append("(" + i + ", " + j + "), "); + } + } + return res.toString(); + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = forLoop(n); + System.out.println("\nfor 循环的求和结果 res = " + res); + + res = whileLoop(n); + System.out.println("\nwhile 循环的求和结果 res = " + res); + + res = whileLoopII(n); + System.out.println("\nwhile 循环(两次更新)求和结果 res = " + res); + + String resStr = nestedForLoop(n); + System.out.println("\n双层 for 循环的遍历结果 " + resStr); + } +} diff --git a/codes/java/chapter_computational_complexity/leetcode_two_sum.java b/codes/java/chapter_computational_complexity/leetcode_two_sum.java deleted file mode 100644 index b9f8bf4d7d..0000000000 --- a/codes/java/chapter_computational_complexity/leetcode_two_sum.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * File: leetcode_two_sum.java - * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) - */ - -package chapter_computational_complexity; - -import java.util.*; - -class SolutionBruteForce { - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return new int[] { i, j }; - } - } - return new int[0]; - } -} - -class SolutionHashMap { - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 辅助哈希表,空间复杂度 O(n) - Map dic = new HashMap<>(); - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.containsKey(target - nums[i])) { - return new int[] { dic.get(target - nums[i]), i }; - } - dic.put(nums[i], i); - } - return new int[0]; - } -} - -public class leetcode_two_sum { - public static void main(String[] args) { - // ======= Test Case ======= - int[] nums = { 2,7,11,15 }; - int target = 9; - - // ====== Driver Code ====== - // 方法一 - SolutionBruteForce slt1 = new SolutionBruteForce(); - int[] res = slt1.twoSum(nums, target); - System.out.println("方法一 res = " + Arrays.toString(res)); - // 方法二 - SolutionHashMap slt2 = new SolutionHashMap(); - res = slt2.twoSum(nums, target); - System.out.println("方法二 res = " + Arrays.toString(res)); - } -} diff --git a/codes/java/chapter_computational_complexity/recursion.java b/codes/java/chapter_computational_complexity/recursion.java new file mode 100644 index 0000000000..b2b0da2ff0 --- /dev/null +++ b/codes/java/chapter_computational_complexity/recursion.java @@ -0,0 +1,79 @@ +/** + * File: recursion.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.Stack; + +public class recursion { + /* 递归 */ + static int recur(int n) { + // 终止条件 + if (n == 1) + return 1; + // 递:递归调用 + int res = recur(n - 1); + // 归:返回结果 + return n + res; + } + + /* 使用迭代模拟递归 */ + static int forLoopRecur(int n) { + // 使用一个显式的栈来模拟系统调用栈 + Stack stack = new Stack<>(); + int res = 0; + // 递:递归调用 + for (int i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack.push(i); + } + // 归:返回结果 + while (!stack.isEmpty()) { + // 通过“出栈操作”模拟“归” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* 尾递归 */ + static int tailRecur(int n, int res) { + // 终止条件 + if (n == 0) + return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); + } + + /* 斐波那契数列:递归 */ + static 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; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = recur(n); + System.out.println("\n递归函数的求和结果 res = " + res); + + res = forLoopRecur(n); + System.out.println("\n使用迭代模拟递归求和结果 res = " + res); + + res = tailRecur(n, 0); + System.out.println("\n尾递归函数的求和结果 res = " + res); + + res = fib(n); + System.out.println("\n斐波那契数列的第 " + n + " 项为 " + res); + } +} diff --git a/codes/java/chapter_computational_complexity/space_complexity.java b/codes/java/chapter_computational_complexity/space_complexity.java index 275a37ec0a..06619e926f 100644 --- a/codes/java/chapter_computational_complexity/space_complexity.java +++ b/codes/java/chapter_computational_complexity/space_complexity.java @@ -1,21 +1,21 @@ /** * File: space_complexity.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_computational_complexity; -import include.*; +import utils.*; import java.util.*; public class space_complexity { /* 函数 */ static int function() { - // do something + // 执行某些操作 return 0; } - + /* 常数阶 */ static void constant(int n) { // 常量、变量、对象占用 O(1) 空间 @@ -52,7 +52,8 @@ static void linear(int n) { /* 线性阶(递归实现) */ static void linearRecur(int n) { System.out.println("递归 n = " + n); - if (n == 1) return; + if (n == 1) + return; linearRecur(n - 1); } @@ -73,7 +74,9 @@ static void quadratic(int n) { /* 平方阶(递归实现) */ static int quadraticRecur(int n) { - if (n <= 0) return 0; + if (n <= 0) + return 0; + // 数组 nums 长度为 n, n-1, ..., 2, 1 int[] nums = new int[n]; System.out.println("递归 n = " + n + " 中的 nums 长度 = " + nums.length); return quadraticRecur(n - 1); @@ -81,7 +84,8 @@ static int quadraticRecur(int n) { /* 指数阶(建立满二叉树) */ static TreeNode buildTree(int n) { - if (n == 0) return null; + if (n == 0) + return null; TreeNode root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); diff --git a/codes/java/chapter_computational_complexity/time_complexity.java b/codes/java/chapter_computational_complexity/time_complexity.java index 5a232e5b8c..04d6b199a7 100644 --- a/codes/java/chapter_computational_complexity/time_complexity.java +++ b/codes/java/chapter_computational_complexity/time_complexity.java @@ -1,7 +1,7 @@ /** * File: time_complexity.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_computational_complexity; @@ -23,7 +23,7 @@ static int linear(int n) { count++; return count; } - + /* 线性阶(遍历数组) */ static int arrayTraversal(int[] nums) { int count = 0; @@ -37,7 +37,7 @@ static int arrayTraversal(int[] nums) { /* 平方阶 */ static int quadratic(int n) { int count = 0; - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; @@ -48,17 +48,17 @@ static int quadratic(int n) { /* 平方阶(冒泡排序) */ static int bubbleSort(int[] nums) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + int count = 0; // 计数器 + // 外循环:未排序区间为 [0, i] for (int i = nums.length - 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 个单元操作 + count += 3; // 元素交换包含 3 个单元操作 } } } @@ -68,7 +68,7 @@ static int bubbleSort(int[] nums) { /* 指数阶(循环实现) */ static int exponential(int n) { int count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (int i = 0; i < n; i++) { for (int j = 0; j < base; j++) { count++; @@ -81,12 +81,13 @@ static int exponential(int n) { /* 指数阶(递归实现) */ static int expRecur(int n) { - if (n == 1) return 1; + if (n == 1) + return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } /* 对数阶(循环实现) */ - static int logarithmic(float n) { + static int logarithmic(int n) { int count = 0; while (n > 1) { n = n / 2; @@ -96,16 +97,17 @@ static int logarithmic(float n) { } /* 对数阶(递归实现) */ - static int logRecur(float n) { - if (n <= 1) return 0; + static int logRecur(int n) { + if (n <= 1) + return 0; return logRecur(n / 2) + 1; } /* 线性对数阶 */ - static int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); + static 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++; } @@ -114,7 +116,8 @@ static int linearLogRecur(float n) { /* 阶乘阶(递归实现) */ static int factorialRecur(int n) { - if (n == 0) return 1; + if (n == 0) + return 1; int count = 0; // 从 1 个分裂出 n 个 for (int i = 0; i < n; i++) { @@ -130,35 +133,35 @@ public static void main(String[] args) { System.out.println("输入数据大小 n = " + n); int count = constant(n); - System.out.println("常数阶的计算操作数量 = " + count); + System.out.println("常数阶的操作数量 = " + count); count = linear(n); - System.out.println("线性阶的计算操作数量 = " + count); + System.out.println("线性阶的操作数量 = " + count); count = arrayTraversal(new int[n]); - System.out.println("线性阶(遍历数组)的计算操作数量 = " + count); + System.out.println("线性阶(遍历数组)的操作数量 = " + count); count = quadratic(n); - System.out.println("平方阶的计算操作数量 = " + count); + System.out.println("平方阶的操作数量 = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) - nums[i] = n - i; // [n,n-1,...,2,1] + nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); - System.out.println("平方阶(冒泡排序)的计算操作数量 = " + count); + System.out.println("平方阶(冒泡排序)的操作数量 = " + count); count = exponential(n); - System.out.println("指数阶(循环实现)的计算操作数量 = " + count); + System.out.println("指数阶(循环实现)的操作数量 = " + count); count = expRecur(n); - System.out.println("指数阶(递归实现)的计算操作数量 = " + count); + System.out.println("指数阶(递归实现)的操作数量 = " + count); - count = logarithmic((float) n); - System.out.println("对数阶(循环实现)的计算操作数量 = " + count); - count = logRecur((float) n); - System.out.println("对数阶(递归实现)的计算操作数量 = " + count); + count = logarithmic(n); + System.out.println("对数阶(循环实现)的操作数量 = " + count); + count = logRecur(n); + System.out.println("对数阶(递归实现)的操作数量 = " + count); - count = linearLogRecur((float) n); - System.out.println("线性对数阶(递归实现)的计算操作数量 = " + count); + count = linearLogRecur(n); + System.out.println("线性对数阶(递归实现)的操作数量 = " + count); count = factorialRecur(n); - System.out.println("阶乘阶(递归实现)的计算操作数量 = " + count); + System.out.println("阶乘阶(递归实现)的操作数量 = " + count); } } diff --git a/codes/java/chapter_computational_complexity/worst_best_time_complexity.java b/codes/java/chapter_computational_complexity/worst_best_time_complexity.java index c0fe0baa70..631b01d84a 100644 --- a/codes/java/chapter_computational_complexity/worst_best_time_complexity.java +++ b/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -1,7 +1,7 @@ /** * File: worst_best_time_complexity.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_computational_complexity; @@ -29,12 +29,14 @@ static int[] randomNumbers(int n) { /* 查找数组 nums 中数字 1 所在索引 */ static int findOne(int[] nums) { for (int i = 0; i < nums.length; i++) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] == 1) return i; } return -1; } - + /* Driver Code */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { diff --git a/codes/java/chapter_divide_and_conquer/binary_search_recur.java b/codes/java/chapter_divide_and_conquer/binary_search_recur.java new file mode 100644 index 0000000000..1b4f6af8ae --- /dev/null +++ b/codes/java/chapter_divide_and_conquer/binary_search_recur.java @@ -0,0 +1,45 @@ +/** + * File: binary_search_recur.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +public class binary_search_recur { + /* 二分查找:问题 f(i, j) */ + static 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; + } + } + + /* 二分查找 */ + static int binarySearch(int[] nums, int target) { + int n = nums.length; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + // 二分查找(双闭区间) + int index = binarySearch(nums, target); + System.out.println("目标元素 6 的索引 = " + index); + } +} diff --git a/codes/java/chapter_divide_and_conquer/build_tree.java b/codes/java/chapter_divide_and_conquer/build_tree.java new file mode 100644 index 0000000000..7571e20f0b --- /dev/null +++ b/codes/java/chapter_divide_and_conquer/build_tree.java @@ -0,0 +1,51 @@ +/** + * File: build_tree.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import utils.*; +import java.util.*; + +public class build_tree { + /* 构建二叉树:分治 */ + static TreeNode dfs(int[] preorder, Map inorderMap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return null; + // 初始化根节点 + TreeNode root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = inorderMap.get(preorder[i]); + // 子问题:构建左子树 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; + } + + /* 构建二叉树 */ + static TreeNode buildTree(int[] preorder, int[] inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + Map inorderMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderMap.put(inorder[i], i); + } + TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; + } + + public static void main(String[] args) { + int[] preorder = { 3, 9, 2, 1, 7 }; + int[] inorder = { 9, 3, 1, 2, 7 }; + System.out.println("前序遍历 = " + Arrays.toString(preorder)); + System.out.println("中序遍历 = " + Arrays.toString(inorder)); + + TreeNode root = buildTree(preorder, inorder); + System.out.println("构建的二叉树为:"); + PrintUtil.printTree(root); + } +} diff --git a/codes/java/chapter_divide_and_conquer/hanota.java b/codes/java/chapter_divide_and_conquer/hanota.java new file mode 100644 index 0000000000..c59c634f47 --- /dev/null +++ b/codes/java/chapter_divide_and_conquer/hanota.java @@ -0,0 +1,59 @@ +/** + * File: hanota.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import java.util.*; + +public class hanota { + /* 移动一个圆盘 */ + static void move(List src, List tar) { + // 从 src 顶部拿出一个圆盘 + Integer pan = src.remove(src.size() - 1); + // 将圆盘放入 tar 顶部 + tar.add(pan); + } + + /* 求解汉诺塔问题 f(i) */ + static void dfs(int i, List src, List buf, List tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); + } + + /* 求解汉诺塔问题 */ + static void solveHanota(List A, List B, List C) { + int n = A.size(); + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); + } + + public static void main(String[] args) { + // 列表尾部是柱子顶部 + List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); + List B = new ArrayList<>(); + List C = new ArrayList<>(); + System.out.println("初始状态下:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + + solveHanota(A, B, C); + + System.out.println("圆盘移动完成后:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + } +} diff --git a/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java b/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java new file mode 100644 index 0000000000..5bd928f5eb --- /dev/null +++ b/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.*; + +public class climbing_stairs_backtrack { + /* 回溯 */ + public static void backtrack(List choices, int state, int n, List res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res.set(0, res.get(0) + 1); + // 遍历所有选择 + for (Integer choice : choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) + continue; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } + } + + /* 爬楼梯:回溯 */ + public static int climbingStairsBacktrack(int n) { + List choices = Arrays.asList(1, 2); // 可选择向上爬 1 阶或 2 阶 + int state = 0; // 从第 0 阶开始爬 + List res = new ArrayList<>(); + res.add(0); // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res.get(0); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsBacktrack(n); + System.out.println(String.format("爬 %d 阶楼梯共有 %d 种方案", n, res)); + } +} diff --git a/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java b/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java new file mode 100644 index 0000000000..412d59f06a --- /dev/null +++ b/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.java + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 带约束爬楼梯:动态规划 */ + static int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + int[][] dp = new int[n + 1][3]; + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsConstraintDP(n); + System.out.println(String.format("爬 %d 阶楼梯共有 %d 种方案", n, res)); + } +} diff --git a/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java b/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java new file mode 100644 index 0000000000..e105b34467 --- /dev/null +++ b/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java @@ -0,0 +1,31 @@ +/** + * File: climbing_stairs_dfs.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 搜索 */ + public static 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; + } + + /* 爬楼梯:搜索 */ + public static int climbingStairsDFS(int n) { + return dfs(n); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFS(n); + System.out.println(String.format("爬 %d 阶楼梯共有 %d 种方案", n, res)); + } +} diff --git a/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java b/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java new file mode 100644 index 0000000000..4d3e3b6c6a --- /dev/null +++ b/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_dfs_mem.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class climbing_stairs_dfs_mem { + /* 记忆化搜索 */ + public static 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; + } + + /* 爬楼梯:记忆化搜索 */ + public static int climbingStairsDFSMem(int n) { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + int[] mem = new int[n + 1]; + Arrays.fill(mem, -1); + return dfs(n, mem); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFSMem(n); + System.out.println(String.format("爬 %d 阶楼梯共有 %d 种方案", n, res)); + } +} \ No newline at end of file diff --git a/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java b/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java new file mode 100644 index 0000000000..ca1e583131 --- /dev/null +++ b/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java @@ -0,0 +1,48 @@ +/** + * File: climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 爬楼梯:动态规划 */ + public static int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* 爬楼梯:空间优化后的动态规划 */ + public static 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; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDP(n); + System.out.println(String.format("爬 %d 阶楼梯共有 %d 种方案", n, res)); + + res = climbingStairsDPComp(n); + System.out.println(String.format("爬 %d 阶楼梯共有 %d 种方案", n, res)); + } +} diff --git a/codes/java/chapter_dynamic_programming/coin_change.java b/codes/java/chapter_dynamic_programming/coin_change.java new file mode 100644 index 0000000000..8623721562 --- /dev/null +++ b/codes/java/chapter_dynamic_programming/coin_change.java @@ -0,0 +1,72 @@ +/** + * File: coin_change.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class coin_change { + /* 零钱兑换:动态规划 */ + static int coinChangeDP(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 状态转移:首行首列 + 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] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; + } + + /* 零钱兑换:空间优化后的动态规划 */ + static int coinChangeDPComp(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Arrays.fill(dp, 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] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 4; + + // 动态规划 + int res = coinChangeDP(coins, amt); + System.out.println("凑到目标金额所需的最少硬币数量为 " + res); + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins, amt); + System.out.println("凑到目标金额所需的最少硬币数量为 " + res); + } +} diff --git a/codes/java/chapter_dynamic_programming/coin_change_ii.java b/codes/java/chapter_dynamic_programming/coin_change_ii.java new file mode 100644 index 0000000000..f92c12a49f --- /dev/null +++ b/codes/java/chapter_dynamic_programming/coin_change_ii.java @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class coin_change_ii { + /* 零钱兑换 II:动态规划 */ + static int coinChangeIIDP(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; + } + + /* 零钱兑换 II:空间优化后的动态规划 */ + static int coinChangeIIDPComp(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 5; + + // 动态规划 + int res = coinChangeIIDP(coins, amt); + System.out.println("凑出目标金额的硬币组合数量为 " + res); + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(coins, amt); + System.out.println("凑出目标金额的硬币组合数量为 " + res); + } +} diff --git a/codes/java/chapter_dynamic_programming/edit_distance.java b/codes/java/chapter_dynamic_programming/edit_distance.java new file mode 100644 index 0000000000..0e8f17d49d --- /dev/null +++ b/codes/java/chapter_dynamic_programming/edit_distance.java @@ -0,0 +1,139 @@ +/** + * File: edit_distance.java + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class edit_distance { + /* 编辑距离:暴力搜索 */ + static int editDistanceDFS(String s, String t, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若两字符相等,则直接跳过此两字符 + if (s.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return Math.min(Math.min(insert, delete), replace) + 1; + } + + /* 编辑距离:记忆化搜索 */ + static int editDistanceDFSMem(String s, String t, 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.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = Math.min(Math.min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* 编辑距离:动态规划 */ + static int editDistanceDP(String s, String t) { + int n = s.length(), m = t.length(); + int[][] dp = new int[n + 1][m + 1]; + // 状态转移:首行首列 + 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.charAt(i - 1) == t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; + } + + /* 编辑距离:空间优化后的动态规划 */ + static int editDistanceDPComp(String s, String t) { + int n = s.length(), m = t.length(); + int[] dp = new int[m + 1]; + // 状态转移:首行 + 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.charAt(i - 1) == t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; + } + + public static void main(String[] args) { + String s = "bag"; + String t = "pack"; + int n = s.length(), m = t.length(); + + // 暴力搜索 + int res = editDistanceDFS(s, t, n, m); + System.out.println("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + + // 记忆化搜索 + int[][] mem = new int[n + 1][m + 1]; + for (int[] row : mem) + Arrays.fill(row, -1); + res = editDistanceDFSMem(s, t, mem, n, m); + System.out.println("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + + // 动态规划 + res = editDistanceDP(s, t); + System.out.println("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t); + System.out.println("将 " + s + " 更改为 " + t + " 最少需要编辑 " + res + " 步"); + } +} diff --git a/codes/java/chapter_dynamic_programming/knapsack.java b/codes/java/chapter_dynamic_programming/knapsack.java new file mode 100644 index 0000000000..614efac83f --- /dev/null +++ b/codes/java/chapter_dynamic_programming/knapsack.java @@ -0,0 +1,116 @@ +/** + * File: knapsack.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class knapsack { + + /* 0-1 背包:暴力搜索 */ + static 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 Math.max(no, yes); + } + + /* 0-1 背包:记忆化搜索 */ + static int knapsackDFSMem(int[] wgt, int[] val, 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, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; + } + + /* 0-1 背包:动态规划 */ + static int knapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 状态转移 + 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] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 0-1 背包:空间优化后的动态规划 */ + static int knapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + int n = wgt.length; + + // 暴力搜索 + int res = knapsackDFS(wgt, val, n, cap); + System.out.println("不超过背包容量的最大物品价值为 " + res); + + // 记忆化搜索 + int[][] mem = new int[n + 1][cap + 1]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = knapsackDFSMem(wgt, val, mem, n, cap); + System.out.println("不超过背包容量的最大物品价值为 " + res); + + // 动态规划 + res = knapsackDP(wgt, val, cap); + System.out.println("不超过背包容量的最大物品价值为 " + res); + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt, val, cap); + System.out.println("不超过背包容量的最大物品价值为 " + res); + } +} diff --git a/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java b/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java new file mode 100644 index 0000000000..b2798dca70 --- /dev/null +++ b/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_cost_climbing_stairs_dp { + /* 爬楼梯最小代价:动态规划 */ + public static int minCostClimbingStairsDP(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* 爬楼梯最小代价:空间优化后的动态规划 */ + public static int minCostClimbingStairsDPComp(int[] cost) { + int n = cost.length - 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 = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + public static void main(String[] args) { + int[] cost = { 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + System.out.println(String.format("输入楼梯的代价列表为 %s", Arrays.toString(cost))); + + int res = minCostClimbingStairsDP(cost); + System.out.println(String.format("爬完楼梯的最低代价为 %d", res)); + + res = minCostClimbingStairsDPComp(cost); + System.out.println(String.format("爬完楼梯的最低代价为 %d", res)); + } +} diff --git a/codes/java/chapter_dynamic_programming/min_path_sum.java b/codes/java/chapter_dynamic_programming/min_path_sum.java new file mode 100644 index 0000000000..c55625cdeb --- /dev/null +++ b/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -0,0 +1,125 @@ +/** + * File: min_path_sum.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_path_sum { + /* 最小路径和:暴力搜索 */ + static int minPathSumDFS(int[][] grid, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.min(left, up) + grid[i][j]; + } + + /* 最小路径和:记忆化搜索 */ + static int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 若已有记录,则直接返回 + 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] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* 最小路径和:动态规划 */ + static int minPathSumDP(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[][] dp = new int[n][m]; + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; + } + + /* 最小路径和:空间优化后的动态规划 */ + static int minPathSumDPComp(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[] dp = new int[m]; + // 状态转移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + public static void main(String[] args) { + int[][] grid = { + { 1, 3, 1, 5 }, + { 2, 2, 4, 2 }, + { 5, 3, 2, 1 }, + { 4, 3, 5, 2 } + }; + int n = grid.length, m = grid[0].length; + + // 暴力搜索 + int res = minPathSumDFS(grid, n - 1, m - 1); + System.out.println("从左上角到右下角的最小路径和为 " + res); + + // 记忆化搜索 + int[][] mem = new int[n][m]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + System.out.println("从左上角到右下角的最小路径和为 " + res); + + // 动态规划 + res = minPathSumDP(grid); + System.out.println("从左上角到右下角的最小路径和为 " + res); + + // 空间优化后的动态规划 + res = minPathSumDPComp(grid); + System.out.println("从左上角到右下角的最小路径和为 " + res); + } +} diff --git a/codes/java/chapter_dynamic_programming/unbounded_knapsack.java b/codes/java/chapter_dynamic_programming/unbounded_knapsack.java new file mode 100644 index 0000000000..36f67a4632 --- /dev/null +++ b/codes/java/chapter_dynamic_programming/unbounded_knapsack.java @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全背包:动态规划 */ + static int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 状态转移 + 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] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 完全背包:空间优化后的动态规划 */ + static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + 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] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 1, 2, 3 }; + int[] val = { 5, 11, 15 }; + int cap = 4; + + // 动态规划 + int res = unboundedKnapsackDP(wgt, val, cap); + System.out.println("不超过背包容量的最大物品价值为 " + res); + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(wgt, val, cap); + System.out.println("不超过背包容量的最大物品价值为 " + res); + } +} diff --git a/codes/java/chapter_graph/graph_adjacency_list.java b/codes/java/chapter_graph/graph_adjacency_list.java new file mode 100644 index 0000000000..6a4d3fdada --- /dev/null +++ b/codes/java/chapter_graph/graph_adjacency_list.java @@ -0,0 +1,117 @@ +/** + * File: graph_adjacency_list.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +/* 基于邻接表实现的无向图类 */ +class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + Map> adjList; + + /* 构造方法 */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // 添加所有顶点和边 + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + public int size() { + return adjList.size(); + } + + /* 添加边 */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 添加边 vet1 - vet2 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* 删除边 */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 删除边 vet1 - vet2 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* 添加顶点 */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // 在邻接表中添加一个新链表 + adjList.put(vet, new ArrayList<>()); + } + + /* 删除顶点 */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (List list : adjList.values()) { + list.remove(vet); + } + } + + /* 打印邻接表 */ + public void print() { + System.out.println("邻接表 ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } +} + +public class graph_adjacency_list { + public static void main(String[] args) { + /* 初始化无向图 */ + Vertex[] v = Vertex.valsToVets(new int[] { 1, 3, 2, 5, 4 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化后,图为"); + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + System.out.println("\n添加边 1-2 后,图为"); + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + System.out.println("\n删除边 1-3 后,图为"); + graph.print(); + + /* 添加顶点 */ + Vertex v5 = new Vertex(6); + graph.addVertex(v5); + System.out.println("\n添加顶点 6 后,图为"); + graph.print(); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(v[1]); + System.out.println("\n删除顶点 3 后,图为"); + graph.print(); + } +} diff --git a/codes/java/chapter_graph/graph_adjacency_matrix.java b/codes/java/chapter_graph/graph_adjacency_matrix.java new file mode 100644 index 0000000000..aa1bb77af6 --- /dev/null +++ b/codes/java/chapter_graph/graph_adjacency_matrix.java @@ -0,0 +1,131 @@ +/** + * File: graph_adjacency_matrix.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import utils.*; +import java.util.*; + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造方法 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // 添加顶点 + for (int val : vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + public int size() { + return vertices.size(); + } + + /* 添加顶点 */ + public void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.add(val); + // 在邻接矩阵中添加一行 + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // 在邻接矩阵中添加一列 + for (List row : adjMat) { + row.add(0); + } + } + + /* 删除顶点 */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // 在顶点列表中移除索引 index 的顶点 + vertices.remove(index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.remove(index); + // 在邻接矩阵中删除索引 index 的列 + for (List row : adjMat) { + row.remove(index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + public void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat.get(i).set(j, 1); + adjMat.get(j).set(i, 1); + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + public void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + adjMat.get(i).set(j, 0); + adjMat.get(j).set(i, 0); + } + + /* 打印邻接矩阵 */ + public void print() { + System.out.print("顶点列表 = "); + System.out.println(vertices); + System.out.println("邻接矩阵 ="); + PrintUtil.printMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + public static void main(String[] args) { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + int[] vertices = { 1, 3, 2, 5, 4 }; + int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; + GraphAdjMat graph = new GraphAdjMat(vertices, edges); + System.out.println("\n初始化后,图为"); + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.addEdge(0, 2); + System.out.println("\n添加边 1-2 后,图为"); + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.removeEdge(0, 1); + System.out.println("\n删除边 1-3 后,图为"); + graph.print(); + + /* 添加顶点 */ + graph.addVertex(6); + System.out.println("\n添加顶点 6 后,图为"); + graph.print(); + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.removeVertex(1); + System.out.println("\n删除顶点 3 后,图为"); + graph.print(); + } +} diff --git a/codes/java/chapter_graph/graph_bfs.java b/codes/java/chapter_graph/graph_bfs.java new file mode 100644 index 0000000000..fe34b0b506 --- /dev/null +++ b/codes/java/chapter_graph/graph_bfs.java @@ -0,0 +1,55 @@ +/** + * File: graph_bfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_bfs { + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + static List graphBFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new ArrayList<>(); + // 哈希集合,用于记录已被访问过的顶点 + Set visited = new HashSet<>(); + visited.add(startVet); + // 队列用于实现 BFS + Queue que = new LinkedList<>(); + que.offer(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.isEmpty()) { + Vertex vet = que.poll(); // 队首顶点出队 + res.add(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳过已被访问的顶点 + que.offer(adjVet); // 只入队未访问的顶点 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; + } + + public static void main(String[] args) { + /* 初始化无向图 */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + Vertex[][] edges = { { 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] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化后,图为"); + graph.print(); + + /* 广度优先遍历 */ + List res = graphBFS(graph, v[0]); + System.out.println("\n广度优先遍历(BFS)顶点序列为"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/codes/java/chapter_graph/graph_dfs.java b/codes/java/chapter_graph/graph_dfs.java new file mode 100644 index 0000000000..c803cffabb --- /dev/null +++ b/codes/java/chapter_graph/graph_dfs.java @@ -0,0 +1,51 @@ +/** + * File: graph_dfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_dfs { + /* 深度优先遍历辅助函数 */ + static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳过已被访问的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + static List graphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new ArrayList<>(); + // 哈希集合,用于记录已被访问过的顶点 + Set visited = new HashSet<>(); + dfs(graph, visited, res, startVet); + return res; + } + + public static void main(String[] args) { + /* 初始化无向图 */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[5] }, { v[4], v[5] }, { v[5], v[6] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化后,图为"); + graph.print(); + + /* 深度优先遍历 */ + List res = graphDFS(graph, v[0]); + System.out.println("\n深度优先遍历(DFS)顶点序列为"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/codes/java/chapter_greedy/coin_change_greedy.java b/codes/java/chapter_greedy/coin_change_greedy.java new file mode 100644 index 0000000000..6dbb0b6ca3 --- /dev/null +++ b/codes/java/chapter_greedy/coin_change_greedy.java @@ -0,0 +1,55 @@ +/** + * File: coin_change_greedy.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; + +public class coin_change_greedy { + /* 零钱兑换:贪心 */ + static int coinChangeGreedy(int[] coins, int amt) { + // 假设 coins 列表有序 + int i = coins.length - 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; + } + + public static void main(String[] args) { + // 贪心:能够保证找到全局最优解 + int[] coins = { 1, 5, 10, 20, 50, 100 }; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("凑到 " + amt + " 所需的最少硬币数量为 " + res); + + // 贪心:无法保证找到全局最优解 + coins = new int[] { 1, 20, 50 }; + amt = 60; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("凑到 " + amt + " 所需的最少硬币数量为 " + res); + System.out.println("实际上需要的最少数量为 3 ,即 20 + 20 + 20"); + + // 贪心:无法保证找到全局最优解 + coins = new int[] { 1, 49, 50 }; + amt = 98; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("凑到 " + amt + " 所需的最少硬币数量为 " + res); + System.out.println("实际上需要的最少数量为 2 ,即 49 + 49"); + } +} diff --git a/codes/java/chapter_greedy/fractional_knapsack.java b/codes/java/chapter_greedy/fractional_knapsack.java new file mode 100644 index 0000000000..e01b27c99e --- /dev/null +++ b/codes/java/chapter_greedy/fractional_knapsack.java @@ -0,0 +1,59 @@ +/** + * File: fractional_knapsack.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; +import java.util.Comparator; + +/* 物品 */ +class Item { + int w; // 物品重量 + int v; // 物品价值 + + public Item(int w, int v) { + this.w = w; + this.v = v; + } +} + +public class fractional_knapsack { + /* 分数背包:贪心 */ + static double fractionalKnapsack(int[] wgt, int[] val, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + Item[] items = new Item[wgt.length]; + for (int i = 0; i < wgt.length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); + // 循环贪心选择 + double res = 0; + for (Item item : items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (double) item.v / item.w * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + + // 贪心算法 + double res = fractionalKnapsack(wgt, val, cap); + System.out.println("不超过背包容量的最大物品价值为 " + res); + } +} diff --git a/codes/java/chapter_greedy/max_capacity.java b/codes/java/chapter_greedy/max_capacity.java new file mode 100644 index 0000000000..770a5f2323 --- /dev/null +++ b/codes/java/chapter_greedy/max_capacity.java @@ -0,0 +1,38 @@ +/** + * File: max_capacity.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +public class max_capacity { + /* 最大容量:贪心 */ + static int maxCapacity(int[] ht) { + // 初始化 i, j,使其分列数组两端 + int i = 0, j = ht.length - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + public static void main(String[] args) { + int[] ht = { 3, 8, 5, 2, 7, 7, 3, 4 }; + + // 贪心算法 + int res = maxCapacity(ht); + System.out.println("最大容量为 " + res); + } +} diff --git a/codes/java/chapter_greedy/max_product_cutting.java b/codes/java/chapter_greedy/max_product_cutting.java new file mode 100644 index 0000000000..e88a7b60fe --- /dev/null +++ b/codes/java/chapter_greedy/max_product_cutting.java @@ -0,0 +1,40 @@ +/** + * File: max_product_cutting.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.lang.Math; + +public class max_product_cutting { + /* 最大切分乘积:贪心 */ + public static 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 (int) Math.pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return (int) Math.pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return (int) Math.pow(3, a); + } + + public static void main(String[] args) { + int n = 58; + + // 贪心算法 + int res = maxProductCutting(n); + System.out.println("最大切分乘积为 " + res); + } +} diff --git a/codes/java/chapter_hashing/array_hash_map.java b/codes/java/chapter_hashing/array_hash_map.java index 980a00ab7e..483babb318 100644 --- a/codes/java/chapter_hashing/array_hash_map.java +++ b/codes/java/chapter_hashing/array_hash_map.java @@ -1,31 +1,33 @@ /** - * File: hash_map.java + * File: array_hash_map.java * Created Time: 2022-12-04 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_hashing; import java.util.*; -/* 键值对 int->String */ -class Entry { +/* 键值对 */ +class Pair { public int key; public String val; - public Entry(int key, String val) { + + public Pair(int key, String val) { this.key = key; this.val = val; } } -/* 基于数组简易实现的哈希表 */ +/* 基于数组实现的哈希表 */ class ArrayHashMap { - private List bucket; + private List buckets; + public ArrayHashMap() { - // 初始化一个长度为 100 的桶(数组) - bucket = new ArrayList<>(); + // 初始化数组,包含 100 个桶 + buckets = new ArrayList<>(); for (int i = 0; i < 100; i++) { - bucket.add(null); + buckets.add(null); } } @@ -38,39 +40,40 @@ private int hashFunc(int key) { /* 查询操作 */ public String get(int key) { int index = hashFunc(key); - Entry pair = bucket.get(index); - if (pair == null) return null; + Pair pair = buckets.get(index); + if (pair == null) + return null; return pair.val; } /* 添加操作 */ public void put(int key, String val) { - Entry pair = new Entry(key, val); + Pair pair = new Pair(key, val); int index = hashFunc(key); - bucket.set(index, pair); + buckets.set(index, pair); } /* 删除操作 */ public void remove(int key) { int index = hashFunc(key); // 置为 null ,代表删除 - bucket.set(index, null); + buckets.set(index, null); } /* 获取所有键值对 */ - public List entrySet() { - List entrySet = new ArrayList<>(); - for (Entry pair : bucket) { + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { if (pair != null) - entrySet.add(pair); + pairSet.add(pair); } - return entrySet; + return pairSet; } /* 获取所有键 */ public List keySet() { List keySet = new ArrayList<>(); - for (Entry pair : bucket) { + for (Pair pair : buckets) { if (pair != null) keySet.add(pair.key); } @@ -80,7 +83,7 @@ public List keySet() { /* 获取所有值 */ public List valueSet() { List valueSet = new ArrayList<>(); - for (Entry pair : bucket) { + for (Pair pair : buckets) { if (pair != null) valueSet.add(pair.val); } @@ -89,13 +92,12 @@ public List valueSet() { /* 打印哈希表 */ public void print() { - for (Entry kv: entrySet()) { + for (Pair kv : pairSet()) { System.out.println(kv.key + " -> " + kv.val); } } } - public class array_hash_map { public static void main(String[] args) { /* 初始化哈希表 */ @@ -103,16 +105,16 @@ public static void main(String[] args) { /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); System.out.println("\n添加完成后,哈希表为\nKey -> Value"); map.print(); /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value + // 向哈希表中输入键 key ,得到值 value String name = map.get(15937); System.out.println("\n输入学号 15937 ,查询到姓名 " + name); @@ -124,15 +126,15 @@ public static void main(String[] args) { /* 遍历哈希表 */ System.out.println("\n遍历键值对 Key->Value"); - for (Entry kv: map.entrySet()) { + for (Pair kv : map.pairSet()) { System.out.println(kv.key + " -> " + kv.val); } System.out.println("\n单独遍历键 Key"); - for (int key: map.keySet()) { + for (int key : map.keySet()) { System.out.println(key); } System.out.println("\n单独遍历值 Value"); - for (String val: map.valueSet()) { + for (String val : map.valueSet()) { System.out.println(val); } } diff --git a/codes/java/chapter_hashing/built_in_hash.java b/codes/java/chapter_hashing/built_in_hash.java new file mode 100644 index 0000000000..0cd0136558 --- /dev/null +++ b/codes/java/chapter_hashing/built_in_hash.java @@ -0,0 +1,38 @@ +/** + * File: built_in_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import utils.*; +import java.util.*; + +public class built_in_hash { + public static void main(String[] args) { + int num = 3; + int hashNum = Integer.hashCode(num); + System.out.println("整数 " + num + " 的哈希值为 " + hashNum); + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + System.out.println("布尔量 " + bol + " 的哈希值为 " + hashBol); + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + System.out.println("小数 " + dec + " 的哈希值为 " + hashDec); + + String str = "Hello 算法"; + int hashStr = str.hashCode(); + System.out.println("字符串 " + str + " 的哈希值为 " + hashStr); + + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + System.out.println("数组 " + Arrays.toString(arr) + " 的哈希值为 " + hashTup); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + System.out.println("节点对象 " + obj + " 的哈希值为 " + hashObj); + } +} diff --git a/codes/java/chapter_hashing/hash_map.java b/codes/java/chapter_hashing/hash_map.java index 47f4823bf2..9323085f59 100644 --- a/codes/java/chapter_hashing/hash_map.java +++ b/codes/java/chapter_hashing/hash_map.java @@ -1,13 +1,13 @@ /** * File: hash_map.java * Created Time: 2022-12-04 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_hashing; import java.util.*; -import include.*; +import utils.*; public class hash_map { public static void main(String[] args) { @@ -16,16 +16,16 @@ public static void main(String[] args) { /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); System.out.println("\n添加完成后,哈希表为\nKey -> Value"); PrintUtil.printHashMap(map); /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value + // 向哈希表中输入键 key ,得到值 value String name = map.get(15937); System.out.println("\n输入学号 15937 ,查询到姓名 " + name); @@ -37,15 +37,15 @@ public static void main(String[] args) { /* 遍历哈希表 */ System.out.println("\n遍历键值对 Key->Value"); - for (Map.Entry kv: map.entrySet()) { + for (Map.Entry kv : map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } System.out.println("\n单独遍历键 Key"); - for (int key: map.keySet()) { + for (int key : map.keySet()) { System.out.println(key); } System.out.println("\n单独遍历值 Value"); - for (String val: map.values()) { + for (String val : map.values()) { System.out.println(val); } } diff --git a/codes/java/chapter_hashing/hash_map_chaining.java b/codes/java/chapter_hashing/hash_map_chaining.java new file mode 100644 index 0000000000..72073a16bc --- /dev/null +++ b/codes/java/chapter_hashing/hash_map_chaining.java @@ -0,0 +1,148 @@ +/** + * File: hash_map_chaining.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.ArrayList; +import java.util.List; + +/* 链式地址哈希表 */ +class HashMapChaining { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + List> buckets; // 桶数组 + + /* 构造方法 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return (double) size / capacity; + } + + /* 查询操作 */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,若找到 key ,则返回对应 val + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,则返回 null + return null; + } + + /* 添加操作 */ + void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,从中删除键值对 + for (Pair pair : bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + List> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (List bucket : bucketsTmp) { + for (Pair pair : bucket) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } +} + +public class hash_map_chaining { + public static void main(String[] args) { + /* 初始化哈希表 */ + HashMapChaining map = new HashMapChaining(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + System.out.println("\n添加完成后,哈希表为\nKey -> Value"); + map.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + String name = map.get(13276); + System.out.println("\n输入学号 13276 ,查询到姓名 " + name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(12836); + System.out.println("\n删除 12836 后,哈希表为\nKey -> Value"); + map.print(); + } +} diff --git a/codes/java/chapter_hashing/hash_map_open_addressing.java b/codes/java/chapter_hashing/hash_map_open_addressing.java new file mode 100644 index 0000000000..b20d6779b8 --- /dev/null +++ b/codes/java/chapter_hashing/hash_map_open_addressing.java @@ -0,0 +1,158 @@ +/** + * File: hash_map_open_addressing.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + private int size; // 键值对数量 + private int capacity = 4; // 哈希表容量 + private final double loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + private final int extendRatio = 2; // 扩容倍数 + private Pair[] buckets; // 桶数组 + private final Pair TOMBSTONE = new Pair(-1, "-1"); // 删除标记 + + /* 构造方法 */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* 哈希函数 */ + private int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + private double loadFactor() { + return (double) size / capacity; + } + + /* 搜索 key 对应的桶索引 */ + private int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回对应的桶索引 + if (buckets[index].key == key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查询操作 */ + public String get(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则返回对应 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // 若键值对不存在,则返回 null + return null; + } + + /* 添加操作 */ + public void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // 若键值对不存在,则添加该键值对 + buckets[index] = new Pair(key, val); + size++; + } + + /* 删除操作 */ + public void remove(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 扩容哈希表 */ + private void extend() { + // 暂存原哈希表 + Pair[] bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (Pair pair : bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + public void print() { + for (Pair pair : buckets) { + if (pair == null) { + System.out.println("null"); + } else if (pair == TOMBSTONE) { + System.out.println("TOMBSTONE"); + } else { + System.out.println(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + public static void main(String[] args) { + // 初始化哈希表 + HashMapOpenAddressing hashmap = new HashMapOpenAddressing(); + + // 添加操作 + // 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小啰"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鸭"); + System.out.println("\n添加完成后,哈希表为\nKey -> Value"); + hashmap.print(); + + // 查询操作 + // 向哈希表中输入键 key ,得到值 val + String name = hashmap.get(13276); + System.out.println("\n输入学号 13276 ,查询到姓名 " + name); + + // 删除操作 + // 在哈希表中删除键值对 (key, val) + hashmap.remove(16750); + System.out.println("\n删除 16750 后,哈希表为\nKey -> Value"); + hashmap.print(); + } +} diff --git a/codes/java/chapter_hashing/simple_hash.java b/codes/java/chapter_hashing/simple_hash.java new file mode 100644 index 0000000000..6551bd07c0 --- /dev/null +++ b/codes/java/chapter_hashing/simple_hash.java @@ -0,0 +1,65 @@ +/** + * File: simple_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +public class simple_hash { + /* 加法哈希 */ + static int addHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* 乘法哈希 */ + static int mulHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (31 * hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* 异或哈希 */ + static int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash ^= (int) c; + } + return hash & MODULUS; + } + + /* 旋转哈希 */ + static int rotHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; + } + return (int) hash; + } + + public static void main(String[] args) { + String key = "Hello 算法"; + + int hash = addHash(key); + System.out.println("加法哈希值为 " + hash); + + hash = mulHash(key); + System.out.println("乘法哈希值为 " + hash); + + hash = xorHash(key); + System.out.println("异或哈希值为 " + hash); + + hash = rotHash(key); + System.out.println("旋转哈希值为 " + hash); + } +} diff --git a/codes/java/chapter_heap/heap.java b/codes/java/chapter_heap/heap.java index 1a0c53b94e..d18b086b50 100644 --- a/codes/java/chapter_heap/heap.java +++ b/codes/java/chapter_heap/heap.java @@ -1,23 +1,22 @@ /** - * File: my_heap.java + * File: heap.java * Created Time: 2023-01-07 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_heap; -import include.*; +import utils.*; import java.util.*; - public class heap { public static void testPush(Queue heap, int val) { - heap.add(val); // 元素入堆 + heap.offer(val); // 元素入堆 System.out.format("\n元素 %d 入堆后\n", val); PrintUtil.printHeap(heap); } - public static void testPoll(Queue heap) { + public static void testPop(Queue heap) { int val = heap.poll(); // 堆顶元素出堆 System.out.format("\n堆顶元素 %d 出堆后\n", val); PrintUtil.printHeap(heap); @@ -28,7 +27,7 @@ public static void main(String[] args) { // 初始化小顶堆 Queue minHeap = new PriorityQueue<>(); // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) - Queue maxHeap = new PriorityQueue<>((a, b) -> { return b - a; }); + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); System.out.println("\n以下测试样例为大顶堆"); @@ -44,11 +43,11 @@ public static void main(String[] args) { System.out.format("\n堆顶元素为 %d\n", peek); /* 堆顶元素出堆 */ - testPoll(maxHeap); - testPoll(maxHeap); - testPoll(maxHeap); - testPoll(maxHeap); - testPoll(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); /* 获取堆大小 */ int size = maxHeap.size(); diff --git a/codes/java/chapter_heap/my_heap.java b/codes/java/chapter_heap/my_heap.java index b80ce6d08c..a94a079d49 100644 --- a/codes/java/chapter_heap/my_heap.java +++ b/codes/java/chapter_heap/my_heap.java @@ -1,54 +1,48 @@ /** * File: my_heap.java * Created Time: 2023-01-07 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_heap; -import include.*; +import utils.*; import java.util.*; +/* 大顶堆 */ class MaxHeap { - // 使用列表而非数组,这样无需考虑扩容问题 + // 使用列表而非数组,这样无须考虑扩容问题 private List maxHeap; - /* 构造函数,建立空堆 */ - public MaxHeap() { - maxHeap = new ArrayList<>(); - } - - /* 构造函数,根据输入列表建堆 */ + /* 构造方法,根据输入列表建堆 */ public MaxHeap(List nums) { - // 所有元素入堆 + // 将列表元素原封不动添加进堆 maxHeap = new ArrayList<>(nums); - // 堆化除叶结点以外的其他所有结点 + // 堆化除叶节点以外的其他所有节点 for (int i = parent(size() - 1); i >= 0; i--) { siftDown(i); } } - /* 获取左子结点索引 */ + /* 获取左子节点的索引 */ private int left(int i) { return 2 * i + 1; } - /* 获取右子结点索引 */ + /* 获取右子节点的索引 */ private int right(int i) { return 2 * i + 2; } - /* 获取父结点索引 */ + /* 获取父节点的索引 */ private int parent(int i) { return (i - 1) / 2; // 向下整除 } /* 交换元素 */ private void swap(int i, int j) { - int a = maxHeap.get(i), - b = maxHeap.get(j), - tmp = a; - maxHeap.set(i, b); + int tmp = maxHeap.get(i); + maxHeap.set(i, maxHeap.get(j)); maxHeap.set(j, tmp); } @@ -69,21 +63,21 @@ public int peek() { /* 元素入堆 */ public void push(int val) { - // 添加结点 + // 添加节点 maxHeap.add(val); // 从底至顶堆化 siftUp(size() - 1); } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ private void siftUp(int i) { while (true) { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 int p = parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无须修复”时,结束堆化 if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) break; - // 交换两结点 + // 交换两节点 swap(i, p); // 循环向上堆化 i = p; @@ -91,13 +85,13 @@ private void siftUp(int i) { } /* 元素出堆 */ - public int poll() { + public int pop() { // 判空处理 if (isEmpty()) - throw new EmptyStackException(); - // 交换根结点与最右叶结点(即交换首元素与尾元素) + throw new IndexOutOfBoundsException(); + // 交换根节点与最右叶节点(交换首元素与尾元素) swap(0, size() - 1); - // 删除结点 + // 删除节点 int val = maxHeap.remove(size() - 1); // 从顶至底堆化 siftDown(0); @@ -105,18 +99,19 @@ public int poll() { return val; } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ private void siftDown(int i) { while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma int l = left(i), r = right(i), ma = i; if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) ma = l; if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if (ma == i) break; - // 交换两结点 + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) + break; + // 交换两节点 swap(i, ma); // 循环向下堆化 i = ma; @@ -131,20 +126,7 @@ public void print() { } } - public class my_heap { - public static void testPush(MaxHeap maxHeap, int val) { - maxHeap.push(val); // 元素入堆 - System.out.format("\n添加元素 %d 后\n", val); - maxHeap.print(); - } - - public static void testPoll(MaxHeap maxHeap) { - int val = maxHeap.poll(); // 堆顶元素出堆 - System.out.format("\n出堆元素为 %d\n", val); - maxHeap.print(); - } - public static void main(String[] args) { /* 初始化大顶堆 */ MaxHeap maxHeap = new MaxHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)); @@ -162,7 +144,7 @@ public static void main(String[] args) { maxHeap.print(); /* 堆顶元素出堆 */ - peek = maxHeap.poll(); + peek = maxHeap.pop(); System.out.format("\n堆顶元素 %d 出堆后\n", peek); maxHeap.print(); diff --git a/codes/java/chapter_heap/top_k.java b/codes/java/chapter_heap/top_k.java new file mode 100644 index 0000000000..5cfec44c04 --- /dev/null +++ b/codes/java/chapter_heap/top_k.java @@ -0,0 +1,40 @@ +/** + * File: top_k.java + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class top_k { + /* 基于堆查找数组中最大的 k 个元素 */ + static Queue topKHeap(int[] nums, int k) { + // 初始化小顶堆 + Queue heap = new PriorityQueue(); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + + public static void main(String[] args) { + int[] nums = { 1, 7, 6, 3, 2 }; + int k = 3; + + Queue res = topKHeap(nums, k); + System.out.println("最大的 " + k + " 个元素为"); + PrintUtil.printHeap(res); + } +} diff --git a/codes/java/chapter_searching/binary_search.java b/codes/java/chapter_searching/binary_search.java index 05c2a4d327..e220d5d886 100644 --- a/codes/java/chapter_searching/binary_search.java +++ b/codes/java/chapter_searching/binary_search.java @@ -1,7 +1,7 @@ /** * File: binary_search.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_searching; @@ -13,46 +13,46 @@ static int binarySearch(int[] nums, int target) { int i = 0, j = nums.length - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, 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 // 找到目标元素,返回其索引 + else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } - /* 二分查找(左闭右开) */ - static int binarySearch1(int[] nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + /* 二分查找(左闭右开区间) */ + static int binarySearchLCRO(int[] nums, int target) { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 int i = 0, j = nums.length; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, 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 // 找到目标元素,返回其索引 + else // 找到目标元素,返回其索引 return m; } // 未找到目标元素,返回 -1 return -1; } - + public static void main(String[] args) { int target = 6; - int[] nums = { 1, 3, 6, 8, 12, 15, 23, 67, 70, 92 }; - + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + /* 二分查找(双闭区间) */ int index = binarySearch(nums, target); System.out.println("目标元素 6 的索引 = " + index); - /* 二分查找(左闭右开) */ - index = binarySearch1(nums, target); + /* 二分查找(左闭右开区间) */ + index = binarySearchLCRO(nums, target); System.out.println("目标元素 6 的索引 = " + index); } } diff --git a/codes/java/chapter_searching/binary_search_edge.java b/codes/java/chapter_searching/binary_search_edge.java new file mode 100644 index 0000000000..a6d742771b --- /dev/null +++ b/codes/java/chapter_searching/binary_search_edge.java @@ -0,0 +1,49 @@ +/** + * File: binary_search_edge.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search_edge { + /* 二分查找最左一个 target */ + static int binarySearchLeftEdge(int[] nums, int target) { + // 等价于查找 target 的插入点 + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } + + /* 二分查找最右一个 target */ + static int binarySearchRightEdge(int[] nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } + + public static void main(String[] args) { + // 包含重复元素的数组 + int[] nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\n数组 nums = " + java.util.Arrays.toString(nums)); + + // 二分查找左边界和右边界 + for (int target : new int[] { 6, 7 }) { + int index = binarySearchLeftEdge(nums, target); + System.out.println("最左一个元素 " + target + " 的索引为 " + index); + index = binarySearchRightEdge(nums, target); + System.out.println("最右一个元素 " + target + " 的索引为 " + index); + } + } +} diff --git a/codes/java/chapter_searching/binary_search_insertion.java b/codes/java/chapter_searching/binary_search_insertion.java new file mode 100644 index 0000000000..c6c753a960 --- /dev/null +++ b/codes/java/chapter_searching/binary_search_insertion.java @@ -0,0 +1,63 @@ +/** + * File: binary_search_insertion.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +class binary_search_insertion { + /* 二分查找插入点(无重复元素) */ + static int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.length - 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; + } + + /* 二分查找插入点(存在重复元素) */ + static int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.length - 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; + } + + public static void main(String[] args) { + // 无重复元素的数组 + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + System.out.println("\n数组 nums = " + java.util.Arrays.toString(nums)); + // 二分查找插入点 + for (int target : new int[] { 6, 9 }) { + int index = binarySearchInsertionSimple(nums, target); + System.out.println("元素 " + target + " 的插入点的索引为 " + index); + } + + // 包含重复元素的数组 + nums = new int[] { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\n数组 nums = " + java.util.Arrays.toString(nums)); + // 二分查找插入点 + for (int target : new int[] { 2, 6, 20 }) { + int index = binarySearchInsertion(nums, target); + System.out.println("元素 " + target + " 的插入点的索引为 " + index); + } + } +} diff --git a/codes/java/chapter_searching/hashing_search.java b/codes/java/chapter_searching/hashing_search.java index cfd0153132..00e26f2efc 100644 --- a/codes/java/chapter_searching/hashing_search.java +++ b/codes/java/chapter_searching/hashing_search.java @@ -1,25 +1,25 @@ /** * File: hashing_search.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_searching; -import include.*; +import utils.*; import java.util.*; public class hashing_search { /* 哈希查找(数组) */ - static int hashingSearch(Map map, int target) { + static int hashingSearchArray(Map map, int target) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 return map.getOrDefault(target, -1); } /* 哈希查找(链表) */ - static ListNode hashingSearch1(Map map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 + static ListNode hashingSearchLinkedList(Map map, int target) { + // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null return map.getOrDefault(target, null); } @@ -32,9 +32,9 @@ public static void main(String[] args) { // 初始化哈希表 Map map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { - map.put(nums[i], i); // key: 元素,value: 索引 + map.put(nums[i], i); // key: 元素,value: 索引 } - int index = hashingSearch(map, target); + int index = hashingSearchArray(map, target); System.out.println("目标元素 3 的索引 = " + index); /* 哈希查找(链表) */ @@ -42,10 +42,10 @@ public static void main(String[] args) { // 初始化哈希表 Map map1 = new HashMap<>(); while (head != null) { - map1.put(head.val, head); // key: 结点值,value: 结点 + map1.put(head.val, head); // key: 节点值,value: 节点 head = head.next; } - ListNode node = hashingSearch1(map1, target); - System.out.println("目标结点值 3 的对应结点对象为 " + node); + ListNode node = hashingSearchLinkedList(map1, target); + System.out.println("目标节点值 3 的对应节点对象为 " + node); } } diff --git a/codes/java/chapter_searching/linear_search.java b/codes/java/chapter_searching/linear_search.java index c33b51178d..f7083dd6c1 100644 --- a/codes/java/chapter_searching/linear_search.java +++ b/codes/java/chapter_searching/linear_search.java @@ -1,16 +1,16 @@ /** * File: linear_search.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_searching; -import include.*; +import utils.*; public class linear_search { /* 线性查找(数组) */ - static int linearSearch(int[] nums, int target) { + static int linearSearchArray(int[] nums, int target) { // 遍历数组 for (int i = 0; i < nums.length; i++) { // 找到目标元素,返回其索引 @@ -22,15 +22,15 @@ static int linearSearch(int[] nums, int target) { } /* 线性查找(链表) */ - static ListNode linearSearch(ListNode head, int target) { + static ListNode linearSearchLinkedList(ListNode head, int target) { // 遍历链表 while (head != null) { - // 找到目标结点,返回之 + // 找到目标节点,返回之 if (head.val == target) return head; head = head.next; } - // 未找到目标结点,返回 null + // 未找到目标节点,返回 null return null; } @@ -39,12 +39,12 @@ public static void main(String[] args) { /* 在数组中执行线性查找 */ int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; - int index = linearSearch(nums, target); + int index = linearSearchArray(nums, target); System.out.println("目标元素 3 的索引 = " + index); /* 在链表中执行线性查找 */ ListNode head = ListNode.arrToLinkedList(nums); - ListNode node = linearSearch(head, target); - System.out.println("目标结点值 3 的对应结点对象为 " + node); + ListNode node = linearSearchLinkedList(head, target); + System.out.println("目标节点值 3 的对应节点对象为 " + node); } } diff --git a/codes/java/chapter_searching/two_sum.java b/codes/java/chapter_searching/two_sum.java new file mode 100644 index 0000000000..2049035e3d --- /dev/null +++ b/codes/java/chapter_searching/two_sum.java @@ -0,0 +1,53 @@ +/** + * File: two_sum.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import java.util.*; + +public class two_sum { + /* 方法一:暴力枚举 */ + static int[] twoSumBruteForce(int[] nums, int target) { + int size = nums.length; + // 两层循环,时间复杂度为 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } + } + return new int[0]; + } + + /* 方法二:辅助哈希表 */ + static int[] twoSumHashTable(int[] nums, int target) { + int size = nums.length; + // 辅助哈希表,空间复杂度为 O(n) + Map dic = new HashMap<>(); + // 单层循环,时间复杂度为 O(n) + for (int i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return new int[] { dic.get(target - nums[i]), i }; + } + dic.put(nums[i], i); + } + return new int[0]; + } + + public static void main(String[] args) { + // ======= Test Case ======= + int[] nums = { 2, 7, 11, 15 }; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + int[] res = twoSumBruteForce(nums, target); + System.out.println("方法一 res = " + Arrays.toString(res)); + // 方法二 + res = twoSumHashTable(nums, target); + System.out.println("方法二 res = " + Arrays.toString(res)); + } +} diff --git a/codes/java/chapter_sorting/bubble_sort.java b/codes/java/chapter_sorting/bubble_sort.java index 082d2d9091..920797dd97 100644 --- a/codes/java/chapter_sorting/bubble_sort.java +++ b/codes/java/chapter_sorting/bubble_sort.java @@ -1,7 +1,7 @@ /** * File: bubble_sort.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_sorting; @@ -11,9 +11,9 @@ public class bubble_sort { /* 冒泡排序 */ static void bubbleSort(int[] nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (int i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -25,22 +25,23 @@ static void bubbleSort(int[] nums) { } } - /* 冒泡排序(标志优化)*/ + /* 冒泡排序(标志优化) */ static void bubbleSortWithFlag(int[] nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (int i = nums.length - 1; i > 0; i--) { boolean flag = false; // 初始化标志位 - // 内循环:冒泡操作 + // 内循环:将未排序区间 [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; - flag = true; // 记录交换元素 + flag = true; // 记录交换元素 } } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + if (!flag) + break; // 此轮“冒泡”未交换任何元素,直接跳出 } } @@ -51,6 +52,6 @@ public static void main(String[] args) { int[] nums1 = { 4, 1, 3, 1, 5, 2 }; bubbleSortWithFlag(nums1); - System.out.println("冒泡排序完成后 nums1 = " + Arrays.toString(nums)); + System.out.println("冒泡排序完成后 nums1 = " + Arrays.toString(nums1)); } } diff --git a/codes/java/chapter_sorting/bucket_sort.java b/codes/java/chapter_sorting/bucket_sort.java new file mode 100644 index 0000000000..fd9f36ad83 --- /dev/null +++ b/codes/java/chapter_sorting/bucket_sort.java @@ -0,0 +1,47 @@ +/** + * File: bucket_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bucket_sort { + /* 桶排序 */ + static void bucketSort(float[] nums) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + int k = nums.length / 2; + List> buckets = new ArrayList<>(); + for (int i = 0; i < k; i++) { + buckets.add(new ArrayList<>()); + } + // 1. 将数组元素分配到各个桶中 + for (float num : nums) { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + int i = (int) (num * k); + // 将 num 添加进桶 i + buckets.get(i).add(num); + } + // 2. 对各个桶执行排序 + for (List bucket : buckets) { + // 使用内置排序函数,也可以替换成其他排序算法 + Collections.sort(bucket); + } + // 3. 遍历桶合并结果 + int i = 0; + for (List bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } + } + + public static void main(String[] args) { + // 设输入数据为浮点数,范围为 [0, 1) + float[] nums = { 0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f }; + bucketSort(nums); + System.out.println("桶排序完成后 nums = " + Arrays.toString(nums)); + } +} diff --git a/codes/java/chapter_sorting/counting_sort.java b/codes/java/chapter_sorting/counting_sort.java new file mode 100644 index 0000000000..f329cc33e9 --- /dev/null +++ b/codes/java/chapter_sorting/counting_sort.java @@ -0,0 +1,78 @@ +/** + * File: counting_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class counting_sort { + /* 计数排序 */ + // 简单实现,无法用于排序对象 + static void countingSortNaive(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 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; + } + } + } + + /* 计数排序 */ + // 完整实现,可排序对象,并且是稳定排序 + static void countingSort(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int n = nums.length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + public static void main(String[] args) { + int[] nums = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSortNaive(nums); + System.out.println("计数排序(无法排序对象)完成后 nums = " + Arrays.toString(nums)); + + int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSort(nums1); + System.out.println("计数排序完成后 nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/codes/java/chapter_sorting/heap_sort.java b/codes/java/chapter_sorting/heap_sort.java new file mode 100644 index 0000000000..4bc6db245b --- /dev/null +++ b/codes/java/chapter_sorting/heap_sort.java @@ -0,0 +1,57 @@ +/** + * File: heap_sort.java + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class heap_sort { + /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ + public static void siftDown(int[] nums, int n, int i) { + while (true) { + // 判断节点 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; + } + } + + /* 堆排序 */ + public static void heapSort(int[] nums) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = nums.length / 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = nums.length - 1; i > 0; i--) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + heapSort(nums); + System.out.println("堆排序完成后 nums = " + Arrays.toString(nums)); + } +} diff --git a/codes/java/chapter_sorting/insertion_sort.java b/codes/java/chapter_sorting/insertion_sort.java index fb5b2cd66e..cf75a525e0 100644 --- a/codes/java/chapter_sorting/insertion_sort.java +++ b/codes/java/chapter_sorting/insertion_sort.java @@ -1,7 +1,7 @@ /** * File: insertion_sort.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_sorting; @@ -11,15 +11,15 @@ public class insertion_sort { /* 插入排序 */ static void insertionSort(int[] nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] + // 外循环:已排序区间为 [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 + nums[j + 1] = base; // 将 base 赋值到正确位置 } } diff --git a/codes/java/chapter_sorting/merge_sort.java b/codes/java/chapter_sorting/merge_sort.java index 68f76c9b17..e35a0445b6 100644 --- a/codes/java/chapter_sorting/merge_sort.java +++ b/codes/java/chapter_sorting/merge_sort.java @@ -1,7 +1,7 @@ /** * File: merge_sort.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_sorting; @@ -9,41 +9,41 @@ import java.util.*; public class merge_sort { - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ + /* 合并左子数组和右子数组 */ static void merge(int[] nums, int left, int mid, int right) { - // 初始化辅助数组 - int[] tmp = Arrays.copyOfRange(nums, left, right + 1); - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + int[] tmp = new int[right - left + 1]; + // 初始化左子数组和右子数组的起始索引 + int i = left, j = mid + 1, k = 0; + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; else - nums[k] = tmp[j++]; + tmp[k++] = nums[j++]; + } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; } } /* 归并排序 */ static void mergeSort(int[] nums, int left, int right) { // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 + if (left >= right) + return; // 当子数组长度为 1 时终止递归 // 划分阶段 - int mid = (left + right) / 2; // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 + int mid = left + (right - left) / 2; // 计算中点 + mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); diff --git a/codes/java/chapter_sorting/quick_sort.java b/codes/java/chapter_sorting/quick_sort.java index b121b757b1..a6c39c346d 100644 --- a/codes/java/chapter_sorting/quick_sort.java +++ b/codes/java/chapter_sorting/quick_sort.java @@ -1,7 +1,7 @@ /** * File: quick_sort.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_sorting; @@ -19,7 +19,7 @@ static void swap(int[] nums, int i, int j) { /* 哨兵划分 */ static int partition(int[] nums, int left, int right) { - // 以 nums[left] 作为基准数 + // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) @@ -54,16 +54,14 @@ static void swap(int[] nums, int i, int j) { nums[j] = tmp; } - /* 选取三个元素的中位数 */ + /* 选取三个候选元素的中位数 */ static int medianThree(int[] nums, int left, int mid, int right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return 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; } /* 哨兵划分(三数取中值) */ @@ -72,7 +70,7 @@ static int partition(int[] nums, int left, int right) { int med = medianThree(nums, left, (left + right) / 2, right); // 将中位数交换至数组最左端 swap(nums, left, med); - // 以 nums[left] 作为基准数 + // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) @@ -109,7 +107,7 @@ static void swap(int[] nums, int i, int j) { /* 哨兵划分 */ static int partition(int[] nums, int left, int right) { - // 以 nums[left] 作为基准数 + // 以 nums[left] 为基准数 int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) @@ -119,22 +117,22 @@ static int partition(int[] nums, int left, int right) { swap(nums, i, j); // 交换这两个元素 } swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 + return i; // 返回基准数的索引 } /* 快速排序(尾递归优化) */ - static void quickSort(int[] nums, int left, int right) { + public static void quickSort(int[] nums, int left, int right) { // 子数组长度为 1 时终止 while (left < right) { // 哨兵划分操作 int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 + // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] + quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } diff --git a/codes/java/chapter_sorting/radix_sort.java b/codes/java/chapter_sorting/radix_sort.java new file mode 100644 index 0000000000..25bc8d665a --- /dev/null +++ b/codes/java/chapter_sorting/radix_sort.java @@ -0,0 +1,69 @@ +/** + * File: radix_sort.java + * Created Time: 2023-01-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class radix_sort { + /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + static int digit(int num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10; + } + + /* 计数排序(根据 nums 第 k 位排序) */ + static void countingSortDigit(int[] nums, int exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + int[] counter = new int[10]; + int n = nums.length; + // 统计 0~9 各数字的出现次数 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + int[] res = new int[n]; + for (int i = n - 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 < n; i++) + nums[i] = res[i]; + } + + /* 基数排序 */ + static void radixSort(int[] nums) { + // 获取数组的最大元素,用于判断最大位数 + int m = Integer.MIN_VALUE; + for (int num : nums) + if (num > m) + m = num; + // 按照从低位到高位的顺序遍历 + for (int exp = 1; exp <= m; exp *= 10) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } + } + + public static void main(String[] args) { + // 基数排序 + int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 }; + radixSort(nums); + System.out.println("基数排序完成后 nums = " + Arrays.toString(nums)); + } +} diff --git a/codes/java/chapter_sorting/selection_sort.java b/codes/java/chapter_sorting/selection_sort.java new file mode 100644 index 0000000000..221ab0a79e --- /dev/null +++ b/codes/java/chapter_sorting/selection_sort.java @@ -0,0 +1,35 @@ +/** + * File: selection_sort.java + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class selection_sort { + /* 选择排序 */ + public static void selectionSort(int[] nums) { + int n = nums.length; + // 外循环:未排序区间为 [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; + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + selectionSort(nums); + System.out.println("选择排序完成后 nums = " + Arrays.toString(nums)); + } +} diff --git a/codes/java/chapter_stack_and_queue/array_deque.java b/codes/java/chapter_stack_and_queue/array_deque.java new file mode 100644 index 0000000000..1c2bef5a65 --- /dev/null +++ b/codes/java/chapter_stack_and_queue/array_deque.java @@ -0,0 +1,151 @@ +/** + * File: array_deque.java + * Created Time: 2023-02-16 + * Author: krahets (krahets@163.com), FangYuan33 (374072213@qq.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基于环形数组实现的双向队列 */ +class ArrayDeque { + private int[] nums; // 用于存储双向队列元素的数组 + private int front; // 队首指针,指向队首元素 + private int queSize; // 双向队列长度 + + /* 构造方法 */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* 获取双向队列的容量 */ + public int capacity() { + return nums.length; + } + + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* 计算环形数组索引 */ + private int index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 队首入队 */ + public void pushFirst(int num) { + if (queSize == capacity()) { + System.out.println("双向队列已满"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + front = index(front - 1); + // 将 num 添加至队首 + nums[front] = num; + queSize++; + } + + /* 队尾入队 */ + public void pushLast(int num) { + if (queSize == capacity()) { + System.out.println("双向队列已满"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + int rear = index(front + queSize); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 队首出队 */ + public int popFirst() { + int num = peekFirst(); + // 队首指针向后移动一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 队尾出队 */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* 访问队尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 计算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回数组用于打印 */ + public int[] toArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +} + +public class array_deque { + public static void main(String[] args) { + /* 初始化双向队列 */ + ArrayDeque deque = new ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("双向队列 deque = " + Arrays.toString(deque.toArray())); + + /* 访问元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("队首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("队尾元素 peekLast = " + peekLast); + + /* 元素入队 */ + deque.pushLast(4); + System.out.println("元素 4 队尾入队后 deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("元素 1 队首入队后 deque = " + Arrays.toString(deque.toArray())); + + /* 元素出队 */ + int popLast = deque.popLast(); + System.out.println("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("队首出队元素 = " + popFirst + ",队首出队后 deque = " + Arrays.toString(deque.toArray())); + + /* 获取双向队列的长度 */ + int size = deque.size(); + System.out.println("双向队列长度 size = " + size); + + /* 判断双向队列是否为空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("双向队列是否为空 = " + isEmpty); + } +} diff --git a/codes/java/chapter_stack_and_queue/array_queue.java b/codes/java/chapter_stack_and_queue/array_queue.java index 5a1ab645db..ab2ff32eec 100644 --- a/codes/java/chapter_stack_and_queue/array_queue.java +++ b/codes/java/chapter_stack_and_queue/array_queue.java @@ -1,7 +1,7 @@ /** * File: array_queue.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; @@ -10,13 +10,13 @@ /* 基于环形数组实现的队列 */ class ArrayQueue { - private int[] nums; // 用于存储队列元素的数组 - private int front = 0; // 头指针,指向队首 - private int rear = 0; // 尾指针,指向队尾 + 1 + private int[] nums; // 用于存储队列元素的数组 + private int front; // 队首指针,指向队首元素 + private int queSize; // 队列长度 public ArrayQueue(int capacity) { - // 初始化数组 nums = new int[capacity]; + front = queSize = 0; } /* 获取队列的容量 */ @@ -26,51 +26,50 @@ public int capacity() { /* 获取队列的长度 */ public int size() { - int capacity = capacity(); - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity + rear - front) % capacity; + return queSize; } /* 判断队列是否为空 */ public boolean isEmpty() { - return rear - front == 0; + return queSize == 0; } /* 入队 */ - public void offer(int num) { - if (size() == capacity()) { + public void push(int num) { + if (queSize == capacity()) { System.out.println("队列已满"); return; } - // 尾结点后添加 num + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % capacity(); + // 将 num 添加至队尾 nums[rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity(); + queSize++; } /* 出队 */ - public int poll() { + public int pop() { int num = peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 front = (front + 1) % capacity(); + queSize--; return num; } /* 访问队首元素 */ public int peek() { if (isEmpty()) - throw new EmptyStackException(); + throw new IndexOutOfBoundsException(); return nums[front]; } /* 返回数组 */ public int[] toArray() { - int size = size(); - int capacity = capacity(); // 仅转换有效长度范围内的列表元素 - int[] res = new int[size]; - for (int i = 0, j = front; i < size; i++, j++) { - res[i] = nums[j % capacity]; + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % capacity()]; } return res; } @@ -83,11 +82,11 @@ public static void main(String[] args) { ArrayQueue queue = new ArrayQueue(capacity); /* 元素入队 */ - queue.offer(1); - queue.offer(3); - queue.offer(2); - queue.offer(5); - queue.offer(4); + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); System.out.println("队列 queue = " + Arrays.toString(queue.toArray())); /* 访问队首元素 */ @@ -95,8 +94,8 @@ public static void main(String[] args) { System.out.println("队首元素 peek = " + peek); /* 元素出队 */ - int poll = queue.poll(); - System.out.println("出队元素 poll = " + poll + ",出队后 queue = " + Arrays.toString(queue.toArray())); + int pop = queue.pop(); + System.out.println("出队元素 pop = " + pop + ",出队后 queue = " + Arrays.toString(queue.toArray())); /* 获取队列的长度 */ int size = queue.size(); @@ -108,8 +107,8 @@ public static void main(String[] args) { /* 测试环形数组 */ for (int i = 0; i < 10; i++) { - queue.offer(i); - queue.poll(); + queue.push(i); + queue.pop(); System.out.println("第 " + i + " 轮入队 + 出队后 queue = " + Arrays.toString(queue.toArray())); } } diff --git a/codes/java/chapter_stack_and_queue/array_stack.java b/codes/java/chapter_stack_and_queue/array_stack.java index e32b164049..dfd47a4249 100644 --- a/codes/java/chapter_stack_and_queue/array_stack.java +++ b/codes/java/chapter_stack_and_queue/array_stack.java @@ -1,7 +1,7 @@ /** * File: array_stack.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; @@ -11,6 +11,7 @@ /* 基于数组实现的栈 */ class ArrayStack { private ArrayList stack; + public ArrayStack() { // 初始化列表(动态数组) stack = new ArrayList<>(); @@ -34,14 +35,14 @@ public void push(int num) { /* 出栈 */ public int pop() { if (isEmpty()) - throw new EmptyStackException(); + throw new IndexOutOfBoundsException(); return stack.remove(size() - 1); } /* 访问栈顶元素 */ public int peek() { if (isEmpty()) - throw new EmptyStackException(); + throw new IndexOutOfBoundsException(); return stack.get(size() - 1); } diff --git a/codes/java/chapter_stack_and_queue/deque.java b/codes/java/chapter_stack_and_queue/deque.java index 1a8f196b18..0e766651c4 100644 --- a/codes/java/chapter_stack_and_queue/deque.java +++ b/codes/java/chapter_stack_and_queue/deque.java @@ -1,7 +1,7 @@ /** * File: deque.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; @@ -12,13 +12,9 @@ public class deque { public static void main(String[] args) { /* 初始化双向队列 */ Deque deque = new LinkedList<>(); - - /* 元素入队 */ + deque.offerLast(3); deque.offerLast(2); deque.offerLast(5); - deque.offerLast(4); - deque.offerFirst(3); - deque.offerFirst(1); System.out.println("双向队列 deque = " + deque); /* 访问元素 */ @@ -27,11 +23,17 @@ public static void main(String[] args) { int peekLast = deque.peekLast(); System.out.println("队尾元素 peekLast = " + peekLast); + /* 元素入队 */ + deque.offerLast(4); + System.out.println("元素 4 队尾入队后 deque = " + deque); + deque.offerFirst(1); + System.out.println("元素 1 队首入队后 deque = " + deque); + /* 元素出队 */ - int pollFirst = deque.pollFirst(); - System.out.println("队首出队元素 pollFirst = " + pollFirst + ",队首出队后 deque = " + deque); - int pollLast = deque.pollLast(); - System.out.println("队尾出队元素 pollLast = " + pollLast + ",队尾出队后 deque = " + deque); + int popLast = deque.pollLast(); + System.out.println("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + deque); + int popFirst = deque.pollFirst(); + System.out.println("队首出队元素 = " + popFirst + ",队首出队后 deque = " + deque); /* 获取双向队列的长度 */ int size = deque.size(); diff --git a/codes/java/chapter_stack_and_queue/linkedlist_deque.java b/codes/java/chapter_stack_and_queue/linkedlist_deque.java new file mode 100644 index 0000000000..41a1bfbd30 --- /dev/null +++ b/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -0,0 +1,175 @@ +/** + * File: linkedlist_deque.java + * Created Time: 2023-01-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 双向链表节点 */ +class ListNode { + int val; // 节点值 + ListNode next; // 后继节点引用 + ListNode prev; // 前驱节点引用 + + ListNode(int val) { + this.val = val; + prev = next = null; + } +} + +/* 基于双向链表实现的双向队列 */ +class LinkedListDeque { + private ListNode front, rear; // 头节点 front ,尾节点 rear + private int queSize = 0; // 双向队列的长度 + + public LinkedListDeque() { + front = rear = null; + } + + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + private void push(int num, boolean isFront) { + ListNode node = new ListNode(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front.prev = node; + node.next = front; + front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + rear.next = node; + node.prev = rear; + rear = node; // 更新尾节点 + } + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + public void pushFirst(int num) { + push(num, true); + } + + /* 队尾入队 */ + public void pushLast(int num) { + push(num, false); + } + + /* 出队操作 */ + private int pop(boolean isFront) { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + int val; + // 队首出队操作 + if (isFront) { + val = front.val; // 暂存头节点值 + // 删除头节点 + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = rear.val; // 暂存尾节点值 + // 删除尾节点 + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + rear = rPrev; // 更新尾节点 + } + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + public int popFirst() { + return pop(true); + } + + /* 队尾出队 */ + public int popLast() { + return pop(false); + } + + /* 访问队首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* 访问队尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return rear.val; + } + + /* 返回数组用于打印 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_deque { + public static void main(String[] args) { + /* 初始化双向队列 */ + LinkedListDeque deque = new LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("双向队列 deque = " + Arrays.toString(deque.toArray())); + + /* 访问元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("队首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("队尾元素 peekLast = " + peekLast); + + /* 元素入队 */ + deque.pushLast(4); + System.out.println("元素 4 队尾入队后 deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("元素 1 队首入队后 deque = " + Arrays.toString(deque.toArray())); + + /* 元素出队 */ + int popLast = deque.popLast(); + System.out.println("队尾出队元素 = " + popLast + ",队尾出队后 deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("队首出队元素 = " + popFirst + ",队首出队后 deque = " + Arrays.toString(deque.toArray())); + + /* 获取双向队列的长度 */ + int size = deque.size(); + System.out.println("双向队列长度 size = " + size); + + /* 判断双向队列是否为空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("双向队列是否为空 = " + isEmpty); + } +} diff --git a/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/codes/java/chapter_stack_and_queue/linkedlist_queue.java index 82e2571c2f..bf031d0f13 100644 --- a/codes/java/chapter_stack_and_queue/linkedlist_queue.java +++ b/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -1,17 +1,16 @@ /** * File: linkedlist_queue.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; import java.util.*; -import include.*; /* 基于链表实现的队列 */ class LinkedListQueue { - private ListNode front, rear; // 头结点 front ,尾结点 rear + private ListNode front, rear; // 头节点 front ,尾节点 rear private int queSize = 0; public LinkedListQueue() { @@ -30,14 +29,14 @@ public boolean isEmpty() { } /* 入队 */ - public void offer(int num) { - // 尾结点后添加 num + public void push(int num) { + // 在尾节点后添加 num ListNode node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 + // 如果队列为空,则令头、尾节点都指向该节点 if (front == null) { front = node; rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 + // 如果队列不为空,则将该节点添加到尾节点后 } else { rear.next = node; rear = node; @@ -46,9 +45,9 @@ public void offer(int num) { } /* 出队 */ - public int poll() { + public int pop() { int num = peek(); - // 删除头结点 + // 删除头节点 front = front.next; queSize--; return num; @@ -56,8 +55,8 @@ public int poll() { /* 访问队首元素 */ public int peek() { - if (size() == 0) - throw new EmptyStackException(); + if (isEmpty()) + throw new IndexOutOfBoundsException(); return front.val; } @@ -79,11 +78,11 @@ public static void main(String[] args) { LinkedListQueue queue = new LinkedListQueue(); /* 元素入队 */ - queue.offer(1); - queue.offer(3); - queue.offer(2); - queue.offer(5); - queue.offer(4); + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); System.out.println("队列 queue = " + Arrays.toString(queue.toArray())); /* 访问队首元素 */ @@ -91,8 +90,8 @@ public static void main(String[] args) { System.out.println("队首元素 peek = " + peek); /* 元素出队 */ - int poll = queue.poll(); - System.out.println("出队元素 poll = " + poll + ",出队后 queue = " + Arrays.toString(queue.toArray())); + int pop = queue.pop(); + System.out.println("出队元素 pop = " + pop + ",出队后 queue = " + Arrays.toString(queue.toArray())); /* 获取队列的长度 */ int size = queue.size(); diff --git a/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/codes/java/chapter_stack_and_queue/linkedlist_stack.java index b1a78aa4aa..32bcd5d3ca 100644 --- a/codes/java/chapter_stack_and_queue/linkedlist_stack.java +++ b/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -1,19 +1,19 @@ /** * File: linkedlist_stack.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; import java.util.*; -import include.*; +import utils.*; /* 基于链表实现的栈 */ class LinkedListStack { - private ListNode stackPeek; // 将头结点作为栈顶 - private int stkSize = 0; // 栈的长度 - + private ListNode stackPeek; // 将头节点作为栈顶 + private int stkSize = 0; // 栈的长度 + public LinkedListStack() { stackPeek = null; } @@ -46,8 +46,8 @@ public int pop() { /* 访问栈顶元素 */ public int peek() { - if (size() == 0) - throw new EmptyStackException(); + if (isEmpty()) + throw new IndexOutOfBoundsException(); return stackPeek.val; } diff --git a/codes/java/chapter_stack_and_queue/queue.java b/codes/java/chapter_stack_and_queue/queue.java index 25457fbdb0..1464040d84 100644 --- a/codes/java/chapter_stack_and_queue/queue.java +++ b/codes/java/chapter_stack_and_queue/queue.java @@ -1,7 +1,7 @@ /** * File: queue.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; @@ -26,8 +26,8 @@ public static void main(String[] args) { System.out.println("队首元素 peek = " + peek); /* 元素出队 */ - int poll = queue.poll(); - System.out.println("出队元素 poll = " + poll + ",出队后 queue = " + queue); + int pop = queue.poll(); + System.out.println("出队元素 pop = " + pop + ",出队后 queue = " + queue); /* 获取队列的长度 */ int size = queue.size(); diff --git a/codes/java/chapter_stack_and_queue/stack.java b/codes/java/chapter_stack_and_queue/stack.java index 2a72592205..016157132e 100644 --- a/codes/java/chapter_stack_and_queue/stack.java +++ b/codes/java/chapter_stack_and_queue/stack.java @@ -1,7 +1,7 @@ /** * File: stack.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_stack_and_queue; @@ -11,23 +11,22 @@ public class stack { public static void main(String[] args) { /* 初始化栈 */ - // 在 Java 中,推荐将 LinkedList 当作栈来使用 - LinkedList stack = new LinkedList<>(); + Stack stack = new Stack<>(); /* 元素入栈 */ - stack.addLast(1); - stack.addLast(3); - stack.addLast(2); - stack.addLast(5); - stack.addLast(4); + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); System.out.println("栈 stack = " + stack); /* 访问栈顶元素 */ - int peek = stack.peekLast(); + int peek = stack.peek(); System.out.println("栈顶元素 peek = " + peek); /* 元素出栈 */ - int pop = stack.removeLast(); + int pop = stack.pop(); System.out.println("出栈元素 pop = " + pop + ",出栈后 stack = " + stack); /* 获取栈的长度 */ diff --git a/codes/java/chapter_tree/array_binary_tree.java b/codes/java/chapter_tree/array_binary_tree.java new file mode 100644 index 0000000000..749b11464d --- /dev/null +++ b/codes/java/chapter_tree/array_binary_tree.java @@ -0,0 +1,136 @@ +/** + * File: array_binary_tree.java + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree { + private List tree; + + /* 构造方法 */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } + + /* 列表容量 */ + public int size() { + return tree.size(); + } + + /* 获取索引为 i 节点的值 */ + public Integer val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } + + /* 获取索引为 i 节点的左子节点的索引 */ + public Integer left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + public Integer right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + public Integer parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + public List levelOrder() { + List res = new ArrayList<>(); + // 直接遍历数组 + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + return res; + } + + /* 深度优先遍历 */ + private void dfs(Integer i, String order, List res) { + // 若为空位,则返回 + if (val(i) == null) + return; + // 前序遍历 + if ("pre".equals(order)) + res.add(val(i)); + dfs(left(i), order, res); + // 中序遍历 + if ("in".equals(order)) + res.add(val(i)); + dfs(right(i), order, res); + // 后序遍历 + if ("post".equals(order)) + res.add(val(i)); + } + + /* 前序遍历 */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } + + /* 中序遍历 */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } + + /* 后序遍历 */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } +} + +public class array_binary_tree { + public static void main(String[] args) { + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + List arr = Arrays.asList(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15); + + TreeNode root = TreeNode.listToTree(arr); + System.out.println("\n初始化二叉树\n"); + System.out.println("二叉树的数组表示:"); + System.out.println(arr); + System.out.println("二叉树的链表表示:"); + PrintUtil.printTree(root); + + // 数组表示下的二叉树类 + ArrayBinaryTree abt = new ArrayBinaryTree(arr); + + // 访问节点 + int i = 1; + Integer l = abt.left(i); + Integer r = abt.right(i); + Integer p = abt.parent(i); + System.out.println("\n当前节点的索引为 " + i + " ,值为 " + abt.val(i)); + System.out.println("其左子节点的索引为 " + l + " ,值为 " + (l == null ? "null" : abt.val(l))); + System.out.println("其右子节点的索引为 " + r + " ,值为 " + (r == null ? "null" : abt.val(r))); + System.out.println("其父节点的索引为 " + p + " ,值为 " + (p == null ? "null" : abt.val(p))); + + // 遍历树 + List res = abt.levelOrder(); + System.out.println("\n层序遍历为:" + res); + res = abt.preOrder(); + System.out.println("前序遍历为:" + res); + res = abt.inOrder(); + System.out.println("中序遍历为:" + res); + res = abt.postOrder(); + System.out.println("后序遍历为:" + res); + } +} diff --git a/codes/java/chapter_tree/avl_tree.java b/codes/java/chapter_tree/avl_tree.java index 9120262f44..095c9f5327 100644 --- a/codes/java/chapter_tree/avl_tree.java +++ b/codes/java/chapter_tree/avl_tree.java @@ -1,34 +1,35 @@ /** * File: avl_tree.java * Created Time: 2022-12-10 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_tree; -import include.*; +import utils.*; -// Tree class +/* AVL 树 */ class AVLTree { TreeNode root; // 根节点 - /* 获取结点高度 */ + /* 获取节点高度 */ public int height(TreeNode node) { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 return node == null ? -1 : node.height; } - /* 更新结点高度 */ + /* 更新节点高度 */ private void updateHeight(TreeNode node) { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node.height = Math.max(height(node.left), height(node.right)) + 1; } /* 获取平衡因子 */ public int balanceFactor(TreeNode node) { - // 空结点平衡因子为 0 - if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 空节点平衡因子为 0 + if (node == null) + return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node.left) - height(node.right); } @@ -39,7 +40,7 @@ private TreeNode rightRotate(TreeNode node) { // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); // 返回旋转后子树的根节点 @@ -53,7 +54,7 @@ private TreeNode leftRotate(TreeNode node) { // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); // 返回旋转后子树的根节点 @@ -62,7 +63,7 @@ private TreeNode leftRotate(TreeNode node) { /* 执行旋转操作,使该子树重新恢复平衡 */ private TreeNode rotate(TreeNode node) { - // 获取结点 node 的平衡因子 + // 获取节点 node 的平衡因子 int balanceFactor = balanceFactor(node); // 左偏树 if (balanceFactor > 1) { @@ -86,43 +87,43 @@ private TreeNode rotate(TreeNode node) { return leftRotate(node); } } - // 平衡树,无需旋转,直接返回 + // 平衡树,无须旋转,直接返回 return node; } - /* 插入结点 */ - public TreeNode insert(int val) { + /* 插入节点 */ + public void insert(int val) { root = insertHelper(root, val); - return root; } - /* 递归插入结点(辅助函数) */ + /* 递归插入节点(辅助方法) */ private TreeNode insertHelper(TreeNode node, int val) { - if (node == null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ + if (node == null) + return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else - return node; // 重复结点不插入,直接返回 - updateHeight(node); // 更新结点高度 + return node; // 重复节点不插入,直接返回 + updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node); // 返回子树的根节点 return node; } - /* 删除结点 */ - public TreeNode remove(int val) { + /* 删除节点 */ + public void remove(int val) { root = removeHelper(root, val); - return root; } - /* 递归删除结点(辅助函数) */ + /* 递归删除节点(辅助方法) */ private TreeNode removeHelper(TreeNode node, int val) { - if (node == null) return null; - /* 1. 查找结点,并删除之 */ + if (node == null) + return null; + /* 1. 查找节点并删除 */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) @@ -130,52 +131,45 @@ else if (val > node.val) else { if (node.left == null || node.right == null) { TreeNode child = node.left != null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 + // 子节点数量 = 0 ,直接删除 node 并返回 if (child == null) return null; - // 子结点数量 = 1 ,直接删除 node + // 子节点数量 = 1 ,直接删除 node else node = child; } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode temp = getInOrderNext(node.right); + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode temp = node.right; + while (temp.left != null) { + temp = temp.left; + } node.right = removeHelper(node.right, temp.val); node.val = temp.val; } } - updateHeight(node); // 更新结点高度 + updateHeight(node); // 更新节点高度 /* 2. 执行旋转操作,使该子树重新恢复平衡 */ node = rotate(node); // 返回子树的根节点 return node; } - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - private TreeNode getInOrderNext(TreeNode node) { - if (node == null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left != null) { - node = node.left; - } - return node; - } - - /* 查找结点 */ + /* 查找节点 */ public TreeNode search(int val) { TreeNode cur = root; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 目标结点在 root 的右子树中 + // 目标节点在 cur 的右子树中 if (cur.val < val) cur = cur.right; - // 目标结点在 root 的左子树中 + // 目标节点在 cur 的左子树中 else if (cur.val > val) cur = cur.left; - // 找到目标结点,跳出循环 + // 找到目标节点,跳出循环 else break; } - // 返回目标结点 + // 返回目标节点 return cur; } } @@ -183,13 +177,13 @@ else if (cur.val > val) public class avl_tree { static void testInsert(AVLTree tree, int val) { tree.insert(val); - System.out.println("\n插入结点 " + val + " 后,AVL 树为"); + System.out.println("\n插入节点 " + val + " 后,AVL 树为"); PrintUtil.printTree(tree.root); } static void testRemove(AVLTree tree, int val) { tree.remove(val); - System.out.println("\n删除结点 " + val + " 后,AVL 树为"); + System.out.println("\n删除节点 " + val + " 后,AVL 树为"); PrintUtil.printTree(tree.root); } @@ -197,8 +191,8 @@ public static void main(String[] args) { /* 初始化空 AVL 树 */ AVLTree avlTree = new AVLTree(); - /* 插入结点 */ - // 请关注插入结点后,AVL 树是如何保持平衡的 + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); @@ -210,17 +204,17 @@ public static void main(String[] args) { testInsert(avlTree, 10); testInsert(avlTree, 6); - /* 插入重复结点 */ + /* 插入重复节点 */ testInsert(avlTree, 7); - /* 删除结点 */ - // 请关注删除结点后,AVL 树是如何保持平衡的 - testRemove(avlTree, 8); // 删除度为 0 的结点 - testRemove(avlTree, 5); // 删除度为 1 的结点 - testRemove(avlTree, 4); // 删除度为 2 的结点 + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(avlTree, 8); // 删除度为 0 的节点 + testRemove(avlTree, 5); // 删除度为 1 的节点 + testRemove(avlTree, 4); // 删除度为 2 的节点 - /* 查询结点 */ + /* 查询节点 */ TreeNode node = avlTree.search(7); - System.out.println("\n查找到的结点对象为 " + node + ",结点值 = " + node.val); + System.out.println("\n查找到的节点对象为 " + node + ",节点值 = " + node.val); } } diff --git a/codes/java/chapter_tree/binary_search_tree.java b/codes/java/chapter_tree/binary_search_tree.java index a3b0f75844..238929a934 100644 --- a/codes/java/chapter_tree/binary_search_tree.java +++ b/codes/java/chapter_tree/binary_search_tree.java @@ -1,153 +1,158 @@ /** * File: binary_search_tree.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_tree; -import java.util.*; -import include.*; +import utils.*; /* 二叉搜索树 */ class BinarySearchTree { private TreeNode root; - public BinarySearchTree(int[] nums) { - Arrays.sort(nums); // 排序数组 - root = buildTree(nums, 0, nums.length - 1); // 构建二叉搜索树 + /* 构造方法 */ + public BinarySearchTree() { + // 初始化空树 + root = null; } - /* 获取二叉树根结点 */ + /* 获取二叉树根节点 */ public TreeNode getRoot() { return root; } - /* 构建二叉搜索树 */ - public TreeNode buildTree(int[] nums, int i, int j) { - if (i > j) return null; - // 将数组中间结点作为根结点 - int mid = (i + j) / 2; - TreeNode root = new TreeNode(nums[mid]); - // 递归建立左子树和右子树 - root.left = buildTree(nums, i, mid - 1); - root.right = buildTree(nums, mid + 1, j); - return root; - } - - /* 查找结点 */ + /* 查找节点 */ public TreeNode search(int num) { TreeNode cur = root; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 目标结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 root 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; + // 目标节点在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) + cur = cur.left; + // 找到目标节点,跳出循环 + else + break; } - // 返回目标结点 + // 返回目标节点 return cur; } - /* 插入结点 */ - public TreeNode insert(int num) { - // 若树为空,直接提前返回 - if (root == null) return null; + /* 插入节点 */ + public void insert(int num) { + // 若树为空,则初始化根节点 + if (root == null) { + root = new TreeNode(num); + return; + } TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到重复结点,直接返回 - if (cur.val == num) return null; + // 找到重复节点,直接返回 + if (cur.val == num) + return; pre = cur; - // 插入位置在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 root 的左子树中 - else cur = cur.left; + // 插入位置在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; } - // 插入结点 val + // 插入节点 TreeNode node = new TreeNode(num); - if (pre.val < num) pre.right = node; - else pre.left = node; - return node; + if (pre.val < num) + pre.right = node; + else + pre.left = node; } - /* 删除结点 */ - public TreeNode remove(int num) { + /* 删除节点 */ + public void remove(int num) { // 若树为空,直接提前返回 - if (root == null) return null; + if (root == null) + return; TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到待删除结点,跳出循环 - if (cur.val == num) break; + // 找到待删除节点,跳出循环 + if (cur.val == num) + break; pre = cur; - // 待删除结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 root 的左子树中 - else cur = cur.left; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 待删除节点在 cur 的左子树中 + else + cur = cur.left; } - // 若无待删除结点,则直接返回 - if (cur == null) return null; - // 子结点数量 = 0 or 1 + // 若无待删除节点,则直接返回 + if (cur == null) + return; + // 子节点数量 = 0 or 1 if (cur.left == null || cur.right == null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 TreeNode child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left == cur) pre.left = child; - else pre.right = child; + // 删除节点 cur + if (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 - TreeNode nex = getInOrderNext(cur.right); - int tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; + // 获取中序遍历中 cur 的下一个节点 + TreeNode tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; } - return cur; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - public TreeNode getInOrderNext(TreeNode root) { - if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left != null) { - root = root.left; - } - return root; } } public class binary_search_tree { public static void main(String[] args) { /* 初始化二叉搜索树 */ - int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - BinarySearchTree bst = new BinarySearchTree(nums); + BinarySearchTree bst = new BinarySearchTree(); + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + int[] nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }; + for (int num : nums) { + bst.insert(num); + } System.out.println("\n初始化的二叉树为\n"); PrintUtil.printTree(bst.getRoot()); - /* 查找结点 */ + /* 查找节点 */ TreeNode node = bst.search(7); - System.out.println("\n查找到的结点对象为 " + node + ",结点值 = " + node.val); + System.out.println("\n查找到的节点对象为 " + node + ",节点值 = " + node.val); - /* 插入结点 */ - node = bst.insert(16); - System.out.println("\n插入结点 16 后,二叉树为\n"); + /* 插入节点 */ + bst.insert(16); + System.out.println("\n插入节点 16 后,二叉树为\n"); PrintUtil.printTree(bst.getRoot()); - /* 删除结点 */ + /* 删除节点 */ bst.remove(1); - System.out.println("\n删除结点 1 后,二叉树为\n"); + System.out.println("\n删除节点 1 后,二叉树为\n"); PrintUtil.printTree(bst.getRoot()); bst.remove(2); - System.out.println("\n删除结点 2 后,二叉树为\n"); + System.out.println("\n删除节点 2 后,二叉树为\n"); PrintUtil.printTree(bst.getRoot()); bst.remove(4); - System.out.println("\n删除结点 4 后,二叉树为\n"); + System.out.println("\n删除节点 4 后,二叉树为\n"); PrintUtil.printTree(bst.getRoot()); } } diff --git a/codes/java/chapter_tree/binary_tree.java b/codes/java/chapter_tree/binary_tree.java index 65a511e8c4..47f4b29360 100644 --- a/codes/java/chapter_tree/binary_tree.java +++ b/codes/java/chapter_tree/binary_tree.java @@ -1,23 +1,23 @@ /** * File: binary_tree.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_tree; -import include.*; +import utils.*; public class binary_tree { public static void main(String[] args) { /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); - // 构建引用指向(即指针) + // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; @@ -25,16 +25,16 @@ public static void main(String[] args) { System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(n1); - /* 插入与删除结点 */ + /* 插入与删除节点 */ TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - System.out.println("\n插入结点 P 后\n"); + System.out.println("\n插入节点 P 后\n"); PrintUtil.printTree(n1); - // 删除结点 P + // 删除节点 P n1.left = n2; - System.out.println("\n删除结点 P 后\n"); + System.out.println("\n删除节点 P 后\n"); PrintUtil.printTree(n1); } } diff --git a/codes/java/chapter_tree/binary_tree_bfs.java b/codes/java/chapter_tree/binary_tree_bfs.java index ffeb352e04..0156438c00 100644 --- a/codes/java/chapter_tree/binary_tree_bfs.java +++ b/codes/java/chapter_tree/binary_tree_bfs.java @@ -1,28 +1,29 @@ /** * File: binary_tree_bfs.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_tree; -import include.*; +import utils.*; import java.util.*; public class binary_tree_bfs { /* 层序遍历 */ - static List hierOrder(TreeNode root) { - // 初始化队列,加入根结点 - Queue queue = new LinkedList<>() {{ add(root); }}; + static List levelOrder(TreeNode root) { + // 初始化队列,加入根节点 + Queue queue = new LinkedList<>(); + queue.add(root); // 初始化一个列表,用于保存遍历序列 List list = new ArrayList<>(); while (!queue.isEmpty()) { - TreeNode node = queue.poll(); // 队列出队 - list.add(node.val); // 保存结点 + TreeNode node = queue.poll(); // 队列出队 + list.add(node.val); // 保存节点值 if (node.left != null) - queue.offer(node.left); // 左子结点入队 + queue.offer(node.left); // 左子节点入队 if (node.right != null) - queue.offer(node.right); // 右子结点入队 + queue.offer(node.right); // 右子节点入队 } return list; } @@ -35,7 +36,7 @@ public static void main(String[] args) { PrintUtil.printTree(root); /* 层序遍历 */ - List list = hierOrder(root); - System.out.println("\n层序遍历的结点打印序列 = " + list); + List list = levelOrder(root); + System.out.println("\n层序遍历的节点打印序列 = " + list); } } diff --git a/codes/java/chapter_tree/binary_tree_dfs.java b/codes/java/chapter_tree/binary_tree_dfs.java index 9f2d0507dc..51f0ae7b1b 100644 --- a/codes/java/chapter_tree/binary_tree_dfs.java +++ b/codes/java/chapter_tree/binary_tree_dfs.java @@ -1,12 +1,12 @@ /** * File: binary_tree_dfs.java * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) + * Author: krahets (krahets@163.com) */ package chapter_tree; -import include.*; +import utils.*; import java.util.*; public class binary_tree_dfs { @@ -15,8 +15,9 @@ public class binary_tree_dfs { /* 前序遍历 */ static void preOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 + if (root == null) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 list.add(root.val); preOrder(root.left); preOrder(root.right); @@ -24,8 +25,9 @@ static void preOrder(TreeNode root) { /* 中序遍历 */ static void inOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 + if (root == null) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root.left); list.add(root.val); inOrder(root.right); @@ -33,8 +35,9 @@ static void inOrder(TreeNode root) { /* 后序遍历 */ static void postOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 + if (root == null) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root.left); postOrder(root.right); list.add(root.val); @@ -50,16 +53,16 @@ public static void main(String[] args) { /* 前序遍历 */ list.clear(); preOrder(root); - System.out.println("\n前序遍历的结点打印序列 = " + list); + System.out.println("\n前序遍历的节点打印序列 = " + list); /* 中序遍历 */ list.clear(); inOrder(root); - System.out.println("\n中序遍历的结点打印序列 = " + list); + System.out.println("\n中序遍历的节点打印序列 = " + list); /* 后序遍历 */ list.clear(); postOrder(root); - System.out.println("\n后序遍历的结点打印序列 = " + list); + System.out.println("\n后序遍历的节点打印序列 = " + list); } } diff --git a/codes/java/include/ListNode.java b/codes/java/include/ListNode.java deleted file mode 100755 index 60590100ab..0000000000 --- a/codes/java/include/ListNode.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * File: ListNode.java - * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) - */ - -package include; - -/** - * Definition for a singly-linked list node - */ -public class ListNode { - public int val; - public ListNode next; - - public ListNode(int x) { - val = x; - } - - /** - * Generate a linked list with an array - * @param arr - * @return - */ - public static ListNode arrToLinkedList(int[] arr) { - ListNode dum = new ListNode(0); - ListNode head = dum; - for (int val : arr) { - head.next = new ListNode(val); - head = head.next; - } - return dum.next; - } - - /** - * Get a list node with specific value from a linked list - * @param head - * @param val - * @return - */ - public static ListNode getListNode(ListNode head, int val) { - while (head != null && head.val != val) { - head = head.next; - } - return head; - } -} diff --git a/codes/java/include/PrintUtil.java b/codes/java/include/PrintUtil.java deleted file mode 100755 index 1ebc45ba0f..0000000000 --- a/codes/java/include/PrintUtil.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * File: PrintUtil.java - * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) - */ - -package include; - -import java.util.*; - - -class Trunk { - Trunk prev; - String str; - - Trunk(Trunk prev, String str) { - this.prev = prev; - this.str = str; - } -}; - -public class PrintUtil { - /** - * Print a linked list - * @param head - */ - public static void printLinkedList(ListNode head) { - List list = new ArrayList<>(); - while (head != null) { - list.add(String.valueOf(head.val)); - head = head.next; - } - System.out.println(String.join(" -> ", list)); - } - - /** - * The interface of the tree printer - * This tree printer is borrowed from TECHIE DELIGHT - * https://www.techiedelight.com/c-program-print-binary-tree/ - * @param root - */ - public static void printTree(TreeNode root) { - printTree(root, null, false); - } - - /** - * Print a binary tree - * @param root - * @param prev - * @param isLeft - */ - public static void printTree(TreeNode root, Trunk prev, boolean isLeft) { - if (root == null) { - return; - } - - String prev_str = " "; - Trunk trunk = new Trunk(prev, prev_str); - - printTree(root.right, trunk, true); - - if (prev == null) { - trunk.str = "———"; - } else if (isLeft) { - trunk.str = "/———"; - prev_str = " |"; - } else { - trunk.str = "\\———"; - prev.str = prev_str; - } - - showTrunks(trunk); - System.out.println(" " + root.val); - - if (prev != null) { - prev.str = prev_str; - } - trunk.str = " |"; - - printTree(root.left, trunk, false); - } - - /** - * Helper function to print branches of the binary tree - * @param p - */ - public static void showTrunks(Trunk p) { - if (p == null) { - return; - } - - showTrunks(p.prev); - System.out.print(p.str); - } - - /** - * Print a hash map - * @param - * @param - * @param map - */ - public static void printHashMap(Map map) { - for (Map.Entry kv: map.entrySet()) { - System.out.println(kv.getKey() + " -> " + kv.getValue()); - } - } - - /** - * Print a heap (PriorityQueue) - * @param queue - */ - public static void printHeap(Queue queue) { - List list = new ArrayList<>(queue); - System.out.print("堆的数组表示:"); - System.out.println(list); - System.out.println("堆的树状表示:"); - TreeNode root = TreeNode.listToTree(list); - printTree(root); - } -} diff --git a/codes/java/include/TreeNode.java b/codes/java/include/TreeNode.java deleted file mode 100644 index 22a349747a..0000000000 --- a/codes/java/include/TreeNode.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * File: TreeNode.java - * Created Time: 2022-11-25 - * Author: Krahets (krahets@163.com) - */ - -package include; - -import java.util.*; - -/** - * Definition for a binary tree node. - */ -public class TreeNode { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode left; // 左子结点引用 - public TreeNode right; // 右子结点引用 - - public TreeNode(int x) { - val = x; - } - - /** - * Generate a binary tree given an array - * @param list - * @return - */ - public static TreeNode listToTree(List list) { - int size = list.size(); - if (size == 0) - return null; - - TreeNode root = new TreeNode(list.get(0)); - Queue queue = new LinkedList<>() {{ add(root); }}; - int i = 0; - while(!queue.isEmpty()) { - TreeNode node = queue.poll(); - if (++i >= size) break; - if (list.get(i) != null) { - node.left = new TreeNode(list.get(i)); - queue.add(node.left); - } - if (++i >= size) break; - if (list.get(i) != null) { - node.right = new TreeNode(list.get(i)); - queue.add(node.right); - } - } - return root; - } - - /** - * Serialize a binary tree to a list - * @param root - * @return - */ - public static List treeToList(TreeNode root) { - List list = new ArrayList<>(); - if(root == null) return list; - Queue queue = new LinkedList<>() {{ add(root); }}; - while(!queue.isEmpty()) { - TreeNode node = queue.poll(); - if(node != null) { - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - else { - list.add(null); - } - } - return list; - } -} diff --git a/codes/java/utils/ListNode.java b/codes/java/utils/ListNode.java new file mode 100755 index 0000000000..8e1d4d1279 --- /dev/null +++ b/codes/java/utils/ListNode.java @@ -0,0 +1,28 @@ +/** + * File: ListNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +/* 链表节点 */ +public class ListNode { + public int val; + public ListNode next; + + public ListNode(int x) { + val = x; + } + + /* 将列表反序列化为链表 */ + public static ListNode arrToLinkedList(int[] arr) { + ListNode dum = new ListNode(0); + ListNode head = dum; + for (int val : arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } +} diff --git a/codes/java/utils/PrintUtil.java b/codes/java/utils/PrintUtil.java new file mode 100755 index 0000000000..e8ff8ff524 --- /dev/null +++ b/codes/java/utils/PrintUtil.java @@ -0,0 +1,116 @@ +/** + * File: PrintUtil.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +class Trunk { + Trunk prev; + String str; + + Trunk(Trunk prev, String str) { + this.prev = prev; + this.str = str; + } +}; + +public class PrintUtil { + /* 打印矩阵(Array) */ + public static void printMatrix(T[][] matrix) { + System.out.println("["); + for (T[] row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 打印矩阵(List) */ + public static void printMatrix(List> matrix) { + System.out.println("["); + for (List row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 打印链表 */ + public static void printLinkedList(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + list.add(String.valueOf(head.val)); + head = head.next; + } + System.out.println(String.join(" -> ", list)); + } + + /* 打印二叉树 */ + public static void printTree(TreeNode root) { + printTree(root, null, false); + } + + /** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void printTree(TreeNode root, Trunk prev, boolean isRight) { + if (root == null) { + return; + } + + String prev_str = " "; + Trunk trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + showTrunks(trunk); + System.out.println(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + printTree(root.left, trunk, false); + } + + public static void showTrunks(Trunk p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + System.out.print(p.str); + } + + /* 打印哈希表 */ + public static void printHashMap(Map map) { + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + } + + /* 打印堆(优先队列) */ + public static void printHeap(Queue queue) { + List list = new ArrayList<>(queue); + System.out.print("堆的数组表示:"); + System.out.println(list); + System.out.println("堆的树状表示:"); + TreeNode root = TreeNode.listToTree(list); + printTree(root); + } +} diff --git a/codes/java/utils/TreeNode.java b/codes/java/utils/TreeNode.java new file mode 100644 index 0000000000..ff5d46c2de --- /dev/null +++ b/codes/java/utils/TreeNode.java @@ -0,0 +1,73 @@ +/** + * File: TreeNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 二叉树节点类 */ +public class TreeNode { + public int val; // 节点值 + public int height; // 节点高度 + public TreeNode left; // 左子节点引用 + public TreeNode right; // 右子节点引用 + + /* 构造方法 */ + public TreeNode(int x) { + val = x; + } + + // 序列化编码规则请参考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二叉树的数组表示: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二叉树的链表表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 将列表反序列化为二叉树:递归 */ + private static TreeNode listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.size() || arr.get(i) == null) { + return null; + } + TreeNode root = new TreeNode(arr.get(i)); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; + } + + /* 将列表反序列化为二叉树 */ + public static TreeNode listToTree(List arr) { + return listToTreeDFS(arr, 0); + } + + /* 将二叉树序列化为列表:递归 */ + private static void treeToListDFS(TreeNode root, int i, List res) { + if (root == null) + return; + while (i >= res.size()) { + res.add(null); + } + res.set(i, root.val); + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); + } + + /* 将二叉树序列化为列表 */ + public static List treeToList(TreeNode root) { + List res = new ArrayList<>(); + treeToListDFS(root, 0, res); + return res; + } +} diff --git a/codes/java/utils/Vertex.java b/codes/java/utils/Vertex.java new file mode 100644 index 0000000000..35c161fb7d --- /dev/null +++ b/codes/java/utils/Vertex.java @@ -0,0 +1,36 @@ +/** + * File: Vertex.java + * Created Time: 2023-02-15 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 顶点类 */ +public class Vertex { + public int val; + + public Vertex(int val) { + this.val = val; + } + + /* 输入值列表 vals ,返回顶点列表 vets */ + public static Vertex[] valsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.length]; + for (int i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 输入顶点列表 vets ,返回值列表 vals */ + public static List vetsToVals(List vets) { + List vals = new ArrayList<>(); + for (Vertex vet : vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/codes/javascript/.prettierrc b/codes/javascript/.prettierrc new file mode 100644 index 0000000000..3f4aa8cb65 --- /dev/null +++ b/codes/javascript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/codes/javascript/chapter_array_and_linkedlist/array.js b/codes/javascript/chapter_array_and_linkedlist/array.js index 191b6f3fab..abd3dc791d 100644 --- a/codes/javascript/chapter_array_and_linkedlist/array.js +++ b/codes/javascript/chapter_array_and_linkedlist/array.js @@ -15,7 +15,7 @@ function randomAccess(nums) { /* 扩展数组长度 */ // 请注意,JavaScript 的 Array 是动态数组,可以直接扩展 -// 为了方便学习,本函数将 Array 看作是长度不可变的数组 +// 为了方便学习,本函数将 Array 看作长度不可变的数组 function extend(nums, enlarge) { // 初始化一个扩展长度后的数组 const res = new Array(nums.length + enlarge).fill(0); @@ -33,11 +33,11 @@ function insert(nums, num, index) { for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num; } -/* 删除索引 index 处元素 */ +/* 删除索引 index 处的元素 */ function remove(nums, index) { // 把索引 index 之后的所有元素向前移动一位 for (let i = index; i < nums.length - 1; i++) { @@ -50,48 +50,48 @@ function traverse(nums) { let count = 0; // 通过索引遍历数组 for (let i = 0; i < nums.length; i++) { - count++; + count += nums[i]; } - // 直接遍历数组 - for (let num of nums) { - count += 1; + // 直接遍历数组元素 + for (const num of nums) { + count += num; } } /* 在数组中查找指定元素 */ function find(nums, target) { for (let i = 0; i < nums.length; i++) { - if (nums[i] == target) return i; + if (nums[i] === target) return i; } return -1; } -/* Driver Codes*/ +/* Driver Code */ /* 初始化数组 */ const arr = new Array(5).fill(0); -console.log("数组 arr =", arr); +console.log('数组 arr =', arr); let nums = [1, 3, 2, 5, 4]; -console.log("数组 nums =", nums); +console.log('数组 nums =', nums); /* 随机访问 */ let random_num = randomAccess(nums); -console.log("在 nums 中获取随机元素", random_num); +console.log('在 nums 中获取随机元素', random_num); /* 长度扩展 */ nums = extend(nums, 3); -console.log("将数组长度扩展至 8 ,得到 nums =", nums); +console.log('将数组长度扩展至 8 ,得到 nums =', nums); /* 插入元素 */ insert(nums, 6, 3); -console.log("在索引 3 处插入数字 6 ,得到 nums =", nums); +console.log('在索引 3 处插入数字 6 ,得到 nums =', nums); /* 删除元素 */ remove(nums, 2); -console.log("删除索引 2 处的元素,得到 nums =", nums); +console.log('删除索引 2 处的元素,得到 nums =', nums); /* 遍历数组 */ traverse(nums); /* 查找元素 */ let index = find(nums, 3); -console.log("在 nums 中查找元素 3 ,得到索引 =", index); +console.log('在 nums 中查找元素 3 ,得到索引 =', index); diff --git a/codes/javascript/chapter_array_and_linkedlist/linked_list.js b/codes/javascript/chapter_array_and_linkedlist/linked_list.js index cf701a0c02..ea64a964a1 100644 --- a/codes/javascript/chapter_array_and_linkedlist/linked_list.js +++ b/codes/javascript/chapter_array_and_linkedlist/linked_list.js @@ -4,27 +4,26 @@ * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) */ -const PrintUtil = require("../include/PrintUtil"); -const ListNode = require("../include/ListNode"); +const { printLinkedList } = require('../modules/PrintUtil'); +const { ListNode } = require('../modules/ListNode'); -/* 在链表的结点 n0 之后插入结点 P */ +/* 在链表的节点 n0 之后插入节点 P */ function insert(n0, P) { const n1 = n0.next; - n0.next = P; P.next = n1; + n0.next = P; } -/* 删除链表的结点 n0 之后的首个结点 */ +/* 删除链表的节点 n0 之后的首个节点 */ function remove(n0) { - if (!n0.next) - return; + if (!n0.next) return; // n0 -> P -> n1 const P = n0.next; const n1 = P.next; n0.next = n1; } -/* 访问链表中索引为 index 的结点 */ +/* 访问链表中索引为 index 的节点 */ function access(head, index) { for (let i = 0; i < index; i++) { if (!head) { @@ -35,7 +34,7 @@ function access(head, index) { return head; } -/* 在链表中查找值为 target 的首个结点 */ +/* 在链表中查找值为 target 的首个节点 */ function find(head, target) { let index = 0; while (head !== null) { @@ -50,34 +49,34 @@ function find(head, target) { /* Driver Code */ /* 初始化链表 */ -// 初始化各个结点 +// 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); -// 构建引用指向 +// 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; -console.log("初始化的链表为"); -PrintUtil.printLinkedList(n0); +console.log('初始化的链表为'); +printLinkedList(n0); -/* 插入结点 */ +/* 插入节点 */ insert(n0, new ListNode(0)); -console.log("插入结点后的链表为"); -PrintUtil.printLinkedList(n0); +console.log('插入节点后的链表为'); +printLinkedList(n0); -/* 删除结点 */ +/* 删除节点 */ remove(n0); -console.log("删除结点后的链表为"); -PrintUtil.printLinkedList(n0); +console.log('删除节点后的链表为'); +printLinkedList(n0); -/* 访问结点 */ +/* 访问节点 */ const node = access(n0, 3); -console.log("链表中索引 3 处的结点的值 = " + node.val); +console.log('链表中索引 3 处的节点的值 = ' + node.val); -/* 查找结点 */ +/* 查找节点 */ const index = find(n0, 2); -console.log("链表中值为 2 的结点的索引 = " + index); +console.log('链表中值为 2 的节点的索引 = ' + index); diff --git a/codes/javascript/chapter_array_and_linkedlist/list.js b/codes/javascript/chapter_array_and_linkedlist/list.js index 593917589e..88d0fb64e3 100644 --- a/codes/javascript/chapter_array_and_linkedlist/list.js +++ b/codes/javascript/chapter_array_and_linkedlist/list.js @@ -5,54 +5,53 @@ */ /* 初始化列表 */ -const list = [1, 3, 2, 5, 4]; -console.log(`列表 list = ${list}`); +const nums = [1, 3, 2, 5, 4]; +console.log(`列表 nums = ${nums}`); /* 访问元素 */ -const num = list[1]; +const num = nums[1]; console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ -list[1] = 0; -console.log(`将索引 1 处的元素更新为 0 ,得到 list = ${list}`); +nums[1] = 0; +console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums}`); /* 清空列表 */ -list.length = 0; -console.log(`清空列表后 list = ${list}`); +nums.length = 0; +console.log(`清空列表后 nums = ${nums}`); -/* 尾部添加元素 */ -list.push(1); -list.push(3); -list.push(2); -list.push(5); -list.push(4); -console.log(`添加元素后 list = ${list}`); +/* 在尾部添加元素 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`添加元素后 nums = ${nums}`); -/* 中间插入元素 */ -list.splice(3, 0, 6); -console.log(`在索引 3 处插入数字 6 ,得到 list = ${list}`); +/* 在中间插入元素 */ +nums.splice(3, 0, 6); +console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums}`); /* 删除元素 */ -list.splice(3, 1); -console.log(`删除索引 3 处的元素,得到 list = ${list}`); +nums.splice(3, 1); +console.log(`删除索引 3 处的元素,得到 nums = ${nums}`); /* 通过索引遍历列表 */ let count = 0; -for (let i = 0; i < list.length; i++) { - count++; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; } - /* 直接遍历列表元素 */ count = 0; -for (const n of list) { - count++; +for (const x of nums) { + count += x; } /* 拼接两个列表 */ -const list1 = [6, 8, 7, 10, 9]; -list.push(...list1); -console.log(`将列表 list1 拼接到 list 之后,得到 list = ${list}`); +const nums1 = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`将列表 nums1 拼接到 nums 之后,得到 nums = ${nums}`); /* 排序列表 */ -list.sort((a, b) => a - b); -console.log(`排序列表后 list = ${list}`); +nums.sort((a, b) => a - b); +console.log(`排序列表后 nums = ${nums}`); diff --git a/codes/javascript/chapter_array_and_linkedlist/my_list.js b/codes/javascript/chapter_array_and_linkedlist/my_list.js index 388bb1f3d8..fb1f264ba5 100644 --- a/codes/javascript/chapter_array_and_linkedlist/my_list.js +++ b/codes/javascript/chapter_array_and_linkedlist/my_list.js @@ -4,19 +4,19 @@ * Author: Justin (xiefahit@gmail.com) */ -/* 列表类简易实现 */ +/* 列表类 */ class MyList { - #nums = new Array(); // 数组(存储列表元素) + #arr = new Array(); // 数组(存储列表元素) #capacity = 10; // 列表容量 - #size = 0; // 列表长度(即当前元素数量) + #size = 0; // 列表长度(当前元素数量) #extendRatio = 2; // 每次列表扩容的倍数 - /* 构造函数 */ + /* 构造方法 */ constructor() { - this.#nums = new Array(this.#capacity); + this.#arr = new Array(this.#capacity); } - /* 获取列表长度(即当前元素数量)*/ + /* 获取列表长度(当前元素数量)*/ size() { return this.#size; } @@ -28,118 +28,114 @@ class MyList { /* 访问元素 */ get(index) { - // 索引如果越界则抛出异常,下同 - if (index >= this.#size) { - throw new Error('索引越界'); - } - return this.#nums[index]; + // 索引如果越界,则抛出异常,下同 + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + return this.#arr[index]; } /* 更新元素 */ set(index, num) { - if (index >= this._size) throw new Error('索引越界'); - this.#nums[index] = num; + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + this.#arr[index] = num; } - /* 尾部添加元素 */ + /* 在尾部添加元素 */ add(num) { // 如果长度等于容量,则需要扩容 if (this.#size === this.#capacity) { this.extendCapacity(); } // 将新元素添加到列表尾部 - this.#nums[this.#size] = num; + this.#arr[this.#size] = num; this.#size++; } - /* 中间插入元素 */ + /* 在中间插入元素 */ insert(index, num) { - if (index >= this.#size) { - throw new Error('索引越界'); - } + if (index < 0 || index >= this.#size) throw new Error('索引越界'); // 元素数量超出容量时,触发扩容机制 if (this.#size === this.#capacity) { this.extendCapacity(); } // 将索引 index 以及之后的元素都向后移动一位 for (let j = this.#size - 1; j >= index; j--) { - this.#nums[j + 1] = this.#nums[j]; + this.#arr[j + 1] = this.#arr[j]; } // 更新元素数量 - this.#nums[index] = num; + this.#arr[index] = num; this.#size++; } /* 删除元素 */ remove(index) { - if (index >= this.#size) throw new Error('索引越界'); - let num = this.#nums[index]; + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + let num = this.#arr[index]; // 将索引 index 之后的元素都向前移动一位 for (let j = index; j < this.#size - 1; j++) { - this.#nums[j] = this.#nums[j + 1]; + this.#arr[j] = this.#arr[j + 1]; } // 更新元素数量 this.#size--; - // 返回被删除元素 + // 返回被删除的元素 return num; } /* 列表扩容 */ extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - this.#nums = this.#nums.concat( + // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组复制到新数组 + this.#arr = this.#arr.concat( new Array(this.capacity() * (this.#extendRatio - 1)) ); // 更新列表容量 - this.#capacity = this.#nums.length; + this.#capacity = this.#arr.length; } /* 将列表转换为数组 */ toArray() { let size = this.size(); // 仅转换有效长度范围内的列表元素 - const nums = new Array(size); + const arr = new Array(size); for (let i = 0; i < size; i++) { - nums[i] = this.get(i); + arr[i] = this.get(i); } - return nums; + return arr; } } /* Driver Code */ /* 初始化列表 */ -const list = new MyList(); -/* 尾部添加元素 */ -list.add(1); -list.add(3); -list.add(2); -list.add(5); -list.add(4); +const nums = new MyList(); +/* 在尾部添加元素 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); console.log( - `列表 list = ${list.toArray()} ,容量 = ${list.capacity()} ,长度 = ${list.size()}` + `列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); -/* 中间插入元素 */ -list.insert(3, 6); -console.log(`在索引 3 处插入数字 6 ,得到 list = ${list.toArray()}`); +/* 在中间插入元素 */ +nums.insert(3, 6); +console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums.toArray()}`); /* 删除元素 */ -list.remove(3); -console.log(`删除索引 3 处的元素,得到 list = ${list.toArray()}`); +nums.remove(3); +console.log(`删除索引 3 处的元素,得到 nums = ${nums.toArray()}`); /* 访问元素 */ -const num = list.get(1); +const num = nums.get(1); console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ -list.set(1, 0); -console.log(`将索引 1 处的元素更新为 0 ,得到 list = ${list.toArray()}`); +nums.set(1, 0); +console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums.toArray()}`); /* 测试扩容机制 */ for (let i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list.add(i); + nums.add(i); } console.log( - `扩容后的列表 list = ${list.toArray()} ,容量 = ${list.capacity()} ,长度 = ${list.size()}` + `扩容后的列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); diff --git a/codes/javascript/chapter_backtracking/n_queens.js b/codes/javascript/chapter_backtracking/n_queens.js new file mode 100644 index 0000000000..a018e047e8 --- /dev/null +++ b/codes/javascript/chapter_backtracking/n_queens.js @@ -0,0 +1,55 @@ +/** + * File: n_queens.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯算法:n 皇后 */ +function backtrack(row, n, state, res, cols, diags1, diags2) { + // 当放置完所有行时,记录解 + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // 遍历所有列 + for (let col = 0; col < n; col++) { + // 计算该格子对应的主对角线和次对角线 + const diag1 = row - col + n - 1; + const diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 尝试:将皇后放置在该格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +function nQueens(n) { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 记录列是否有皇后 + const diags1 = Array(2 * n - 1).fill(false); // 记录主对角线上是否有皇后 + const diags2 = Array(2 * n - 1).fill(false); // 记录次对角线上是否有皇后 + const res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`输入棋盘长宽为 ${n}`); +console.log(`皇后放置方案共有 ${res.length} 种`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); diff --git a/codes/javascript/chapter_backtracking/permutations_i.js b/codes/javascript/chapter_backtracking/permutations_i.js new file mode 100644 index 0000000000..8981fc4b48 --- /dev/null +++ b/codes/javascript/chapter_backtracking/permutations_i.js @@ -0,0 +1,42 @@ +/** + * File: permutations_i.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯算法:全排列 I */ +function backtrack(state, choices, selected, res) { + // 当状态长度等于元素数量时,记录解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 遍历所有选择 + choices.forEach((choice, i) => { + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.push(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 I */ +function permutationsI(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 3]; +const res = permutationsI(nums); + +console.log(`输入数组 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); diff --git a/codes/javascript/chapter_backtracking/permutations_ii.js b/codes/javascript/chapter_backtracking/permutations_ii.js new file mode 100644 index 0000000000..d0e27cec65 --- /dev/null +++ b/codes/javascript/chapter_backtracking/permutations_ii.js @@ -0,0 +1,44 @@ +/** + * File: permutations_ii.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯算法:全排列 II */ +function backtrack(state, choices, selected, res) { + // 当状态长度等于元素数量时,记录解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 遍历所有选择 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.has(choice)) { + // 尝试:做出选择,更新状态 + duplicated.add(choice); // 记录选择过的元素值 + selected[i] = true; + state.push(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 II */ +function permutationsII(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 2]; +const res = permutationsII(nums); + +console.log(`输入数组 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); diff --git a/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js b/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js new file mode 100644 index 0000000000..a952b0a7ca --- /dev/null +++ b/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js @@ -0,0 +1,33 @@ +/** + * File: preorder_traversal_i_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序遍历:例题一 */ +function preOrder(root, res) { + if (root === null) { + return; + } + if (root.val === 7) { + // 记录解 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 前序遍历 +const res = []; +preOrder(root, res); + +console.log('\n输出所有值为 7 的节点'); +console.log(res.map((node) => node.val)); diff --git a/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js b/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js new file mode 100644 index 0000000000..c214352bd7 --- /dev/null +++ b/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js @@ -0,0 +1,40 @@ +/** + * File: preorder_traversal_ii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序遍历:例题二 */ +function preOrder(root, path, res) { + if (root === null) { + return; + } + // 尝试 + path.push(root); + if (root.val === 7) { + // 记录解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 前序遍历 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n输出所有根节点到节点 7 的路径'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js b/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js new file mode 100644 index 0000000000..5b942df67a --- /dev/null +++ b/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js @@ -0,0 +1,41 @@ +/** + * File: preorder_traversal_iii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序遍历:例题三 */ +function preOrder(root, path, res) { + // 剪枝 + if (root === null || root.val === 3) { + return; + } + // 尝试 + path.push(root); + if (root.val === 7) { + // 记录解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 前序遍历 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js b/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js new file mode 100644 index 0000000000..a8d9112718 --- /dev/null +++ b/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js @@ -0,0 +1,68 @@ +/** + * File: preorder_traversal_iii_template.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 判断当前状态是否为解 */ +function isSolution(state) { + return state && state[state.length - 1]?.val === 7; +} + +/* 记录解 */ +function recordSolution(state, res) { + res.push([...state]); +} + +/* 判断在当前状态下,该选择是否合法 */ +function isValid(state, choice) { + return choice !== null && choice.val !== 3; +} + +/* 更新状态 */ +function makeChoice(state, choice) { + state.push(choice); +} + +/* 恢复状态 */ +function undoChoice(state) { + state.pop(); +} + +/* 回溯算法:例题三 */ +function backtrack(state, choices, res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + for (const choice of choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + backtrack(state, [choice.left, choice.right], res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 回溯算法 +const res = []; +backtrack([], [root], res); + +console.log('\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/codes/javascript/chapter_backtracking/subset_sum_i.js b/codes/javascript/chapter_backtracking/subset_sum_i.js new file mode 100644 index 0000000000..add8121849 --- /dev/null +++ b/codes/javascript/chapter_backtracking/subset_sum_i.js @@ -0,0 +1,46 @@ +/** + * File: subset_sum_i.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯算法:子集和 I */ +function backtrack(state, target, choices, start, res) { + // 子集和等于 target 时,记录解 + if (target === 0) { + res.push([...state]); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 I */ +function subsetSumI(nums, target) { + const state = []; // 状态(子集) + nums.sort((a, b) => a - b); // 对 nums 进行排序 + const start = 0; // 遍历起始点 + const res = []; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); diff --git a/codes/javascript/chapter_backtracking/subset_sum_i_naive.js b/codes/javascript/chapter_backtracking/subset_sum_i_naive.js new file mode 100644 index 0000000000..16d9b1dc44 --- /dev/null +++ b/codes/javascript/chapter_backtracking/subset_sum_i_naive.js @@ -0,0 +1,44 @@ +/** + * File: subset_sum_i_naive.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯算法:子集和 I */ +function backtrack(state, target, total, choices, res) { + // 子集和等于 target 时,记录解 + if (total === target) { + res.push([...state]); + return; + } + // 遍历所有选择 + for (let i = 0; i < choices.length; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 I(包含重复子集) */ +function subsetSumINaive(nums, target) { + const state = []; // 状态(子集) + const total = 0; // 子集和 + const res = []; // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); +console.log('请注意,该方法输出的结果包含重复集合'); diff --git a/codes/javascript/chapter_backtracking/subset_sum_ii.js b/codes/javascript/chapter_backtracking/subset_sum_ii.js new file mode 100644 index 0000000000..78fcce604e --- /dev/null +++ b/codes/javascript/chapter_backtracking/subset_sum_ii.js @@ -0,0 +1,51 @@ +/** + * File: subset_sum_ii.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯算法:子集和 II */ +function backtrack(state, target, choices, start, res) { + // 子集和等于 target 时,记录解 + if (target === 0) { + res.push([...state]); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 II */ +function subsetSumII(nums, target) { + const state = []; // 状态(子集) + nums.sort((a, b) => a - b); // 对 nums 进行排序 + const start = 0; // 遍历起始点 + const res = []; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); diff --git a/codes/javascript/chapter_computational_complexity/iteration.js b/codes/javascript/chapter_computational_complexity/iteration.js new file mode 100644 index 0000000000..1e548374ff --- /dev/null +++ b/codes/javascript/chapter_computational_complexity/iteration.js @@ -0,0 +1,70 @@ +/** + * File: iteration.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for 循环 */ +function forLoop(n) { + let res = 0; + // 循环求和 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 循环 */ +function whileLoop(n) { + let res = 0; + let i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; +} + +/* while 循环(两次更新) */ +function whileLoopII(n) { + let res = 0; + let i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; +} + +/* 双层 for 循环 */ +function nestedForLoop(n) { + let res = ''; + // 循环 i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = forLoop(n); +console.log(`for 循环的求和结果 res = ${res}`); + +res = whileLoop(n); +console.log(`while 循环的求和结果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while 循环(两次更新)求和结果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`双层 for 循环的遍历结果 ${resStr}`); diff --git a/codes/javascript/chapter_computational_complexity/leetcode_two_sum.js b/codes/javascript/chapter_computational_complexity/leetcode_two_sum.js deleted file mode 100644 index 0db9cd705d..0000000000 --- a/codes/javascript/chapter_computational_complexity/leetcode_two_sum.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * File: leetcode_two_sum.js - * Created Time: 2022-12-15 - * Author: gyt95 (gytkwan@gmail.com) - */ - -function twoSumBruteForce(nums, target) { - const n = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (let i = 0; i < n; i++) { - for (let j = i + 1; j < n; j++) { - if (nums[i] + nums[j] === target) { - return [i, j]; - } - } - } - return []; -} - -function twoSumHashTable(nums, target) { - // 辅助哈希表,空间复杂度 O(n) - let m = {}; - // 单层循环,时间复杂度 O(n) - for (let i = 0; i < nums.length; i++) { - if (m[nums[i]] !== undefined) { - return [m[nums[i]], i]; - } else { - m[target - nums[i]] = i; - } - } - return []; -} - -/* Driver Code */ -// 方法一 -const nums = [2, 7, 11, 15], target = 9; - -let res = twoSumBruteForce(nums, target); -console.log("方法一 res = ", res); - -// 方法二 -res = twoSumHashTable(nums, target); -console.log("方法二 res = ", res); diff --git a/codes/javascript/chapter_computational_complexity/recursion.js b/codes/javascript/chapter_computational_complexity/recursion.js new file mode 100644 index 0000000000..c64e035383 --- /dev/null +++ b/codes/javascript/chapter_computational_complexity/recursion.js @@ -0,0 +1,69 @@ +/** + * File: recursion.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 递归 */ +function recur(n) { + // 终止条件 + if (n === 1) return 1; + // 递:递归调用 + const res = recur(n - 1); + // 归:返回结果 + return n + res; +} + +/* 使用迭代模拟递归 */ +function forLoopRecur(n) { + // 使用一个显式的栈来模拟系统调用栈 + const stack = []; + let res = 0; + // 递:递归调用 + for (let i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack.push(i); + } + // 归:返回结果 + while (stack.length) { + // 通过“出栈操作”模拟“归” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾递归 */ +function tailRecur(n, res) { + // 终止条件 + if (n === 0) return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); +} + +/* 斐波那契数列:递归 */ +function fib(n) { + // 终止条件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = recur(n); +console.log(`递归函数的求和结果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`使用迭代模拟递归的求和结果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`尾递归函数的求和结果 res = ${res}`); + +res = fib(n); +console.log(`斐波那契数列的第 ${n} 项为 ${res}`); + diff --git a/codes/javascript/chapter_computational_complexity/space_complexity.js b/codes/javascript/chapter_computational_complexity/space_complexity.js new file mode 100644 index 0000000000..7708eaabb7 --- /dev/null +++ b/codes/javascript/chapter_computational_complexity/space_complexity.js @@ -0,0 +1,103 @@ +/** + * File: space_complexity.js + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +const { ListNode } = require('../modules/ListNode'); +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 函数 */ +function constFunc() { + // 执行某些操作 + return 0; +} + +/* 常数阶 */ +function constant(n) { + // 常量、变量、对象占用 O(1) 空间 + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // 循环中的变量占用 O(1) 空间 + for (let i = 0; i < n; i++) { + const c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 线性阶 */ +function linear(n) { + // 长度为 n 的数组占用 O(n) 空间 + const nums = new Array(n); + // 长度为 n 的列表占用 O(n) 空间 + const nodes = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 线性阶(递归实现) */ +function linearRecur(n) { + console.log(`递归 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 平方阶 */ +function quadratic(n) { + // 矩阵占用 O(n^2) 空间 + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二维列表占用 O(n^2) 空间 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 平方阶(递归实现) */ +function quadraticRecur(n) { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指数阶(建立满二叉树) */ +function buildTree(n) { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 常数阶 +constant(n); +// 线性阶 +linear(n); +linearRecur(n); +// 平方阶 +quadratic(n); +quadraticRecur(n); +// 指数阶 +const root = buildTree(n); +printTree(root); diff --git a/codes/javascript/chapter_computational_complexity/time_complexity.js b/codes/javascript/chapter_computational_complexity/time_complexity.js index 2e4688dd21..3d3ab4e19d 100644 --- a/codes/javascript/chapter_computational_complexity/time_complexity.js +++ b/codes/javascript/chapter_computational_complexity/time_complexity.js @@ -32,7 +32,7 @@ function arrayTraversal(nums) { /* 平方阶 */ function quadratic(n) { let count = 0; - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; @@ -44,9 +44,9 @@ function quadratic(n) { /* 平方阶(冒泡排序) */ function bubbleSort(nums) { let count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -64,7 +64,7 @@ function bubbleSort(nums) { function exponential(n) { let count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; @@ -77,7 +77,7 @@ function exponential(n) { /* 指数阶(递归实现) */ function expRecur(n) { - if (n == 1) return 1; + if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } @@ -109,7 +109,7 @@ function linearLogRecur(n) { /* 阶乘阶(递归实现) */ function factorialRecur(n) { - if (n == 0) return 1; + if (n === 0) return 1; let count = 0; // 从 1 个分裂出 n 个 for (let i = 0; i < n; i++) { @@ -121,35 +121,35 @@ function factorialRecur(n) { /* Driver Code */ // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 const n = 8; -console.log("输入数据大小 n = " + n); +console.log('输入数据大小 n = ' + n); let count = constant(n); -console.log("常数阶的计算操作数量 = " + count); +console.log('常数阶的操作数量 = ' + count); count = linear(n); -console.log("线性阶的计算操作数量 = " + count); +console.log('线性阶的操作数量 = ' + count); count = arrayTraversal(new Array(n)); -console.log("线性阶(遍历数组)的计算操作数量 = " + count); +console.log('线性阶(遍历数组)的操作数量 = ' + count); count = quadratic(n); -console.log("平方阶的计算操作数量 = " + count); +console.log('平方阶的操作数量 = ' + count); let nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); -console.log("平方阶(冒泡排序)的计算操作数量 = " + count); +console.log('平方阶(冒泡排序)的操作数量 = ' + count); count = exponential(n); -console.log("指数阶(循环实现)的计算操作数量 = " + count); +console.log('指数阶(循环实现)的操作数量 = ' + count); count = expRecur(n); -console.log("指数阶(递归实现)的计算操作数量 = " + count); +console.log('指数阶(递归实现)的操作数量 = ' + count); count = logarithmic(n); -console.log("对数阶(循环实现)的计算操作数量 = " + count); +console.log('对数阶(循环实现)的操作数量 = ' + count); count = logRecur(n); -console.log("对数阶(递归实现)的计算操作数量 = " + count); +console.log('对数阶(递归实现)的操作数量 = ' + count); count = linearLogRecur(n); -console.log("线性对数阶(递归实现)的计算操作数量 = " + count); +console.log('线性对数阶(递归实现)的操作数量 = ' + count); count = factorialRecur(n); -console.log("阶乘阶(递归实现)的计算操作数量 = " + count); +console.log('阶乘阶(递归实现)的操作数量 = ' + count); diff --git a/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js b/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js index 949e50b042..7f22e44d3e 100644 --- a/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js +++ b/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js @@ -1,4 +1,4 @@ -/* +/** * File: worst_best_time_complexity.js * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) @@ -6,15 +6,15 @@ /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ function randomNumbers(n) { - let nums = Array(n); + const nums = Array(n); // 生成数组 nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 随机打乱数组元素 for (let i = 0; i < n; i++) { - let r = Math.floor(Math.random() * (i + 1)); - let temp = nums[i]; + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } @@ -24,6 +24,8 @@ function randomNumbers(n) { /* 查找数组 nums 中数字 1 所在索引 */ function findOne(nums) { for (let i = 0; i < nums.length; i++) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] === 1) { return i; } @@ -32,14 +34,10 @@ function findOne(nums) { } /* Driver Code */ -function main() { - for (let i = 0; i < 10; i++) { - let n = 100; - let nums = randomNumbers(n); - let index = findOne(nums); - console.log( - "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" - ); - console.log("数字 1 的索引为 " + index); - } +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n数组 [ 1, 2, ..., n ] 被打乱后 = [' + nums.join(', ') + ']'); + console.log('数字 1 的索引为 ' + index); } diff --git a/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js b/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js new file mode 100644 index 0000000000..699caae5b5 --- /dev/null +++ b/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js @@ -0,0 +1,39 @@ +/** + * File: binary_search_recur.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分查找:问题 f(i, j) */ +function dfs(nums, target, i, j) { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + const m = i + ((j - i) >> 1); + 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; + } +} + +/* 二分查找 */ +function binarySearch(nums, target) { + const n = nums.length; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分查找(双闭区间) +const index = binarySearch(nums, target); +console.log(`目标元素 6 的索引 = ${index}`); diff --git a/codes/javascript/chapter_divide_and_conquer/build_tree.js b/codes/javascript/chapter_divide_and_conquer/build_tree.js new file mode 100644 index 0000000000..1505b04955 --- /dev/null +++ b/codes/javascript/chapter_divide_and_conquer/build_tree.js @@ -0,0 +1,44 @@ +/** + * File: build_tree.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { printTree } = require('../modules/PrintUtil'); +const { TreeNode } = require('../modules/TreeNode'); + +/* 构建二叉树:分治 */ +function dfs(preorder, inorderMap, i, l, r) { + // 子树区间为空时终止 + if (r - l < 0) return null; + // 初始化根节点 + const root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + const m = inorderMap.get(preorder[i]); + // 子问题:构建左子树 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; +} + +/* 构建二叉树 */ +function buildTree(preorder, inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前序遍历 = ' + JSON.stringify(preorder)); +console.log('中序遍历 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('构建的二叉树为:'); +printTree(root); diff --git a/codes/javascript/chapter_divide_and_conquer/hanota.js b/codes/javascript/chapter_divide_and_conquer/hanota.js new file mode 100644 index 0000000000..66281a6431 --- /dev/null +++ b/codes/javascript/chapter_divide_and_conquer/hanota.js @@ -0,0 +1,52 @@ +/** + * File: hanota.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 移动一个圆盘 */ +function move(src, tar) { + // 从 src 顶部拿出一个圆盘 + const pan = src.pop(); + // 将圆盘放入 tar 顶部 + tar.push(pan); +} + +/* 求解汉诺塔问题 f(i) */ +function dfs(i, src, buf, tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i === 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解汉诺塔问题 */ +function solveHanota(A, B, C) { + const n = A.length; + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +// 列表尾部是柱子顶部 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初始状态下:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('圆盘移动完成后:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js b/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js new file mode 100644 index 0000000000..ee9f111d0f --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js @@ -0,0 +1,34 @@ +/** + * File: climbing_stairs_backtrack.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯 */ +function backtrack(choices, state, n, res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state === n) res.set(0, res.get(0) + 1); + // 遍历所有选择 + for (const choice of choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) continue; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬楼梯:回溯 */ +function climbingStairsBacktrack(n) { + const choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 + const state = 0; // 从第 0 阶开始爬 + const res = new Map(); + res.set(0, 0); // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); diff --git a/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js b/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js new file mode 100644 index 0000000000..54a3b930fa --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_constraint_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 带约束爬楼梯:动态规划 */ +function climbingStairsConstraintDP(n) { + if (n === 1 || n === 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = Array.from(new Array(n + 1), () => new Array(3)); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); diff --git a/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js b/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js new file mode 100644 index 0000000000..f93d0f668f --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js @@ -0,0 +1,24 @@ +/** + * File: climbing_stairs_dfs.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 搜索 */ +function dfs(i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬楼梯:搜索 */ +function climbingStairsDFS(n) { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); diff --git a/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js b/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js new file mode 100644 index 0000000000..5986a5461b --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_dfs_mem.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 记忆化搜索 */ +function dfs(i, 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] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; +} + +/* 爬楼梯:记忆化搜索 */ +function climbingStairsDFSMem(n) { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); diff --git a/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js b/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js new file mode 100644 index 0000000000..cb5d37c5df --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dp.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 爬楼梯:动态规划 */ +function climbingStairsDP(n) { + if (n === 1 || n === 2) return n; + // 初始化 dp 表,用于存储子问题的解 + const dp = new Array(n + 1).fill(-1); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬楼梯:空间优化后的动态规划 */ +function climbingStairsDPComp(n) { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); +res = climbingStairsDPComp(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); diff --git a/codes/javascript/chapter_dynamic_programming/coin_change.js b/codes/javascript/chapter_dynamic_programming/coin_change.js new file mode 100644 index 0000000000..7aa21b043c --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/coin_change.js @@ -0,0 +1,66 @@ +/** + * File: coin_change.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换:动态规划 */ +function coinChangeDP(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 状态转移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行和列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零钱兑换:空间优化后的动态规划 */ +function coinChangeDPComp(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 动态规划 +let res = coinChangeDP(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); + +// 空间优化后的动态规划 +res = coinChangeDPComp(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/coin_change_ii.js b/codes/javascript/chapter_dynamic_programming/coin_change_ii.js new file mode 100644 index 0000000000..5a17533656 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/coin_change_ii.js @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换 II:动态规划 */ +function coinChangeIIDP(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +function coinChangeIIDPComp(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 动态规划 +let res = coinChangeIIDP(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); + +// 空间优化后的动态规划 +res = coinChangeIIDPComp(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/edit_distance.js b/codes/javascript/chapter_dynamic_programming/edit_distance.js new file mode 100644 index 0000000000..5c1258584f --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/edit_distance.js @@ -0,0 +1,135 @@ +/** + * File: edit_distance.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 编辑距离:暴力搜索 */ +function editDistanceDFS(s, t, i, 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return Math.min(insert, del, replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +function editDistanceDFSMem(s, t, mem, i, 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +function editDistanceDP(s, t) { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + // 状态转移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行和列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 编辑距离:空间优化后的动态规划 */ +function editDistanceDPComp(s, t) { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 状态转移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (let i = 1; i <= n; i++) { + // 状态转移:首列 + let leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜索 +let res = editDistanceDFS(s, t, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 记忆化搜索 +const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 动态规划 +res = editDistanceDP(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 空间优化后的动态规划 +res = editDistanceDPComp(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); diff --git a/codes/javascript/chapter_dynamic_programming/knapsack.js b/codes/javascript/chapter_dynamic_programming/knapsack.js new file mode 100644 index 0000000000..382e8dd7c6 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/knapsack.js @@ -0,0 +1,113 @@ +/** + * File: knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜索 */ +function knapsackDFS(wgt, val, i, c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return Math.max(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +function knapsackDFSMem(wgt, val, mem, i, c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +function knapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(n + 1) + .fill(0) + .map(() => Array(cap + 1).fill(0)); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空间优化后的动态规划 */ +function knapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 状态转移 + for (let i = 1; i <= n; i++) { + // 倒序遍历 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜索 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 动态规划 +res = knapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 空间优化后的动态规划 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js b/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js new file mode 100644 index 0000000000..58e2a349a7 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js @@ -0,0 +1,49 @@ +/** + * File: min_cost_climbing_stairs_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬楼梯最小代价:动态规划 */ +function minCostClimbingStairsDP(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = new Array(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +function minCostClimbingStairsDPComp(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log('输入楼梯的代价列表为:', cost); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完楼梯的最低代价为:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完楼梯的最低代价为:${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/min_path_sum.js b/codes/javascript/chapter_dynamic_programming/min_path_sum.js new file mode 100644 index 0000000000..08c508f328 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/min_path_sum.js @@ -0,0 +1,121 @@ +/** + * File: min_path_sum.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路径和:暴力搜索 */ +function minPathSumDFS(grid, i, j) { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路径和:记忆化搜索 */ +function minPathSumDFSMem(grid, mem, i, j) { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有记录,则直接返回 + if (mem[i][j] !== -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +function minPathSumDP(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路径和:空间优化后的动态规划 */ +function minPathSumDPComp(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 状态转移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (let i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 暴力搜索 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 动态规划 +res = minPathSumDP(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 空间优化后的动态规划 +res = minPathSumDPComp(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); diff --git a/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js b/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js new file mode 100644 index 0000000000..d6d6bfe663 --- /dev/null +++ b/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:动态规划 */ +function unboundedKnapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空间优化后的动态规划 */ +function unboundedKnapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 动态规划 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 空间优化后的动态规划 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); diff --git a/codes/javascript/chapter_graph/graph_adjacency_list.js b/codes/javascript/chapter_graph/graph_adjacency_list.js new file mode 100644 index 0000000000..ec7567c3ce --- /dev/null +++ b/codes/javascript/chapter_graph/graph_adjacency_list.js @@ -0,0 +1,141 @@ +/** + * File: graph_adjacency_list.js + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { Vertex } = require('../modules/Vertex'); + +/* 基于邻接表实现的无向图类 */ +class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + adjList; + + /* 构造方法 */ + constructor(edges) { + this.adjList = new Map(); + // 添加所有顶点和边 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + size() { + return this.adjList.size; + } + + /* 添加边 */ + addEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 添加边 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 删除边 */ + removeEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 删除边 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 添加顶点 */ + addVertex(vet) { + if (this.adjList.has(vet)) return; + // 在邻接表中添加一个新链表 + this.adjList.set(vet, []); + } + + /* 删除顶点 */ + removeVertex(vet) { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在邻接表中删除顶点 vet 对应的链表 + this.adjList.delete(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (const set of this.adjList.values()) { + const index = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 打印邻接表 */ + print() { + console.log('邻接表 ='); + for (const [key, value] of this.adjList) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +if (require.main === module) { + /* Driver Code */ + /* 初始化无向图 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初始化后,图为'); + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 即 v0, v2 + graph.addEdge(v0, v2); + console.log('\n添加边 1-2 后,图为'); + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 即 v0, v1 + graph.removeEdge(v0, v1); + console.log('\n删除边 1-3 后,图为'); + graph.print(); + + /* 添加顶点 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n添加顶点 6 后,图为'); + graph.print(); + + /* 删除顶点 */ + // 顶点 3 即 v1 + graph.removeVertex(v1); + console.log('\n删除顶点 3 后,图为'); + graph.print(); +} + +module.exports = { + GraphAdjList, +}; diff --git a/codes/javascript/chapter_graph/graph_adjacency_matrix.js b/codes/javascript/chapter_graph/graph_adjacency_matrix.js new file mode 100644 index 0000000000..d6ecb08591 --- /dev/null +++ b/codes/javascript/chapter_graph/graph_adjacency_matrix.js @@ -0,0 +1,132 @@ +/** + * File: graph_adjacency_matrix.js + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + constructor(vertices, edges) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + for (const val of vertices) { + this.addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + size() { + return this.vertices.length; + } + + /* 添加顶点 */ + addVertex(val) { + const n = this.size(); + // 向顶点列表中添加新顶点的值 + this.vertices.push(val); + // 在邻接矩阵中添加一行 + const newRow = []; + for (let j = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在邻接矩阵中添加一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 删除顶点 */ + removeVertex(index) { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在顶点列表中移除索引 index 的顶点 + this.vertices.splice(index, 1); + + // 在邻接矩阵中删除索引 index 的行 + this.adjMat.splice(index, 1); + // 在邻接矩阵中删除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + addEdge(i, j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + removeEdge(i, j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + print() { + console.log('顶点列表 = ', this.vertices); + console.log('邻接矩阵 =', this.adjMat); + } +} + +/* Driver Code */ +/* 初始化无向图 */ +// 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 +const vertices = [1, 3, 2, 5, 4]; +const edges = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph = new GraphAdjMat(vertices, edges); +console.log('\n初始化后,图为'); +graph.print(); + +/* 添加边 */ +// 顶点 1, 2 的索引分别为 0, 2 +graph.addEdge(0, 2); +console.log('\n添加边 1-2 后,图为'); +graph.print(); + +/* 删除边 */ +// 顶点 1, 3 的索引分别为 0, 1 +graph.removeEdge(0, 1); +console.log('\n删除边 1-3 后,图为'); +graph.print(); + +/* 添加顶点 */ +graph.addVertex(6); +console.log('\n添加顶点 6 后,图为'); +graph.print(); + +/* 删除顶点 */ +// 顶点 3 的索引为 1 +graph.removeVertex(1); +console.log('\n删除顶点 3 后,图为'); +graph.print(); diff --git a/codes/javascript/chapter_graph/graph_bfs.js b/codes/javascript/chapter_graph/graph_bfs.js new file mode 100644 index 0000000000..8af1f0170f --- /dev/null +++ b/codes/javascript/chapter_graph/graph_bfs.js @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { GraphAdjList } = require('./graph_adjacency_list'); +const { Vertex } = require('../modules/Vertex'); + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +function graphBFS(graph, startVet) { + // 顶点遍历序列 + const res = []; + // 哈希集合,用于记录已被访问过的顶点 + const visited = new Set(); + visited.add(startVet); + // 队列用于实现 BFS + const que = [startVet]; + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.length) { + const vet = que.shift(); // 队首顶点出队 + res.push(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.push(adjVet); // 只入队未访问的顶点 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; +} + +/* Driver Code */ +/* 初始化无向图 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化后,图为'); +graph.print(); + +/* 广度优先遍历 */ +const res = graphBFS(graph, v[0]); +console.log('\n广度优先遍历(BFS)顶点序列为'); +console.log(Vertex.vetsToVals(res)); diff --git a/codes/javascript/chapter_graph/graph_dfs.js b/codes/javascript/chapter_graph/graph_dfs.js new file mode 100644 index 0000000000..2043d69099 --- /dev/null +++ b/codes/javascript/chapter_graph/graph_dfs.js @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { Vertex } = require('../modules/Vertex'); +const { GraphAdjList } = require('./graph_adjacency_list'); + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +function dfs(graph, visited, res, vet) { + res.push(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +function graphDFS(graph, startVet) { + // 顶点遍历序列 + const res = []; + // 哈希集合,用于记录已被访问过的顶点 + const visited = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 初始化无向图 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化后,图为'); +graph.print(); + +/* 深度优先遍历 */ +const res = graphDFS(graph, v[0]); +console.log('\n深度优先遍历(DFS)顶点序列为'); +console.log(Vertex.vetsToVals(res)); diff --git a/codes/javascript/chapter_greedy/coin_change_greedy.js b/codes/javascript/chapter_greedy/coin_change_greedy.js new file mode 100644 index 0000000000..2d80ddad75 --- /dev/null +++ b/codes/javascript/chapter_greedy/coin_change_greedy.js @@ -0,0 +1,48 @@ +/** + * File: coin_change_greedy.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 零钱兑换:贪心 */ +function coinChangeGreedy(coins, amt) { + // 假设 coins 数组有序 + let i = coins.length - 1; + let count = 0; + // 循环进行贪心选择,直到无剩余金额 + while (amt > 0) { + // 找到小于且最接近剩余金额的硬币 + while (i > 0 && coins[i] > amt) { + i--; + } + // 选择 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,则返回 -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 贪心:能够保证找到全局最优解 +let coins = [1, 5, 10, 20, 50, 100]; +let amt = 186; +let res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); + +// 贪心:无法保证找到全局最优解 +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); +console.log('实际上需要的最少数量为 3 ,即 20 + 20 + 20'); + +// 贪心:无法保证找到全局最优解 +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); +console.log('实际上需要的最少数量为 2 ,即 49 + 49'); diff --git a/codes/javascript/chapter_greedy/fractional_knapsack.js b/codes/javascript/chapter_greedy/fractional_knapsack.js new file mode 100644 index 0000000000..67e499c3c4 --- /dev/null +++ b/codes/javascript/chapter_greedy/fractional_knapsack.js @@ -0,0 +1,46 @@ +/** + * File: fractional_knapsack.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 物品 */ +class Item { + constructor(w, v) { + this.w = w; // 物品重量 + this.v = v; // 物品价值 + } +} + +/* 分数背包:贪心 */ +function fractionalKnapsack(wgt, val, cap) { + // 创建物品列表,包含两个属性:重量、价值 + const items = wgt.map((w, i) => new Item(w, val[i])); + // 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 循环贪心选择 + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (item.v / item.w) * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 贪心算法 +const res = fractionalKnapsack(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); diff --git a/codes/javascript/chapter_greedy/max_capacity.js b/codes/javascript/chapter_greedy/max_capacity.js new file mode 100644 index 0000000000..5f13b391cf --- /dev/null +++ b/codes/javascript/chapter_greedy/max_capacity.js @@ -0,0 +1,34 @@ +/** + * File: max_capacity.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:贪心 */ +function maxCapacity(ht) { + // 初始化 i, j,使其分列数组两端 + let i = 0, + j = ht.length - 1; + // 初始最大容量为 0 + let res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + const cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 贪心算法 +const res = maxCapacity(ht); +console.log(`最大容量为 ${res}`); diff --git a/codes/javascript/chapter_greedy/max_product_cutting.js b/codes/javascript/chapter_greedy/max_product_cutting.js new file mode 100644 index 0000000000..c222eeb5e2 --- /dev/null +++ b/codes/javascript/chapter_greedy/max_product_cutting.js @@ -0,0 +1,33 @@ +/** + * File: max_product_cutting.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切分乘积:贪心 */ +function maxProductCutting(n) { + // 当 n <= 3 时,必须切分出一个 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + let a = Math.floor(n / 3); + let b = n % 3; + if (b === 1) { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 当余数为 2 时,不做处理 + return Math.pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return Math.pow(3, a); +} + +/* Driver Code */ +let n = 58; + +// 贪心算法 +let res = maxProductCutting(n); +console.log(`最大切分乘积为 ${res}`); diff --git a/codes/javascript/chapter_hashing/array_hash_map.js b/codes/javascript/chapter_hashing/array_hash_map.js index 405d0f3183..9d0236b5b0 100644 --- a/codes/javascript/chapter_hashing/array_hash_map.js +++ b/codes/javascript/chapter_hashing/array_hash_map.js @@ -5,19 +5,19 @@ */ /* 键值对 Number -> String */ -class Entry { +class Pair { constructor(key, val) { this.key = key; this.val = val; } } -/* 基于数组简易实现的哈希表 */ +/* 基于数组实现的哈希表 */ class ArrayHashMap { - #bucket; + #buckets; constructor() { - // 初始化一个长度为 100 的桶(数组) - this.#bucket = new Array(100).fill(null); + // 初始化数组,包含 100 个桶 + this.#buckets = new Array(100).fill(null); } /* 哈希函数 */ @@ -28,30 +28,30 @@ class ArrayHashMap { /* 查询操作 */ get(key) { let index = this.#hashFunc(key); - let entry = this.#bucket[index]; - if (entry === null) return null; - return entry.val; + let pair = this.#buckets[index]; + if (pair === null) return null; + return pair.val; } /* 添加操作 */ set(key, val) { let index = this.#hashFunc(key); - this.#bucket[index] = new Entry(key, val); + this.#buckets[index] = new Pair(key, val); } /* 删除操作 */ delete(key) { let index = this.#hashFunc(key); // 置为 null ,代表删除 - this.#bucket[index] = null; + this.#buckets[index] = null; } /* 获取所有键值对 */ entries() { let arr = []; - for (let i = 0; i < this.#bucket.length; i++) { - if (this.#bucket[i]) { - arr.push(this.#bucket[i]); + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i]); } } return arr; @@ -60,9 +60,9 @@ class ArrayHashMap { /* 获取所有键 */ keys() { let arr = []; - for (let i = 0; i < this.#bucket.length; i++) { - if (this.#bucket[i]) { - arr.push(this.#bucket[i]?.key); + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].key); } } return arr; @@ -71,9 +71,9 @@ class ArrayHashMap { /* 获取所有值 */ values() { let arr = []; - for (let i = 0; i < this.#bucket.length; i++) { - if (this.#bucket[i]) { - arr.push(this.#bucket[i]?.val); + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].val); } } return arr; @@ -81,10 +81,9 @@ class ArrayHashMap { /* 打印哈希表 */ print() { - let entrySet = this.entries(); - for (const entry of entrySet) { - if (!entry) continue; - console.info(`${entry.key} -> ${entry.val}`); + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); } } } @@ -103,7 +102,7 @@ console.info('\n添加完成后,哈希表为\nKey -> Value'); map.print(); /* 查询操作 */ -// 向哈希表输入键 key ,得到值 value +// 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); @@ -115,9 +114,9 @@ map.print(); /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); -for (const entry of map.entries()) { - if (!entry) continue; - console.info(entry.key + ' -> ' + entry.val); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); } console.info('\n单独遍历键 Key'); for (const key of map.keys()) { diff --git a/codes/javascript/chapter_hashing/hash_map.js b/codes/javascript/chapter_hashing/hash_map.js index 9157b31594..ed03b7bf9a 100644 --- a/codes/javascript/chapter_hashing/hash_map.js +++ b/codes/javascript/chapter_hashing/hash_map.js @@ -19,7 +19,7 @@ console.info('\n添加完成后,哈希表为\nKey -> Value'); console.info(map); /* 查询操作 */ -// 向哈希表输入键 key ,得到值 value +// 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); diff --git a/codes/javascript/chapter_hashing/hash_map_chaining.js b/codes/javascript/chapter_hashing/hash_map_chaining.js new file mode 100644 index 0000000000..aa4a396c94 --- /dev/null +++ b/codes/javascript/chapter_hashing/hash_map_chaining.js @@ -0,0 +1,142 @@ +/** + * File: hash_map_chaining.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 键值对 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 链式地址哈希表 */ +class HashMapChaining { + #size; // 键值对数量 + #capacity; // 哈希表容量 + #loadThres; // 触发扩容的负载因子阈值 + #extendRatio; // 扩容倍数 + #buckets; // 桶数组 + + /* 构造方法 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 哈希函数 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 负载因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 查询操作 */ + get(key) { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若找到 key ,则返回对应 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key ,则返回 null + return null; + } + + /* 添加操作 */ + put(key, val) { + // 当负载因子超过阈值时,执行扩容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 删除操作 */ + remove(key) { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 遍历桶,从中删除键值对 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* 扩容哈希表 */ + #extend() { + // 暂存原哈希表 + const bucketsTmp = this.#buckets; + // 初始化扩容后的新哈希表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print() { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化哈希表 */ +const map = new HashMapChaining(); + +/* 添加操作 */ +// 在哈希表中添加键值对 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小啰'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +map.print(); + +/* 查询操作 */ +// 向哈希表中输入键 key ,得到值 value +const name = map.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +/* 删除操作 */ +// 在哈希表中删除键值对 (key, value) +map.remove(12836); +console.log('\n删除 12836 后,哈希表为\nKey -> Value'); +map.print(); diff --git a/codes/javascript/chapter_hashing/hash_map_open_addressing.js b/codes/javascript/chapter_hashing/hash_map_open_addressing.js new file mode 100644 index 0000000000..3f83fd2a84 --- /dev/null +++ b/codes/javascript/chapter_hashing/hash_map_open_addressing.js @@ -0,0 +1,177 @@ +/** + * File: hashMapOpenAddressing.js + * Created Time: 2023-06-13 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* 键值对 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + #size; // 键值对数量 + #capacity; // 哈希表容量 + #loadThres; // 触发扩容的负载因子阈值 + #extendRatio; // 扩容倍数 + #buckets; // 桶数组 + #TOMBSTONE; // 删除标记 + + /* 构造方法 */ + constructor() { + this.#size = 0; // 键值对数量 + this.#capacity = 4; // 哈希表容量 + this.#loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + this.#extendRatio = 2; // 扩容倍数 + this.#buckets = Array(this.#capacity).fill(null); // 桶数组 + this.#TOMBSTONE = new Pair(-1, '-1'); // 删除标记 + } + + /* 哈希函数 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 负载因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 搜索 key 对应的桶索引 */ + #findBucket(key) { + let index = this.#hashFunc(key); + let firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (this.#buckets[index] !== null) { + // 若遇到 key ,返回对应的桶索引 + if (this.#buckets[index].key === key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone !== -1) { + this.#buckets[firstTombstone] = this.#buckets[index]; + this.#buckets[index] = this.#TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if ( + firstTombstone === -1 && + this.#buckets[index] === this.#TOMBSTONE + ) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % this.#capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 查询操作 */ + get(key) { + // 搜索 key 对应的桶索引 + const index = this.#findBucket(key); + // 若找到键值对,则返回对应 val + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + return this.#buckets[index].val; + } + // 若键值对不存在,则返回 null + return null; + } + + /* 添加操作 */ + put(key, val) { + // 当负载因子超过阈值时,执行扩容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + // 搜索 key 对应的桶索引 + const index = this.#findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index].val = val; + return; + } + // 若键值对不存在,则添加该键值对 + this.#buckets[index] = new Pair(key, val); + this.#size++; + } + + /* 删除操作 */ + remove(key) { + // 搜索 key 对应的桶索引 + const index = this.#findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index] = this.#TOMBSTONE; + this.#size--; + } + } + + /* 扩容哈希表 */ + #extend() { + // 暂存原哈希表 + const bucketsTmp = this.#buckets; + // 初始化扩容后的新哈希表 + this.#capacity *= this.#extendRatio; + this.#buckets = Array(this.#capacity).fill(null); + this.#size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.#TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print() { + for (const pair of this.#buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.#TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// 初始化哈希表 +const hashmap = new HashMapOpenAddressing(); + +// 添加操作 +// 在哈希表中添加键值对 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小啰'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +hashmap.print(); + +// 查询操作 +// 向哈希表中输入键 key ,得到值 val +const name = hashmap.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +// 删除操作 +// 在哈希表中删除键值对 (key, val) +hashmap.remove(16750); +console.log('\n删除 16750 后,哈希表为\nKey -> Value'); +hashmap.print(); diff --git a/codes/javascript/chapter_hashing/simple_hash.js b/codes/javascript/chapter_hashing/simple_hash.js new file mode 100644 index 0000000000..4287515b3a --- /dev/null +++ b/codes/javascript/chapter_hashing/simple_hash.js @@ -0,0 +1,60 @@ +/** + * File: simple_hash.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法哈希 */ +function addHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法哈希 */ +function mulHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 异或哈希 */ +function xorHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* 旋转哈希 */ +function rotHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 算法'; + +let hash = addHash(key); +console.log('加法哈希值为 ' + hash); + +hash = mulHash(key); +console.log('乘法哈希值为 ' + hash); + +hash = xorHash(key); +console.log('异或哈希值为 ' + hash); + +hash = rotHash(key); +console.log('旋转哈希值为 ' + hash); diff --git a/codes/javascript/chapter_heap/my_heap.js b/codes/javascript/chapter_heap/my_heap.js new file mode 100644 index 0000000000..42c6a0ccb9 --- /dev/null +++ b/codes/javascript/chapter_heap/my_heap.js @@ -0,0 +1,158 @@ +/** + * File: my_heap.js + * Created Time: 2023-02-06 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { printHeap } = require('../modules/PrintUtil'); + +/* 最大堆类 */ +class MaxHeap { + #maxHeap; + + /* 构造方法,建立空堆或根据输入列表建堆 */ + constructor(nums) { + // 将列表元素原封不动添加进堆 + this.#maxHeap = nums === undefined ? [] : [...nums]; + // 堆化除叶节点以外的其他所有节点 + for (let i = this.#parent(this.size() - 1); i >= 0; i--) { + this.#siftDown(i); + } + } + + /* 获取左子节点的索引 */ + #left(i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + #right(i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + #parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 交换元素 */ + #swap(i, j) { + const tmp = this.#maxHeap[i]; + this.#maxHeap[i] = this.#maxHeap[j]; + this.#maxHeap[j] = tmp; + } + + /* 获取堆大小 */ + size() { + return this.#maxHeap.length; + } + + /* 判断堆是否为空 */ + isEmpty() { + return this.size() === 0; + } + + /* 访问堆顶元素 */ + peek() { + return this.#maxHeap[0]; + } + + /* 元素入堆 */ + push(val) { + // 添加节点 + this.#maxHeap.push(val); + // 从底至顶堆化 + this.#siftUp(this.size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + #siftUp(i) { + while (true) { + // 获取节点 i 的父节点 + const p = this.#parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; + // 交换两节点 + this.#swap(i, p); + // 循环向上堆化 + i = p; + } + } + + /* 元素出堆 */ + pop() { + // 判空处理 + if (this.isEmpty()) throw new Error('堆为空'); + // 交换根节点与最右叶节点(交换首元素与尾元素) + this.#swap(0, this.size() - 1); + // 删除节点 + const val = this.#maxHeap.pop(); + // 从顶至底堆化 + this.#siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + #siftDown(i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + const l = this.#left(i), + r = this.#right(i); + let ma = i; + if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; + if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma === i) break; + // 交换两节点 + this.#swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + + /* 打印堆(二叉树) */ + print() { + printHeap(this.#maxHeap); + } + + /* 取出堆中元素 */ + getMaxHeap() { + return this.#maxHeap; + } +} + +/* Driver Code */ +if (require.main === module) { + /* 初始化大顶堆 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\n输入列表并建堆后'); + maxHeap.print(); + + /* 获取堆顶元素 */ + let peek = maxHeap.peek(); + console.log(`\n堆顶元素为 ${peek}`); + + /* 元素入堆 */ + let val = 7; + maxHeap.push(val); + console.log(`\n元素 ${val} 入堆后`); + maxHeap.print(); + + /* 堆顶元素出堆 */ + peek = maxHeap.pop(); + console.log(`\n堆顶元素 ${peek} 出堆后`); + maxHeap.print(); + + /* 获取堆大小 */ + let size = maxHeap.size(); + console.log(`\n堆元素数量为 ${size}`); + + /* 判断堆是否为空 */ + let isEmpty = maxHeap.isEmpty(); + console.log(`\n堆是否为空 ${isEmpty}`); +} + +module.exports = { + MaxHeap, +}; diff --git a/codes/javascript/chapter_heap/top_k.js b/codes/javascript/chapter_heap/top_k.js new file mode 100644 index 0000000000..9e14082001 --- /dev/null +++ b/codes/javascript/chapter_heap/top_k.js @@ -0,0 +1,58 @@ +/** + * File: top_k.js + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +const { MaxHeap } = require('./my_heap'); + +/* 元素入堆 */ +function pushMinHeap(maxHeap, val) { + // 元素取反 + maxHeap.push(-val); +} + +/* 元素出堆 */ +function popMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.pop(); +} + +/* 访问堆顶元素 */ +function peekMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.peek(); +} + +/* 取出堆中元素 */ +function getMinHeap(maxHeap) { + // 元素取反 + return maxHeap.getMaxHeap().map((num) => -num); +} + +/* 基于堆查找数组中最大的 k 个元素 */ +function topKHeap(nums, k) { + // 初始化小顶堆 + // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 + const maxHeap = new MaxHeap([]); + // 将数组的前 k 个元素入堆 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (let i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆中元素 + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大的 ${k} 个元素为`, res); diff --git a/codes/javascript/chapter_searching/binary_search.js b/codes/javascript/chapter_searching/binary_search.js index 3cca17884c..618a7c06f1 100644 --- a/codes/javascript/chapter_searching/binary_search.js +++ b/codes/javascript/chapter_searching/binary_search.js @@ -7,47 +7,54 @@ /* 二分查找(双闭区间) */ function binarySearch(nums, target) { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - let i = 0, j = nums.length - 1; + let i = 0, + j = nums.length - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { - let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整 - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 + // 计算中点索引 m ,使用 parseInt() 向下取整 + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 + else if (nums[m] > target) + // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; - else - return m; // 找到目标元素,返回其索引 + else return m; // 找到目标元素,返回其索引 } // 未找到目标元素,返回 -1 return -1; } -/* 二分查找(左闭右开) */ -function binarySearch1(nums, target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - let i = 0, j = nums.length; +/* 二分查找(左闭右开区间) */ +function binarySearchLCRO(nums, target) { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + let i = 0, + j = nums.length; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { - let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整 - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 + // 计算中点索引 m ,使用 parseInt() 向下取整 + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 + else if (nums[m] > target) + // 此情况说明 target 在区间 [i, m) 中 j = m; - else // 找到目标元素,返回其索引 - return m; + // 找到目标元素,返回其索引 + else return m; } // 未找到目标元素,返回 -1 return -1; } /* Driver Code */ -var target = 6; -var nums = [1, 3, 6, 8, 12, 15, 23, 67, 70, 92]; +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分查找(双闭区间) */ -var index = binarySearch(nums, target); -console.log("目标元素 6 的索引 = " + index); +let index = binarySearch(nums, target); +console.log('目标元素 6 的索引 = ' + index); -/* 二分查找(左闭右开) */ -index = binarySearch1(nums, target); -console.log("目标元素 6 的索引 = " + index); +/* 二分查找(左闭右开区间) */ +index = binarySearchLCRO(nums, target); +console.log('目标元素 6 的索引 = ' + index); diff --git a/codes/javascript/chapter_searching/binary_search_edge.js b/codes/javascript/chapter_searching/binary_search_edge.js new file mode 100644 index 0000000000..30dcb5a20b --- /dev/null +++ b/codes/javascript/chapter_searching/binary_search_edge.js @@ -0,0 +1,45 @@ +/** + * File: binary_search_edge.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +const { binarySearchInsertion } = require('./binary_search_insertion.js'); + +/* 二分查找最左一个 target */ +function binarySearchLeftEdge(nums, target) { + // 等价于查找 target 的插入点 + const i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分查找最右一个 target */ +function binarySearchRightEdge(nums, target) { + // 转化为查找最左一个 target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + const j = i - 1; + // 未找到 target ,返回 -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +// 包含重复元素的数组 +const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n数组 nums = ' + nums); +// 二分查找左边界和右边界 +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('最左一个元素 ' + target + ' 的索引为 ' + index); + index = binarySearchRightEdge(nums, target); + console.log('最右一个元素 ' + target + ' 的索引为 ' + index); +} diff --git a/codes/javascript/chapter_searching/binary_search_insertion.js b/codes/javascript/chapter_searching/binary_search_insertion.js new file mode 100644 index 0000000000..af6544211b --- /dev/null +++ b/codes/javascript/chapter_searching/binary_search_insertion.js @@ -0,0 +1,64 @@ +/** + * File: binary_search_insertion.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分查找插入点(无重复元素) */ +function binarySearchInsertionSimple(nums, target) { + let i = 0, + j = nums.length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* 二分查找插入点(存在重复元素) */ +function binarySearchInsertion(nums, target) { + let i = 0, + j = nums.length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* Driver Code */ +// 无重复元素的数组 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n数组 nums = ' + nums); +// 二分查找插入点 +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('元素 ' + target + ' 的插入点的索引为 ' + index); +} + +// 包含重复元素的数组 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n数组 nums = ' + nums); +// 二分查找插入点 +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('元素 ' + target + ' 的插入点的索引为 ' + index); +} + +module.exports = { + binarySearchInsertion, +}; diff --git a/codes/javascript/chapter_searching/hashing_search.js b/codes/javascript/chapter_searching/hashing_search.js index ebe6a139f3..d5cc441f19 100644 --- a/codes/javascript/chapter_searching/hashing_search.js +++ b/codes/javascript/chapter_searching/hashing_search.js @@ -4,48 +4,42 @@ * Author: Zhuo Qinyue (1403450829@qq.com) */ -const PrintUtil = require("../include/PrintUtil"); -const ListNode = require("../include/ListNode"); - +const { arrToLinkedList } = require('../modules/ListNode'); /* 哈希查找(数组) */ -function hashingSearch(map, target) { +function hashingSearchArray(map, target) { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 return map.has(target) ? map.get(target) : -1; } /* 哈希查找(链表) */ -function hashingSearch1(map, target) { - // 哈希表的 key: 目标结点值,value: 结点对象 +function hashingSearchLinkedList(map, target) { + // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null return map.has(target) ? map.get(target) : null; } -function main() { - const target = 3; - - /* 哈希查找(数组) */ - const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; - // 初始化哈希表 - const map = new Map(); - for (let i = 0; i < nums.length; i++) { - map.set(nums[i], i); // key: 元素,value: 索引 - } - const index = hashingSearch(map, target); - console.log("目标元素 3 的索引 = " + index); +/* Driver Code */ +const target = 3; - /* 哈希查找(链表) */ - let head = new ListNode().arrToLinkedList(nums) - // 初始化哈希表 - const map1 = new Map(); - while (head != null) { - map1.set(head.val, head); // key: 结点值,value: 结点 - head = head.next; - } - const node = hashingSearch1(map1, target); - console.log("目标结点值 3 的对应结点对象为" ); - PrintUtil.printLinkedList(node); +/* 哈希查找(数组) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// 初始化哈希表 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 元素,value: 索引 } +const index = hashingSearchArray(map, target); +console.log('目标元素 3 的索引 = ' + index); -main(); +/* 哈希查找(链表) */ +let head = arrToLinkedList(nums); +// 初始化哈希表 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: 节点值,value: 节点 + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('目标节点值 3 的对应节点对象为', node); diff --git a/codes/javascript/chapter_searching/linear_search.js b/codes/javascript/chapter_searching/linear_search.js index b71f0cede8..9f62cc3a24 100644 --- a/codes/javascript/chapter_searching/linear_search.js +++ b/codes/javascript/chapter_searching/linear_search.js @@ -1,10 +1,10 @@ /** - * File: linear-search.js + * File: linear_search.js * Created Time: 2022-12-22 * Author: JoseHung (szhong@link.cuhk.edu.hk) */ -const ListNode = require("../include/ListNode"); +const { ListNode, arrToLinkedList } = require('../modules/ListNode'); /* 线性查找(数组) */ function linearSearchArray(nums, target) { @@ -16,33 +16,32 @@ function linearSearchArray(nums, target) { } } // 未找到目标元素,返回 -1 - return -1; + return -1; } /* 线性查找(链表)*/ function linearSearchLinkedList(head, target) { // 遍历链表 - while(head) { - // 找到目标结点,返回之 - if(head.val === target) { + while (head) { + // 找到目标节点,返回之 + if (head.val === target) { return head; } head = head.next; } - // 未找到目标结点,返回 null + // 未找到目标节点,返回 null return null; } /* Driver Code */ -var target = 3; +const target = 3; /* 在数组中执行线性查找 */ -var nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; -var index = linearSearchArray(nums, target); -console.log("目标元素 3 的索引 = " + index); +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('目标元素 3 的索引 = ' + index); /* 在链表中执行线性查找 */ -var linkedList = new ListNode(); -var head = linkedList.arrToLinkedList(nums); -var node = linearSearchLinkedList(head, target); -console.log("目标结点值 3 的对应结点对象为 " + node); +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('目标节点值 3 的对应节点对象为 ', node); diff --git a/codes/javascript/chapter_searching/two_sum.js b/codes/javascript/chapter_searching/two_sum.js new file mode 100644 index 0000000000..104775b74f --- /dev/null +++ b/codes/javascript/chapter_searching/two_sum.js @@ -0,0 +1,46 @@ +/** + * File: two_sum.js + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法一:暴力枚举 */ +function twoSumBruteForce(nums, target) { + const n = nums.length; + // 两层循环,时间复杂度为 O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法二:辅助哈希表 */ +function twoSumHashTable(nums, target) { + // 辅助哈希表,空间复杂度为 O(n) + let m = {}; + // 单层循环,时间复杂度为 O(n) + for (let i = 0; i < nums.length; i++) { + if (m[target - nums[i]] !== undefined) { + return [m[target - nums[i]], i]; + } else { + m[nums[i]] = i; + } + } + return []; +} + +/* Driver Code */ +// 方法一 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法一 res = ', res); + +// 方法二 +res = twoSumHashTable(nums, target); +console.log('方法二 res = ', res); diff --git a/codes/javascript/chapter_sorting/bubble_sort.js b/codes/javascript/chapter_sorting/bubble_sort.js index 3d3c6a83bd..3d68717778 100644 --- a/codes/javascript/chapter_sorting/bubble_sort.js +++ b/codes/javascript/chapter_sorting/bubble_sort.js @@ -1,20 +1,20 @@ /** - * File: quick_sort.js + * File: bubble_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 冒泡排序 */ function bubbleSort(nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; + // 交换 nums[j] 与 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; } } } @@ -22,28 +22,28 @@ function bubbleSort(nums) { /* 冒泡排序(标志优化)*/ function bubbleSortWithFlag(nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // 初始化标志位 - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] let tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; - flag = true; // 记录交换元素 + flag = true; // 记录交换元素 } } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } /* Driver Code */ -var nums = [4, 1, 3, 1, 5, 2] -bubbleSort(nums) -console.log("排序后数组 nums =", nums) +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('冒泡排序完成后 nums =', nums); -var nums1 = [4, 1, 3, 1, 5, 2] -bubbleSortWithFlag(nums1) -console.log("排序后数组 nums =", nums1) +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('冒泡排序完成后 nums =', nums1); diff --git a/codes/javascript/chapter_sorting/bucket_sort.js b/codes/javascript/chapter_sorting/bucket_sort.js new file mode 100644 index 0000000000..7b2c45bdff --- /dev/null +++ b/codes/javascript/chapter_sorting/bucket_sort.js @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 桶排序 */ +function bucketSort(nums) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + const k = nums.length / 2; + const buckets = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 将数组元素分配到各个桶中 + for (const num of nums) { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + const i = Math.floor(num * k); + // 将 num 添加进桶 i + buckets[i].push(num); + } + // 2. 对各个桶执行排序 + for (const bucket of buckets) { + // 使用内置排序函数,也可以替换成其他排序算法 + bucket.sort((a, b) => a - b); + } + // 3. 遍历桶合并结果 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('桶排序完成后 nums =', nums); diff --git a/codes/javascript/chapter_sorting/counting_sort.js b/codes/javascript/chapter_sorting/counting_sort.js new file mode 100644 index 0000000000..dee3c16037 --- /dev/null +++ b/codes/javascript/chapter_sorting/counting_sort.js @@ -0,0 +1,71 @@ +/** + * File: counting_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +function countingSortNaive(nums) { + // 1. 统计数组最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +function countingSort(nums) { + // 1. 统计数组最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + const n = nums.length; + const res = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('计数排序(无法排序对象)完成后 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('计数排序完成后 nums1 =', nums1); diff --git a/codes/javascript/chapter_sorting/heap_sort.js b/codes/javascript/chapter_sorting/heap_sort.js new file mode 100644 index 0000000000..03c5356b5c --- /dev/null +++ b/codes/javascript/chapter_sorting/heap_sort.js @@ -0,0 +1,49 @@ +/** + * File: heap_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +function siftDown(nums, n, i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let 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; + } + // 交换两节点 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // 循环向下堆化 + i = ma; + } +} + +/* 堆排序 */ +function heapSort(nums) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (let i = nums.length - 1; i > 0; i--) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('堆排序完成后 nums =', nums); diff --git a/codes/javascript/chapter_sorting/insertion_sort.js b/codes/javascript/chapter_sorting/insertion_sort.js index 02c45a237f..152eb5d11e 100644 --- a/codes/javascript/chapter_sorting/insertion_sort.js +++ b/codes/javascript/chapter_sorting/insertion_sort.js @@ -1,24 +1,25 @@ /** - * File: quick_sort.js + * File: insertion_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ /* 插入排序 */ function insertionSort(nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] + // 外循环:已排序区间为 [0, i-1] for (let i = 1; i < nums.length; i++) { - let base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 + let base = nums[i], + j = i - 1; + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 + nums[j + 1] = base; // 将 base 赋值到正确位置 } } /* Driver Code */ -var nums = [4, 1, 3, 1, 5, 2] -insertionSort(nums) -console.log("排序后数组 nums =", nums) +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('插入排序完成后 nums =', nums); diff --git a/codes/javascript/chapter_sorting/merge_sort.js b/codes/javascript/chapter_sorting/merge_sort.js index b00c17bcd2..107c09fb88 100644 --- a/codes/javascript/chapter_sorting/merge_sort.js +++ b/codes/javascript/chapter_sorting/merge_sort.js @@ -1,51 +1,52 @@ /** - * File: quick_sort.js + * File: merge_sort.js * Created Time: 2022-12-01 * Author: IsChristina (christinaxia77@foxmail.com) */ -/** -* 合并左子数组和右子数组 -* 左子数组区间 [left, mid] -* 右子数组区间 [mid + 1, right] -*/ +/* 合并左子数组和右子数组 */ function merge(nums, left, mid, right) { - // 初始化辅助数组 - let tmp = nums.slice(left, right + 1); - // 左子数组的起始索引和结束索引 - let leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - let rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - let i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (let k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) { - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if (j > rightEnd || tmp[i] <= tmp[j]) { - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + const tmp = new Array(right - left + 1); + // 初始化左子数组和右子数组的起始索引 + let i = left, + j = mid + 1, + k = 0; + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; } else { - nums[k] = tmp[j++]; + tmp[k++] = nums[j++]; } } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } } /* 归并排序 */ function mergeSort(nums, left, right) { // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 + if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 - let mid = Math.floor((left + right) / 2); // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 + let mid = Math.floor(left + (right - left) / 2); // 计算中点 + mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } /* Driver Code */ -var nums = [ 7, 3, 2, 6, 0, 1, 5, 4 ] -mergeSort(nums, 0, nums.length - 1) -console.log("归并排序完成后 nums =", nums) +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('归并排序完成后 nums =', nums); diff --git a/codes/javascript/chapter_sorting/quick_sort.js b/codes/javascript/chapter_sorting/quick_sort.js index 405bf76218..fd96b2d167 100644 --- a/codes/javascript/chapter_sorting/quick_sort.js +++ b/codes/javascript/chapter_sorting/quick_sort.js @@ -8,38 +8,39 @@ class QuickSort { /* 元素交换 */ swap(nums, i, j) { - let tmp = nums[i] - nums[i] = nums[j] - nums[j] = tmp + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; } /* 哨兵划分 */ - partition(nums, left, right){ - // 以 nums[left] 作为基准数 - let i = left, j = right - while(i < j){ - while(i < j && nums[j] >= nums[left]){ - j -= 1 // 从右向左找首个小于基准数的元素 + partition(nums, left, right) { + // 以 nums[left] 为基准数 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // 从右向左找首个小于基准数的元素 } - while(i < j && nums[i] <= nums[left]){ - i += 1 // 从左向右找首个大于基准数的元素 + while (i < j && nums[i] <= nums[left]) { + i += 1; // 从左向右找首个大于基准数的元素 } // 元素交换 - this.swap(nums, i, j) // 交换这两个元素 + this.swap(nums, i, j); // 交换这两个元素 } - this.swap(nums, i, left) // 将基准数交换至两子数组的分界线 - return i // 返回基准数的索引 + this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 } /* 快速排序 */ - quickSort(nums, left, right){ + quickSort(nums, left, right) { // 子数组长度为 1 时终止递归 - if(left >= right) return + if (left >= right) return; // 哨兵划分 - const pivot = this.partition(nums, left, right) + const pivot = this.partition(nums, left, right); // 递归左子数组、右子数组 - this.quickSort(nums, left, pivot - 1) - this.quickSort(nums, pivot + 1, right) + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); } } @@ -47,40 +48,44 @@ class QuickSort { class QuickSortMedian { /* 元素交换 */ swap(nums, i, j) { - let tmp = nums[i] - nums[i] = nums[j] - nums[j] = tmp + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; } - /* 选取三个元素的中位数 */ + /* 选取三个候选元素的中位数 */ medianThree(nums, left, mid, right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m 在 l 和 r 之间 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l 在 m 和 r 之间 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; } /* 哨兵划分(三数取中值) */ partition(nums, left, right) { // 选取三个候选元素的中位数 - let med = this.medianThree(nums, left, Math.floor((left + right) / 2), right); + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); // 将中位数交换至数组最左端 this.swap(nums, left, med); - // 以 nums[left] 作为基准数 - let i = left, j = right; + // 以 nums[left] 为基准数 + let i = left, + j = right; while (i < j) { - while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 - while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 + while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 this.swap(nums, i, j); // 交换这两个元素 } - this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 + this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 } /* 快速排序 */ @@ -99,23 +104,22 @@ class QuickSortMedian { class QuickSortTailCall { /* 元素交换 */ swap(nums, i, j) { - let tmp = nums[i] - nums[i] = nums[j] - nums[j] = tmp + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; } /* 哨兵划分 */ partition(nums, left, right) { - // 以 nums[left] 作为基准数 - let i = left, j = right; + // 以 nums[left] 为基准数 + let i = left, + j = right; while (i < j) { - while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 - while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 + while (i < j && nums[j] >= nums[left]) j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) i++; // 从左向右找首个大于基准数的元素 this.swap(nums, i, j); // 交换这两个元素 } - this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 + this.swap(nums, i, left); // 将基准数交换至两子数组的分界线 return i; // 返回基准数的索引 } @@ -125,13 +129,13 @@ class QuickSortTailCall { while (left < right) { // 哨兵划分操作 let pivot = this.partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 + // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { - this.quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] + this.quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } @@ -139,19 +143,19 @@ class QuickSortTailCall { /* Driver Code */ /* 快速排序 */ -var nums = [4, 1, 3, 1, 5, 2] -var quickSort = new QuickSort() -quickSort.quickSort(nums, 0, nums.length - 1) -console.log("快速排序完成后 nums =", nums) +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('快速排序完成后 nums =', nums); /* 快速排序(中位基准数优化) */ -nums1 = [4, 1, 3, 1, 5,2] -var quickSortMedian = new QuickSort() -quickSortMedian.quickSort(nums1, 0, nums1.length - 1) -console.log("快速排序(中位基准数优化)完成后 nums =", nums1) +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('快速排序(中位基准数优化)完成后 nums =', nums1); /* 快速排序(尾递归优化) */ -nums2 = [4, 1, 3, 1, 5, 2] -var quickSortTailCall = new QuickSort() -quickSortTailCall.quickSort(nums2, 0, nums2.length - 1) -console.log("快速排序(尾递归优化)完成后 nums =", nums2) +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('快速排序(尾递归优化)完成后 nums =', nums2); diff --git a/codes/javascript/chapter_sorting/radix_sort.js b/codes/javascript/chapter_sorting/radix_sort.js new file mode 100644 index 0000000000..7d9627db9d --- /dev/null +++ b/codes/javascript/chapter_sorting/radix_sort.js @@ -0,0 +1,66 @@ +/** + * File: radix_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +function digit(num, exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return Math.floor(num / exp) % 10; +} + +/* 计数排序(根据 nums 第 k 位排序) */ +function countingSortDigit(nums, exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + const counter = new Array(10).fill(0); + const n = nums.length; + // 统计 0~9 各数字的出现次数 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d]--; // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基数排序 */ +function radixSort(nums) { + // 获取数组的最大元素,用于判断最大位数 + let m = Number.MIN_VALUE; + for (const num of nums) { + if (num > m) { + m = num; + } + } + // 按照从低位到高位的顺序遍历 + for (let exp = 1; exp <= m; exp *= 10) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基数排序完成后 nums =', nums); diff --git a/codes/javascript/chapter_sorting/selection_sort.js b/codes/javascript/chapter_sorting/selection_sort.js new file mode 100644 index 0000000000..9383e23f84 --- /dev/null +++ b/codes/javascript/chapter_sorting/selection_sort.js @@ -0,0 +1,27 @@ +/** + * File: selection_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 选择排序 */ +function selectionSort(nums) { + let n = nums.length; + // 外循环:未排序区间为 [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 内循环:找到未排序区间内的最小元素 + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 记录最小元素的索引 + } + } + // 将该最小元素与未排序区间的首个元素交换 + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('选择排序完成后 nums =', nums); diff --git a/codes/javascript/chapter_stack_and_queue/array_deque.js b/codes/javascript/chapter_stack_and_queue/array_deque.js new file mode 100644 index 0000000000..0233c3a8f8 --- /dev/null +++ b/codes/javascript/chapter_stack_and_queue/array_deque.js @@ -0,0 +1,156 @@ +/** + * File: array_deque.js + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基于环形数组实现的双向队列 */ +class ArrayDeque { + #nums; // 用于存储双向队列元素的数组 + #front; // 队首指针,指向队首元素 + #queSize; // 双向队列长度 + + /* 构造方法 */ + constructor(capacity) { + this.#nums = new Array(capacity); + this.#front = 0; + this.#queSize = 0; + } + + /* 获取双向队列的容量 */ + capacity() { + return this.#nums.length; + } + + /* 获取双向队列的长度 */ + size() { + return this.#queSize; + } + + /* 判断双向队列是否为空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 计算环形数组索引 */ + index(i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + this.capacity()) % this.capacity(); + } + + /* 队首入队 */ + pushFirst(num) { + if (this.#queSize === this.capacity()) { + console.log('双向队列已满'); + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + this.#front = this.index(this.#front - 1); + // 将 num 添加至队首 + this.#nums[this.#front] = num; + this.#queSize++; + } + + /* 队尾入队 */ + pushLast(num) { + if (this.#queSize === this.capacity()) { + console.log('双向队列已满'); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + const rear = this.index(this.#front + this.#queSize); + // 将 num 添加至队尾 + this.#nums[rear] = num; + this.#queSize++; + } + + /* 队首出队 */ + popFirst() { + const num = this.peekFirst(); + // 队首指针向后移动一位 + this.#front = this.index(this.#front + 1); + this.#queSize--; + return num; + } + + /* 队尾出队 */ + popLast() { + const num = this.peekLast(); + this.#queSize--; + return num; + } + + /* 访问队首元素 */ + peekFirst() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.#nums[this.#front]; + } + + /* 访问队尾元素 */ + peekLast() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 计算尾元素索引 + const last = this.index(this.#front + this.#queSize - 1); + return this.#nums[last]; + } + + /* 返回数组用于打印 */ + toArray() { + // 仅转换有效长度范围内的列表元素 + const res = []; + for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { + res[i] = this.#nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 初始化双向队列 */ +const capacity = 5; +const deque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('双向队列 deque = [' + deque.toArray() + ']'); + +/* 访问元素 */ +const peekFirst = deque.peekFirst(); +console.log('队首元素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('队尾元素 peekLast = ' + peekLast); + +/* 元素入队 */ +deque.pushLast(4); +console.log('元素 4 队尾入队后 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('元素 1 队首入队后 deque = [' + deque.toArray() + ']'); + +/* 元素出队 */ +const popLast = deque.popLast(); +console.log( + '队尾出队元素 = ' + + popLast + + ',队尾出队后 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '队首出队元素 = ' + + popFirst + + ',队首出队后 deque = [' + + deque.toArray() + + ']' +); + +/* 获取双向队列的长度 */ +const size = deque.size(); +console.log('双向队列长度 size = ' + size); + +/* 判断双向队列是否为空 */ +const isEmpty = deque.isEmpty(); +console.log('双向队列是否为空 = ' + isEmpty); diff --git a/codes/javascript/chapter_stack_and_queue/array_queue.js b/codes/javascript/chapter_stack_and_queue/array_queue.js index abe0766d85..85f2500b6f 100644 --- a/codes/javascript/chapter_stack_and_queue/array_queue.js +++ b/codes/javascript/chapter_stack_and_queue/array_queue.js @@ -4,107 +4,103 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ - /* 基于环形数组实现的队列 */ class ArrayQueue { - #queue; // 用于存储队列元素的数组 - #front = 0; // 头指针,指向队首 - #rear = 0; // 尾指针,指向队尾 + 1 + #nums; // 用于存储队列元素的数组 + #front = 0; // 队首指针,指向队首元素 + #queSize = 0; // 队列长度 constructor(capacity) { - this.#queue = new Array(capacity); + this.#nums = new Array(capacity); } /* 获取队列的容量 */ get capacity() { - return this.#queue.length; + return this.#nums.length; } /* 获取队列的长度 */ get size() { - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (this.capacity + this.#rear - this.#front) % this.capacity; + return this.#queSize; } /* 判断队列是否为空 */ - empty() { - return this.#rear - this.#front == 0; + isEmpty() { + return this.#queSize === 0; } /* 入队 */ - offer(num) { - if (this.size == this.capacity) - throw new Error("队列已满"); - // 尾结点后添加 num - this.#queue[this.#rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - this.#rear = (this.#rear + 1) % this.capacity; + push(num) { + if (this.size === this.capacity) { + console.log('队列已满'); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + const rear = (this.#front + this.size) % this.capacity; + // 将 num 添加至队尾 + this.#nums[rear] = num; + this.#queSize++; } /* 出队 */ - poll() { + pop() { const num = this.peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 this.#front = (this.#front + 1) % this.capacity; + this.#queSize--; return num; } /* 访问队首元素 */ peek() { - if (this.empty()) - throw new Error("队列为空"); - return this.#queue[this.#front]; + if (this.isEmpty()) throw new Error('队列为空'); + return this.#nums[this.#front]; } /* 返回 Array */ toArray() { - const siz = this.size; - const cap = this.capacity; // 仅转换有效长度范围内的列表元素 - const arr = new Array(siz); - for (let i = 0, j = this.#front; i < siz; i++, j++) { - arr[i] = this.#queue[j % cap]; + const arr = new Array(this.size); + for (let i = 0, j = this.#front; i < this.size; i++, j++) { + arr[i] = this.#nums[j % this.capacity]; } return arr; } } - /* Driver Code */ /* 初始化队列 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 元素入队 */ -queue.offer(1); -queue.offer(3); -queue.offer(2); -queue.offer(5); -queue.offer(4); -console.log("队列 queue = "); -console.log(queue.toArray()); +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('队列 queue =', queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); -console.log("队首元素 peek = " + peek); +console.log('队首元素 peek = ' + peek); /* 元素出队 */ -const poll = queue.poll(); -console.log("出队元素 poll = " + poll + ",出队后 queue = "); -console.log(queue.toArray()); +const pop = queue.pop(); +console.log('出队元素 pop = ' + pop + ',出队后 queue =', queue.toArray()); /* 获取队列的长度 */ const size = queue.size; -console.log("队列长度 size = " + size); +console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ -const empty = queue.empty(); -console.log("队列是否为空 = " + empty); +const isEmpty = queue.isEmpty(); +console.log('队列是否为空 = ' + isEmpty); /* 测试环形数组 */ for (let i = 0; i < 10; i++) { - queue.offer(i); - queue.poll(); - console.log("第 " + i + " 轮入队 + 出队后 queue = "); - console.log(queue.toArray()); + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 轮入队 + 出队后 queue =', queue.toArray()); } diff --git a/codes/javascript/chapter_stack_and_queue/array_stack.js b/codes/javascript/chapter_stack_and_queue/array_stack.js index 37fa23b1f6..7fceeb1822 100644 --- a/codes/javascript/chapter_stack_and_queue/array_stack.js +++ b/codes/javascript/chapter_stack_and_queue/array_stack.js @@ -4,52 +4,47 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ - /* 基于数组实现的栈 */ class ArrayStack { - stack; + #stack; constructor() { - this.stack = []; + this.#stack = []; } - + /* 获取栈的长度 */ get size() { - return this.stack.length; + return this.#stack.length; } /* 判断栈是否为空 */ - empty() { - return this.stack.length === 0; + isEmpty() { + return this.#stack.length === 0; } /* 入栈 */ push(num) { - this.stack.push(num); + this.#stack.push(num); } /* 出栈 */ pop() { - if (this.empty()) - throw new Error("栈为空"); - return this.stack.pop(); + if (this.isEmpty()) throw new Error('栈为空'); + return this.#stack.pop(); } /* 访问栈顶元素 */ top() { - if (this.empty()) - throw new Error("栈为空"); - return this.stack[this.stack.length - 1]; + if (this.isEmpty()) throw new Error('栈为空'); + return this.#stack[this.#stack.length - 1]; } /* 返回 Array */ toArray() { - return this.stack; + return this.#stack; } -}; - +} /* Driver Code */ - /* 初始化栈 */ const stack = new ArrayStack(); @@ -59,22 +54,22 @@ stack.push(3); stack.push(2); stack.push(5); stack.push(4); -console.log("栈 stack = "); +console.log('栈 stack = '); console.log(stack.toArray()); /* 访问栈顶元素 */ const top = stack.top(); -console.log("栈顶元素 top = " + top); +console.log('栈顶元素 top = ' + top); /* 元素出栈 */ const pop = stack.pop(); -console.log("出栈元素 pop = " + pop + ",出栈后 stack = "); +console.log('出栈元素 pop = ' + pop + ',出栈后 stack = '); console.log(stack.toArray()); /* 获取栈的长度 */ const size = stack.size; -console.log("栈的长度 size = " + size); +console.log('栈的长度 size = ' + size); /* 判断是否为空 */ -const empty = stack.empty(); -console.log("栈是否为空 = " + empty); +const isEmpty = stack.isEmpty(); +console.log('栈是否为空 = ' + isEmpty); diff --git a/codes/javascript/chapter_stack_and_queue/deque.js b/codes/javascript/chapter_stack_and_queue/deque.js new file mode 100644 index 0000000000..2fae3efdb4 --- /dev/null +++ b/codes/javascript/chapter_stack_and_queue/deque.js @@ -0,0 +1,44 @@ +/** + * File: deque.js + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 初始化双向队列 */ +// JavaScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 +const deque = []; + +/* 元素入队 */ +deque.push(2); +deque.push(5); +deque.push(4); +// 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) +deque.unshift(3); +deque.unshift(1); +console.log('双向队列 deque = ', deque); + +/* 访问元素 */ +const peekFirst = deque[0]; +console.log('队首元素 peekFirst = ' + peekFirst); +const peekLast = deque[deque.length - 1]; +console.log('队尾元素 peekLast = ' + peekLast); + +/* 元素出队 */ +// 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) +const popFront = deque.shift(); +console.log( + '队首出队元素 popFront = ' + popFront + ',队首出队后 deque = ' + deque +); +const popBack = deque.pop(); +console.log( + '队尾出队元素 popBack = ' + popBack + ',队尾出队后 deque = ' + deque +); + +/* 获取双向队列的长度 */ +const size = deque.length; +console.log('双向队列长度 size = ' + size); + +/* 判断双向队列是否为空 */ +const isEmpty = size === 0; +console.log('双向队列是否为空 = ' + isEmpty); diff --git a/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js b/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js new file mode 100644 index 0000000000..884b2313b6 --- /dev/null +++ b/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.js + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 双向链表节点 */ +class ListNode { + prev; // 前驱节点引用 (指针) + next; // 后继节点引用 (指针) + val; // 节点值 + + constructor(val) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 基于双向链表实现的双向队列 */ +class LinkedListDeque { + #front; // 头节点 front + #rear; // 尾节点 rear + #queSize; // 双向队列的长度 + + constructor() { + this.#front = null; + this.#rear = null; + this.#queSize = 0; + } + + /* 队尾入队操作 */ + pushLast(val) { + const node = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 将 node 添加至链表尾部 + this.#rear.next = node; + node.prev = this.#rear; + this.#rear = node; // 更新尾节点 + } + this.#queSize++; + } + + /* 队首入队操作 */ + pushFirst(val) { + const node = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 将 node 添加至链表头部 + this.#front.prev = node; + node.next = this.#front; + this.#front = node; // 更新头节点 + } + this.#queSize++; + } + + /* 队尾出队操作 */ + popLast() { + if (this.#queSize === 0) { + return null; + } + const value = this.#rear.val; // 存储尾节点值 + // 删除尾节点 + let temp = this.#rear.prev; + if (temp !== null) { + temp.next = null; + this.#rear.prev = null; + } + this.#rear = temp; // 更新尾节点 + this.#queSize--; + return value; + } + + /* 队首出队操作 */ + popFirst() { + if (this.#queSize === 0) { + return null; + } + const value = this.#front.val; // 存储尾节点值 + // 删除头节点 + let temp = this.#front.next; + if (temp !== null) { + temp.prev = null; + this.#front.next = null; + } + this.#front = temp; // 更新头节点 + this.#queSize--; + return value; + } + + /* 访问队尾元素 */ + peekLast() { + return this.#queSize === 0 ? null : this.#rear.val; + } + + /* 访问队首元素 */ + peekFirst() { + return this.#queSize === 0 ? null : this.#front.val; + } + + /* 获取双向队列的长度 */ + size() { + return this.#queSize; + } + + /* 判断双向队列是否为空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 打印双向队列 */ + print() { + const arr = []; + let temp = this.#front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 初始化双向队列 */ +const linkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('双向队列 linkedListDeque = '); +linkedListDeque.print(); + +/* 访问元素 */ +const peekFirst = linkedListDeque.peekFirst(); +console.log('队首元素 peekFirst = ' + peekFirst); +const peekLast = linkedListDeque.peekLast(); +console.log('队尾元素 peekLast = ' + peekLast); + +/* 元素入队 */ +linkedListDeque.pushLast(4); +console.log('元素 4 队尾入队后 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('元素 1 队首入队后 linkedListDeque = '); +linkedListDeque.print(); + +/* 元素出队 */ +const popLast = linkedListDeque.popLast(); +console.log('队尾出队元素 = ' + popLast + ',队尾出队后 linkedListDeque = '); +linkedListDeque.print(); +const popFirst = linkedListDeque.popFirst(); +console.log('队首出队元素 = ' + popFirst + ',队首出队后 linkedListDeque = '); +linkedListDeque.print(); + +/* 获取双向队列的长度 */ +const size = linkedListDeque.size(); +console.log('双向队列长度 size = ' + size); + +/* 判断双向队列是否为空 */ +const isEmpty = linkedListDeque.isEmpty(); +console.log('双向队列是否为空 = ' + isEmpty); diff --git a/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js b/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js index 62c2a3834d..75dcf65d31 100644 --- a/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js +++ b/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js @@ -4,12 +4,12 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ -const ListNode = require("../include/ListNode"); +const { ListNode } = require('../modules/ListNode'); /* 基于链表实现的队列 */ class LinkedListQueue { - #front; // 头结点 #front - #rear; // 尾结点 #rear + #front; // 头节点 #front + #rear; // 尾节点 #rear #queSize = 0; constructor() { @@ -28,14 +28,14 @@ class LinkedListQueue { } /* 入队 */ - offer(num) { - // 尾结点后添加 num + push(num) { + // 在尾节点后添加 num const node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 + // 如果队列为空,则令头、尾节点都指向该节点 if (!this.#front) { this.#front = node; this.#rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 + // 如果队列不为空,则将该节点添加到尾节点后 } else { this.#rear.next = node; this.#rear = node; @@ -44,9 +44,9 @@ class LinkedListQueue { } /* 出队 */ - poll() { + pop() { const num = this.peek(); - // 删除头结点 + // 删除头节点 this.#front = this.#front.next; this.#queSize--; return num; @@ -54,8 +54,7 @@ class LinkedListQueue { /* 访问队首元素 */ peek() { - if (this.size === 0) - throw new Error("队列为空"); + if (this.size === 0) throw new Error('队列为空'); return this.#front.val; } @@ -71,32 +70,30 @@ class LinkedListQueue { } } - - /* Driver Code */ /* 初始化队列 */ const queue = new LinkedListQueue(); /* 元素入队 */ -queue.offer(1); -queue.offer(3); -queue.offer(2); -queue.offer(5); -queue.offer(4); -console.log("队列 queue = " + queue.toArray()); +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('队列 queue = ' + queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); -console.log("队首元素 peek = " + peek); +console.log('队首元素 peek = ' + peek); /* 元素出队 */ -const poll = queue.poll(); -console.log("出队元素 poll = " + poll + ",出队后 queue = " + queue.toArray()); +const pop = queue.pop(); +console.log('出队元素 pop = ' + pop + ',出队后 queue = ' + queue.toArray()); /* 获取队列的长度 */ const size = queue.size; -console.log("队列长度 size = " + size); +console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ const isEmpty = queue.isEmpty(); -console.log("队列是否为空 = " + isEmpty); +console.log('队列是否为空 = ' + isEmpty); diff --git a/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js b/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js index c6e9f27ce5..c5f9a53acb 100644 --- a/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js +++ b/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js @@ -4,12 +4,12 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ -const ListNode = require("../include/ListNode"); +const { ListNode } = require('../modules/ListNode'); /* 基于链表实现的栈 */ class LinkedListStack { - #stackPeek; // 将头结点作为栈顶 - #stkSize = 0; // 栈的长度 + #stackPeek; // 将头节点作为栈顶 + #stkSize = 0; // 栈的长度 constructor() { this.#stackPeek = null; @@ -22,7 +22,7 @@ class LinkedListStack { /* 判断栈是否为空 */ isEmpty() { - return this.size == 0; + return this.size === 0; } /* 入栈 */ @@ -43,8 +43,7 @@ class LinkedListStack { /* 访问栈顶元素 */ peek() { - if (!this.#stackPeek) - throw new Error("栈为空!"); + if (!this.#stackPeek) throw new Error('栈为空'); return this.#stackPeek.val; } @@ -60,7 +59,7 @@ class LinkedListStack { } } - +/* Driver Code */ /* 初始化栈 */ const stack = new LinkedListStack(); @@ -70,20 +69,20 @@ stack.push(3); stack.push(2); stack.push(5); stack.push(4); -console.log("栈 stack = " + stack.toArray()); +console.log('栈 stack = ' + stack.toArray()); /* 访问栈顶元素 */ const peek = stack.peek(); -console.log("栈顶元素 peek = " + peek); +console.log('栈顶元素 peek = ' + peek); /* 元素出栈 */ const pop = stack.pop(); -console.log("出栈元素 pop = " + pop + ",出栈后 stack = " + stack.toArray()); +console.log('出栈元素 pop = ' + pop + ',出栈后 stack = ' + stack.toArray()); /* 获取栈的长度 */ const size = stack.size; -console.log("栈的长度 size = " + size); +console.log('栈的长度 size = ' + size); /* 判断是否为空 */ const isEmpty = stack.isEmpty(); -console.log("栈是否为空 = " + isEmpty); +console.log('栈是否为空 = ' + isEmpty); diff --git a/codes/javascript/chapter_stack_and_queue/queue.js b/codes/javascript/chapter_stack_and_queue/queue.js index a443021cb4..1d4c751f04 100644 --- a/codes/javascript/chapter_stack_and_queue/queue.js +++ b/codes/javascript/chapter_stack_and_queue/queue.js @@ -4,7 +4,6 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ - /* Driver Code */ /* 初始化队列 */ // JavaScript 没有内置的队列,可以把 Array 当作队列来使用 @@ -16,16 +15,21 @@ queue.push(3); queue.push(2); queue.push(5); queue.push(4); +console.log('队列 queue =', queue); /* 访问队首元素 */ const peek = queue[0]; +console.log('队首元素 peek =', peek); /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) -const poll = queue.shift(); +const pop = queue.shift(); +console.log('出队元素 pop =', pop, ',出队后 queue = ', queue); /* 获取队列的长度 */ const size = queue.length; +console.log('队列长度 size =', size); /* 判断队列是否为空 */ -const empty = queue.length === 0; +const isEmpty = queue.length === 0; +console.log('队列是否为空 = ', isEmpty); diff --git a/codes/javascript/chapter_stack_and_queue/stack.js b/codes/javascript/chapter_stack_and_queue/stack.js index 1be7980e3a..debf7fa6d0 100644 --- a/codes/javascript/chapter_stack_and_queue/stack.js +++ b/codes/javascript/chapter_stack_and_queue/stack.js @@ -4,10 +4,9 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ - /* Driver Code */ /* 初始化栈 */ -// Javascript 没有内置的栈类,可以把 Array 当作栈来使用 +// JavaScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack = []; /* 元素入栈 */ @@ -16,21 +15,21 @@ stack.push(3); stack.push(2); stack.push(5); stack.push(4); -console.log("栈 stack =", stack) +console.log('栈 stack =', stack); /* 访问栈顶元素 */ const peek = stack[stack.length - 1]; -console.log("栈顶元素 peek =", peek) +console.log('栈顶元素 peek =', peek); /* 元素出栈 */ const pop = stack.pop(); -console.log("出栈元素 pop =", pop) -console.log("出栈后 stack =", stack) +console.log('出栈元素 pop =', pop); +console.log('出栈后 stack =', stack); /* 获取栈的长度 */ const size = stack.length; -console.log("栈的长度 size =", size) +console.log('栈的长度 size =', size); /* 判断是否为空 */ -const is_empty = stack.length === 0; -console.log("栈是否为空 =", is_empty) +const isEmpty = stack.length === 0; +console.log('栈是否为空 =', isEmpty); diff --git a/codes/javascript/chapter_tree/array_binary_tree.js b/codes/javascript/chapter_tree/array_binary_tree.js new file mode 100644 index 0000000000..00a9865927 --- /dev/null +++ b/codes/javascript/chapter_tree/array_binary_tree.js @@ -0,0 +1,147 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree { + #tree; + + /* 构造方法 */ + constructor(arr) { + this.#tree = arr; + } + + /* 列表容量 */ + size() { + return this.#tree.length; + } + + /* 获取索引为 i 节点的值 */ + val(i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + left(i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + right(i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 层序遍历 */ + levelOrder() { + let res = []; + // 直接遍历数组 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度优先遍历 */ + #dfs(i, order, res) { + // 若为空位,则返回 + if (this.val(i) === null) return; + // 前序遍历 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序遍历 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 后序遍历 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序遍历 */ + preOrder() { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序遍历 */ + inOrder() { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 后序遍历 */ + postOrder() { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 初始化二叉树 +// 这里借助了一个从数组直接生成二叉树的函数 +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n初始化二叉树\n'); +console.log('二叉树的数组表示:'); +console.log(arr); +console.log('二叉树的链表表示:'); +printTree(root); + +// 数组表示下的二叉树类 +const abt = new ArrayBinaryTree(arr); + +// 访问节点 +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n当前节点的索引为 ' + i + ' ,值为 ' + abt.val(i)); +console.log( + '其左子节点的索引为 ' + l + ' ,值为 ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + '其右子节点的索引为 ' + r + ' ,值为 ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + '其父节点的索引为 ' + p + ' ,值为 ' + (p === null ? 'null' : abt.val(p)) +); + +// 遍历树 +let res = abt.levelOrder(); +console.log('\n层序遍历为:' + res); +res = abt.preOrder(); +console.log('前序遍历为:' + res); +res = abt.inOrder(); +console.log('中序遍历为:' + res); +res = abt.postOrder(); +console.log('后序遍历为:' + res); diff --git a/codes/javascript/chapter_tree/avl_tree.js b/codes/javascript/chapter_tree/avl_tree.js new file mode 100644 index 0000000000..33dc0766f3 --- /dev/null +++ b/codes/javascript/chapter_tree/avl_tree.js @@ -0,0 +1,208 @@ +/** + * File: avl_tree.js + * Created Time: 2023-02-05 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* AVL 树*/ +class AVLTree { + /* 构造方法 */ + constructor() { + this.root = null; //根节点 + } + + /* 获取节点高度 */ + height(node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node === null ? -1 : node.height; + } + + /* 更新节点高度 */ + #updateHeight(node) { + // 节点高度等于最高子树高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 获取平衡因子 */ + balanceFactor(node) { + // 空节点平衡因子为 0 + if (node === null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return this.height(node.left) - this.height(node.right); + } + + /* 右旋操作 */ + #rightRotate(node) { + const child = node.left; + const grandChild = child.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 左旋操作 */ + #leftRotate(node) { + const child = node.right; + const grandChild = child.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + #rotate(node) { + // 获取节点 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏树 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.#rightRotate(node); + } else { + // 先左旋后右旋 + node.left = this.#leftRotate(node.left); + return this.#rightRotate(node); + } + } + // 右偏树 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.#leftRotate(node); + } else { + // 先右旋后左旋 + node.right = this.#rightRotate(node.right); + return this.#leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + + /* 插入节点 */ + insert(val) { + this.root = this.#insertHelper(this.root, val); + } + + /* 递归插入节点(辅助方法) */ + #insertHelper(node, val) { + if (node === null) return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) node.left = this.#insertHelper(node.left, val); + else if (val > node.val) + node.right = this.#insertHelper(node.right, val); + else return node; // 重复节点不插入,直接返回 + this.#updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.#rotate(node); + // 返回子树的根节点 + return node; + } + + /* 删除节点 */ + remove(val) { + this.root = this.#removeHelper(this.root, val); + } + + /* 递归删除节点(辅助方法) */ + #removeHelper(node, val) { + if (node === null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) node.left = this.#removeHelper(node.left, val); + else if (val > node.val) + node.right = this.#removeHelper(node.right, val); + else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child === null) return null; + // 子节点数量 = 1 ,直接删除 node + else node = child; + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.#removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.#updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.#rotate(node); + // 返回子树的根节点 + return node; + } + + /* 查找节点 */ + search(val) { + let cur = this.root; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 目标节点在 cur 的右子树中 + if (cur.val < val) cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > val) cur = cur.left; + // 找到目标节点,跳出循环 + else break; + } + // 返回目标节点 + return cur; + } +} + +function testInsert(tree, val) { + tree.insert(val); + console.log('\n插入节点 ' + val + ' 后,AVL 树为'); + printTree(tree.root); +} + +function testRemove(tree, val) { + tree.remove(val); + console.log('\n删除节点 ' + val + ' 后,AVL 树为'); + printTree(tree.root); +} + +/* Driver Code */ +/* 初始化空 AVL 树 */ +const avlTree = new AVLTree(); +/* 插入节点 */ +// 请关注插入节点后,AVL 树是如何保持平衡的 +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 插入重复节点 */ +testInsert(avlTree, 7); + +/* 删除节点 */ +// 请关注删除节点后,AVL 树是如何保持平衡的 +testRemove(avlTree, 8); // 删除度为 0 的节点 +testRemove(avlTree, 5); // 删除度为 1 的节点 +testRemove(avlTree, 4); // 删除度为 2 的节点 + +/* 查询节点 */ +const node = avlTree.search(7); +console.log('\n查找到的节点对象为', node, ',节点值 = ' + node.val); diff --git a/codes/javascript/chapter_tree/binary_search_tree.js b/codes/javascript/chapter_tree/binary_search_tree.js index 9c808aaae9..254eb5ade0 100644 --- a/codes/javascript/chapter_tree/binary_search_tree.js +++ b/codes/javascript/chapter_tree/binary_search_tree.js @@ -1,146 +1,139 @@ /** - * File: binary_tree.js + * File: binary_search_tree.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ -const Tree = require("../include/TreeNode"); -const { printTree } = require("../include/PrintUtil"); +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); /* 二叉搜索树 */ -var root; - -function BinarySearchTree(nums) { - nums.sort((a, b) => { return a - b }); // 排序数组 - root = buildTree(nums, 0, nums.length - 1); // 构建二叉搜索树 -} - -/* 获取二叉树根结点 */ -function getRoot() { - return root; -} - -/* 构建二叉搜索树 */ -function buildTree(nums, i, j) { - if (i > j) return null; - // 将数组中间结点作为根结点 - let mid = Math.floor((i + j) / 2); - let root = new Tree.TreeNode(nums[mid]); - // 递归建立左子树和右子树 - root.left = buildTree(nums, i, mid - 1); - root.right = buildTree(nums, mid + 1, j); - return root; -} - -/* 查找结点 */ -function search(num) { - let cur = root; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 目标结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 root 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; +class BinarySearchTree { + /* 构造方法 */ + constructor() { + // 初始化空树 + this.root = null; } - // 返回目标结点 - return cur; -} -/* 插入结点 */ -function insert(num) { - // 若树为空,直接提前返回 - if (root === null) return null; - let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到重复结点,直接返回 - if (cur.val === num) return null; - pre = cur; - // 插入位置在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 root 的左子树中 - else cur = cur.left; + /* 获取二叉树根节点 */ + getRoot() { + return this.root; } - // 插入结点 val - let node = new Tree.TreeNode(num); - if (pre.val < num) pre.right = node; - else pre.left = node; - return node; -} -/* 删除结点 */ -function remove(num) { - // 若树为空,直接提前返回 - if (root === null) return null; - let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到待删除结点,跳出循环 - if (cur.val === num) break; - pre = cur; - // 待删除结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 root 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur === null) return null; - // 子结点数量 = 0 or 1 - if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left === cur) pre.left = child; - else pre.right = child; + /* 查找节点 */ + search(num) { + let cur = this.root; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 目标节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) cur = cur.left; + // 找到目标节点,跳出循环 + else break; + } + // 返回目标节点 + return cur; } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - let nex = getInOrderNext(cur.right); - let tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; + + /* 插入节点 */ + insert(num) { + // 若树为空,则初始化根节点 + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur = this.root, + pre = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到重复节点,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子树中 + else cur = cur.left; + } + // 插入节点 + const node = new TreeNode(num); + if (pre.val < num) pre.right = node; + else pre.left = node; } - return cur; -} -/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ -function getInOrderNext(root) { - if (root === null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left !== null) { - root = root.left; + /* 删除节点 */ + remove(num) { + // 若树为空,直接提前返回 + if (this.root === null) return; + let cur = this.root, + pre = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到待删除节点,跳出循环 + if (cur.val === num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 待删除节点在 cur 的左子树中 + else cur = cur.left; + } + // 若无待删除节点,则直接返回 + if (cur === null) return; + // 子节点数量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + const child = cur.left !== null ? cur.left : cur.right; + // 删除节点 cur + if (cur !== this.root) { + if (pre.left === cur) pre.left = child; + else pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + this.root = child; + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + let tmp = cur.right; + while (tmp.left !== null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + this.remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } } - return root; } /* Driver Code */ /* 初始化二叉搜索树 */ -var nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; -BinarySearchTree(nums); -console.log("\n初始化的二叉树为\n"); -printTree(getRoot()); +const bst = new BinarySearchTree(); +// 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\n初始化的二叉树为\n'); +printTree(bst.getRoot()); -/* 查找结点 */ -let node = search(7); -console.log("\n查找到的结点对象为 " + node + ",结点值 = " + node.val); +/* 查找节点 */ +const node = bst.search(7); +console.log('\n查找到的节点对象为 ' + node + ',节点值 = ' + node.val); -/* 插入结点 */ -node = insert(16); -console.log("\n插入结点 16 后,二叉树为\n"); -printTree(getRoot()); +/* 插入节点 */ +bst.insert(16); +console.log('\n插入节点 16 后,二叉树为\n'); +printTree(bst.getRoot()); -/* 删除结点 */ -remove(1); -console.log("\n删除结点 1 后,二叉树为\n"); -printTree(getRoot()); -remove(2); -console.log("\n删除结点 2 后,二叉树为\n"); -printTree(getRoot()); -remove(4); -console.log("\n删除结点 4 后,二叉树为\n"); -printTree(getRoot()); +/* 删除节点 */ +bst.remove(1); +console.log('\n删除节点 1 后,二叉树为\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\n删除节点 2 后,二叉树为\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\n删除节点 4 后,二叉树为\n'); +printTree(bst.getRoot()); diff --git a/codes/javascript/chapter_tree/binary_tree.js b/codes/javascript/chapter_tree/binary_tree.js index 4f3d785251..2a4ca7d41a 100644 --- a/codes/javascript/chapter_tree/binary_tree.js +++ b/codes/javascript/chapter_tree/binary_tree.js @@ -4,32 +4,32 @@ * Author: IsChristina (christinaxia77@foxmail.com) */ -const Tree = require("../include/TreeNode"); -const { printTree } = require("../include/PrintUtil"); +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); /* 初始化二叉树 */ -// 初始化结点 -let n1 = new Tree.TreeNode(1), - n2 = new Tree.TreeNode(2), - n3 = new Tree.TreeNode(3), - n4 = new Tree.TreeNode(4), - n5 = new Tree.TreeNode(5); -// 构建引用指向(即指针) +// 初始化节点 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; -console.log("\n初始化二叉树\n"); +console.log('\n初始化二叉树\n'); printTree(n1); -/* 插入与删除结点 */ -let P = new Tree.TreeNode(0); -// 在 n1 -> n2 中间插入结点 P +/* 插入与删除节点 */ +const P = new TreeNode(0); +// 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; -console.log("\n插入结点 P 后\n"); +console.log('\n插入节点 P 后\n'); printTree(n1); -// 删除结点 P +// 删除节点 P n1.left = n2; -console.log("\n删除结点 P 后\n"); +console.log('\n删除节点 P 后\n'); printTree(n1); diff --git a/codes/javascript/chapter_tree/binary_tree_bfs.js b/codes/javascript/chapter_tree/binary_tree_bfs.js index 21daaa8dc6..d6ba8489b8 100644 --- a/codes/javascript/chapter_tree/binary_tree_bfs.js +++ b/codes/javascript/chapter_tree/binary_tree_bfs.js @@ -1,26 +1,23 @@ /** - * File: binary_tree.js + * File: binary_tree_bfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ -const { arrToTree } = require("../include/TreeNode"); -const { printTree } = require("../include/PrintUtil"); +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); /* 层序遍历 */ -function hierOrder(root) { - // 初始化队列,加入根结点 - let queue = [root]; +function levelOrder(root) { + // 初始化队列,加入根节点 + const queue = [root]; // 初始化一个列表,用于保存遍历序列 - let list = []; + const list = []; while (queue.length) { - let node = queue.shift(); // 队列出队 - list.push(node.val); // 保存结点 - if (node.left) - queue.push(node.left); // 左子结点入队 - if (node.right) - queue.push(node.right); // 右子结点入队 - + let node = queue.shift(); // 队列出队 + list.push(node.val); // 保存节点值 + if (node.left) queue.push(node.left); // 左子节点入队 + if (node.right) queue.push(node.right); // 右子节点入队 } return list; } @@ -28,10 +25,10 @@ function hierOrder(root) { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); -console.log("\n初始化二叉树\n"); +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树\n'); printTree(root); /* 层序遍历 */ -let list = hierOrder(root); -console.log("\n层序遍历的结点打印序列 = " + list); +const list = levelOrder(root); +console.log('\n层序遍历的节点打印序列 = ' + list); diff --git a/codes/javascript/chapter_tree/binary_tree_dfs.js b/codes/javascript/chapter_tree/binary_tree_dfs.js index d23b69b6de..8fffad6c2c 100644 --- a/codes/javascript/chapter_tree/binary_tree_dfs.js +++ b/codes/javascript/chapter_tree/binary_tree_dfs.js @@ -1,19 +1,19 @@ /** - * File: binary_tree.js + * File: binary_tree_dfs.js * Created Time: 2022-12-04 * Author: IsChristina (christinaxia77@foxmail.com) */ -const { arrToTree } = require("../include/TreeNode"); -const { printTree } = require("../include/PrintUtil"); +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); // 初始化列表,用于存储遍历序列 -var list = []; +const list = []; /* 前序遍历 */ function preOrder(root) { if (root === null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 + // 访问优先级:根节点 -> 左子树 -> 右子树 list.push(root.val); preOrder(root.left); preOrder(root.right); @@ -22,7 +22,7 @@ function preOrder(root) { /* 中序遍历 */ function inOrder(root) { if (root === null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 + // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root.left); list.push(root.val); inOrder(root.right); @@ -31,7 +31,7 @@ function inOrder(root) { /* 后序遍历 */ function postOrder(root) { if (root === null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 + // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root.left); postOrder(root.right); list.push(root.val); @@ -40,21 +40,21 @@ function postOrder(root) { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); -console.log("\n初始化二叉树\n"); +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树\n'); printTree(root); /* 前序遍历 */ list.length = 0; preOrder(root); -console.log("\n前序遍历的结点打印序列 = " + list); +console.log('\n前序遍历的节点打印序列 = ' + list); /* 中序遍历 */ list.length = 0; inOrder(root); -console.log("\n中序遍历的结点打印序列 = " + list); +console.log('\n中序遍历的节点打印序列 = ' + list); /* 后序遍历 */ list.length = 0; postOrder(root); -console.log("\n后序遍历的结点打印序列 = " + list); +console.log('\n后序遍历的节点打印序列 = ' + list); diff --git a/codes/javascript/include/ListNode.js b/codes/javascript/include/ListNode.js deleted file mode 100755 index 6e226e9c3c..0000000000 --- a/codes/javascript/include/ListNode.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * File: ListNode.js - * Created Time: 2022-12-12 - * Author: IsChristina (christinaxia77@foxmail.com) - */ - -/** - * Definition for a singly-linked list node - */ -class ListNode { - val; - next; - constructor(val, next) { - this.val = (val === undefined ? 0 : val); - this.next = (next === undefined ? null : next); - } - - /** - * Generate a linked list with an array - * @param arr - * @return - */ - arrToLinkedList(arr) { - const dum = new ListNode(0); - let head = dum; - for (const val of arr) { - head.next = new ListNode(val); - head = head.next; - } - return dum.next; - } - - /** - * Get a list node with specific value from a linked list - * @param head - * @param val - * @return - */ - getListNode(head, val) { - while (head !== null && head.val !== val) { - head = head.next; - } - return head; - } -} - -module.exports = ListNode diff --git a/codes/javascript/include/PrintUtil.js b/codes/javascript/include/PrintUtil.js deleted file mode 100644 index 3590f333b9..0000000000 --- a/codes/javascript/include/PrintUtil.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * File: PrintUtil.js - * Created Time: 2022-12-04 - * Author: IsChristina (christinaxia77@foxmail.com) - */ - -function Trunk(prev, str) { - this.prev = prev; - this.str = str; -} - -/** - * Print a linked list - * @param head - */ -function printLinkedList(head) { - let list = []; - while (head !== null) { - list.push(head.val.toString()); - head = head.next; - } - console.log(list.join(" -> ")); -} - -/** - * The interface of the tree printer - * This tree printer is borrowed from TECHIE DELIGHT - * https://www.techiedelight.com/c-program-print-binary-tree/ - * @param root - */ -function printTree(root) { - printTree(root, null, false); -} - -/** - * Print a binary tree - * @param root - * @param prev - * @param isLeft - */ -function printTree(root, prev, isLeft) { - if (root === null) { - return; - } - - let prev_str = " "; - let trunk = new Trunk(prev, prev_str); - - printTree(root.right, trunk, true); - - if (!prev) { - trunk.str = "———"; - } else if (isLeft) { - trunk.str = "/———"; - prev_str = " |"; - } else { - trunk.str = "\\———"; - prev.str = prev_str; - } - - showTrunks(trunk); - console.log(" " + root.val); - - if (prev) { - prev.str = prev_str; - } - trunk.str = " |"; - - printTree(root.left, trunk, false); -} - -/** - * Helper function to print branches of the binary tree - * @param p - */ -function showTrunks(p) { - if (!p) { - return; - } - - showTrunks(p.prev); - process.stdout.write(p.str); -} - -module.exports = { - printTree, - printLinkedList, -} diff --git a/codes/javascript/include/TreeNode.js b/codes/javascript/include/TreeNode.js deleted file mode 100644 index ce6d667bf0..0000000000 --- a/codes/javascript/include/TreeNode.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * File: TreeNode.js - * Created Time: 2022-12-04 - * Author: IsChristina (christinaxia77@foxmail.com) - */ - -/** - * Definition for a binary tree node. - */ -function TreeNode(val, left, right) { - this.val = (val === undefined ? 0 : val); // 结点值 - this.left = (left === undefined ? null : left); // 左子结点指针 - this.right = (right === undefined ? null : right); // 右子结点指针 -} - -/** -* Generate a binary tree given an array -* @param arr -* @return -*/ -function arrToTree(arr) { - if (arr.length === 0) - return null; - - let root = new TreeNode(arr[0]); - let queue = [root] - let i = 0; - while (queue.length) { - let node = queue.shift(); - if (++i >= arr.length) break; - if (arr[i] !== null) { - node.left = new TreeNode(arr[i]); - queue.push(node.left); - } - if (++i >= arr.length) break; - if (arr[i] !== null) { - node.right = new TreeNode(arr[i]); - queue.push(node.right); - } - } - - return root; -} - -module.exports = { - TreeNode, - arrToTree, -} \ No newline at end of file diff --git a/codes/javascript/modules/ListNode.js b/codes/javascript/modules/ListNode.js new file mode 100755 index 0000000000..5a7cd0c222 --- /dev/null +++ b/codes/javascript/modules/ListNode.js @@ -0,0 +1,31 @@ +/** + * File: ListNode.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 链表节点 */ +class ListNode { + val; // 节点值 + next; // 指向下一节点的引用(指针) + constructor(val, next) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* 将列表反序列化为链表 */ +function arrToLinkedList(arr) { + const dum = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +module.exports = { + ListNode, + arrToLinkedList, +}; diff --git a/codes/javascript/modules/PrintUtil.js b/codes/javascript/modules/PrintUtil.js new file mode 100644 index 0000000000..77787d3f0b --- /dev/null +++ b/codes/javascript/modules/PrintUtil.js @@ -0,0 +1,86 @@ +/** + * File: PrintUtil.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('./TreeNode'); + +/* 打印链表 */ +function printLinkedList(head) { + let list = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +function Trunk(prev, str) { + this.prev = prev; + this.str = str; +} + +/** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root) { + printTree(root, null, false); +} + +/* 打印二叉树 */ +function printTree(root, prev, isRight) { + if (root === null) { + return; + } + + let prev_str = ' '; + let trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (!prev) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +function showTrunks(p) { + if (!p) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* 打印堆 */ +function printHeap(arr) { + console.log('堆的数组表示:'); + console.log(arr); + console.log('堆的树状表示:'); + printTree(arrToTree(arr)); +} + +module.exports = { + printLinkedList, + printTree, + printHeap, +}; diff --git a/codes/javascript/modules/TreeNode.js b/codes/javascript/modules/TreeNode.js new file mode 100644 index 0000000000..d3263c35ea --- /dev/null +++ b/codes/javascript/modules/TreeNode.js @@ -0,0 +1,35 @@ +/** + * File: TreeNode.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 二叉树节点 */ +class TreeNode { + val; // 节点值 + left; // 左子节点指针 + right; // 右子节点指针 + height; //节点高度 + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + this.height = height === undefined ? 0 : height; + } +} + +/* 将数组反序列化为二叉树 */ +function arrToTree(arr, i = 0) { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +module.exports = { + TreeNode, + arrToTree, +}; diff --git a/codes/javascript/modules/Vertex.js b/codes/javascript/modules/Vertex.js new file mode 100644 index 0000000000..e20619a28e --- /dev/null +++ b/codes/javascript/modules/Vertex.js @@ -0,0 +1,35 @@ +/** + * File: Vertex.js + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 顶点类 */ +class Vertex { + val; + constructor(val) { + this.val = val; + } + + /* 输入值列表 vals ,返回顶点列表 vets */ + static valsToVets(vals) { + const vets = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 输入顶点列表 vets ,返回值列表 vals */ + static vetsToVals(vets) { + const vals = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +module.exports = { + Vertex, +}; diff --git a/codes/javascript/test_all.js b/codes/javascript/test_all.js new file mode 100644 index 0000000000..b43ef49f5f --- /dev/null +++ b/codes/javascript/test_all.js @@ -0,0 +1,63 @@ +import { bold, brightRed } from 'jsr:@std/fmt/colors'; +import { expandGlob } from 'jsr:@std/fs'; +import { relative, resolve } from 'jsr:@std/path'; + +/** + * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry + * @type {WalkEntry[]} + */ +const entries = []; + +for await (const entry of expandGlob( + resolve(import.meta.dirname, './chapter_*/*.js') +)) { + entries.push(entry); +} + +/** @type {{ status: Promise; stderr: ReadableStream; }[]} */ +const processes = []; + +for (const file of entries) { + const execute = new Deno.Command('node', { + args: [relative(import.meta.dirname, file.path)], + cwd: import.meta.dirname, + stdin: 'piped', + stdout: 'piped', + stderr: 'piped', + }); + + const process = execute.spawn(); + processes.push({ status: process.status, stderr: process.stderr }); +} + +const results = await Promise.all( + processes.map(async (item) => { + const status = await item.status; + return { status, stderr: item.stderr }; + }) +); + +/** @type {ReadableStream[]} */ +const errors = []; + +for (const result of results) { + if (!result.status.success) { + errors.push(result.stderr); + } +} + +console.log(`Tested ${entries.length} files`); +console.log(`Found exception in ${errors.length} files`); + +if (errors.length) { + console.log(); + + for (const error of errors) { + const reader = error.getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); + } + + throw new Error('Test failed'); +} diff --git a/codes/kotlin/chapter_array_and_linkedlist/array.kt b/codes/kotlin/chapter_array_and_linkedlist/array.kt new file mode 100644 index 0000000000..90adea53c9 --- /dev/null +++ b/codes/kotlin/chapter_array_and_linkedlist/array.kt @@ -0,0 +1,102 @@ +/** + * File: array.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_array_and_linkedlist + +import java.util.concurrent.ThreadLocalRandom + +/* 随机访问元素 */ +fun randomAccess(nums: IntArray): Int { + // 在区间 [0, nums.size) 中随机抽取一个数字 + val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) + // 获取并返回随机元素 + val randomNum = nums[randomIndex] + return randomNum +} + +/* 扩展数组长度 */ +fun extend(nums: IntArray, enlarge: Int): IntArray { + // 初始化一个扩展长度后的数组 + val res = IntArray(nums.size + enlarge) + // 将原数组中的所有元素复制到新数组 + for (i in nums.indices) { + res[i] = nums[i] + } + // 返回扩展后的新数组 + return res +} + +/* 在数组的索引 index 处插入元素 num */ +fun insert(nums: IntArray, num: Int, index: Int) { + // 把索引 index 以及之后的所有元素向后移动一位 + for (i in nums.size - 1 downTo index + 1) { + nums[i] = nums[i - 1] + } + // 将 num 赋给 index 处的元素 + nums[index] = num +} + +/* 删除索引 index 处的元素 */ +fun remove(nums: IntArray, index: Int) { + // 把索引 index 之后的所有元素向前移动一位 + for (i in index.. P -> n1 + val p = n0.next + val n1 = p?.next + n0.next = n1 +} + +/* 访问链表中索引为 index 的节点 */ +fun access(head: ListNode?, index: Int): ListNode? { + var h = head + for (i in 0..= size) + throw IndexOutOfBoundsException("索引越界") + return arr[index] + } + + /* 更新元素 */ + fun set(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + arr[index] = num + } + + /* 在尾部添加元素 */ + fun add(num: Int) { + // 元素数量超出容量时,触发扩容机制 + if (size == capacity()) + extendCapacity() + arr[size] = num + // 更新元素数量 + size++ + } + + /* 在中间插入元素 */ + fun insert(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + // 元素数量超出容量时,触发扩容机制 + if (size == capacity()) + extendCapacity() + // 将索引 index 以及之后的元素都向后移动一位 + for (j in size - 1 downTo index) + arr[j + 1] = arr[j] + arr[index] = num + // 更新元素数量 + size++ + } + + /* 删除元素 */ + fun remove(index: Int): Int { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + val num = arr[index] + // 将将索引 index 之后的元素都向前移动一位 + for (j in index..>, + res: MutableList>?>, + cols: BooleanArray, + diags1: BooleanArray, + diags2: BooleanArray +) { + // 当放置完所有行时,记录解 + if (row == n) { + val copyState = mutableListOf>() + for (sRow in state) { + copyState.add(sRow.toMutableList()) + } + res.add(copyState) + return + } + // 遍历所有列 + for (col in 0..>?> { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + val state = mutableListOf>() + for (i in 0..() + for (j in 0..>?>() + + backtrack(0, n, state, res, cols, diags1, diags2) + + return res +} + +/* Driver Code */ +fun main() { + val n = 4 + val res = nQueens(n) + + println("输入棋盘长宽为 $n") + println("皇后放置方案共有 ${res.size} 种") + for (state in res) { + println("--------------------") + for (row in state!!) { + println(row) + } + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/permutations_i.kt b/codes/kotlin/chapter_backtracking/permutations_i.kt new file mode 100644 index 0000000000..d8724c9476 --- /dev/null +++ b/codes/kotlin/chapter_backtracking/permutations_i.kt @@ -0,0 +1,53 @@ +/** + * File: permutations_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_i + +/* 回溯算法:全排列 I */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 当状态长度等于元素数量时,记录解 + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // 遍历所有选择 + for (i in choices.indices) { + val choice = choices[i] + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true + state.add(choice) + // 进行下一轮选择 + backtrack(state, choices, selected, res) + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全排列 I */ +fun permutationsI(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 3) + + val res = permutationsI(nums) + + println("输入数组 nums = ${nums.contentToString()}") + println("所有排列 res = $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/permutations_ii.kt b/codes/kotlin/chapter_backtracking/permutations_ii.kt new file mode 100644 index 0000000000..6f893a9109 --- /dev/null +++ b/codes/kotlin/chapter_backtracking/permutations_ii.kt @@ -0,0 +1,54 @@ +/** + * File: permutations_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_ii + +/* 回溯算法:全排列 II */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 当状态长度等于元素数量时,记录解 + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // 遍历所有选择 + val duplicated = HashSet() + for (i in choices.indices) { + val choice = choices[i] + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 尝试:做出选择,更新状态 + duplicated.add(choice) // 记录选择过的元素值 + selected[i] = true + state.add(choice) + // 进行下一轮选择 + backtrack(state, choices, selected, res) + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全排列 II */ +fun permutationsII(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 2) + val res = permutationsII(nums) + + println("输入数组 nums = ${nums.contentToString()}") + println("所有排列 res = $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt b/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt new file mode 100644 index 0000000000..676ec18c47 --- /dev/null +++ b/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_i_compact + +import utils.TreeNode +import utils.printTree + +var res: MutableList? = null + +/* 前序遍历:例题一 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + if (root._val == 7) { + // 记录解 + res!!.add(root) + } + preOrder(root.left) + preOrder(root.right) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二叉树") + printTree(root) + + // 前序遍历 + res = mutableListOf() + preOrder(root) + + println("\n输出所有值为 7 的节点") + val vals = mutableListOf() + for (node in res!!) { + vals.add(node._val) + } + println(vals) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt b/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt new file mode 100644 index 0000000000..eff1ec531b --- /dev/null +++ b/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_ii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前序遍历:例题二 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + // 尝试 + path!!.add(root) + if (root._val == 7) { + // 记录解 + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // 回退 + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二叉树") + printTree(root) + + // 前序遍历 + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\n输出所有根节点到节点 7 的路径") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt b/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt new file mode 100644 index 0000000000..ff10f3b010 --- /dev/null +++ b/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前序遍历:例题三 */ +fun preOrder(root: TreeNode?) { + // 剪枝 + if (root == null || root._val == 3) { + return + } + // 尝试 + path!!.add(root) + if (root._val == 7) { + // 记录解 + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // 回退 + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二叉树") + printTree(root) + + // 前序遍历 + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt b/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt new file mode 100644 index 0000000000..8ff0d476bb --- /dev/null +++ b/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt @@ -0,0 +1,82 @@ +/** + * File: preorder_traversal_iii_template.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_template + +import utils.TreeNode +import utils.printTree + +/* 判断当前状态是否为解 */ +fun isSolution(state: MutableList): Boolean { + return state.isNotEmpty() && state[state.size - 1]?._val == 7 +} + +/* 记录解 */ +fun recordSolution(state: MutableList?, res: MutableList?>) { + res.add(state!!.toMutableList()) +} + +/* 判断在当前状态下,该选择是否合法 */ +fun isValid(state: MutableList?, choice: TreeNode?): Boolean { + return choice != null && choice._val != 3 +} + +/* 更新状态 */ +fun makeChoice(state: MutableList, choice: TreeNode?) { + state.add(choice) +} + +/* 恢复状态 */ +fun undoChoice(state: MutableList, choice: TreeNode?) { + state.removeLast() +} + +/* 回溯算法:例题三 */ +fun backtrack( + state: MutableList, + choices: MutableList, + res: MutableList?> +) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res) + } + // 遍历所有选择 + for (choice in choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice) + // 进行下一轮选择 + backtrack(state, mutableListOf(choice!!.left, choice.right), res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice) + } + } +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二叉树") + printTree(root) + + // 回溯算法 + val res = mutableListOf?>() + backtrack(mutableListOf(), mutableListOf(root), res) + + println("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点") + for (path in res) { + val vals = mutableListOf() + for (node in path!!) { + if (node != null) { + vals.add(node._val) + } + } + println(vals) + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/subset_sum_i.kt b/codes/kotlin/chapter_backtracking/subset_sum_i.kt new file mode 100644 index 0000000000..1fd1114e89 --- /dev/null +++ b/codes/kotlin/chapter_backtracking/subset_sum_i.kt @@ -0,0 +1,58 @@ +/** + * File: subset_sum_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i + +/* 回溯算法:子集和 I */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(state.toMutableList()) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (i in start..?> { + val state = mutableListOf() // 状态(子集) + nums.sort() // 对 nums 进行排序 + val start = 0 // 遍历起始点 + val res = mutableListOf?>() // 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + + val res = subsetSumI(nums, target) + + println("输入数组 nums = ${nums.contentToString()}, target = $target") + println("所有和等于 $target 的子集 res = $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt b/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt new file mode 100644 index 0000000000..9f48734c65 --- /dev/null +++ b/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i_native.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i_naive + +/* 回溯算法:子集和 I */ +fun backtrack( + state: MutableList, + target: Int, + total: Int, + choices: IntArray, + res: MutableList?> +) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.add(state.toMutableList()) + return + } + // 遍历所有选择 + for (i in choices.indices) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue + } + // 尝试:做出选择,更新元素和 total + state.add(choices[i]) + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res) + // 回退:撤销选择,恢复到之前的状态 + state.removeAt(state.size - 1) + } +} + +/* 求解子集和 I(包含重复子集) */ +fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // 状态(子集) + val total = 0 // 子集和 + val res = mutableListOf?>() // 结果列表(子集列表) + backtrack(state, target, total, nums, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + val res = subsetSumINaive(nums, target) + + println("输入数组 nums = ${nums.contentToString()}, target = $target") + println("所有和等于 $target 的子集 res = $res") + println("请注意,该方法输出的结果包含重复集合") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_backtracking/subset_sum_ii.kt b/codes/kotlin/chapter_backtracking/subset_sum_ii.kt new file mode 100644 index 0000000000..13323b7d9d --- /dev/null +++ b/codes/kotlin/chapter_backtracking/subset_sum_ii.kt @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_ii + +/* 回溯算法:子集和 II */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(state.toMutableList()) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (i in start.. start && choices[i] == choices[i - 1]) { + continue + } + // 尝试:做出选择,更新 target, start + state.add(choices[i]) + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res) + // 回退:撤销选择,恢复到之前的状态 + state.removeAt(state.size - 1) + } +} + +/* 求解子集和 II */ +fun subsetSumII(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // 状态(子集) + nums.sort() // 对 nums 进行排序 + val start = 0 // 遍历起始点 + val res = mutableListOf?>() // 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 4, 5) + val target = 9 + val res = subsetSumII(nums, target) + + println("输入数组 nums = ${nums.contentToString()}, target = $target") + println("所有和等于 $target 的子集 res = $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_computational_complexity/iteration.kt b/codes/kotlin/chapter_computational_complexity/iteration.kt new file mode 100644 index 0000000000..b04a6182d6 --- /dev/null +++ b/codes/kotlin/chapter_computational_complexity/iteration.kt @@ -0,0 +1,74 @@ +/** + * File: iteration.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.iteration + +/* for 循环 */ +fun forLoop(n: Int): Int { + var res = 0 + // 循环求和 1, 2, ..., n-1, n + for (i in 1..n) { + res += i + } + return res +} + +/* while 循环 */ +fun whileLoop(n: Int): Int { + var res = 0 + var i = 1 // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i + i++ // 更新条件变量 + } + return res +} + +/* while 循环(两次更新) */ +fun whileLoopII(n: Int): Int { + var res = 0 + var i = 1 // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i + // 更新条件变量 + i++ + i *= 2 + } + return res +} + +/* 双层 for 循环 */ +fun nestedForLoop(n: Int): String { + val res = StringBuilder() + // 循环 i = 1, 2, ..., n-1, n + for (i in 1..n) { + // 循环 j = 1, 2, ..., n-1, n + for (j in 1..n) { + res.append(" ($i, $j), ") + } + } + return res.toString() +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = forLoop(n) + println("\nfor 循环的求和结果 res = $res") + + res = whileLoop(n) + println("\nwhile 循环的求和结果 res = $res") + + res = whileLoopII(n) + println("\nwhile 循环 (两次更新) 求和结果 res = $res") + + val resStr = nestedForLoop(n) + println("\n双层 for 循环的遍历结果 $resStr") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_computational_complexity/recursion.kt b/codes/kotlin/chapter_computational_complexity/recursion.kt new file mode 100644 index 0000000000..0b7ac65a83 --- /dev/null +++ b/codes/kotlin/chapter_computational_complexity/recursion.kt @@ -0,0 +1,78 @@ +/** + * File: recursion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.recursion + +import java.util.* + +/* 递归 */ +fun recur(n: Int): Int { + // 终止条件 + if (n == 1) + return 1 + // 递: 递归调用 + val res = recur(n - 1) + // 归: 返回结果 + return n + res +} + +/* 使用迭代模拟递归 */ +fun forLoopRecur(n: Int): Int { + // 使用一个显式的栈来模拟系统调用栈 + val stack = Stack() + var res = 0 + // 递: 递归调用 + for (i in n downTo 0) { + // 通过“入栈操作”模拟“递” + stack.push(i) + } + // 归: 返回结果 + while (stack.isNotEmpty()) { + // 通过“出栈操作”模拟“归” + res += stack.pop() + } + // res = 1+2+3+...+n + return res +} + +/* 尾递归 */ +tailrec fun tailRecur(n: Int, res: Int): Int { + // 添加 tailrec 关键词,以开启尾递归优化 + // 终止条件 + if (n == 0) + return res + // 尾递归调用 + return tailRecur(n - 1, res + n) +} + +/* 斐波那契数列:递归 */ +fun fib(n: Int): Int { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1 + // 递归调用 f(n) = f(n-1) + f(n-2) + val res = fib(n - 1) + fib(n - 2) + // 返回结果 f(n) + return res +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = recur(n) + println("\n递归函数的求和结果 res = $res") + + res = forLoopRecur(n) + println("\n使用迭代模拟递归求和结果 res = $res") + + res = tailRecur(n, 0) + println("\n尾递归函数的求和结果 res = $res") + + res = fib(n) + println("\n斐波那契数列的第 $n 项为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_computational_complexity/space_complexity.kt b/codes/kotlin/chapter_computational_complexity/space_complexity.kt new file mode 100644 index 0000000000..60bfbc339b --- /dev/null +++ b/codes/kotlin/chapter_computational_complexity/space_complexity.kt @@ -0,0 +1,109 @@ +/** + * File: space_complexity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.space_complexity + +import utils.ListNode +import utils.TreeNode +import utils.printTree + +/* 函数 */ +fun function(): Int { + // 执行某些操作 + return 0 +} + +/* 常数阶 */ +fun constant(n: Int) { + // 常量、变量、对象占用 O(1) 空间 + val a = 0 + var b = 0 + val nums = Array(10000) { 0 } + val node = ListNode(0) + // 循环中的变量占用 O(1) 空间 + for (i in 0..() + for (i in 0..() + for (i in 0..?>(n) + // 二维列表占用 O(n^2) 空间 + val numList = mutableListOf>() + for (i in 0..() + for (j in 0.. nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + count += 3 // 元素交换包含 3 个单元操作 + } + } + } + return count +} + +/* 指数阶(循环实现) */ +fun exponential(n: Int): Int { + var count = 0 + var base = 1 + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for (i in 0.. 1) { + n1 /= 2 + count++ + } + return count +} + +/* 对数阶(递归实现) */ +fun logRecur(n: Int): Int { + if (n <= 1) + return 0 + return logRecur(n / 2) + 1 +} + +/* 线性对数阶 */ +fun linearLogRecur(n: Int): Int { + if (n <= 1) + return 1 + var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) + for (i in 0.. { + val nums = IntArray(n) + // 生成数组 nums = { 1, 2, 3, ..., n } + for (i in 0..(n) + for (i in 0..): Int { + for (i in nums.indices) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if (nums[i] == 1) + return i + } + return -1 +} + +/* Driver Code */ +fun main() { + for (i in 0..9) { + val n = 100 + val nums = randomNumbers(n) + val index = findOne(nums) + println("\n数组 [ 1, 2, ..., n ] 被打乱后 = ${nums.contentToString()}") + println("数字 1 的索引为 $index") + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt b/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt new file mode 100644 index 0000000000..433d327d80 --- /dev/null +++ b/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt @@ -0,0 +1,49 @@ +/** + * File: binary_search_recur.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.binary_search_recur + +/* 二分查找:问题 f(i, j) */ +fun dfs( + nums: IntArray, + target: Int, + i: Int, + j: Int +): Int { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1 + } + // 计算中点索引 m + val m = (i + j) / 2 + return if (nums[m] < target) { + // 递归子问题 f(m+1, j) + dfs(nums, target, m + 1, j) + } else if (nums[m] > target) { + // 递归子问题 f(i, m-1) + dfs(nums, target, i, m - 1) + } else { + // 找到目标元素,返回其索引 + m + } +} + +/* 二分查找 */ +fun binarySearch(nums: IntArray, target: Int): Int { + val n = nums.size + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + // 二分查找(双闭区间) + val index = binarySearch(nums, target) + println("目标元素 6 的索引 = $index") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_divide_and_conquer/build_tree.kt b/codes/kotlin/chapter_divide_and_conquer/build_tree.kt new file mode 100644 index 0000000000..9440494aa2 --- /dev/null +++ b/codes/kotlin/chapter_divide_and_conquer/build_tree.kt @@ -0,0 +1,55 @@ +/** + * File: build_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.build_tree + +import utils.TreeNode +import utils.printTree + +/* 构建二叉树:分治 */ +fun dfs( + preorder: IntArray, + inorderMap: Map, + i: Int, + l: Int, + r: Int +): TreeNode? { + // 子树区间为空时终止 + if (r - l < 0) return null + // 初始化根节点 + val root = TreeNode(preorder[i]) + // 查询 m ,从而划分左右子树 + val m = inorderMap[preorder[i]]!! + // 子问题:构建左子树 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) + // 子问题:构建右子树 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) + // 返回根节点 + return root +} + +/* 构建二叉树 */ +fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { + // 初始化哈希表,存储 inorder 元素到索引的映射 + val inorderMap = HashMap() + for (i in inorder.indices) { + inorderMap[inorder[i]] = i + } + val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) + return root +} + +/* Driver Code */ +fun main() { + val preorder = intArrayOf(3, 9, 2, 1, 7) + val inorder = intArrayOf(9, 3, 1, 2, 7) + println("前序遍历 = ${preorder.contentToString()}") + println("中序遍历 = ${inorder.contentToString()}") + + val root = buildTree(preorder, inorder) + println("构建的二叉树为:") + printTree(root) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_divide_and_conquer/hanota.kt b/codes/kotlin/chapter_divide_and_conquer/hanota.kt new file mode 100644 index 0000000000..174e8be651 --- /dev/null +++ b/codes/kotlin/chapter_divide_and_conquer/hanota.kt @@ -0,0 +1,56 @@ +/** + * File: hanota.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.hanota + +/* 移动一个圆盘 */ +fun move(src: MutableList, tar: MutableList) { + // 从 src 顶部拿出一个圆盘 + val pan = src.removeAt(src.size - 1) + // 将圆盘放入 tar 顶部 + tar.add(pan) +} + +/* 求解汉诺塔问题 f(i) */ +fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar) + return + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf) + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar) + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar) +} + +/* 求解汉诺塔问题 */ +fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { + val n = A.size + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C) +} + +/* Driver Code */ +fun main() { + // 列表尾部是柱子顶部 + val A = mutableListOf(5, 4, 3, 2, 1) + val B = mutableListOf() + val C = mutableListOf() + println("初始状态下:") + println("A = $A") + println("B = $B") + println("C = $C") + + solveHanota(A, B, C) + + println("圆盘移动完成后:") + println("A = $A") + println("B = $B") + println("C = $C") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt new file mode 100644 index 0000000000..04bab9caab --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt @@ -0,0 +1,45 @@ +/** + * File: climbing_stairs_backtrack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 回溯 */ +fun backtrack( + choices: MutableList, + state: Int, + n: Int, + res: MutableList +) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res[0] = res[0] + 1 + // 遍历所有选择 + for (choice in choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) continue + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res) + // 回退 + } +} + +/* 爬楼梯:回溯 */ +fun climbingStairsBacktrack(n: Int): Int { + val choices = mutableListOf(1, 2) // 可选择向上爬 1 阶或 2 阶 + val state = 0 // 从第 0 阶开始爬 + val res = mutableListOf() + res.add(0) // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res) + return res[0] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsBacktrack(n) + println("爬 $n 阶楼梯共有 $res 种方案") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt new file mode 100644 index 0000000000..4dc70a1513 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt @@ -0,0 +1,35 @@ +/** + * File: climbing_stairs_constraint_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 带约束爬楼梯:动态规划 */ +fun climbingStairsConstraintDP(n: Int): Int { + if (n == 1 || n == 2) { + return 1 + } + // 初始化 dp 表,用于存储子问题的解 + val dp = Array(n + 1) { IntArray(3) } + // 初始状态:预设最小子问题的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状态转移:从较小子问题逐步求解较大子问题 + for (i in 3..n) { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsConstraintDP(n) + println("爬 $n 阶楼梯共有 $res 种方案") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt new file mode 100644 index 0000000000..ecb2345ad0 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt @@ -0,0 +1,29 @@ +/** + * File: climbing_stairs_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 搜索 */ +fun dfs(i: Int): Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) return i + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1) + dfs(i - 2) + return count +} + +/* 爬楼梯:搜索 */ +fun climbingStairsDFS(n: Int): Int { + return dfs(n) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFS(n) + println("爬 $n 阶楼梯共有 $res 种方案") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt new file mode 100644 index 0000000000..ec36f96afe --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_dfs_mem.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 记忆化搜索 */ +fun dfs(i: Int, mem: IntArray): Int { + // 已知 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] + val count = dfs(i - 1, mem) + dfs(i - 2, mem) + // 记录 dp[i] + mem[i] = count + return count +} + +/* 爬楼梯:记忆化搜索 */ +fun climbingStairsDFSMem(n: Int): Int { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + val mem = IntArray(n + 1) + mem.fill(-1) + return dfs(n, mem) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFSMem(n) + println("爬 $n 阶楼梯共有 $res 种方案") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt new file mode 100644 index 0000000000..5a029a30e4 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 爬楼梯:动态规划 */ +fun climbingStairsDP(n: Int): Int { + if (n == 1 || n == 2) return n + // 初始化 dp 表,用于存储子问题的解 + val dp = IntArray(n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for (i in 3..n) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 爬楼梯:空间优化后的动态规划 */ +fun climbingStairsDPComp(n: Int): Int { + if (n == 1 || n == 2) return n + var a = 1 + var b = 2 + for (i in 3..n) { + val temp = b + b += a + a = temp + } + return b +} + +/* Driver Code */ +fun main() { + val n = 9 + + var res = climbingStairsDP(n) + println("爬 $n 阶楼梯共有 $res 种方案") + + res = climbingStairsDPComp(n) + println("爬 $n 阶楼梯共有 $res 种方案") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/coin_change.kt b/codes/kotlin/chapter_dynamic_programming/coin_change.kt new file mode 100644 index 0000000000..c89bbf11b2 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/coin_change.kt @@ -0,0 +1,71 @@ +/** + * File: coin_change.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 零钱兑换:动态规划 */ +fun coinChangeDP(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 状态转移:首行首列 + for (a in 1..amt) { + dp[0][a] = MAX + } + // 状态转移:其余行和列 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return if (dp[n][amt] != MAX) dp[n][amt] else -1 +} + +/* 零钱兑换:空间优化后的动态规划 */ +fun coinChangeDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // 初始化 dp 表 + val dp = IntArray(amt + 1) + dp.fill(MAX) + dp[0] = 0 + // 状态转移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return if (dp[amt] != MAX) dp[amt] else -1 +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 4 + + // 动态规划 + var res = coinChangeDP(coins, amt) + println("凑到目标金额所需的最少硬币数量为 $res") + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins, amt) + println("凑到目标金额所需的最少硬币数量为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt b/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt new file mode 100644 index 0000000000..683a860557 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 零钱兑换 II:动态规划 */ +fun coinChangeIIDP(coins: IntArray, amt: Int): Int { + val n = coins.size + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 初始化首列 + for (i in 0..n) { + dp[i][0] = 1 + } + // 状态转移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + // 初始化 dp 表 + val dp = IntArray(amt + 1) + dp[0] = 1 + // 状态转移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 5 + + // 动态规划 + var res = coinChangeIIDP(coins, amt) + println("凑出目标金额的硬币组合数量为 $res") + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(coins, amt) + println("凑出目标金额的硬币组合数量为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/edit_distance.kt b/codes/kotlin/chapter_dynamic_programming/edit_distance.kt new file mode 100644 index 0000000000..00617d4032 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/edit_distance.kt @@ -0,0 +1,143 @@ +/** + * File: edit_distance.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 编辑距离:暴力搜索 */ +fun editDistanceDFS( + s: String, + t: String, + i: Int, + j: Int +): Int { + // 若 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 + val insert = editDistanceDFS(s, t, i, j - 1) + val delete = editDistanceDFS(s, t, i - 1, j) + val replace = editDistanceDFS(s, t, i - 1, j - 1) + // 返回最少编辑步数 + return min(min(insert, delete), replace) + 1 +} + +/* 编辑距离:记忆化搜索 */ +fun editDistanceDFSMem( + s: String, + t: String, + mem: Array, + i: Int, + j: Int +): Int { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) return 0 + // 若 s 为空,则返回 t 长度 + if (i == 0) return j + // 若 t 为空,则返回 s 长度 + if (j == 0) return i + // 若已有记录,则直接返回之 + if (mem[i][j] != -1) return mem[i][j] + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + val insert = editDistanceDFSMem(s, t, mem, i, j - 1) + val delete = editDistanceDFSMem(s, t, mem, i - 1, j) + val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 记录并返回最少编辑步数 + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 编辑距离:动态规划 */ +fun editDistanceDP(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = Array(n + 1) { IntArray(m + 1) } + // 状态转移:首行首列 + for (i in 1..n) { + dp[i][0] = i + } + for (j in 1..m) { + dp[0][j] = j + } + // 状态转移:其余行和列 + for (i in 1..n) { + for (j in 1..m) { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* 编辑距离:空间优化后的动态规划 */ +fun editDistanceDPComp(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = IntArray(m + 1) + // 状态转移:首行 + for (j in 1..m) { + dp[j] = j + } + // 状态转移:其余行 + for (i in 1..n) { + // 状态转移:首列 + var leftup = dp[0] // 暂存 dp[i-1, j-1] + dp[0] = i + // 状态转移:其余列 + for (j in 1..m) { + val temp = dp[j] + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m] +} + +/* Driver Code */ +fun main() { + val s = "bag" + val t = "pack" + val n = s.length + val m = t.length + + // 暴力搜索 + var res = editDistanceDFS(s, t, n, m) + println("将 $s 更改为 $t 最少需要编辑 $res 步") + + // 记忆化搜索 + val mem = Array(n + 1) { IntArray(m + 1) } + for (row in mem) + row.fill(-1) + res = editDistanceDFSMem(s, t, mem, n, m) + println("将 $s 更改为 $t 最少需要编辑 $res 步") + + // 动态规划 + res = editDistanceDP(s, t) + println("将 $s 更改为 $t 最少需要编辑 $res 步") + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t) + println("将 $s 更改为 $t 最少需要编辑 $res 步") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/knapsack.kt b/codes/kotlin/chapter_dynamic_programming/knapsack.kt new file mode 100644 index 0000000000..be7225f09a --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/knapsack.kt @@ -0,0 +1,125 @@ +/** + * File: knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.max + +/* 0-1 背包:暴力搜索 */ +fun knapsackDFS( + wgt: IntArray, + _val: IntArray, + i: Int, + c: Int +): Int { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0 + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, _val, i - 1, c) + } + // 计算不放入和放入物品 i 的最大价值 + val no = knapsackDFS(wgt, _val, i - 1, c) + val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] + // 返回两种方案中价值更大的那一个 + return max(no, yes) +} + +/* 0-1 背包:记忆化搜索 */ +fun knapsackDFSMem( + wgt: IntArray, + _val: IntArray, + mem: Array, + i: Int, + c: Int +): Int { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0 + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c] + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, _val, mem, i - 1, c) + } + // 计算不放入和放入物品 i 的最大价值 + val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) + val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* 0-1 背包:动态规划 */ +fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(cap + 1) } + // 状态转移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空间优化后的动态规划 */ +fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = IntArray(cap + 1) + // 状态转移 + for (i in 1..n) { + // 倒序遍历 + for (c in cap downTo 1) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + val n = wgt.size + + // 暴力搜索 + var res = knapsackDFS(wgt, _val, n, cap) + println("不超过背包容量的最大物品价值为 $res") + + // 记忆化搜索 + val mem = Array(n + 1) { IntArray(cap + 1) } + for (row in mem) { + row.fill(-1) + } + res = knapsackDFSMem(wgt, _val, mem, n, cap) + println("不超过背包容量的最大物品价值为 $res") + + // 动态规划 + res = knapsackDP(wgt, _val, cap) + println("不超过背包容量的最大物品价值为 $res") + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt, _val, cap) + println("不超过背包容量的最大物品价值为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt b/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt new file mode 100644 index 0000000000..914d4335b6 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 爬楼梯最小代价:动态规划 */ +fun minCostClimbingStairsDP(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + // 初始化 dp 表,用于存储子问题的解 + val dp = IntArray(n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 状态转移:从较小子问题逐步求解较大子问题 + for (i in 3..n) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +fun minCostClimbingStairsDPComp(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + var a = cost[1] + var b = cost[2] + for (i in 3..n) { + val tmp = b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} + +/* Driver Code */ +fun main() { + val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) + println("输入楼梯的代价列表为 ${cost.contentToString()}") + + var res = minCostClimbingStairsDP(cost) + println("爬完楼梯的最低代价为 $res") + + res = minCostClimbingStairsDPComp(cost) + println("爬完楼梯的最低代价为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt b/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt new file mode 100644 index 0000000000..860cbb90f8 --- /dev/null +++ b/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 最小路径和:暴力搜索 */ +fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + val up = minPathSumDFS(grid, i - 1, j) + val left = minPathSumDFS(grid, i, j - 1) + // 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j] +} + +/* 最小路径和:记忆化搜索 */ +fun minPathSumDFSMem( + grid: Array, + mem: Array, + i: Int, + j: Int +): Int { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j] + } + // 左边和上边单元格的最小路径代价 + val up = minPathSumDFSMem(grid, mem, i - 1, j) + val left = minPathSumDFSMem(grid, mem, i, j - 1) + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* 最小路径和:动态规划 */ +fun minPathSumDP(grid: Array): Int { + val n = grid.size + val m = grid[0].size + // 初始化 dp 表 + val dp = Array(n) { IntArray(m) } + dp[0][0] = grid[0][0] + // 状态转移:首行 + for (j in 1..): Int { + val n = grid.size + val m = grid[0].size + // 初始化 dp 表 + val dp = IntArray(m) + // 状态转移:首行 + dp[0] = grid[0][0] + for (j in 1.. c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 完全背包:空间优化后的动态规划 */ +fun unboundedKnapsackDPComp( + wgt: IntArray, + _val: IntArray, + cap: Int +): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = IntArray(cap + 1) + // 状态转移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(1, 2, 3) + val _val = intArrayOf(5, 11, 15) + val cap = 4 + + // 动态规划 + var res = unboundedKnapsackDP(wgt, _val, cap) + println("不超过背包容量的最大物品价值为 $res") + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(wgt, _val, cap) + println("不超过背包容量的最大物品价值为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_graph/graph_adjacency_list.kt b/codes/kotlin/chapter_graph/graph_adjacency_list.kt new file mode 100644 index 0000000000..47024610dc --- /dev/null +++ b/codes/kotlin/chapter_graph/graph_adjacency_list.kt @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 基于邻接表实现的无向图类 */ +class GraphAdjList(edges: Array>) { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + val adjList = HashMap>() + + /* 构造方法 */ + init { + // 添加所有顶点和边 + for (edge in edges) { + addVertex(edge[0]!!) + addVertex(edge[1]!!) + addEdge(edge[0]!!, edge[1]!!) + } + } + + /* 获取顶点数量 */ + fun size(): Int { + return adjList.size + } + + /* 添加边 */ + fun addEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 添加边 vet1 - vet2 + adjList[vet1]?.add(vet2) + adjList[vet2]?.add(vet1) + } + + /* 删除边 */ + fun removeEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 删除边 vet1 - vet2 + adjList[vet1]?.remove(vet2) + adjList[vet2]?.remove(vet1) + } + + /* 添加顶点 */ + fun addVertex(vet: Vertex) { + if (adjList.containsKey(vet)) + return + // 在邻接表中添加一个新链表 + adjList[vet] = mutableListOf() + } + + /* 删除顶点 */ + fun removeVertex(vet: Vertex) { + if (!adjList.containsKey(vet)) + throw IllegalArgumentException() + // 在邻接表中删除顶点 vet 对应的链表 + adjList.remove(vet) + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (list in adjList.values) { + list.remove(vet) + } + } + + /* 打印邻接表 */ + fun print() { + println("邻接表 =") + for (pair in adjList.entries) { + val tmp = mutableListOf() + for (vertex in pair.value) { + tmp.add(vertex._val) + } + println("${pair.key._val}: $tmp,") + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化无向图 */ + val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[3]), + arrayOf(v[2], v[4]), + arrayOf(v[3], v[4]) + ) + val graph = GraphAdjList(edges) + println("\n初始化后,图为") + graph.print() + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(v[0]!!, v[2]!!) + println("\n添加边 1-2 后,图为") + graph.print() + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(v[0]!!, v[1]!!) + println("\n删除边 1-3 后,图为") + graph.print() + + /* 添加顶点 */ + val v5 = Vertex(6) + graph.addVertex(v5) + println("\n添加顶点 6 后,图为") + graph.print() + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(v[1]!!) + println("\n删除顶点 3 后,图为") + graph.print() +} \ No newline at end of file diff --git a/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt b/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt new file mode 100644 index 0000000000..7471f9bf0d --- /dev/null +++ b/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.printMatrix + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat(vertices: IntArray, edges: Array) { + val vertices = mutableListOf() // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + val adjMat = mutableListOf>() // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造方法 */ + init { + // 添加顶点 + for (vertex in vertices) { + addVertex(vertex) + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (edge in edges) { + addEdge(edge[0], edge[1]) + } + } + + /* 获取顶点数量 */ + fun size(): Int { + return vertices.size + } + + /* 添加顶点 */ + fun addVertex(_val: Int) { + val n = size() + // 向顶点列表中添加新顶点的值 + vertices.add(_val) + // 在邻接矩阵中添加一行 + val newRow = mutableListOf() + for (j in 0..= size()) + throw IndexOutOfBoundsException() + // 在顶点列表中移除索引 index 的顶点 + vertices.removeAt(index) + // 在邻接矩阵中删除索引 index 的行 + adjMat.removeAt(index) + // 在邻接矩阵中删除索引 index 的列 + for (row in adjMat) { + row.removeAt(index) + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + fun addEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + fun removeEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 打印邻接矩阵 */ + fun print() { + print("顶点列表 = ") + println(vertices) + println("邻接矩阵 =") + printMatrix(adjMat) + } +} + +/* Driver Code */ +fun main() { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + val vertices = intArrayOf(1, 3, 2, 5, 4) + val edges = arrayOf( + intArrayOf(0, 1), + intArrayOf(0, 3), + intArrayOf(1, 2), + intArrayOf(2, 3), + intArrayOf(2, 4), + intArrayOf(3, 4) + ) + val graph = GraphAdjMat(vertices, edges) + println("\n初始化后,图为") + graph.print() + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.addEdge(0, 2) + println("\n添加边 1-2 后,图为") + graph.print() + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.removeEdge(0, 1) + println("\n删除边 1-3 后,图为") + graph.print() + + /* 添加顶点 */ + graph.addVertex(6) + println("\n添加顶点 6 后,图为") + graph.print() + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.removeVertex(1) + println("\n删除顶点 3 后,图为") + graph.print() +} \ No newline at end of file diff --git a/codes/kotlin/chapter_graph/graph_bfs.kt b/codes/kotlin/chapter_graph/graph_bfs.kt new file mode 100644 index 0000000000..9696967376 --- /dev/null +++ b/codes/kotlin/chapter_graph/graph_bfs.kt @@ -0,0 +1,65 @@ +/** + * File: graph_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex +import java.util.* + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { + // 顶点遍历序列 + val res = mutableListOf() + // 哈希集合,用于记录已被访问过的顶点 + val visited = HashSet() + visited.add(startVet) + // 队列用于实现 BFS + val que = LinkedList() + que.offer(startVet) + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.isEmpty()) { + val vet = que.poll() // 队首顶点出队 + res.add(vet) // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // 跳过已被访问的顶点 + que.offer(adjVet) // 只入队未访问的顶点 + visited.add(adjVet) // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res +} + +/* Driver Code */ +fun main() { + /* 初始化无向图 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[1], v[4]), + arrayOf(v[2], v[5]), + arrayOf(v[3], v[4]), + arrayOf(v[3], v[6]), + arrayOf(v[4], v[5]), + arrayOf(v[4], v[7]), + arrayOf(v[5], v[8]), + arrayOf(v[6], v[7]), + arrayOf(v[7], v[8]) + ) + val graph = GraphAdjList(edges) + println("\n初始化后,图为") + graph.print() + + /* 广度优先遍历 */ + val res = graphBFS(graph, v[0]!!) + println("\n广度优先遍历(BFS)顶点序列为") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_graph/graph_dfs.kt b/codes/kotlin/chapter_graph/graph_dfs.kt new file mode 100644 index 0000000000..d09e5513e7 --- /dev/null +++ b/codes/kotlin/chapter_graph/graph_dfs.kt @@ -0,0 +1,60 @@ +/** + * File: graph_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 深度优先遍历辅助函数 */ +fun dfs( + graph: GraphAdjList, + visited: MutableSet, + res: MutableList, + vet: Vertex? +) { + res.add(vet) // 记录访问顶点 + visited.add(vet) // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // 跳过已被访问的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { + // 顶点遍历序列 + val res = mutableListOf() + // 哈希集合,用于记录已被访问过的顶点 + val visited = HashSet() + dfs(graph, visited, res, startVet) + return res +} + +/* Driver Code */ +fun main() { + /* 初始化无向图 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[5]), + arrayOf(v[4], v[5]), + arrayOf(v[5], v[6]) + ) + val graph = GraphAdjList(edges) + println("\n初始化后,图为") + graph.print() + + /* 深度优先遍历 */ + val res = graphDFS(graph, v[0]) + println("\n深度优先遍历(DFS)顶点序列为") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_greedy/coin_change_greedy.kt b/codes/kotlin/chapter_greedy/coin_change_greedy.kt new file mode 100644 index 0000000000..32fce1f67c --- /dev/null +++ b/codes/kotlin/chapter_greedy/coin_change_greedy.kt @@ -0,0 +1,53 @@ +/** + * File: coin_change_greedy.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* 零钱兑换:贪心 */ +fun coinChangeGreedy(coins: IntArray, amt: Int): Int { + // 假设 coins 列表有序 + var am = amt + var i = coins.size - 1 + var count = 0 + // 循环进行贪心选择,直到无剩余金额 + while (am > 0) { + // 找到小于且最接近剩余金额的硬币 + while (i > 0 && coins[i] > am) { + i-- + } + // 选择 coins[i] + am -= coins[i] + count++ + } + // 若未找到可行方案,则返回 -1 + return if (am == 0) count else -1 +} + +/* Driver Code */ +fun main() { + // 贪心:能够保证找到全局最优解 + var coins = intArrayOf(1, 5, 10, 20, 50, 100) + var amt = 186 + var res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("凑到 $amt 所需的最少硬币数量为 $res") + + // 贪心:无法保证找到全局最优解 + coins = intArrayOf(1, 20, 50) + amt = 60 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("凑到 $amt 所需的最少硬币数量为 $res") + println("实际上需要的最少数量为 3 ,即 20 + 20 + 20") + + // 贪心:无法保证找到全局最优解 + coins = intArrayOf(1, 49, 50) + amt = 98 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("凑到 $amt 所需的最少硬币数量为 $res") + println("实际上需要的最少数量为 2 ,即 49 + 49") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_greedy/fractional_knapsack.kt b/codes/kotlin/chapter_greedy/fractional_knapsack.kt new file mode 100644 index 0000000000..b98e771033 --- /dev/null +++ b/codes/kotlin/chapter_greedy/fractional_knapsack.kt @@ -0,0 +1,51 @@ +/** + * File: fractional_knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* 物品 */ +class Item( + val w: Int, // 物品 + val v: Int // 物品价值 +) + +/* 分数背包:贪心 */ +fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { + // 创建物品列表,包含两个属性:重量、价值 + var cap = c + val items = arrayOfNulls(wgt.size) + for (i in wgt.indices) { + items[i] = Item(wgt[i], _val[i]) + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } + // 循环贪心选择 + var res = 0.0 + for (item in items) { + if (item!!.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v + cap -= item.w + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += item.v.toDouble() / item.w * cap + // 已无剩余容量,因此跳出循环 + break + } + } + return res +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + + // 贪心算法 + val res = fractionalKnapsack(wgt, _val, cap) + println("不超过背包容量的最大物品价值为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_greedy/max_capacity.kt b/codes/kotlin/chapter_greedy/max_capacity.kt new file mode 100644 index 0000000000..9b4ffc7efb --- /dev/null +++ b/codes/kotlin/chapter_greedy/max_capacity.kt @@ -0,0 +1,41 @@ +/** + * File: max_capacity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.max +import kotlin.math.min + +/* 最大容量:贪心 */ +fun maxCapacity(ht: IntArray): Int { + // 初始化 i, j,使其分列数组两端 + var i = 0 + var j = ht.size - 1 + // 初始最大容量为 0 + var res = 0 + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + val cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 向内移动短板 + if (ht[i] < ht[j]) { + i++ + } else { + j-- + } + } + return res +} + +/* Driver Code */ +fun main() { + val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) + + // 贪心算法 + val res = maxCapacity(ht) + println("最大容量为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_greedy/max_product_cutting.kt b/codes/kotlin/chapter_greedy/max_product_cutting.kt new file mode 100644 index 0000000000..9d6a4735f7 --- /dev/null +++ b/codes/kotlin/chapter_greedy/max_product_cutting.kt @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.pow + +/* 最大切分乘积:贪心 */ +fun maxProductCutting(n: Int): Int { + // 当 n <= 3 时,必须切分出一个 1 + if (n <= 3) { + return 1 * (n - 1) + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + val a = n / 3 + val b = n % 3 + if (b == 1) { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return 3.0.pow((a - 1)).toInt() * 2 * 2 + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return 3.0.pow(a).toInt() * 2 * 2 + } + // 当余数为 0 时,不做处理 + return 3.0.pow(a).toInt() +} + +/* Driver Code */ +fun main() { + val n = 58 + + // 贪心算法 + val res = maxProductCutting(n) + println("最大切分乘积为 $res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_hashing/array_hash_map.kt b/codes/kotlin/chapter_hashing/array_hash_map.kt new file mode 100644 index 0000000000..f0bab31f14 --- /dev/null +++ b/codes/kotlin/chapter_hashing/array_hash_map.kt @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 键值对 */ +class Pair( + var key: Int, + var _val: String +) + +/* 基于数组实现的哈希表 */ +class ArrayHashMap { + // 初始化数组,包含 100 个桶 + private val buckets = arrayOfNulls(100) + + /* 哈希函数 */ + fun hashFunc(key: Int): Int { + val index = key % 100 + return index + } + + /* 查询操作 */ + fun get(key: Int): String? { + val index = hashFunc(key) + val pair = buckets[index] ?: return null + return pair._val + } + + /* 添加操作 */ + fun put(key: Int, _val: String) { + val pair = Pair(key, _val) + val index = hashFunc(key) + buckets[index] = pair + } + + /* 删除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + // 置为 null ,代表删除 + buckets[index] = null + } + + /* 获取所有键值对 */ + fun pairSet(): MutableList { + val pairSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + pairSet.add(pair) + } + return pairSet + } + + /* 获取所有键 */ + fun keySet(): MutableList { + val keySet = mutableListOf() + for (pair in buckets) { + if (pair != null) + keySet.add(pair.key) + } + return keySet + } + + /* 获取所有值 */ + fun valueSet(): MutableList { + val valueSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + valueSet.add(pair._val) + } + return valueSet + } + + /* 打印哈希表 */ + fun print() { + for (kv in pairSet()) { + val key = kv.key + val _val = kv._val + println("$key -> $_val") + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化哈希表 */ + val map = ArrayHashMap() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈") + map.put(15937, "小啰") + map.put(16750, "小算") + map.put(13276, "小法") + map.put(10583, "小鸭") + println("\n添加完成后,哈希表为\nKey -> Value") + map.print() + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + val name = map.get(15937) + println("\n输入学号 15937 ,查询到姓名 $name") + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583) + println("\n删除 10583 后,哈希表为\nKey -> Value") + map.print() + + /* 遍历哈希表 */ + println("\n遍历键值对 Key -> Value") + for (kv in map.pairSet()) { + println("${kv.key} -> ${kv._val}") + } + println("\n单独遍历键 Key") + for (key in map.keySet()) { + println(key) + } + println("\n单独遍历值 Value") + for (_val in map.valueSet()) { + println(_val) + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_hashing/built_in_hash.kt b/codes/kotlin/chapter_hashing/built_in_hash.kt new file mode 100644 index 0000000000..2ad958b041 --- /dev/null +++ b/codes/kotlin/chapter_hashing/built_in_hash.kt @@ -0,0 +1,36 @@ +/** + * File: built_in_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.ListNode + +/* Driver Code */ +fun main() { + val num = 3 + val hashNum = num.hashCode() + println("整数 $num 的哈希值为 $hashNum") + + val bol = true + val hashBol = bol.hashCode() + println("布尔量 $bol 的哈希值为 $hashBol") + + val dec = 3.14159 + val hashDec = dec.hashCode() + println("小数 $dec 的哈希值为 $hashDec") + + val str = "Hello 算法" + val hashStr = str.hashCode() + println("字符串 $str 的哈希值为 $hashStr") + + val arr = arrayOf(12836, "小哈") + val hashTup = arr.contentHashCode() + println("数组 ${arr.contentToString()} 的哈希值为 $hashTup") + + val obj = ListNode(0) + val hashObj = obj.hashCode() + println("节点对象 $obj 的哈希值为 $hashObj") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_hashing/hash_map.kt b/codes/kotlin/chapter_hashing/hash_map.kt new file mode 100644 index 0000000000..a426ec8cbd --- /dev/null +++ b/codes/kotlin/chapter_hashing/hash_map.kt @@ -0,0 +1,50 @@ +/** + * File: hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.printHashMap + +/* Driver Code */ +fun main() { + /* 初始化哈希表 */ + val map = HashMap() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map[12836] = "小哈" + map[15937] = "小啰" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鸭" + println("\n添加完成后,哈希表为\nKey -> Value") + printHashMap(map) + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + val name = map[15937] + println("\n输入学号 15937 ,查询到姓名 $name") + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583) + println("\n删除 10583 后,哈希表为\nKey -> Value") + printHashMap(map) + + /* 遍历哈希表 */ + println("\n遍历键值对 Key->Value") + for ((key, value) in map) { + println("$key -> $value") + } + println("\n单独遍历键 Key") + for (key in map.keys) { + println(key) + } + println("\n单独遍历值 Value") + for (_val in map.values) { + println(_val) + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_hashing/hash_map_chaining.kt b/codes/kotlin/chapter_hashing/hash_map_chaining.kt new file mode 100644 index 0000000000..c8fdb8f2fd --- /dev/null +++ b/codes/kotlin/chapter_hashing/hash_map_chaining.kt @@ -0,0 +1,145 @@ +/** + * File: hash_map_chaining.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 链式地址哈希表 */ +class HashMapChaining { + var size: Int // 键值对数量 + var capacity: Int // 哈希表容量 + val loadThres: Double // 触发扩容的负载因子阈值 + val extendRatio: Int // 扩容倍数 + var buckets: MutableList> // 桶数组 + + /* 构造方法 */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = mutableListOf() + for (i in 0.. loadThres) { + extend() + } + val index = hashFunc(key) + val bucket = buckets[index] + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (pair in bucket) { + if (pair.key == key) { + pair._val = _val + return + } + } + // 若无该 key ,则将键值对添加至尾部 + val pair = Pair(key, _val) + bucket.add(pair) + size++ + } + + /* 删除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + val bucket = buckets[index] + // 遍历桶,从中删除键值对 + for (pair in bucket) { + if (pair.key == key) { + bucket.remove(pair) + size-- + break + } + } + } + + /* 扩容哈希表 */ + fun extend() { + // 暂存原哈希表 + val bucketsTmp = buckets + // 初始化扩容后的新哈希表 + capacity *= extendRatio + // mutablelist 无固定大小 + buckets = mutableListOf() + for (i in 0..() + for (pair in bucket) { + val k = pair.key + val v = pair._val + res.add("$k -> $v") + } + println(res) + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化哈希表 */ + val map = HashMapChaining() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈") + map.put(15937, "小啰") + map.put(16750, "小算") + map.put(13276, "小法") + map.put(10583, "小鸭") + println("\n添加完成后,哈希表为\nKey -> Value") + map.print() + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + val name = map.get(13276) + println("\n输入学号 13276 ,查询到姓名 $name") + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(12836) + println("\n删除 12836 后,哈希表为\nKey -> Value") + map.print() +} \ No newline at end of file diff --git a/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt b/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt new file mode 100644 index 0000000000..042e8cfbde --- /dev/null +++ b/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt @@ -0,0 +1,161 @@ +/** + * File: hash_map_open_addressing.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + private var size: Int // 键值对数量 + private var capacity: Int // 哈希表容量 + private val loadThres: Double // 触发扩容的负载因子阈值 + private val extendRatio: Int // 扩容倍数 + private var buckets: Array // 桶数组 + private val TOMBSTONE: Pair // 删除标记 + + /* 构造方法 */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = arrayOfNulls(capacity) + TOMBSTONE = Pair(-1, "-1") + } + + /* 哈希函数 */ + fun hashFunc(key: Int): Int { + return key % capacity + } + + /* 负载因子 */ + fun loadFactor(): Double { + return (size / capacity).toDouble() + } + + /* 搜索 key 对应的桶索引 */ + fun findBucket(key: Int): Int { + var index = hashFunc(key) + var firstTombstone = -1 + // 线性探测,当遇到空桶时跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回对应的桶索引 + if (buckets[index]?.key == key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 返回移动后的桶索引 + } + return index // 返回桶索引 + } + // 记录遇到的首个删除标记 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % capacity + } + // 若 key 不存在,则返回添加点的索引 + return if (firstTombstone == -1) index else firstTombstone + } + + /* 查询操作 */ + fun get(key: Int): String? { + // 搜索 key 对应的桶索引 + val index = findBucket(key) + // 若找到键值对,则返回对应 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index]?._val + } + // 若键值对不存在,则返回 null + return null + } + + /* 添加操作 */ + fun put(key: Int, _val: String) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend() + } + // 搜索 key 对应的桶索引 + val index = findBucket(key) + // 若找到键值对,则覆盖 val 并返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index]!!._val = _val + return + } + // 若键值对不存在,则添加该键值对 + buckets[index] = Pair(key, _val) + size++ + } + + /* 删除操作 */ + fun remove(key: Int) { + // 搜索 key 对应的桶索引 + val index = findBucket(key) + // 若找到键值对,则用删除标记覆盖它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE + size-- + } + } + + /* 扩容哈希表 */ + fun extend() { + // 暂存原哈希表 + val bucketsTmp = buckets + // 初始化扩容后的新哈希表 + capacity *= extendRatio + buckets = arrayOfNulls(capacity) + size = 0 + // 将键值对从原哈希表搬运至新哈希表 + for (pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair._val) + } + } + } + + /* 打印哈希表 */ + fun print() { + for (pair in buckets) { + if (pair == null) { + println("null") + } else if (pair == TOMBSTONE) { + println("TOMESTOME") + } else { + println("${pair.key} -> ${pair._val}") + } + } + } +} + +/* Driver Code */ +fun main() { + // 初始化哈希表 + val hashmap = HashMapOpenAddressing() + + // 添加操作 + // 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小啰") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鸭") + println("\n添加完成后,哈希表为\nKey -> Value") + hashmap.print() + + // 查询操作 + // 向哈希表中输入键 key ,得到值 val + val name = hashmap.get(13276) + println("\n输入学号 13276 ,查询到姓名 $name") + + // 删除操作 + // 在哈希表中删除键值对 (key, val) + hashmap.remove(16750) + println("\n删除 16750 后,哈希表为\nKey -> Value") + hashmap.print() +} \ No newline at end of file diff --git a/codes/kotlin/chapter_hashing/simple_hash.kt b/codes/kotlin/chapter_hashing/simple_hash.kt new file mode 100644 index 0000000000..a860eb84cb --- /dev/null +++ b/codes/kotlin/chapter_hashing/simple_hash.kt @@ -0,0 +1,64 @@ +/** + * File: simple_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 加法哈希 */ +fun addHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* 乘法哈希 */ +fun mulHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (31 * hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* 异或哈希 */ +fun xorHash(key: String): Int { + var hash = 0 + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = hash xor c.code + } + return hash and MODULUS +} + +/* 旋转哈希 */ +fun rotHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS + } + return hash.toInt() +} + +/* Driver Code */ +fun main() { + val key = "Hello 算法" + + var hash = addHash(key) + println("加法哈希值为 $hash") + + hash = mulHash(key) + println("乘法哈希值为 $hash") + + hash = xorHash(key) + println("异或哈希值为 $hash") + + hash = rotHash(key) + println("旋转哈希值为 $hash") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_heap/heap.kt b/codes/kotlin/chapter_heap/heap.kt new file mode 100644 index 0000000000..5b6dc3a11d --- /dev/null +++ b/codes/kotlin/chapter_heap/heap.kt @@ -0,0 +1,66 @@ +/** + * File: heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +fun testPush(heap: Queue, _val: Int) { + heap.offer(_val) // 元素入堆 + print("\n元素 $_val 入堆后\n") + printHeap(heap) +} + +fun testPop(heap: Queue) { + val _val = heap.poll() // 堆顶元素出堆 + print("\n堆顶元素 $_val 出堆后\n") + printHeap(heap) +} + +/* Driver Code */ +fun main() { + /* 初始化堆 */ + // 初始化小顶堆 + var minHeap = PriorityQueue() + + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + println("\n以下测试样例为大顶堆") + + /* 元素入堆 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* 获取堆顶元素 */ + val peek = maxHeap.peek() + print("\n堆顶元素为 $peek\n") + + /* 堆顶元素出堆 */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* 获取堆大小 */ + val size = maxHeap.size + print("\n堆元素数量为 $size\n") + + /* 判断堆是否为空 */ + val isEmpty = maxHeap.isEmpty() + print("\n堆是否为空 $isEmpty\n") + + /* 输入列表并建堆 */ + // 时间复杂度为 O(n) ,而非 O(nlogn) + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + println("\n输入列表并建立小顶堆后") + printHeap(minHeap) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_heap/my_heap.kt b/codes/kotlin/chapter_heap/my_heap.kt new file mode 100644 index 0000000000..4381af22a4 --- /dev/null +++ b/codes/kotlin/chapter_heap/my_heap.kt @@ -0,0 +1,160 @@ +/** + * File: my_heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* 大顶堆 */ +class MaxHeap(nums: MutableList?) { + // 使用列表而非数组,这样无须考虑扩容问题 + private val maxHeap = mutableListOf() + + /* 构造方法,根据输入列表建堆 */ + init { + // 将列表元素原封不动添加进堆 + maxHeap.addAll(nums!!) + // 堆化除叶节点以外的其他所有节点 + for (i in parent(size() - 1) downTo 0) { + siftDown(i) + } + } + + /* 获取左子节点的索引 */ + private fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 获取右子节点的索引 */ + private fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 获取父节点的索引 */ + private fun parent(i: Int): Int { + return (i - 1) / 2 // 向下整除 + } + + /* 交换元素 */ + private fun swap(i: Int, j: Int) { + val temp = maxHeap[i] + maxHeap[i] = maxHeap[j] + maxHeap[j] = temp + } + + /* 获取堆大小 */ + fun size(): Int { + return maxHeap.size + } + + /* 判断堆是否为空 */ + fun isEmpty(): Boolean { + /* 判断堆是否为空 */ + return size() == 0 + } + + /* 访问堆顶元素 */ + fun peek(): Int { + return maxHeap[0] + } + + /* 元素入堆 */ + fun push(_val: Int) { + // 添加节点 + maxHeap.add(_val) + // 从底至顶堆化 + siftUp(size() - 1) + } + + /* 从节点 i 开始,从底至顶堆化 */ + private fun siftUp(it: Int) { + // Kotlin的函数参数不可变,因此创建临时变量 + var i = it + while (true) { + // 获取节点 i 的父节点 + val p = parent(i) + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) break + // 交换两节点 + swap(i, p) + // 循环向上堆化 + i = p + } + } + + /* 元素出堆 */ + fun pop(): Int { + // 判空处理 + if (isEmpty()) throw IndexOutOfBoundsException() + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(0, size() - 1) + // 删除节点 + val _val = maxHeap.removeAt(size() - 1) + // 从顶至底堆化 + siftDown(0) + // 返回堆顶元素 + return _val + } + + /* 从节点 i 开始,从顶至底堆化 */ + private fun siftDown(it: Int) { + // Kotlin的函数参数不可变,因此创建临时变量 + var i = it + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + val l = left(i) + val r = right(i) + var ma = i + if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l + if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break + // 交换两节点 + swap(i, ma) + // 循环向下堆化 + i = ma + } + } + + /* 打印堆(二叉树) */ + fun print() { + val queue = PriorityQueue { a: Int, b: Int -> b - a } + queue.addAll(maxHeap) + printHeap(queue) + } +} + +/* Driver Code */ +fun main() { + /* 初始化大顶堆 */ + val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) + println("\n输入列表并建堆后") + maxHeap.print() + + /* 获取堆顶元素 */ + var peek = maxHeap.peek() + print("\n堆顶元素为 $peek\n") + + /* 元素入堆 */ + val _val = 7 + maxHeap.push(_val) + print("\n元素 $_val 入堆后\n") + maxHeap.print() + + /* 堆顶元素出堆 */ + peek = maxHeap.pop() + print("\n堆顶元素 $peek 出堆后\n") + maxHeap.print() + + /* 获取堆大小 */ + val size = maxHeap.size() + print("\n堆元素数量为 $size\n") + + /* 判断堆是否为空 */ + val isEmpty = maxHeap.isEmpty() + print("\n堆是否为空 $isEmpty\n") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_heap/top_k.kt b/codes/kotlin/chapter_heap/top_k.kt new file mode 100644 index 0000000000..208ede6533 --- /dev/null +++ b/codes/kotlin/chapter_heap/top_k.kt @@ -0,0 +1,38 @@ +/** + * File: top_k.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* 基于堆查找数组中最大的 k 个元素 */ +fun topKHeap(nums: IntArray, k: Int): Queue { + // 初始化小顶堆 + val heap = PriorityQueue() + // 将数组的前 k 个元素入堆 + for (i in 0.. heap.peek()) { + heap.poll() + heap.offer(nums[i]) + } + } + return heap +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 7, 6, 3, 2) + val k = 3 + val res = topKHeap(nums, k) + println("最大的 $k 个元素为") + printHeap(res) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_searching/binary_search.kt b/codes/kotlin/chapter_searching/binary_search.kt new file mode 100644 index 0000000000..4eae98e687 --- /dev/null +++ b/codes/kotlin/chapter_searching/binary_search.kt @@ -0,0 +1,59 @@ +/** + * File: binary_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分查找(双闭区间) */ +fun binarySearch(nums: IntArray, target: Int): Int { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + var i = 0 + var j = nums.size - 1 + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) { + val 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 +} + +/* 二分查找(左闭右开区间) */ +fun binarySearchLCRO(nums: IntArray, target: Int): Int { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + var i = 0 + var j = nums.size + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i < j) { + val 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 +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + /* 二分查找(双闭区间) */ + var index = binarySearch(nums, target) + println("目标元素 6 的索引 = $index") + + /* 二分查找(左闭右开区间) */ + index = binarySearchLCRO(nums, target) + println("目标元素 6 的索引 = $index") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_searching/binary_search_edge.kt b/codes/kotlin/chapter_searching/binary_search_edge.kt new file mode 100644 index 0000000000..a51803b1df --- /dev/null +++ b/codes/kotlin/chapter_searching/binary_search_edge.kt @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分查找最左一个 target */ +fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { + // 等价于查找 target 的插入点 + val i = binarySearchInsertion(nums, target) + // 未找到 target ,返回 -1 + if (i == nums.size || nums[i] != target) { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分查找最右一个 target */ +fun binarySearchRightEdge(nums: IntArray, target: Int): Int { + // 转化为查找最左一个 target + 1 + val i = binarySearchInsertion(nums, target + 1) + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + val j = i - 1 + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1 + } + // 找到 target ,返回索引 j + return j +} + +/* Driver Code */ +fun main() { + // 包含重复元素的数组 + val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n数组 nums = ${nums.contentToString()}") + + // 二分查找左边界和右边界 + for (target in intArrayOf(6, 7)) { + var index = binarySearchLeftEdge(nums, target) + println("最左一个元素 $target 的索引为 $index") + index = binarySearchRightEdge(nums, target) + println("最右一个元素 $target 的索引为 $index") + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_searching/binary_search_insertion.kt b/codes/kotlin/chapter_searching/binary_search_insertion.kt new file mode 100644 index 0000000000..7c5279e6b6 --- /dev/null +++ b/codes/kotlin/chapter_searching/binary_search_insertion.kt @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分查找插入点(无重复元素) */ +fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 初始化双闭区间 [0, n-1] + while (i <= j) { + val 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 +} + +/* 二分查找插入点(存在重复元素) */ +fun binarySearchInsertion(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 初始化双闭区间 [0, n-1] + while (i <= j) { + val 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 +} + +/* Driver Code */ +fun main() { + // 无重复元素的数组 + var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + println("\n数组 nums = ${nums.contentToString()}") + // 二分查找插入点 + for (target in intArrayOf(6, 9)) { + val index = binarySearchInsertionSimple(nums, target) + println("元素 $target 的插入点的索引为 $index") + } + + // 包含重复元素的数组 + nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n数组 nums = ${nums.contentToString()}") + + // 二分查找插入点 + for (target in intArrayOf(2, 6, 20)) { + val index = binarySearchInsertion(nums, target) + println("元素 $target 的插入点的索引为 $index") + } +} \ No newline at end of file diff --git a/codes/kotlin/chapter_searching/hashing_search.kt b/codes/kotlin/chapter_searching/hashing_search.kt new file mode 100644 index 0000000000..965260e228 --- /dev/null +++ b/codes/kotlin/chapter_searching/hashing_search.kt @@ -0,0 +1,49 @@ +/** + * File: hashing_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* 哈希查找(数组) */ +fun hashingSearchArray(map: Map, target: Int): Int { + // 哈希表的 key: 目标元素,_val: 索引 + // 若哈希表中无此 key ,返回 -1 + return map.getOrDefault(target, -1) +} + +/* 哈希查找(链表) */ +fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { + // 哈希表的 key: 目标节点值,_val: 节点对象 + // 若哈希表中无此 key ,返回 null + return map.getOrDefault(target, null) +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* 哈希查找(数组) */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + // 初始化哈希表 + val map = HashMap() + for (i in nums.indices) { + map[nums[i]] = i // key: 元素,_val: 索引 + } + val index = hashingSearchArray(map, target) + println("目标元素 3 的索引 = $index") + + /* 哈希查找(链表) */ + var head = ListNode.arrToLinkedList(nums) + // 初始化哈希表 + val map1 = HashMap() + while (head != null) { + map1[head._val] = head // key: 节点值,_val: 节点 + head = head.next + } + val node = hashingSearchLinkedList(map1, target) + println("目标节点值 3 的对应节点对象为 $node") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_searching/linear_search.kt b/codes/kotlin/chapter_searching/linear_search.kt new file mode 100644 index 0000000000..915eb572cc --- /dev/null +++ b/codes/kotlin/chapter_searching/linear_search.kt @@ -0,0 +1,50 @@ +/** + * File: linear_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* 线性查找(数组) */ +fun linearSearchArray(nums: IntArray, target: Int): Int { + // 遍历数组 + for (i in nums.indices) { + // 找到目标元素,返回其索引 + if (nums[i] == target) + return i + } + // 未找到目标元素,返回 -1 + return -1 +} + +/* 线性查找(链表) */ +fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { + // 遍历链表 + var head = h + while (head != null) { + // 找到目标节点,返回之 + if (head._val == target) + return head + head = head.next + } + // 未找到目标节点,返回 null + return null +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* 在数组中执行线性查找 */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + val index = linearSearchArray(nums, target) + println("目标元素 3 的索引 = $index") + + /* 在链表中执行线性查找 */ + val head = ListNode.arrToLinkedList(nums) + val node = linearSearchLinkedList(head, target) + println("目标节点值 3 的对应节点对象为 $node") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_searching/two_sum.kt b/codes/kotlin/chapter_searching/two_sum.kt new file mode 100644 index 0000000000..8e3e1e16dd --- /dev/null +++ b/codes/kotlin/chapter_searching/two_sum.kt @@ -0,0 +1,49 @@ +/** + * File: two_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 方法一:暴力枚举 */ +fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { + val size = nums.size + // 两层循环,时间复杂度为 O(n^2) + for (i in 0..() + // 单层循环,时间复杂度为 O(n) + for (i in 0.. nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + } + } + } +} + +/* 冒泡排序(标志优化) */ +fun bubbleSortWithFlag(nums: IntArray) { + // 外循环:未排序区间为 [0, i] + for (i in nums.size - 1 downTo 1) { + var flag = false // 初始化标志位 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (j in 0.. nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + flag = true // 记录交换元素 + } + } + if (!flag) break // 此轮“冒泡”未交换任何元素,直接跳出 + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSort(nums) + println("冒泡排序完成后 nums = ${nums.contentToString()}") + + val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSortWithFlag(nums1) + println("冒泡排序完成后 nums1 = ${nums1.contentToString()}") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_sorting/bucket_sort.kt b/codes/kotlin/chapter_sorting/bucket_sort.kt new file mode 100644 index 0000000000..ccfcffc757 --- /dev/null +++ b/codes/kotlin/chapter_sorting/bucket_sort.kt @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 桶排序 */ +fun bucketSort(nums: FloatArray) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + val k = nums.size / 2 + val buckets = mutableListOf>() + for (i in 0.. nums[ma]) + ma = l + if (r < n && nums[r] > nums[ma]) + ma = r + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) + break + // 交换两节点 + val temp = nums[i] + nums[i] = nums[ma] + nums[ma] = temp + // 循环向下堆化 + i = ma + } +} + +/* 堆排序 */ +fun heapSort(nums: IntArray) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (i in nums.size / 2 - 1 downTo 0) { + siftDown(nums, nums.size, i) + } + // 从堆中提取最大元素,循环 n-1 轮 + for (i in nums.size - 1 downTo 1) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + val temp = nums[0] + nums[0] = nums[i] + nums[i] = temp + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0) + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + heapSort(nums) + println("堆排序完成后 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_sorting/insertion_sort.kt b/codes/kotlin/chapter_sorting/insertion_sort.kt new file mode 100644 index 0000000000..57ff3f3cb5 --- /dev/null +++ b/codes/kotlin/chapter_sorting/insertion_sort.kt @@ -0,0 +1,29 @@ +/** + * File: insertion_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 插入排序 */ +fun insertionSort(nums: IntArray) { + //外循环: 已排序元素为 1, 2, ..., n + for (i in nums.indices) { + val base = nums[i] + var j = i - 1 + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j] // 将 nums[j] 向右移动一位 + j-- + } + nums[j + 1] = base // 将 base 赋值到正确位置 + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + insertionSort(nums) + println("插入排序完成后 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_sorting/merge_sort.kt b/codes/kotlin/chapter_sorting/merge_sort.kt new file mode 100644 index 0000000000..d08d3d925c --- /dev/null +++ b/codes/kotlin/chapter_sorting/merge_sort.kt @@ -0,0 +1,56 @@ +/** + * File: merge_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 合并左子数组和右子数组 */ +fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + val tmp = IntArray(right - left + 1) + // 初始化左子数组和右子数组的起始索引 + var i = left + var j = mid + 1 + var 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 (l in tmp.indices) { + nums[left + l] = tmp[l] + } +} + +/* 归并排序 */ +fun mergeSort(nums: IntArray, left: Int, right: Int) { + // 终止条件 + if (left >= right) return // 当子数组长度为 1 时终止递归 + // 划分阶段 + val mid = left + (right - left) / 2 // 计算中点 + mergeSort(nums, left, mid) // 递归左子数组 + mergeSort(nums, mid + 1, right) // 递归右子数组 + // 合并阶段 + merge(nums, left, mid, right) +} + +/* Driver Code */ +fun main() { + /* 归并排序 */ + val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) + mergeSort(nums, 0, nums.size - 1) + println("归并排序完成后 nums = ${nums.contentToString()}") +} diff --git a/codes/kotlin/chapter_sorting/quick_sort.kt b/codes/kotlin/chapter_sorting/quick_sort.kt new file mode 100644 index 0000000000..77bfa92357 --- /dev/null +++ b/codes/kotlin/chapter_sorting/quick_sort.kt @@ -0,0 +1,121 @@ +/** + * File: quick_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 元素交换 */ +fun swap(nums: IntArray, i: Int, j: Int) { + val temp = nums[i] + nums[i] = nums[j] + nums[j] = temp +} + +/* 哨兵划分 */ +fun partition(nums: IntArray, left: Int, right: Int): Int { + // 以 nums[left] 为基准数 + var i = left + var 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 // 返回基准数的索引 +} + +/* 快速排序 */ +fun quickSort(nums: IntArray, left: Int, right: Int) { + // 子数组长度为 1 时终止递归 + if (left >= right) return + // 哨兵划分 + val pivot = partition(nums, left, right) + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* 选取三个候选元素的中位数 */ +fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { + val l = nums[left] + val m = nums[mid] + val r = nums[right] + if ((m in l..r) || (m in r..l)) + return mid // m 在 l 和 r 之间 + if ((l in m..r) || (l in r..m)) + return left // l 在 m 和 r 之间 + return right +} + +/* 哨兵划分(三数取中值) */ +fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { + // 选取三个候选元素的中位数 + val med = medianThree(nums, left, (left + right) / 2, right) + // 将中位数交换至数组最左端 + swap(nums, left, med) + // 以 nums[left] 为基准数 + var i = left + var 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 // 返回基准数的索引 +} + +/* 快速排序 */ +fun quickSortMedian(nums: IntArray, left: Int, right: Int) { + // 子数组长度为 1 时终止递归 + if (left >= right) return + // 哨兵划分 + val pivot = partitionMedian(nums, left, right) + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* 快速排序(尾递归优化) */ +fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { + // 子数组长度为 1 时终止 + var l = left + var r = right + while (l < r) { + // 哨兵划分操作 + val pivot = partition(nums, l, r) + // 对两个子数组中较短的那个执行快速排序 + if (pivot - l < r - pivot) { + quickSort(nums, l, pivot - 1) // 递归排序左子数组 + l = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, r) // 递归排序右子数组 + r = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] + } + } +} + +/* Driver Code */ +fun main() { + /* 快速排序 */ + val nums = intArrayOf(2, 4, 1, 0, 3, 5) + quickSort(nums, 0, nums.size - 1) + println("快速排序完成后 nums = ${nums.contentToString()}") + + /* 快速排序(中位基准数优化) */ + val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortMedian(nums1, 0, nums1.size - 1) + println("快速排序(中位基准数优化)完成后 nums1 = ${nums1.contentToString()}") + + /* 快速排序(尾递归优化) */ + val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortTailCall(nums2, 0, nums2.size - 1) + println("快速排序(尾递归优化)完成后 nums2 = ${nums2.contentToString()}") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_sorting/radix_sort.kt b/codes/kotlin/chapter_sorting/radix_sort.kt new file mode 100644 index 0000000000..1fefdccf11 --- /dev/null +++ b/codes/kotlin/chapter_sorting/radix_sort.kt @@ -0,0 +1,68 @@ +/** + * File: radix_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +fun digit(num: Int, exp: Int): Int { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10 +} + +/* 计数排序(根据 nums 第 k 位排序) */ +fun countingSortDigit(nums: IntArray, exp: Int) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + val counter = IntArray(10) + val n = nums.size + // 统计 0~9 各数字的出现次数 + for (i in 0.. m) m = num + var exp = 1 + // 按照从低位到高位的顺序遍历 + while (exp <= m) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp) + exp *= 10 + } +} + +/* Driver Code */ +fun main() { + // 基数排序 + val nums = intArrayOf( + 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 + ) + radixSort(nums) + println("基数排序完成后 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_sorting/selection_sort.kt b/codes/kotlin/chapter_sorting/selection_sort.kt new file mode 100644 index 0000000000..653b7ab089 --- /dev/null +++ b/codes/kotlin/chapter_sorting/selection_sort.kt @@ -0,0 +1,32 @@ +/** + * File: selection_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 选择排序 */ +fun selectionSort(nums: IntArray) { + val n = nums.size + // 外循环:未排序区间为 [i, n-1] + for (i in 0..() + + /* 获取栈的长度 */ + fun size(): Int { + return stack.size + } + + /* 判断栈是否为空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入栈 */ + fun push(num: Int) { + stack.add(num) + } + + /* 出栈 */ + fun pop(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack.removeAt(size() - 1) + } + + /* 访问栈顶元素 */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack[size() - 1] + } + + /* 将 List 转化为 Array 并返回 */ + fun toArray(): Array { + return stack.toTypedArray() + } +} + +/* Driver Code */ +fun main() { + /* 初始化栈 */ + val stack = ArrayStack() + + /* 元素入栈 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("栈 stack = ${stack.toArray().contentToString()}") + + /* 访问栈顶元素 */ + val peek = stack.peek() + println("栈顶元素 peek = $peek") + + /* 元素出栈 */ + val pop = stack.pop() + println("出栈元素 pop = $pop,出栈后 stack = ${stack.toArray().contentToString()}") + + /* 获取栈的长度 */ + val size = stack.size() + println("栈的长度 size = $size") + + /* 判断是否为空 */ + val isEmpty = stack.isEmpty() + println("栈是否为空 = $isEmpty") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_stack_and_queue/deque.kt b/codes/kotlin/chapter_stack_and_queue/deque.kt new file mode 100644 index 0000000000..7031335292 --- /dev/null +++ b/codes/kotlin/chapter_stack_and_queue/deque.kt @@ -0,0 +1,45 @@ +/** + * File: deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化双向队列 */ + val deque = LinkedList() + deque.offerLast(3) + deque.offerLast(2) + deque.offerLast(5) + println("双向队列 deque = $deque") + + /* 访问元素 */ + val peekFirst = deque.peekFirst() + println("队首元素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("队尾元素 peekLast = $peekLast") + + /* 元素入队 */ + deque.offerLast(4) + println("元素 4 队尾入队后 deque = $deque") + deque.offerFirst(1) + println("元素 1 队首入队后 deque = $deque") + + /* 元素出队 */ + val popLast = deque.pollLast() + println("队尾出队元素 = $popLast,队尾出队后 deque = $deque") + val popFirst = deque.pollFirst() + println("队首出队元素 = $popFirst,队首出队后 deque = $deque") + + /* 获取双向队列的长度 */ + val size = deque.size + println("双向队列长度 size = $size") + + /* 判断双向队列是否为空 */ + val isEmpty = deque.isEmpty() + println("双向队列是否为空 = $isEmpty") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt b/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt new file mode 100644 index 0000000000..88ad608271 --- /dev/null +++ b/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt @@ -0,0 +1,163 @@ +/** + * File: linkedlist_deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 双向链表节点 */ +class ListNode(var _val: Int) { + // 节点值 + var next: ListNode? = null // 后继节点引用 + var prev: ListNode? = null // 前驱节点引用 +} + +/* 基于双向链表实现的双向队列 */ +class LinkedListDeque { + private var front: ListNode? = null // 头节点 front + private var rear: ListNode? = null // 尾节点 rear + private var queSize: Int = 0 // 双向队列的长度 + + /* 获取双向队列的长度 */ + fun size(): Int { + return queSize + } + + /* 判断双向队列是否为空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入队操作 */ + fun push(num: Int, isFront: Boolean) { + val node = ListNode(num) + // 若链表为空,则令 front 和 rear 都指向 node + if (isEmpty()) { + rear = node + front = rear + // 队首入队操作 + } else if (isFront) { + // 将 node 添加至链表头部 + front?.prev = node + node.next = front + front = node // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾节点 + } + queSize++ // 更新队列长度 + } + + /* 队首入队 */ + fun pushFirst(num: Int) { + push(num, true) + } + + /* 队尾入队 */ + fun pushLast(num: Int) { + push(num, false) + } + + /* 出队操作 */ + fun pop(isFront: Boolean): Int { + if (isEmpty()) + throw IndexOutOfBoundsException() + val _val: Int + // 队首出队操作 + if (isFront) { + _val = front!!._val // 暂存头节点值 + // 删除头节点 + val fNext = front!!.next + if (fNext != null) { + fNext.prev = null + front!!.next = null + } + front = fNext // 更新头节点 + // 队尾出队操作 + } else { + _val = rear!!._val // 暂存尾节点值 + // 删除尾节点 + val rPrev = rear!!.prev + if (rPrev != null) { + rPrev.next = null + rear!!.prev = null + } + rear = rPrev // 更新尾节点 + } + queSize-- // 更新队列长度 + return _val + } + + /* 队首出队 */ + fun popFirst(): Int { + return pop(true) + } + + /* 队尾出队 */ + fun popLast(): Int { + return pop(false) + } + + /* 访问队首元素 */ + fun peekFirst(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* 访问队尾元素 */ + fun peekLast(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return rear!!._val + } + + /* 返回数组用于打印 */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化双向队列 */ + val deque = LinkedListDeque() + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + println("双向队列 deque = ${deque.toArray().contentToString()}") + + /* 访问元素 */ + val peekFirst = deque.peekFirst() + println("队首元素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("队尾元素 peekLast = $peekLast") + + /* 元素入队 */ + deque.pushLast(4) + println("元素 4 队尾入队后 deque = ${deque.toArray().contentToString()}") + deque.pushFirst(1) + println("元素 1 队首入队后 deque = ${deque.toArray().contentToString()}") + + /* 元素出队 */ + val popLast = deque.popLast() + println("队尾出队元素 = ${popLast},队尾出队后 deque = ${deque.toArray().contentToString()}") + val popFirst = deque.popFirst() + println("队首出队元素 = ${popFirst},队首出队后 deque = ${deque.toArray().contentToString()}") + + /* 获取双向队列的长度 */ + val size = deque.size() + println("双向队列长度 size = $size") + + /* 判断双向队列是否为空 */ + val isEmpty = deque.isEmpty() + println("双向队列是否为空 = $isEmpty") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt b/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt new file mode 100644 index 0000000000..cc0da41aa6 --- /dev/null +++ b/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt @@ -0,0 +1,98 @@ +/** + * File: linkedlist_queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 基于链表实现的队列 */ +class LinkedListQueue( + // 头节点 front ,尾节点 rear + private var front: ListNode? = null, + private var rear: ListNode? = null, + private var queSize: Int = 0 +) { + + /* 获取队列的长度 */ + fun size(): Int { + return queSize + } + + /* 判断队列是否为空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入队 */ + fun push(num: Int) { + // 在尾节点后添加 num + val node = ListNode(num) + // 如果队列为空,则令头、尾节点都指向该节点 + if (front == null) { + front = node + rear = node + // 如果队列不为空,则将该节点添加到尾节点后 + } else { + rear?.next = node + rear = node + } + queSize++ + } + + /* 出队 */ + fun pop(): Int { + val num = peek() + // 删除头节点 + front = front?.next + queSize-- + return num + } + + /* 访问队首元素 */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* 将链表转化为 Array 并返回 */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化队列 */ + val queue = LinkedListQueue() + + /* 元素入队 */ + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + println("队列 queue = ${queue.toArray().contentToString()}") + + /* 访问队首元素 */ + val peek = queue.peek() + println("队首元素 peek = $peek") + + /* 元素出队 */ + val pop = queue.pop() + println("出队元素 pop = $pop,出队后 queue = ${queue.toArray().contentToString()}") + + /* 获取队列的长度 */ + val size = queue.size() + println("队列长度 size = $size") + + /* 判断队列是否为空 */ + val isEmpty = queue.isEmpty() + println("队列是否为空 = $isEmpty") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt b/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt new file mode 100644 index 0000000000..434f0891df --- /dev/null +++ b/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt @@ -0,0 +1,87 @@ +/** + * File: linkedlist_stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 基于链表实现的栈 */ +class LinkedListStack( + private var stackPeek: ListNode? = null, // 将头节点作为栈顶 + private var stkSize: Int = 0 // 栈的长度 +) { + + /* 获取栈的长度 */ + fun size(): Int { + return stkSize + } + + /* 判断栈是否为空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入栈 */ + fun push(num: Int) { + val node = ListNode(num) + node.next = stackPeek + stackPeek = node + stkSize++ + } + + /* 出栈 */ + fun pop(): Int? { + val num = peek() + stackPeek = stackPeek?.next + stkSize-- + return num + } + + /* 访问栈顶元素 */ + fun peek(): Int? { + if (isEmpty()) throw IndexOutOfBoundsException() + return stackPeek?._val + } + + /* 将 List 转化为 Array 并返回 */ + fun toArray(): IntArray { + var node = stackPeek + val res = IntArray(size()) + for (i in res.size - 1 downTo 0) { + res[i] = node?._val!! + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化栈 */ + val stack = LinkedListStack() + + /* 元素入栈 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("栈 stack = ${stack.toArray().contentToString()}") + + /* 访问栈顶元素 */ + val peek = stack.peek()!! + println("栈顶元素 peek = $peek") + + /* 元素出栈 */ + val pop = stack.pop()!! + println("出栈元素 pop = $pop,出栈后 stack = ${stack.toArray().contentToString()}") + + /* 获取栈的长度 */ + val size = stack.size() + println("栈的长度 size = $size") + + /* 判断是否为空 */ + val isEmpty = stack.isEmpty() + println("栈是否为空 = $isEmpty") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_stack_and_queue/queue.kt b/codes/kotlin/chapter_stack_and_queue/queue.kt new file mode 100644 index 0000000000..5f87fcc1e8 --- /dev/null +++ b/codes/kotlin/chapter_stack_and_queue/queue.kt @@ -0,0 +1,39 @@ +/** + * File: queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化队列 */ + val queue = LinkedList() + + /* 元素入队 */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + println("队列 queue = $queue") + + /* 访问队首元素 */ + val peek = queue.peek() + println("队首元素 peek = $peek") + + /* 元素出队 */ + val pop = queue.poll() + println("出队元素 pop = $pop,出队后 queue = $queue") + + /* 获取队列的长度 */ + val size = queue.size + println("队列长度 size = $size") + + /* 判断队列是否为空 */ + val isEmpty = queue.isEmpty() + println("队列是否为空 = $isEmpty") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_stack_and_queue/stack.kt b/codes/kotlin/chapter_stack_and_queue/stack.kt new file mode 100644 index 0000000000..51a3be98f7 --- /dev/null +++ b/codes/kotlin/chapter_stack_and_queue/stack.kt @@ -0,0 +1,39 @@ +/** + * File: stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化栈 */ + val stack = Stack() + + /* 元素入栈 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("栈 stack = $stack") + + /* 访问栈顶元素 */ + val peek = stack.peek() + println("栈顶元素 peek = $peek") + + /* 元素出栈 */ + val pop = stack.pop() + println("出栈元素 pop = $pop,出栈后 stack = $stack") + + /* 获取栈的长度 */ + val size = stack.size + println("栈的长度 size = $size") + + /* 判断是否为空 */ + val isEmpty = stack.isEmpty() + println("栈是否为空 = $isEmpty") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_tree/array_binary_tree.kt b/codes/kotlin/chapter_tree/array_binary_tree.kt new file mode 100644 index 0000000000..06c3b0c1f6 --- /dev/null +++ b/codes/kotlin/chapter_tree/array_binary_tree.kt @@ -0,0 +1,127 @@ +/** + * File: array_binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree(private val tree: MutableList) { + /* 列表容量 */ + fun size(): Int { + return tree.size + } + + /* 获取索引为 i 节点的值 */ + fun _val(i: Int): Int? { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) return null + return tree[i] + } + + /* 获取索引为 i 节点的左子节点的索引 */ + fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + fun parent(i: Int): Int { + return (i - 1) / 2 + } + + /* 层序遍历 */ + fun levelOrder(): MutableList { + val res = mutableListOf() + // 直接遍历数组 + for (i in 0..) { + // 若为空位,则返回 + if (_val(i) == null) + return + // 前序遍历 + if ("pre" == order) + res.add(_val(i)) + dfs(left(i), order, res) + // 中序遍历 + if ("in" == order) + res.add(_val(i)) + dfs(right(i), order, res) + // 后序遍历 + if ("post" == order) + res.add(_val(i)) + } + + /* 前序遍历 */ + fun preOrder(): MutableList { + val res = mutableListOf() + dfs(0, "pre", res) + return res + } + + /* 中序遍历 */ + fun inOrder(): MutableList { + val res = mutableListOf() + dfs(0, "in", res) + return res + } + + /* 后序遍历 */ + fun postOrder(): MutableList { + val res = mutableListOf() + dfs(0, "post", res) + return res + } +} + +/* Driver Code */ +fun main() { + // 初始化二叉树 + // 这里借助了一个从列表直接生成二叉树的函数 + val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) + + val root = TreeNode.listToTree(arr) + println("\n初始化二叉树\n") + println("二叉树的数组表示:") + println(arr) + println("二叉树的链表表示:") + printTree(root) + + // 数组表示下的二叉树类 + val abt = ArrayBinaryTree(arr) + + // 访问节点 + val i = 1 + val l = abt.left(i) + val r = abt.right(i) + val p = abt.parent(i) + println("当前节点的索引为 $i ,值为 ${abt._val(i)}") + println("其左子节点的索引为 $l ,值为 ${abt._val(l)}") + println("其右子节点的索引为 $r ,值为 ${abt._val(r)}") + println("其父节点的索引为 $p ,值为 ${abt._val(p)}") + + // 遍历树 + var res = abt.levelOrder() + println("\n层序遍历为:$res") + res = abt.preOrder() + println("前序遍历为:$res") + res = abt.inOrder() + println("中序遍历为:$res") + res = abt.postOrder() + println("后序遍历为:$res") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_tree/avl_tree.kt b/codes/kotlin/chapter_tree/avl_tree.kt new file mode 100644 index 0000000000..0ec93bd8c5 --- /dev/null +++ b/codes/kotlin/chapter_tree/avl_tree.kt @@ -0,0 +1,223 @@ +/** + * File: avl_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import kotlin.math.max + +/* AVL 树 */ +class AVLTree { + var root: TreeNode? = null // 根节点 + + /* 获取节点高度 */ + fun height(node: TreeNode?): Int { + // 空节点高度为 -1 ,叶节点高度为 0 + return node?.height ?: -1 + } + + /* 更新节点高度 */ + private fun updateHeight(node: TreeNode?) { + // 节点高度等于最高子树高度 + 1 + node?.height = max(height(node?.left), height(node?.right)) + 1 + } + + /* 获取平衡因子 */ + fun balanceFactor(node: TreeNode?): Int { + // 空节点平衡因子为 0 + if (node == null) return 0 + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right) + } + + /* 右旋操作 */ + private fun rightRotate(node: TreeNode?): TreeNode { + val child = node!!.left + val grandChild = child!!.right + // 以 child 为原点,将 node 向右旋转 + child.right = node + node.left = grandChild + // 更新节点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } + + /* 左旋操作 */ + private fun leftRotate(node: TreeNode?): TreeNode { + val child = node!!.right + val grandChild = child!!.left + // 以 child 为原点,将 node 向左旋转 + child.left = node + node.right = grandChild + // 更新节点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + private fun rotate(node: TreeNode): TreeNode { + // 获取节点 node 的平衡因子 + val balanceFactor = balanceFactor(node) + // 左偏树 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右旋 + return rightRotate(node) + } else { + // 先左旋后右旋 + node.left = leftRotate(node.left) + return rightRotate(node) + } + } + // 右偏树 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左旋 + return leftRotate(node) + } else { + // 先右旋后左旋 + node.right = rightRotate(node.right) + return leftRotate(node) + } + } + // 平衡树,无须旋转,直接返回 + return node + } + + /* 插入节点 */ + fun insert(_val: Int) { + root = insertHelper(root, _val) + } + + /* 递归插入节点(辅助方法) */ + private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { + if (n == null) + return TreeNode(_val) + var node = n + /* 1. 查找插入位置并插入节点 */ + if (_val < node._val) + node.left = insertHelper(node.left, _val) + else if (_val > node._val) + node.right = insertHelper(node.right, _val) + else + return node // 重复节点不插入,直接返回 + updateHeight(node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node + } + + /* 删除节点 */ + fun remove(_val: Int) { + root = removeHelper(root, _val) + } + + /* 递归删除节点(辅助方法) */ + private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { + var node = n ?: return null + /* 1. 查找节点并删除 */ + if (_val < node._val) + node.left = removeHelper(node.left, _val) + else if (_val > node._val) + node.right = removeHelper(node.right, _val) + else { + if (node.left == null || node.right == null) { + val child = if (node.left != null) + node.left + else + node.right + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null + // 子节点数量 = 1 ,直接删除 node + else + node = child + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node.right + while (temp!!.left != null) { + temp = temp.left + } + node.right = removeHelper(node.right, temp._val) + node._val = temp._val + } + } + updateHeight(node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node + } + + /* 查找节点 */ + fun search(_val: Int): TreeNode? { + var cur = root + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + cur = if (cur._val < _val) + cur.right!! + // 目标节点在 cur 的左子树中 + else if (cur._val > _val) + cur.left + // 找到目标节点,跳出循环 + else + break + } + // 返回目标节点 + return cur + } +} + +fun testInsert(tree: AVLTree, _val: Int) { + tree.insert(_val) + println("\n插入节点 $_val 后,AVL 树为") + printTree(tree.root) +} + +fun testRemove(tree: AVLTree, _val: Int) { + tree.remove(_val) + println("\n删除节点 $_val 后,AVL 树为") + printTree(tree.root) +} + +/* Driver Code */ +fun main() { + /* 初始化空 AVL 树 */ + val avlTree = AVLTree() + + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + testInsert(avlTree, 1) + testInsert(avlTree, 2) + testInsert(avlTree, 3) + testInsert(avlTree, 4) + testInsert(avlTree, 5) + testInsert(avlTree, 8) + testInsert(avlTree, 7) + testInsert(avlTree, 9) + testInsert(avlTree, 10) + testInsert(avlTree, 6) + + /* 插入重复节点 */ + testInsert(avlTree, 7) + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(avlTree, 8) // 删除度为 0 的节点 + testRemove(avlTree, 5) // 删除度为 1 的节点 + testRemove(avlTree, 4) // 删除度为 2 的节点 + + /* 查询节点 */ + val node = avlTree.search(7) + println("\n 查找到的节点对象为 $node,节点值 = ${node?._val}") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_tree/binary_search_tree.kt b/codes/kotlin/chapter_tree/binary_search_tree.kt new file mode 100644 index 0000000000..0a1497be37 --- /dev/null +++ b/codes/kotlin/chapter_tree/binary_search_tree.kt @@ -0,0 +1,157 @@ +/** + * File: binary_search_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 二叉搜索树 */ +class BinarySearchTree { + // 初始化空树 + private var root: TreeNode? = null + + /* 获取二叉树根节点 */ + fun getRoot(): TreeNode? { + return root + } + + /* 查找节点 */ + fun search(num: Int): TreeNode? { + var cur = root + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + cur = if (cur._val < num) + cur.right + // 目标节点在 cur 的左子树中 + else if (cur._val > num) + cur.left + // 找到目标节点,跳出循环 + else + break + } + // 返回目标节点 + return cur + } + + /* 插入节点 */ + fun insert(num: Int) { + // 若树为空,则初始化根节点 + if (root == null) { + root = TreeNode(num) + return + } + var cur = root + var pre: TreeNode? = null + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur._val == num) + return + pre = cur + // 插入位置在 cur 的右子树中 + cur = if (cur._val < num) + cur.right + // 插入位置在 cur 的左子树中 + else + cur.left + } + // 插入节点 + val node = TreeNode(num) + if (pre?._val!! < num) + pre.right = node + else + pre.left = node + } + + /* 删除节点 */ + fun remove(num: Int) { + // 若树为空,直接提前返回 + if (root == null) + return + var cur = root + var pre: TreeNode? = null + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur._val == num) + break + pre = cur + // 待删除节点在 cur 的右子树中 + cur = if (cur._val < num) + cur.right + // 待删除节点在 cur 的左子树中 + else + cur.left + } + // 若无待删除节点,则直接返回 + if (cur == null) + return + // 子节点数量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + val child = if (cur.left != null) + cur.left + else + cur.right + // 删除节点 cur + if (cur != root) { + if (pre!!.left == cur) + pre.left = child + else + pre.right = child + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child + } + // 子节点数量 = 2 + } else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur.right + while (tmp!!.left != null) { + tmp = tmp.left + } + // 递归删除节点 tmp + remove(tmp._val) + // 用 tmp 覆盖 cur + cur._val = tmp._val + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化二叉搜索树 */ + val bst = BinarySearchTree() + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) + for (num in nums) { + bst.insert(num) + } + println("\n初始化的二叉树为\n") + printTree(bst.getRoot()) + + /* 查找节点 */ + val node = bst.search(7) + println("查找到的节点对象为 $node,节点值 = ${node?._val}") + + /* 插入节点 */ + bst.insert(16) + println("\n插入节点 16 后,二叉树为\n") + printTree(bst.getRoot()) + + /* 删除节点 */ + bst.remove(1) + println("\n删除节点 1 后,二叉树为\n") + printTree(bst.getRoot()) + bst.remove(2) + println("\n删除节点 2 后,二叉树为\n") + printTree(bst.getRoot()) + bst.remove(4) + println("\n删除节点 4 后,二叉树为\n") + printTree(bst.getRoot()) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_tree/binary_tree.kt b/codes/kotlin/chapter_tree/binary_tree.kt new file mode 100644 index 0000000000..aca6742ca1 --- /dev/null +++ b/codes/kotlin/chapter_tree/binary_tree.kt @@ -0,0 +1,40 @@ +/** + * File: binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* Driver Code */ +fun main() { + /* 初始化二叉树 */ + // 初始化节点 + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + println("\n初始化二叉树\n") + printTree(n1) + + /* 插入与删除节点 */ + val P = TreeNode(0) + // 在 n1 -> n2 中间插入节点 P + n1.left = P + P.left = n2 + println("\n插入节点 P 后\n") + printTree(n1) + // 删除节点 P + n1.left = n2 + println("\n删除节点 P 后\n") + printTree(n1) +} \ No newline at end of file diff --git a/codes/kotlin/chapter_tree/binary_tree_bfs.kt b/codes/kotlin/chapter_tree/binary_tree_bfs.kt new file mode 100644 index 0000000000..9f5bf6c593 --- /dev/null +++ b/codes/kotlin/chapter_tree/binary_tree_bfs.kt @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import java.util.* + +/* 层序遍历 */ +fun levelOrder(root: TreeNode?): MutableList { + // 初始化队列,加入根节点 + val queue = LinkedList() + queue.add(root) + // 初始化一个列表,用于保存遍历序列 + val list = mutableListOf() + while (queue.isNotEmpty()) { + val node = queue.poll() // 队列出队 + list.add(node?._val!!) // 保存节点值 + if (node.left != null) + queue.offer(node.left) // 左子节点入队 + if (node.right != null) + queue.offer(node.right) // 右子节点入队 + } + return list +} + +/* Driver Code */ +fun main() { + /* 初始化二叉树 */ + // 这里借助了一个从列表直接生成二叉树的函数 + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n初始化二叉树\n") + printTree(root) + + /* 层序遍历 */ + val list = levelOrder(root) + println("\n层序遍历的节点打印序列 = $list") +} \ No newline at end of file diff --git a/codes/kotlin/chapter_tree/binary_tree_dfs.kt b/codes/kotlin/chapter_tree/binary_tree_dfs.kt new file mode 100644 index 0000000000..7a1ae8b9ae --- /dev/null +++ b/codes/kotlin/chapter_tree/binary_tree_dfs.kt @@ -0,0 +1,64 @@ +/** + * File: binary_tree_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +// 初始化列表,用于存储遍历序列 +var list = mutableListOf() + +/* 前序遍历 */ +fun preOrder(root: TreeNode?) { + if (root == null) return + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.add(root._val) + preOrder(root.left) + preOrder(root.right) +} + +/* 中序遍历 */ +fun inOrder(root: TreeNode?) { + if (root == null) return + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root.left) + list.add(root._val) + inOrder(root.right) +} + +/* 后序遍历 */ +fun postOrder(root: TreeNode?) { + if (root == null) return + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root.left) + postOrder(root.right) + list.add(root._val) +} + +/* Driver Code */ +fun main() { + /* 初始化二叉树 */ + // 这里借助了一个从列表直接生成二叉树的函数 + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n初始化二叉树\n") + printTree(root) + + /* 前序遍历 */ + list.clear() + preOrder(root) + println("\n前序遍历的节点打印序列 = $list") + + /* 中序遍历 */ + list.clear() + inOrder(root) + println("\n中序遍历的节点打印序列 = $list") + + /* 后序遍历 */ + list.clear() + postOrder(root) + println("\n后序遍历的节点打印序列 = $list") +} \ No newline at end of file diff --git a/codes/kotlin/utils/ListNode.kt b/codes/kotlin/utils/ListNode.kt new file mode 100644 index 0000000000..2fad5f77d4 --- /dev/null +++ b/codes/kotlin/utils/ListNode.kt @@ -0,0 +1,25 @@ +/** + * File: ListNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 链表节点 */ +class ListNode(var _val: Int) { + var next: ListNode? = null + + companion object { + /* 将列表反序列化为链表 */ + fun arrToLinkedList(arr: IntArray): ListNode? { + val dum = ListNode(0) + var head = dum + for (_val in arr) { + head.next = ListNode(_val) + head = head.next!! + } + return dum.next + } + } +} \ No newline at end of file diff --git a/codes/kotlin/utils/PrintUtil.kt b/codes/kotlin/utils/PrintUtil.kt new file mode 100644 index 0000000000..2e5c7a363b --- /dev/null +++ b/codes/kotlin/utils/PrintUtil.kt @@ -0,0 +1,107 @@ +/** + * File: PrintUtil.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +import java.util.* + +class Trunk(var prev: Trunk?, var str: String) + +/* 打印矩阵(Array) */ +fun printMatrix(matrix: Array>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 打印矩阵(List) */ +fun printMatrix(matrix: MutableList>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 打印链表 */ +fun printLinkedList(h: ListNode?) { + var head = h + val list = mutableListOf() + while (head != null) { + list.add(head._val.toString()) + head = head.next + } + println(list.joinToString(separator = " -> ")) +} + +/* 打印二叉树 */ +fun printTree(root: TreeNode?) { + printTree(root, null, false) +} + +/** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { + if (root == null) { + return + } + + var prevStr = " " + val trunk = Trunk(prev, prevStr) + + printTree(root.right, trunk, true) + + if (prev == null) { + trunk.str = "———" + } else if (isRight) { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + + showTrunks(trunk) + println(" ${root._val}") + + if (prev != null) { + prev.str = prevStr + } + trunk.str = " |" + + printTree(root.left, trunk, false) +} + +fun showTrunks(p: Trunk?) { + if (p == null) { + return + } + showTrunks(p.prev) + print(p.str) +} + +/* 打印哈希表 */ +fun printHashMap(map: Map) { + for ((key, value) in map) { + println("${key.toString()} -> $value") + } +} + +/* 打印堆 */ +fun printHeap(queue: Queue?) { + val list = mutableListOf() + queue?.let { list.addAll(it) } + print("堆的数组表示:") + println(list) + println("堆的树状表示:") + val root = TreeNode.listToTree(list) + printTree(root) +} \ No newline at end of file diff --git a/codes/kotlin/utils/TreeNode.kt b/codes/kotlin/utils/TreeNode.kt new file mode 100644 index 0000000000..408f70b47f --- /dev/null +++ b/codes/kotlin/utils/TreeNode.kt @@ -0,0 +1,69 @@ +/** + * File: TreeNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 二叉树节点类 */ +/* 构造方法 */ +class TreeNode( + var _val: Int // 节点值 +) { + var height: Int = 0 // 节点高度 + var left: TreeNode? = null // 左子节点引用 + var right: TreeNode? = null // 右子节点引用 + + // 序列化编码规则请参考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二叉树的数组表示: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二叉树的链表表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 将列表反序列化为二叉树:递归 */ + companion object { + private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { + if (i < 0 || i >= arr.size || arr[i] == null) { + return null + } + val root = TreeNode(arr[i]!!) + root.left = listToTreeDFS(arr, 2 * i + 1) + root.right = listToTreeDFS(arr, 2 * i + 2) + return root + } + + /* 将列表反序列化为二叉树 */ + fun listToTree(arr: MutableList): TreeNode? { + return listToTreeDFS(arr, 0) + } + + /* 将二叉树序列化为列表:递归 */ + private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { + if (root == null) return + while (i >= res.size) { + res.add(null) + } + res[i] = root._val + treeToListDFS(root.left, 2 * i + 1, res) + treeToListDFS(root.right, 2 * i + 2, res) + } + + /* 将二叉树序列化为列表 */ + fun treeToList(root: TreeNode?): MutableList { + val res = mutableListOf() + treeToListDFS(root, 0, res) + return res + } + } +} \ No newline at end of file diff --git a/codes/kotlin/utils/Vertex.kt b/codes/kotlin/utils/Vertex.kt new file mode 100644 index 0000000000..c65f2a7066 --- /dev/null +++ b/codes/kotlin/utils/Vertex.kt @@ -0,0 +1,30 @@ +/** + * File: Vertex.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 顶点类 */ +class Vertex(val _val: Int) { + companion object { + /* 输入值列表 vals ,返回顶点列表 vets */ + fun valsToVets(vals: IntArray): Array { + val vets = arrayOfNulls(vals.size) + for (i in vals.indices) { + vets[i] = Vertex(vals[i]) + } + return vets + } + + /* 输入顶点列表 vets ,返回值列表 vals */ + fun vetsToVals(vets: MutableList): MutableList { + val vals = mutableListOf() + for (vet in vets) { + vals.add(vet!!._val) + } + return vals + } + } +} \ No newline at end of file diff --git a/codes/python/.gitignore b/codes/python/.gitignore new file mode 100644 index 0000000000..bee8a64b79 --- /dev/null +++ b/codes/python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/codes/python/chapter_array_and_linkedlist/array.py b/codes/python/chapter_array_and_linkedlist/array.py index 8933fb482d..8373ba0969 100644 --- a/codes/python/chapter_array_and_linkedlist/array.py +++ b/codes/python/chapter_array_and_linkedlist/array.py @@ -1,25 +1,25 @@ """ File: array.py Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) +Author: krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import random -""" 随机访问元素 """ -def randomAccess(nums): - # 在区间 [0, len(nums)) 中随机抽取一个数字 - random_index = random.randint(0, len(nums)) + +def random_access(nums: list[int]) -> int: + """随机访问元素""" + # 在区间 [0, len(nums)-1] 中随机抽取一个数字 + random_index = random.randint(0, len(nums) - 1) # 获取并返回随机元素 random_num = nums[random_index] return random_num -""" 扩展数组长度 """ + # 请注意,Python 的 list 是动态数组,可以直接扩展 -# 为了方便学习,本函数将 list 看作是长度不可变的数组 -def extend(nums, enlarge): +# 为了方便学习,本函数将 list 看作长度不可变的数组 +def extend(nums: list[int], enlarge: int) -> list[int]: + """扩展数组长度""" # 初始化一个扩展长度后的数组 res = [0] * (len(nums) + enlarge) # 将原数组中的所有元素复制到新数组 @@ -28,65 +28,73 @@ def extend(nums, enlarge): # 返回扩展后的新数组 return res -""" 在数组的索引 index 处插入元素 num """ -def insert(nums, num, index): + +def insert(nums: list[int], num: int, index: int): + """在数组的索引 index 处插入元素 num""" # 把索引 index 以及之后的所有元素向后移动一位 for i in range(len(nums) - 1, index, -1): nums[i] = nums[i - 1] - # 将 num 赋给 index 处元素 + # 将 num 赋给 index 处的元素 nums[index] = num -""" 删除索引 index 处元素 """ -def remove(nums, index): + +def remove(nums: list[int], index: int): + """删除索引 index 处的元素""" # 把索引 index 之后的所有元素向前移动一位 for i in range(index, len(nums) - 1): nums[i] = nums[i + 1] - -""" 遍历数组 """ -def traverse(nums): + + +def traverse(nums: list[int]): + """遍历数组""" count = 0 # 通过索引遍历数组 for i in range(len(nums)): - count += 1 - # 直接遍历数组 + count += nums[i] + # 直接遍历数组元素 for num in nums: - count += 1 + count += num + # 同时遍历数据索引和元素 + for i, num in enumerate(nums): + count += nums[i] + count += num -""" 在数组中查找指定元素 """ -def find(nums, target): + +def find(nums: list[int], target: int) -> int: + """在数组中查找指定元素""" for i in range(len(nums)): if nums[i] == target: return i return -1 -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化数组 """ + # 初始化数组 arr = [0] * 5 print("数组 arr =", arr) nums = [1, 3, 2, 5, 4] print("数组 nums =", nums) - - """ 随机访问 """ - random_num = randomAccess(nums) + + # 随机访问 + random_num: int = random_access(nums) print("在 nums 中获取随机元素", random_num) - - """ 长度扩展 """ - nums = extend(nums, 3) + + # 长度扩展 + nums: list[int] = extend(nums, 3) print("将数组长度扩展至 8 ,得到 nums =", nums) - - """ 插入元素 """ + + # 插入元素 insert(nums, 6, 3) print("在索引 3 处插入数字 6 ,得到 nums =", nums) - """ 删除元素 """ + # 删除元素 remove(nums, 2) print("删除索引 2 处的元素,得到 nums =", nums) - - """ 遍历数组 """ + + # 遍历数组 traverse(nums) - - """ 查找元素 """ - index = find(nums, 3) + + # 查找元素 + index: int = find(nums, 3) print("在 nums 中查找元素 3 ,得到索引 =", index) diff --git a/codes/python/chapter_array_and_linkedlist/linked_list.py b/codes/python/chapter_array_and_linkedlist/linked_list.py index 4fb6b1ba56..197c8ffb67 100644 --- a/codes/python/chapter_array_and_linkedlist/linked_list.py +++ b/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -1,21 +1,25 @@ """ File: linked_list.py Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) +Author: krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path -""" 在链表的结点 n0 之后插入结点 P """ -def insert(n0, P): +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, print_linked_list + + +def insert(n0: ListNode, P: ListNode): + """在链表的节点 n0 之后插入节点 P""" n1 = n0.next - n0.next = P P.next = n1 + n0.next = P -""" 删除链表的结点 n0 之后的首个结点 """ -def remove(n0): + +def remove(n0: ListNode): + """删除链表的节点 n0 之后的首个节点""" if not n0.next: return # n0 -> P -> n1 @@ -23,16 +27,18 @@ def remove(n0): n1 = P.next n0.next = n1 -""" 访问链表中索引为 index 的结点 """ -def access(head, index): + +def access(head: ListNode, index: int) -> ListNode | None: + """访问链表中索引为 index 的节点""" for _ in range(index): if not head: return None head = head.next return head -""" 在链表中查找值为 target 的首个结点 """ -def find(head, target): + +def find(head: ListNode, target: int) -> int: + """在链表中查找值为 target 的首个节点""" index = 0 while head: if head.val == target: @@ -42,16 +48,16 @@ def find(head, target): return -1 -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化链表 """ - # 初始化各个结点 + # 初始化链表 + # 初始化各个节点 n0 = ListNode(1) n1 = ListNode(3) n2 = ListNode(2) n3 = ListNode(5) n4 = ListNode(4) - # 构建引用指向 + # 构建节点之间的引用 n0.next = n1 n1.next = n2 n2.next = n3 @@ -59,20 +65,21 @@ def find(head, target): print("初始化的链表为") print_linked_list(n0) - """ 插入结点 """ - insert(n0, ListNode(0)) - print("插入结点后的链表为") + # 插入节点 + p = ListNode(0) + insert(n0, p) + print("插入节点后的链表为") print_linked_list(n0) - """ 删除结点 """ + # 删除节点 remove(n0) - print("删除结点后的链表为") + print("删除节点后的链表为") print_linked_list(n0) - """ 访问结点 """ - node = access(n0, 3) - print("链表中索引 3 处的结点的值 = {}".format(node.val)) + # 访问节点 + node: ListNode = access(n0, 3) + print("链表中索引 3 处的节点的值 = {}".format(node.val)) - """ 查找结点 """ - index = find(n0, 2) - print("链表中值为 2 的结点的索引 = {}".format(index)) + # 查找节点 + index: int = find(n0, 2) + print("链表中值为 2 的节点的索引 = {}".format(index)) diff --git a/codes/python/chapter_array_and_linkedlist/list.py b/codes/python/chapter_array_and_linkedlist/list.py index b0e2f2536e..07b4d4d86f 100644 --- a/codes/python/chapter_array_and_linkedlist/list.py +++ b/codes/python/chapter_array_and_linkedlist/list.py @@ -1,63 +1,56 @@ """ File: list.py Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) +Author: krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * - - -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化列表 """ - list = [1, 3, 2, 5, 4] - print("列表 list =", list) - - """ 访问元素 """ - num = list[1] - print("访问索引 1 处的元素,得到 num =", num) - - """ 更新元素 """ - list[1] = 0 - print("将索引 1 处的元素更新为 0 ,得到 list =", list) - - """ 清空列表 """ - list.clear() - print("清空列表后 list =", list) - - """ 尾部添加元素 """ - list.append(1) - list.append(3) - list.append(2) - list.append(5) - list.append(4) - print("添加元素后 list =", list) - - """ 中间插入元素 """ - list.insert(3, 6) - print("在索引 3 处插入数字 6 ,得到 list =", list) - - """ 删除元素 """ - list.pop(3) - print("删除索引 3 处的元素,得到 list =", list) - - """ 通过索引遍历列表 """ - count = 0 - for i in range(len(list)): - count += 1 - - """ 直接遍历列表元素 """ + # 初始化列表 + nums: list[int] = [1, 3, 2, 5, 4] + print("\n列表 nums =", nums) + + # 访问元素 + x: int = nums[1] + print("\n访问索引 1 处的元素,得到 x =", x) + + # 更新元素 + nums[1] = 0 + print("\n将索引 1 处的元素更新为 0 ,得到 nums =", nums) + + # 清空列表 + nums.clear() + print("\n清空列表后 nums =", nums) + + # 在尾部添加元素 + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("\n添加元素后 nums =", nums) + + # 在中间插入元素 + nums.insert(3, 6) + print("\n在索引 3 处插入数字 6 ,得到 nums =", nums) + + # 删除元素 + nums.pop(3) + print("\n删除索引 3 处的元素,得到 nums =", nums) + + # 通过索引遍历列表 count = 0 - for n in list: - count += 1 - - """ 拼接两个列表 """ - list1 = [6, 8, 7, 10, 9] - list += list1 - print("将列表 list1 拼接到 list 之后,得到 list =", list) - - """ 排序列表 """ - list.sort() - print("排序列表后 list =", list) + for i in range(len(nums)): + count += nums[i] + # 直接遍历列表元素 + for num in nums: + count += num + + # 拼接两个列表 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + print("\n将列表 nums1 拼接到 nums 之后,得到 nums =", nums) + + # 排序列表 + nums.sort() + print("\n排序列表后 nums =", nums) diff --git a/codes/python/chapter_array_and_linkedlist/my_list.py b/codes/python/chapter_array_and_linkedlist/my_list.py index bd76fdcd06..c80c841843 100644 --- a/codes/python/chapter_array_and_linkedlist/my_list.py +++ b/codes/python/chapter_array_and_linkedlist/my_list.py @@ -1,113 +1,118 @@ """ File: my_list.py Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) +Author: krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" 列表类简易实现 """ class MyList: - """ 构造函数 """ + """列表类""" + def __init__(self): - self.__capacity = 10 # 列表容量 - self.__nums = [0] * self.__capacity # 数组(存储列表元素) - self.__size = 0 # 列表长度(即当前元素数量) - self.__extend_ratio = 2 # 每次列表扩容的倍数 - - """ 获取列表长度(即当前元素数量) """ - def size(self): - return self.__size - - """ 获取列表容量 """ - def capacity(self): - return self.__capacity - - """ 访问元素 """ - def get(self, index): - # 索引如果越界则抛出异常,下同 - assert index < self.__size, "索引越界" - return self.__nums[index] - - """ 更新元素 """ - def set(self, num, index): - assert index < self.__size, "索引越界" - self.__nums[index] = num - - """ 中间插入(尾部添加)元素 """ - def add(self, num, index=-1): - assert index < self.__size, "索引越界" - # 若不指定索引 index ,则向数组尾部添加元素 - if index == -1: - index = self.__size + """构造方法""" + self._capacity: int = 10 # 列表容量 + self._arr: list[int] = [0] * self._capacity # 数组(存储列表元素) + self._size: int = 0 # 列表长度(当前元素数量) + self._extend_ratio: int = 2 # 每次列表扩容的倍数 + + def size(self) -> int: + """获取列表长度(当前元素数量)""" + return self._size + + def capacity(self) -> int: + """获取列表容量""" + return self._capacity + + def get(self, index: int) -> int: + """访问元素""" + # 索引如果越界,则抛出异常,下同 + if index < 0 or index >= self._size: + raise IndexError("索引越界") + return self._arr[index] + + def set(self, num: int, index: int): + """更新元素""" + if index < 0 or index >= self._size: + raise IndexError("索引越界") + self._arr[index] = num + + def add(self, num: int): + """在尾部添加元素""" + # 元素数量超出容量时,触发扩容机制 + if self.size() == self.capacity(): + self.extend_capacity() + self._arr[self._size] = num + self._size += 1 + + def insert(self, num: int, index: int): + """在中间插入元素""" + if index < 0 or index >= self._size: + raise IndexError("索引越界") # 元素数量超出容量时,触发扩容机制 - if self.__size == self.capacity(): + if self._size == self.capacity(): self.extend_capacity() - # 索引 i 以及之后的元素都向后移动一位 - for j in range(self.__size - 1, index - 1, -1): - self.__nums[j + 1] = self.__nums[j] - self.__nums[index] = num + # 将索引 index 以及之后的元素都向后移动一位 + for j in range(self._size - 1, index - 1, -1): + self._arr[j + 1] = self._arr[j] + self._arr[index] = num # 更新元素数量 - self.__size += 1 - - """ 删除元素 """ - def remove(self, index): - assert index < self.__size, "索引越界" - num = self.nums[index] - # 索引 i 之后的元素都向前移动一位 - for j in range(index, self.__size - 1): - self.__nums[j] = self.__nums[j + 1] + self._size += 1 + + def remove(self, index: int) -> int: + """删除元素""" + if index < 0 or index >= self._size: + raise IndexError("索引越界") + num = self._arr[index] + # 将索引 index 之后的元素都向前移动一位 + for j in range(index, self._size - 1): + self._arr[j] = self._arr[j + 1] # 更新元素数量 - self.__size -= 1 - # 返回被删除元素 + self._size -= 1 + # 返回被删除的元素 return num - """ 列表扩容 """ def extend_capacity(self): - # 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 - self.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1) + """列表扩容""" + # 新建一个长度为原数组 _extend_ratio 倍的新数组,并将原数组复制到新数组 + self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1) # 更新列表容量 - self.__capacity = len(self.__nums) - - """ 返回有效长度的列表 """ - def to_array(self): - return self.__nums[:self.__size] + self._capacity = len(self._arr) + + def to_array(self) -> list[int]: + """返回有效长度的列表""" + return self._arr[: self._size] -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化列表 """ - list = MyList() - """ 尾部添加元素 """ - list.add(1) - list.add(3) - list.add(2) - list.add(5) - list.add(4) - print("列表 list = {} ,容量 = {} ,长度 = {}" - .format(list.to_array(), list.capacity(), list.size())) - - """ 中间插入元素 """ - list.add(num=6, index=3) - print("在索引 3 处插入数字 6 ,得到 list =", list.to_array()) - - """ 删除元素 """ - list.remove(3) - print("删除索引 3 处的元素,得到 list =", list.to_array()) - - """ 访问元素 """ - num = list.get(1) + # 初始化列表 + nums = MyList() + # 在尾部添加元素 + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + print(f"列表 nums = {nums.to_array()} ,容量 = {nums.capacity()} ,长度 = {nums.size()}") + + # 在中间插入元素 + nums.insert(6, index=3) + print("在索引 3 处插入数字 6 ,得到 nums =", nums.to_array()) + + # 删除元素 + nums.remove(3) + print("删除索引 3 处的元素,得到 nums =", nums.to_array()) + + # 访问元素 + num = nums.get(1) print("访问索引 1 处的元素,得到 num =", num) - """ 更新元素 """ - list.set(0, 1) - print("将索引 1 处的元素更新为 0 ,得到 list =", list.to_array()) + # 更新元素 + nums.set(0, 1) + print("将索引 1 处的元素更新为 0 ,得到 nums =", nums.to_array()) - """ 测试扩容机制 """ + # 测试扩容机制 for i in range(10): # 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list.add(i) - print("扩容后的列表 list = {} ,容量 = {} ,长度 = {}" - .format(list.to_array(), list.capacity(), list.size())) + nums.add(i) + print(f"扩容后的列表 {nums.to_array()} ,容量 = {nums.capacity()} ,长度 = {nums.size()}") diff --git a/codes/python/chapter_backtracking/n_queens.py b/codes/python/chapter_backtracking/n_queens.py new file mode 100644 index 0000000000..93aa5f8ae7 --- /dev/null +++ b/codes/python/chapter_backtracking/n_queens.py @@ -0,0 +1,62 @@ +""" +File: n_queens.py +Created Time: 2023-04-26 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + row: int, + n: int, + state: list[list[str]], + res: list[list[list[str]]], + cols: list[bool], + diags1: list[bool], + diags2: list[bool], +): + """回溯算法:n 皇后""" + # 当放置完所有行时,记录解 + if row == n: + res.append([list(row) for row in state]) + return + # 遍历所有列 + for col in range(n): + # 计算该格子对应的主对角线和次对角线 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if not cols[col] and not diags1[diag1] and not diags2[diag2]: + # 尝试:将皇后放置在该格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = True + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:将该格子恢复为空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = False + + +def n_queens(n: int) -> list[list[list[str]]]: + """求解 n 皇后""" + # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + state = [["#" for _ in range(n)] for _ in range(n)] + cols = [False] * n # 记录列是否有皇后 + diags1 = [False] * (2 * n - 1) # 记录主对角线上是否有皇后 + diags2 = [False] * (2 * n - 1) # 记录次对角线上是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 4 + res = n_queens(n) + + print(f"输入棋盘长宽为 {n}") + print(f"皇后放置方案共有 {len(res)} 种") + for state in res: + print("--------------------") + for row in state: + print(row) diff --git a/codes/python/chapter_backtracking/permutations_i.py b/codes/python/chapter_backtracking/permutations_i.py new file mode 100644 index 0000000000..9250c722c5 --- /dev/null +++ b/codes/python/chapter_backtracking/permutations_i.py @@ -0,0 +1,44 @@ +""" +File: permutations_i.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """回溯算法:全排列 I""" + # 当状态长度等于元素数量时,记录解 + if len(state) == len(choices): + res.append(list(state)) + return + # 遍历所有选择 + for i, choice in enumerate(choices): + # 剪枝:不允许重复选择元素 + if not selected[i]: + # 尝试:做出选择,更新状态 + selected[i] = True + state.append(choice) + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = False + state.pop() + + +def permutations_i(nums: list[int]) -> list[list[int]]: + """全排列 I""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 3] + + res = permutations_i(nums) + + print(f"输入数组 nums = {nums}") + print(f"所有排列 res = {res}") diff --git a/codes/python/chapter_backtracking/permutations_ii.py b/codes/python/chapter_backtracking/permutations_ii.py new file mode 100644 index 0000000000..145cfc707f --- /dev/null +++ b/codes/python/chapter_backtracking/permutations_ii.py @@ -0,0 +1,46 @@ +""" +File: permutations_ii.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """回溯算法:全排列 II""" + # 当状态长度等于元素数量时,记录解 + if len(state) == len(choices): + res.append(list(state)) + return + # 遍历所有选择 + duplicated = set[int]() + for i, choice in enumerate(choices): + # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if not selected[i] and choice not in duplicated: + # 尝试:做出选择,更新状态 + duplicated.add(choice) # 记录选择过的元素值 + selected[i] = True + state.append(choice) + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = False + state.pop() + + +def permutations_ii(nums: list[int]) -> list[list[int]]: + """全排列 II""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 2] + + res = permutations_ii(nums) + + print(f"输入数组 nums = {nums}") + print(f"所有排列 res = {res}") diff --git a/codes/python/chapter_backtracking/preorder_traversal_i_compact.py b/codes/python/chapter_backtracking/preorder_traversal_i_compact.py new file mode 100644 index 0000000000..944447fea2 --- /dev/null +++ b/codes/python/chapter_backtracking/preorder_traversal_i_compact.py @@ -0,0 +1,36 @@ +""" +File: preorder_traversal_i_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序遍历:例题一""" + if root is None: + return + if root.val == 7: + # 记录解 + res.append(root) + pre_order(root.left) + pre_order(root.right) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 前序遍历 + res = list[TreeNode]() + pre_order(root) + + print("\n输出所有值为 7 的节点") + print([node.val for node in res]) diff --git a/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py b/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py new file mode 100644 index 0000000000..28731a21d0 --- /dev/null +++ b/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py @@ -0,0 +1,42 @@ +""" +File: preorder_traversal_ii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序遍历:例题二""" + if root is None: + return + # 尝试 + path.append(root) + if root.val == 7: + # 记录解 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 前序遍历 + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\n输出所有根节点到节点 7 的路径") + for path in res: + print([node.val for node in path]) diff --git a/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py new file mode 100644 index 0000000000..c8704b8180 --- /dev/null +++ b/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -0,0 +1,43 @@ +""" +File: preorder_traversal_iii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序遍历:例题三""" + # 剪枝 + if root is None or root.val == 3: + return + # 尝试 + path.append(root) + if root.val == 7: + # 记录解 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 前序遍历 + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for path in res: + print([node.val for node in path]) diff --git a/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/codes/python/chapter_backtracking/preorder_traversal_iii_template.py new file mode 100644 index 0000000000..057454e0c1 --- /dev/null +++ b/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -0,0 +1,71 @@ +""" +File: preorder_traversal_iii_template.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def is_solution(state: list[TreeNode]) -> bool: + """判断当前状态是否为解""" + return state and state[-1].val == 7 + + +def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): + """记录解""" + res.append(list(state)) + + +def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: + """判断在当前状态下,该选择是否合法""" + return choice is not None and choice.val != 3 + + +def make_choice(state: list[TreeNode], choice: TreeNode): + """更新状态""" + state.append(choice) + + +def undo_choice(state: list[TreeNode], choice: TreeNode): + """恢复状态""" + state.pop() + + +def backtrack( + state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] +): + """回溯算法:例题三""" + # 检查是否为解 + if is_solution(state): + # 记录解 + record_solution(state, res) + # 遍历所有选择 + for choice in choices: + # 剪枝:检查选择是否合法 + if is_valid(state, choice): + # 尝试:做出选择,更新状态 + make_choice(state, choice) + # 进行下一轮选择 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 回溯算法 + res = [] + backtrack(state=[], choices=[root], res=res) + + print("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点") + for path in res: + print([node.val for node in path]) diff --git a/codes/python/chapter_backtracking/subset_sum_i.py b/codes/python/chapter_backtracking/subset_sum_i.py new file mode 100644 index 0000000000..c11e9406f5 --- /dev/null +++ b/codes/python/chapter_backtracking/subset_sum_i.py @@ -0,0 +1,48 @@ +""" +File: subset_sum_i.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """回溯算法:子集和 I""" + # 子集和等于 target 时,记录解 + if target == 0: + res.append(list(state)) + return + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in range(start, len(choices)): + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0: + break + # 尝试:做出选择,更新 target, start + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() + + +def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 I""" + state = [] # 状态(子集) + nums.sort() # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + print(f"输入数组 nums = {nums}, target = {target}") + print(f"所有和等于 {target} 的子集 res = {res}") diff --git a/codes/python/chapter_backtracking/subset_sum_i_naive.py b/codes/python/chapter_backtracking/subset_sum_i_naive.py new file mode 100644 index 0000000000..89b15ea84c --- /dev/null +++ b/codes/python/chapter_backtracking/subset_sum_i_naive.py @@ -0,0 +1,50 @@ +""" +File: subset_sum_i_naive.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], + target: int, + total: int, + choices: list[int], + res: list[list[int]], +): + """回溯算法:子集和 I""" + # 子集和等于 target 时,记录解 + if total == target: + res.append(list(state)) + return + # 遍历所有选择 + for i in range(len(choices)): + # 剪枝:若子集和超过 target ,则跳过该选择 + if total + choices[i] > target: + continue + # 尝试:做出选择,更新元素和 total + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() + + +def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 I(包含重复子集)""" + state = [] # 状态(子集) + total = 0 # 子集和 + res = [] # 结果列表(子集列表) + backtrack(state, target, total, nums, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + print(f"输入数组 nums = {nums}, target = {target}") + print(f"所有和等于 {target} 的子集 res = {res}") + print(f"请注意,该方法输出的结果包含重复集合") diff --git a/codes/python/chapter_backtracking/subset_sum_ii.py b/codes/python/chapter_backtracking/subset_sum_ii.py new file mode 100644 index 0000000000..3462c67603 --- /dev/null +++ b/codes/python/chapter_backtracking/subset_sum_ii.py @@ -0,0 +1,52 @@ +""" +File: subset_sum_ii.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """回溯算法:子集和 II""" + # 子集和等于 target 时,记录解 + if target == 0: + res.append(list(state)) + return + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + # 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in range(start, len(choices)): + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0: + break + # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start and choices[i] == choices[i - 1]: + continue + # 尝试:做出选择,更新 target, start + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() + + +def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 II""" + state = [] # 状态(子集) + nums.sort() # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + print(f"输入数组 nums = {nums}, target = {target}") + print(f"所有和等于 {target} 的子集 res = {res}") diff --git a/codes/python/chapter_computational_complexity/iteration.py b/codes/python/chapter_computational_complexity/iteration.py new file mode 100644 index 0000000000..57a0346d9c --- /dev/null +++ b/codes/python/chapter_computational_complexity/iteration.py @@ -0,0 +1,65 @@ +""" +File: iteration.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def for_loop(n: int) -> int: + """for 循环""" + res = 0 + # 循环求和 1, 2, ..., n-1, n + for i in range(1, n + 1): + res += i + return res + + +def while_loop(n: int) -> int: + """while 循环""" + res = 0 + i = 1 # 初始化条件变量 + # 循环求和 1, 2, ..., n-1, n + while i <= n: + res += i + i += 1 # 更新条件变量 + return res + + +def while_loop_ii(n: int) -> int: + """while 循环(两次更新)""" + res = 0 + i = 1 # 初始化条件变量 + # 循环求和 1, 4, 10, ... + while i <= n: + res += i + # 更新条件变量 + i += 1 + i *= 2 + return res + + +def nested_for_loop(n: int) -> str: + """双层 for 循环""" + res = "" + # 循环 i = 1, 2, ..., n-1, n + for i in range(1, n + 1): + # 循环 j = 1, 2, ..., n-1, n + for j in range(1, n + 1): + res += f"({i}, {j}), " + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = for_loop(n) + print(f"\nfor 循环的求和结果 res = {res}") + + res = while_loop(n) + print(f"\nwhile 循环的求和结果 res = {res}") + + res = while_loop_ii(n) + print(f"\nwhile 循环(两次更新)求和结果 res = {res}") + + res = nested_for_loop(n) + print(f"\n双层 for 循环的遍历结果 {res}") diff --git a/codes/python/chapter_computational_complexity/leetcode_two_sum.py b/codes/python/chapter_computational_complexity/leetcode_two_sum.py deleted file mode 100644 index cb3bf3b027..0000000000 --- a/codes/python/chapter_computational_complexity/leetcode_two_sum.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -File: leetcode_two_sum.py -Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) -""" - -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * - -class SolutionBruteForce: - def twoSum(self, nums: List[int], target: int) -> List[int]: - for i in range(len(nums) - 1): - for j in range(i + 1, len(nums)): - if nums[i] + nums[j] == target: - return i, j - return [] - -class SolutionHashMap: - def twoSum(self, nums: List[int], target: int) -> List[int]: - dic = {} - for i in range(len(nums)): - if target - nums[i] in dic: - return dic[target - nums[i]], i - dic[nums[i]] = i - return [] - - -""" Driver Code """ -if __name__ == '__main__': - # ======= Test Case ======= - nums = [ 2,7,11,15 ]; - target = 9; - - # ====== Driver Code ====== - # 方法一 - res = SolutionBruteForce().twoSum(nums, target); - print("方法一 res =", res) - # 方法二 - res = SolutionHashMap().twoSum(nums, target); - print("方法二 res =", res) diff --git a/codes/python/chapter_computational_complexity/recursion.py b/codes/python/chapter_computational_complexity/recursion.py new file mode 100644 index 0000000000..311f4dafb9 --- /dev/null +++ b/codes/python/chapter_computational_complexity/recursion.py @@ -0,0 +1,69 @@ +""" +File: recursion.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def recur(n: int) -> int: + """递归""" + # 终止条件 + if n == 1: + return 1 + # 递:递归调用 + res = recur(n - 1) + # 归:返回结果 + return n + res + + +def for_loop_recur(n: int) -> int: + """使用迭代模拟递归""" + # 使用一个显式的栈来模拟系统调用栈 + stack = [] + res = 0 + # 递:递归调用 + for i in range(n, 0, -1): + # 通过“入栈操作”模拟“递” + stack.append(i) + # 归:返回结果 + while stack: + # 通过“出栈操作”模拟“归” + res += stack.pop() + # res = 1+2+3+...+n + return res + + +def tail_recur(n, res): + """尾递归""" + # 终止条件 + if n == 0: + return res + # 尾递归调用 + return tail_recur(n - 1, res + n) + + +def fib(n: int) -> int: + """斐波那契数列:递归""" + # 终止条件 f(1) = 0, f(2) = 1 + if n == 1 or n == 2: + return n - 1 + # 递归调用 f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # 返回结果 f(n) + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = recur(n) + print(f"\n递归函数的求和结果 res = {res}") + + res = for_loop_recur(n) + print(f"\n使用迭代模拟递归求和结果 res = {res}") + + res = tail_recur(n, 0) + print(f"\n尾递归函数的求和结果 res = {res}") + + res = fib(n) + print(f"\n斐波那契数列的第 {n} 项为 {res}") diff --git a/codes/python/chapter_computational_complexity/space_complexity.py b/codes/python/chapter_computational_complexity/space_complexity.py index 7efe8d9485..715b160e0c 100644 --- a/codes/python/chapter_computational_complexity/space_complexity.py +++ b/codes/python/chapter_computational_complexity/space_complexity.py @@ -1,75 +1,87 @@ """ File: space_complexity.py Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) +Author: krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path -""" 函数 """ -def function(): - # do something +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, TreeNode, print_tree + + +def function() -> int: + """函数""" + # 执行某些操作 return 0 -""" 常数阶 """ -def constant(n): + +def constant(n: int): + """常数阶""" # 常量、变量、对象占用 O(1) 空间 a = 0 nums = [0] * 10000 node = ListNode(0) # 循环中的变量占用 O(1) 空间 for _ in range(n): - c = 0 + c = 0 # 循环中的函数占用 O(1) 空间 for _ in range(n): function() -""" 线性阶 """ -def linear(n): + +def linear(n: int): + """线性阶""" # 长度为 n 的列表占用 O(n) 空间 nums = [0] * n # 长度为 n 的哈希表占用 O(n) 空间 - mapp = {} + hmap = dict[int, str]() for i in range(n): - mapp[i] = str(i) + hmap[i] = str(i) -""" 线性阶(递归实现) """ -def linearRecur(n): + +def linear_recur(n: int): + """线性阶(递归实现)""" print("递归 n =", n) - if n == 1: return - linearRecur(n - 1) + if n == 1: + return + linear_recur(n - 1) + -""" 平方阶 """ -def quadratic(n): +def quadratic(n: int): + """平方阶""" # 二维列表占用 O(n^2) 空间 num_matrix = [[0] * n for _ in range(n)] -""" 平方阶(递归实现) """ -def quadratic_recur(n): - if n <= 0: return 0 + +def quadratic_recur(n: int) -> int: + """平方阶(递归实现)""" + if n <= 0: + return 0 + # 数组 nums 长度为 n, n-1, ..., 2, 1 nums = [0] * n - print("递归 n =", n, "中的 nums 长度 =", len(nums)) return quadratic_recur(n - 1) -""" 指数阶(建立满二叉树) """ -def build_tree(n): - if n == 0: return None + +def build_tree(n: int) -> TreeNode | None: + """指数阶(建立满二叉树)""" + if n == 0: + return None root = TreeNode(0) root.left = build_tree(n - 1) root.right = build_tree(n - 1) return root -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": n = 5 # 常数阶 constant(n) # 线性阶 linear(n) - linearRecur(n) + linear_recur(n) # 平方阶 quadratic(n) quadratic_recur(n) diff --git a/codes/python/chapter_computational_complexity/time_complexity.py b/codes/python/chapter_computational_complexity/time_complexity.py index bfb5b3b115..5e25f2798b 100644 --- a/codes/python/chapter_computational_complexity/time_complexity.py +++ b/codes/python/chapter_computational_complexity/time_complexity.py @@ -1,64 +1,67 @@ """ File: time_complexity.py Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) +Author: krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" 常数阶 """ -def constant(n): +def constant(n: int) -> int: + """常数阶""" count = 0 size = 100000 for _ in range(size): count += 1 return count -""" 线性阶 """ -def linear(n): + +def linear(n: int) -> int: + """线性阶""" count = 0 for _ in range(n): count += 1 return count -""" 线性阶(遍历数组)""" -def array_traversal(nums): + +def array_traversal(nums: list[int]) -> int: + """线性阶(遍历数组)""" count = 0 # 循环次数与数组长度成正比 for num in nums: count += 1 return count -""" 平方阶 """ -def quadratic(n): + +def quadratic(n: int) -> int: + """平方阶""" count = 0 - # 循环次数与数组长度成平方关系 + # 循环次数与数据大小 n 成平方关系 for i in range(n): for j in range(n): count += 1 return count -""" 平方阶(冒泡排序)""" -def bubble_sort(nums): + +def bubble_sort(nums: list[int]) -> int: + """平方阶(冒泡排序)""" count = 0 # 计数器 - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 + # 外循环:未排序区间为 [0, i] for i in range(len(nums) - 1, 0, -1): - # 内循环:冒泡操作 + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in range(i): if nums[j] > nums[j + 1]: # 交换 nums[j] 与 nums[j + 1] - tmp = nums[j] + tmp: int = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp count += 3 # 元素交换包含 3 个单元操作 return count -""" 指数阶(循环实现)""" -def exponential(n): - count, base = 0, 1 - # cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + +def exponential(n: int) -> int: + """指数阶(循环实现)""" + count = 0 + base = 1 + # 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for _ in range(n): for _ in range(base): count += 1 @@ -66,36 +69,46 @@ def exponential(n): # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 return count -""" 指数阶(递归实现)""" -def exp_recur(n): - if n == 1: return 1 + +def exp_recur(n: int) -> int: + """指数阶(递归实现)""" + if n == 1: + return 1 return exp_recur(n - 1) + exp_recur(n - 1) + 1 -""" 对数阶(循环实现)""" -def logarithmic(n): + +def logarithmic(n: int) -> int: + """对数阶(循环实现)""" count = 0 while n > 1: n = n / 2 count += 1 return count -""" 对数阶(递归实现)""" -def log_recur(n): - if n <= 1: return 0 + +def log_recur(n: int) -> int: + """对数阶(递归实现)""" + if n <= 1: + return 0 return log_recur(n / 2) + 1 -""" 线性对数阶 """ -def linear_log_recur(n): - if n <= 1: return 1 - count = linear_log_recur(n // 2) + \ - linear_log_recur(n // 2) + +def linear_log_recur(n: int) -> int: + """线性对数阶""" + if n <= 1: + return 1 + # 一分为二,子问题的规模减小一半 + count = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 当前子问题包含 n 个操作 for _ in range(n): count += 1 return count -""" 阶乘阶(递归实现)""" -def factorial_recur(n): - if n == 0: return 1 + +def factorial_recur(n: int) -> int: + """阶乘阶(递归实现)""" + if n == 0: + return 1 count = 0 # 从 1 个分裂出 n 个 for _ in range(n): @@ -103,38 +116,38 @@ def factorial_recur(n): return count -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": # 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 n = 8 print("输入数据大小 n =", n) count = constant(n) - print("常数阶的计算操作数量 =", count) + print("常数阶的操作数量 =", count) count = linear(n) - print("线性阶的计算操作数量 =", count) + print("线性阶的操作数量 =", count) count = array_traversal([0] * n) - print("线性阶(遍历数组)的计算操作数量 =", count) + print("线性阶(遍历数组)的操作数量 =", count) count = quadratic(n) - print("平方阶的计算操作数量 =", count) - nums = [i for i in range(n, 0, -1)] # [n,n-1,...,2,1] + print("平方阶的操作数量 =", count) + nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] count = bubble_sort(nums) - print("平方阶(冒泡排序)的计算操作数量 =", count) + print("平方阶(冒泡排序)的操作数量 =", count) count = exponential(n) - print("指数阶(循环实现)的计算操作数量 =", count) + print("指数阶(循环实现)的操作数量 =", count) count = exp_recur(n) - print("指数阶(递归实现)的计算操作数量 =", count) + print("指数阶(递归实现)的操作数量 =", count) count = logarithmic(n) - print("对数阶(循环实现)的计算操作数量 =", count) + print("对数阶(循环实现)的操作数量 =", count) count = log_recur(n) - print("对数阶(递归实现)的计算操作数量 =", count) + print("对数阶(递归实现)的操作数量 =", count) count = linear_log_recur(n) - print("线性对数阶(递归实现)的计算操作数量 =", count) + print("线性对数阶(递归实现)的操作数量 =", count) count = factorial_recur(n) - print("阶乘阶(递归实现)的计算操作数量 =", count) + print("阶乘阶(递归实现)的操作数量 =", count) diff --git a/codes/python/chapter_computational_complexity/worst_best_time_complexity.py b/codes/python/chapter_computational_complexity/worst_best_time_complexity.py index b70dce4f0a..8889e21c3c 100644 --- a/codes/python/chapter_computational_complexity/worst_best_time_complexity.py +++ b/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -1,34 +1,36 @@ """ File: worst_best_time_complexity.py Created Time: 2022-11-25 -Author: Krahets (krahets@163.com) +Author: krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import random -""" 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """ -def random_numbers(n): - # 生成数组 nums =: 1, 2, 3, ..., n + +def random_numbers(n: int) -> list[int]: + """生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱""" + # 生成数组 nums =: 1, 2, 3, ..., n nums = [i for i in range(1, n + 1)] # 随机打乱数组元素 random.shuffle(nums) return nums -""" 查找数组 nums 中数字 1 所在索引 """ -def find_one(nums): + +def find_one(nums: list[int]) -> int: + """查找数组 nums 中数字 1 所在索引""" for i in range(len(nums)): + # 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + # 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if nums[i] == 1: return i return -1 -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": for i in range(10): n = 100 - nums = random_numbers(n) - index = find_one(nums) + nums: list[int] = random_numbers(n) + index: int = find_one(nums) print("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) print("数字 1 的索引为", index) diff --git a/codes/python/chapter_divide_and_conquer/binary_search_recur.py b/codes/python/chapter_divide_and_conquer/binary_search_recur.py new file mode 100644 index 0000000000..c4bcce39ca --- /dev/null +++ b/codes/python/chapter_divide_and_conquer/binary_search_recur.py @@ -0,0 +1,40 @@ +""" +File: binary_search_recur.py +Created Time: 2023-07-17 +Author: krahets (krahets@163.com) +""" + + +def dfs(nums: list[int], target: int, i: int, j: int) -> int: + """二分查找:问题 f(i, j)""" + # 若区间为空,代表无目标元素,则返回 -1 + if i > j: + return -1 + # 计算中点索引 m + m = (i + j) // 2 + if nums[m] < target: + # 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j) + elif nums[m] > target: + # 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1) + else: + # 找到目标元素,返回其索引 + return m + + +def binary_search(nums: list[int], target: int) -> int: + """二分查找""" + n = len(nums) + # 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1) + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分查找(双闭区间) + index = binary_search(nums, target) + print("目标元素 6 的索引 = ", index) diff --git a/codes/python/chapter_divide_and_conquer/build_tree.py b/codes/python/chapter_divide_and_conquer/build_tree.py new file mode 100644 index 0000000000..5e3f6cca94 --- /dev/null +++ b/codes/python/chapter_divide_and_conquer/build_tree.py @@ -0,0 +1,54 @@ +""" +File: build_tree.py +Created Time: 2023-07-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +def dfs( + preorder: list[int], + inorder_map: dict[int, int], + i: int, + l: int, + r: int, +) -> TreeNode | None: + """构建二叉树:分治""" + # 子树区间为空时终止 + if r - l < 0: + return None + # 初始化根节点 + root = TreeNode(preorder[i]) + # 查询 m ,从而划分左右子树 + m = inorder_map[preorder[i]] + # 子问题:构建左子树 + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # 子问题:构建右子树 + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + # 返回根节点 + return root + + +def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: + """构建二叉树""" + # 初始化哈希表,存储 inorder 元素到索引的映射 + inorder_map = {val: i for i, val in enumerate(inorder)} + root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + print(f"前序遍历 = {preorder}") + print(f"中序遍历 = {inorder}") + + root = build_tree(preorder, inorder) + print("构建的二叉树为:") + print_tree(root) diff --git a/codes/python/chapter_divide_and_conquer/hanota.py b/codes/python/chapter_divide_and_conquer/hanota.py new file mode 100644 index 0000000000..83e15f6c91 --- /dev/null +++ b/codes/python/chapter_divide_and_conquer/hanota.py @@ -0,0 +1,53 @@ +""" +File: hanota.py +Created Time: 2023-07-16 +Author: krahets (krahets@163.com) +""" + + +def move(src: list[int], tar: list[int]): + """移动一个圆盘""" + # 从 src 顶部拿出一个圆盘 + pan = src.pop() + # 将圆盘放入 tar 顶部 + tar.append(pan) + + +def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): + """求解汉诺塔问题 f(i)""" + # 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1: + move(src, tar) + return + # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf) + # 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar) + # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar) + + +def solve_hanota(A: list[int], B: list[int], C: list[int]): + """求解汉诺塔问题""" + n = len(A) + # 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C) + + +"""Driver Code""" +if __name__ == "__main__": + # 列表尾部是柱子顶部 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + print("初始状态下:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") + + solve_hanota(A, B, C) + + print("圆盘移动完成后:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") diff --git a/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py b/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py new file mode 100644 index 0000000000..bff4cdbf20 --- /dev/null +++ b/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py @@ -0,0 +1,37 @@ +""" +File: climbing_stairs_backtrack.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: + """回溯""" + # 当爬到第 n 阶时,方案数量加 1 + if state == n: + res[0] += 1 + # 遍历所有选择 + for choice in choices: + # 剪枝:不允许越过第 n 阶 + if state + choice > n: + continue + # 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res) + # 回退 + + +def climbing_stairs_backtrack(n: int) -> int: + """爬楼梯:回溯""" + choices = [1, 2] # 可选择向上爬 1 阶或 2 阶 + state = 0 # 从第 0 阶开始爬 + res = [0] # 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res) + return res[0] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_backtrack(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py b/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py new file mode 100644 index 0000000000..5c0188b724 --- /dev/null +++ b/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py @@ -0,0 +1,29 @@ +""" +File: climbing_stairs_constraint_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_constraint_dp(n: int) -> int: + """带约束爬楼梯:动态规划""" + if n == 1 or n == 2: + return 1 + # 初始化 dp 表,用于存储子问题的解 + dp = [[0] * 3 for _ in range(n + 1)] + # 初始状态:预设最小子问题的解 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + return dp[n][1] + dp[n][2] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_constraint_dp(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py b/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py new file mode 100644 index 0000000000..5069af3097 --- /dev/null +++ b/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py @@ -0,0 +1,28 @@ +""" +File: climbing_stairs_dfs.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int) -> int: + """搜索""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1) + dfs(i - 2) + return count + + +def climbing_stairs_dfs(n: int) -> int: + """爬楼梯:搜索""" + return dfs(n) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py b/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py new file mode 100644 index 0000000000..96fa1f7c42 --- /dev/null +++ b/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py @@ -0,0 +1,35 @@ +""" +File: climbing_stairs_dfs_mem.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int, mem: list[int]) -> int: + """记忆化搜索""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # 若存在记录 dp[i] ,则直接返回之 + if mem[i] != -1: + return mem[i] + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # 记录 dp[i] + mem[i] = count + return count + + +def climbing_stairs_dfs_mem(n: int) -> int: + """爬楼梯:记忆化搜索""" + # mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + mem = [-1] * (n + 1) + return dfs(n, mem) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs_mem(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py b/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py new file mode 100644 index 0000000000..8d2b037063 --- /dev/null +++ b/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py @@ -0,0 +1,40 @@ +""" +File: climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_dp(n: int) -> int: + """爬楼梯:动态规划""" + if n == 1 or n == 2: + return n + # 初始化 dp 表,用于存储子问题的解 + dp = [0] * (n + 1) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = 1, 2 + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] + + +def climbing_stairs_dp_comp(n: int) -> int: + """爬楼梯:空间优化后的动态规划""" + if n == 1 or n == 2: + return n + a, b = 1, 2 + for _ in range(3, n + 1): + a, b = b, a + b + return b + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dp(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") + + res = climbing_stairs_dp_comp(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/codes/python/chapter_dynamic_programming/coin_change.py b/codes/python/chapter_dynamic_programming/coin_change.py new file mode 100644 index 0000000000..95a7fb88ed --- /dev/null +++ b/codes/python/chapter_dynamic_programming/coin_change.py @@ -0,0 +1,60 @@ +""" +File: coin_change.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_dp(coins: list[int], amt: int) -> int: + """零钱兑换:动态规划""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 状态转移:首行首列 + for a in range(1, amt + 1): + dp[0][a] = MAX + # 状态转移:其余行和列 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else: + # 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + return dp[n][amt] if dp[n][amt] != MAX else -1 + + +def coin_change_dp_comp(coins: list[int], amt: int) -> int: + """零钱兑换:空间优化后的动态规划""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [MAX] * (amt + 1) + dp[0] = 0 + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + else: + # 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + return dp[amt] if dp[amt] != MAX else -1 + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 4 + + # 动态规划 + res = coin_change_dp(coins, amt) + print(f"凑到目标金额所需的最少硬币数量为 {res}") + + # 空间优化后的动态规划 + res = coin_change_dp_comp(coins, amt) + print(f"凑到目标金额所需的最少硬币数量为 {res}") diff --git a/codes/python/chapter_dynamic_programming/coin_change_ii.py b/codes/python/chapter_dynamic_programming/coin_change_ii.py new file mode 100644 index 0000000000..94fe5840ca --- /dev/null +++ b/codes/python/chapter_dynamic_programming/coin_change_ii.py @@ -0,0 +1,58 @@ +""" +File: coin_change_ii.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_ii_dp(coins: list[int], amt: int) -> int: + """零钱兑换 II:动态规划""" + n = len(coins) + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 初始化首列 + for i in range(n + 1): + dp[i][0] = 1 + # 状态转移 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else: + # 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + return dp[n][amt] + + +def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: + """零钱兑换 II:空间优化后的动态规划""" + n = len(coins) + # 初始化 dp 表 + dp = [0] * (amt + 1) + dp[0] = 1 + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + else: + # 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + return dp[amt] + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 5 + + # 动态规划 + res = coin_change_ii_dp(coins, amt) + print(f"凑出目标金额的硬币组合数量为 {res}") + + # 空间优化后的动态规划 + res = coin_change_ii_dp_comp(coins, amt) + print(f"凑出目标金额的硬币组合数量为 {res}") diff --git a/codes/python/chapter_dynamic_programming/edit_distance.py b/codes/python/chapter_dynamic_programming/edit_distance.py new file mode 100644 index 0000000000..69dd0fca69 --- /dev/null +++ b/codes/python/chapter_dynamic_programming/edit_distance.py @@ -0,0 +1,123 @@ +""" +File: edit_distancde.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + + +def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: + """编辑距离:暴力搜索""" + # 若 s 和 t 都为空,则返回 0 + if i == 0 and 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 edit_distance_dfs(s, t, i - 1, j - 1) + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # 返回最少编辑步数 + return min(insert, delete, replace) + 1 + + +def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int: + """编辑距离:记忆化搜索""" + # 若 s 和 t 都为空,则返回 0 + if i == 0 and 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 edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 记录并返回最少编辑步数 + mem[i][j] = min(insert, delete, replace) + 1 + return mem[i][j] + + +def edit_distance_dp(s: str, t: str) -> int: + """编辑距离:动态规划""" + n, m = len(s), len(t) + dp = [[0] * (m + 1) for _ in range(n + 1)] + # 状态转移:首行首列 + for i in range(1, n + 1): + dp[i][0] = i + for j in range(1, m + 1): + dp[0][j] = j + # 状态转移:其余行和列 + for i in range(1, n + 1): + for j in range(1, m + 1): + if s[i - 1] == t[j - 1]: + # 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + else: + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + return dp[n][m] + + +def edit_distance_dp_comp(s: str, t: str) -> int: + """编辑距离:空间优化后的动态规划""" + n, m = len(s), len(t) + dp = [0] * (m + 1) + # 状态转移:首行 + for j in range(1, m + 1): + dp[j] = j + # 状态转移:其余行 + for i in range(1, n + 1): + # 状态转移:首列 + leftup = dp[0] # 暂存 dp[i-1, j-1] + dp[0] += 1 + # 状态转移:其余列 + for j in range(1, m + 1): + temp = dp[j] + if s[i - 1] == t[j - 1]: + # 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + else: + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(dp[j - 1], dp[j], leftup) + 1 + leftup = temp # 更新为下一轮的 dp[i-1, j-1] + return dp[m] + + +"""Driver Code""" +if __name__ == "__main__": + s = "bag" + t = "pack" + n, m = len(s), len(t) + + # 暴力搜索 + res = edit_distance_dfs(s, t, n, m) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") + + # 记忆化搜索 + mem = [[-1] * (m + 1) for _ in range(n + 1)] + res = edit_distance_dfs_mem(s, t, mem, n, m) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") + + # 动态规划 + res = edit_distance_dp(s, t) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") + + # 空间优化后的动态规划 + res = edit_distance_dp_comp(s, t) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") diff --git a/codes/python/chapter_dynamic_programming/knapsack.py b/codes/python/chapter_dynamic_programming/knapsack.py new file mode 100644 index 0000000000..2edf7ece4d --- /dev/null +++ b/codes/python/chapter_dynamic_programming/knapsack.py @@ -0,0 +1,101 @@ +""" +File: knapsack.py +Created Time: 2023-07-03 +Author: krahets (krahets@163.com) +""" + + +def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: + """0-1 背包:暴力搜索""" + # 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 or c == 0: + return 0 + # 若超过背包容量,则只能选择不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs(wgt, val, i - 1, c) + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 返回两种方案中价值更大的那一个 + return max(no, yes) + + +def knapsack_dfs_mem( + wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int +) -> int: + """0-1 背包:记忆化搜索""" + # 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 or c == 0: + return 0 + # 若已有记录,则直接返回 + if mem[i][c] != -1: + return mem[i][c] + # 若超过背包容量,则只能选择不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes) + return mem[i][c] + + +def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 状态转移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:空间优化后的动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 状态转移 + for i in range(1, n + 1): + # 倒序遍历 + for c in range(cap, 0, -1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # 暴力搜索 + res = knapsack_dfs(wgt, val, n, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 记忆化搜索 + mem = [[-1] * (cap + 1) for _ in range(n + 1)] + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 动态规划 + res = knapsack_dp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 空间优化后的动态规划 + res = knapsack_dp_comp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") diff --git a/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py b/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py new file mode 100644 index 0000000000..a73f205d6b --- /dev/null +++ b/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py @@ -0,0 +1,43 @@ +""" +File: min_cost_climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def min_cost_climbing_stairs_dp(cost: list[int]) -> int: + """爬楼梯最小代价:动态规划""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + # 初始化 dp 表,用于存储子问题的解 + dp = [0] * (n + 1) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = cost[1], cost[2] + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + return dp[n] + + +def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: + """爬楼梯最小代价:空间优化后的动态规划""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + a, b = cost[1], cost[2] + for i in range(3, n + 1): + a, b = b, min(a, b) + cost[i] + return b + + +"""Driver Code""" +if __name__ == "__main__": + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print(f"输入楼梯的代价列表为 {cost}") + + res = min_cost_climbing_stairs_dp(cost) + print(f"爬完楼梯的最低代价为 {res}") + + res = min_cost_climbing_stairs_dp_comp(cost) + print(f"爬完楼梯的最低代价为 {res}") diff --git a/codes/python/chapter_dynamic_programming/min_path_sum.py b/codes/python/chapter_dynamic_programming/min_path_sum.py new file mode 100644 index 0000000000..41d451b268 --- /dev/null +++ b/codes/python/chapter_dynamic_programming/min_path_sum.py @@ -0,0 +1,104 @@ +""" +File: min_path_sum.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + +from math import inf + + +def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: + """最小路径和:暴力搜索""" + # 若为左上角单元格,则终止搜索 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0: + return inf + # 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j] + + +def min_path_sum_dfs_mem( + grid: list[list[int]], mem: list[list[int]], i: int, j: int +) -> int: + """最小路径和:记忆化搜索""" + # 若为左上角单元格,则终止搜索 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0: + return inf + # 若已有记录,则直接返回 + if mem[i][j] != -1: + return mem[i][j] + # 左边和上边单元格的最小路径代价 + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] + + +def min_path_sum_dp(grid: list[list[int]]) -> int: + """最小路径和:动态规划""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [[0] * m for _ in range(n)] + dp[0][0] = grid[0][0] + # 状态转移:首行 + for j in range(1, m): + dp[0][j] = dp[0][j - 1] + grid[0][j] + # 状态转移:首列 + for i in range(1, n): + dp[i][0] = dp[i - 1][0] + grid[i][0] + # 状态转移:其余行和列 + for i in range(1, n): + for j in range(1, m): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + return dp[n - 1][m - 1] + + +def min_path_sum_dp_comp(grid: list[list[int]]) -> int: + """最小路径和:空间优化后的动态规划""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [0] * m + # 状态转移:首行 + dp[0] = grid[0][0] + for j in range(1, m): + dp[j] = dp[j - 1] + grid[0][j] + # 状态转移:其余行 + for i in range(1, n): + # 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + # 状态转移:其余列 + for j in range(1, m): + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + return dp[m - 1] + + +"""Driver Code""" +if __name__ == "__main__": + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = len(grid), len(grid[0]) + + # 暴力搜索 + res = min_path_sum_dfs(grid, n - 1, m - 1) + print(f"从左上角到右下角的最小路径和为 {res}") + + # 记忆化搜索 + mem = [[-1] * m for _ in range(n)] + res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) + print(f"从左上角到右下角的最小路径和为 {res}") + + # 动态规划 + res = min_path_sum_dp(grid) + print(f"从左上角到右下角的最小路径和为 {res}") + + # 空间优化后的动态规划 + res = min_path_sum_dp_comp(grid) + print(f"从左上角到右下角的最小路径和为 {res}") diff --git a/codes/python/chapter_dynamic_programming/unbounded_knapsack.py b/codes/python/chapter_dynamic_programming/unbounded_knapsack.py new file mode 100644 index 0000000000..ef8f5ffe7b --- /dev/null +++ b/codes/python/chapter_dynamic_programming/unbounded_knapsack.py @@ -0,0 +1,55 @@ +""" +File: unbounded_knapsack.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 状态转移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:空间优化后的动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 动态规划 + res = unbounded_knapsack_dp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 空间优化后的动态规划 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") diff --git a/codes/python/chapter_graph/graph_adjacency_list.py b/codes/python/chapter_graph/graph_adjacency_list.py new file mode 100644 index 0000000000..8087660fa5 --- /dev/null +++ b/codes/python/chapter_graph/graph_adjacency_list.py @@ -0,0 +1,111 @@ +""" +File: graph_adjacency_list.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets + + +class GraphAdjList: + """基于邻接表实现的无向图类""" + + def __init__(self, edges: list[list[Vertex]]): + """构造方法""" + # 邻接表,key:顶点,value:该顶点的所有邻接顶点 + self.adj_list = dict[Vertex, list[Vertex]]() + # 添加所有顶点和边 + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + def size(self) -> int: + """获取顶点数量""" + return len(self.adj_list) + + def add_edge(self, vet1: Vertex, vet2: Vertex): + """添加边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 添加边 vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """删除边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 删除边 vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + def add_vertex(self, vet: Vertex): + """添加顶点""" + if vet in self.adj_list: + return + # 在邻接表中添加一个新链表 + self.adj_list[vet] = [] + + def remove_vertex(self, vet: Vertex): + """删除顶点""" + if vet not in self.adj_list: + raise ValueError() + # 在邻接表中删除顶点 vet 对应的链表 + self.adj_list.pop(vet) + # 遍历其他顶点的链表,删除所有包含 vet 的边 + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) + + def print(self): + """打印邻接表""" + print("邻接表 =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + # 添加边 + # 顶点 1, 2 即 v[0], v[2] + graph.add_edge(v[0], v[2]) + print("\n添加边 1-2 后,图为") + graph.print() + + # 删除边 + # 顶点 1, 3 即 v[0], v[1] + graph.remove_edge(v[0], v[1]) + print("\n删除边 1-3 后,图为") + graph.print() + + # 添加顶点 + v5 = Vertex(6) + graph.add_vertex(v5) + print("\n添加顶点 6 后,图为") + graph.print() + + # 删除顶点 + # 顶点 3 即 v[1] + graph.remove_vertex(v[1]) + print("\n删除顶点 3 后,图为") + graph.print() diff --git a/codes/python/chapter_graph/graph_adjacency_matrix.py b/codes/python/chapter_graph/graph_adjacency_matrix.py new file mode 100644 index 0000000000..ecafbc5644 --- /dev/null +++ b/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -0,0 +1,116 @@ +""" +File: graph_adjacency_matrix.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, print_matrix + + +class GraphAdjMat: + """基于邻接矩阵实现的无向图类""" + + def __init__(self, vertices: list[int], edges: list[list[int]]): + """构造方法""" + # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + self.vertices: list[int] = [] + # 邻接矩阵,行列索引对应“顶点索引” + self.adj_mat: list[list[int]] = [] + # 添加顶点 + for val in vertices: + self.add_vertex(val) + # 添加边 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """获取顶点数量""" + return len(self.vertices) + + def add_vertex(self, val: int): + """添加顶点""" + n = self.size() + # 向顶点列表中添加新顶点的值 + self.vertices.append(val) + # 在邻接矩阵中添加一行 + new_row = [0] * n + self.adj_mat.append(new_row) + # 在邻接矩阵中添加一列 + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """删除顶点""" + if index >= self.size(): + raise IndexError() + # 在顶点列表中移除索引 index 的顶点 + self.vertices.pop(index) + # 在邻接矩阵中删除索引 index 的行 + self.adj_mat.pop(index) + # 在邻接矩阵中删除索引 index 的列 + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """添加边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + def remove_edge(self, i: int, j: int): + """删除边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 + + def print(self): + """打印邻接矩阵""" + print("顶点列表 =", self.vertices) + print("邻接矩阵 =") + print_matrix(self.adj_mat) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat(vertices, edges) + print("\n初始化后,图为") + graph.print() + + # 添加边 + # 顶点 1, 2 的索引分别为 0, 2 + graph.add_edge(0, 2) + print("\n添加边 1-2 后,图为") + graph.print() + + # 删除边 + # 顶点 1, 3 的索引分别为 0, 1 + graph.remove_edge(0, 1) + print("\n删除边 1-3 后,图为") + graph.print() + + # 添加顶点 + graph.add_vertex(6) + print("\n添加顶点 6 后,图为") + graph.print() + + # 删除顶点 + # 顶点 3 的索引为 1 + graph.remove_vertex(1) + print("\n删除顶点 3 后,图为") + graph.print() diff --git a/codes/python/chapter_graph/graph_bfs.py b/codes/python/chapter_graph/graph_bfs.py new file mode 100644 index 0000000000..ccad3c9140 --- /dev/null +++ b/codes/python/chapter_graph/graph_bfs.py @@ -0,0 +1,64 @@ +""" +File: graph_bfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets, vets_to_vals +from collections import deque +from graph_adjacency_list import GraphAdjList + + +def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """广度优先遍历""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希集合,用于记录已被访问过的顶点 + visited = set[Vertex]([start_vet]) + # 队列用于实现 BFS + que = deque[Vertex]([start_vet]) + # 以顶点 vet 为起点,循环直至访问完所有顶点 + while len(que) > 0: + vet = que.popleft() # 队首顶点出队 + res.append(vet) # 记录访问顶点 + # 遍历该顶点的所有邻接顶点 + for adj_vet in graph.adj_list[vet]: + if adj_vet in visited: + continue # 跳过已被访问的顶点 + que.append(adj_vet) # 只入队未访问的顶点 + visited.add(adj_vet) # 标记该顶点已被访问 + # 返回顶点遍历序列 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [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]], + ] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + # 广度优先遍历 + res = graph_bfs(graph, v[0]) + print("\n广度优先遍历(BFS)顶点序列为") + print(vets_to_vals(res)) diff --git a/codes/python/chapter_graph/graph_dfs.py b/codes/python/chapter_graph/graph_dfs.py new file mode 100644 index 0000000000..ede6daa6b7 --- /dev/null +++ b/codes/python/chapter_graph/graph_dfs.py @@ -0,0 +1,57 @@ +""" +File: graph_dfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vets_to_vals, vals_to_vets +from graph_adjacency_list import GraphAdjList + + +def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """深度优先遍历辅助函数""" + res.append(vet) # 记录访问顶点 + visited.add(vet) # 标记该顶点已被访问 + # 遍历该顶点的所有邻接顶点 + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # 跳过已被访问的顶点 + # 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) + + +def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """深度优先遍历""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希集合,用于记录已被访问过的顶点 + visited = set[Vertex]() + dfs(graph, visited, res, start_vet) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + # 深度优先遍历 + res = graph_dfs(graph, v[0]) + print("\n深度优先遍历(DFS)顶点序列为") + print(vets_to_vals(res)) diff --git a/codes/python/chapter_greedy/coin_change_greedy.py b/codes/python/chapter_greedy/coin_change_greedy.py new file mode 100644 index 0000000000..3e52b1a4ad --- /dev/null +++ b/codes/python/chapter_greedy/coin_change_greedy.py @@ -0,0 +1,48 @@ +""" +File: coin_change_greedy.py +Created Time: 2023-07-18 +Author: krahets (krahets@163.com) +""" + + +def coin_change_greedy(coins: list[int], amt: int) -> int: + """零钱兑换:贪心""" + # 假设 coins 列表有序 + i = len(coins) - 1 + count = 0 + # 循环进行贪心选择,直到无剩余金额 + while amt > 0: + # 找到小于且最接近剩余金额的硬币 + while i > 0 and coins[i] > amt: + i -= 1 + # 选择 coins[i] + amt -= coins[i] + count += 1 + # 若未找到可行方案,则返回 -1 + return count if amt == 0 else -1 + + +"""Driver Code""" +if __name__ == "__main__": + # 贪心:能够保证找到全局最优解 + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"凑到 {amt} 所需的最少硬币数量为 {res}") + + # 贪心:无法保证找到全局最优解 + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"凑到 {amt} 所需的最少硬币数量为 {res}") + print(f"实际上需要的最少数量为 3 ,即 20 + 20 + 20") + + # 贪心:无法保证找到全局最优解 + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"凑到 {amt} 所需的最少硬币数量为 {res}") + print(f"实际上需要的最少数量为 2 ,即 49 + 49") diff --git a/codes/python/chapter_greedy/fractional_knapsack.py b/codes/python/chapter_greedy/fractional_knapsack.py new file mode 100644 index 0000000000..97723cfc5d --- /dev/null +++ b/codes/python/chapter_greedy/fractional_knapsack.py @@ -0,0 +1,46 @@ +""" +File: fractional_knapsack.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + + +class Item: + """物品""" + + def __init__(self, w: int, v: int): + self.w = w # 物品重量 + self.v = v # 物品价值 + + +def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: + """分数背包:贪心""" + # 创建物品列表,包含两个属性:重量、价值 + items = [Item(w, v) for w, v in zip(wgt, val)] + # 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort(key=lambda item: item.v / item.w, reverse=True) + # 循环贪心选择 + res = 0 + for item in items: + if item.w <= cap: + # 若剩余容量充足,则将当前物品整个装进背包 + res += item.v + cap -= item.w + else: + # 若剩余容量不足,则将当前物品的一部分装进背包 + res += (item.v / item.w) * cap + # 已无剩余容量,因此跳出循环 + break + return res + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # 贪心算法 + res = fractional_knapsack(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") diff --git a/codes/python/chapter_greedy/max_capacity.py b/codes/python/chapter_greedy/max_capacity.py new file mode 100644 index 0000000000..fe94725af9 --- /dev/null +++ b/codes/python/chapter_greedy/max_capacity.py @@ -0,0 +1,33 @@ +""" +File: max_capacity.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + + +def max_capacity(ht: list[int]) -> int: + """最大容量:贪心""" + # 初始化 i, j,使其分列数组两端 + i, j = 0, len(ht) - 1 + # 初始最大容量为 0 + res = 0 + # 循环贪心选择,直至两板相遇 + while i < j: + # 更新最大容量 + cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + # 向内移动短板 + if ht[i] < ht[j]: + i += 1 + else: + j -= 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # 贪心算法 + res = max_capacity(ht) + print(f"最大容量为 {res}") diff --git a/codes/python/chapter_greedy/max_product_cutting.py b/codes/python/chapter_greedy/max_product_cutting.py new file mode 100644 index 0000000000..21116a756d --- /dev/null +++ b/codes/python/chapter_greedy/max_product_cutting.py @@ -0,0 +1,33 @@ +""" +File: max_product_cutting.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + +import math + + +def max_product_cutting(n: int) -> int: + """最大切分乘积:贪心""" + # 当 n <= 3 时,必须切分出一个 1 + if n <= 3: + return 1 * (n - 1) + # 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + a, b = n // 3, n % 3 + if b == 1: + # 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return int(math.pow(3, a - 1)) * 2 * 2 + if b == 2: + # 当余数为 2 时,不做处理 + return int(math.pow(3, a)) * 2 + # 当余数为 0 时,不做处理 + return int(math.pow(3, a)) + + +"""Driver Code""" +if __name__ == "__main__": + n = 58 + + # 贪心算法 + res = max_product_cutting(n) + print(f"最大切分乘积为 {res}") diff --git a/codes/python/chapter_hashing/array_hash_map.py b/codes/python/chapter_hashing/array_hash_map.py index a65f1544b6..945f44bb0a 100644 --- a/codes/python/chapter_hashing/array_hash_map.py +++ b/codes/python/chapter_hashing/array_hash_map.py @@ -4,110 +4,114 @@ Author: msk397 (machangxinq@gmail.com) """ -""" 键值对 int->String """ -class Entry: - def __init__(self, key, val): + +class Pair: + """键值对""" + + def __init__(self, key: int, val: str): self.key = key self.val = val -""" 基于数组简易实现的哈希表 """ class ArrayHashMap: + """基于数组实现的哈希表""" + def __init__(self): - # 初始化一个长度为 100 的桶(数组) - self.bucket = [None] * 100 + """构造方法""" + # 初始化数组,包含 100 个桶 + self.buckets: list[Pair | None] = [None] * 100 - """ 哈希函数 """ - def hashFunc(self, key): + def hash_func(self, key: int) -> int: + """哈希函数""" index = key % 100 return index - """ 查询操作 """ - def get(self, key): - index = self.hashFunc(key) - pair = self.bucket[index] + def get(self, key: int) -> str: + """查询操作""" + index: int = self.hash_func(key) + pair: Pair = self.buckets[index] if pair is None: return None return pair.val - """ 添加操作 """ - def put(self, key, val): - pair = Entry(key, val) - index = self.hashFunc(key) - self.bucket[index] = pair - - """ 删除操作 """ - def remove(self, key): - index = self.hashFunc(key) - # 置为None,代表删除 - self.bucket[index] = None - - """ 获取所有键值对 """ - def entrySet(self): - result = [] - for pair in self.bucket: + def put(self, key: int, val: str): + """添加操作""" + pair = Pair(key, val) + index: int = self.hash_func(key) + self.buckets[index] = pair + + def remove(self, key: int): + """删除操作""" + index: int = self.hash_func(key) + # 置为 None ,代表删除 + self.buckets[index] = None + + def entry_set(self) -> list[Pair]: + """获取所有键值对""" + result: list[Pair] = [] + for pair in self.buckets: if pair is not None: result.append(pair) return result - """ 获取所有键 """ - def keySet(self): + def key_set(self) -> list[int]: + """获取所有键""" result = [] - for pair in self.bucket: + for pair in self.buckets: if pair is not None: result.append(pair.key) return result - """ 获取所有值 """ - def valueSet(self): + def value_set(self) -> list[str]: + """获取所有值""" result = [] - for pair in self.bucket: + for pair in self.buckets: if pair is not None: result.append(pair.val) return result - """ 打印哈希表 """ def print(self): - for pair in self.bucket: + """打印哈希表""" + for pair in self.buckets: if pair is not None: print(pair.key, "->", pair.val) -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化哈希表 """ - mapp = ArrayHashMap() + # 初始化哈希表 + hmap = ArrayHashMap() - """ 添加操作 """ + # 添加操作 # 在哈希表中添加键值对 (key, value) - mapp.put(12836, "小哈") - mapp.put(15937, "小啰") - mapp.put(16750, "小算") - mapp.put(13276, "小法") - mapp.put(10583, "小鸭") + hmap.put(12836, "小哈") + hmap.put(15937, "小啰") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鸭") print("\n添加完成后,哈希表为\nKey -> Value") - mapp.print() + hmap.print() - """ 查询操作 """ - # 向哈希表输入键 key ,得到值 value - name = mapp.get(15937) + # 查询操作 + # 向哈希表中输入键 key ,得到值 value + name = hmap.get(15937) print("\n输入学号 15937 ,查询到姓名 " + name) - """ 删除操作 """ + # 删除操作 # 在哈希表中删除键值对 (key, value) - mapp.remove(10583) + hmap.remove(10583) print("\n删除 10583 后,哈希表为\nKey -> Value") - mapp.print() + hmap.print() - """ 遍历哈希表 """ + # 遍历哈希表 print("\n遍历键值对 Key->Value") - for pair in mapp.entrySet(): + for pair in hmap.entry_set(): print(pair.key, "->", pair.val) print("\n单独遍历键 Key") - for key in mapp.keySet(): + for key in hmap.key_set(): print(key) print("\n单独遍历值 Value") - for val in mapp.valueSet(): + for val in hmap.value_set(): print(val) diff --git a/codes/python/chapter_hashing/built_in_hash.py b/codes/python/chapter_hashing/built_in_hash.py new file mode 100644 index 0000000000..90c534c307 --- /dev/null +++ b/codes/python/chapter_hashing/built_in_hash.py @@ -0,0 +1,37 @@ +""" +File: built_in_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + +"""Driver Code""" +if __name__ == "__main__": + num = 3 + hash_num = hash(num) + print(f"整数 {num} 的哈希值为 {hash_num}") + + bol = True + hash_bol = hash(bol) + print(f"布尔量 {bol} 的哈希值为 {hash_bol}") + + dec = 3.14159 + hash_dec = hash(dec) + print(f"小数 {dec} 的哈希值为 {hash_dec}") + + str = "Hello 算法" + hash_str = hash(str) + print(f"字符串 {str} 的哈希值为 {hash_str}") + + tup = (12836, "小哈") + hash_tup = hash(tup) + print(f"元组 {tup} 的哈希值为 {hash(hash_tup)}") + + obj = ListNode(0) + hash_obj = hash(obj) + print(f"节点对象 {obj} 的哈希值为 {hash_obj}") diff --git a/codes/python/chapter_hashing/hash_map.py b/codes/python/chapter_hashing/hash_map.py index f5db786330..7a9fe810db 100644 --- a/codes/python/chapter_hashing/hash_map.py +++ b/codes/python/chapter_hashing/hash_map.py @@ -4,46 +4,47 @@ Author: msk397 (machangxinq@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_dict -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化哈希表 """ - mapp = {} + # 初始化哈希表 + hmap = dict[int, str]() - """ 添加操作 """ + # 添加操作 # 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" print("\n添加完成后,哈希表为\nKey -> Value") - print_dict(mapp) + print_dict(hmap) - """ 查询操作 """ - # 向哈希表输入键 key ,得到值 value - name = mapp[15937] + # 查询操作 + # 向哈希表中输入键 key ,得到值 value + name: str = hmap[15937] print("\n输入学号 15937 ,查询到姓名 " + name) - """ 删除操作 """ + # 删除操作 # 在哈希表中删除键值对 (key, value) - mapp.pop(10583) + hmap.pop(10583) print("\n删除 10583 后,哈希表为\nKey -> Value") - print_dict(mapp) + print_dict(hmap) - """ 遍历哈希表 """ + # 遍历哈希表 print("\n遍历键值对 Key->Value") - for key, value in mapp.items(): + for key, value in hmap.items(): print(key, "->", value) print("\n单独遍历键 Key") - for key in mapp.keys(): + for key in hmap.keys(): print(key) print("\n单独遍历值 Value") - for val in mapp.values(): + for val in hmap.values(): print(val) diff --git a/codes/python/chapter_hashing/hash_map_chaining.py b/codes/python/chapter_hashing/hash_map_chaining.py new file mode 100644 index 0000000000..bc6609c6da --- /dev/null +++ b/codes/python/chapter_hashing/hash_map_chaining.py @@ -0,0 +1,118 @@ +""" +File: hash_map_chaining.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapChaining: + """链式地址哈希表""" + + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets = [[] for _ in range(self.capacity)] # 桶数组 + + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity + + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity + + def get(self, key: int) -> str | None: + """查询操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若找到 key ,则返回对应 val + for pair in bucket: + if pair.key == key: + return pair.val + # 若未找到 key ,则返回 None + return None + + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in bucket: + if pair.key == key: + pair.val = val + return + # 若无该 key ,则将键值对添加至尾部 + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 + + def remove(self, key: int): + """删除操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,从中删除键值对 + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + break + + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [[] for _ in range(self.capacity)] + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for bucket in buckets: + for pair in bucket: + self.put(pair.key, pair.val) + + def print(self): + """打印哈希表""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化哈希表 + hashmap = HashMapChaining() + + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小啰") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鸭") + print("\n添加完成后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() + + # 查询操作 + # 向哈希表中输入键 key ,得到值 value + name = hashmap.get(13276) + print("\n输入学号 13276 ,查询到姓名 " + name) + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hashmap.remove(12836) + print("\n删除 12836 后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() diff --git a/codes/python/chapter_hashing/hash_map_open_addressing.py b/codes/python/chapter_hashing/hash_map_open_addressing.py new file mode 100644 index 0000000000..7b3d2ba6e3 --- /dev/null +++ b/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -0,0 +1,138 @@ +""" +File: hash_map_open_addressing.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapOpenAddressing: + """开放寻址哈希表""" + + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets: list[Pair | None] = [None] * self.capacity # 桶数组 + self.TOMBSTONE = Pair(-1, "-1") # 删除标记 + + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity + + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity + + def find_bucket(self, key: int) -> int: + """搜索 key 对应的桶索引""" + index = self.hash_func(key) + first_tombstone = -1 + # 线性探测,当遇到空桶时跳出 + while self.buckets[index] is not None: + # 若遇到 key ,返回对应的桶索引 + if self.buckets[index].key == key: + # 若之前遇到了删除标记,则将键值对移动至该索引处 + if first_tombstone != -1: + self.buckets[first_tombstone] = self.buckets[index] + self.buckets[index] = self.TOMBSTONE + return first_tombstone # 返回移动后的桶索引 + return index # 返回桶索引 + # 记录遇到的首个删除标记 + if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: + first_tombstone = index + # 计算桶索引,越过尾部则返回头部 + index = (index + 1) % self.capacity + # 若 key 不存在,则返回添加点的索引 + return index if first_tombstone == -1 else first_tombstone + + def get(self, key: int) -> str: + """查询操作""" + # 搜索 key 对应的桶索引 + index = self.find_bucket(key) + # 若找到键值对,则返回对应 val + if self.buckets[index] not in [None, self.TOMBSTONE]: + return self.buckets[index].val + # 若键值对不存在,则返回 None + return None + + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + # 搜索 key 对应的桶索引 + index = self.find_bucket(key) + # 若找到键值对,则覆盖 val 并返回 + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index].val = val + return + # 若键值对不存在,则添加该键值对 + self.buckets[index] = Pair(key, val) + self.size += 1 + + def remove(self, key: int): + """删除操作""" + # 搜索 key 对应的桶索引 + index = self.find_bucket(key) + # 若找到键值对,则用删除标记覆盖它 + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index] = self.TOMBSTONE + self.size -= 1 + + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets_tmp = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [None] * self.capacity + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for pair in buckets_tmp: + if pair not in [None, self.TOMBSTONE]: + self.put(pair.key, pair.val) + + def print(self): + """打印哈希表""" + for pair in self.buckets: + if pair is None: + print("None") + elif pair is self.TOMBSTONE: + print("TOMBSTONE") + else: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化哈希表 + hashmap = HashMapOpenAddressing() + + # 添加操作 + # 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小啰") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + hashmap.print() + + # 查询操作 + # 向哈希表中输入键 key ,得到值 val + name = hashmap.get(13276) + print("\n输入学号 13276 ,查询到姓名 " + name) + + # 删除操作 + # 在哈希表中删除键值对 (key, val) + hashmap.remove(16750) + print("\n删除 16750 后,哈希表为\nKey -> Value") + hashmap.print() diff --git a/codes/python/chapter_hashing/simple_hash.py b/codes/python/chapter_hashing/simple_hash.py new file mode 100644 index 0000000000..37a6f8209d --- /dev/null +++ b/codes/python/chapter_hashing/simple_hash.py @@ -0,0 +1,58 @@ +""" +File: simple_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + + +def add_hash(key: str) -> int: + """加法哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash += ord(c) + return hash % modulus + + +def mul_hash(key: str) -> int: + """乘法哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = 31 * hash + ord(c) + return hash % modulus + + +def xor_hash(key: str) -> int: + """异或哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash ^= ord(c) + return hash % modulus + + +def rot_hash(key: str) -> int: + """旋转哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = (hash << 4) ^ (hash >> 28) ^ ord(c) + return hash % modulus + + +"""Driver Code""" +if __name__ == "__main__": + key = "Hello 算法" + + hash = add_hash(key) + print(f"加法哈希值为 {hash}") + + hash = mul_hash(key) + print(f"乘法哈希值为 {hash}") + + hash = xor_hash(key) + print(f"异或哈希值为 {hash}") + + hash = rot_hash(key) + print(f"旋转哈希值为 {hash}") diff --git a/codes/python/chapter_heap/heap.py b/codes/python/chapter_heap/heap.py new file mode 100644 index 0000000000..fb73a282af --- /dev/null +++ b/codes/python/chapter_heap/heap.py @@ -0,0 +1,71 @@ +""" +File: heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def test_push(heap: list, val: int, flag: int = 1): + heapq.heappush(heap, flag * val) # 元素入堆 + print(f"\n元素 {val} 入堆后") + print_heap([flag * val for val in heap]) + + +def test_pop(heap: list, flag: int = 1): + val = flag * heapq.heappop(heap) # 堆顶元素出堆 + print(f"\n堆顶元素 {val} 出堆后") + print_heap([flag * val for val in heap]) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化小顶堆 + min_heap, flag = [], 1 + # 初始化大顶堆 + max_heap, flag = [], -1 + + print("\n以下测试样例为大顶堆") + # Python 的 heapq 模块默认实现小顶堆 + # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + + # 元素入堆 + test_push(max_heap, 1, flag) + test_push(max_heap, 3, flag) + test_push(max_heap, 2, flag) + test_push(max_heap, 5, flag) + test_push(max_heap, 4, flag) + + # 获取堆顶元素 + peek: int = flag * max_heap[0] + print(f"\n堆顶元素为 {peek}") + + # 堆顶元素出堆 + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + # 获取堆大小 + size: int = len(max_heap) + print(f"\n堆元素数量为 {size}") + + # 判断堆是否为空 + is_empty: bool = not max_heap + print(f"\n堆是否为空 {is_empty}") + + # 输入列表并建堆 + # 时间复杂度为 O(n) ,而非 O(nlogn) + min_heap = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + print("\n输入列表并建立小顶堆后") + print_heap(min_heap) diff --git a/codes/python/chapter_heap/my_heap.py b/codes/python/chapter_heap/my_heap.py new file mode 100644 index 0000000000..221488a020 --- /dev/null +++ b/codes/python/chapter_heap/my_heap.py @@ -0,0 +1,137 @@ +""" +File: my_heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + + +class MaxHeap: + """大顶堆""" + + def __init__(self, nums: list[int]): + """构造方法,根据输入列表建堆""" + # 将列表元素原封不动添加进堆 + self.max_heap = nums + # 堆化除叶节点以外的其他所有节点 + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) + + def left(self, i: int) -> int: + """获取左子节点的索引""" + return 2 * i + 1 + + def right(self, i: int) -> int: + """获取右子节点的索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int: + """获取父节点的索引""" + return (i - 1) // 2 # 向下整除 + + def swap(self, i: int, j: int): + """交换元素""" + self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] + + def size(self) -> int: + """获取堆大小""" + return len(self.max_heap) + + def is_empty(self) -> bool: + """判断堆是否为空""" + return self.size() == 0 + + def peek(self) -> int: + """访问堆顶元素""" + return self.max_heap[0] + + def push(self, val: int): + """元素入堆""" + # 添加节点 + self.max_heap.append(val) + # 从底至顶堆化 + self.sift_up(self.size() - 1) + + def sift_up(self, i: int): + """从节点 i 开始,从底至顶堆化""" + while True: + # 获取节点 i 的父节点 + p = self.parent(i) + # 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # 交换两节点 + self.swap(i, p) + # 循环向上堆化 + i = p + + def pop(self) -> int: + """元素出堆""" + # 判空处理 + if self.is_empty(): + raise IndexError("堆为空") + # 交换根节点与最右叶节点(交换首元素与尾元素) + self.swap(0, self.size() - 1) + # 删除节点 + val = self.max_heap.pop() + # 从顶至底堆化 + self.sift_down(0) + # 返回堆顶元素 + return val + + def sift_down(self, i: int): + """从节点 i 开始,从顶至底堆化""" + while True: + # 判断节点 i, l, r 中值最大的节点,记为 ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i: + break + # 交换两节点 + self.swap(i, ma) + # 循环向下堆化 + i = ma + + def print(self): + """打印堆(二叉树)""" + print_heap(self.max_heap) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化大顶堆 + max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\n输入列表并建堆后") + max_heap.print() + + # 获取堆顶元素 + peek = max_heap.peek() + print(f"\n堆顶元素为 {peek}") + + # 元素入堆 + val = 7 + max_heap.push(val) + print(f"\n元素 {val} 入堆后") + max_heap.print() + + # 堆顶元素出堆 + peek = max_heap.pop() + print(f"\n堆顶元素 {peek} 出堆后") + max_heap.print() + + # 获取堆大小 + size = max_heap.size() + print(f"\n堆元素数量为 {size}") + + # 判断堆是否为空 + is_empty = max_heap.is_empty() + print(f"\n堆是否为空 {is_empty}") diff --git a/codes/python/chapter_heap/top_k.py b/codes/python/chapter_heap/top_k.py new file mode 100644 index 0000000000..dd79922b3d --- /dev/null +++ b/codes/python/chapter_heap/top_k.py @@ -0,0 +1,39 @@ +""" +File: top_k.py +Created Time: 2023-06-10 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def top_k_heap(nums: list[int], k: int) -> list[int]: + """基于堆查找数组中最大的 k 个元素""" + # 初始化小顶堆 + heap = [] + # 将数组的前 k 个元素入堆 + for i in range(k): + heapq.heappush(heap, nums[i]) + # 从第 k+1 个元素开始,保持堆的长度为 k + for i in range(k, len(nums)): + # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > heap[0]: + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + return heap + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + print(f"最大的 {k} 个元素为") + print_heap(res) diff --git a/codes/python/chapter_searching/binary_search.py b/codes/python/chapter_searching/binary_search.py index 275ae28ec7..62f30a7b93 100644 --- a/codes/python/chapter_searching/binary_search.py +++ b/codes/python/chapter_searching/binary_search.py @@ -4,50 +4,49 @@ Author: timi (xisunyy@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" 二分查找(双闭区间) """ -def binary_search(nums, target): +def binary_search(nums: list[int], target: int) -> int: + """二分查找(双闭区间)""" # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 i, j = 0, len(nums) - 1 + # 循环,当搜索区间为空时跳出(当 i > j 时为空) while i <= j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: # 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1 - elif nums[m] > target: # 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1 + # 理论上 Python 的数字可以无限大(取决于内存大小),无须考虑大数越界问题 + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # 此情况说明 target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # 此情况说明 target 在区间 [i, m-1] 中 else: - return m # 找到目标元素,返回其索引 - return -1 # 未找到目标元素,返回 -1 + return m # 找到目标元素,返回其索引 + return -1 # 未找到目标元素,返回 -1 -""" 二分查找(左闭右开) """ -def binary_search1(nums, target): - # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 +def binary_search_lcro(nums: list[int], target: int) -> int: + """二分查找(左闭右开区间)""" + # 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 i, j = 0, len(nums) # 循环,当搜索区间为空时跳出(当 i = j 时为空) while i < j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: # 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1 - elif nums[m] > target: # 此情况说明 target 在区间 [i, m) 中 - j = m - else: # 找到目标元素,返回其索引 - return m - return -1 # 未找到目标元素,返回 -1 - - -""" Driver Code """ -if __name__ == '__main__': + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # 此情况说明 target 在区间 [m+1, j) 中 + elif nums[m] > target: + j = m # 此情况说明 target 在区间 [i, m) 中 + else: + return m # 找到目标元素,返回其索引 + return -1 # 未找到目标元素,返回 -1 + + +"""Driver Code""" +if __name__ == "__main__": target = 6 - nums = [1, 3, 6, 8, 12, 15, 23, 67, 70, 92] + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] # 二分查找(双闭区间) index = binary_search(nums, target) print("目标元素 6 的索引 = ", index) - - # 二分查找(左闭右开) - index = binary_search1(nums, target) + + # 二分查找(左闭右开区间) + index = binary_search_lcro(nums, target) print("目标元素 6 的索引 = ", index) diff --git a/codes/python/chapter_searching/binary_search_edge.py b/codes/python/chapter_searching/binary_search_edge.py new file mode 100644 index 0000000000..2eae810cd1 --- /dev/null +++ b/codes/python/chapter_searching/binary_search_edge.py @@ -0,0 +1,49 @@ +""" +File: binary_search_edge.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from binary_search_insertion import binary_search_insertion + + +def binary_search_left_edge(nums: list[int], target: int) -> int: + """二分查找最左一个 target""" + # 等价于查找 target 的插入点 + i = binary_search_insertion(nums, target) + # 未找到 target ,返回 -1 + if i == len(nums) or nums[i] != target: + return -1 + # 找到 target ,返回索引 i + return i + + +def binary_search_right_edge(nums: list[int], target: int) -> int: + """二分查找最右一个 target""" + # 转化为查找最左一个 target + 1 + i = binary_search_insertion(nums, target + 1) + # j 指向最右一个 target ,i 指向首个大于 target 的元素 + j = i - 1 + # 未找到 target ,返回 -1 + if j == -1 or nums[j] != target: + return -1 + # 找到 target ,返回索引 j + return j + + +"""Driver Code""" +if __name__ == "__main__": + # 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\n数组 nums = {nums}") + + # 二分查找左边界和右边界 + for target in [6, 7]: + index = binary_search_left_edge(nums, target) + print(f"最左一个元素 {target} 的索引为 {index}") + index = binary_search_right_edge(nums, target) + print(f"最右一个元素 {target} 的索引为 {index}") diff --git a/codes/python/chapter_searching/binary_search_insertion.py b/codes/python/chapter_searching/binary_search_insertion.py new file mode 100644 index 0000000000..1ba0b956fb --- /dev/null +++ b/codes/python/chapter_searching/binary_search_insertion.py @@ -0,0 +1,54 @@ +""" +File: binary_search_insertion.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + + +def binary_search_insertion_simple(nums: list[int], target: int) -> int: + """二分查找插入点(无重复元素)""" + i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] + while i <= j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在区间 [i, m-1] 中 + else: + return m # 找到 target ,返回插入点 m + # 未找到 target ,返回插入点 i + return i + + +def binary_search_insertion(nums: list[int], target: int) -> int: + """二分查找插入点(存在重复元素)""" + i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] + while i <= j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在区间 [i, m-1] 中 + else: + j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 + # 返回插入点 i + return i + + +"""Driver Code""" +if __name__ == "__main__": + # 无重复元素的数组 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print(f"\n数组 nums = {nums}") + # 二分查找插入点 + for target in [6, 9]: + index = binary_search_insertion_simple(nums, target) + print(f"元素 {target} 的插入点的索引为 {index}") + + # 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\n数组 nums = {nums}") + # 二分查找插入点 + for target in [2, 6, 20]: + index = binary_search_insertion(nums, target) + print(f"元素 {target} 的插入点的索引为 {index}") diff --git a/codes/python/chapter_searching/hashing_search.py b/codes/python/chapter_searching/hashing_search.py index f49c703be5..ae5c38b0ae 100644 --- a/codes/python/chapter_searching/hashing_search.py +++ b/codes/python/chapter_searching/hashing_search.py @@ -4,42 +4,48 @@ Author: timi (xisunyy@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path -""" 哈希查找(数组) """ -def hashing_search(mapp, target): +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def hashing_search_array(hmap: dict[int, int], target: int) -> int: + """哈希查找(数组)""" # 哈希表的 key: 目标元素,value: 索引 # 若哈希表中无此 key ,返回 -1 - return mapp.get(target, -1) + return hmap.get(target, -1) -""" 哈希查找(链表) """ -def hashing_search1(mapp, target): - # 哈希表的 key: 目标元素,value: 结点对象 - # 若哈希表中无此 key ,返回 -1 - return mapp.get(target, -1) +def hashing_search_linkedlist( + hmap: dict[int, ListNode], target: int +) -> ListNode | None: + """哈希查找(链表)""" + # 哈希表的 key: 目标元素,value: 节点对象 + # 若哈希表中无此 key ,返回 None + return hmap.get(target, None) -""" Driver Code """ -if __name__ == '__main__': + +"""Driver Code""" +if __name__ == "__main__": target = 3 - + # 哈希查找(数组) nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] # 初始化哈希表 - mapp = {} + map0 = dict[int, int]() for i in range(len(nums)): - mapp[nums[i]] = i # key: 元素,value: 索引 - index = hashing_search(mapp, target) + map0[nums[i]] = i # key: 元素,value: 索引 + index: int = hashing_search_array(map0, target) print("目标元素 3 的索引 =", index) # 哈希查找(链表) - head = list_to_linked_list(nums) + head: ListNode = list_to_linked_list(nums) # 初始化哈希表 - map1 = {} + map1 = dict[int, ListNode]() while head: - map1[head.val] = head # key: 结点值,value: 结点 + map1[head.val] = head # key: 节点值,value: 节点 head = head.next - node = hashing_search1(map1, target) - print("目标结点值 3 的对应结点对象为", node) + node: ListNode = hashing_search_linkedlist(map1, target) + print("目标节点值 3 的对应节点对象为", node) diff --git a/codes/python/chapter_searching/linear_search.py b/codes/python/chapter_searching/linear_search.py index 8ff9e08034..fb449e7bcf 100644 --- a/codes/python/chapter_searching/linear_search.py +++ b/codes/python/chapter_searching/linear_search.py @@ -4,38 +4,42 @@ Author: timi (xisunyy@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path -""" 线性查找(数组) """ -def linear_search(nums, target): +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def linear_search_array(nums: list[int], target: int) -> int: + """线性查找(数组)""" # 遍历数组 for i in range(len(nums)): if nums[i] == target: # 找到目标元素,返回其索引 return i - return -1 # 未找到目标元素,返回 -1 + return -1 # 未找到目标元素,返回 -1 -""" 线性查找(链表) """ -def linear_search1(head, target): + +def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: + """线性查找(链表)""" # 遍历链表 while head: - if head.val == target: # 找到目标结点,返回之 + if head.val == target: # 找到目标节点,返回之 return head head = head.next - return None # 未找到目标结点,返回 None + return None # 未找到目标节点,返回 None -""" Driver Code """ -if __name__ == '__main__': +"""Driver Code""" +if __name__ == "__main__": target = 3 - + # 在数组中执行线性查找 nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] - index = linear_search(nums, target) + index: int = linear_search_array(nums, target) print("目标元素 3 的索引 =", index) # 在链表中执行线性查找 - head = list_to_linked_list(nums) - node = linear_search1(head, target) - print("目标结点值 3 的对应结点对象为", node) + head: ListNode = list_to_linked_list(nums) + node: ListNode | None = linear_search_linkedlist(head, target) + print("目标节点值 3 的对应节点对象为", node) diff --git a/codes/python/chapter_searching/two_sum.py b/codes/python/chapter_searching/two_sum.py new file mode 100644 index 0000000000..1a191623ad --- /dev/null +++ b/codes/python/chapter_searching/two_sum.py @@ -0,0 +1,42 @@ +""" +File: two_sum.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def two_sum_brute_force(nums: list[int], target: int) -> list[int]: + """方法一:暴力枚举""" + # 两层循环,时间复杂度为 O(n^2) + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return [i, j] + return [] + + +def two_sum_hash_table(nums: list[int], target: int) -> list[int]: + """方法二:辅助哈希表""" + # 辅助哈希表,空间复杂度为 O(n) + dic = {} + # 单层循环,时间复杂度为 O(n) + for i in range(len(nums)): + if target - nums[i] in dic: + return [dic[target - nums[i]], i] + dic[nums[i]] = i + return [] + + +"""Driver Code""" +if __name__ == "__main__": + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Driver Code ====== + # 方法一 + res: list[int] = two_sum_brute_force(nums, target) + print("方法一 res =", res) + # 方法二 + res: list[int] = two_sum_hash_table(nums, target) + print("方法二 res =", res) diff --git a/codes/python/chapter_sorting/bubble_sort.py b/codes/python/chapter_sorting/bubble_sort.py index 610e3186c7..7752988082 100644 --- a/codes/python/chapter_sorting/bubble_sort.py +++ b/codes/python/chapter_sorting/bubble_sort.py @@ -4,43 +4,41 @@ Author: timi (xisunyy@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" 冒泡排序 """ -def bubble_sort(nums): +def bubble_sort(nums: list[int]): + """冒泡排序""" n = len(nums) - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(n - 1, -1, -1): - # 内循环:冒泡操作 + # 外循环:未排序区间为 [0, i] + for i in range(n - 1, 0, -1): + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in range(i): if nums[j] > nums[j + 1]: # 交换 nums[j] 与 nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] -""" 冒泡排序(标志优化) """ -def bubble_sort_with_flag(nums): + +def bubble_sort_with_flag(nums: list[int]): + """冒泡排序(标志优化)""" n = len(nums) - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(n - 1, -1, -1): + # 外循环:未排序区间为 [0, i] + for i in range(n - 1, 0, -1): flag = False # 初始化标志位 - # 内循环:冒泡操作 + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in range(i): if nums[j] > nums[j + 1]: # 交换 nums[j] 与 nums[j + 1] nums[j], nums[j + 1] = nums[j + 1], nums[j] flag = True # 记录交换元素 if not flag: - break # 此轮冒泡未交换任何元素,直接跳出 + break # 此轮“冒泡”未交换任何元素,直接跳出 -""" Driver Code """ -if __name__ == '__main__': +"""Driver Code""" +if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) - print("排序后数组 nums =", nums) + print("冒泡排序完成后 nums =", nums) nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) - print("排序后数组 nums =", nums1) + print("冒泡排序完成后 nums =", nums1) diff --git a/codes/python/chapter_sorting/bucket_sort.py b/codes/python/chapter_sorting/bucket_sort.py new file mode 100644 index 0000000000..53b6c74548 --- /dev/null +++ b/codes/python/chapter_sorting/bucket_sort.py @@ -0,0 +1,35 @@ +""" +File: bucket_sort.py +Created Time: 2023-03-30 +Author: krahets (krahets@163.com) +""" + + +def bucket_sort(nums: list[float]): + """桶排序""" + # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + k = len(nums) // 2 + buckets = [[] for _ in range(k)] + # 1. 将数组元素分配到各个桶中 + for num in nums: + # 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + i = int(num * k) + # 将 num 添加进桶 i + buckets[i].append(num) + # 2. 对各个桶执行排序 + for bucket in buckets: + # 使用内置排序函数,也可以替换成其他排序算法 + bucket.sort() + # 3. 遍历桶合并结果 + i = 0 + for bucket in buckets: + for num in bucket: + nums[i] = num + i += 1 + + +if __name__ == "__main__": + # 设输入数据为浮点数,范围为 [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + print("桶排序完成后 nums =", nums) diff --git a/codes/python/chapter_sorting/counting_sort.py b/codes/python/chapter_sorting/counting_sort.py new file mode 100644 index 0000000000..bfa33e2f9c --- /dev/null +++ b/codes/python/chapter_sorting/counting_sort.py @@ -0,0 +1,62 @@ +""" +File: counting_sort.py +Created Time: 2023-03-21 +Author: krahets (krahets@163.com) +""" + + +def counting_sort_naive(nums: list[int]): + """计数排序""" + # 简单实现,无法用于排序对象 + # 1. 统计数组最大元素 m + m = max(nums) + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 遍历 counter ,将各元素填入原数组 nums + i = 0 + for num in range(m + 1): + for _ in range(counter[num]): + nums[i] = num + i += 1 + + +def counting_sort(nums: list[int]): + """计数排序""" + # 完整实现,可排序对象,并且是稳定排序 + # 1. 统计数组最大元素 m + m = max(nums) + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i in range(m): + counter[i + 1] += counter[i] + # 4. 倒序遍历 nums ,将各元素填入结果数组 res + # 初始化数组 res 用于记录结果 + n = len(nums) + res = [0] * n + for i in range(n - 1, -1, -1): + num = nums[i] + res[counter[num] - 1] = num # 将 num 放置到对应索引处 + counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引 + # 使用结果数组 res 覆盖原数组 nums + for i in range(n): + nums[i] = res[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + print(f"计数排序(无法排序对象)完成后 nums = {nums}") + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + print(f"计数排序完成后 nums1 = {nums1}") diff --git a/codes/python/chapter_sorting/heap_sort.py b/codes/python/chapter_sorting/heap_sort.py new file mode 100644 index 0000000000..249da1be08 --- /dev/null +++ b/codes/python/chapter_sorting/heap_sort.py @@ -0,0 +1,45 @@ +""" +File: heap_sort.py +Created Time: 2023-05-24 +Author: krahets (krahets@163.com) +""" + + +def sift_down(nums: list[int], n: int, i: int): + """堆的长度为 n ,从节点 i 开始,从顶至底堆化""" + while True: + # 判断节点 i, l, r 中值最大的节点,记为 ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + if l < n and nums[l] > nums[ma]: + ma = l + if r < n and nums[r] > nums[ma]: + ma = r + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i: + break + # 交换两节点 + nums[i], nums[ma] = nums[ma], nums[i] + # 循环向下堆化 + i = ma + + +def heap_sort(nums: list[int]): + """堆排序""" + # 建堆操作:堆化除叶节点以外的其他所有节点 + for i in range(len(nums) // 2 - 1, -1, -1): + sift_down(nums, len(nums), i) + # 从堆中提取最大元素,循环 n-1 轮 + for i in range(len(nums) - 1, 0, -1): + # 交换根节点与最右叶节点(交换首元素与尾元素) + nums[0], nums[i] = nums[i], nums[0] + # 以根节点为起点,从顶至底进行堆化 + sift_down(nums, i, 0) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + print("堆排序完成后 nums =", nums) diff --git a/codes/python/chapter_sorting/insertion_sort.py b/codes/python/chapter_sorting/insertion_sort.py index bf8492cddc..975dbfea9e 100644 --- a/codes/python/chapter_sorting/insertion_sort.py +++ b/codes/python/chapter_sorting/insertion_sort.py @@ -4,25 +4,22 @@ Author: timi (xisunyy@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" 插入排序 """ -def insertion_sort(nums): - # 外循环:base = nums[1], nums[2], ..., nums[n-1] +def insertion_sort(nums: list[int]): + """插入排序""" + # 外循环:已排序区间为 [0, i-1] for i in range(1, len(nums)): base = nums[i] j = i - 1 - # 内循环:将 base 插入到左边的正确位置 + # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while j >= 0 and nums[j] > base: - nums[j + 1] = nums[j] # 1. 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 j -= 1 - nums[j + 1] = base # 2. 将 base 赋值到正确位置 + nums[j + 1] = base # 将 base 赋值到正确位置 -""" Driver Code """ -if __name__ == '__main__': +"""Driver Code""" +if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) - print("排序后数组 nums =", nums) + print("插入排序完成后 nums =", nums) diff --git a/codes/python/chapter_sorting/merge_sort.py b/codes/python/chapter_sorting/merge_sort.py index 5fa7d83a9d..dbd32e6656 100644 --- a/codes/python/chapter_sorting/merge_sort.py +++ b/codes/python/chapter_sorting/merge_sort.py @@ -1,57 +1,55 @@ """ File: merge_sort.py Created Time: 2022-11-25 -Author: timi (xisunyy@163.com) +Author: timi (xisunyy@163.com), krahets (krahets@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" -合并左子数组和右子数组 -左子数组区间 [left, mid] -右子数组区间 [mid + 1, right] -""" -def merge(nums, left, mid, right): - # 初始化辅助数组 借助 copy模块 - tmp = nums[left:right + 1] - # 左子数组的起始索引和结束索引 - left_start, left_end = left - left, mid - left - # 右子数组的起始索引和结束索引 - right_start, right_end = mid + 1 - left, right - left - # i, j 分别指向左子数组、右子数组的首元素 - i, j = left_start, right_start - # 通过覆盖原数组 nums 来合并左子数组和右子数组 - for k in range(left, right + 1): - # 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > left_end: - nums[k] = tmp[j] - j += 1 - # 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - elif j > right_end or tmp[i] <= tmp[j]: - nums[k] = tmp[i] +def merge(nums: list[int], left: int, mid: int, right: int): + """合并左子数组和右子数组""" + # 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + # 创建一个临时数组 tmp ,用于存放合并后的结果 + tmp = [0] * (right - left + 1) + # 初始化左子数组和右子数组的起始索引 + i, j, k = left, mid + 1, 0 + # 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while i <= mid and j <= right: + if nums[i] <= nums[j]: + tmp[k] = nums[i] i += 1 - # 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else: - nums[k] = tmp[j] + tmp[k] = nums[j] j += 1 + k += 1 + # 将左子数组和右子数组的剩余元素复制到临时数组中 + while i <= mid: + tmp[k] = nums[i] + i += 1 + k += 1 + while j <= right: + tmp[k] = nums[j] + j += 1 + k += 1 + # 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for k in range(0, len(tmp)): + nums[left + k] = tmp[k] + -""" 归并排序 """ -def merge_sort(nums, left, right): +def merge_sort(nums: list[int], left: int, right: int): + """归并排序""" # 终止条件 if left >= right: - return # 当子数组长度为 1 时终止递归 + return # 当子数组长度为 1 时终止递归 # 划分阶段 - mid = (left + right) // 2 # 计算中点 - merge_sort(nums, left, mid) # 递归左子数组 + mid = (left + right) // 2 # 计算中点 + merge_sort(nums, left, mid) # 递归左子数组 merge_sort(nums, mid + 1, right) # 递归右子数组 # 合并阶段 merge(nums, left, mid, right) -""" Driver Code """ -if __name__ == '__main__': - nums = [ 7, 3, 2, 6, 0, 1, 5, 4 ] +"""Driver Code""" +if __name__ == "__main__": + nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, len(nums) - 1) print("归并排序完成后 nums =", nums) diff --git a/codes/python/chapter_sorting/quick_sort.py b/codes/python/chapter_sorting/quick_sort.py index f5ce8120cd..2c73cc1c5d 100644 --- a/codes/python/chapter_sorting/quick_sort.py +++ b/codes/python/chapter_sorting/quick_sort.py @@ -4,15 +4,13 @@ Author: timi (xisunyy@163.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" 快速排序类 """ class QuickSort: - """ 哨兵划分 """ - def partition(self, nums, left, right): - # 以 nums[left] 作为基准数 + """快速排序类""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分""" + # 以 nums[left] 为基准数 i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: @@ -25,8 +23,8 @@ def partition(self, nums, left, right): nums[i], nums[left] = nums[left], nums[i] return i # 返回基准数的索引 - """ 快速排序 """ - def quick_sort(self, nums, left, right): + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序""" # 子数组长度为 1 时终止递归 if left >= right: return @@ -36,25 +34,26 @@ def quick_sort(self, nums, left, right): self.quick_sort(nums, left, pivot - 1) self.quick_sort(nums, pivot + 1, right) -""" 快速排序类(中位基准数优化)""" + class QuickSortMedian: - """ 选取三个元素的中位数 """ - def median_three(self, nums, left, mid, right): - # 使用了异或操作来简化代码 - # 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if (nums[left] > nums[mid]) ^ (nums[left] > nums[right]): - return left - elif (nums[mid] < nums[left]) ^ (nums[mid] > nums[right]): - return mid + """快速排序类(中位基准数优化)""" + + def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: + """选取三个候选元素的中位数""" + l, m, r = nums[left], nums[mid], nums[right] + if (l <= m <= r) or (r <= m <= l): + return mid # m 在 l 和 r 之间 + if (m <= l <= r) or (r <= l <= m): + return left # l 在 m 和 r 之间 return right - """ 哨兵划分(三数取中值) """ - def partition(self, nums, left, right): - # 以 nums[left] 作为基准数 + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分(三数取中值)""" + # 以 nums[left] 为基准数 med = self.median_three(nums, left, (left + right) // 2, right) # 将中位数交换至数组最左端 nums[left], nums[med] = nums[med], nums[left] - # 以 nums[left] 作为基准数 + # 以 nums[left] 为基准数 i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: @@ -67,21 +66,24 @@ def partition(self, nums, left, right): nums[i], nums[left] = nums[left], nums[i] return i # 返回基准数的索引 - """ 快速排序 """ - def quick_sort(self, nums, left, right): + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序""" # 子数组长度为 1 时终止递归 - if left >= right: return + if left >= right: + return # 哨兵划分 pivot = self.partition(nums, left, right) # 递归左子数组、右子数组 self.quick_sort(nums, left, pivot - 1) self.quick_sort(nums, pivot + 1, right) -""" 快速排序类(尾递归优化) """ + class QuickSortTailCall: - """ 哨兵划分 """ - def partition(self, nums, left, right): - # 以 nums[left] 作为基准数 + """快速排序类(尾递归优化)""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分""" + # 以 nums[left] 为基准数 i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: @@ -92,36 +94,36 @@ def partition(self, nums, left, right): nums[i], nums[j] = nums[j], nums[i] # 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] - return i # 返回基准数的索引 + return i # 返回基准数的索引 - """ 快速排序(尾递归优化) """ - def quick_sort(self, nums, left, right): + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序(尾递归优化)""" # 子数组长度为 1 时终止 while left < right: # 哨兵划分操作 pivot = self.partition(nums, left, right) - # 对两个子数组中较短的那个执行快排 + # 对两个子数组中较短的那个执行快速排序 if pivot - left < right - pivot: self.quick_sort(nums, left, pivot - 1) # 递归排序左子数组 - left = pivot + 1 # 剩余待排序区间为 [pivot + 1, right] + left = pivot + 1 # 剩余未排序区间为 [pivot + 1, right] else: self.quick_sort(nums, pivot + 1, right) # 递归排序右子数组 - right = pivot - 1 # 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1 # 剩余未排序区间为 [left, pivot - 1] -""" Driver Code """ -if __name__ == '__main__': - # 快速排序 - nums = [4, 1, 3, 1, 5, 2] +"""Driver Code""" +if __name__ == "__main__": + # 快速排序 + nums = [2, 4, 1, 0, 3, 5] QuickSort().quick_sort(nums, 0, len(nums) - 1) print("快速排序完成后 nums =", nums) # 快速排序(中位基准数优化) - nums1 = [4, 1, 3, 1, 5, 2] + nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) - print("快速排序(中位基准数优化)完成后 nums =", nums) + print("快速排序(中位基准数优化)完成后 nums =", nums1) # 快速排序(尾递归优化) - nums2 = [4, 1, 3, 1, 5, 2] - QuickSortTailCall().quick_sort(nums, 0, len(nums2) - 1) - print("快速排序(尾递归优化)完成后 nums =", nums) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1) + print("快速排序(尾递归优化)完成后 nums =", nums2) diff --git a/codes/python/chapter_sorting/radix_sort.py b/codes/python/chapter_sorting/radix_sort.py new file mode 100644 index 0000000000..9d17bf0369 --- /dev/null +++ b/codes/python/chapter_sorting/radix_sort.py @@ -0,0 +1,69 @@ +""" +File: radix_sort.py +Created Time: 2023-03-26 +Author: krahets (krahets@163.com) +""" + + +def digit(num: int, exp: int) -> int: + """获取元素 num 的第 k 位,其中 exp = 10^(k-1)""" + # 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num // exp) % 10 + + +def counting_sort_digit(nums: list[int], exp: int): + """计数排序(根据 nums 第 k 位排序)""" + # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + counter = [0] * 10 + n = len(nums) + # 统计 0~9 各数字的出现次数 + for i in range(n): + d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d + counter[d] += 1 # 统计数字 d 的出现次数 + # 求前缀和,将“出现个数”转换为“数组索引” + for i in range(1, 10): + counter[i] += counter[i - 1] + # 倒序遍历,根据桶内统计结果,将各元素填入 res + res = [0] * n + for i in range(n - 1, -1, -1): + d = digit(nums[i], exp) + j = counter[d] - 1 # 获取 d 在数组中的索引 j + res[j] = nums[i] # 将当前元素填入索引 j + counter[d] -= 1 # 将 d 的数量减 1 + # 使用结果覆盖原数组 nums + for i in range(n): + nums[i] = res[i] + + +def radix_sort(nums: list[int]): + """基数排序""" + # 获取数组的最大元素,用于判断最大位数 + m = max(nums) + # 按照从低位到高位的顺序遍历 + exp = 1 + while exp <= m: + # 对数组元素的第 k 位执行计数排序 + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # 即 exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + + +"""Driver Code""" +if __name__ == "__main__": + # 基数排序 + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + print("基数排序完成后 nums =", nums) diff --git a/codes/python/chapter_sorting/selection_sort.py b/codes/python/chapter_sorting/selection_sort.py new file mode 100644 index 0000000000..c3252c1f79 --- /dev/null +++ b/codes/python/chapter_sorting/selection_sort.py @@ -0,0 +1,26 @@ +""" +File: selection_sort.py +Created Time: 2023-05-22 +Author: krahets (krahets@163.com) +""" + + +def selection_sort(nums: list[int]): + """选择排序""" + n = len(nums) + # 外循环:未排序区间为 [i, n-1] + for i in range(n - 1): + # 内循环:找到未排序区间内的最小元素 + k = i + for j in range(i + 1, n): + if nums[j] < nums[k]: + k = j # 记录最小元素的索引 + # 将该最小元素与未排序区间的首个元素交换 + nums[i], nums[k] = nums[k], nums[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + print("选择排序完成后 nums =", nums) diff --git a/codes/python/chapter_stack_and_queue/array_deque.py b/codes/python/chapter_stack_and_queue/array_deque.py new file mode 100644 index 0000000000..7f35fbbfdc --- /dev/null +++ b/codes/python/chapter_stack_and_queue/array_deque.py @@ -0,0 +1,129 @@ +""" +File: array_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ArrayDeque: + """基于环形数组实现的双向队列""" + + def __init__(self, capacity: int): + """构造方法""" + self._nums: list[int] = [0] * capacity + self._front: int = 0 + self._size: int = 0 + + def capacity(self) -> int: + """获取双向队列的容量""" + return len(self._nums) + + def size(self) -> int: + """获取双向队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断双向队列是否为空""" + return self._size == 0 + + def index(self, i: int) -> int: + """计算环形数组索引""" + # 通过取余操作实现数组首尾相连 + # 当 i 越过数组尾部后,回到头部 + # 当 i 越过数组头部后,回到尾部 + return (i + self.capacity()) % self.capacity() + + def push_first(self, num: int): + """队首入队""" + if self._size == self.capacity(): + print("双向队列已满") + return + # 队首指针向左移动一位 + # 通过取余操作实现 front 越过数组头部后回到尾部 + self._front = self.index(self._front - 1) + # 将 num 添加至队首 + self._nums[self._front] = num + self._size += 1 + + def push_last(self, num: int): + """队尾入队""" + if self._size == self.capacity(): + print("双向队列已满") + return + # 计算队尾指针,指向队尾索引 + 1 + rear = self.index(self._front + self._size) + # 将 num 添加至队尾 + self._nums[rear] = num + self._size += 1 + + def pop_first(self) -> int: + """队首出队""" + num = self.peek_first() + # 队首指针向后移动一位 + self._front = self.index(self._front + 1) + self._size -= 1 + return num + + def pop_last(self) -> int: + """队尾出队""" + num = self.peek_last() + self._size -= 1 + return num + + def peek_first(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._nums[self._front] + + def peek_last(self) -> int: + """访问队尾元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + # 计算尾元素索引 + last = self.index(self._front + self._size - 1) + return self._nums[last] + + def to_array(self) -> list[int]: + """返回数组用于打印""" + # 仅转换有效长度范围内的列表元素 + res = [] + for i in range(self._size): + res.append(self._nums[self.index(self._front + i)]) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化双向队列 + deque = ArrayDeque(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("双向队列 deque =", deque.to_array()) + + # 访问元素 + peek_first: int = deque.peek_first() + print("队首元素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("队尾元素 peek_last =", peek_last) + + # 元素入队 + deque.push_last(4) + print("元素 4 队尾入队后 deque =", deque.to_array()) + deque.push_first(1) + print("元素 1 队首入队后 deque =", deque.to_array()) + + # 元素出队 + pop_last: int = deque.pop_last() + print("队尾出队元素 =", pop_last, ",队尾出队后 deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("队首出队元素 =", pop_first, ",队首出队后 deque =", deque.to_array()) + + # 获取双向队列的长度 + size: int = deque.size() + print("双向队列长度 size =", size) + + # 判断双向队列是否为空 + is_empty: bool = deque.is_empty() + print("双向队列是否为空 =", is_empty) diff --git a/codes/python/chapter_stack_and_queue/array_queue.py b/codes/python/chapter_stack_and_queue/array_queue.py index 947c060916..c1d884d306 100644 --- a/codes/python/chapter_stack_and_queue/array_queue.py +++ b/codes/python/chapter_stack_and_queue/array_queue.py @@ -4,72 +4,69 @@ Author: Peng Chen (pengchzn@gmail.com) """ -import os.path as osp -import sys -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * - -""" 基于环形数组实现的队列 """ class ArrayQueue: - def __init__(self, size): - self.__nums = [0] * size # 用于存储队列元素的数组 - self.__front = 0 # 头指针,指向队首 - self.__rear = 0 # 尾指针,指向队尾 + 1 - - """ 获取队列的容量 """ - def capacity(self): - return len(self.__nums) - - """ 获取队列的长度 """ - def size(self): - # 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (self.capacity() + self.__rear - self.__front) % self.capacity() - - """ 判断队列是否为空 """ - def is_empty(self): - return (self.__rear - self.__front) == 0 - - """ 入队 """ - def push(self, val): - if self.size() == self.capacity(): - print("队列已满") - return False - # 尾结点后添加 num - self.__nums[self.__rear] = val - # 尾指针向后移动一位,越过尾部后返回到数组头部 - self.__rear = (self.__rear + 1) % self.capacity() - - """ 出队 """ - def poll(self): - num = self.peek() - # 队头指针向后移动一位,若越过尾部则返回到数组头部 - self.__front = (self.__front + 1) % self.capacity() + """基于环形数组实现的队列""" + + def __init__(self, size: int): + """构造方法""" + self._nums: list[int] = [0] * size # 用于存储队列元素的数组 + self._front: int = 0 # 队首指针,指向队首元素 + self._size: int = 0 # 队列长度 + + def capacity(self) -> int: + """获取队列的容量""" + return len(self._nums) + + def size(self) -> int: + """获取队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断队列是否为空""" + return self._size == 0 + + def push(self, num: int): + """入队""" + if self._size == self.capacity(): + raise IndexError("队列已满") + # 计算队尾指针,指向队尾索引 + 1 + # 通过取余操作实现 rear 越过数组尾部后回到头部 + rear: int = (self._front + self._size) % self.capacity() + # 将 num 添加至队尾 + self._nums[rear] = num + self._size += 1 + + def pop(self) -> int: + """出队""" + num: int = self.peek() + # 队首指针向后移动一位,若越过尾部,则返回到数组头部 + self._front = (self._front + 1) % self.capacity() + self._size -= 1 return num - """ 访问队首元素 """ - def peek(self): + def peek(self) -> int: + """访问队首元素""" if self.is_empty(): - print("队列为空") - return False - return self.__nums[self.__front] + raise IndexError("队列为空") + return self._nums[self._front] - """ 返回列表用于打印 """ - def to_list(self): + def to_list(self) -> list[int]: + """返回列表用于打印""" res = [0] * self.size() - j = self.__front + j: int = self._front for i in range(self.size()): - res[i] = self.__nums[(j % self.capacity())] + res[i] = self._nums[(j % self.capacity())] j += 1 return res -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化队列 """ + # 初始化队列 queue = ArrayQueue(10) - """ 元素入队 """ + # 元素入队 queue.push(1) queue.push(3) queue.push(2) @@ -77,19 +74,25 @@ def to_list(self): queue.push(4) print("队列 queue =", queue.to_list()) - """ 访问队首元素 """ - peek = queue.peek() + # 访问队首元素 + peek: int = queue.peek() print("队首元素 peek =", peek) - """ 元素出队 """ - poll = queue.poll() - print("出队元素 poll =", poll) + # 元素出队 + pop: int = queue.pop() + print("出队元素 pop =", pop) print("出队后 queue =", queue.to_list()) - """ 获取队列的长度 """ - size = queue.size() + # 获取队列的长度 + size: int = queue.size() print("队列长度 size =", size) - """ 判断队列是否为空 """ - is_empty = queue.is_empty() + # 判断队列是否为空 + is_empty: bool = queue.is_empty() print("队列是否为空 =", is_empty) + + # 测试环形数组 + for i in range(10): + queue.push(i) + queue.pop() + print("第", i, "轮入队 + 出队后 queue = ", queue.to_list()) diff --git a/codes/python/chapter_stack_and_queue/array_stack.py b/codes/python/chapter_stack_and_queue/array_stack.py index 18aee16b04..343f155dc8 100644 --- a/codes/python/chapter_stack_and_queue/array_stack.py +++ b/codes/python/chapter_stack_and_queue/array_stack.py @@ -4,48 +4,49 @@ Author: Peng Chen (pengchzn@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * -""" 基于数组实现的栈 """ class ArrayStack: + """基于数组实现的栈""" + def __init__(self): - self.__stack = [] + """构造方法""" + self._stack: list[int] = [] + + def size(self) -> int: + """获取栈的长度""" + return len(self._stack) - """ 获取栈的长度 """ - def size(self): - return len(self.__stack) + def is_empty(self) -> bool: + """判断栈是否为空""" + return self.size() == 0 - """ 判断栈是否为空 """ - def is_empty(self): - return self.__stack == [] + def push(self, item: int): + """入栈""" + self._stack.append(item) - """ 入栈 """ - def push(self, item): - self.__stack.append(item) + def pop(self) -> int: + """出栈""" + if self.is_empty(): + raise IndexError("栈为空") + return self._stack.pop() - """ 出栈 """ - def pop(self): - assert not self.is_empty(), "栈为空" - return self.__stack.pop() + def peek(self) -> int: + """访问栈顶元素""" + if self.is_empty(): + raise IndexError("栈为空") + return self._stack[-1] - """ 访问栈顶元素 """ - def peek(self): - assert not self.is_empty(), "栈为空" - return self.__stack[-1] - - """ 返回列表用于打印 """ - def to_list(self): - return self.__stack + def to_list(self) -> list[int]: + """返回列表用于打印""" + return self._stack -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化栈 """ + # 初始化栈 stack = ArrayStack() - """ 元素入栈 """ + # 元素入栈 stack.push(1) stack.push(3) stack.push(2) @@ -53,19 +54,19 @@ def to_list(self): stack.push(4) print("栈 stack =", stack.to_list()) - """ 访问栈顶元素 """ - peek = stack.peek() + # 访问栈顶元素 + peek: int = stack.peek() print("栈顶元素 peek =", peek) - """ 元素出栈 """ - pop = stack.pop() + # 元素出栈 + pop: int = stack.pop() print("出栈元素 pop =", pop) print("出栈后 stack =", stack.to_list()) - """ 获取栈的长度 """ - size = stack.size() + # 获取栈的长度 + size: int = stack.size() print("栈的长度 size =", size) - """ 判断是否为空 """ - is_empty = stack.is_empty() + # 判断是否为空 + is_empty: bool = stack.is_empty() print("栈是否为空 =", is_empty) diff --git a/codes/python/chapter_stack_and_queue/deque.py b/codes/python/chapter_stack_and_queue/deque.py index 30444c6b30..2717165400 100644 --- a/codes/python/chapter_stack_and_queue/deque.py +++ b/codes/python/chapter_stack_and_queue/deque.py @@ -4,46 +4,39 @@ Author: Peng Chen (pengchzn@gmail.com) """ -import os.path as osp -import sys - -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * - from collections import deque - -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化双向队列 """ - duque = deque() - - """ 元素入队 """ - duque.append(2) # 添加至队尾 - duque.append(5) - duque.append(4) - duque.appendleft(3) # 添加至队首 - duque.appendleft(1) - print("双向队列 duque =", duque) - - """ 访问元素 """ - front = duque[0] # 队首元素 + # 初始化双向队列 + deq: deque[int] = deque() + + # 元素入队 + deq.append(2) # 添加至队尾 + deq.append(5) + deq.append(4) + deq.appendleft(3) # 添加至队首 + deq.appendleft(1) + print("双向队列 deque =", deq) + + # 访问元素 + front: int = deq[0] # 队首元素 print("队首元素 front =", front) - rear = duque[-1] # 队尾元素 + rear: int = deq[-1] # 队尾元素 print("队尾元素 rear =", rear) - """ 元素出队 """ - pop_front = duque.popleft() # 队首元素出队 + # 元素出队 + pop_front: int = deq.popleft() # 队首元素出队 print("队首出队元素 pop_front =", pop_front) - print("队首出队后 duque =", duque) - pop_rear = duque.pop() # 队尾元素出队 + print("队首出队后 deque =", deq) + pop_rear: int = deq.pop() # 队尾元素出队 print("队尾出队元素 pop_rear =", pop_rear) - print("队尾出队后 duque =", duque) + print("队尾出队后 deque =", deq) - """ 获取双向队列的长度 """ - size = len(duque) + # 获取双向队列的长度 + size: int = len(deq) print("双向队列长度 size =", size) - """ 判断双向队列是否为空 """ - is_empty = len(duque) == 0 + # 判断双向队列是否为空 + is_empty: bool = len(deq) == 0 print("双向队列是否为空 =", is_empty) diff --git a/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/codes/python/chapter_stack_and_queue/linkedlist_deque.py new file mode 100644 index 0000000000..29aa0dfa11 --- /dev/null +++ b/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -0,0 +1,151 @@ +""" +File: linkedlist_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """双向链表节点""" + + def __init__(self, val: int): + """构造方法""" + self.val: int = val + self.next: ListNode | None = None # 后继节点引用 + self.prev: ListNode | None = None # 前驱节点引用 + + +class LinkedListDeque: + """基于双向链表实现的双向队列""" + + def __init__(self): + """构造方法""" + self._front: ListNode | None = None # 头节点 front + self._rear: ListNode | None = None # 尾节点 rear + self._size: int = 0 # 双向队列的长度 + + def size(self) -> int: + """获取双向队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断双向队列是否为空""" + return self._size == 0 + + def push(self, num: int, is_front: bool): + """入队操作""" + node = ListNode(num) + # 若链表为空,则令 front 和 rear 都指向 node + if self.is_empty(): + self._front = self._rear = node + # 队首入队操作 + elif is_front: + # 将 node 添加至链表头部 + self._front.prev = node + node.next = self._front + self._front = node # 更新头节点 + # 队尾入队操作 + else: + # 将 node 添加至链表尾部 + self._rear.next = node + node.prev = self._rear + self._rear = node # 更新尾节点 + self._size += 1 # 更新队列长度 + + def push_first(self, num: int): + """队首入队""" + self.push(num, True) + + def push_last(self, num: int): + """队尾入队""" + self.push(num, False) + + def pop(self, is_front: bool) -> int: + """出队操作""" + if self.is_empty(): + raise IndexError("双向队列为空") + # 队首出队操作 + if is_front: + val: int = self._front.val # 暂存头节点值 + # 删除头节点 + fnext: ListNode | None = self._front.next + if fnext is not None: + fnext.prev = None + self._front.next = None + self._front = fnext # 更新头节点 + # 队尾出队操作 + else: + val: int = self._rear.val # 暂存尾节点值 + # 删除尾节点 + rprev: ListNode | None = self._rear.prev + if rprev is not None: + rprev.next = None + self._rear.prev = None + self._rear = rprev # 更新尾节点 + self._size -= 1 # 更新队列长度 + return val + + def pop_first(self) -> int: + """队首出队""" + return self.pop(True) + + def pop_last(self) -> int: + """队尾出队""" + return self.pop(False) + + def peek_first(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._front.val + + def peek_last(self) -> int: + """访问队尾元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._rear.val + + def to_array(self) -> list[int]: + """返回数组用于打印""" + node = self._front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化双向队列 + deque = LinkedListDeque() + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("双向队列 deque =", deque.to_array()) + + # 访问元素 + peek_first: int = deque.peek_first() + print("队首元素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("队尾元素 peek_last =", peek_last) + + # 元素入队 + deque.push_last(4) + print("元素 4 队尾入队后 deque =", deque.to_array()) + deque.push_first(1) + print("元素 1 队首入队后 deque =", deque.to_array()) + + # 元素出队 + pop_last: int = deque.pop_last() + print("队尾出队元素 =", pop_last, ",队尾出队后 deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("队首出队元素 =", pop_first, ",队首出队后 deque =", deque.to_array()) + + # 获取双向队列的长度 + size: int = deque.size() + print("双向队列长度 size =", size) + + # 判断双向队列是否为空 + is_empty: bool = deque.is_empty() + print("双向队列是否为空 =", is_empty) diff --git a/codes/python/chapter_stack_and_queue/linkedlist_queue.py b/codes/python/chapter_stack_and_queue/linkedlist_queue.py index 2ab7196d3e..94bbe73999 100644 --- a/codes/python/chapter_stack_and_queue/linkedlist_queue.py +++ b/codes/python/chapter_stack_and_queue/linkedlist_queue.py @@ -4,72 +4,74 @@ Author: Peng Chen (pengchzn@gmail.com) """ -import os.path as osp import sys +from pathlib import Path -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode -""" 基于链表实现的队列 """ -class LinkedListQueue: - def __init__(self): - self.__front = None # 头结点 front - self.__rear = None # 尾结点 rear - self.__size = 0 - - """ 获取队列的长度 """ - def size(self): - return self.__size - """ 判断队列是否为空 """ - def is_empty(self): - return not self.__front +class LinkedListQueue: + """基于链表实现的队列""" - """ 入队 """ - def push(self, num): - # 尾结点后添加 num + def __init__(self): + """构造方法""" + self._front: ListNode | None = None # 头节点 front + self._rear: ListNode | None = None # 尾节点 rear + self._size: int = 0 + + def size(self) -> int: + """获取队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断队列是否为空""" + return self._size == 0 + + def push(self, num: int): + """入队""" + # 在尾节点后添加 num node = ListNode(num) - # 如果队列为空,则令头、尾结点都指向该结点 - if self.__front == 0: - self.__front = node - self.__rear = node - # 如果队列不为空,则将该结点添加到尾结点后 + # 如果队列为空,则令头、尾节点都指向该节点 + if self._front is None: + self._front = node + self._rear = node + # 如果队列不为空,则将该节点添加到尾节点后 else: - self.__rear.next = node - self.__rear = node - self.__size += 1 + self._rear.next = node + self._rear = node + self._size += 1 - """ 出队 """ - def poll(self): + def pop(self) -> int: + """出队""" num = self.peek() - # 删除头结点 - self.__front = self.__front.next - self.__size -= 1 + # 删除头节点 + self._front = self._front.next + self._size -= 1 return num - """ 访问队首元素 """ - def peek(self): - if self.size() == 0: - print("队列为空") - return False - return self.__front.val + def peek(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("队列为空") + return self._front.val - """ 转化为列表用于打印 """ - def to_list(self): + def to_list(self) -> list[int]: + """转化为列表用于打印""" queue = [] - temp = self.__front + temp = self._front while temp: queue.append(temp.val) temp = temp.next return queue -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化队列 """ + # 初始化队列 queue = LinkedListQueue() - """ 元素入队 """ + # 元素入队 queue.push(1) queue.push(3) queue.push(2) @@ -77,19 +79,19 @@ def to_list(self): queue.push(4) print("队列 queue =", queue.to_list()) - """ 访问队首元素 """ - peek = queue.peek() + # 访问队首元素 + peek: int = queue.peek() print("队首元素 front =", peek) - """ 元素出队 """ - pop_front = queue.poll() - print("出队元素 poll =", pop_front) + # 元素出队 + pop_front: int = queue.pop() + print("出队元素 pop =", pop_front) print("出队后 queue =", queue.to_list()) - """ 获取队列的长度 """ - size = queue.size() + # 获取队列的长度 + size: int = queue.size() print("队列长度 size =", size) - """ 判断队列是否为空 """ - is_empty = queue.is_empty() + # 判断队列是否为空 + is_empty: bool = queue.is_empty() print("队列是否为空 =", is_empty) diff --git a/codes/python/chapter_stack_and_queue/linkedlist_stack.py b/codes/python/chapter_stack_and_queue/linkedlist_stack.py index 21f6ca6af4..8d3d7f7533 100644 --- a/codes/python/chapter_stack_and_queue/linkedlist_stack.py +++ b/codes/python/chapter_stack_and_queue/linkedlist_stack.py @@ -4,48 +4,53 @@ Author: Peng Chen (pengchzn@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + -""" 基于链表实现的栈 """ class LinkedListStack: + """基于链表实现的栈""" + def __init__(self): - self.__peek = None - self.__size = 0 + """构造方法""" + self._peek: ListNode | None = None + self._size: int = 0 - """ 获取栈的长度 """ - def size(self): - return self.__size + def size(self) -> int: + """获取栈的长度""" + return self._size - """ 判断栈是否为空 """ - def is_empty(self): - return not self.__peek + def is_empty(self) -> bool: + """判断栈是否为空""" + return self._size == 0 - """ 入栈 """ - def push(self, val): + def push(self, val: int): + """入栈""" node = ListNode(val) - node.next = self.__peek - self.__peek = node - self.__size += 1 + node.next = self._peek + self._peek = node + self._size += 1 - """ 出栈 """ - def pop(self): + def pop(self) -> int: + """出栈""" num = self.peek() - self.__peek = self.__peek.next - self.__size -= 1 + self._peek = self._peek.next + self._size -= 1 return num - """ 访问栈顶元素 """ - def peek(self): - # 判空处理 - if not self.__peek: return None - return self.__peek.val + def peek(self) -> int: + """访问栈顶元素""" + if self.is_empty(): + raise IndexError("栈为空") + return self._peek.val - """ 转化为列表用于打印 """ - def to_list(self): + def to_list(self) -> list[int]: + """转化为列表用于打印""" arr = [] - node = self.__peek + node = self._peek while node: arr.append(node.val) node = node.next @@ -53,12 +58,12 @@ def to_list(self): return arr -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化栈 """ + # 初始化栈 stack = LinkedListStack() - """ 元素入栈 """ + # 元素入栈 stack.push(1) stack.push(3) stack.push(2) @@ -66,19 +71,19 @@ def to_list(self): stack.push(4) print("栈 stack =", stack.to_list()) - """ 访问栈顶元素 """ - peek = stack.peek() + # 访问栈顶元素 + peek: int = stack.peek() print("栈顶元素 peek =", peek) - """ 元素出栈 """ - pop = stack.pop() + # 元素出栈 + pop: int = stack.pop() print("出栈元素 pop =", pop) print("出栈后 stack =", stack.to_list()) - """ 获取栈的长度 """ - size = stack.size() + # 获取栈的长度 + size: int = stack.size() print("栈的长度 size =", size) - """ 判断是否为空 """ - is_empty = stack.is_empty() + # 判断是否为空 + is_empty: bool = stack.is_empty() print("栈是否为空 =", is_empty) diff --git a/codes/python/chapter_stack_and_queue/queue.py b/codes/python/chapter_stack_and_queue/queue.py index ce220d7c17..923070ff6b 100644 --- a/codes/python/chapter_stack_and_queue/queue.py +++ b/codes/python/chapter_stack_and_queue/queue.py @@ -4,21 +4,16 @@ Author: Peng Chen (pengchzn@gmail.com) """ -import os.path as osp -import sys +from collections import deque -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +"""Driver Code""" +if __name__ == "__main__": + # 初始化队列 + # 在 Python 中,我们一般将双向队列类 deque 看作队列使用 + # 虽然 queue.Queue() 是纯正的队列类,但不太好用 + que: deque[int] = deque() - -""" Driver Code """ -if __name__ == "__main__": - """ 初始化队列 """ - # 在 Python 中,我们一般将双向队列类 deque 看左队列使用 - # 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议 - que = collections.deque() - - """ 元素入队 """ + # 元素入队 que.append(1) que.append(3) que.append(2) @@ -26,19 +21,19 @@ que.append(4) print("队列 que =", que) - """ 访问队首元素 """ - front = que[0]; - print("队首元素 front =", front); + # 访问队首元素 + front: int = que[0] + print("队首元素 front =", front) - """ 元素出队 """ - pop = que.popleft() + # 元素出队 + pop: int = que.popleft() print("出队元素 pop =", pop) print("出队后 que =", que) - """ 获取队列的长度 """ - size = len(que) + # 获取队列的长度 + size: int = len(que) print("队列长度 size =", size) - """ 判断队列是否为空 """ - is_empty = len(que) == 0 + # 判断队列是否为空 + is_empty: bool = len(que) == 0 print("队列是否为空 =", is_empty) diff --git a/codes/python/chapter_stack_and_queue/stack.py b/codes/python/chapter_stack_and_queue/stack.py index 5e95258fed..1276271d69 100644 --- a/codes/python/chapter_stack_and_queue/stack.py +++ b/codes/python/chapter_stack_and_queue/stack.py @@ -4,18 +4,13 @@ Author: Peng Chen (pengchzn@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * - - -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化栈 """ - # Python 没有内置的栈类,可以把 list 当作栈来使用 - stack = [] + # 初始化栈 + # Python 没有内置的栈类,可以把 list 当作栈来使用 + stack: list[int] = [] - """ 元素入栈 """ + # 元素入栈 stack.append(1) stack.append(3) stack.append(2) @@ -23,19 +18,19 @@ stack.append(4) print("栈 stack =", stack) - """ 访问栈顶元素 """ - peek = stack[-1] + # 访问栈顶元素 + peek: int = stack[-1] print("栈顶元素 peek =", peek) - """ 元素出栈 """ - pop = stack.pop() + # 元素出栈 + pop: int = stack.pop() print("出栈元素 pop =", pop) print("出栈后 stack =", stack) - """ 获取栈的长度 """ - size = len(stack) + # 获取栈的长度 + size: int = len(stack) print("栈的长度 size =", size) - """ 判断是否为空 """ - is_empty = len(stack) == 0 + # 判断是否为空 + is_empty: bool = len(stack) == 0 print("栈是否为空 =", is_empty) diff --git a/codes/python/chapter_tree/array_binary_tree.py b/codes/python/chapter_tree/array_binary_tree.py new file mode 100644 index 0000000000..fd8fc032c6 --- /dev/null +++ b/codes/python/chapter_tree/array_binary_tree.py @@ -0,0 +1,119 @@ +""" +File: array_binary_tree.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree + + +class ArrayBinaryTree: + """数组表示下的二叉树类""" + + def __init__(self, arr: list[int | None]): + """构造方法""" + self._tree = list(arr) + + def size(self): + """列表容量""" + return len(self._tree) + + def val(self, i: int) -> int | None: + """获取索引为 i 节点的值""" + # 若索引越界,则返回 None ,代表空位 + if i < 0 or i >= self.size(): + return None + return self._tree[i] + + def left(self, i: int) -> int | None: + """获取索引为 i 节点的左子节点的索引""" + return 2 * i + 1 + + def right(self, i: int) -> int | None: + """获取索引为 i 节点的右子节点的索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int | None: + """获取索引为 i 节点的父节点的索引""" + return (i - 1) // 2 + + def level_order(self) -> list[int]: + """层序遍历""" + self.res = [] + # 直接遍历数组 + for i in range(self.size()): + if self.val(i) is not None: + self.res.append(self.val(i)) + return self.res + + def dfs(self, i: int, order: str): + """深度优先遍历""" + if self.val(i) is None: + return + # 前序遍历 + if order == "pre": + self.res.append(self.val(i)) + self.dfs(self.left(i), order) + # 中序遍历 + if order == "in": + self.res.append(self.val(i)) + self.dfs(self.right(i), order) + # 后序遍历 + if order == "post": + self.res.append(self.val(i)) + + def pre_order(self) -> list[int]: + """前序遍历""" + self.res = [] + self.dfs(0, order="pre") + return self.res + + def in_order(self) -> list[int]: + """中序遍历""" + self.res = [] + self.dfs(0, order="in") + return self.res + + def post_order(self) -> list[int]: + """后序遍历""" + self.res = [] + self.dfs(0, order="post") + return self.res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二叉树 + # 这里借助了一个从数组直接生成二叉树的函数 + arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + root = list_to_tree(arr) + print("\n初始化二叉树\n") + print("二叉树的数组表示:") + print(arr) + print("二叉树的链表表示:") + print_tree(root) + + # 数组表示下的二叉树类 + abt = ArrayBinaryTree(arr) + + # 访问节点 + i = 1 + l, r, p = abt.left(i), abt.right(i), abt.parent(i) + print(f"\n当前节点的索引为 {i} ,值为 {abt.val(i)}") + print(f"其左子节点的索引为 {l} ,值为 {abt.val(l)}") + print(f"其右子节点的索引为 {r} ,值为 {abt.val(r)}") + print(f"其父节点的索引为 {p} ,值为 {abt.val(p)}") + + # 遍历树 + res = abt.level_order() + print("\n层序遍历为:", res) + res = abt.pre_order() + print("前序遍历为:", res) + res = abt.in_order() + print("中序遍历为:", res) + res = abt.post_order() + print("后序遍历为:", res) diff --git a/codes/python/chapter_tree/avl_tree.py b/codes/python/chapter_tree/avl_tree.py index 83929981d7..eee6e2b6fc 100644 --- a/codes/python/chapter_tree/avl_tree.py +++ b/codes/python/chapter_tree/avl_tree.py @@ -4,203 +4,197 @@ Author: a16su (lpluls001@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree class AVLTree: - def __init__(self, root: Optional[TreeNode] = None): - self.root = root + """AVL 树""" + + def __init__(self): + """构造方法""" + self._root = None + + def get_root(self) -> TreeNode | None: + """获取二叉树根节点""" + return self._root - """ 获取结点高度 """ - def height(self, node: Optional[TreeNode]) -> int: - # 空结点高度为 -1 ,叶结点高度为 0 + def height(self, node: TreeNode | None) -> int: + """获取节点高度""" + # 空节点高度为 -1 ,叶节点高度为 0 if node is not None: return node.height return -1 - """ 更新结点高度 """ - def __update_height(self, node: Optional[TreeNode]): - # 结点高度等于最高子树高度 + 1 + def update_height(self, node: TreeNode | None): + """更新节点高度""" + # 节点高度等于最高子树高度 + 1 node.height = max([self.height(node.left), self.height(node.right)]) + 1 - """ 获取平衡因子 """ - def balance_factor(self, node: Optional[TreeNode]) -> int: - # 空结点平衡因子为 0 + def balance_factor(self, node: TreeNode | None) -> int: + """获取平衡因子""" + # 空节点平衡因子为 0 if node is None: return 0 - # 结点平衡因子 = 左子树高度 - 右子树高度 + # 节点平衡因子 = 左子树高度 - 右子树高度 return self.height(node.left) - self.height(node.right) - """ 右旋操作 """ - def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode: + def right_rotate(self, node: TreeNode | None) -> TreeNode | None: + """右旋操作""" child = node.left grand_child = child.right # 以 child 为原点,将 node 向右旋转 child.right = node node.left = grand_child - # 更新结点高度 - self.__update_height(node) - self.__update_height(child) + # 更新节点高度 + self.update_height(node) + self.update_height(child) # 返回旋转后子树的根节点 return child - """ 左旋操作 """ - def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode: + def left_rotate(self, node: TreeNode | None) -> TreeNode | None: + """左旋操作""" child = node.right grand_child = child.left # 以 child 为原点,将 node 向左旋转 child.left = node node.right = grand_child - # 更新结点高度 - self.__update_height(node) - self.__update_height(child) + # 更新节点高度 + self.update_height(node) + self.update_height(child) # 返回旋转后子树的根节点 return child - """ 执行旋转操作,使该子树重新恢复平衡 """ - def __rotate(self, node: Optional[TreeNode]) -> TreeNode: - # 获取结点 node 的平衡因子 + def rotate(self, node: TreeNode | None) -> TreeNode | None: + """执行旋转操作,使该子树重新恢复平衡""" + # 获取节点 node 的平衡因子 balance_factor = self.balance_factor(node) # 左偏树 if balance_factor > 1: if self.balance_factor(node.left) >= 0: # 右旋 - return self.__right_rotate(node) + return self.right_rotate(node) else: # 先左旋后右旋 - node.left = self.__left_rotate(node.left) - return self.__right_rotate(node) + node.left = self.left_rotate(node.left) + return self.right_rotate(node) # 右偏树 elif balance_factor < -1: if self.balance_factor(node.right) <= 0: # 左旋 - return self.__left_rotate(node) + return self.left_rotate(node) else: # 先右旋后左旋 - node.right = self.__right_rotate(node.right) - return self.__left_rotate(node) - # 平衡树,无需旋转,直接返回 + node.right = self.right_rotate(node.right) + return self.left_rotate(node) + # 平衡树,无须旋转,直接返回 return node - """ 插入结点 """ - def insert(self, val) -> TreeNode: - self.root = self.__insert_helper(self.root, val) - return self.root + def insert(self, val): + """插入节点""" + self._root = self.insert_helper(self._root, val) - """ 递归插入结点(辅助函数)""" - def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode: + def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: + """递归插入节点(辅助方法)""" if node is None: return TreeNode(val) - # 1. 查找插入位置,并插入结点 + # 1. 查找插入位置并插入节点 if val < node.val: - node.left = self.__insert_helper(node.left, val) + node.left = self.insert_helper(node.left, val) elif val > node.val: - node.right = self.__insert_helper(node.right, val) + node.right = self.insert_helper(node.right, val) else: - # 重复结点不插入,直接返回 + # 重复节点不插入,直接返回 return node - # 更新结点高度 - self.__update_height(node) + # 更新节点高度 + self.update_height(node) # 2. 执行旋转操作,使该子树重新恢复平衡 - return self.__rotate(node) + return self.rotate(node) - """ 删除结点 """ def remove(self, val: int): - root = self.__remove_helper(self.root, val) - return root + """删除节点""" + self._root = self.remove_helper(self._root, val) - """ 递归删除结点(辅助函数) """ - def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]: + def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: + """递归删除节点(辅助方法)""" if node is None: return None - # 1. 查找结点,并删除之 + # 1. 查找节点并删除 if val < node.val: - node.left = self.__remove_helper(node.left, val) + node.left = self.remove_helper(node.left, val) elif val > node.val: - node.right = self.__remove_helper(node.right, val) + node.right = self.remove_helper(node.right, val) else: if node.left is None or node.right is None: child = node.left or node.right - # 子结点数量 = 0 ,直接删除 node 并返回 + # 子节点数量 = 0 ,直接删除 node 并返回 if child is None: return None - # 子结点数量 = 1 ,直接删除 node + # 子节点数量 = 1 ,直接删除 node else: node = child - else: # 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp = self.__get_inorder_next(node.right) - node.right = self.__remove_helper(node.right, temp.val) + else: + # 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + temp = node.right + while temp.left is not None: + temp = temp.left + node.right = self.remove_helper(node.right, temp.val) node.val = temp.val - # 更新结点高度 - self.__update_height(node) + # 更新节点高度 + self.update_height(node) # 2. 执行旋转操作,使该子树重新恢复平衡 - return self.__rotate(node) - - """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ - def __get_inorder_next(self, node: Optional[TreeNode]) -> Optional[TreeNode]: - if node is None: - return None - # 循环访问左子结点,直到叶结点时为最小结点,跳出 - while node.left is not None: - node = node.left - return node + return self.rotate(node) - """ 查找结点 """ - def search(self, val: int): - cur = self.root - # 循环查找,越过叶结点后跳出 + def search(self, val: int) -> TreeNode | None: + """查找节点""" + cur = self._root + # 循环查找,越过叶节点后跳出 while cur is not None: - # 目标结点在 root 的右子树中 + # 目标节点在 cur 的右子树中 if cur.val < val: cur = cur.right - # 目标结点在 root 的左子树中 + # 目标节点在 cur 的左子树中 elif cur.val > val: cur = cur.left - # 找到目标结点,跳出循环 + # 找到目标节点,跳出循环 else: break - # 返回目标结点 + # 返回目标节点 return cur -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": + def test_insert(tree: AVLTree, val: int): tree.insert(val) - print("\n插入结点 {} 后,AVL 树为".format(val)) - print_tree(tree.root) + print("\n插入节点 {} 后,AVL 树为".format(val)) + print_tree(tree.get_root()) def test_remove(tree: AVLTree, val: int): tree.remove(val) - print("\n删除结点 {} 后,AVL 树为".format(val)) - print_tree(tree.root) + print("\n删除节点 {} 后,AVL 树为".format(val)) + print_tree(tree.get_root()) # 初始化空 AVL 树 avl_tree = AVLTree() - # 插入结点 - # 请关注插入结点后,AVL 树是如何保持平衡的 - test_insert(avl_tree, 1) - test_insert(avl_tree, 2) - test_insert(avl_tree, 3) - test_insert(avl_tree, 4) - test_insert(avl_tree, 5) - test_insert(avl_tree, 8) - test_insert(avl_tree, 7) - test_insert(avl_tree, 9) - test_insert(avl_tree, 10) - test_insert(avl_tree, 6) + # 插入节点 + # 请关注插入节点后,AVL 树是如何保持平衡的 + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]: + test_insert(avl_tree, val) - # 插入重复结点 + # 插入重复节点 test_insert(avl_tree, 7) - # 删除结点 - # 请关注删除结点后,AVL 树是如何保持平衡的 - test_remove(avl_tree, 8) # 删除度为 0 的结点 - test_remove(avl_tree, 5) # 删除度为 1 的结点 - test_remove(avl_tree, 4) # 删除度为 2 的结点 + # 删除节点 + # 请关注删除节点后,AVL 树是如何保持平衡的 + test_remove(avl_tree, 8) # 删除度为 0 的节点 + test_remove(avl_tree, 5) # 删除度为 1 的节点 + test_remove(avl_tree, 4) # 删除度为 2 的节点 result_node = avl_tree.search(7) - print("\n查找到的结点对象为 {},结点值 = {}".format(result_node, result_node.val)) + print("\n查找到的节点对象为 {},节点值 = {}".format(result_node, result_node.val)) diff --git a/codes/python/chapter_tree/binary_search_tree.py b/codes/python/chapter_tree/binary_search_tree.py index 8817d4a5a6..d339bf8f3f 100644 --- a/codes/python/chapter_tree/binary_search_tree.py +++ b/codes/python/chapter_tree/binary_search_tree.py @@ -4,161 +4,143 @@ Author: a16su (lpluls001@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree -""" 二叉搜索树 """ class BinarySearchTree: - def __init__(self, nums: List[int]) -> None: - nums.sort() - self.__root = self.build_tree(nums, 0, len(nums) - 1) - - """ 构建二叉搜索树 """ - def build_tree(self, nums: List[int], start_index: int, end_index: int) -> Optional[TreeNode]: - if start_index > end_index: - return None - - # 将数组中间结点作为根结点 - mid = (start_index + end_index) // 2 - root = TreeNode(nums[mid]) - # 递归建立左子树和右子树 - root.left = self.build_tree(nums=nums, start_index=start_index, end_index=mid - 1) - root.right = self.build_tree(nums=nums, start_index=mid + 1, end_index=end_index) - return root - - @property - def root(self) -> Optional[TreeNode]: - return self.__root - - """ 查找结点 """ - def search(self, num: int) -> Optional[TreeNode]: - cur = self.root - # 循环查找,越过叶结点后跳出 + """二叉搜索树""" + + def __init__(self): + """构造方法""" + # 初始化空树 + self._root = None + + def get_root(self) -> TreeNode | None: + """获取二叉树根节点""" + return self._root + + def search(self, num: int) -> TreeNode | None: + """查找节点""" + cur = self._root + # 循环查找,越过叶节点后跳出 while cur is not None: - # 目标结点在 root 的右子树中 + # 目标节点在 cur 的右子树中 if cur.val < num: cur = cur.right - # 目标结点在 root 的左子树中 + # 目标节点在 cur 的左子树中 elif cur.val > num: cur = cur.left - # 找到目标结点,跳出循环 + # 找到目标节点,跳出循环 else: break return cur - """ 插入结点 """ - def insert(self, num: int) -> Optional[TreeNode]: - root = self.root - # 若树为空,直接提前返回 - if root is None: - return None - - cur = root - pre = None - - # 循环查找,越过叶结点后跳出 + def insert(self, num: int): + """插入节点""" + # 若树为空,则初始化根节点 + if self._root is None: + self._root = TreeNode(num) + return + # 循环查找,越过叶节点后跳出 + cur, pre = self._root, None while cur is not None: - # 找到重复结点,直接返回 + # 找到重复节点,直接返回 if cur.val == num: - return None + return pre = cur - - if cur.val < num: # 插入位置在 root 的右子树中 + # 插入位置在 cur 的右子树中 + if cur.val < num: cur = cur.right - else: # 插入位置在 root 的左子树中 + # 插入位置在 cur 的左子树中 + else: cur = cur.left - - # 插入结点 val + # 插入节点 node = TreeNode(num) if pre.val < num: pre.right = node else: pre.left = node - return node - """ 删除结点 """ - def remove(self, num: int) -> Optional[TreeNode]: - root = self.root + def remove(self, num: int): + """删除节点""" # 若树为空,直接提前返回 - if root is None: - return None - - cur = root - pre = None - - # 循环查找,越过叶结点后跳出 + if self._root is None: + return + # 循环查找,越过叶节点后跳出 + cur, pre = self._root, None while cur is not None: - # 找到待删除结点,跳出循环 + # 找到待删除节点,跳出循环 if cur.val == num: break pre = cur - if cur.val < num: # 待删除结点在 root 的右子树中 + # 待删除节点在 cur 的右子树中 + if cur.val < num: cur = cur.right - else: # 待删除结点在 root 的左子树中 + # 待删除节点在 cur 的左子树中 + else: cur = cur.left - - # 若无待删除结点,则直接返回 + # 若无待删除节点,则直接返回 if cur is None: - return None + return - # 子结点数量 = 0 or 1 + # 子节点数量 = 0 or 1 if cur.left is None or cur.right is None: - # 当子结点数量 = 0 / 1 时, child = null / 该子结点 + # 当子节点数量 = 0 / 1 时, child = null / 该子节点 child = cur.left or cur.right - # 删除结点 cur - if pre.left == cur: - pre.left = child + # 删除节点 cur + if cur != self._root: + if pre.left == cur: + pre.left = child + else: + pre.right = child else: - pre.right = child - # 子结点数量 = 2 + # 若删除节点为根节点,则重新指定根节点 + self._root = child + # 子节点数量 = 2 else: - # 获取中序遍历中 cur 的下一个结点 - nex = self.get_inorder_next(cur.right) - tmp = nex.val - # 递归删除结点 nex - self.remove(nex.val) - # 将 nex 的值复制给 cur - cur.val = tmp - return cur - - """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ - def get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - if root is None: - return root - # 循环访问左子结点,直到叶结点时为最小结点,跳出 - while root.left is not None: - root = root.left - return root + # 获取中序遍历中 cur 的下一个节点 + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # 递归删除节点 tmp + self.remove(tmp.val) + # 用 tmp 覆盖 cur + cur.val = tmp.val -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": # 初始化二叉搜索树 - nums = list(range(1, 16)) # [1, 2, ..., 15] - bst = BinarySearchTree(nums=nums) + bst = BinarySearchTree() + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + for num in nums: + bst.insert(num) print("\n初始化的二叉树为\n") - print_tree(bst.root) + print_tree(bst.get_root()) - # 查找结点 + # 查找节点 node = bst.search(7) - print("\n查找到的结点对象为: {},结点值 = {}".format(node, node.val)) + print("\n查找到的节点对象为: {},节点值 = {}".format(node, node.val)) - # 插入结点 - ndoe = bst.insert(16) - print("\n插入结点 16 后,二叉树为\n") - print_tree(bst.root) + # 插入节点 + bst.insert(16) + print("\n插入节点 16 后,二叉树为\n") + print_tree(bst.get_root()) - # 删除结点 + # 删除节点 bst.remove(1) - print("\n删除结点 1 后,二叉树为\n") - print_tree(bst.root) + print("\n删除节点 1 后,二叉树为\n") + print_tree(bst.get_root()) bst.remove(2) - print("\n删除结点 2 后,二叉树为\n") - print_tree(bst.root) + print("\n删除节点 2 后,二叉树为\n") + print_tree(bst.get_root()) bst.remove(4) - print("\n删除结点 4 后,二叉树为\n") - print_tree(bst.root) + print("\n删除节点 4 后,二叉树为\n") + print_tree(bst.get_root()) diff --git a/codes/python/chapter_tree/binary_tree.py b/codes/python/chapter_tree/binary_tree.py index e00eeb4e5a..dbc2f582e8 100644 --- a/codes/python/chapter_tree/binary_tree.py +++ b/codes/python/chapter_tree/binary_tree.py @@ -4,22 +4,23 @@ Author: a16su (lpluls001@gmail.com) """ -import sys, os.path as osp +import sys +from pathlib import Path -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": - """ 初始化二叉树 """ + # 初始化二叉树 # 初始化节点 n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) - # 构建引用指向(即指针) + # 构建节点之间的引用(指针) n1.left = n2 n1.right = n3 n2.left = n4 @@ -27,14 +28,14 @@ print("\n初始化二叉树\n") print_tree(n1) - """ 插入与删除结点 """ + # 插入与删除节点 P = TreeNode(0) # 在 n1 -> n2 中间插入节点 P n1.left = P P.left = n2 - print("\n插入结点 P 后\n") + print("\n插入节点 P 后\n") print_tree(n1) - # 删除结点 + # 删除节点 n1.left = n2 - print("\n删除结点 P 后\n") + print("\n删除节点 P 后\n") print_tree(n1) diff --git a/codes/python/chapter_tree/binary_tree_bfs.py b/codes/python/chapter_tree/binary_tree_bfs.py index fb3e930593..d75b1f684f 100644 --- a/codes/python/chapter_tree/binary_tree_bfs.py +++ b/codes/python/chapter_tree/binary_tree_bfs.py @@ -4,37 +4,39 @@ Author: a16su (lpluls001@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree +from collections import deque -""" 层序遍历 """ -def hier_order(root: Optional[TreeNode]): - # 初始化队列,加入根结点 - queue = collections.deque() + +def level_order(root: TreeNode | None) -> list[int]: + """层序遍历""" + # 初始化队列,加入根节点 + queue: deque[TreeNode] = deque() queue.append(root) # 初始化一个列表,用于保存遍历序列 res = [] while queue: - node = queue.popleft() # 队列出队 - res.append(node.val) # 保存节点值 + node: TreeNode = queue.popleft() # 队列出队 + res.append(node.val) # 保存节点值 if node.left is not None: - queue.append(node.left) # 左子结点入队 + queue.append(node.left) # 左子节点入队 if node.right is not None: - queue.append(node.right) # 右子结点入队 + queue.append(node.right) # 右子节点入队 return res -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 - root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二叉树\n") print_tree(root) # 层序遍历 - res = hier_order(root) - print("\n层序遍历的结点打印序列 = ", res) - assert res == [1, 2, 3, 4, 5, 6, 7] + res: list[int] = level_order(root) + print("\n层序遍历的节点打印序列 = ", res) diff --git a/codes/python/chapter_tree/binary_tree_dfs.py b/codes/python/chapter_tree/binary_tree_dfs.py index b0efb14c83..9b3488ad05 100644 --- a/codes/python/chapter_tree/binary_tree_dfs.py +++ b/codes/python/chapter_tree/binary_tree_dfs.py @@ -4,42 +4,44 @@ Author: a16su (lpluls001@gmail.com) """ -import sys, os.path as osp -sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) -from include import * +import sys +from pathlib import Path +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree -res = [] -""" 前序遍历 """ -def pre_order(root: Optional[TreeNode]): +def pre_order(root: TreeNode | None): + """前序遍历""" if root is None: return - # 访问优先级:根结点 -> 左子树 -> 右子树 + # 访问优先级:根节点 -> 左子树 -> 右子树 res.append(root.val) pre_order(root=root.left) pre_order(root=root.right) -""" 中序遍历 """ -def in_order(root: Optional[TreeNode]): + +def in_order(root: TreeNode | None): + """中序遍历""" if root is None: return - # 访问优先级:左子树 -> 根结点 -> 右子树 + # 访问优先级:左子树 -> 根节点 -> 右子树 in_order(root=root.left) res.append(root.val) in_order(root=root.right) -""" 后序遍历 """ -def post_order(root: Optional[TreeNode]): + +def post_order(root: TreeNode | None): + """后序遍历""" if root is None: return - # 访问优先级:左子树 -> 右子树 -> 根结点 + # 访问优先级:左子树 -> 右子树 -> 根节点 post_order(root=root.left) post_order(root=root.right) res.append(root.val) -""" Driver Code """ +"""Driver Code""" if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 @@ -48,19 +50,16 @@ def post_order(root: Optional[TreeNode]): print_tree(root) # 前序遍历 - res.clear() + res = [] pre_order(root) - print("\n前序遍历的结点打印序列 = ", res) - assert res == [1, 2, 4, 5, 3, 6, 7] + print("\n前序遍历的节点打印序列 = ", res) # 中序遍历 res.clear() in_order(root) - print("\n中序遍历的结点打印序列 = ", res) - assert res == [4, 2, 5, 1, 6, 3, 7] + print("\n中序遍历的节点打印序列 = ", res) # 后序遍历 res.clear() post_order(root) - print("\n后序遍历的结点打印序列 = ", res) - assert res == [4, 5, 2, 6, 7, 3, 1] + print("\n后序遍历的节点打印序列 = ", res) diff --git a/codes/python/include/__init__.py b/codes/python/include/__init__.py deleted file mode 100644 index 9d4e90bee2..0000000000 --- a/codes/python/include/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import copy -import math -import queue -import random -import functools -import collections -from typing import Optional, List, Dict, DefaultDict, OrderedDict, Set, Deque -from .linked_list import ListNode, list_to_linked_list, linked_list_to_list, get_list_node -from .binary_tree import TreeNode, list_to_tree, tree_to_list, get_tree_node -from .print_util import print_matrix, print_linked_list, print_tree, print_dict \ No newline at end of file diff --git a/codes/python/include/binary_tree.py b/codes/python/include/binary_tree.py deleted file mode 100644 index 9d612a7e2d..0000000000 --- a/codes/python/include/binary_tree.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -File: binary_tree.py -Created Time: 2021-12-11 -Author: Krahets (krahets@163.com) -""" - -import collections - -class TreeNode: - """Definition for a binary tree node - """ - def __init__(self, val=0, left=None, right=None): - self.val = val # 结点值 - self.height = 0 # 结点高度 - self.left = left # 左子结点引用 - self.right = right # 右子结点引用 - - def __str__(self): - val = self.val - left_node_val = self.left.val if self.left else None - right_node_val = self.right.val if self.right else None - return "".format(val, left_node_val, right_node_val) - - __repr__ = __str__ - - -def list_to_tree(arr): - """Generate a binary tree with a list - """ - if not arr: - return None - - i = 0 - root = TreeNode(arr[0]) - queue = collections.deque([root]) - while queue: - node = queue.popleft() - i += 1 - if i >= len(arr): break - if arr[i] != None: - node.left = TreeNode(arr[i]) - queue.append(node.left) - i += 1 - if i >= len(arr): break - if arr[i] != None: - node.right = TreeNode(arr[i]) - queue.append(node.right) - - return root - -def tree_to_list(root): - """Serialize a tree into an array - """ - if not root: return [] - queue = collections.deque() - queue.append(root) - res = [] - while queue: - node = queue.popleft() - if node: - res.append(node.val) - queue.append(node.left) - queue.append(node.right) - else: res.append(None) - return res - -def get_tree_node(root, val): - """Get a tree node with specific value in a binary tree - """ - if not root: - return - if root.val == val: - return root - left = get_tree_node(root.left, val) - right = get_tree_node(root.right, val) - return left if left else right diff --git a/codes/python/include/linked_list.py b/codes/python/include/linked_list.py deleted file mode 100644 index e2eff9a2cc..0000000000 --- a/codes/python/include/linked_list.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -File: linked_list.py -Created Time: 2021-12-11 -Author: Krahets (krahets@163.com) -""" - -class ListNode: - """Definition for a singly-linked list node - """ - def __init__(self, val=0, next=None): - self.val = val - self.next = next - -def list_to_linked_list(arr): - """Generate a linked list with a list - - Args: - arr ([type]): [description] - - Returns: - [type]: [description] - """ - dum = head = ListNode(0) - for a in arr: - node = ListNode(a) - head.next = node - head = head.next - return dum.next - -def linked_list_to_list(head): - """Serialize a linked list into an array - - Args: - head ([type]): [description] - - Returns: - [type]: [description] - """ - arr = [] - while head: - arr.append(head.val) - head = head.next - return arr - -def get_list_node(head, val): - """Get a list node with specific value from a linked list - - Args: - head ([type]): [description] - val ([type]): [description] - - Returns: - [type]: [description] - """ - while head and head.val != val: - head = head.next - return head diff --git a/codes/python/include/print_util.py b/codes/python/include/print_util.py deleted file mode 100644 index 9f211806c5..0000000000 --- a/codes/python/include/print_util.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -File: print_util.py -Created Time: 2021-12-11 -Author: Krahets (krahets@163.com), msk397 (machangxinq@gmail.com) -""" - -import copy -import queue -from .binary_tree import TreeNode, tree_to_list -from .linked_list import ListNode, linked_list_to_list - -def print_matrix(mat): - """Print a matrix - - Args: - mat ([type]): [description] - """ - pstr = [] - for arr in mat: - pstr.append(' ' + str(arr)) - - print('[\n' + ',\n'.join(pstr) + '\n]') - -def print_linked_list(head): - """Print a linked list - - Args: - head ([type]): [description] - """ - arr = linked_list_to_list(head) - print(' -> '.join([str(a) for a in arr])) - -class Trunk: - def __init__(self, prev=None, str=None): - self.prev = prev - self.str = str - -def showTrunks(p): - if p is None: - return - showTrunks(p.prev) - print(p.str, end='') - -def print_tree(root, prev=None, isLeft=False): - """Print a binary tree - This tree printer is borrowed from TECHIE DELIGHT - https://www.techiedelight.com/c-program-print-binary-tree/ - Args: - root ([type]): [description] - prev ([type], optional): [description]. Defaults to None. - isLeft (bool, optional): [description]. Defaults to False. - """ - if root is None: - return - - prev_str = ' ' - trunk = Trunk(prev, prev_str) - print_tree(root.right, trunk, True) - - if prev is None: - trunk.str = '———' - elif isLeft: - trunk.str = '/———' - prev_str = ' |' - else: - trunk.str = '\———' - prev.str = prev_str - - showTrunks(trunk) - print(' ' + str(root.val)) - if prev: - prev.str = prev_str - trunk.str = ' |' - print_tree(root.left, trunk, False) - -def print_dict(d): - """Print a dict - - Args: - d ([type]): [description] - """ - for key, value in d.items(): - print(key, '->', value) \ No newline at end of file diff --git a/codes/python/modules/__init__.py b/codes/python/modules/__init__.py new file mode 100644 index 0000000000..b10799e3c5 --- /dev/null +++ b/codes/python/modules/__init__.py @@ -0,0 +1,19 @@ +# Follow the PEP 585 - Type Hinting Generics In Standard Collections +# https://peps.python.org/pep-0585/ +from __future__ import annotations + +# Import common libs here to simplify the code by `from module import *` +from .list_node import ( + ListNode, + list_to_linked_list, + linked_list_to_list, +) +from .tree_node import TreeNode, list_to_tree, tree_to_list +from .vertex import Vertex, vals_to_vets, vets_to_vals +from .print_util import ( + print_matrix, + print_linked_list, + print_tree, + print_dict, + print_heap, +) diff --git a/codes/python/modules/list_node.py b/codes/python/modules/list_node.py new file mode 100644 index 0000000000..ee51f234b0 --- /dev/null +++ b/codes/python/modules/list_node.py @@ -0,0 +1,32 @@ +""" +File: list_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """链表节点类""" + + def __init__(self, val: int): + self.val: int = val # 节点值 + self.next: ListNode | None = None # 后继节点引用 + + +def list_to_linked_list(arr: list[int]) -> ListNode | None: + """将列表反序列化为链表""" + dum = head = ListNode(0) + for a in arr: + node = ListNode(a) + head.next = node + head = head.next + return dum.next + + +def linked_list_to_list(head: ListNode | None) -> list[int]: + """将链表序列化为列表""" + arr: list[int] = [] + while head: + arr.append(head.val) + head = head.next + return arr diff --git a/codes/python/modules/print_util.py b/codes/python/modules/print_util.py new file mode 100644 index 0000000000..74abc36ee7 --- /dev/null +++ b/codes/python/modules/print_util.py @@ -0,0 +1,81 @@ +""" +File: print_util.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com) +""" + +from .tree_node import TreeNode, list_to_tree +from .list_node import ListNode, linked_list_to_list + + +def print_matrix(mat: list[list[int]]): + """打印矩阵""" + s = [] + for arr in mat: + s.append(" " + str(arr)) + print("[\n" + ",\n".join(s) + "\n]") + + +def print_linked_list(head: ListNode | None): + """打印链表""" + arr: list[int] = linked_list_to_list(head) + print(" -> ".join([str(a) for a in arr])) + + +class Trunk: + def __init__(self, prev, string: str | None = None): + self.prev = prev + self.str = string + + +def show_trunks(p: Trunk | None): + if p is None: + return + show_trunks(p.prev) + print(p.str, end="") + + +def print_tree( + root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False +): + """ + 打印二叉树 + This tree printer is borrowed from TECHIE DELIGHT + https://www.techiedelight.com/c-program-print-binary-tree/ + """ + if root is None: + return + + prev_str = " " + trunk = Trunk(prev, prev_str) + print_tree(root.right, trunk, True) + + if prev is None: + trunk.str = "———" + elif is_right: + trunk.str = "/———" + prev_str = " |" + else: + trunk.str = "\———" + prev.str = prev_str + + show_trunks(trunk) + print(" " + str(root.val)) + if prev: + prev.str = prev_str + trunk.str = " |" + print_tree(root.left, trunk, False) + + +def print_dict(hmap: dict): + """打印字典""" + for key, value in hmap.items(): + print(key, "->", value) + + +def print_heap(heap: list[int]): + """打印堆""" + print("堆的数组表示:", heap) + print("堆的树状表示:") + root: TreeNode | None = list_to_tree(heap) + print_tree(root) diff --git a/codes/python/modules/tree_node.py b/codes/python/modules/tree_node.py new file mode 100644 index 0000000000..c342a367cb --- /dev/null +++ b/codes/python/modules/tree_node.py @@ -0,0 +1,69 @@ +""" +File: tree_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + +from collections import deque + + +class TreeNode: + """二叉树节点类""" + + def __init__(self, val: int = 0): + self.val: int = val # 节点值 + self.height: int = 0 # 节点高度 + self.left: TreeNode | None = None # 左子节点引用 + self.right: TreeNode | None = None # 右子节点引用 + + # 序列化编码规则请参考: + # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + # 二叉树的数组表示: + # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + # 二叉树的链表表示: + # /——— 15 + # /——— 7 + # /——— 3 + # | \——— 6 + # | \——— 12 + # ——— 1 + # \——— 2 + # | /——— 9 + # \——— 4 + # \——— 8 + + +def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: + """将列表反序列化为二叉树:递归""" + # 如果索引超出数组长度,或者对应的元素为 None ,则返回 None + if i < 0 or i >= len(arr) or arr[i] is None: + return None + # 构建当前节点 + root = TreeNode(arr[i]) + # 递归构建左右子树 + root.left = list_to_tree_dfs(arr, 2 * i + 1) + root.right = list_to_tree_dfs(arr, 2 * i + 2) + return root + + +def list_to_tree(arr: list[int]) -> TreeNode | None: + """将列表反序列化为二叉树""" + return list_to_tree_dfs(arr, 0) + + +def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: + """将二叉树序列化为列表:递归""" + if root is None: + return + if i >= len(res): + res += [None] * (i - len(res) + 1) + res[i] = root.val + tree_to_list_dfs(root.left, 2 * i + 1, res) + tree_to_list_dfs(root.right, 2 * i + 2, res) + + +def tree_to_list(root: TreeNode | None) -> list[int]: + """将二叉树序列化为列表""" + res = [] + tree_to_list_dfs(root, 0, res) + return res diff --git a/codes/python/modules/vertex.py b/codes/python/modules/vertex.py new file mode 100644 index 0000000000..3080365507 --- /dev/null +++ b/codes/python/modules/vertex.py @@ -0,0 +1,20 @@ +# File: vertex.py +# Created Time: 2023-02-23 +# Author: krahets (krahets@163.com) + + +class Vertex: + """顶点类""" + + def __init__(self, val: int): + self.val = val + + +def vals_to_vets(vals: list[int]) -> list["Vertex"]: + """输入值列表 vals ,返回顶点列表 vets""" + return [Vertex(val) for val in vals] + + +def vets_to_vals(vets: list["Vertex"]) -> list[int]: + """输入顶点列表 vets ,返回值列表 vals""" + return [vet.val for vet in vets] diff --git a/codes/python/test_all.py b/codes/python/test_all.py new file mode 100644 index 0000000000..a90c773a4f --- /dev/null +++ b/codes/python/test_all.py @@ -0,0 +1,33 @@ +import os +import glob +import subprocess + +env = os.environ.copy() +env["PYTHONIOENCODING"] = "utf-8" + +if __name__ == "__main__": + # find source code files + src_paths = sorted(glob.glob("codes/python/chapter_*/*.py")) + errors = [] + + # run python code + for src_path in src_paths: + process = subprocess.Popen( + ["python", src_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + encoding='utf-8' + ) + # Wait for the process to complete, and get the output and error messages + stdout, stderr = process.communicate() + # Check the exit status + exit_status = process.returncode + if exit_status != 0: + errors.append(stderr) + + print(f"Tested {len(src_paths)} files") + print(f"Found exception in {len(errors)} files") + if len(errors) > 0: + raise RuntimeError("\n\n".join(errors)) diff --git a/codes/pythontutor/chapter_array_and_linkedlist/array.md b/codes/pythontutor/chapter_array_and_linkedlist/array.md new file mode 100644 index 0000000000..303c747116 --- /dev/null +++ b/codes/pythontutor/chapter_array_and_linkedlist/array.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5B0,%20len%28nums%29-1%5D%20%E4%B8%AD%E9%9A%8F%E6%9C%BA%E6%8A%BD%E5%8F%96%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%0A%20%20%20%20random_index%20%3D%20random.randint%280,%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%B9%B6%E8%BF%94%E5%9B%9E%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%22,%20random_num%29%0A&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E6%95%B0%E7%BB%84%E7%9A%84%E7%B4%A2%E5%BC%95%20index%20%E5%A4%84%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%20num%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%90%8E%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%20index,%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%B0%86%20num%20%E8%B5%8B%E7%BB%99%20index%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20insert%28nums,%206,%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D,%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%B9%8B%E5%90%8E%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%89%8D%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28index,%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20remove%28nums,%202%29%0A%20%20%20%20print%28%22%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%202%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%E5%90%8C%E6%97%B6%E9%81%8D%E5%8E%86%E6%95%B0%E6%8D%AE%E7%B4%A2%E5%BC%95%E5%92%8C%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i,%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%8C%87%E5%AE%9A%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums,%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%203%20%EF%BC%8C%E5%BE%97%E5%88%B0%E7%B4%A2%E5%BC%95%20%3D%22,%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8CPython%20%E7%9A%84%20list%20%E6%98%AF%E5%8A%A8%E6%80%81%E6%95%B0%E7%BB%84%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%89%A9%E5%B1%95%0A%23%20%E4%B8%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E5%AD%A6%E4%B9%A0%EF%BC%8C%E6%9C%AC%E5%87%BD%E6%95%B0%E5%B0%86%20list%20%E7%9C%8B%E4%BD%9C%E9%95%BF%E5%BA%A6%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%9A%84%E6%95%B0%E7%BB%84%0Adef%20extend%28nums%3A%20list%5Bint%5D,%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%89%A9%E5%B1%95%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%E6%89%A9%E5%B1%95%E9%95%BF%E5%BA%A6%E5%90%8E%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%E5%B0%86%E5%8E%9F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E6%96%B0%E6%95%B0%E7%BB%84%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%89%A9%E5%B1%95%E5%90%8E%E7%9A%84%E6%96%B0%E6%95%B0%E7%BB%84%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%0A%20%20%20%20nums%20%3D%20extend%28nums,%203%29%0A%20%20%20%20print%28%22%E5%B0%86%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%E8%87%B3%208%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md b/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md new file mode 100644 index 0000000000..5637b1be81 --- /dev/null +++ b/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20insert%28n0%3A%20ListNode,%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9%20n0%20%E4%B9%8B%E5%90%8E%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0,%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9%20n0%20%E4%B9%8B%E5%90%8E%E7%9A%84%E9%A6%96%E4%B8%AA%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20access%28head%3A%20ListNode,%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%B4%A2%E5%BC%95%E4%B8%BA%20index%20%E7%9A%84%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E8%8A%82%E7%82%B9%0A%20%20%20%20node%20%3D%20access%28n0,%203%29%0A%20%20%20%20print%28%22%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20find%28head%3A%20ListNode,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%93%BE%E8%A1%A8%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%80%BC%E4%B8%BA%20target%20%E7%9A%84%E9%A6%96%E4%B8%AA%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20index%20%3D%20find%28n0,%202%29%0A%20%20%20%20print%28%22%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%BC%E4%B8%BA%202%20%E7%9A%84%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_array_and_linkedlist/my_list.md b/codes/pythontutor/chapter_array_and_linkedlist/my_list.md new file mode 100644 index 0000000000..ef3d6f1322 --- /dev/null +++ b/codes/pythontutor/chapter_array_and_linkedlist/my_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20MyList%3A%0A%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20*%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%88%97%E8%A1%A8%E9%95%BF%E5%BA%A6%EF%BC%88%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%EF%BC%89%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%88%97%E8%A1%A8%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self,%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%90%8E%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201,%20index%20-%201,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self,%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%20i%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%89%8D%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index,%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E6%89%A9%E5%AE%B9%22%22%22%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20*%20self.capacity%28%29%20*%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%286,%20index%3D3%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.remove%283%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.set%280,%201%29%0A%0A%20%20%20%20%23%20%E6%B5%8B%E8%AF%95%E6%89%A9%E5%AE%B9%E6%9C%BA%E5%88%B6%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/n_queens.md b/codes/pythontutor/chapter_backtracking/n_queens.md new file mode 100644 index 0000000000..cf72b190c2 --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/n_queens.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20row%3A%20int,%0A%20%20%20%20n%3A%20int,%0A%20%20%20%20state%3A%20list%5Blist%5Bstr%5D%5D,%0A%20%20%20%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D,%0A%20%20%20%20cols%3A%20list%5Bbool%5D,%0A%20%20%20%20diags1%3A%20list%5Bbool%5D,%0A%20%20%20%20diags2%3A%20list%5Bbool%5D,%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9AN%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E6%94%BE%E7%BD%AE%E5%AE%8C%E6%89%80%E6%9C%89%E8%A1%8C%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E5%88%97%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E8%AF%A5%E6%A0%BC%E5%AD%90%E5%AF%B9%E5%BA%94%E7%9A%84%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E5%92%8C%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E8%AF%A5%E6%A0%BC%E5%AD%90%E6%89%80%E5%9C%A8%E5%88%97%E3%80%81%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E3%80%81%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E5%AD%98%E5%9C%A8%E7%9A%87%E5%90%8E%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20not%20diags1%5Bdiag1%5D%20and%20not%20diags2%5Bdiag2%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%B0%86%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E5%9C%A8%E8%AF%A5%E6%A0%BC%E5%AD%90%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22Q%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%94%BE%E7%BD%AE%E4%B8%8B%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201,%20n,%20state,%20res,%20cols,%20diags1,%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E5%B0%86%E8%AF%A5%E6%A0%BC%E5%AD%90%E6%81%A2%E5%A4%8D%E4%B8%BA%E7%A9%BA%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22%23%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%20N%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20n*n%20%E5%A4%A7%E5%B0%8F%E7%9A%84%E6%A3%8B%E7%9B%98%EF%BC%8C%E5%85%B6%E4%B8%AD%20'Q'%20%E4%BB%A3%E8%A1%A8%E7%9A%87%E5%90%8E%EF%BC%8C'%23'%20%E4%BB%A3%E8%A1%A8%E7%A9%BA%E4%BD%8D%0A%20%20%20%20state%20%3D%20%5B%5B%22%23%22%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20*%20n%20%20%23%20%E8%AE%B0%E5%BD%95%E5%88%97%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20*%20%282%20*%20n%20-%201%29%20%20%23%20%E8%AE%B0%E5%BD%95%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20*%20%282%20*%20n%20-%201%29%20%20%23%20%E8%AE%B0%E5%BD%95%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280,%20n,%20state,%20res,%20cols,%20diags1,%20diags2%29%0A%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A3%8B%E7%9B%98%E9%95%BF%E5%AE%BD%E4%B8%BA%20%7Bn%7D%22%29%0A%20%20%20%20print%28f%22%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E6%96%B9%E6%A1%88%E5%85%B1%E6%9C%89%20%7Blen%28res%29%7D%20%E7%A7%8D%22%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%22--------------------%22%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/permutations_i.md b/codes/pythontutor/chapter_backtracking/permutations_i.md new file mode 100644 index 0000000000..7f283e8b82 --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/permutations_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20choices%3A%20list%5Bint%5D,%20selected%3A%20list%5Bbool%5D,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%8A%B6%E6%80%81%E9%95%BF%E5%BA%A6%E7%AD%89%E4%BA%8E%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20i,%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20choices,%20selected,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3Dnums,%20selected%3D%5BFalse%5D%20*%20len%28nums%29,%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%202,%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/permutations_ii.md b/codes/pythontutor/chapter_backtracking/permutations_ii.md new file mode 100644 index 0000000000..e02bad4b96 --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/permutations_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20choices%3A%20list%5Bint%5D,%20selected%3A%20list%5Bbool%5D,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%8A%B6%E6%80%81%E9%95%BF%E5%BA%A6%E7%AD%89%E4%BA%8E%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i,%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%20%E4%B8%94%20%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E7%9B%B8%E7%AD%89%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%E8%AE%B0%E5%BD%95%E9%80%89%E6%8B%A9%E8%BF%87%E7%9A%84%E5%85%83%E7%B4%A0%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20choices,%20selected,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3Dnums,%20selected%3D%5BFalse%5D%20*%20len%28nums%29,%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%202,%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md b/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md new file mode 100644 index 0000000000..c6d771a333 --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%80%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E5%80%BC%E4%B8%BA%207%20%E7%9A%84%E8%8A%82%E7%82%B9%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md b/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md new file mode 100644 index 0000000000..2bfff08984 --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%BA%8C%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E8%8A%82%E7%82%B9%207%20%E7%9A%84%E8%B7%AF%E5%BE%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md b/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md new file mode 100644 index 0000000000..1d5202206b --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E8%8A%82%E7%82%B9%207%20%E7%9A%84%E8%B7%AF%E5%BE%84%EF%BC%8C%E8%B7%AF%E5%BE%84%E4%B8%AD%E4%B8%8D%E5%8C%85%E5%90%AB%E5%80%BC%E4%B8%BA%203%20%E7%9A%84%E8%8A%82%E7%82%B9%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md b/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md new file mode 100644 index 0000000000..f9632b251c --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%E6%98%AF%E5%90%A6%E4%B8%BA%E8%A7%A3%22%22%22%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D,%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%B0%E5%BD%95%E8%A7%A3%22%22%22%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%9C%A8%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%E4%B8%8B%EF%BC%8C%E8%AF%A5%E9%80%89%E6%8B%A9%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%22%22%22%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20!%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%22%22%22%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%81%A2%E5%A4%8D%E7%8A%B6%E6%80%81%22%22%22%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28%0A%20%20%20%20state%3A%20list%5BTreeNode%5D,%20choices%3A%20list%5BTreeNode%5D,%20res%3A%20list%5Blist%5BTreeNode%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E4%B8%BA%E8%A7%A3%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20record_solution%28state,%20res%29%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E6%A3%80%E6%9F%A5%E9%80%89%E6%8B%A9%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%0A%20%20%20%20%20%20%20%20if%20is_valid%28state,%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state,%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20%5Bchoice.left,%20choice.right%5D,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state,%20choice%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3D%5Broot%5D,%20res%3Dres%29%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/subset_sum_i.md b/codes/pythontutor/chapter_backtracking/subset_sum_i.md new file mode 100644 index 0000000000..5a4d9e06a7 --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/subset_sum_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20target%3A%20int,%20choices%3A%20list%5Bint%5D,%20start%3A%20int,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%0A%20%20%20%20for%20i%20in%20range%28start,%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E7%BB%93%E6%9D%9F%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%99%E6%98%AF%E5%9B%A0%E4%B8%BA%E6%95%B0%E7%BB%84%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%90%8E%E8%BE%B9%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E8%BF%87%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%20target,%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target%20-%20choices%5Bi%5D,%20choices,%20i,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%AF%B9%20nums%20%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%81%8D%E5%8E%86%E8%B5%B7%E5%A7%8B%E7%82%B9%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20nums,%20start,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md b/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md new file mode 100644 index 0000000000..d073acce7d --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%0A%20%20%20%20target%3A%20int,%0A%20%20%20%20total%3A%20int,%0A%20%20%20%20choices%3A%20list%5Bint%5D,%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D,%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E8%B7%B3%E8%BF%87%E8%AF%A5%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%E5%92%8C%20total%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target,%20total%20%2B%20choices%5Bi%5D,%20choices,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%EF%BC%88%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%EF%BC%89%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20total%20%3D%200%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20total,%20nums,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%AF%A5%E6%96%B9%E6%B3%95%E8%BE%93%E5%87%BA%E7%9A%84%E7%BB%93%E6%9E%9C%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E9%9B%86%E5%90%88%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_backtracking/subset_sum_ii.md b/codes/pythontutor/chapter_backtracking/subset_sum_ii.md new file mode 100644 index 0000000000..2645241c0f --- /dev/null +++ b/codes/pythontutor/chapter_backtracking/subset_sum_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20target%3A%20int,%20choices%3A%20list%5Bint%5D,%20start%3A%20int,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%89%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%90%8C%E4%B8%80%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i%20in%20range%28start,%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E7%BB%93%E6%9D%9F%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%99%E6%98%AF%E5%9B%A0%E4%B8%BA%E6%95%B0%E7%BB%84%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%90%8E%E8%BE%B9%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E8%BF%87%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E5%9B%9B%EF%BC%9A%E5%A6%82%E6%9E%9C%E8%AF%A5%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B7%A6%E8%BE%B9%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%EF%BC%8C%E8%AF%B4%E6%98%8E%E8%AF%A5%E6%90%9C%E7%B4%A2%E5%88%86%E6%94%AF%E9%87%8D%E5%A4%8D%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%20target,%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target%20-%20choices%5Bi%5D,%20choices,%20i%20%2B%201,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%AF%B9%20nums%20%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%81%8D%E5%8E%86%E8%B5%B7%E5%A7%8B%E7%82%B9%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20nums,%20start,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_computational_complexity/iteration.md b/codes/pythontutor/chapter_computational_complexity/iteration.md new file mode 100644 index 0000000000..d743e333ec --- /dev/null +++ b/codes/pythontutor/chapter_computational_complexity/iteration.md @@ -0,0 +1,29 @@ + + + +https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22for%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnfor%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& + + +https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E5%BE%AA%E7%8E%AF%EF%BC%88%E4%B8%A4%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%204,%2010,%20...%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20*%3D%202%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E5%BE%AA%E7%8E%AF%EF%BC%88%E4%B8%A4%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%E5%8F%8C%E5%B1%82%20for%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%20i%20%3D%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%20j%20%3D%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D,%20%7Bj%7D%29,%20%22%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%8F%8C%E5%B1%82%20for%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E9%81%8D%E5%8E%86%E7%BB%93%E6%9E%9C%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n,%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20return%20tail_recur%28n%20-%201,%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n,%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200,%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A1%B9%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E6%98%BE%E5%BC%8F%E7%9A%84%E6%A0%88%E6%9D%A5%E6%A8%A1%E6%8B%9F%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%A0%88%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20for%20i%20in%20range%28n,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E9%80%92%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%87%BA%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E5%BD%92%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_computational_complexity/recursion.md b/codes/pythontutor/chapter_computational_complexity/recursion.md new file mode 100644 index 0000000000..c3bfa730d8 --- /dev/null +++ b/codes/pythontutor/chapter_computational_complexity/recursion.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n,%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20return%20tail_recur%28n%20-%201,%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n,%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200,%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A1%B9%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E6%98%BE%E5%BC%8F%E7%9A%84%E6%A0%88%E6%9D%A5%E6%A8%A1%E6%8B%9F%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%A0%88%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20for%20i%20in%20range%28n,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E9%80%92%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%87%BA%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E5%BD%92%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_computational_complexity/space_complexity.md b/codes/pythontutor/chapter_computational_complexity/space_complexity.md new file mode 100644 index 0000000000..11697c5e0c --- /dev/null +++ b/codes/pythontutor/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%23%20%E6%89%A7%E8%A1%8C%E6%9F%90%E4%BA%9B%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E5%B8%B8%E9%87%8F%E3%80%81%E5%8F%98%E9%87%8F%E3%80%81%E5%AF%B9%E8%B1%A1%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84%E5%8F%98%E9%87%8F%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84%E5%87%BD%E6%95%B0%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B8%B8%E6%95%B0%E9%98%B6%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%E7%9A%84%E5%93%88%E5%B8%8C%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20hmap%20%3D%20dict%5Bint,%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E7%BA%BF%E6%80%A7%E9%98%B6%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20print%28%22%E9%80%92%E5%BD%92%20n%20%3D%22,%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E7%BA%BF%E6%80%A7%E9%98%B6%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E4%BA%8C%E7%BB%B4%E5%88%97%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%5E2%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20*%20n%20for%20_%20in%20range%28n%29%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%98%B6%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E6%95%B0%E7%BB%84%20nums%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n,%20n-1,%20...,%202,%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%98%B6%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BB%BA%E7%AB%8B%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E6%8C%87%E6%95%B0%E9%98%B6%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_computational_complexity/time_complexity.md b/codes/pythontutor/chapter_computational_complexity/time_complexity.md new file mode 100644 index 0000000000..d32a48260d --- /dev/null +++ b/codes/pythontutor/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,38 @@ + + + +https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%E5%B8%B8%E6%95%B0%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%AC%A1%E6%95%B0%E4%B8%8E%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E6%88%90%E6%AD%A3%E6%AF%94%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20*%20n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%AC%A1%E6%95%B0%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%E6%88%90%E5%B9%B3%E6%96%B9%E5%85%B3%E7%B3%BB%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%E8%AE%A1%E6%95%B0%E5%99%A8%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E5%8C%85%E5%90%AB%203%20%E4%B8%AA%E5%8D%95%E5%85%83%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n,%200,%20-1%29%5D%20%20%23%20%5Bn,%20n-1,%20...,%202,%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%E7%BB%86%E8%83%9E%E6%AF%8F%E8%BD%AE%E4%B8%80%E5%88%86%E4%B8%BA%E4%BA%8C%EF%BC%8C%E5%BD%A2%E6%88%90%E6%95%B0%E5%88%97%201,%202,%204,%208,%20...,%202%5E%28n-1%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20*%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20/%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20/%202%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E5%AF%B9%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20//%202%29%20%2B%20linear_log_recur%28n%20//%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%98%B6%E4%B9%98%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E4%BB%8E%201%20%E4%B8%AA%E5%88%86%E8%A3%82%E5%87%BA%20n%20%E4%B8%AA%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%E9%98%B6%E4%B9%98%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md b/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md new file mode 100644 index 0000000000..bc3420963b --- /dev/null +++ b/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E7%94%9F%E6%88%90%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%EF%BC%8C%E5%85%83%E7%B4%A0%E4%B8%BA%3A%201,%202,%20...,%20n%20%EF%BC%8C%E9%A1%BA%E5%BA%8F%E8%A2%AB%E6%89%93%E4%B9%B1%22%22%22%0A%20%20%20%20%23%20%E7%94%9F%E6%88%90%E6%95%B0%E7%BB%84%20nums%20%3D%3A%201,%202,%203,%20...,%20n%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281,%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E9%9A%8F%E6%9C%BA%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9F%A5%E6%89%BE%E6%95%B0%E7%BB%84%20nums%20%E4%B8%AD%E6%95%B0%E5%AD%97%201%20%E6%89%80%E5%9C%A8%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E6%95%B0%E7%BB%84%E5%A4%B4%E9%83%A8%E6%97%B6%EF%BC%8C%E8%BE%BE%E5%88%B0%E6%9C%80%E4%BD%B3%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%20O%281%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E6%95%B0%E7%BB%84%E5%B0%BE%E9%83%A8%E6%97%B6%EF%BC%8C%E8%BE%BE%E5%88%B0%E6%9C%80%E5%B7%AE%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%20O%28n%29%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%E6%95%B0%E7%BB%84%20%5B%201,%202,%20...,%20n%20%5D%20%E8%A2%AB%E6%89%93%E4%B9%B1%E5%90%8E%20%3D%22,%20nums%29%0A%20%20%20%20print%28%22%E6%95%B0%E5%AD%97%201%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%22,%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md b/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 0000000000..193abf62ee --- /dev/null +++ b/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D,%20target%3A%20int,%20i%3A%20int,%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%9A%E9%97%AE%E9%A2%98%20f%28i,%20j%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%EF%BC%8C%E4%BB%A3%E8%A1%A8%E6%97%A0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%AD%90%E9%97%AE%E9%A2%98%20f%28m%2B1,%20j%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums,%20target,%20m%20%2B%201,%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i,%20m-1%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums,%20target,%20i,%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E6%B1%82%E8%A7%A3%E9%97%AE%E9%A2%98%20f%280,%20n-1%29%0A%20%20%20%20return%20dfs%28nums,%20target,%200,%20n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_divide_and_conquer/build_tree.md b/codes/pythontutor/chapter_divide_and_conquer/build_tree.md new file mode 100644 index 0000000000..6863dee123 --- /dev/null +++ b/codes/pythontutor/chapter_divide_and_conquer/build_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D,%0A%20%20%20%20inorder_map%3A%20dict%5Bint,%20int%5D,%0A%20%20%20%20i%3A%20int,%0A%20%20%20%20l%3A%20int,%0A%20%20%20%20r%3A%20int,%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%9E%84%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E5%88%86%E6%B2%BB%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%A0%91%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E7%BB%88%E6%AD%A2%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%20m%20%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%88%92%E5%88%86%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%EF%BC%9A%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20dfs%28preorder,%20inorder_map,%20i%20%2B%201,%20l,%20m%20-%201%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%EF%BC%9A%E6%9E%84%E5%BB%BA%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.right%20%3D%20dfs%28preorder,%20inorder_map,%20i%20%2B%201%20%2B%20m%20-%20l,%20m%20%2B%201,%20r%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D,%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%9E%84%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E5%AD%98%E5%82%A8%20inorder%20%E5%85%83%E7%B4%A0%E5%88%B0%E7%B4%A2%E5%BC%95%E7%9A%84%E6%98%A0%E5%B0%84%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i,%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder,%20inorder_map,%200,%200,%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3,%209,%202,%201,%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9,%203,%201,%202,%207%5D%0A%20%20%20%20print%28f%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder,%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_divide_and_conquer/hanota.md b/codes/pythontutor/chapter_divide_and_conquer/hanota.md new file mode 100644 index 0000000000..755cb9ebc2 --- /dev/null +++ b/codes/pythontutor/chapter_divide_and_conquer/hanota.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D,%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%22%22%22%0A%20%20%20%20%23%20%E4%BB%8E%20src%20%E9%A1%B6%E9%83%A8%E6%8B%BF%E5%87%BA%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%E5%B0%86%E5%9C%86%E7%9B%98%E6%94%BE%E5%85%A5%20tar%20%E9%A1%B6%E9%83%A8%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int,%20src%3A%20list%5Bint%5D,%20buf%3A%20list%5Bint%5D,%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98%20f%28i%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%20src%20%E5%8F%AA%E5%89%A9%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E5%B0%86%E5%85%B6%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src,%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i-1%29%20%EF%BC%9A%E5%B0%86%20src%20%E9%A1%B6%E9%83%A8%20i-1%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20tar%20%E7%A7%BB%E5%88%B0%20buf%0A%20%20%20%20dfs%28i%20-%201,%20src,%20tar,%20buf%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%281%29%20%EF%BC%9A%E5%B0%86%20src%20%E5%89%A9%E4%BD%99%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20move%28src,%20tar%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i-1%29%20%EF%BC%9A%E5%B0%86%20buf%20%E9%A1%B6%E9%83%A8%20i-1%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20src%20%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20dfs%28i%20-%201,%20buf,%20src,%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D,%20B%3A%20list%5Bint%5D,%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20%E5%B0%86%20A%20%E9%A1%B6%E9%83%A8%20n%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20B%20%E7%A7%BB%E5%88%B0%20C%0A%20%20%20%20dfs%28n,%20A,%20B,%20C%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%97%E8%A1%A8%E5%B0%BE%E9%83%A8%E6%98%AF%E6%9F%B1%E5%AD%90%E9%A1%B6%E9%83%A8%0A%20%20%20%20A%20%3D%20%5B5,%204,%203,%202,%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%E4%B8%8B%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A,%20B,%20C%29%0A%0A%20%20%20%20print%28%22%E5%9C%86%E7%9B%98%E7%A7%BB%E5%8A%A8%E5%AE%8C%E6%88%90%E5%90%8E%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md new file mode 100644 index 0000000000..0d1ce488d4 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D,%20state%3A%20int,%20n%3A%20int,%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%88%AC%E5%88%B0%E7%AC%AC%20n%20%E9%98%B6%E6%97%B6%EF%BC%8C%E6%96%B9%E6%A1%88%E6%95%B0%E9%87%8F%E5%8A%A0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E8%B6%8A%E8%BF%87%E7%AC%AC%20n%20%E9%98%B6%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20backtrack%28choices,%20state%20%2B%20choice,%20n,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1,%202%5D%20%20%23%20%E5%8F%AF%E9%80%89%E6%8B%A9%E5%90%91%E4%B8%8A%E7%88%AC%201%20%E9%98%B6%E6%88%96%202%20%E9%98%B6%0A%20%20%20%20state%20%3D%200%20%20%23%20%E4%BB%8E%E7%AC%AC%200%20%E9%98%B6%E5%BC%80%E5%A7%8B%E7%88%AC%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%E4%BD%BF%E7%94%A8%20res%5B0%5D%20%E8%AE%B0%E5%BD%95%E6%96%B9%E6%A1%88%E6%95%B0%E9%87%8F%0A%20%20%20%20backtrack%28choices,%20state,%20n,%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md new file mode 100644 index 0000000000..221a4c9b9d --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%A6%E7%BA%A6%E6%9D%9F%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%5B1%5D,%20dp%5B1%5D%5B2%5D%20%3D%201,%200%0A%20%20%20%20dp%5B2%5D%5B1%5D,%20dp%5B2%5D%5B2%5D%20%3D%200,%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md new file mode 100644 index 0000000000..8092103b9d --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md new file mode 100644 index 0000000000..40575bde42 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int,%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20%E8%8B%A5%E5%AD%98%E5%9C%A8%E8%AE%B0%E5%BD%95%20dp%5Bi%5D%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20mem%5Bi%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201,%20mem%29%20%2B%20dfs%28i%20-%202,%20mem%29%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%20dp%5Bi%5D%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%E8%AE%B0%E5%BD%95%E7%88%AC%E5%88%B0%E7%AC%AC%20i%20%E9%98%B6%E7%9A%84%E6%96%B9%E6%A1%88%E6%80%BB%E6%95%B0%EF%BC%8C-1%20%E4%BB%A3%E8%A1%A8%E6%97%A0%E8%AE%B0%E5%BD%95%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n,%20mem%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md new file mode 100644 index 0000000000..b7741e6aaa --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D,%20dp%5B2%5D%20%3D%201,%202%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a,%20b%20%3D%201,%202%0A%20%20%20%20for%20_%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a,%20b%20%3D%20b,%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/coin_change.md b/codes/pythontutor/chapter_dynamic_programming/coin_change.md new file mode 100644 index 0000000000..b7000e7433 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/coin_change.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D,%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20!%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20*%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D,%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20!%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md b/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md new file mode 100644 index 0000000000..ce99875ef9 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%87%BA%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%E7%BB%84%E5%90%88%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%87%BA%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%E7%BB%84%E5%90%88%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/edit_distance.md b/codes/pythontutor/chapter_dynamic_programming/edit_distance.md new file mode 100644 index 0000000000..93ef24eba0 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/edit_distance.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D,%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%E6%9A%82%E5%AD%98%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D,%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%9B%B4%E6%96%B0%E4%B8%BA%E4%B8%8B%E4%B8%80%E8%BD%AE%E7%9A%84%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/knapsack.md b/codes/pythontutor/chapter_dynamic_programming/knapsack.md new file mode 100644 index 0000000000..ef46bf9e98 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/knapsack.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20i%3A%20int,%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%80%89%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E4%BB%B7%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E5%8F%AA%E8%83%BD%E9%80%89%E6%8B%A9%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%29%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B8%AD%E4%BB%B7%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E4%B8%AA%0A%20%20%20%20return%20max%28no,%20yes%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt,%20val,%20n,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20mem%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%80%89%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E4%BB%B7%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%AE%B0%E5%BD%95%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E5%8F%AA%E8%83%BD%E9%80%89%E6%8B%A9%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%29%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E5%B9%B6%E8%BF%94%E5%9B%9E%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B8%AD%E4%BB%B7%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E4%B8%AA%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no,%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20n,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D,%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D,%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md b/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md new file mode 100644 index 0000000000..4b4c108ee9 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D,%20dp%5B2%5D%20%3D%20cost%5B1%5D,%20cost%5B2%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D,%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0,%201,%2010,%201,%201,%201,%2010,%201,%201,%2010,%201%5D%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A5%BC%E6%A2%AF%E7%9A%84%E4%BB%A3%E4%BB%B7%E5%88%97%E8%A1%A8%E4%B8%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E4%BB%B7%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a,%20b%20%3D%20cost%5B1%5D,%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a,%20b%20%3D%20b,%20min%28a,%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0,%201,%2010,%201,%201,%201,%2010,%201,%201,%2010,%201%5D%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A5%BC%E6%A2%AF%E7%9A%84%E4%BB%A3%E4%BB%B7%E5%88%97%E8%A1%A8%E4%B8%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E4%BB%B7%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md b/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md new file mode 100644 index 0000000000..0b8364cf0a --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E4%B8%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%8D%95%E5%85%83%E6%A0%BC%EF%BC%8C%E5%88%99%E7%BB%88%E6%AD%A2%E6%90%9C%E7%B4%A2%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E4%BB%B7%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i-1,%20j%29%20%E5%92%8C%20%28i,%20j-1%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid,%20i%20-%201,%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid,%20i,%20j%20-%201%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i,%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20return%20min%28left,%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid,%20n%20-%201,%20m%20-%201%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D,%20mem%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E4%B8%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%8D%95%E5%85%83%E6%A0%BC%EF%BC%8C%E5%88%99%E7%BB%88%E6%AD%A2%E6%90%9C%E7%B4%A2%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E4%BB%B7%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%AE%B0%E5%BD%95%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%E5%B7%A6%E8%BE%B9%E5%92%8C%E4%B8%8A%E8%BE%B9%E5%8D%95%E5%85%83%E6%A0%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20i%20-%201,%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20i,%20j%20-%201%29%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E5%B9%B6%E8%BF%94%E5%9B%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i,%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left,%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20*%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20n%20-%201,%20m%20-%201%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20m%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md b/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md new file mode 100644 index 0000000000..bbd2ed1988 --- /dev/null +++ b/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D,%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1,%202,%203%5D%0A%20%20%20%20val%20%3D%20%5B5,%2011,%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D,%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1,%202,%203%5D%0A%20%20%20%20val%20%3D%20%5B5,%2011,%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_graph/graph_adjacency_list.md b/codes/pythontutor/chapter_graph/graph_adjacency_list.md new file mode 100644 index 0000000000..aa43d5806a --- /dev/null +++ b/codes/pythontutor/chapter_graph/graph_adjacency_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%89%80%E6%9C%89%E9%A1%B6%E7%82%B9%E5%92%8C%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%A1%B6%E7%82%B9%E6%95%B0%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E6%96%B0%E9%93%BE%E8%A1%A8%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%20vet%20%E5%AF%B9%E5%BA%94%E7%9A%84%E9%93%BE%E8%A1%A8%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%85%B6%E4%BB%96%E9%A1%B6%E7%82%B9%E7%9A%84%E9%93%BE%E8%A1%A8%EF%BC%8C%E5%88%A0%E9%99%A4%E6%89%80%E6%9C%89%E5%8C%85%E5%90%AB%20vet%20%E7%9A%84%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1,%203,%202,%205,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20graph.add_edge%28v%5B0%5D,%20v%5B2%5D%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%0A%20%20%20%20graph.remove_edge%28v%5B0%5D,%20v%5B1%5D%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md b/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md new file mode 100644 index 0000000000..a053b757e7 --- /dev/null +++ b/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20vertices%3A%20list%5Bint%5D,%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D,%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%A1%B6%E7%82%B9%E6%95%B0%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E6%96%B0%E9%A1%B6%E7%82%B9%E7%9A%84%E5%80%BC%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%E4%B8%AD%E7%A7%BB%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E8%A1%8C%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E4%B8%8E%E7%9B%B8%E7%AD%89%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E4%B8%8E%E7%9B%B8%E7%AD%89%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20vertices%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0,%201%5D,%20%5B0,%203%5D,%20%5B1,%202%5D,%20%5B2,%203%5D,%20%5B2,%204%5D,%20%5B3,%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices,%20edges%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%201,%202%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%AB%E4%B8%BA%200,%202%0A%20%20%20%20graph.add_edge%280,%202%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%201,%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%AB%E4%B8%BA%200,%201%0A%20%20%20%20graph.remove_edge%280,%201%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20graph.add_vertex%286%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%201%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_graph/graph_bfs.md b/codes/pythontutor/chapter_graph/graph_bfs.md new file mode 100644 index 0000000000..6edd606e6f --- /dev/null +++ b/codes/pythontutor/chapter_graph/graph_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList,%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E8%BF%87%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E9%98%9F%E5%88%97%E7%94%A8%E4%BA%8E%E5%AE%9E%E7%8E%B0%20BFS%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E4%BB%A5%E9%A1%B6%E7%82%B9%20vet%20%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E5%BE%AA%E7%8E%AF%E7%9B%B4%E8%87%B3%E8%AE%BF%E9%97%AE%E5%AE%8C%E6%89%80%E6%9C%89%E9%A1%B6%E7%82%B9%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E9%A1%B6%E7%82%B9%E5%87%BA%E9%98%9F%0A%20%20%20%20%20%20%20%20res.append%28vet%29%20%20%23%20%E8%AE%B0%E5%BD%95%E8%AE%BF%E9%97%AE%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E8%AF%A5%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E8%BF%87%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%20%20%23%20%E5%8F%AA%E5%85%A5%E9%98%9F%E6%9C%AA%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%20%20%23%20%E6%A0%87%E8%AE%B0%E8%AF%A5%E9%A1%B6%E7%82%B9%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0,%201,%202,%203,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20graph_bfs%28graph,%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_graph/graph_dfs.md b/codes/pythontutor/chapter_graph/graph_dfs.md new file mode 100644 index 0000000000..c6ed274c40 --- /dev/null +++ b/codes/pythontutor/chapter_graph/graph_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20dfs%28graph%3A%20GraphAdjList,%20visited%3A%20set%5BVertex%5D,%20res%3A%20list%5BVertex%5D,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20res.append%28vet%29%20%20%23%20%E8%AE%B0%E5%BD%95%E8%AE%BF%E9%97%AE%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited.add%28vet%29%20%20%23%20%E6%A0%87%E8%AE%B0%E8%AF%A5%E9%A1%B6%E7%82%B9%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E8%AF%A5%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E8%BF%87%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%AE%BF%E9%97%AE%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20dfs%28graph,%20visited,%20res,%20adjVet%29%0A%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList,%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E6%9D%A5%E8%A1%A8%E7%A4%BA%E5%9B%BE%EF%BC%8C%E4%BB%A5%E4%BE%BF%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E8%BF%87%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph,%20visited,%20res,%20start_vet%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0,%201,%202,%203,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%0A%20%20%20%20%23%20%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20graph_dfs%28graph,%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_greedy/coin_change_greedy.md b/codes/pythontutor/chapter_greedy/coin_change_greedy.md new file mode 100644 index 0000000000..25d8e0ddcc --- /dev/null +++ b/codes/pythontutor/chapter_greedy/coin_change_greedy.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%81%87%E8%AE%BE%20coins%20%E5%88%97%E8%A1%A8%E6%9C%89%E5%BA%8F%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%BF%9B%E8%A1%8C%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%EF%BC%8C%E7%9B%B4%E5%88%B0%E6%97%A0%E5%89%A9%E4%BD%99%E9%87%91%E9%A2%9D%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E5%B0%8F%E4%BA%8E%E4%B8%94%E6%9C%80%E6%8E%A5%E8%BF%91%E5%89%A9%E4%BD%99%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%89%E6%8B%A9%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%E8%8B%A5%E6%9C%AA%E6%89%BE%E5%88%B0%E5%8F%AF%E8%A1%8C%E6%96%B9%E6%A1%88%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%EF%BC%9A%E8%83%BD%E5%A4%9F%E4%BF%9D%E8%AF%81%E6%89%BE%E5%88%B0%E5%85%A8%E5%B1%80%E6%9C%80%E4%BC%98%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1,%205,%2010,%2020,%2050,%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins,%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D,%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%EF%BC%9A%E6%97%A0%E6%B3%95%E4%BF%9D%E8%AF%81%E6%89%BE%E5%88%B0%E5%85%A8%E5%B1%80%E6%9C%80%E4%BC%98%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1,%2020,%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins,%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D,%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E5%AE%9E%E9%99%85%E4%B8%8A%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E4%B8%BA%203%20%EF%BC%8C%E5%8D%B3%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_greedy/fractional_knapsack.md b/codes/pythontutor/chapter_greedy/fractional_knapsack.md new file mode 100644 index 0000000000..d72831d36f --- /dev/null +++ b/codes/pythontutor/chapter_greedy/fractional_knapsack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%E7%89%A9%E5%93%81%22%22%22%0A%20%20%20%20def%20__init__%28self,%20w%3A%20int,%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%E7%89%A9%E5%93%81%E9%87%8D%E9%87%8F%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%86%E6%95%B0%E8%83%8C%E5%8C%85%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E7%89%A9%E5%93%81%E5%88%97%E8%A1%A8%EF%BC%8C%E5%8C%85%E5%90%AB%E4%B8%A4%E4%B8%AA%E5%B1%9E%E6%80%A7%EF%BC%9A%E9%87%8D%E9%87%8F%E3%80%81%E4%BB%B7%E5%80%BC%0A%20%20%20%20items%20%3D%20%5BItem%28w,%20v%29%20for%20w,%20v%20in%20zip%28wgt,%20val%29%5D%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E5%8D%95%E4%BD%8D%E4%BB%B7%E5%80%BC%20item.v%20/%20item.w%20%E4%BB%8E%E9%AB%98%E5%88%B0%E4%BD%8E%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20/%20item.w,%20reverse%3DTrue%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%E5%85%85%E8%B6%B3%EF%BC%8C%E5%88%99%E5%B0%86%E5%BD%93%E5%89%8D%E7%89%A9%E5%93%81%E6%95%B4%E4%B8%AA%E8%A3%85%E8%BF%9B%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%E4%B8%8D%E8%B6%B3%EF%BC%8C%E5%88%99%E5%B0%86%E5%BD%93%E5%89%8D%E7%89%A9%E5%93%81%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86%E8%A3%85%E8%BF%9B%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20/%20item.w%29%20*%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B7%B2%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%9B%A0%E6%AD%A4%E8%B7%B3%E5%87%BA%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_greedy/max_capacity.md b/codes/pythontutor/chapter_greedy/max_capacity.md new file mode 100644 index 0000000000..140b07ce4d --- /dev/null +++ b/codes/pythontutor/chapter_greedy/max_capacity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20i,%20j%EF%BC%8C%E4%BD%BF%E5%85%B6%E5%88%86%E5%88%97%E6%95%B0%E7%BB%84%E4%B8%A4%E7%AB%AF%0A%20%20%20%20i,%20j%20%3D%200,%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E4%B8%BA%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%EF%BC%8C%E7%9B%B4%E8%87%B3%E4%B8%A4%E6%9D%BF%E7%9B%B8%E9%81%87%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D,%20ht%5Bj%5D%29%20*%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res,%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E5%86%85%E7%A7%BB%E5%8A%A8%E7%9F%AD%E6%9D%BF%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3,%208,%205,%202,%207,%207,%203,%204%5D%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_greedy/max_product_cutting.md b/codes/pythontutor/chapter_greedy/max_product_cutting.md new file mode 100644 index 0000000000..c46de9c110 --- /dev/null +++ b/codes/pythontutor/chapter_greedy/max_product_cutting.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A7%AF%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%20n%20%3C%3D%203%20%E6%97%B6%EF%BC%8C%E5%BF%85%E9%A1%BB%E5%88%87%E5%88%86%E5%87%BA%E4%B8%80%E4%B8%AA%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20*%20%28n%20-%201%29%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E5%9C%B0%E5%88%87%E5%88%86%E5%87%BA%203%20%EF%BC%8Ca%20%E4%B8%BA%203%20%E7%9A%84%E4%B8%AA%E6%95%B0%EF%BC%8Cb%20%E4%B8%BA%E4%BD%99%E6%95%B0%0A%20%20%20%20a,%20b%20%3D%20n%20//%203,%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%201%20%E6%97%B6%EF%BC%8C%E5%B0%86%E4%B8%80%E5%AF%B9%201%20*%203%20%E8%BD%AC%E5%8C%96%E4%B8%BA%202%20*%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283,%20a%20-%201%29%29%20*%202%20*%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%202%20%E6%97%B6%EF%BC%8C%E4%B8%8D%E5%81%9A%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283,%20a%29%29%20*%202%0A%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%200%20%E6%97%B6%EF%BC%8C%E4%B8%8D%E5%81%9A%E5%A4%84%E7%90%86%0A%20%20%20%20return%20int%28math.pow%283,%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A7%AF%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_hashing/array_hash_map.md b/codes/pythontutor/chapter_hashing/array_hash_map.md new file mode 100644 index 0000000000..5ff71db93e --- /dev/null +++ b/codes/pythontutor/chapter_hashing/array_hash_map.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20def%20__init__%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0A%0Aclass%20ArrayHashMap%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%EF%BC%8C%E5%8C%85%E5%90%AB%2020%20%E4%B8%AA%E6%A1%B6%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20*%2020%0A%0A%20%20%20%20def%20hash_func%28self,%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self,%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key,%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self,%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20%23%20%E7%BD%AE%E4%B8%BA%20None%20%EF%BC%8C%E4%BB%A3%E8%A1%A8%E5%88%A0%E9%99%A4%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E9%94%AE%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%93%E5%8D%B0%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key,%20%22-%3E%22,%20pair.val%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.put%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hmap.put%2815937,%20%22%E5%B0%8F%E5%95%B0%22%29%0A%20%20%20%20hmap.put%2816750,%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hmap.put%2813276,%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hmap.put%2810583,%20%22%E5%B0%8F%E9%B8%AD%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.remove%2810583%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20print%28%22%5Cn%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20Key-%3EValue%22%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key,%20%22-%3E%22,%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_hashing/hash_map_chaining.md b/codes/pythontutor/chapter_hashing/hash_map_chaining.md new file mode 100644 index 0000000000..477c4da252 --- /dev/null +++ b/codes/pythontutor/chapter_hashing/hash_map_chaining.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20def%20__init__%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E5%BC%8F%E5%9C%B0%E5%9D%80%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20/%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self,%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%B4%9F%E8%BD%BD%E5%9B%A0%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%20/%20self.capacity%0A%0A%20%20%20%20def%20get%28self,%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key,%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self,%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%A9%E5%AE%B9%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20*%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key,%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%93%E5%8D%B0%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%22%20-%3E%20%22%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.put%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hashmap.put%2815937,%20%22%E5%B0%8F%E5%95%B0%22%29%0A%20%20%20%20hashmap.put%2816750,%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hashmap.put%2813276,%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hashmap.put%2810583,%20%22%E5%B0%8F%E9%B8%AD%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_hashing/simple_hash.md b/codes/pythontutor/chapter_hashing/simple_hash.md new file mode 100644 index 0000000000..9345f5f6ec --- /dev/null +++ b/codes/pythontutor/chapter_hashing/simple_hash.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20*%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_heap/my_heap.md b/codes/pythontutor/chapter_heap/my_heap.md new file mode 100644 index 0000000000..133a1f4a04 --- /dev/null +++ b/codes/pythontutor/chapter_heap/my_heap.md @@ -0,0 +1,20 @@ + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%EF%BC%8C%E6%A0%B9%E6%8D%AE%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%BB%BA%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20%23%20%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l,%20r,%20ma%20%3D%20self.left%28i%29,%20self.right%28i%29,%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1,%202,%203,%204,%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%20%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%206,%206,%207,%205,%202,%201,%204,%203,%206,%202%5D%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E4%B8%BA%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%8E%E5%BA%95%E8%87%B3%E9%A1%B6%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E5%BA%95%E8%87%B3%E9%A1%B6%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E8%8A%82%E7%82%B9%20i%20%E7%9A%84%E7%88%B6%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E2%80%9C%E8%B6%8A%E8%BF%87%E6%A0%B9%E8%8A%82%E7%82%B9%E2%80%9D%E6%88%96%E2%80%9C%E8%8A%82%E7%82%B9%E6%97%A0%E9%A1%BB%E4%BF%AE%E5%A4%8D%E2%80%9D%E6%97%B6%EF%BC%8C%E7%BB%93%E6%9D%9F%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8A%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%206,%206,%207,%205,%202,%201,%204,%203,%206,%202%5D%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E7%A9%BA%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20self.swap%280,%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l,%20r,%20ma%20%3D%20self.left%28i%29,%20self.right%28i%29,%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%207,%206,%207,%206,%202,%201,%204,%203,%206,%202,%205%5D%29%0A%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_heap/top_k.md b/codes/pythontutor/chapter_heap/top_k.md new file mode 100644 index 0000000000..45adf03e83 --- /dev/null +++ b/codes/pythontutor/chapter_heap/top_k.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D,%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E5%A0%86%E6%9F%A5%E6%89%BE%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%B0%86%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%8D%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap,%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%E4%BB%8E%E7%AC%AC%20k%2B1%20%E4%B8%AA%E5%85%83%E7%B4%A0%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BF%9D%E6%8C%81%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20k%0A%20%20%20%20for%20i%20in%20range%28k,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A4%A7%E4%BA%8E%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%EF%BC%8C%E5%88%99%E5%B0%86%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E3%80%81%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap,%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%207,%206,%203,%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums,%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_searching/binary_search.md b/codes/pythontutor/chapter_searching/binary_search.md new file mode 100644 index 0000000000..f54c96cd5b --- /dev/null +++ b/codes/pythontutor/chapter_searching/binary_search.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%20%EF%BC%8C%E5%8D%B3%20i,%20j%20%E5%88%86%E5%88%AB%E6%8C%87%E5%90%91%E6%95%B0%E7%BB%84%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%EF%BC%8C%E5%BD%93%E6%90%9C%E7%B4%A2%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E8%B7%B3%E5%87%BA%EF%BC%88%E5%BD%93%20i%20%3E%20j%20%E6%97%B6%E4%B8%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%90%86%E8%AE%BA%E4%B8%8A%20Python%20%E7%9A%84%E6%95%B0%E5%AD%97%E5%8F%AF%E4%BB%A5%E6%97%A0%E9%99%90%E5%A4%A7%EF%BC%88%E5%8F%96%E5%86%B3%E4%BA%8E%E5%86%85%E5%AD%98%E5%A4%A7%E5%B0%8F%EF%BC%89%EF%BC%8C%E6%97%A0%E9%A1%BB%E8%80%83%E8%99%91%E5%A4%A7%E6%95%B0%E8%B6%8A%E7%95%8C%E9%97%AE%E9%A2%98%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%20%5B0,%20n%29%20%EF%BC%8C%E5%8D%B3%20i,%20j%20%E5%88%86%E5%88%AB%E6%8C%87%E5%90%91%E6%95%B0%E7%BB%84%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%2B1%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%EF%BC%8C%E5%BD%93%E6%90%9C%E7%B4%A2%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E8%B7%B3%E5%87%BA%EF%BC%88%E5%BD%93%20i%20%3D%20j%20%E6%97%B6%E4%B8%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_searching/binary_search_edge.md b/codes/pythontutor/chapter_searching/binary_search_edge.md new file mode 100644 index 0000000000..b3b1eb2f3d --- /dev/null +++ b/codes/pythontutor/chapter_searching/binary_search_edge.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%20target%22%22%22%0A%20%20%20%20%23%20%E7%AD%89%E4%BB%B7%E4%BA%8E%E6%9F%A5%E6%89%BE%20target%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums,%20target%29%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20!%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20i%0A%20%20%20%20return%20i%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%B7%A6%E8%BE%B9%E7%95%8C%E5%92%8C%E5%8F%B3%E8%BE%B9%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums,%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%20target%22%22%22%0A%20%20%20%20%23%20%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%9F%A5%E6%89%BE%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%20target%20%2B%201%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums,%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%E6%8C%87%E5%90%91%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%20target%20%EF%BC%8Ci%20%E6%8C%87%E5%90%91%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20!%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20return%20j%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%B7%A6%E8%BE%B9%E7%95%8C%E5%92%8C%E5%8F%B3%E8%BE%B9%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums,%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_searching/binary_search_insertion.md b/codes/pythontutor/chapter_searching/binary_search_insertion.md new file mode 100644 index 0000000000..64b53e1862 --- /dev/null +++ b/codes/pythontutor/chapter_searching/binary_search_insertion.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E6%97%A0%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20m%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums,%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums,%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_searching/two_sum.md b/codes/pythontutor/chapter_searching/two_sum.md new file mode 100644 index 0000000000..3aca0bc0df --- /dev/null +++ b/codes/pythontutor/chapter_searching/two_sum.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%B8%80%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%9E%9A%E4%B8%BE%22%22%22%0A%20%20%20%20%23%20%E4%B8%A4%E5%B1%82%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi,%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%207,%2011,%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums,%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E8%BE%85%E5%8A%A9%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%23%20%E8%BE%85%E5%8A%A9%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%A9%BA%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%E5%8D%95%E5%B1%82%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D,%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%207,%2011,%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums,%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/bubble_sort.md b/codes/pythontutor/chapter_sorting/bubble_sort.md new file mode 100644 index 0000000000..e254840aed --- /dev/null +++ b/codes/pythontutor/chapter_sorting/bubble_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%87%E5%BF%97%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%87%E5%BF%97%E4%BD%8D%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E8%AE%B0%E5%BD%95%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E6%AD%A4%E8%BD%AE%E2%80%9C%E5%86%92%E6%B3%A1%E2%80%9D%E6%9C%AA%E4%BA%A4%E6%8D%A2%E4%BB%BB%E4%BD%95%E5%85%83%E7%B4%A0%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%87%BA%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/bucket_sort.md b/codes/pythontutor/chapter_sorting/bucket_sort.md new file mode 100644 index 0000000000..e3589ddc14 --- /dev/null +++ b/codes/pythontutor/chapter_sorting/bucket_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E6%A1%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20k%20%3D%20n/2%20%E4%B8%AA%E6%A1%B6%EF%BC%8C%E9%A2%84%E6%9C%9F%E5%90%91%E6%AF%8F%E4%B8%AA%E6%A1%B6%E5%88%86%E9%85%8D%202%20%E4%B8%AA%E5%85%83%E7%B4%A0%0A%20%20%20%20k%20%3D%20len%28nums%29%20//%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%88%86%E9%85%8D%E5%88%B0%E5%90%84%E4%B8%AA%E6%A1%B6%E4%B8%AD%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%EF%BC%8C%E4%BD%BF%E7%94%A8%20num%20*%20k%20%E6%98%A0%E5%B0%84%E5%88%B0%E7%B4%A2%E5%BC%95%E8%8C%83%E5%9B%B4%20%5B0,%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20*%20k%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%BF%9B%E6%A1%B6%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%AF%B9%E5%90%84%E4%B8%AA%E6%A1%B6%E6%89%A7%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%86%85%E7%BD%AE%E6%8E%92%E5%BA%8F%E5%87%BD%E6%95%B0%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9B%BF%E6%8D%A2%E6%88%90%E5%85%B6%E4%BB%96%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%E6%A1%B6%E5%90%88%E5%B9%B6%E7%BB%93%E6%9E%9C%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%AE%BE%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E4%B8%BA%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%8C%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49,%200.96,%200.82,%200.09,%200.57,%200.43,%200.91,%200.75,%200.15,%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E6%A1%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/counting_sort.md b/codes/pythontutor/chapter_sorting/counting_sort.md new file mode 100644 index 0000000000..34dfc56afe --- /dev/null +++ b/codes/pythontutor/chapter_sorting/counting_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0%EF%BC%8C%E6%97%A0%E6%B3%95%E7%94%A8%E4%BA%8E%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m,%20num%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%20counter%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%97%A0%E6%B3%95%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0%EF%BC%8C%E5%8F%AF%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%B9%B6%E4%B8%94%E6%98%AF%E7%A8%B3%E5%AE%9A%E6%8E%92%E5%BA%8F%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E6%B1%82%20counter%20%E7%9A%84%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E5%B0%BE%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20%23%20%E5%8D%B3%20counter%5Bnum%5D-1%20%E6%98%AF%20num%20%E5%9C%A8%20res%20%E4%B8%AD%E6%9C%80%E5%90%8E%E4%B8%80%E6%AC%A1%E5%87%BA%E7%8E%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%20nums%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%20res%20%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E7%BB%93%E6%9E%9C%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%E5%B0%86%20num%20%E6%94%BE%E7%BD%AE%E5%88%B0%E5%AF%B9%E5%BA%94%E7%B4%A2%E5%BC%95%E5%A4%84%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E4%BB%A4%E5%89%8D%E7%BC%80%E5%92%8C%E8%87%AA%E5%87%8F%201%20%EF%BC%8C%E5%BE%97%E5%88%B0%E4%B8%8B%E6%AC%A1%E6%94%BE%E7%BD%AE%20num%20%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%20%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/heap_sort.md b/codes/pythontutor/chapter_sorting/heap_sort.md new file mode 100644 index 0000000000..243f681d07 --- /dev/null +++ b/codes/pythontutor/chapter_sorting/heap_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D,%20n%3A%20int,%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%EF%BC%8C%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20*%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20*%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bma%5D%20%3D%20nums%5Bma%5D,%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E5%A0%86%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20//%202%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20len%28nums%29,%20i%29%0A%20%20%20%20%23%20%E4%BB%8E%E5%A0%86%E4%B8%AD%E6%8F%90%E5%8F%96%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%AA%E7%8E%AF%20n-1%20%E8%BD%AE%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D,%20nums%5Bi%5D%20%3D%20nums%5Bi%5D,%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%A5%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E8%BF%9B%E8%A1%8C%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20i,%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E5%A0%86%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/insertion_sort.md b/codes/pythontutor/chapter_sorting/insertion_sort.md new file mode 100644 index 0000000000..51be921b1b --- /dev/null +++ b/codes/pythontutor/chapter_sorting/insertion_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%20base%20%E6%8F%92%E5%85%A5%E5%88%B0%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i-1%5D%20%E4%B8%AD%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%E5%B0%86%20nums%5Bj%5D%20%E5%90%91%E5%8F%B3%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%E5%B0%86%20base%20%E8%B5%8B%E5%80%BC%E5%88%B0%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/merge_sort.md b/codes/pythontutor/chapter_sorting/merge_sort.md new file mode 100644 index 0000000000..2c62845d88 --- /dev/null +++ b/codes/pythontutor/chapter_sorting/merge_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E5%B9%B6%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20mid%5D,%20%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bmid%2B1,%20right%5D%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E6%94%BE%E5%90%88%E5%B9%B6%E5%90%8E%E7%9A%84%E7%BB%93%E6%9E%9C%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20*%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E8%B5%B7%E5%A7%8B%E7%B4%A2%E5%BC%95%0A%20%20%20%20i,%20j,%20k%20%3D%20left,%20mid%20%2B%201,%200%0A%20%20%20%20%23%20%E5%BD%93%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E9%83%BD%E8%BF%98%E6%9C%89%E5%85%83%E7%B4%A0%E6%97%B6%EF%BC%8C%E8%BF%9B%E8%A1%8C%E6%AF%94%E8%BE%83%E5%B9%B6%E5%B0%86%E8%BE%83%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%A9%E4%BD%99%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%9B%9E%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%20%E7%9A%84%E5%AF%B9%E5%BA%94%E5%8C%BA%E9%97%B4%0A%20%20%20%20for%20k%20in%20range%280,%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E5%BD%93%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20%23%20%E5%88%92%E5%88%86%E9%98%B6%E6%AE%B5%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%0A%20%20%20%20merge_sort%28nums,%20left,%20mid%29%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20merge_sort%28nums,%20mid%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%23%20%E5%90%88%E5%B9%B6%E9%98%B6%E6%AE%B5%0A%20%20%20%20merge%28nums,%20left,%20mid,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7,%203,%202,%206,%200,%201,%205,%204%5D%0A%20%20%20%20merge_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/quick_sort.md b/codes/pythontutor/chapter_sorting/quick_sort.md new file mode 100644 index 0000000000..7272170263 --- /dev/null +++ b/codes/pythontutor/chapter_sorting/quick_sort.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20partition%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%0A%20%20%20%20pivot%20%3D%20partition%28nums,%20left,%20right%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E3%80%81%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20quick_sort%28nums,%20left,%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums,%20pivot%20%2B%201,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20quick_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%89%E5%8F%96%E4%B8%89%E4%B8%AA%E5%80%99%E9%80%89%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0%22%22%22%0A%20%20%20%20l,%20m,%20r%20%3D%20nums%5Bleft%5D,%20nums%5Bmid%5D,%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%E5%9C%A8%20l%20%E5%92%8C%20r%20%E4%B9%8B%E9%97%B4%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%E5%9C%A8%20m%20%E5%92%8C%20r%20%E4%B9%8B%E9%97%B4%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%EF%BC%88%E4%B8%89%E6%95%B0%E5%8F%96%E4%B8%AD%E5%80%BC%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20med%20%3D%20median_three%28nums,%20left,%20%28left%20%2B%20right%29%20//%202,%20right%29%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%AD%E4%BD%8D%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E6%95%B0%E7%BB%84%E6%9C%80%E5%B7%A6%E7%AB%AF%0A%20%20%20%20nums%5Bleft%5D,%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D,%20nums%5Bleft%5D%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%AD%E4%BD%8D%E5%9F%BA%E5%87%86%E6%95%B0%E4%BC%98%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20partition%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%EF%BC%88%E4%B8%AD%E4%BD%8D%E5%9F%BA%E5%87%86%E6%95%B0%E4%BC%98%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%E6%93%8D%E4%BD%9C%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums,%20left,%20right%29%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E4%B8%A4%E4%B8%AA%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AD%E8%BE%83%E7%9F%AD%E7%9A%84%E9%82%A3%E4%B8%AA%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums,%20left,%20pivot%20-%201%29%20%20%23%20%E9%80%92%E5%BD%92%E6%8E%92%E5%BA%8F%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%20%20%23%20%E5%89%A9%E4%BD%99%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bpivot%20%2B%201,%20right%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums,%20pivot%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E6%8E%92%E5%BA%8F%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%20%20%23%20%E5%89%A9%E4%BD%99%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20pivot%20-%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20quick_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/radix_sort.md b/codes/pythontutor/chapter_sorting/radix_sort.md new file mode 100644 index 0000000000..98ecd2cd1c --- /dev/null +++ b/codes/pythontutor/chapter_sorting/radix_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int,%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%85%83%E7%B4%A0%20num%20%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E5%85%B6%E4%B8%AD%20exp%20%3D%2010%5E%28k-1%29%22%22%22%0A%20%20%20%20%23%20%E4%BC%A0%E5%85%A5%20exp%20%E8%80%8C%E9%9D%9E%20k%20%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%9C%A8%E6%AD%A4%E9%87%8D%E5%A4%8D%E6%89%A7%E8%A1%8C%E6%98%82%E8%B4%B5%E7%9A%84%E6%AC%A1%E6%96%B9%E8%AE%A1%E7%AE%97%0A%20%20%20%20return%20%28num%20//%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D,%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%B9%E6%8D%AE%20nums%20%E7%AC%AC%20k%20%E4%BD%8D%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%8D%81%E8%BF%9B%E5%88%B6%E7%9A%84%E4%BD%8D%E8%8C%83%E5%9B%B4%E4%B8%BA%200~9%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E9%9C%80%E8%A6%81%E9%95%BF%E5%BA%A6%E4%B8%BA%2010%20%E7%9A%84%E6%A1%B6%E6%95%B0%E7%BB%84%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E7%BB%9F%E8%AE%A1%200~9%20%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%20%20%23%20%E8%8E%B7%E5%8F%96%20nums%5Bi%5D%20%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E8%AE%B0%E4%B8%BA%20d%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E5%AD%97%20d%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20%E6%B1%82%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E4%B8%AA%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E6%95%B0%E7%BB%84%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20for%20i%20in%20range%281,%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%8C%E6%A0%B9%E6%8D%AE%E6%A1%B6%E5%86%85%E7%BB%9F%E8%AE%A1%E7%BB%93%E6%9E%9C%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%20res%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20%E8%8E%B7%E5%8F%96%20d%20%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E5%B0%86%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20%E5%B0%86%20d%20%E7%9A%84%E6%95%B0%E9%87%8F%E5%87%8F%201%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%88%A4%E6%96%AD%E6%9C%80%E5%A4%A7%E4%BD%8D%E6%95%B0%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E4%BB%8E%E4%BD%8E%E4%BD%8D%E5%88%B0%E9%AB%98%E4%BD%8D%E7%9A%84%E9%A1%BA%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%E6%89%A7%E8%A1%8C%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E5%8D%B3%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums,%20exp%29%0A%20%20%20%20%20%20%20%20exp%20*%3D%2010%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105,%0A%20%20%20%20%20%20%20%20356,%0A%20%20%20%20%20%20%20%20428,%0A%20%20%20%20%20%20%20%20348,%0A%20%20%20%20%20%20%20%20818,%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_sorting/selection_sort.md b/codes/pythontutor/chapter_sorting/selection_sort.md new file mode 100644 index 0000000000..2a9e7531d2 --- /dev/null +++ b/codes/pythontutor/chapter_sorting/selection_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bi,%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%89%BE%E5%88%B0%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E5%86%85%E7%9A%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E8%AE%B0%E5%BD%95%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E8%AF%A5%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E4%B8%8E%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E7%9A%84%E9%A6%96%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bk%5D%20%3D%20nums%5Bk%5D,%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_stack_and_queue/array_queue.md b/codes/pythontutor/chapter_stack_and_queue/array_queue.md new file mode 100644 index 0000000000..cb987db7f4 --- /dev/null +++ b/codes/pythontutor/chapter_stack_and_queue/array_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E7%8E%AF%E5%BD%A2%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%98%9F%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20*%20size%20%20%23%20%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E9%98%9F%E5%88%97%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%20%20%23%20%E9%98%9F%E9%A6%96%E6%8C%87%E9%92%88%EF%BC%8C%E6%8C%87%E5%90%91%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%20%20%23%20%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E5%B7%B2%E6%BB%A1%22%29%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E9%98%9F%E5%B0%BE%E6%8C%87%E9%92%88%EF%BC%8C%E6%8C%87%E5%90%91%E9%98%9F%E5%B0%BE%E7%B4%A2%E5%BC%95%20%2B%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E5%8F%96%E4%BD%99%E6%93%8D%E4%BD%9C%E5%AE%9E%E7%8E%B0%20rear%20%E8%B6%8A%E8%BF%87%E6%95%B0%E7%BB%84%E5%B0%BE%E9%83%A8%E5%90%8E%E5%9B%9E%E5%88%B0%E5%A4%B4%E9%83%A8%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E9%98%9F%E9%A6%96%E6%8C%87%E9%92%88%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%EF%BC%8C%E8%8B%A5%E8%B6%8A%E8%BF%87%E5%B0%BE%E9%83%A8%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E5%88%B0%E6%95%B0%E7%BB%84%E5%A4%B4%E9%83%A8%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20*%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5B%28j%20%25%20self.capacity%28%29%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_stack_and_queue/array_stack.md b/codes/pythontutor/chapter_stack_and_queue/array_stack.md new file mode 100644 index 0000000000..c28ef2a54c --- /dev/null +++ b/codes/pythontutor/chapter_stack_and_queue/array_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%A0%88%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self,%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md b/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md new file mode 100644 index 0000000000..340b9f57d8 --- /dev/null +++ b/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%98%9F%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%A4%B4%E8%8A%82%E7%82%B9%20front%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B0%BE%E8%8A%82%E7%82%B9%20rear%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E8%8A%82%E7%82%B9%E5%90%8E%E6%B7%BB%E5%8A%A0%20num%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E4%BB%A4%E5%A4%B4%E3%80%81%E5%B0%BE%E8%8A%82%E7%82%B9%E9%83%BD%E6%8C%87%E5%90%91%E8%AF%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E9%98%9F%E5%88%97%E4%B8%8D%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%B0%86%E8%AF%A5%E8%8A%82%E7%82%B9%E6%B7%BB%E5%8A%A0%E5%88%B0%E5%B0%BE%E8%8A%82%E7%82%B9%E5%90%8E%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%A4%B4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20queue%20%3D%22,%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20queue%20%3D%22,%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md b/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md new file mode 100644 index 0000000000..10ffaaca1a --- /dev/null +++ b/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%A0%88%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_tree/array_binary_tree.md b/codes/pythontutor/chapter_tree/array_binary_tree.md new file mode 100644 index 0000000000..3ebb2f845f --- /dev/null +++ b/codes/pythontutor/chapter_tree/array_binary_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%20%20%20%20%22%22%22%E6%95%B0%E7%BB%84%E8%A1%A8%E7%A4%BA%E4%B8%8B%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%B4%A2%E5%BC%95%E4%B8%BA%20i%20%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self,%20i%3A%20int,%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22pre%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29,%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22in%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29,%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22post%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22pre%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22in%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22post%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20arr%20%3D%20%5B1,%202,%203,%204,%20None,%206,%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E8%8A%82%E7%82%B9%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20l,%20r,%20p%20%3D%20abt.left%28i%29,%20abt.right%28i%29,%20abt.parent%28i%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%A0%91%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_tree/binary_search_tree.md b/codes/pythontutor/chapter_tree/binary_search_tree.md new file mode 100644 index 0000000000..960c4c2f2f --- /dev/null +++ b/codes/pythontutor/chapter_tree/binary_search_tree.md @@ -0,0 +1,14 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A0%91%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self,%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%EF%BC%8C%E8%B7%B3%E5%87%BA%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A0%91%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E5%A4%8D%E8%8A%82%E7%82%B9%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%22%5Cn%E6%9F%A5%E6%89%BE%E5%88%B0%E7%9A%84%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%E4%B8%BA%3A%20%7B%7D%EF%BC%8C%E8%8A%82%E7%82%B9%E5%80%BC%20%3D%20%7B%7D%22.format%28node,%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A0%91%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A0%91%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E5%A4%8D%E8%8A%82%E7%82%B9%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%200%20or%201%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%200%20/%201%20%E6%97%B6%EF%BC%8C%20child%20%3D%20null%20/%20%E8%AF%A5%E5%AD%90%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20!%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%202%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E4%B8%AD%20cur%20%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%94%A8%20tmp%20%E8%A6%86%E7%9B%96%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20bst.remove%281%29%20%23%20%E5%BA%A6%E4%B8%BA%200%0A%20%20%20%20bst.remove%282%29%20%23%20%E5%BA%A6%E4%B8%BA%201%0A%20%20%20%20bst.remove%284%29%20%23%20%E5%BA%A6%E4%B8%BA%202&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_tree/binary_tree_bfs.md b/codes/pythontutor/chapter_tree/binary_tree_bfs.md new file mode 100644 index 0000000000..e59386e4db --- /dev/null +++ b/codes/pythontutor/chapter_tree/binary_tree_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%EF%BC%8C%E5%8A%A0%E5%85%A5%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%E5%88%97%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E4%BF%9D%E5%AD%98%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%20%20%23%20%E9%98%9F%E5%88%97%E5%87%BA%E9%98%9F%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%20%20%23%20%E4%BF%9D%E5%AD%98%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%85%A5%E9%98%9F%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%85%A5%E9%98%9F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E8%BF%99%E9%87%8C%E5%80%9F%E5%8A%A9%E4%BA%86%E4%B8%80%E4%B8%AA%E4%BB%8E%E6%95%B0%E7%BB%84%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%87%BD%E6%95%B0%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1,%202,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/pythontutor/chapter_tree/binary_tree_dfs.md b/codes/pythontutor/chapter_tree/binary_tree_dfs.md new file mode 100644 index 0000000000..16128184e7 --- /dev/null +++ b/codes/pythontutor/chapter_tree/binary_tree_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E6%A0%B9%E8%8A%82%E7%82%B9%20-%3E%20%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E6%A0%B9%E8%8A%82%E7%82%B9%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%20-%3E%20%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E8%BF%99%E9%87%8C%E5%80%9F%E5%8A%A9%E4%BA%86%E4%B8%80%E4%B8%AA%E4%BB%8E%E6%95%B0%E7%BB%84%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%87%BD%E6%95%B0%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1,%202,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29%0A%0A%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29%0A%0A%20%20%20%20%23%20%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/codes/ruby/chapter_array_and_linkedlist/array.rb b/codes/ruby/chapter_array_and_linkedlist/array.rb new file mode 100644 index 0000000000..f9febd0d5f --- /dev/null +++ b/codes/ruby/chapter_array_and_linkedlist/array.rb @@ -0,0 +1,108 @@ +=begin +File: array.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 随机访问元素 ### +def random_access(nums) + # 在区间 [0, nums.length) 中随机抽取一个数字 + random_index = Random.rand(0...nums.length) + + # 获取并返回随机元素 + nums[random_index] +end + + +### 扩展数组长度 ### +# 请注意,Ruby 的 Array 是动态数组,可以直接扩展 +# 为了方便学习,本函数将 Array 看作长度不可变的数组 +def extend(nums, enlarge) + # 初始化一个扩展长度后的数组 + res = Array.new(nums.length + enlarge, 0) + + # 将原数组中的所有元素复制到新数组 + for i in 0...nums.length + res[i] = nums[i] + end + + # 返回扩展后的新数组 + res +end + +### 在数组的索引 index 处插入元素 num ### +def insert(nums, num, index) + # 把索引 index 以及之后的所有元素向后移动一位 + for i in (nums.length - 1).downto(index + 1) + nums[i] = nums[i - 1] + end + + # 将 num 赋给 index 处的元素 + nums[index] = num +end + + +### 删除索引 index 处的元素 ### +def remove(nums, index) + # 把索引 index 之后的所有元素向前移动一位 + for i in index...(nums.length - 1) + nums[i] = nums[i + 1] + end +end + +### 遍历数组 ### +def traverse(nums) + count = 0 + + # 通过索引遍历数组 + for i in 0...nums.length + count += nums[i] + end + + # 直接遍历数组元素 + for num in nums + count += num + end +end + +### 在数组中查找指定元素 ### +def find(nums, target) + for i in 0...nums.length + return i if nums[i] == target + end + + -1 +end + + +### Driver Code ### +if __FILE__ == $0 + # 初始化数组 + arr = Array.new(5, 0) + puts "数组 arr = #{arr}" + nums = [1, 3, 2, 5, 4] + puts "数组 nums = #{nums}" + + # 随机访问 + random_num = random_access(nums) + puts "在 nums 中获取随机元素 #{random_num}" + + # 长度扩展 + nums = extend(nums, 3) + puts "将数组长度扩展至 8 ,得到 nums = #{nums}" + + # 插入元素 + insert(nums, 6, 3) + puts "在索引 3 处插入数字 6 ,得到 nums = #{nums}" + + # 删除元素 + remove(nums, 2) + puts "删除索引 2 处的元素,得到 nums = #{nums}" + + # 遍历数组 + traverse(nums) + + # 查找元素 + index = find(nums, 3) + puts "在 nums 中查找元素 3 ,得到索引 = #{index}" +end diff --git a/codes/ruby/chapter_array_and_linkedlist/linked_list.rb b/codes/ruby/chapter_array_and_linkedlist/linked_list.rb new file mode 100644 index 0000000000..14a4f6cf26 --- /dev/null +++ b/codes/ruby/chapter_array_and_linkedlist/linked_list.rb @@ -0,0 +1,83 @@ +=begin +File: linked_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/print_util' + +### 在链表的节点 n0 之后插入节点 _p ### +# Ruby 的 `p` 是一个内置函数, `P` 是一个常量,所以可以使用 `_p` 代替 +def insert(n0, _p) + n1 = n0.next + _p.next = n1 + n0.next = _p +end + +### 删除链表的节点 n0 之后的首个节点 ### +def remove(n0) + return if n0.next.nil? + + # n0 -> remove_node -> n1 + remove_node = n0.next + n1 = remove_node.next + n0.next = n1 +end + +### 访问链表中索引为 index 的节点 ### +def access(head, index) + for i in 0...index + return nil if head.nil? + head = head.next + end + + head +end + +### 在链表中查找值为 target 的首个节点 ### +def find(head, target) + index = 0 + while head + return index if head.val == target + head = head.next + index += 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化链表 + # 初始化各个节点 + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # 构建节点之间的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + puts "初始化的链表为" + print_linked_list(n0) + + # 插入节点 + insert(n0, ListNode.new(0)) + print_linked_list n0 + + # 删除节点 + remove(n0) + puts "删除节点后的链表为" + print_linked_list(n0) + + # 访问节点 + node = access(n0, 3) + puts "链表中索引 3 处的节点的值 = #{node.val}" + + # 查找节点 + index = find(n0, 2) + puts "链表中值为 2 的节点的索引 = #{index}" +end diff --git a/codes/ruby/chapter_array_and_linkedlist/list.rb b/codes/ruby/chapter_array_and_linkedlist/list.rb new file mode 100644 index 0000000000..b22bba14ef --- /dev/null +++ b/codes/ruby/chapter_array_and_linkedlist/list.rb @@ -0,0 +1,60 @@ +=begin +File: list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化列表 + nums = [1, 3, 2, 5, 4] + puts "列表 nums = #{nums}" + + # 访问元素 + num = nums[1] + puts "访问索引 1 处的元素,得到 num = #{num}" + + # 更新元素 + nums[1] = 0 + puts "将索引 1 处的元素更新为 0 ,得到 nums = #{nums}" + + # 清空列表 + nums.clear + puts "清空列表后 nums = #{nums}" + + # 在尾部添加元素 + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + puts "添加元素后 nums = #{nums}" + + # 在中间插入元素 + nums.insert(3, 6) + puts "在索引 3 处插入元素 6 ,得到 nums = #{nums}" + + # 删除元素 + nums.delete_at(3) + puts "删除索引 3 处的元素,得到 nums = #{nums}" + + # 通过索引遍历列表 + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # 直接遍历列表元素 + count = 0 + nums.each do |x| + count += x + end + + # 拼接两个列表 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + puts "将列表 nums1 拼接到 nums 之后,得到 nums = #{nums}" + + nums = nums.sort { |a, b| a <=> b } + puts "排序列表后 nums = #{nums}" +end diff --git a/codes/ruby/chapter_array_and_linkedlist/my_list.rb b/codes/ruby/chapter_array_and_linkedlist/my_list.rb new file mode 100644 index 0000000000..c89f7cae3c --- /dev/null +++ b/codes/ruby/chapter_array_and_linkedlist/my_list.rb @@ -0,0 +1,132 @@ +=begin +File: my_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 列表类 ### +class MyList + attr_reader :size # 获取列表长度(当前元素数量) + attr_reader :capacity # 获取列表容量 + + ### 构造方法 ### + def initialize + @capacity = 10 + @size = 0 + @extend_ratio = 2 + @arr = Array.new(capacity) + end + + ### 访问元素 ### + def get(index) + # 索引如果越界,则抛出异常,下同 + raise IndexError, "索引越界" if index < 0 || index >= size + @arr[index] + end + + ### 访问元素 ### + def set(index, num) + raise IndexError, "索引越界" if index < 0 || index >= size + @arr[index] = num + end + + ### 在尾部添加元素 ### + def add(num) + # 元素数量超出容量时,触发扩容机制 + extend_capacity if size == capacity + @arr[size] = num + + # 更新元素数量 + @size += 1 + end + + ### 在中间插入元素 ### + def insert(index, num) + raise IndexError, "索引越界" if index < 0 || index >= size + + # 元素数量超出容量时,触发扩容机制 + extend_capacity if size == capacity + + # 将索引 index 以及之后的元素都向后移动一位 + for j in (size - 1).downto(index) + @arr[j + 1] = @arr[j] + end + @arr[index] = num + + # 更新元素数量 + @size += 1 + end + + ### 删除元素 ### + def remove(index) + raise IndexError, "索引越界" if index < 0 || index >= size + num = @arr[index] + + # 将将索引 index 之后的元素都向前移动一位 + for j in index...size + @arr[j] = @arr[j + 1] + end + + # 更新元素数量 + @size -= 1 + + # 返回被删除的元素 + num + end + + ### 列表扩容 ### + def extend_capacity + # 新建一个长度为原数组 extend_ratio 倍的新数组,并将原数组复制到新数组 + arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) + # 更新列表容量 + @capacity = arr.length + end + + ### 将列表转换为数组 ### + def to_array + sz = size + # 仅转换有效长度范围内的列表元素 + arr = Array.new(sz) + for i in 0...sz + arr[i] = get(i) + end + arr + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化列表 + nums = MyList.new + + # 在尾部添加元素 + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + puts "列表 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,长度 = #{nums.size}" + + # 在中间插入元素 + nums.insert(3, 6) + puts "在索引 3 处插入数字 6 ,得到 nums = #{nums.to_array}" + + # 删除元素 + nums.remove(3) + puts "删除索引 3 的元素,得到 nums = #{nums.to_array}" + + # 访问元素 + num = nums.get(1) + puts "访问索引 1 处的元素,得到 num = #{num}" + + # 更新元素 + nums.set(1, 0) + puts "将索引 1 处的元素更新为 0 ,得到 nums = #{nums.to_array}" + + # 测试扩容机制 + for i in 0...10 + # 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + nums.add(i) + end + puts "扩容后的列表 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,长度 = #{nums.size}" +end diff --git a/codes/ruby/chapter_backtracking/n_queens.rb b/codes/ruby/chapter_backtracking/n_queens.rb new file mode 100644 index 0000000000..b782f168e4 --- /dev/null +++ b/codes/ruby/chapter_backtracking/n_queens.rb @@ -0,0 +1,61 @@ +=begin +File: n_queens.rb +Created Time: 2024-05-21 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:n 皇后 ### +def backtrack(row, n, state, res, cols, diags1, diags2) + # 当放置完所有行时,记录解 + if row == n + res << state.map { |row| row.dup } + return + end + + # 遍历所有列 + for col in 0...n + # 计算该格子对应的主对角线和次对角线 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] + # 尝试:将皇后放置在该格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = true + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:将该格子恢复为空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = false + end + end +end + +### 求解 n 皇后 ### +def n_queens(n) + # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + state = Array.new(n) { Array.new(n, "#") } + cols = Array.new(n, false) # 记录列是否有皇后 + diags1 = Array.new(2 * n - 1, false) # 记录主对角线上是否有皇后 + diags2 = Array.new(2 * n - 1, false) # 记录次对角线上是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 4 + res = n_queens(n) + + puts "输入棋盘长宽为 #{n}" + puts "皇后放置方案共有 #{res.length} 种" + + for state in res + puts "--------------------" + for row in state + p row + end + end +end diff --git a/codes/ruby/chapter_backtracking/permutations_i.rb b/codes/ruby/chapter_backtracking/permutations_i.rb new file mode 100644 index 0000000000..e7866ddb96 --- /dev/null +++ b/codes/ruby/chapter_backtracking/permutations_i.rb @@ -0,0 +1,46 @@ +=begin +File: permutations_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:全排列 I ### +def backtrack(state, choices, selected, res) + # 当状态长度等于元素数量时,记录解 + if state.length == choices.length + res << state.dup + return + end + + # 遍历所有选择 + choices.each_with_index do |choice, i| + # 剪枝:不允许重复选择元素 + unless selected[i] + # 尝试:做出选择,更新状态 + selected[i] = true + state << choice + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.pop + end + end +end + +### 全排列 I ### +def permutations_i(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 3] + + res = permutations_i(nums) + + puts "输入数组 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/codes/ruby/chapter_backtracking/permutations_ii.rb b/codes/ruby/chapter_backtracking/permutations_ii.rb new file mode 100644 index 0000000000..ee3e84cd98 --- /dev/null +++ b/codes/ruby/chapter_backtracking/permutations_ii.rb @@ -0,0 +1,48 @@ +=begin +File: permutations_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:全排列 II ### +def backtrack(state, choices, selected, res) + # 当状态长度等于元素数量时,记录解 + if state.length == choices.length + res << state.dup + return + end + + # 遍历所有选择 + duplicated = Set.new + choices.each_with_index do |choice, i| + # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if !selected[i] && !duplicated.include?(choice) + # 尝试:做出选择,更新状态 + duplicated.add(choice) + selected[i] = true + state << choice + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.pop + end + end +end + +### 全排列 II ### +def permutations_ii(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 2] + + res = permutations_ii(nums) + + puts "输入数组 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb b/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb new file mode 100644 index 0000000000..5745e9cf54 --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb @@ -0,0 +1,33 @@ +=begin +File: preorder_traversal_i_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序遍历:例题一 ### +def pre_order(root) + return unless root + + # 记录解 + $res << root if root.val == 7 + + pre_order(root.left) + pre_order(root.right) +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 前序遍历 + $res = [] + pre_order(root) + + puts "\n输出所有值为 7 的节点" + p $res.map { |node| node.val } +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb b/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb new file mode 100644 index 0000000000..58a9e90735 --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb @@ -0,0 +1,41 @@ +=begin +File: preorder_traversal_ii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序遍历:例题二 ### +def pre_order(root) + return unless root + + # 尝试 + $path << root + + # 记录解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 前序遍历 + $path, $res = [], [] + pre_order(root) + + puts "\n输出所有根节点到节点 7 的路径" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb b/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb new file mode 100644 index 0000000000..9dcd79545e --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb @@ -0,0 +1,42 @@ +=begin +File: preorder_traversal_iii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序遍历:例题三 ### +def pre_order(root) + # 剪枝 + return if !root || root.val == 3 + + # 尝试 + $path.append(root) + + # 记录解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 前序遍历 + $path, $res = [], [] + pre_order(root) + + puts "\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb b/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb new file mode 100644 index 0000000000..7de0a82133 --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb @@ -0,0 +1,68 @@ +=begin +File: preorder_traversal_iii_template.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 判断当前状态是否为解 ### +def is_solution?(state) + !state.empty? && state.last.val == 7 +end + +### 记录解 ### +def record_solution(state, res) + res << state.dup +end + +### 判断在当前状态下,该选择是否合法 ### +def is_valid?(state, choice) + choice && choice.val != 3 +end + +### 更新状态 ### +def make_choice(state, choice) + state << choice +end + +### 恢复状态 ### +def undo_choice(state, choice) + state.pop +end + +### 回溯算法:例题三 ### +def backtrack(state, choices, res) + # 检查是否为解 + record_solution(state, res) if is_solution?(state) + + # 遍历所有选择 + for choice in choices + # 剪枝:检查选择是否合法 + if is_valid?(state, choice) + # 尝试:做出选择,更新状态 + make_choice(state, choice) + # 进行下一轮选择 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + end + end +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 回溯算法 + res = [] + backtrack([], [root], res) + + puts "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" + for path in res + p path.map { |node| node.val } + end +end diff --git a/codes/ruby/chapter_backtracking/subset_sum_i.rb b/codes/ruby/chapter_backtracking/subset_sum_i.rb new file mode 100644 index 0000000000..76eac9b6b7 --- /dev/null +++ b/codes/ruby/chapter_backtracking/subset_sum_i.rb @@ -0,0 +1,47 @@ +=begin +File: subset_sum_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:子集和 I ### +def backtrack(state, target, choices, start, res) + # 子集和等于 target 时,记录解 + if target.zero? + res << state.dup + return + end + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in start...choices.length + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + break if target - choices[i] < 0 + # 尝试:做出选择,更新 target, start + state << choices[i] + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop + end +end + +### 求解子集和 I ### +def subset_sum_i(nums, target) + state = [] # 状态(子集) + nums.sort! # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + puts "输入数组 = #{nums}, target = #{target}" + puts "所有和等于 #{target} 的子集 res = #{res}" +end diff --git a/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb b/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb new file mode 100644 index 0000000000..276cb5394a --- /dev/null +++ b/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb @@ -0,0 +1,46 @@ +=begin +File: subset_sum_i_naive.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:子集和 I ### +def backtrack(state, target, total, choices, res) + # 子集和等于 target 时,记录解 + if total == target + res << state.dup + return + end + + # 遍历所有选择 + for i in 0...choices.length + # 剪枝:若子集和超过 target ,则跳过该选择 + next if total + choices[i] > target + # 尝试:做出选择,更新元素和 total + state << choices[i] + # 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop + end +end + +### 求解子集和 I(包含重复子集)### +def subset_sum_i_naive(nums, target) + state = [] # 状态(子集) + total = 0 # 子集和 + res = [] # 结果列表(子集列表) + backtrack(state, target, total, nums, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + puts "输入数组 nums = #{nums}, target = #{target}" + puts "所有和等于 #{target} 的子集 res = #{res}" + puts "请注意,该方法输出的结果包含重复集合" +end diff --git a/codes/ruby/chapter_backtracking/subset_sum_ii.rb b/codes/ruby/chapter_backtracking/subset_sum_ii.rb new file mode 100644 index 0000000000..a25861bee2 --- /dev/null +++ b/codes/ruby/chapter_backtracking/subset_sum_ii.rb @@ -0,0 +1,51 @@ +=begin +File: subset_sum_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:子集和 II ### +def backtrack(state, target, choices, start, res) + # 子集和等于 target 时,记录解 + if target.zero? + res << state.dup + return + end + + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + # 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in start...choices.length + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + break if target - choices[i] < 0 + # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + next if i > start && choices[i] == choices[i - 1] + # 尝试:做出选择,更新 target, start + state << choices[i] + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop + end +end + +### 求解子集和 II ### +def subset_sum_ii(nums, target) + state = [] # 状态(子集) + nums.sort! # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + puts "输入数组 nums = #{nums}, target = #{target}" + puts "所有和等于 #{target} 的子集 res = #{res}" +end diff --git a/codes/ruby/chapter_computational_complexity/iteration.rb b/codes/ruby/chapter_computational_complexity/iteration.rb new file mode 100644 index 0000000000..5ce5743251 --- /dev/null +++ b/codes/ruby/chapter_computational_complexity/iteration.rb @@ -0,0 +1,79 @@ +=begin +File: iteration.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) +=end + +### for 循环 ### +def for_loop(n) + res = 0 + + # 循环求和 1, 2, ..., n-1, n + for i in 1..n + res += i + end + + res +end + +### while 循环 ### +def while_loop(n) + res = 0 + i = 1 # 初始化条件变量 + + # 循环求和 1, 2, ..., n-1, n + while i <= n + res += i + i += 1 # 更新条件变量 + end + + res +end + +### while 循环(两次更新)### +def while_loop_ii(n) + res = 0 + i = 1 # 初始化条件变量 + + # 循环求和 1, 4, 10, ... + while i <= n + res += i + # 更新条件变量 + i += 1 + i *= 2 + end + + res +end + +### 双层 for 循环 ### +def nested_for_loop(n) + res = "" + + # 循环 i = 1, 2, ..., n-1, n + for i in 1..n + # 循环 j = 1, 2, ..., n-1, n + for j in 1..n + res += "(#{i}, #{j}), " + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = for_loop(n) + puts "\nfor 循环的求和结果 res = #{res}" + + res = while_loop(n) + puts "\nwhile 循环的求和结果 res = #{res}" + + res = while_loop_ii(n) + puts "\nwhile 循环(两次更新)求和结果 res = #{res}" + + res = nested_for_loop(n) + puts "\n双层 for 循环的遍历结果 #{res}" +end diff --git a/codes/ruby/chapter_computational_complexity/recursion.rb b/codes/ruby/chapter_computational_complexity/recursion.rb new file mode 100644 index 0000000000..4a83f9643d --- /dev/null +++ b/codes/ruby/chapter_computational_complexity/recursion.rb @@ -0,0 +1,70 @@ +=begin +File: recursion.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 递归 ### +def recur(n) + # 终止条件 + return 1 if n == 1 + # 递:递归调用 + res = recur(n - 1) + # 归:返回结果 + n + res +end + +### 使用迭代模拟递归 ### +def for_loop_recur(n) + # 使用一个显式的栈来模拟系统调用栈 + stack = [] + res = 0 + + # 递:递归调用 + for i in n.downto(0) + # 通过“入栈操作”模拟“递” + stack << i + end + # 归:返回结果 + while !stack.empty? + res += stack.pop + end + + # res = 1+2+3+...+n + res +end + +### 尾递归 ### +def tail_recur(n, res) + # 终止条件 + return res if n == 0 + # 尾递归调用 + tail_recur(n - 1, res + n) +end + +### 斐波那契数列:递归 ### +def fib(n) + # 终止条件 f(1) = 0, f(2) = 1 + return n - 1 if n == 1 || n == 2 + # 递归调用 f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # 返回结果 f(n) + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = recur(n) + puts "\n递归函数的求和结果 res = #{res}" + + res = for_loop_recur(n) + puts "\n使用迭代模拟递归求和结果 res = #{res}" + + res = tail_recur(n, 0) + puts "\n尾递归函数的求和结果 res = #{res}" + + res = fib(n) + puts "\n斐波那契数列的第 #{n} 项为 #{res}" +end diff --git a/codes/ruby/chapter_computational_complexity/space_complexity.rb b/codes/ruby/chapter_computational_complexity/space_complexity.rb new file mode 100644 index 0000000000..e5fba2913a --- /dev/null +++ b/codes/ruby/chapter_computational_complexity/space_complexity.rb @@ -0,0 +1,92 @@ +=begin +File: space_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 函数 ### +def function + # 执行某些操作 + 0 +end + +### 常数阶 ### +def constant(n) + # 常量、变量、对象占用 O(1) 空间 + a = 0 + nums = [0] * 10000 + node = ListNode.new + + # 循环中的变量占用 O(1) 空间 + (0...n).each { c = 0 } + # 循环中的函数占用 O(1) 空间 + (0...n).each { function } +end + +### 线性阶 ### +def linear(n) + # 长度为 n 的列表占用 O(n) 空间 + nums = Array.new(n, 0) + + # 长度为 n 的哈希表占用 O(n) 空间 + hmap = {} + for i in 0...n + hmap[i] = i.to_s + end +end + +### 线性阶(递归实现)### +def linear_recur(n) + puts "递归 n = #{n}" + return if n == 1 + linear_recur(n - 1) +end + +### 平方阶 ### +def quadratic(n) + # 二维列表占用 O(n^2) 空间 + Array.new(n) { Array.new(n, 0) } +end + +### 平方阶(递归实现)### +def quadratic_recur(n) + return 0 unless n > 0 + + # 数组 nums 长度为 n, n-1, ..., 2, 1 + nums = Array.new(n, 0) + quadratic_recur(n - 1) +end + +### 指数阶(建立满二叉树)### +def build_tree(n) + return if n == 0 + + TreeNode.new.tap do |root| + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + end +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + # 常数阶 + constant(n) + + # 线性阶 + linear(n) + linear_recur(n) + + # 平方阶 + quadratic(n) + quadratic_recur(n) + + # 指数阶 + root = build_tree(n) + print_tree(root) +end diff --git a/codes/ruby/chapter_computational_complexity/time_complexity.rb b/codes/ruby/chapter_computational_complexity/time_complexity.rb new file mode 100644 index 0000000000..cf41db88b8 --- /dev/null +++ b/codes/ruby/chapter_computational_complexity/time_complexity.rb @@ -0,0 +1,165 @@ +=begin +File: time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 常数阶 ### +def constant(n) + count = 0 + size = 100000 + + (0...size).each { count += 1 } + + count +end + +### 线性阶 ### +def linear(n) + count = 0 + (0...n).each { count += 1 } + count +end + +### 线性阶(遍历数组)### +def array_traversal(nums) + count = 0 + + # 循环次数与数组长度成正比 + for num in nums + count += 1 + end + + count +end + +### 平方阶 ### +def quadratic(n) + count = 0 + + # 循环次数与数据大小 n 成平方关系 + for i in 0...n + for j in 0...n + count += 1 + end + end + + count +end + +### 平方阶(冒泡排序)### +def bubble_sort(nums) + count = 0 # 计数器 + + # 外循环:未排序区间为 [0, i] + for i in (nums.length - 1).downto(0) + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in 0...i + if nums[j] > nums[j + 1] + # 交换 nums[j] 与 nums[j + 1] + tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 元素交换包含 3 个单元操作 + end + end + end + + count +end + +### 指数阶(循环实现)### +def exponential(n) + count, base = 0, 1 + + # 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + (0...n).each do + (0...base).each { count += 1 } + base *= 2 + end + + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +end + +### 指数阶(递归实现)### +def exp_recur(n) + return 1 if n == 1 + exp_recur(n - 1) + exp_recur(n - 1) + 1 +end + +### 对数阶(循环实现)### +def logarithmic(n) + count = 0 + + while n > 1 + n /= 2 + count += 1 + end + + count +end + +### 对数阶(递归实现)### +def log_recur(n) + return 0 unless n > 1 + log_recur(n / 2) + 1 +end + +### 线性对数阶 ### +def linear_log_recur(n) + return 1 unless n > 1 + + count = linear_log_recur(n / 2) + linear_log_recur(n / 2) + (0...n).each { count += 1 } + + count +end + +### 阶乘阶(递归实现)### +def factorial_recur(n) + return 1 if n == 0 + + count = 0 + # 从 1 个分裂出 n 个 + (0...n).each { count += factorial_recur(n - 1) } + + count +end + +### Driver Code ### +if __FILE__ == $0 + # 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + n = 8 + puts "输入数据大小 n = #{n}" + + count = constant(n) + puts "常数阶的操作数量 = #{count}" + + count = linear(n) + puts "线性阶的操作数量 = #{count}" + count = array_traversal(Array.new(n, 0)) + puts "线性阶(遍历数组)的操作数量 = #{count}" + + count = quadratic(n) + puts "平方阶的操作数量 = #{count}" + nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] + count = bubble_sort(nums) + puts "平方阶(冒泡排序)的操作数量 = #{count}" + + count = exponential(n) + puts "指数阶(循环实现)的操作数量 = #{count}" + count = exp_recur(n) + puts "指数阶(递归实现)的操作数量 = #{count}" + + count = logarithmic(n) + puts "对数阶(循环实现)的操作数量 = #{count}" + count = log_recur(n) + puts "对数阶(递归实现)的操作数量 = #{count}" + + count = linear_log_recur(n) + puts "线性对数阶(递归实现)的操作数量 = #{count}" + + count = factorial_recur(n) + puts "阶乘阶(递归实现)的操作数量 = #{count}" +end diff --git a/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb b/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb new file mode 100644 index 0000000000..206dc265ec --- /dev/null +++ b/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb @@ -0,0 +1,35 @@ +=begin +File: worst_best_time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 ### +def random_numbers(n) + # 生成数组 nums =: 1, 2, 3, ..., n + nums = Array.new(n) { |i| i + 1 } + # 随机打乱数组元素 + nums.shuffle! +end + +### 查找数组 nums 中数字 1 所在索引 ### +def find_one(nums) + for i in 0...nums.length + # 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + # 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + return i if nums[i] == 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + for i in 0...10 + n = 100 + nums = random_numbers(n) + index = find_one(nums) + puts "\n数组 [ 1, 2, ..., n ] 被打乱后 = #{nums}" + puts "数字 1 的索引为 #{index}" + end +end diff --git a/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb b/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb new file mode 100644 index 0000000000..214a7cbf52 --- /dev/null +++ b/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb @@ -0,0 +1,42 @@ +=begin +File: binary_search_recur.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二分查找:问题 f(i, j) ### +def dfs(nums, target, i, j) + # 若区间为空,代表无目标元素,则返回 -1 + return -1 if i > j + + # 计算中点索引 m + m = (i + j) / 2 + + if nums[m] < target + # 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j) + elsif nums[m] > target + # 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1) + else + # 找到目标元素,返回其索引 + return m + end +end + +### 二分查找 ### +def binary_search(nums, target) + n = nums.length + # 求解问题 f(0, n-1) + dfs(nums, target, 0, n - 1) +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分查找(双闭区间) + index = binary_search(nums, target) + puts "目标元素 6 的索引 = #{index}" +end diff --git a/codes/ruby/chapter_divide_and_conquer/build_tree.rb b/codes/ruby/chapter_divide_and_conquer/build_tree.rb new file mode 100644 index 0000000000..3d562c8202 --- /dev/null +++ b/codes/ruby/chapter_divide_and_conquer/build_tree.rb @@ -0,0 +1,46 @@ +=begin +File: build_tree.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 构建二叉树:分治 ### +def dfs(preorder, inorder_map, i, l, r) + # 子树区间为空时终止 + return if r - l < 0 + + # 初始化根节点 + root = TreeNode.new(preorder[i]) + # 查询 m ,从而划分左右子树 + m = inorder_map[preorder[i]] + # 子问题:构建左子树 + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # 子问题:构建右子树 + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + + # 返回根节点 + root +end + +### 构建二叉树 ### +def build_tree(preorder, inorder) + # 初始化哈希表,存储 inorder 元素到索引的映射 + inorder_map = {} + inorder.each_with_index { |val, i| inorder_map[val] = i } + dfs(preorder, inorder_map, 0, 0, inorder.length - 1) +end + +### Driver Code ### +if __FILE__ == $0 + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + puts "前序遍历 = #{preorder}" + puts "中序遍历 = #{inorder}" + + root = build_tree(preorder, inorder) + puts "构建的二叉树为:" + print_tree(root) +end diff --git a/codes/ruby/chapter_divide_and_conquer/hanota.rb b/codes/ruby/chapter_divide_and_conquer/hanota.rb new file mode 100644 index 0000000000..456ccbd4e4 --- /dev/null +++ b/codes/ruby/chapter_divide_and_conquer/hanota.rb @@ -0,0 +1,55 @@ +=begin +File: hanota.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 移动一个圆盘 ### +def move(src, tar) + # 从 src 顶部拿出一个圆盘 + pan = src.pop + # 将圆盘放入 tar 顶部 + tar << pan +end + +### 求解汉诺塔问题 f(i) ### +def dfs(i, src, buf, tar) + # 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1 + move(src, tar) + return + end + + # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf) + # 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar) + # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar) +end + +### 求解汉诺塔问题 ### +def solve_hanota(_A, _B, _C) + n = _A.length + # 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, _A, _B, _C) +end + +### Driver Code ### +if __FILE__ == $0 + # 列表尾部是柱子顶部 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + puts "初始状态下:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" + + solve_hanota(A, B, C) + + puts "圆盘移动完成后:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" +end diff --git a/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb b/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb new file mode 100644 index 0000000000..ad0e02bef7 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb @@ -0,0 +1,37 @@ +=begin +File: climbing_stairs_backtrack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯 ### +def backtrack(choices, state, n, res) + # 当爬到第 n 阶时,方案数量加 1 + res[0] += 1 if state == n + # 遍历所有选择 + for choice in choices + # 剪枝:不允许越过第 n 阶 + next if state + choice > n + + # 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res) + end + # 回退 +end + +### 爬楼梯:回溯 ### +def climbing_stairs_backtrack(n) + choices = [1, 2] # 可选择向上爬 1 阶或 2 阶 + state = 0 # 从第 0 阶开始爬 + res = [0] # 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res) + res.first +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_backtrack(n) + puts "爬 #{n} 阶楼梯共有 #{res} 种方案" +end diff --git a/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb b/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb new file mode 100644 index 0000000000..4d6bf065f5 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb @@ -0,0 +1,31 @@ +=begin +File: climbing_stairs_constraint_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 带约束爬楼梯:动态规划 ### +def climbing_stairs_constraint_dp(n) + return 1 if n == 1 || n == 2 + + # 初始化 dp 表,用于存储子问题的解 + dp = Array.new(n + 1) { Array.new(3, 0) } + # 初始状态:预设最小子问题的解 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 状态转移:从较小子问题逐步求解较大子问题 + for i in 3...(n + 1) + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + end + + dp[n][1] + dp[n][2] +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_constraint_dp(n) + puts "爬 #{n} 阶楼梯共有 #{res} 种方案" +end diff --git a/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb b/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb new file mode 100644 index 0000000000..ed9b2b39f9 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb @@ -0,0 +1,26 @@ +=begin +File: climbing_stairs_dfs.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 搜索 ### +def dfs(i) + # 已知 dp[1] 和 dp[2] ,返回之 + return i if i == 1 || i == 2 + # dp[i] = dp[i-1] + dp[i-2] + dfs(i - 1) + dfs(i - 2) +end + +### 爬楼梯:搜索 ### +def climbing_stairs_dfs(n) + dfs(n) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs(n) + puts "爬 #{n} 阶楼梯共有 #{res} 种方案" +end diff --git a/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb b/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb new file mode 100644 index 0000000000..502e9563e8 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb @@ -0,0 +1,33 @@ +=begin +File: climbing_stairs_dfs_mem.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 记忆化搜索 ### +def dfs(i, mem) + # 已知 dp[1] 和 dp[2] ,返回之 + return i if i == 1 || i == 2 + # 若存在记录 dp[i] ,则直接返回之 + return mem[i] if mem[i] != -1 + + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # 记录 dp[i] + mem[i] = count +end + +### 爬楼梯:记忆化搜索 ### +def climbing_stairs_dfs_mem(n) + # mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + mem = Array.new(n + 1, -1) + dfs(n, mem) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs_mem(n) + puts "爬 #{n} 阶楼梯共有 #{res} 种方案" +end diff --git a/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb b/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb new file mode 100644 index 0000000000..a88abb4d14 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb @@ -0,0 +1,40 @@ +=begin +File: climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 爬楼梯:动态规划 ### +def climbing_stairs_dp(n) + return n if n == 1 || n == 2 + + # 初始化 dp 表,用于存储子问题的解 + dp = Array.new(n + 1, 0) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = 1, 2 + # 状态转移:从较小子问题逐步求解较大子问题 + (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } + + dp[n] +end + +### 爬楼梯:空间优化后的动态规划 ### +def climbing_stairs_dp_comp(n) + return n if n == 1 || n == 2 + + a, b = 1, 2 + (3...(n + 1)).each { a, b = b, a + b } + + b +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dp(n) + puts "爬 #{n} 阶楼梯共有 #{res} 种方案" + + res = climbing_stairs_dp_comp(n) + puts "爬 #{n} 阶楼梯共有 #{res} 种方案" +end diff --git a/codes/ruby/chapter_dynamic_programming/coin_change.rb b/codes/ruby/chapter_dynamic_programming/coin_change.rb new file mode 100644 index 0000000000..5135c0f5cb --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/coin_change.rb @@ -0,0 +1,65 @@ +=begin +File: coin_change.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零钱兑换:动态规划 ### +def coin_change_dp(coins, amt) + n = coins.length + _MAX = amt + 1 + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 状态转移:首行首列 + (1...(amt + 1)).each { |a| dp[0][a] = _MAX } + # 状态转移:其余行和列 + for i in 1...(n + 1) + for a in 1...(amt + 1) + 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]] + 1].min + end + end + end + dp[n][amt] != _MAX ? dp[n][amt] : -1 +end + +### 零钱兑换:空间优化后的动态规划 ### +def coin_change_dp_comp(coins, amt) + n = coins.length + _MAX = amt + 1 + # 初始化 dp 表 + dp = Array.new(amt + 1, _MAX) + dp[0] = 0 + # 状态转移 + for i in 1...(n + 1) + # 正序遍历 + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + else + # 不选和选硬币 i 这两种方案的较小值 + dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min + end + end + end + dp[amt] != _MAX ? dp[amt] : -1 +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 4 + + # 动态规划 + res = coin_change_dp(coins, amt) + puts "凑到目标金额所需的最少硬币数量为 #{res}" + + # 空间优化后的动态规划 + res = coin_change_dp_comp(coins, amt) + puts "凑到目标金额所需的最少硬币数量为 #{res}" +end diff --git a/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb b/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb new file mode 100644 index 0000000000..18cd0475b8 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb @@ -0,0 +1,63 @@ +=begin +File: coin_change_ii.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零钱兑换 II:动态规划 ### +def coin_change_ii_dp(coins, amt) + n = coins.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 初始化首列 + (0...(n + 1)).each { |i| dp[i][0] = 1 } + # 状态转移 + for i in 1...(n + 1) + for a in 1...(amt + 1) + 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]] + end + end + end + dp[n][amt] +end + +### 零钱兑换 II:空间优化后的动态规划 ### +def coin_change_ii_dp_comp(coins, amt) + n = coins.length + # 初始化 dp 表 + dp = Array.new(amt + 1, 0) + dp[0] = 1 + # 状态转移 + for i in 1...(n + 1) + # 正序遍历 + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + else + # 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + end + end + end + dp[amt] +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 5 + + # 动态规划 + res = coin_change_ii_dp(coins, amt) + puts "凑出目标金额的硬币组合数量为 #{res}" + + # 空间优化后的动态规划 + res = coin_change_ii_dp_comp(coins, amt) + puts "凑出目标金额的硬币组合数量为 #{res}" +end diff --git a/codes/ruby/chapter_dynamic_programming/edit_distance.rb b/codes/ruby/chapter_dynamic_programming/edit_distance.rb new file mode 100644 index 0000000000..67c6da9168 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/edit_distance.rb @@ -0,0 +1,115 @@ +=begin +File: edit_distance.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 编辑距离:暴力搜索 ### +def edit_distance_dfs(s, t, i, j) + # 若 s 和 t 都为空,则返回 0 + return 0 if i == 0 && j == 0 + # 若 s 为空,则返回 t 长度 + return j if i == 0 + # 若 t 为空,则返回 s 长度 + return i if j == 0 + # 若两字符相等,则直接跳过此两字符 + return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # 返回最少编辑步数 + [insert, delete, replace].min + 1 +end + +def edit_distance_dfs_mem(s, t, mem, i, j) + # 若 s 和 t 都为空,则返回 0 + return 0 if i == 0 && j == 0 + # 若 s 为空,则返回 t 长度 + return j if i == 0 + # 若 t 为空,则返回 s 长度 + return i if j == 0 + # 若已有记录,则直接返回之 + return mem[i][j] if mem[i][j] != -1 + # 若两字符相等,则直接跳过此两字符 + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 记录并返回最少编辑步数 + mem[i][j] = [insert, delete, replace].min + 1 +end + +### 编辑距离:动态规划 ### +def edit_distance_dp(s, t) + n, m = s.length, t.length + dp = Array.new(n + 1) { Array.new(m + 1, 0) } + # 状态转移:首行首列 + (1...(n + 1)).each { |i| dp[i][0] = i } + (1...(m + 1)).each { |j| dp[0][j] = j } + # 状态转移:其余行和列 + for i in 1...(n + 1) + for j in 1...(m +1) + if s[i - 1] == t[j - 1] + # 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + else + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 + end + end + end + dp[n][m] +end + +### 编辑距离:空间优化后的动态规划 ### +def edit_distance_dp_comp(s, t) + n, m = s.length, t.length + dp = Array.new(m + 1, 0) + # 状态转移:首行 + (1...(m + 1)).each { |j| dp[j] = j } + # 状态转移:其余行 + for i in 1...(n + 1) + # 状态转移:首列 + leftup = dp.first # 暂存 dp[i-1, j-1] + dp[0] += 1 + # 状态转移:其余列 + for j in 1...(m + 1) + temp = dp[j] + if s[i - 1] == t[j - 1] + # 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + else + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = [dp[j - 1], dp[j], leftup].min + 1 + end + leftup = temp # 更新为下一轮的 dp[i-1, j-1] + end + end + dp[m] +end + +### Driver Code ### +if __FILE__ == $0 + s = 'bag' + t = 'pack' + n, m = s.length, t.length + + # 暴力搜索 + res = edit_distance_dfs(s, t, n, m) + puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" + + # 记忆化搜索 + mem = Array.new(n + 1) { Array.new(m + 1, -1) } + res = edit_distance_dfs_mem(s, t, mem, n, m) + puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" + + # 动态规划 + res = edit_distance_dp(s, t) + puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" + + # 空间优化后的动态规划 + res = edit_distance_dp_comp(s, t) + puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步" +end diff --git a/codes/ruby/chapter_dynamic_programming/knapsack.rb b/codes/ruby/chapter_dynamic_programming/knapsack.rb new file mode 100644 index 0000000000..c776e7d590 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/knapsack.rb @@ -0,0 +1,99 @@ +=begin +File: knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 0-1 背包:暴力搜索 ### +def knapsack_dfs(wgt, val, i, c) + # 若已选完所有物品或背包无剩余容量,则返回价值 0 + return 0 if i == 0 || c == 0 + # 若超过背包容量,则只能选择不放入背包 + return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 返回两种方案中价值更大的那一个 + [no, yes].max +end + +### 0-1 背包:记忆化搜索 ### +def knapsack_dfs_mem(wgt, val, mem, i, c) + # 若已选完所有物品或背包无剩余容量,则返回价值 0 + return 0 if i == 0 || c == 0 + # 若已有记录,则直接返回 + return mem[i][c] if mem[i][c] != -1 + # 若超过背包容量,则只能选择不放入背包 + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 记录并返回两种方案中价值更大的那一个 + mem[i][c] = [no, yes].max +end + +### 0-1 背包:动态规划 ### +def knapsack_dp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 状态转移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +### 0-1 背包:空间优化后的动态规划 ### +def knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(cap + 1, 0) + # 状态转移 + for i in 1...(n + 1) + # 倒序遍历 + for c in cap.downto(1) + if wgt[i - 1] > c + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else + # 不选和选物品 i 这两种方案的较大值 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 暴力搜索 + res = knapsack_dfs(wgt, val, n, cap) + puts "不超过背包容量的最大物品价值为 #{res}" + + # 记忆化搜索 + mem = Array.new(n + 1) { Array.new(cap + 1, -1) } + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + puts "不超过背包容量的最大物品价值为 #{res}" + + # 动态规划 + res = knapsack_dp(wgt, val, cap) + puts "不超过背包容量的最大物品价值为 #{res}" + + # 空间优化后的动态规划 + res = knapsack_dp_comp(wgt, val, cap) + puts "不超过背包容量的最大物品价值为 #{res}" +end diff --git a/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb b/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb new file mode 100644 index 0000000000..f570d2e7b5 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb @@ -0,0 +1,39 @@ +=begin +File: min_cost_climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 爬楼梯最小代价:动态规划 ### +def min_cost_climbing_stairs_dp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + # 初始化 dp 表,用于存储子问题的解 + dp = Array.new(n + 1, 0) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = cost[1], cost[2] + # 状态转移:从较小子问题逐步求解较大子问题 + (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } + dp[n] +end + +# 爬楼梯最小代价:空间优化后的动态规划 +def min_cost_climbing_stairs_dp_comp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + a, b = cost[1], cost[2] + (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } + b +end + +### Driver Code ### +if __FILE__ == $0 + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + puts "输入楼梯的代价列表为 #{cost}" + + res = min_cost_climbing_stairs_dp(cost) + puts "爬完楼梯的最低代价为 #{res}" + + res = min_cost_climbing_stairs_dp_comp(cost) + puts "爬完楼梯的最低代价为 #{res}" +end diff --git a/codes/ruby/chapter_dynamic_programming/min_path_sum.rb b/codes/ruby/chapter_dynamic_programming/min_path_sum.rb new file mode 100644 index 0000000000..b6396dbca9 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/min_path_sum.rb @@ -0,0 +1,93 @@ +=begin +File: min_path_sum.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最小路径和:暴力搜索 ### +def min_path_sum_dfs(grid, i, j) + # 若为左上角单元格,则终止搜索 + return grid[i][j] if i == 0 && j == 0 + # 若行列索引越界,则返回 +∞ 代价 + return Float::INFINITY if i < 0 || j < 0 + # 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 返回从左上角到 (i, j) 的最小路径代价 + [left, up].min + grid[i][j] +end + +### 最小路径和:记忆化搜索 ### +def min_path_sum_dfs_mem(grid, mem, i, j) + # 若为左上角单元格,则终止搜索 + return grid[0][0] if i == 0 && j == 0 + # 若行列索引越界,则返回 +∞ 代价 + return Float::INFINITY if i < 0 || j < 0 + # 若已有记录,则直接返回 + return mem[i][j] if mem[i][j] != -1 + # 左边和上边单元格的最小路径代价 + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = [left, up].min + grid[i][j] +end + +### 最小路径和:动态规划 ### +def min_path_sum_dp(grid) + n, m = grid.length, grid.first.length + # 初始化 dp 表 + dp = Array.new(n) { Array.new(m, 0) } + dp[0][0] = grid[0][0] + # 状态转移:首行 + (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } + # 状态转移:首列 + (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } + # 状态转移:其余行和列 + for i in 1...n + for j in 1...m + dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] + end + end + dp[n -1][m -1] +end + +### 最小路径和:空间优化后的动态规划 ### +def min_path_sum_dp_comp(grid) + n, m = grid.length, grid.first.length + # 初始化 dp 表 + dp = Array.new(m, 0) + # 状态转移:首行 + dp[0] = grid[0][0] + (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } + # 状态转移:其余行 + for i in 1...n + # 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + # 状态转移:其余列 + (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } + end + dp[m - 1] +end + +### Driver Code ### +if __FILE__ == $0 + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = grid.length, grid.first.length + + # 暴力搜索 + res = min_path_sum_dfs(grid, n - 1, m - 1) + puts "从左上角到右下角的最小路径和为 #{res}" + + # 记忆化搜索 + mem = Array.new(n) { Array.new(m, - 1) } + res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) + puts "从左上角到右下角的最小路径和为 #{res}" + + # 动态规划 + res = min_path_sum_dp(grid) + puts "从左上角到右下角的最小路径和为 #{res}" + + # 空间优化后的动态规划 + res = min_path_sum_dp_comp(grid) + puts "从左上角到右下角的最小路径和为 #{res}" +end diff --git a/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb b/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb new file mode 100644 index 0000000000..f3d5534f08 --- /dev/null +++ b/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb @@ -0,0 +1,61 @@ +=begin +File: unbounded_knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 完全背包:动态规划 ### +def unbounded_knapsack_dp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 状态转移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +### 完全背包:空间优化后的动态规划 ##3 +def unbounded_knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(cap + 1, 0) + # 状态转移 + for i in 1...(n + 1) + # 正序遍历 + for c in 1...(cap + 1) + if wgt[i -1] > c + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else + # 不选和选物品 i 这两种方案的较大值 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 动态规划 + res = unbounded_knapsack_dp(wgt, val, cap) + puts "不超过背包容量的最大物品价值为 #{res}" + + # 空间优化后的动态规划 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + puts "不超过背包容量的最大物品价值为 #{res}" +end diff --git a/codes/ruby/chapter_graph/graph_adjacency_list.rb b/codes/ruby/chapter_graph/graph_adjacency_list.rb new file mode 100644 index 0000000000..d17e5fa8e4 --- /dev/null +++ b/codes/ruby/chapter_graph/graph_adjacency_list.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_list.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/vertex' + +### 基于邻接表实现的无向图类 ### +class GraphAdjList + attr_reader :adj_list + + ### 构造方法 ### + def initialize(edges) + # 邻接表,key:顶点,value:该顶点的所有邻接顶点 + @adj_list = {} + # 添加所有顶点和边 + for edge in edges + add_vertex(edge[0]) + add_vertex(edge[1]) + add_edge(edge[0], edge[1]) + end + end + + ### 获取顶点数量 ### + def size + @adj_list.length + end + + ### 添加边 ### + def add_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + @adj_list[vet1] << vet2 + @adj_list[vet2] << vet1 + end + + ### 删除边 ### + def remove_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + # 删除边 vet1 - vet2 + @adj_list[vet1].delete(vet2) + @adj_list[vet2].delete(vet1) + end + + ### 添加顶点 ### + def add_vertex(vet) + return if @adj_list.include?(vet) + + # 在邻接表中添加一个新链表 + @adj_list[vet] = [] + end + + ### 删除顶点 ### + def remove_vertex(vet) + raise ArgumentError unless @adj_list.include?(vet) + + # 在邻接表中删除顶点 vet 对应的链表 + @adj_list.delete(vet) + # 遍历其他顶点的链表,删除所有包含 vet 的边 + for vertex in @adj_list + @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) + end + end + + ### 打印邻接表 ### + def __print__ + puts '邻接表 =' + for vertex in @adj_list + tmp = @adj_list[vertex.first].map { |v| v.val } + puts "#{vertex.first.val}: #{tmp}," + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化无向图 + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList.new(edges) + puts "\n初始化后,图为" + graph.__print__ + + # 添加边 + # 顶点 1,2 即 v[0],v[2] + graph.add_edge(v[0], v[2]) + puts "\n添加边 1-2 后,图为" + graph.__print__ + + # 删除边 + # 顶点 1,3 即 v[0],v[1] + graph.remove_edge(v[0], v[1]) + puts "\n删除边 1-3 后,图为" + graph.__print__ + + # 添加顶点 + v5 = Vertex.new(6) + graph.add_vertex(v5) + puts "\n添加顶点 6 后,图为" + graph.__print__ + + # 删除顶点 + # 顶点 3 即 v[1] + graph.remove_vertex(v[1]) + puts "\n删除顶点 3 后,图为" + graph.__print__ +end diff --git a/codes/ruby/chapter_graph/graph_adjacency_matrix.rb b/codes/ruby/chapter_graph/graph_adjacency_matrix.rb new file mode 100644 index 0000000000..ecfc21de8d --- /dev/null +++ b/codes/ruby/chapter_graph/graph_adjacency_matrix.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_matrix.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +### 基于邻接矩阵实现的无向图类 ### +class GraphAdjMat + def initialize(vertices, edges) + ### 构造方法 ### + # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + @vertices = [] + # 邻接矩阵,行列索引对应“顶点索引” + @adj_mat = [] + # 添加顶点 + vertices.each { |val| add_vertex(val) } + # 添加边 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + edges.each { |e| add_edge(e[0], e[1]) } + end + + ### 获取顶点数量 ### + def size + @vertices.length + end + + ### 添加顶点 ### + def add_vertex(val) + n = size + # 向顶点列表中添加新顶点的值 + @vertices << val + # 在邻接矩阵中添加一行 + new_row = Array.new(n, 0) + @adj_mat << new_row + # 在邻接矩阵中添加一列 + @adj_mat.each { |row| row << 0 } + end + + ### 删除顶点 ### + def remove_vertex(index) + raise IndexError if index >= size + + # 在顶点列表中移除索引 index 的顶点 + @vertices.delete_at(index) + # 在邻接矩阵中删除索引 index 的行 + @adj_mat.delete_at(index) + # 在邻接矩阵中删除索引 index 的列 + @adj_mat.each { |row| row.delete_at(index) } + end + + ### 添加边 ### + def add_edge(i, j) + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + # 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + @adj_mat[i][j] = 1 + @adj_mat[j][i] = 1 + end + + ### 删除边 ### + def remove_edge(i, j) + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + @adj_mat[i][j] = 0 + @adj_mat[j][i] = 0 + end + + ### 打印邻接矩阵 ### + def __print__ + puts "顶点列表 = #{@vertices}" + puts '邻接矩阵 =' + print_matrix(@adj_mat) + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化无向图 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat.new(vertices, edges) + puts "\n初始化后,图为" + graph.__print__ + + # 添加边 + # 顶点 1, 2 的索引分别为 0, 2 + graph.add_edge(0, 2) + puts "\n添加边 1-2 后,图为" + graph.__print__ + + # 删除边 + # 定点 1, 3 的索引分别为 0, 1 + graph.remove_edge(0, 1) + puts "\n删除边 1-3 后,图为" + graph.__print__ + + # 添加顶点 + graph.add_vertex(6) + puts "\n添加顶点 6 后,图为" + graph.__print__ + + # 删除顶点 + # 顶点 3 的索引为 1 + graph.remove_vertex(1) + puts "\n删除顶点 3 后,图为" + graph.__print__ +end diff --git a/codes/ruby/chapter_graph/graph_bfs.rb b/codes/ruby/chapter_graph/graph_bfs.rb new file mode 100644 index 0000000000..064f1eaef2 --- /dev/null +++ b/codes/ruby/chapter_graph/graph_bfs.rb @@ -0,0 +1,61 @@ +=begin +File: graph_bfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +### 广度优先遍历 ### +def graph_bfs(graph, start_vet) + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希集合,用于记录已被访问过的顶点 + visited = Set.new([start_vet]) + # 队列用于实现 BFS + que = [start_vet] + # 以顶点 vet 为起点,循环直至访问完所有顶点 + while que.length > 0 + vet = que.shift # 队首顶点出队 + res << vet # 记录访问顶点 + # 遍历该顶点的所有邻接顶点 + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # 跳过已被访问的顶点 + que << adj_vet # 只入队未访问的顶点 + visited.add(adj_vet) # 标记该顶点已被访问 + end + end + # 返回顶点遍历序列 + res +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化无向图 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [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]], + ] + graph = GraphAdjList.new(edges) + puts "\n初始化后,图为" + graph.__print__ + + # 广度优先遍历 + res = graph_bfs(graph, v.first) + puts "\n广度优先便利(BFS)顶点序列为" + p vets_to_vals(res) +end diff --git a/codes/ruby/chapter_graph/graph_dfs.rb b/codes/ruby/chapter_graph/graph_dfs.rb new file mode 100644 index 0000000000..3c7a8001ba --- /dev/null +++ b/codes/ruby/chapter_graph/graph_dfs.rb @@ -0,0 +1,54 @@ +=begin +File: graph_dfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +### 深度优先遍历辅助函数 ### +def dfs(graph, visited, res, vet) + res << vet # 记录访问顶点 + visited.add(vet) # 标记该顶点已被访问 + # 遍历该顶点的所有邻接顶点 + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # 跳过已被访问的顶点 + # 递归访问邻接顶点 + dfs(graph, visited, res, adj_vet) + end +end + +### 深度优先遍历 ### +def graph_dfs(graph, start_vet) + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希集合,用于记录已被访问过的顶点 + visited = Set.new + dfs(graph, visited, res, start_vet) + res +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化无向图 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList.new(edges) + puts "\n初始化后,图为" + graph.__print__ + + # 深度优先遍历 + res = graph_dfs(graph, v[0]) + puts "\n深度优先遍历(DFS)顶点序列为" + p vets_to_vals(res) +end diff --git a/codes/ruby/chapter_greedy/coin_change_greedy.rb b/codes/ruby/chapter_greedy/coin_change_greedy.rb new file mode 100644 index 0000000000..7882b44b11 --- /dev/null +++ b/codes/ruby/chapter_greedy/coin_change_greedy.rb @@ -0,0 +1,50 @@ +=begin +File: coin_change_greedy.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零钱兑换:贪心 ### +def coin_change_greedy(coins, amt) + # 假设 coins 列表有序 + i = coins.length - 1 + count = 0 + # 循环进行贪心选择,直到无剩余金额 + while amt > 0 + # 找到小于且最接近剩余金额的硬币 + while i > 0 && coins[i] > amt + i -= 1 + end + # 选择 coins[i] + amt -= coins[i] + count += 1 + end + # 若未找到可行方案, 则返回 -1 + amt == 0 ? count : -1 +end + +### Driver Code ### +if __FILE__ == $0 + # 贪心:能够保证找到全局最优解 + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "凑到 #{amt} 所需的最少硬币数量为 #{res}" + + # 贪心:无法保证找到全局最优解 + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "凑到 #{amt} 所需的最少硬币数量为 #{res}" + puts "实际上需要的最少数量为 3 , 即 20 + 20 + 20" + + # 贪心:无法保证找到全局最优解 + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "凑到 #{amt} 所需的最少硬币数量为 #{res}" + puts "实际上需要的最少数量为 2 , 即 49 + 49" +end diff --git a/codes/ruby/chapter_greedy/fractional_knapsack.rb b/codes/ruby/chapter_greedy/fractional_knapsack.rb new file mode 100644 index 0000000000..5790f278da --- /dev/null +++ b/codes/ruby/chapter_greedy/fractional_knapsack.rb @@ -0,0 +1,51 @@ +=begin +File: fractional_knapsack.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 物品 ### +class Item + attr_accessor :w # 物品重量 + attr_accessor :v # 物品价值 + + def initialize(w, v) + @w = w + @v = v + end +end + +### 分数背包:贪心 ### +def fractional_knapsack(wgt, val, cap) + # 创建物品列表,包含两个属性:重量,价值 + items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } + # 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } + # 循环贪心选择 + res = 0 + for item in items + if item.w <= cap + # 若剩余容量充足,则将当前物品整个装进背包 + res += item.v + cap -= item.w + else + # 若剩余容量不足,则将当前物品的一部分装进背包 + res += (item.v.to_f / item.w) * cap + # 已无剩余容量,因此跳出循环 + break + end + end + res +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 贪心算法 + res = fractional_knapsack(wgt, val, cap) + puts "不超过背包容量的最大物品价值为 #{res}" +end diff --git a/codes/ruby/chapter_greedy/max_capacity.rb b/codes/ruby/chapter_greedy/max_capacity.rb new file mode 100644 index 0000000000..f08a09c9e4 --- /dev/null +++ b/codes/ruby/chapter_greedy/max_capacity.rb @@ -0,0 +1,37 @@ +=begin +File: max_capacity.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大容量:贪心 ### +def max_capacity(ht) + # 初始化 i, j,使其分列数组两端 + i, j = 0, ht.length - 1 + # 初始最大容量为 0 + res = 0 + + # 循环贪心选择,直至两板相遇 + while i < j + # 更新最大容量 + cap = [ht[i], ht[j]].min * (j - i) + res = [res, cap].max + # 向内移动短板 + if ht[i] < ht[j] + i += 1 + else + j -= 1 + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # 贪心算法 + res = max_capacity(ht) + puts "最大容量为 #{res}" +end diff --git a/codes/ruby/chapter_greedy/max_product_cutting.rb b/codes/ruby/chapter_greedy/max_product_cutting.rb new file mode 100644 index 0000000000..cee7523719 --- /dev/null +++ b/codes/ruby/chapter_greedy/max_product_cutting.rb @@ -0,0 +1,28 @@ +=begin +File: max_product_cutting.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大切分乘积:贪心 ### +def max_product_cutting(n) + # 当 n <= 3 时,必须切分出一个 1 + return 1 * (n - 1) if n <= 3 + # 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + a, b = n / 3, n % 3 + # 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return (3.pow(a - 1) * 2 * 2).to_i if b == 1 + # 当余数为 2 时,不做处理 + return (3.pow(a) * 2).to_i if b == 2 + # 当余数为 0 时,不做处理 + 3.pow(a).to_i +end + +### Driver Code ### +if __FILE__ == $0 + n = 58 + + # 贪心算法 + res = max_product_cutting(n) + puts "最大切分乘积为 #{res}" +end diff --git a/codes/ruby/chapter_hashing/array_hash_map.rb b/codes/ruby/chapter_hashing/array_hash_map.rb new file mode 100644 index 0000000000..d4dd4cb656 --- /dev/null +++ b/codes/ruby/chapter_hashing/array_hash_map.rb @@ -0,0 +1,121 @@ +=begin +File: array_hash_map.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 键值对 ### +class Pair + attr_accessor :key, :val + + def initialize(key, val) + @key = key + @val = val + end +end + +### 基于数组实现的哈希表 ### +class ArrayHashMap + ### 构造方法 ### + def initialize + # 初始化数组,包含 100 个桶 + @buckets = Array.new(100) + end + + ### 哈希函数 ### + def hash_func(key) + index = key % 100 + end + + ### 查询操作 ### + def get(key) + index = hash_func(key) + pair = @buckets[index] + + return if pair.nil? + pair.val + end + + ### 添加操作 ### + def put(key, val) + pair = Pair.new(key, val) + index = hash_func(key) + @buckets[index] = pair + end + + ### 删除操作 ### + def remove(key) + index = hash_func(key) + # 置为 nil ,代表删除 + @buckets[index] = nil + end + + ### 获取所有键值对 ### + def entry_set + result = [] + @buckets.each { |pair| result << pair unless pair.nil? } + result + end + + ### 获取所有键 ### + def key_set + result = [] + @buckets.each { |pair| result << pair.key unless pair.nil? } + result + end + + ### 获取所有值 ### + def value_set + result = [] + @buckets.each { |pair| result << pair.val unless pair.nil? } + result + end + + ### 打印哈希表 ### + def print + @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化哈希表 + hmap = ArrayHashMap.new + + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小啰") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鸭") + puts "\n添加完成后,哈希表为\nKey -> Value" + hmap.print + + # 查询操作 + # 向哈希表中输入键 key , 得到值 value + name = hmap.get(15937) + puts "\n输入学号 15937 ,查询到姓名 #{name}" + + # 删除操作 + # 在哈希表中删除值对 (key, value) + hmap.remove(10583) + puts "\n删除 10583 后,哈希表为\nKey -> Value" + hmap.print + + # 遍历哈希表 + puts "\n遍历键值对 Key->Value" + for pair in hmap.entry_set + puts "#{pair.key} -> #{pair.val}" + end + + puts "\n单独篇遍历键 Key" + for key in hmap.key_set + puts key + end + + puts "\n单独遍历值 Value" + for val in hmap.value_set + puts val + end +end diff --git a/codes/ruby/chapter_hashing/built_in_hash.rb b/codes/ruby/chapter_hashing/built_in_hash.rb new file mode 100644 index 0000000000..6136db50f7 --- /dev/null +++ b/codes/ruby/chapter_hashing/built_in_hash.rb @@ -0,0 +1,34 @@ +=begin +File: built_in_hash.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### Driver Code ### +if __FILE__ == $0 + num = 3 + hash_num = num.hash + puts "整数 #{num} 的哈希值为 #{hash_num}" + + bol = true + hash_bol = bol.hash + puts "布尔量 #{bol} 的哈希值为 #{hash_bol}" + + dec = 3.14159 + hash_dec = dec.hash + puts "小数 #{dec} 的哈希值为 #{hash_dec}" + + str = "Hello 算法" + hash_str = str.hash + puts "字符串 #{str} 的哈希值为 #{hash_str}" + + tup = [12836, '小哈'] + hash_tup = tup.hash + puts "元组 #{tup} 的哈希值为 #{hash_tup}" + + obj = ListNode.new(0) + hash_obj = obj.hash + puts "节点对象 #{obj} 的哈希值为 #{hash_obj}" +end diff --git a/codes/ruby/chapter_hashing/hash_map.rb b/codes/ruby/chapter_hashing/hash_map.rb new file mode 100644 index 0000000000..50358af21c --- /dev/null +++ b/codes/ruby/chapter_hashing/hash_map.rb @@ -0,0 +1,44 @@ +=begin +File: hash_map.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # 初始化哈希表 + hmap = {} + + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" + puts "\n添加完成后,哈希表为\nKey -> Value" + print_hash_map(hmap) + + # 查询操作 + # 向哈希表中输入键 key ,得到值 value + name = hmap[15937] + puts "\n输入学号 15937 ,查询到姓名 #{name}" + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hmap.delete(10583) + puts "\n删除 10583 后,哈希表为\nKey -> Value" + print_hash_map(hmap) + + # 遍历哈希表 + puts "\n遍历键值对 Key->Value" + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } + + puts "\n单独遍历键 Key" + hmap.keys.each { |key| puts key } + + puts "\n单独遍历值 Value" + hmap.values.each { |val| puts val } +end diff --git a/codes/ruby/chapter_hashing/hash_map_chaining.rb b/codes/ruby/chapter_hashing/hash_map_chaining.rb new file mode 100644 index 0000000000..bb26c412ce --- /dev/null +++ b/codes/ruby/chapter_hashing/hash_map_chaining.rb @@ -0,0 +1,128 @@ +=begin +File: hash_map_chaining.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +### 键式地址哈希表 ### +class HashMapChaining + ### 构造方法 ### + def initialize + @size = 0 # 键值对数量 + @capacity = 4 # 哈希表容量 + @load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 + @extend_ratio = 2 # 扩容倍数 + @buckets = Array.new(@capacity) { [] } # 桶数组 + end + + ### 哈希函数 ### + def hash_func(key) + key % @capacity + end + + ### 负载因子 ### + def load_factor + @size / @capacity + end + + ### 查询操作 ### + def get(key) + index = hash_func(key) + bucket = @buckets[index] + # 遍历桶,若找到 key ,则返回对应 val + for pair in bucket + return pair.val if pair.key == key + end + # 若未找到 key , 则返回 nil + nil + end + + ### 添加操作 ### + def put(key, val) + # 当负载因子超过阈值时,执行扩容 + extend if load_factor > @load_thres + index = hash_func(key) + bucket = @buckets[index] + # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in bucket + if pair.key == key + pair.val = val + return + end + end + # 若无该 key ,则将键值对添加至尾部 + pair = Pair.new(key, val) + bucket << pair + @size += 1 + end + + ### 删除操作 ### + def remove(key) + index = hash_func(key) + bucket = @buckets[index] + # 遍历桶,从中删除键值对 + for pair in bucket + if pair.key == key + bucket.delete(pair) + @size -= 1 + break + end + end + end + + ### 扩容哈希表 ### + def extend + # 暫存原哈希表 + buckets = @buckets + # 初始化扩容后的新哈希表 + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) { [] } + @size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for bucket in buckets + for pair in bucket + put(pair.key, pair.val) + end + end + end + + ### 打印哈希表 ### + def print + for bucket in @buckets + res = [] + for pair in bucket + res << "#{pair.key} -> #{pair.val}" + end + pp res + end + end +end + +### Driver Code ### +if __FILE__ == $0 + ### 初始化哈希表 + hashmap = HashMapChaining.new + + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小啰") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鸭") + puts "\n添加完成后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print + + # 查询操作 + # 向哈希表中输入键 key ,得到值 value + name = hashmap.get(13276) + puts "\n输入学号 13276 ,查询到姓名 #{name}" + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hashmap.remove(12836) + puts "\n删除 12836 后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print +end diff --git a/codes/ruby/chapter_hashing/hash_map_open_addressing.rb b/codes/ruby/chapter_hashing/hash_map_open_addressing.rb new file mode 100644 index 0000000000..d24d7182f3 --- /dev/null +++ b/codes/ruby/chapter_hashing/hash_map_open_addressing.rb @@ -0,0 +1,147 @@ +=begin +File: hash_map_open_addressing.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +### 开放寻址哈希表 ### +class HashMapOpenAddressing + TOMBSTONE = Pair.new(-1, '-1') # 删除标记 + + ### 构造方法 ### + def initialize + @size = 0 # 键值对数量 + @capacity = 4 # 哈希表容量 + @load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 + @extend_ratio = 2 # 扩容倍数 + @buckets = Array.new(@capacity) # 桶数组 + end + + ### 哈希函数 ### + def hash_func(key) + key % @capacity + end + + ### 负载因子 ### + def load_factor + @size / @capacity + end + + ### 搜索 key 对应的桶索引 ### + def find_bucket(key) + index = hash_func(key) + first_tombstone = -1 + # 线性探测,当遇到空桶时跳出 + while !@buckets[index].nil? + # 若遇到 key ,返回对应的桶索引 + if @buckets[index].key == key + # 若之前遇到了删除标记,则将键值对移动至该索引处 + if first_tombstone != -1 + @buckets[first_tombstone] = @buckets[index] + @buckets[index] = TOMBSTONE + return first_tombstone # 返回移动后的桶索引 + end + return index # 返回桶索引 + end + # 记录遇到的首个删除标记 + first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE + # 计算桶索引,越过尾部则返回头部 + index = (index + 1) % @capacity + end + # 若 key 不存在,则返回添加点的索引 + first_tombstone == -1 ? index : first_tombstone + end + + ### 查询操作 ### + def get(key) + # 搜索 key 对应的桶索引 + index = find_bucket(key) + # 若找到键值对,则返回对应 val + return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) + # 若键值对不存在,则返回 nil + nil + end + + ### 添加操作 ### + def put(key, val) + # 当负载因子超过阈值时,执行扩容 + extend if load_factor > @load_thres + # 搜索 key 对应的桶索引 + index = find_bucket(key) + # 若找到键值对,则覆盖 val 开返回 + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index].val = val + return + end + # 若键值对不存在,则添加该键值对 + @buckets[index] = Pair.new(key, val) + @size += 1 + end + + ### 删除操作 ### + def remove(key) + # 搜索 key 对应的桶索引 + index = find_bucket(key) + # 若找到键值对,则用删除标记覆盖它 + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index] = TOMBSTONE + @size -= 1 + end + end + + ### 扩容哈希表 ### + def extend + # 暂存原哈希表 + buckets_tmp = @buckets + # 初始化扩容后的新哈希表 + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) + @size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for pair in buckets_tmp + put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) + end + end + + ### 打印哈希表 ### + def print + for pair in @buckets + if pair.nil? + puts "Nil" + elsif pair == TOMBSTONE + puts "TOMBSTONE" + else + puts "#{pair.key} -> #{pair.val}" + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化哈希表 + hashmap = HashMapOpenAddressing.new + + # 添加操作 + # 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小啰") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鸭") + puts "\n添加完成后,哈希表为\nKey -> Value" + hashmap.print + + # 查询操作 + # 向哈希表中输入键 key ,得到值 val + name = hashmap.get(13276) + puts "\n输入学号 13276 ,查询到姓名 #{name}" + + # 删除操作 + # 在哈希表中删除键值对 (key, val) + hashmap.remove(16750) + puts "\n删除 16750 后,哈希表为\nKey -> Value" + hashmap.print +end diff --git a/codes/ruby/chapter_hashing/simple_hash.rb b/codes/ruby/chapter_hashing/simple_hash.rb new file mode 100644 index 0000000000..d24c59e360 --- /dev/null +++ b/codes/ruby/chapter_hashing/simple_hash.rb @@ -0,0 +1,62 @@ +=begin +File: simple_hash.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 加法哈希 ### +def add_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash += c.ord } + + hash % modulus +end + +### 乘法哈希 ### +def mul_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = 31 * hash + c.ord } + + hash % modulus +end + +### 异或哈希 ### +def xor_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash ^= c.ord } + + hash % modulus +end + +### 旋转哈希 ### +def rot_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } + + hash % modulus +end + +### Driver Code ### +if __FILE__ == $0 + key = "Hello 算法" + + hash = add_hash(key) + puts "加法哈希值为 #{hash}" + + hash = mul_hash(key) + puts "乘法哈希值为 #{hash}" + + hash = xor_hash(key) + puts "异或哈希值为 #{hash}" + + hash = rot_hash(key) + puts "旋转哈希值为 #{hash}" +end diff --git a/codes/ruby/chapter_heap/my_heap.rb b/codes/ruby/chapter_heap/my_heap.rb new file mode 100644 index 0000000000..f6a60461e8 --- /dev/null +++ b/codes/ruby/chapter_heap/my_heap.rb @@ -0,0 +1,147 @@ +=begin +File: my_heap.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/print_util' + +### 大顶堆 ### +class MaxHeap + attr_reader :max_heap + + ### 构造方法,根据输入列表建堆 ### + def initialize(nums) + # 将列表元素原封不动添加进堆 + @max_heap = nums + # 堆化除叶节点以外的其他所有节点 + parent(size - 1).downto(0) do |i| + sift_down(i) + end + end + + ### 获取左子节点的索引 ### + def left(i) + 2 * i + 1 + end + + ### 获取右子节点的索引 ### + def right(i) + 2 * i + 2 + end + + ### 获取父节点的索引 ### + def parent(i) + (i - 1) / 2 # 向下整除 + end + + ### 交换元素 ### + def swap(i, j) + @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] + end + + ### 获取堆大小 ### + def size + @max_heap.length + end + + ### 判断堆是否为空 ### + def is_empty? + size == 0 + end + + ### 访问堆顶元素 ### + def peek + @max_heap[0] + end + + ### 元素入堆 ### + def push(val) + # 添加节点 + @max_heap << val + # 从底至顶堆化 + sift_up(size - 1) + end + + ### 从节点 i 开始,从底至顶堆化 ### + def sift_up(i) + loop do + # 获取节点 i 的父节点 + p = parent(i) + # 当“越过根节点”或“节点无须修复”时,结束堆化 + break if p < 0 || @max_heap[i] <= @max_heap[p] + # 交换两节点 + swap(i, p) + # 循环向上堆化 + i = p + end + end + + ### 元素出堆 ### + def pop + # 判空处理 + raise IndexError, "堆为空" if is_empty? + # 交换根节点与最右叶节点(交换首元素与尾元素) + swap(0, size - 1) + # 删除节点 + val = @max_heap.pop + # 从顶至底堆化 + sift_down(0) + # 返回堆顶元素 + val + end + + ### 从节点 i 开始,从顶至底堆化 ### + def sift_down(i) + loop do + # 判断节点 i, l, r 中值最大的节点,记为 ma + l, r, ma = left(i), right(i), i + ma = l if l < size && @max_heap[l] > @max_heap[ma] + ma = r if r < size && @max_heap[r] > @max_heap[ma] + + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + break if ma == i + + # 交换两节点 + swap(i, ma) + # 循环向下堆化 + i = ma + end + end + + ### 打印堆(二叉树)### + def __print__ + print_heap(@max_heap) + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化大顶堆 + max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + puts "\n输入列表并建堆后" + max_heap.__print__ + + # 获取堆顶元素 + peek = max_heap.peek + puts "\n堆顶元素为 #{peek}" + + # 元素入堆 + val = 7 + max_heap.push(val) + puts "\n元素 #{val} 入堆后" + max_heap.__print__ + + # 堆顶元素出堆 + peek = max_heap.pop + puts "\n堆顶元素 #{peek} 出堆后" + max_heap.__print__ + + # 获取堆大小 + size = max_heap.size + puts "\n堆元素数量为 #{size}" + + # 判断堆是否为空 + is_empty = max_heap.is_empty? + puts "\n堆是否为空 #{is_empty}" +end diff --git a/codes/ruby/chapter_heap/top_k.rb b/codes/ruby/chapter_heap/top_k.rb new file mode 100644 index 0000000000..cfd42c2341 --- /dev/null +++ b/codes/ruby/chapter_heap/top_k.rb @@ -0,0 +1,64 @@ +=begin +File: top_k.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative "./my_heap" + +### 元素入堆 ### +def push_min_heap(heap, val) + # 元素取反 + heap.push(-val) +end + +### 元素出堆 ### +def pop_min_heap(heap) + # 元素取反 + -heap.pop +end + +### 访问堆顶元素 ### +def peek_min_heap(heap) + # 元素取反 + -heap.peek +end + +### 取出堆中元素 ### +def get_min_heap(heap) + # 将堆中所有元素取反 + heap.max_heap.map { |x| -x } +end + +### 基于堆查找数组中最大的 k 个元素 ### +def top_k_heap(nums, k) + # 初始化小顶堆 + # 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 + max_heap = MaxHeap.new([]) + + # 将数组的前 k 个元素入堆 + for i in 0...k + push_min_heap(max_heap, nums[i]) + end + + # 从第 k+1 个元素开始,保持堆的长度为 k + for i in k...nums.length + # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > peek_min_heap(max_heap) + pop_min_heap(max_heap) + push_min_heap(max_heap, nums[i]) + end + end + + get_min_heap(max_heap) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + puts "最大的 #{k} 个元素为" + print_heap(res) +end diff --git a/codes/ruby/chapter_searching/binary_search.rb b/codes/ruby/chapter_searching/binary_search.rb new file mode 100644 index 0000000000..7b0738fe12 --- /dev/null +++ b/codes/ruby/chapter_searching/binary_search.rb @@ -0,0 +1,63 @@ +=begin +File: binary_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 二分查找(双闭区间) ### +def binary_search(nums, target) + # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + i, j = 0, nums.length - 1 + + # 循环,当搜索区间为空时跳出(当 i > j 时为空) + while i <= j + # 理论上 Ruby 的数字可以无限大(取决于内存大小),无须考虑大数越界问题 + m = (i + j) / 2 # 计算中点索引 m + + if nums[m] < target + i = m + 1 # 此情况说明 target 在区间 [m+1, j] 中 + elsif nums[m] > target + j = m - 1 # 此情况说明 target 在区间 [i, m-1] 中 + else + return m # 找到目标元素,返回其索引 + end + end + + -1 # 未找到目标元素,返回 -1 +end + +### 二分查找(左闭右开区间) ### +def binary_search_lcro(nums, target) + # 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + i, j = 0, nums.length + + # 循环,当搜索区间为空时跳出(当 i = j 时为空) + while i < j + # 计算中点索引 m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # 此情况说明 target 在区间 [m+1, j) 中 + elsif nums[m] > target + j = m - 1 # 此情况说明 target 在区间 [i, m) 中 + else + return m # 找到目标元素,返回其索引 + end + end + + -1 # 未找到目标元素,返回 -1 +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分查找(双闭区间) + index = binary_search(nums, target) + puts "目标元素 6 的索引 = #{index}" + + # 二分查找(左闭右开区间) + index = binary_search_lcro(nums, target) + puts "目标元素 6 的索引 = #{index}" +end diff --git a/codes/ruby/chapter_searching/binary_search_edge.rb b/codes/ruby/chapter_searching/binary_search_edge.rb new file mode 100644 index 0000000000..dbdf95c089 --- /dev/null +++ b/codes/ruby/chapter_searching/binary_search_edge.rb @@ -0,0 +1,47 @@ +=begin +File: binary_search_edge.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative './binary_search_insertion' + +### 二分查找最左一个 target ### +def binary_search_left_edge(nums, target) + # 等价于查找 target 的插入点 + i = binary_search_insertion(nums, target) + + # 未找到 target ,返回 -1 + return -1 if i == nums.length || nums[i] != target + + i # 找到 target ,返回索引 i +end + +### 二分查找最右一个 target ### +def binary_search_right_edge(nums, target) + # 转化为查找最左一个 target + 1 + i = binary_search_insertion(nums, target + 1) + + # j 指向最右一个 target ,i 指向首个大于 target 的元素 + j = i - 1 + + # 未找到 target ,返回 -1 + return -1 if j == -1 || nums[j] != target + + j # 找到 target ,返回索引 j +end + +### Driver Code ### +if __FILE__ == $0 + # 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\n数组 nums = #{nums}" + + # 二分查找左边界和右边界 + for target in [6, 7] + index = binary_search_left_edge(nums, target) + puts "最左一个元素 #{target} 的索引为 #{index}" + index = binary_search_right_edge(nums, target) + puts "最右一个元素 #{target} 的索引为 #{index}" + end +end diff --git a/codes/ruby/chapter_searching/binary_search_insertion.rb b/codes/ruby/chapter_searching/binary_search_insertion.rb new file mode 100644 index 0000000000..5a0ee18ccc --- /dev/null +++ b/codes/ruby/chapter_searching/binary_search_insertion.rb @@ -0,0 +1,68 @@ +=begin +File: binary_search_insertion.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 二分查找插入点(无重复元素) ### +def binary_search_insertion_simple(nums, target) + # 初始化双闭区间 [0, n-1] + i, j = 0, nums.length - 1 + + while i <= j + # 计算中点索引 m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target 在区间 [m+1, j] 中 + elsif nums[m] > target + j = m - 1 # target 在区间 [i, m-1] 中 + else + return m # 找到 target ,返回插入点 m + end + end + + i # 未找到 target ,返回插入点 i +end + +### 二分查找插入点(存在重复元素) ### +def binary_search_insertion(nums, target) + # 初始化双闭区间 [0, n-1] + i, j = 0, nums.length - 1 + + while i <= j + # 计算中点索引 m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target 在区间 [m+1, j] 中 + elsif nums[m] > target + j = m - 1 # target 在区间 [i, m-1] 中 + else + j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 + end + end + + i # 返回插入点 i +end + +### Driver Code ### +if __FILE__ == $0 + # 无重复元素的数组 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + puts "\n数组 nums = #{nums}" + # 二分查找插入点 + for target in [6, 9] + index = binary_search_insertion_simple(nums, target) + puts "元素 #{target} 的插入点的索引为 #{index}" + end + + # 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\n数组 nums = #{nums}" + # 二分查找插入点 + for target in [2, 6, 20] + index = binary_search_insertion(nums, target) + puts "元素 #{target} 的插入点的索引为 #{index}" + end +end diff --git a/codes/ruby/chapter_searching/hashing_search.rb b/codes/ruby/chapter_searching/hashing_search.rb new file mode 100644 index 0000000000..0683a142ac --- /dev/null +++ b/codes/ruby/chapter_searching/hashing_search.rb @@ -0,0 +1,47 @@ +=begin +File: hashing_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +### 哈希查找(数组) ### +def hashing_search_array(hmap, target) + # 哈希表的 key: 目标元素,value: 索引 + # 若哈希表中无此 key ,返回 -1 + hmap[target] || -1 +end + +### 哈希查找(链表) ### +def hashing_search_linkedlist(hmap, target) + # 哈希表的 key: 目标元素,value: 节点对象 + # 若哈希表中无此 key ,返回 None + hmap[target] || nil +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # 哈希查找(数组) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # 初始化哈希表 + map0 = {} + for i in 0...nums.length + map0[nums[i]] = i # key: 元素,value: 索引 + end + index = hashing_search_array(map0, target) + puts "目标元素 3 的索引 = #{index}" + + # 哈希查找(链表) + head = arr_to_linked_list(nums) + # 初始化哈希表 + map1 = {} + while head + map1[head.val] = head + head = head.next + end + node = hashing_search_linkedlist(map1, target) + puts "目标节点值 3 的对应节点对象为 #{node}" +end diff --git a/codes/ruby/chapter_searching/linear_search.rb b/codes/ruby/chapter_searching/linear_search.rb new file mode 100644 index 0000000000..08141a7a14 --- /dev/null +++ b/codes/ruby/chapter_searching/linear_search.rb @@ -0,0 +1,44 @@ +=begin +File: linear_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +### 线性查找(数组) ### +def linear_search_array(nums, target) + # 遍历数组 + for i in 0...nums.length + return i if nums[i] == target # 找到目标元素,返回其索引 + end + + -1 # 未找到目标元素,返回 -1 +end + +### 线性查找(链表) ### +def linear_search_linkedlist(head, target) + # 遍历链表 + while head + return head if head.val == target # 找到目标节点,返回之 + + head = head.next + end + + nil # 未找到目标节点,返回 None +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # 在数组中执行线性查找 + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index = linear_search_array(nums, target) + puts "目标元素 3 的索引 = #{index}" + + # 在链表中执行线性查找 + head = arr_to_linked_list(nums) + node = linear_search_linkedlist(head, target) + puts "目标节点值 3 的对应节点对象为 #{node}" +end diff --git a/codes/ruby/chapter_searching/two_sum.rb b/codes/ruby/chapter_searching/two_sum.rb new file mode 100644 index 0000000000..c311322d14 --- /dev/null +++ b/codes/ruby/chapter_searching/two_sum.rb @@ -0,0 +1,46 @@ +=begin +File: two_sum.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 方法一:暴力枚举 ### +def two_sum_brute_force(nums, target) + # 两层循环,时间复杂度为 O(n^2) + for i in 0...(nums.length - 1) + for j in (i + 1)...nums.length + return [i, j] if nums[i] + nums[j] == target + end + end + + [] +end + +### 方法二:辅助哈希表 ### +def two_sum_hash_table(nums, target) + # 辅助哈希表,空间复杂度为 O(n) + dic = {} + # 单层循环,时间复杂度为 O(n) + for i in 0...nums.length + return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) + + dic[nums[i]] = i + end + + [] +end + +### Driver Code ### +if __FILE__ == $0 + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Driver Code ====== + # 方法一 + res = two_sum_brute_force(nums, target) + puts "方法一 res = #{res}" + # 方法二 + res = two_sum_hash_table(nums, target) + puts "方法二 res = #{res}" +end diff --git a/codes/ruby/chapter_sorting/bubble_sort.rb b/codes/ruby/chapter_sorting/bubble_sort.rb new file mode 100644 index 0000000000..c977642675 --- /dev/null +++ b/codes/ruby/chapter_sorting/bubble_sort.rb @@ -0,0 +1,51 @@ +=begin +File: bubble_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 冒泡排序 ### +def bubble_sort(nums) + n = nums.length + # 外循环:未排序区间为 [0, i] + for i in (n - 1).downto(1) + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in 0...i + if nums[j] > nums[j + 1] + # 交换 nums[j] 与 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + end + end + end +end + +### 冒泡排序(标志优化)### +def bubble_sort_with_flag(nums) + n = nums.length + # 外循环:未排序区间为 [0, i] + for i in (n - 1).downto(1) + flag = false # 初始化标志位 + + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in 0...i + if nums[j] > nums[j + 1] + # 交换 nums[j] 与 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = true # 记录交换元素 + end + end + + break unless flag # 此轮“冒泡”未交换任何元素,直接跳出 + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + puts "冒泡排序完成后 nums = #{nums}" + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + puts "冒泡排序完成后 nums = #{nums1}" +end diff --git a/codes/ruby/chapter_sorting/bucket_sort.rb b/codes/ruby/chapter_sorting/bucket_sort.rb new file mode 100644 index 0000000000..bcdbe81ae1 --- /dev/null +++ b/codes/ruby/chapter_sorting/bucket_sort.rb @@ -0,0 +1,43 @@ +=begin +File: bucket_sort.rb +Created Time: 2024-04-17 +Author: Martin Xu (martin.xus@gmail.com) +=end + +### 桶排序 ### +def bucket_sort(nums) + # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + k = nums.length / 2 + buckets = Array.new(k) { [] } + + # 1. 将数组元素分配到各个桶中 + nums.each do |num| + # 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + i = (num * k).to_i + # 将 num 添加进桶 i + buckets[i] << num + end + + # 2. 对各个桶执行排序 + buckets.each do |bucket| + # 使用内置排序函数,也可以替换成其他排序算法 + bucket.sort! + end + + # 3. 遍历桶合并结果 + i = 0 + buckets.each do |bucket| + bucket.each do |num| + nums[i] = num + i += 1 + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 设输入数据为浮点数,范围为 [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + puts "桶排序完成后 nums = #{nums}" +end diff --git a/codes/ruby/chapter_sorting/counting_sort.rb b/codes/ruby/chapter_sorting/counting_sort.rb new file mode 100644 index 0000000000..ca86944e4b --- /dev/null +++ b/codes/ruby/chapter_sorting/counting_sort.rb @@ -0,0 +1,62 @@ +=begin +File: counting_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 计数排序 ### +def counting_sort_naive(nums) + # 简单实现,无法用于排序对象 + # 1. 统计数组最大元素 m + m = 0 + nums.each { |num| m = [m, num].max } + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. 遍历 counter ,将各元素填入原数组 nums + i = 0 + for num in 0...(m + 1) + (0...counter[num]).each do + nums[i] = num + i += 1 + end + end +end + +### 计数排序 ### +def counting_sort(nums) + # 完整实现,可排序对象,并且是稳定排序 + # 1. 统计数组最大元素 m + m = nums.max + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + (0...m).each { |i| counter[i + 1] += counter[i] } + # 4. 倒序遍历 nums, 将各元素填入结果数组 res + # 初始化数组 res 用于记录结果 + n = nums.length + res = Array.new(n, 0) + (n - 1).downto(0).each do |i| + num = nums[i] + res[counter[num] - 1] = num # 将 num 放置到对应索引处 + counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引 + end + # 使用结果数组 res 覆盖原数组 nums + (0...n).each { |i| nums[i] = res[i] } +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + puts "计数排序(无法排序对象)完成后 nums = #{nums}" + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + puts "计数排序完成后 nums1 = #{nums1}" +end diff --git a/codes/ruby/chapter_sorting/heap_sort.rb b/codes/ruby/chapter_sorting/heap_sort.rb new file mode 100644 index 0000000000..92fe08f10a --- /dev/null +++ b/codes/ruby/chapter_sorting/heap_sort.rb @@ -0,0 +1,45 @@ +=begin +File: heap_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +### 堆的长度为 n ,从节点 i 开始,从顶至底堆化 ### +def sift_down(nums, n, i) + while true + # 判断节点 i, l, r 中值最大的节点,记为 ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + ma = l if l < n && nums[l] > nums[ma] + ma = r if r < n && nums[r] > nums[ma] + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + break if ma == i + # 交换两节点 + nums[i], nums[ma] = nums[ma], nums[i] + # 循环向下堆化 + i = ma + end +end + +### 堆排序 ### +def heap_sort(nums) + # 建堆操作:堆化除叶节点以外的其他所有节点 + (nums.length / 2 - 1).downto(0) do |i| + sift_down(nums, nums.length, i) + end + # 从堆中提取最大元素,循环 n-1 轮 + (nums.length - 1).downto(1) do |i| + # 交换根节点与最右叶节点(交换首元素与尾元素) + nums[0], nums[i] = nums[i], nums[0] + # 以根节点为起点,从顶至底进行堆化 + sift_down(nums, i, 0) + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + puts "堆排序完成后 nums = #{nums.inspect}" +end diff --git a/codes/ruby/chapter_sorting/insertion_sort.rb b/codes/ruby/chapter_sorting/insertion_sort.rb new file mode 100644 index 0000000000..484dcee30b --- /dev/null +++ b/codes/ruby/chapter_sorting/insertion_sort.rb @@ -0,0 +1,26 @@ +=begin +File: insertion_sort.rb +Created Time: 2024-04-02 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 插入排序 ### +def insertion_sort(nums) + n = nums.length + # 外循环:已排序区间为 [0, i-1] + for i in 1...n + base = nums[i] + j = i - 1 + # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while j >= 0 && nums[j] > base + nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 + j -= 1 + end + nums[j + 1] = base # 将 base 赋值到正确位置 + end +end + +### Driver Code ### +nums = [4, 1, 3, 1, 5, 2] +insertion_sort(nums) +puts "插入排序完成后 nums = #{nums}" diff --git a/codes/ruby/chapter_sorting/merge_sort.rb b/codes/ruby/chapter_sorting/merge_sort.rb new file mode 100644 index 0000000000..0bb2d1f526 --- /dev/null +++ b/codes/ruby/chapter_sorting/merge_sort.rb @@ -0,0 +1,60 @@ +=begin +File: merge_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +### 合并左子数组和右子数组 ### +def merge(nums, left, mid, right) + # 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + # 创建一个临时数组 tmp,用于存放合并后的结果 + tmp = Array.new(right - left + 1, 0) + # 初始化左子数组和右子数组的起始索引 + i, j, k = left, mid + 1, 0 + # 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while i <= mid && j <= right + if nums[i] <= nums[j] + tmp[k] = nums[i] + i += 1 + else + tmp[k] = nums[j] + j += 1 + end + k += 1 + end + # 将左子数组和右子数组的剩余元素复制到临时数组中 + while i <= mid + tmp[k] = nums[i] + i += 1 + k += 1 + end + while j <= right + tmp[k] = nums[j] + j += 1 + k += 1 + end + # 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + (0...tmp.length).each do |k| + nums[left + k] = tmp[k] + end +end + +### 归并排序 ### +def merge_sort(nums, left, right) + # 终止条件 + # 当子数组长度为 1 时终止递归 + return if left >= right + # 划分阶段 + mid = left + (right - left) / 2 # 计算中点 + merge_sort(nums, left, mid) # 递归左子数组 + merge_sort(nums, mid + 1, right) # 递归右子数组 + # 合并阶段 + merge(nums, left, mid, right) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, nums.length - 1) + puts "归并排序完成后 nums = #{nums.inspect}" +end diff --git a/codes/ruby/chapter_sorting/quick_sort.rb b/codes/ruby/chapter_sorting/quick_sort.rb new file mode 100644 index 0000000000..0ef9c48ff8 --- /dev/null +++ b/codes/ruby/chapter_sorting/quick_sort.rb @@ -0,0 +1,153 @@ +=begin +File: quick_sort.rb +Created Time: 2024-04-01 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 快速排序类 ### +class QuickSort + class << self + ### 哨兵划分 ### + def partition(nums, left, right) + # 以 nums[left] 为基准数 + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 从右向左找首个小于基准数的元素 + end + while i < j && nums[i] <= nums[left] + i += 1 # 从左向右找首个大于基准数的元素 + end + # 元素交换 + nums[i], nums[j] = nums[j], nums[i] + end + # 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + i # 返回基准数的索引 + end + + ### 快速排序类 ### + def quick_sort(nums, left, right) + # 子数组长度不为 1 时递归 + if left < right + # 哨兵划分 + pivot = partition(nums, left, right) + # 递归左子数组、右子数组 + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +### 快速排序类(中位数优化)### +class QuickSortMedian + class << self + ### 选取三个候选元素的中位数 ### + def median_three(nums, left, mid, right) + # 选取三个候选元素的中位数 + _l, _m, _r = nums[left], nums[mid], nums[right] + # m 在 l 和 r 之间 + return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) + # l 在 m 和 r 之间 + return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) + return right + end + + ### 哨兵划分(三数取中值)### + def partition(nums, left, right) + ### 以 nums[left] 为基准数 + med = median_three(nums, left, (left + right) / 2, right) + # 将中位数交换至数组最左断 + nums[left], nums[med] = nums[med], nums[left] + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 从右向左找首个小于基准数的元素 + end + while i < j && nums[i] <= nums[left] + i += 1 # 从左向右找首个大于基准数的元素 + end + # 元素交换 + nums[i], nums[j] = nums[j], nums[i] + end + # 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + i # 返回基准数的索引 + end + + ### 快速排序 ### + def quick_sort(nums, left, right) + # 子数组长度不为 1 时递归 + if left < right + # 哨兵划分 + pivot = partition(nums, left, right) + # 递归左子数组、右子数组 + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +### 快速排序类(尾递归优化)### +class QuickSortTailCall + class << self + ### 哨兵划分 ### + def partition(nums, left, right) + # 以 nums[left]为基准数 + i = left + j = right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 从右向左找首个小于基准数的元素 + end + while i < j && nums[i] <= nums[left] + i += 1 # 从左向右找首个大于基准数的元素 + end + # 元素交换 + nums[i], nums[j] = nums[j], nums[i] + end + # 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + i # 返回基准数的索引 + end + + ### 快速排序(尾递归优化)### + def quick_sort(nums, left, right) + # 子数组长度不为 1 时递归 + while left < right + # 哨兵划分 + pivot = partition(nums, left, right) + # 对两个子数组中较短的那个执行快速排序 + if pivot - left < right - pivot + quick_sort(nums, left, pivot - 1) + left = pivot + 1 # 剩余未排序区间为 [pivot + 1, right] + else + quick_sort(nums, pivot + 1, right) + right = pivot - 1 # 剩余未排序区间为 [left, pivot - 1] + end + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 快速排序 + nums = [2, 4, 1, 0, 3, 5] + QuickSort.quick_sort(nums, 0, nums.length - 1) + puts "快速排序完成后 nums = #{nums}" + + # 快速排序(中位基准数优化) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) + puts "快速排序(中位基准数优化)完成后 nums1 = #{nums1}" + + # 快速排序(尾递归优化) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) + puts "快速排序(尾递归优化)完成后 nums2 = #{nums2}" +end diff --git a/codes/ruby/chapter_sorting/radix_sort.rb b/codes/ruby/chapter_sorting/radix_sort.rb new file mode 100644 index 0000000000..d5611ee74a --- /dev/null +++ b/codes/ruby/chapter_sorting/radix_sort.rb @@ -0,0 +1,70 @@ +=begin +File: radix_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 获取元素 num 的第 k 位,其中 exp = 10^(k-1) ### +def digit(num, exp) + # 转入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + (num / exp) % 10 +end + +### 计数排序(根据 nums 第 k 位排序)### +def counting_sort_digit(nums, exp) + # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + counter = Array.new(10, 0) + n = nums.length + # 统计 0~9 各数字的出现次数 + for i in 0...n + d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d + counter[d] += 1 # 统计数字 d 的出现次数 + end + # 求前缀和,将“出现个数”转换为“数组索引” + (1...10).each { |i| counter[i] += counter[i - 1] } + # 倒序遍历,根据桶内统计结果,将各元素填入 res + res = Array.new(n, 0) + for i in (n - 1).downto(0) + d = digit(nums[i], exp) + j = counter[d] - 1 # 获取 d 在数组中的索引 j + res[j] = nums[i] # 将当前元素填入索引 j + counter[d] -= 1 # 将 d 的数量减 1 + end + # 使用结果覆盖原数组 nums + (0...n).each { |i| nums[i] = res[i] } +end + +### 基数排序 ### +def radix_sort(nums) + # 获取数组的最大元素,用于判断最大位数 + m = nums.max + # 按照从低位到高位的顺序遍历 + exp = 1 + while exp <= m + # 对数组元素的第 k 位执行计数排序 + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # 即 exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + end +end + +### Driver Code ### +if __FILE__ == $0 + # 基数排序 + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + puts "基数排序完成后 nums = #{nums}" +end diff --git a/codes/ruby/chapter_sorting/selection_sort.rb b/codes/ruby/chapter_sorting/selection_sort.rb new file mode 100644 index 0000000000..79fe73eea9 --- /dev/null +++ b/codes/ruby/chapter_sorting/selection_sort.rb @@ -0,0 +1,29 @@ +=begin +File: selection_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 选择排序 ### +def selection_sort(nums) + n = nums.length + # 外循环:未排序区间为 [i, n-1] + for i in 0...(n - 1) + # 内循环:找到未排序区间内的最小元素 + k = i + for j in (i + 1)...n + if nums[j] < nums[k] + k = j # 记录最小元素的索引 + end + end + # 将该最小元素与未排序区间的首个元素交换 + nums[i], nums[k] = nums[k], nums[i] + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + puts "选择排序完成后 nums = #{nums}" +end diff --git a/codes/ruby/chapter_stack_and_queue/array_deque.rb b/codes/ruby/chapter_stack_and_queue/array_deque.rb new file mode 100644 index 0000000000..91e4ff71fb --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/array_deque.rb @@ -0,0 +1,145 @@ +=begin +File: array_deque.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 基于环形数组实现的双向队列 ### +class ArrayDeque + ### 获取双向队列的长度 ### + attr_reader :size + + ### 构造方法 ### + def initialize(capacity) + @nums = Array.new(capacity, 0) + @front = 0 + @size = 0 + end + + ### 获取双向队列的容量 ### + def capacity + @nums.length + end + + ### 判断双向队列是否为空 ### + def is_empty? + size.zero? + end + + ### 队首入队 ### + def push_first(num) + if size == capacity + puts '双向队列已满' + return + end + + # 队首指针向左移动一位 + # 通过取余操作实现 front 越过数组头部后回到尾部 + @front = index(@front - 1) + # 将 num 添加至队首 + @nums[@front] = num + @size += 1 + end + + ### 队尾入队 ### + def push_last(num) + if size == capacity + puts '双向队列已满' + return + end + + # 计算队尾指针,指向队尾索引 + 1 + rear = index(@front + size) + # 将 num 添加至队尾 + @nums[rear] = num + @size += 1 + end + + ### 队首出队 ### + def pop_first + num = peek_first + # 队首指针向后移动一位 + @front = index(@front + 1) + @size -= 1 + num + end + + ### 队尾出队 ### + def pop_last + num = peek_last + @size -= 1 + num + end + + ### 访问队首元素 ### + def peek_first + raise IndexError, '双向队列为空' if is_empty? + + @nums[@front] + end + + ### 访问队尾元素 ### + def peek_last + raise IndexError, '双向队列为空' if is_empty? + + # 计算尾元素索引 + last = index(@front + size - 1) + @nums[last] + end + + ### 返回数组用于打印 ### + def to_array + # 仅转换有效长度范围内的列表元素 + res = [] + for i in 0...size + res << @nums[index(@front + i)] + end + res + end + + private + + ### 计算环形数组索引 ### + def index(i) + # 通过取余操作实现数组首尾相连 + # 当 i 越过数组尾部后,回到头部 + # 当 i 越过数组头部后,回到尾部 + (i + capacity) % capacity + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化双向队列 + deque = ArrayDeque.new(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "双向队列 deque = #{deque.to_array}" + + # 访问元素 + peek_first = deque.peek_first + puts "队首元素 peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "队尾元素 peek_last = #{peek_last}" + + # 元素入队 + deque.push_last(4) + puts "元素 4 队尾入队后 deque = #{deque.to_array}" + deque.push_first(1) + puts "元素 1 队尾入队后 deque = #{deque.to_array}" + + # 元素出队 + pop_last = deque.pop_last + puts "队尾出队元素 = #{pop_last},队尾出队后 deque = #{deque.to_array}" + pop_first = deque.pop_first + puts "队尾出队元素 = #{pop_first},队尾出队后 deque = #{deque.to_array}" + + # 获取双向队列的长度 + size = deque.size + puts "双向队列长度 size = #{size}" + + # 判断双向队列是否为空 + is_empty = deque.is_empty? + puts "双向队列是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_stack_and_queue/array_queue.rb b/codes/ruby/chapter_stack_and_queue/array_queue.rb new file mode 100644 index 0000000000..089d7a3fc9 --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/array_queue.rb @@ -0,0 +1,107 @@ +=begin +File: array_queue.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 基于环形数组实现的队列 ### +class ArrayQueue + ### 获取队列的长度 ### + attr_reader :size + + ### 构造方法 ### + def initialize(size) + @nums = Array.new(size, 0) # 用于存储队列元素的数组 + @front = 0 # 队首指针,指向队首元素 + @size = 0 # 队列长度 + end + + ### 获取队列的容量 ### + def capacity + @nums.length + end + + ### 判断队列是否为空 ### + def is_empty? + size.zero? + end + + ### 入队 ### + def push(num) + raise IndexError, '队列已满' if size == capacity + + # 计算队尾指针,指向队尾索引 + 1 + # 通过取余操作实现 rear 越过数组尾部后回到头部 + rear = (@front + size) % capacity + # 将 num 添加至队尾 + @nums[rear] = num + @size += 1 + end + + ### 出队 ### + def pop + num = peek + # 队首指针向后移动一位,若越过尾部,则返回到数组头部 + @front = (@front + 1) % capacity + @size -= 1 + num + end + + ### 访问队首元素 ### + def peek + raise IndexError, '队列为空' if is_empty? + + @nums[@front] + end + + ### 返回列表用于打印 ### + def to_array + res = Array.new(size, 0) + j = @front + + for i in 0...size + res[i] = @nums[j % capacity] + j += 1 + end + + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化队列 + queue = ArrayQueue.new(10) + + # 元素入队 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "队列 queue = #{queue.to_array}" + + # 访问队首元素 + peek = queue.peek + puts "队首元素 peek = #{peek}" + + # 元素出队 + pop = queue.pop + puts "出队元素 pop = #{pop}" + puts "出队后 queue = #{queue.to_array}" + + # 获取队列的长度 + size = queue.size + puts "队列长度 size = #{size}" + + # 判断队列是否为空 + is_empty = queue.is_empty? + puts "队列是否为空 = #{is_empty}" + + # 测试环形数组 + for i in 0...10 + queue.push(i) + queue.pop + puts "第 #{i} 轮入队 + 出队后 queue = #{queue.to_array}" + end +end diff --git a/codes/ruby/chapter_stack_and_queue/array_stack.rb b/codes/ruby/chapter_stack_and_queue/array_stack.rb new file mode 100644 index 0000000000..93a7f87da0 --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/array_stack.rb @@ -0,0 +1,78 @@ +=begin +File: array_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 基于数组实现的栈 ### +class ArrayStack + ### 构造方法 ### + def initialize + @stack = [] + end + + ### 获取栈的长度 ### + def size + @stack.length + end + + ### 判断栈是否为空 ### + def is_empty? + @stack.empty? + end + + ### 入栈 ### + def push(item) + @stack << item + end + + ### 出栈 ### + def pop + raise IndexError, '栈为空' if is_empty? + + @stack.pop + end + + ### 访问栈顶元素 ### + def peek + raise IndexError, '栈为空' if is_empty? + + @stack.last + end + + ### 返回列表用于打印 ### + def to_array + @stack + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化栈 + stack = ArrayStack.new + + # 元素入栈 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "栈 stack = #{stack.to_array}" + + # 访问栈顶元素 + peek = stack.peek + puts "栈顶元素 peek = #{peek}" + + # 元素出栈 + pop = stack.pop + puts "出栈元素 pop = #{pop}" + puts "出栈后 stack = #{stack.to_array}" + + # 获取栈的长度 + size = stack.size + puts "栈的长度 size = #{size}" + + # 判断是否为空 + is_empty = stack.is_empty? + puts "栈是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_stack_and_queue/deque.rb b/codes/ruby/chapter_stack_and_queue/deque.rb new file mode 100644 index 0000000000..21e63d2944 --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/deque.rb @@ -0,0 +1,42 @@ +=begin +File: deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化双向队列 + # Ruby 没有内直的双端队列,只能把 Array 当作双端队列来使用 + deque = [] + + # 元素如队 + deque << 2 + deque << 5 + deque << 4 + # 请注意,由于是数组,Array#unshift 方法的时间复杂度为 O(n) + deque.unshift(3) + deque.unshift(1) + puts "双向队列 deque = #{deque}" + + # 访问元素 + peek_first = deque.first + puts "队首元素 peek_first = #{peek_first}" + peek_last = deque.last + puts "队尾元素 peek_last = #{peek_last}" + + # 元素出队 + # 请注意,由于是数组, Array#shift 方法的时间复杂度为 O(n) + pop_front = deque.shift + puts "队首出队元素 pop_front = #{pop_front},队首出队后 deque = #{deque}" + pop_back = deque.pop + puts "队尾出队元素 pop_back = #{pop_back}, 队尾出队后 deque = #{deque}" + + # 获取双向队列的长度 + size = deque.length + puts "双向队列长度 size = #{size}" + + # 判断双向队列是否为空 + is_empty = size.zero? + puts "双向队列是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb b/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb new file mode 100644 index 0000000000..4b901a853f --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb @@ -0,0 +1,168 @@ +=begin +File: linkedlist_deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 双向链表节点 +class ListNode + attr_accessor :val + attr_accessor :next # 后继节点引用 + attr_accessor :prev # 前躯节点引用 + + ### 构造方法 ### + def initialize(val) + @val = val + end +end + +### 基于双向链表实现的双向队列 ### +class LinkedListDeque + ### 获取双向队列的长度 ### + attr_reader :size + + ### 构造方法 ### + def initialize + @front = nil # 头节点 front + @rear = nil # 尾节点 rear + @size = 0 # 双向队列的长度 + end + + ### 判断双向队列是否为空 ### + def is_empty? + size.zero? + end + + ### 入队操作 ### + def push(num, is_front) + node = ListNode.new(num) + # 若链表为空, 则令 front 和 rear 都指向 node + if is_empty? + @front = @rear = node + # 队首入队操作 + elsif is_front + # 将 node 添加至链表头部 + @front.prev = node + node.next = @front + @front = node # 更新头节点 + # 队尾入队操作 + else + # 将 node 添加至链表尾部 + @rear.next = node + node.prev = @rear + @rear = node # 更新尾节点 + end + @size += 1 # 更新队列长度 + end + + ### 队首入队 ### + def push_first(num) + push(num, true) + end + + ### 队尾入队 ### + def push_last(num) + push(num, false) + end + + ### 出队操作 ### + def pop(is_front) + raise IndexError, '双向队列为空' if is_empty? + + # 队首出队操作 + if is_front + val = @front.val # 暂存头节点值 + # 删除头节点 + fnext = @front.next + unless fnext.nil? + fnext.prev = nil + @front.next = nil + end + @front = fnext # 更新头节点 + # 队尾出队操作 + else + val = @rear.val # 暂存尾节点值 + # 删除尾节点 + rprev = @rear.prev + unless rprev.nil? + rprev.next = nil + @rear.prev = nil + end + @rear = rprev # 更新尾节点 + end + @size -= 1 # 更新队列长度 + + val + end + + ### 队首出队 ### + def pop_first + pop(true) + end + + ### 队首出队 ### + def pop_last + pop(false) + end + + ### 访问队首元素 ### + def peek_first + raise IndexError, '双向队列为空' if is_empty? + + @front.val + end + + ### 访问队尾元素 ### + def peek_last + raise IndexError, '双向队列为空' if is_empty? + + @rear.val + end + + ### 返回数组用于打印 ### + def to_array + node = @front + res = Array.new(size, 0) + for i in 0...size + res[i] = node.val + node = node.next + end + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化双向队列 + deque = LinkedListDeque.new + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "双向队列 deque = #{deque.to_array}" + + # 访问元素 + peek_first = deque.peek_first + puts "队首元素 peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "队首元素 peek_last = #{peek_last}" + + # 元素入队 + deque.push_last(4) + puts "元素 4 队尾入队后 deque = #{deque.to_array}" + deque.push_first(1) + puts "元素 1 队首入队后 deque = #{deque.to_array}" + + # 元素出队 + pop_last = deque.pop_last + puts "队尾出队元素 = #{pop_last}, 队尾出队后 deque = #{deque.to_array}" + pop_first = deque.pop_first + puts "队首出队元素 = #{pop_first},队首出队后 deque = #{deque.to_array}" + + # 获取双向队列的长度 + size = deque.size + puts "双向队列长度 size = #{size}" + + # 判断双向队列是否为空 + is_empty = deque.is_empty? + puts "双向队列是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb b/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb new file mode 100644 index 0000000000..fea3fc9cc3 --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb @@ -0,0 +1,101 @@ +=begin +File: linkedlist_queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### 基于链表头现的队列 ### +class LinkedListQueue + ### 获取队列的长度 ### + attr_reader :size + + ### 构造方法 ### + def initialize + @front = nil # 头节点 front + @rear = nil # 尾节点 rear + @size = 0 + end + + ### 判断队列是否为空 ### + def is_empty? + @front.nil? + end + + ### 入队 ### + def push(num) + # 在尾节点后添加 num + node = ListNode.new(num) + + # 如果队列为空,则令头,尾节点都指向该节点 + if @front.nil? + @front = node + @rear = node + # 如果队列不为空,则令该节点添加到尾节点后 + else + @rear.next = node + @rear = node + end + + @size += 1 + end + + ### 出队 ### + def pop + num = peek + # 删除头节点 + @front = @front.next + @size -= 1 + num + end + + ### 访问队首元素 ### + def peek + raise IndexError, '队列为空' if is_empty? + + @front.val + end + + ### 将链表为 Array 并返回 ### + def to_array + queue = [] + temp = @front + while temp + queue << temp.val + temp = temp.next + end + queue + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化队列 + queue = LinkedListQueue.new + + # 元素如队 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "队列 queue = #{queue.to_array}" + + # 访问队首元素 + peek = queue.peek + puts "队首元素 front = #{peek}" + + # 元素出队 + pop_front = queue.pop + puts "出队元素 pop = #{pop_front}" + puts "出队后 queue = #{queue.to_array}" + + # 获取队列的长度 + size = queue.size + puts "队列长度 size = #{size}" + + # 判断队列是否为空 + is_empty = queue.is_empty? + puts "队列是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb b/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb new file mode 100644 index 0000000000..0c3551b4be --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb @@ -0,0 +1,87 @@ +=begin +File: linkedlist_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### 基于链表实现的栈 ### +class LinkedListStack + attr_reader :size + + ### 构造方法 ### + def initialize + @size = 0 + end + + ### 判断栈是否为空 ### + def is_empty? + @peek.nil? + end + + ### 入栈 ### + def push(val) + node = ListNode.new(val) + node.next = @peek + @peek = node + @size += 1 + end + + ### 出栈 ### + def pop + num = peek + @peek = @peek.next + @size -= 1 + num + end + + ### 访问栈顶元素 ### + def peek + raise IndexError, '栈为空' if is_empty? + + @peek.val + end + + ### 将链表转化为 Array 并反回 ### + def to_array + arr = [] + node = @peek + while node + arr << node.val + node = node.next + end + arr.reverse + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化栈 + stack = LinkedListStack.new + + # 元素入栈 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "栈 stack = #{stack.to_array}" + + # 访问栈顶元素 + peek = stack.peek + puts "栈顶元素 peek = #{peek}" + + # 元素出栈 + pop = stack.pop + puts "出栈元素 pop = #{pop}" + puts "出栈后 stack = #{stack.to_array}" + + # 获取栈的长度 + size = stack.size + puts "栈的长度 size = #{size}" + + # 判断是否为空 + is_empty = stack.is_empty? + puts "栈是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_stack_and_queue/queue.rb b/codes/ruby/chapter_stack_and_queue/queue.rb new file mode 100644 index 0000000000..919289d379 --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/queue.rb @@ -0,0 +1,38 @@ +=begin +File: queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化队列 + # Ruby 内置的队列(Thread::Queue) 没有 peek 和遍历方法,可以把 Array 当作队列来使用 + queue = [] + + # 元素入队 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "队列 queue = #{queue}" + + # 访问队列元素 + peek = queue.first + puts "队首元素 peek = #{peek}" + + # 元素出队 + # 清注意,由于是数组,Array#shift 方法时间复杂度为 O(n) + pop = queue.shift + puts "出队元素 pop = #{pop}" + puts "出队后 queue = #{queue}" + + # 获取队列的长度 + size = queue.length + puts "队列长度 size = #{size}" + + # 判断队列是否为空 + is_empty = queue.empty? + puts "队列是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_stack_and_queue/stack.rb b/codes/ruby/chapter_stack_and_queue/stack.rb new file mode 100644 index 0000000000..ff9e7ef9c1 --- /dev/null +++ b/codes/ruby/chapter_stack_and_queue/stack.rb @@ -0,0 +1,37 @@ +=begin +File: stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化栈 + # Ruby 没有内置的栈类,可以把 Array 当作栈来使用 + stack = [] + + # 元素入栈 + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 + puts "栈 stack = #{stack}" + + # 访问栈顶元素 + peek = stack.last + puts "栈顶元素 peek = #{peek}" + + # 元素出栈 + pop = stack.pop + puts "出栈元素 pop = #{pop}" + puts "出栈后 stack = #{stack}" + + # 获取栈的长度 + size = stack.length + puts "栈的长度 size = #{size}" + + # 判断是否为空 + is_empty = stack.empty? + puts "栈是否为空 = #{is_empty}" +end diff --git a/codes/ruby/chapter_tree/array_binary_tree.rb b/codes/ruby/chapter_tree/array_binary_tree.rb new file mode 100644 index 0000000000..0492e31066 --- /dev/null +++ b/codes/ruby/chapter_tree/array_binary_tree.rb @@ -0,0 +1,124 @@ +=begin +File: array_binary_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 数组表示下的二叉树类 ### +class ArrayBinaryTree + ### 构造方法 ### + def initialize(arr) + @tree = arr.to_a + end + + ### 列表容量 ### + def size + @tree.length + end + + ### 获取索引为 i 节点的值 ### + def val(i) + # 若索引越界,则返回 nil ,代表空位 + return if i < 0 || i >= size + + @tree[i] + end + + ### 获取索引为 i 节点的左子节点的索引 ### + def left(i) + 2 * i + 1 + end + + ### 获取索引为 i 节点的右子节点的索引 ### + def right(i) + 2 * i + 2 + end + + ### 获取索引为 i 节点的父节点的索引 ### + def parent(i) + (i - 1) / 2 + end + + ### 层序遍历 ### + def level_order + @res = [] + + # 直接遍历数组 + for i in 0...size + @res << val(i) unless val(i).nil? + end + + @res + end + + ### 深度优先遍历 ### + def dfs(i, order) + return if val(i).nil? + # 前序遍历 + @res << val(i) if order == :pre + dfs(left(i), order) + # 中序遍历 + @res << val(i) if order == :in + dfs(right(i), order) + # 后序遍历 + @res << val(i) if order == :post + end + + ### 前序遍历 ### + def pre_order + @res = [] + dfs(0, :pre) + @res + end + + ### 中序遍历 ### + def in_order + @res = [] + dfs(0, :in) + @res + end + + ### 后序遍历 ### + def post_order + @res = [] + dfs(0, :post) + @res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二叉树 + # 这里借助了一个从数组直接生成二叉树的函数 + arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + root = arr_to_tree(arr) + puts "\n初始化二叉树\n\n" + puts '二叉树的数组表示:' + pp arr + puts '二叉树的链表表示:' + print_tree(root) + + # 数组表示下的二叉树类 + abt = ArrayBinaryTree.new(arr) + + # 访问节点 + i = 1 + l, r, _p = abt.left(i), abt.right(i), abt.parent(i) + puts "\n当前节点的索引为 #{i} ,值为 #{abt.val(i).inspect}" + puts "其左子节点的索引为 #{l} ,值为 #{abt.val(l).inspect}" + puts "其右子节点的索引为 #{r} ,值为 #{abt.val(r).inspect}" + puts "其父节点的索引为 #{_p} ,值为 #{abt.val(_p).inspect}" + + # 遍历树 + res = abt.level_order + puts "\n层序遍历为: #{res}" + res = abt.pre_order + puts "前序遍历为: #{res}" + res = abt.in_order + puts "中序遍历为: #{res}" + res = abt.post_order + puts "后序遍历为: #{res}" +end diff --git a/codes/ruby/chapter_tree/avl_tree.rb b/codes/ruby/chapter_tree/avl_tree.rb new file mode 100644 index 0000000000..2e4db37d73 --- /dev/null +++ b/codes/ruby/chapter_tree/avl_tree.rb @@ -0,0 +1,216 @@ +=begin +File: avl_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### AVL 树 ### +class AVLTree + ### 构造方法 ### + def initialize + @root = nil + end + + ### 获取二叉树根节点 ### + def get_root + @root + end + + ### 获取节点高度 ### + def height(node) + # 空节点高度为 -1 ,叶节点高度为 0 + return node.height unless node.nil? + + -1 + end + + ### 更新节点高度 ### + def update_height(node) + # 节点高度等于最高子树高度 + 1 + node.height = [height(node.left), height(node.right)].max + 1 + end + + ### 获取平衡因子 ### + def balance_factor(node) + # 空节点平衡因子为 0 + return 0 if node.nil? + + # 节点平衡因子 = 左子树高度 - 右子树高度 + height(node.left) - height(node.right) + end + + ### 右旋操作 ### + def right_rotate(node) + child = node.left + grand_child = child.right + # 以 child 为原点,将 node 向右旋转 + child.right = node + node.left = grand_child + # 更新节点高度 + update_height(node) + update_height(child) + # 返回旋转后子树的根节点 + child + end + + ### 左旋操作 ### + def left_rotate(node) + child = node.right + grand_child = child.left + # 以 child 为原点,将 node 向左旋转 + child.left = node + node.right = grand_child + # 更新节点高度 + update_height(node) + update_height(child) + # 返回旋转后子树的根节点 + child + end + + ### 执行旋转操作,使该子树重新恢复平衡 ### + def rotate(node) + # 获取节点 node 的平衡因子 + balance_factor = balance_factor(node) + # 左遍树 + if balance_factor > 1 + if balance_factor(node.left) >= 0 + # 右旋 + return right_rotate(node) + else + # 先左旋后右旋 + node.left = left_rotate(node.left) + return right_rotate(node) + end + # 右遍树 + elsif balance_factor < -1 + if balance_factor(node.right) <= 0 + # 左旋 + return left_rotate(node) + else + # 先右旋后左旋 + node.right = right_rotate(node.right) + return left_rotate(node) + end + end + # 平衡树,无须旋转,直接返回 + node + end + + ### 插入节点 ### + def insert(val) + @root = insert_helper(@root, val) + end + + ### 递归插入节点(辅助方法)### + def insert_helper(node, val) + return TreeNode.new(val) if node.nil? + # 1. 查找插入位置并插入节点 + if val < node.val + node.left = insert_helper(node.left, val) + elsif val > node.val + node.right = insert_helper(node.right, val) + else + # 重复节点不插入,直接返回 + return node + end + # 更新节点高度 + update_height(node) + # 2. 执行旋转操作,使该子树重新恢复平衡 + rotate(node) + end + + ### 删除节点 ### + def remove(val) + @root = remove_helper(@root, val) + end + + ### 递归删除节点(辅助方法)### + def remove_helper(node, val) + return if node.nil? + # 1. 查找节点并删除 + if val < node.val + node.left = remove_helper(node.left, val) + elsif val > node.val + node.right = remove_helper(node.right, val) + else + if node.left.nil? || node.right.nil? + child = node.left || node.right + # 子节点数量 = 0 ,直接删除 node 并返回 + return if child.nil? + # 子节点数量 = 1 ,直接删除 node + node = child + else + # 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + temp = node.right + while !temp.left.nil? + temp = temp.left + end + node.right = remove_helper(node.right, temp.val) + node.val = temp.val + end + end + # 更新节点高度 + update_height(node) + # 2. 执行旋转操作,使该子树重新恢复平衡 + rotate(node) + end + + ### 查找节点 ### + def search(val) + cur = @root + # 循环查找,越过叶节点后跳出 + while !cur.nil? + # 目标节点在 cur 的右子树中 + if cur.val < val + cur = cur.right + # 目标节点在 cur 的左子树中 + elsif cur.val > val + cur = cur.left + # 找到目标节点,跳出循环 + else + break + end + end + # 返回目标节点 + cur + end +end + +### Driver Code ### +if __FILE__ == $0 + def test_insert(tree, val) + tree.insert(val) + puts "\n插入节点 #{val} 后,AVL 树为" + print_tree(tree.get_root) + end + + def test_remove(tree, val) + tree.remove(val) + puts "\n删除节点 #{val} 后,AVL 树为" + print_tree(tree.get_root) + end + + # 初始化空 AVL 树 + avl_tree = AVLTree.new + + # 插入节点 + # 请关注插入节点后,AVL 树是如何保持平衡的 + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] + test_insert(avl_tree, val) + end + + # 插入重复节点 + test_insert(avl_tree, 7) + + # 删除节点 + # 请关注删除节点后,AVL 树是如何保持平衡的 + test_remove(avl_tree, 8) # 删除度为 0 的节点 + test_remove(avl_tree, 5) # 删除度为 1 的节点 + test_remove(avl_tree, 4) # 删除度为 2 的节点 + + result_node = avl_tree.search(7) + puts "\n查找到的节点对象为 #{result_node},节点值 = #{result_node.val}" +end diff --git a/codes/ruby/chapter_tree/binary_search_tree.rb b/codes/ruby/chapter_tree/binary_search_tree.rb new file mode 100644 index 0000000000..7b012797b8 --- /dev/null +++ b/codes/ruby/chapter_tree/binary_search_tree.rb @@ -0,0 +1,161 @@ +=begin +File: binary_search_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 二叉搜索树 ### +class BinarySearchTree + ### 构造方法 ### + def initialize + # 初始化空树 + @root = nil + end + + ### 获取二叉树根节点 ### + def get_root + @root + end + + ### 查找节点 ### + def search(num) + cur = @root + + # 循环查找,越过叶节点后跳出 + while !cur.nil? + # 目标节点在 cur 的右子树中 + if cur.val < num + cur = cur.right + # 目标节点在 cur 的左子树中 + elsif cur.val > num + cur = cur.left + # 找到目标节点,跳出循环 + else + break + end + end + + cur + end + + ### 插入节点 ### + def insert(num) + # 若树为空,则初始化根节点 + if @root.nil? + @root = TreeNode.new(num) + return + end + + # 循环查找,越过叶节点后跳出 + cur, pre = @root, nil + while !cur.nil? + # 找到重复节点,直接返回 + return if cur.val == num + + pre = cur + # 插入位置在 cur 的右子树中 + if cur.val < num + cur = cur.right + # 插入位置在 cur 的左子树中 + else + cur = cur.left + end + end + + # 插入节点 + node = TreeNode.new(num) + if pre.val < num + pre.right = node + else + pre.left = node + end + end + + ### 删除节点 ### + def remove(num) + # 若树为空,直接提前返回 + return if @root.nil? + + # 循环查找,越过叶节点后跳出 + cur, pre = @root, nil + while !cur.nil? + # 找到待删除节点,跳出循环 + break if cur.val == num + + pre = cur + # 待删除节点在 cur 的右子树中 + if cur.val < num + cur = cur.right + # 待删除节点在 cur 的左子树中 + else + cur = cur.left + end + end + # 若无待删除节点,则直接返回 + return if cur.nil? + + # 子节点数量 = 0 or 1 + if cur.left.nil? || cur.right.nil? + # 当子节点数量 = 0 / 1 时, child = null / 该子节点 + child = cur.left || cur.right + # 删除节点 cur + if cur != @root + if pre.left == cur + pre.left = child + else + pre.right = child + end + else + # 若删除节点为根节点,则重新指定根节点 + @root = child + end + # 子节点数量 = 2 + else + # 获取中序遍历中 cur 的下一个节点 + tmp = cur.right + while !tmp.left.nil? + tmp = tmp.left + end + # 递归删除节点 tmp + remove(tmp.val) + # 用 tmp 覆盖 cur + cur.val = tmp.val + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二叉搜索树 + bst = BinarySearchTree.new + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + nums.each { |num| bst.insert(num) } + puts "\n初始化的二叉树为\n" + print_tree(bst.get_root) + + # 查找节点 + node = bst.search(7) + puts "\n查找到的节点对象为: #{node},节点值 = #{node.val}" + + # 插入节点 + bst.insert(16) + puts "\n插入节点 16 后,二叉树为\n" + print_tree(bst.get_root) + + # 删除节点 + bst.remove(1) + puts "\n删除节点 1 后,二叉树为\n" + print_tree(bst.get_root) + + bst.remove(2) + puts "\n删除节点 2 后,二叉树为\n" + print_tree(bst.get_root) + + bst.remove(4) + puts "\n删除节点 4 后,二叉树为\n" + print_tree(bst.get_root) +end diff --git a/codes/ruby/chapter_tree/binary_tree.rb b/codes/ruby/chapter_tree/binary_tree.rb new file mode 100644 index 0000000000..7a6bfa0baa --- /dev/null +++ b/codes/ruby/chapter_tree/binary_tree.rb @@ -0,0 +1,38 @@ +=begin +File: binary_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # 初始化二叉树 + # 初始化节点 + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + puts "\n初始化二叉树\n\n" + print_tree(n1) + + # 插入与删除节点 + _p = TreeNode.new(0) + # 在 n1 -> n2 中间插入节点 _p + n1.left = _p + _p.left = n2 + puts "\n插入节点 _p 后\n\n" + print_tree(n1) + # 删除节点 + n1.left = n2 + puts "\n删除节点 _p 后\n\n" + print_tree(n1) +end diff --git a/codes/ruby/chapter_tree/binary_tree_bfs.rb b/codes/ruby/chapter_tree/binary_tree_bfs.rb new file mode 100644 index 0000000000..1934b0c1cc --- /dev/null +++ b/codes/ruby/chapter_tree/binary_tree_bfs.rb @@ -0,0 +1,36 @@ +=begin +File: binary_tree_bfs.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 层序遍历 ### +def level_order(root) + # 初始化队列,加入根节点 + queue = [root] + # 初始化一个列表,用于保存遍历序列 + res = [] + while !queue.empty? + node = queue.shift # 队列出队 + res << node.val # 保存节点值 + queue << node.left unless node.left.nil? # 左子节点入队 + queue << node.right unless node.right.nil? # 右子节点入队 + end + res +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二叉树 + # 这里借助了一个从数组直接生成二叉树的函数 + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树\n\n" + print_tree(root) + + # 层序遍历 + res = level_order(root) + puts "\n层序遍历的节点打印序列 = #{res}" +end diff --git a/codes/ruby/chapter_tree/binary_tree_dfs.rb b/codes/ruby/chapter_tree/binary_tree_dfs.rb new file mode 100644 index 0000000000..c7d4270cf2 --- /dev/null +++ b/codes/ruby/chapter_tree/binary_tree_dfs.rb @@ -0,0 +1,62 @@ +=begin +File: binary_tree_dfs.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序遍历 ### +def pre_order(root) + return if root.nil? + + # 访问优先级:根节点 -> 左子树 -> 右子树 + $res << root.val + pre_order(root.left) + pre_order(root.right) +end + +### 中序遍历 ### +def in_order(root) + return if root.nil? + + # 访问优先级:左子树 -> 根节点 -> 右子树 + in_order(root.left) + $res << root.val + in_order(root.right) +end + +### 后序遍历 ### +def post_order(root) + return if root.nil? + + # 访问优先级:左子树 -> 右子树 -> 根节点 + post_order(root.left) + post_order(root.right) + $res << root.val +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二叉树 + # 这里借助了一个从数组直接生成二叉树的函数 + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树\n\n" + print_tree(root) + + # 前序遍历 + $res = [] + pre_order(root) + puts "\n前序遍历的节点打印序列 = #{$res}" + + # 中序遍历 + $res.clear + in_order(root) + puts "\nn中序遍历的节点打印序列 = #{$res}" + + # 后序遍历 + $res.clear + post_order(root) + puts "\nn后序遍历的节点打印序列 = #{$res}" +end diff --git a/codes/ruby/test_all.rb b/codes/ruby/test_all.rb new file mode 100644 index 0000000000..a4d417800a --- /dev/null +++ b/codes/ruby/test_all.rb @@ -0,0 +1,23 @@ +require 'open3' + +start_time = Time.now +ruby_code_dir = File.dirname(__FILE__) +files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") + +errors = [] + +files.each do |file| + stdout, stderr, status = Open3.capture3("ruby #{file}") + errors << stderr unless status.success? +end + +puts "\x1b[34mTested #{files.count} files\x1b[m" + +unless errors.empty? + puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" + raise errors.join("\n\n") +else + puts "\x1b[32mPASS\x1b[m" +end + +puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" diff --git a/codes/ruby/utils/list_node.rb b/codes/ruby/utils/list_node.rb new file mode 100644 index 0000000000..278a81d287 --- /dev/null +++ b/codes/ruby/utils/list_node.rb @@ -0,0 +1,38 @@ +=begin +File: list_node.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 链表节点类 ### +class ListNode + attr_accessor :val # 节点值 + attr_accessor :next # 指向下一节点的引用 + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end +end + +### 将列表反序列化为链表 ### +def arr_to_linked_list(arr) + head = current = ListNode.new(arr[0]) + + for i in 1...arr.length + current.next = ListNode.new(arr[i]) + current = current.next + end + + head +end + +### 将链表序列化为列表 ### +def linked_list_to_arr(head) + arr = [] + + while head + arr << head.val + head = head.next + end +end diff --git a/codes/ruby/utils/print_util.rb b/codes/ruby/utils/print_util.rb new file mode 100644 index 0000000000..75f6b9fb4e --- /dev/null +++ b/codes/ruby/utils/print_util.rb @@ -0,0 +1,80 @@ +=begin +File: print_util.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative "./tree_node" + +### 打印矩阵 ### +def print_matrix(mat) + s = [] + mat.each { |arr| s << " #{arr.to_s}" } + puts "[\n#{s.join(",\n")}\n]" +end + +### 打印链表 ### +def print_linked_list(head) + list = [] + while head + list << head.val + head = head.next + end + puts "#{list.join(" -> ")}" +end + +class Trunk + attr_accessor :prev, :str + + def initialize(prev, str) + @prev = prev + @str = str + end +end + +def show_trunk(p) + return if p.nil? + + show_trunk(p.prev) + print p.str +end + +### 打印二叉树 ### +# This tree printer is borrowed from TECHIE DELIGHT +# https://www.techiedelight.com/c-program-print-binary-tree/ +def print_tree(root, prev=nil, is_right=false) + return if root.nil? + + prev_str = " " + trunk = Trunk.new(prev, prev_str) + print_tree(root.right, trunk, true) + + if prev.nil? + trunk.str = "———" + elsif is_right + trunk.str = "/———" + prev_str = " |" + else + trunk.str = "\\———" + prev.str = prev_str + end + + show_trunk(trunk) + puts " #{root.val}" + prev.str = prev_str if prev + trunk.str = " |" + print_tree(root.left, trunk, false) +end + +### 打印哈希表 ### +def print_hash_map(hmap) + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } +end + +### 打印堆 ### +def print_heap(heap) + puts "堆的数组表示:#{heap}" + puts "堆的树状表示:" + root = arr_to_tree(heap) + print_tree(root) +end diff --git a/codes/ruby/utils/tree_node.rb b/codes/ruby/utils/tree_node.rb new file mode 100644 index 0000000000..58f0989963 --- /dev/null +++ b/codes/ruby/utils/tree_node.rb @@ -0,0 +1,53 @@ +=begin +File: tree_node.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二叉树节点类 ### +class TreeNode + attr_accessor :val # 节点值 + attr_accessor :height # 节点高度 + attr_accessor :left # 左子节点引用 + attr_accessor :right # 右子节点引用 + + def initialize(val=0) + @val = val + @height = 0 + end +end + +### 将列表反序列化为二叉数树:递归 ### +def arr_to_tree_dfs(arr, i) + # 如果索引超出数组长度,或者对应的元素为 nil ,则返回 nil + return if i < 0 || i >= arr.length || arr[i].nil? + # 构建当前节点 + root = TreeNode.new(arr[i]) + # 递归构建左右子树 + root.left = arr_to_tree_dfs(arr, 2 * i + 1) + root.right = arr_to_tree_dfs(arr, 2 * i + 2) + root +end + +### 将列表反序列化为二叉树 ### +def arr_to_tree(arr) + arr_to_tree_dfs(arr, 0) +end + +### 将二叉树序列化为列表:递归 ### +def tree_to_arr_dfs(root, i, res) + return if root.nil? + + res += Array.new(i - res.length + 1) if i >= res.length + res[i] = root.val + + tree_to_arr_dfs(root.left, 2 * i + 1, res) + tree_to_arr_dfs(root.right, 2 * i + 2, res) +end + +### 将二叉树序列化为列表 ### +def tree_to_arr(root) + res = [] + tree_to_arr_dfs(root, 0, res) + res +end diff --git a/codes/ruby/utils/vertex.rb b/codes/ruby/utils/vertex.rb new file mode 100644 index 0000000000..bf40e1ea5b --- /dev/null +++ b/codes/ruby/utils/vertex.rb @@ -0,0 +1,24 @@ +=begin +File: vertex.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 顶点类 ### +class Vertex + attr_accessor :val + + def initialize(val) + @val = val + end +end + +### 输入值列表 vals ,返回顶点列表 vets ### +def vals_to_vets(vals) + Array.new(vals.length) { |i| Vertex.new(vals[i]) } +end + +### 输入顶点列表 vets, 返回值列表 vals ### +def vets_to_vals(vets) + Array.new(vets.length) { |i| vets[i].val } +end diff --git a/codes/rust/.gitignore b/codes/rust/.gitignore new file mode 100644 index 0000000000..4470988469 --- /dev/null +++ b/codes/rust/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/codes/rust/Cargo.toml b/codes/rust/Cargo.toml new file mode 100644 index 0000000000..160f9134cf --- /dev/null +++ b/codes/rust/Cargo.toml @@ -0,0 +1,413 @@ +[package] +name = "hello-algo-rust" +version = "0.1.0" +edition = "2021" +publish = false + +# Run Command: cargo run --bin time_complexity +[[bin]] +name = "time_complexity" +path = "chapter_computational_complexity/time_complexity.rs" + +# Run Command: cargo run --bin worst_best_time_complexity +[[bin]] +name = "worst_best_time_complexity" +path = "chapter_computational_complexity/worst_best_time_complexity.rs" + +# Run Command: cargo run --bin space_complexity +[[bin]] +name = "space_complexity" +path = "chapter_computational_complexity/space_complexity.rs" + +# Run Command: cargo run --bin iteration +[[bin]] +name = "iteration" +path = "chapter_computational_complexity/iteration.rs" + +# Run Command: cargo run --bin recursion +[[bin]] +name = "recursion" +path = "chapter_computational_complexity/recursion.rs" + +# Run Command: cargo run --bin two_sum +[[bin]] +name = "two_sum" +path = "chapter_searching/two_sum.rs" + +# Run Command: cargo run --bin array +[[bin]] +name = "array" +path = "chapter_array_and_linkedlist/array.rs" + +# Run Command: cargo run --bin linked_list +[[bin]] +name = "linked_list" +path = "chapter_array_and_linkedlist/linked_list.rs" + +# Run Command: cargo run --bin list +[[bin]] +name = "list" +path = "chapter_array_and_linkedlist/list.rs" + +# Run Command: cargo run --bin my_list +[[bin]] +name = "my_list" +path = "chapter_array_and_linkedlist/my_list.rs" + +# Run Command: cargo run --bin stack +[[bin]] +name = "stack" +path = "chapter_stack_and_queue/stack.rs" + +# Run Command: cargo run --bin linkedlist_stack +[[bin]] +name = "linkedlist_stack" +path = "chapter_stack_and_queue/linkedlist_stack.rs" + +# Run Command: cargo run --bin queue +[[bin]] +name = "queue" +path = "chapter_stack_and_queue/queue.rs" + +# Run Command: cargo run --bin linkedlist_queue +[[bin]] +name = "linkedlist_queue" +path = "chapter_stack_and_queue/linkedlist_queue.rs" + +# Run Command: cargo run --bin deque +[[bin]] +name = "deque" +path = "chapter_stack_and_queue/deque.rs" + +# Run Command: cargo run --bin array_deque +[[bin]] +name = "array_deque" +path = "chapter_stack_and_queue/array_deque.rs" + +# Run Command: cargo run --bin linkedlist_deque +[[bin]] +name = "linkedlist_deque" +path = "chapter_stack_and_queue/linkedlist_deque.rs" + +# Run Command: cargo run --bin simple_hash +[[bin]] +name = "simple_hash" +path = "chapter_hashing/simple_hash.rs" + +# Run Command: cargo run --bin hash_map +[[bin]] +name = "hash_map" +path = "chapter_hashing/hash_map.rs" + +# Run Command: cargo run --bin array_hash_map +[[bin]] +name = "array_hash_map" +path = "chapter_hashing/array_hash_map.rs" + +# Run Command: cargo run --bin build_in_hash +[[bin]] +name = "build_in_hash" +path = "chapter_hashing/build_in_hash.rs" + +# Run Command: cargo run --bin hash_map_chaining +[[bin]] +name = "hash_map_chaining" +path = "chapter_hashing/hash_map_chaining.rs" + +# Run Command: cargo run --bin hash_map_open_addressing +[[bin]] +name = "hash_map_open_addressing" +path = "chapter_hashing/hash_map_open_addressing.rs" + +# Run Command: cargo run --bin binary_search +[[bin]] +name = "binary_search" +path = "chapter_searching/binary_search.rs" + +# Run Command: cargo run --bin binary_search_edge +[[bin]] +name = "binary_search_edge" +path = "chapter_searching/binary_search_edge.rs" + +# Run Command: cargo run --bin binary_search_insertion +[[bin]] +name = "binary_search_insertion" +path = "chapter_searching/binary_search_insertion.rs" + +# Run Command: cargo run --bin bubble_sort +[[bin]] +name = "bubble_sort" +path = "chapter_sorting/bubble_sort.rs" + +# Run Command: cargo run --bin insertion_sort +[[bin]] +name = "insertion_sort" +path = "chapter_sorting/insertion_sort.rs" + +# Run Command: cargo run --bin quick_sort +[[bin]] +name = "quick_sort" +path = "chapter_sorting/quick_sort.rs" + +# Run Command: cargo run --bin merge_sort +[[bin]] +name = "merge_sort" +path = "chapter_sorting/merge_sort.rs" + +# Run Command: cargo run --bin selection_sort +[[bin]] +name = "selection_sort" +path = "chapter_sorting/selection_sort.rs" + +# Run Command: cargo run --bin bucket_sort +[[bin]] +name = "bucket_sort" +path = "chapter_sorting/bucket_sort.rs" + +# Run Command: cargo run --bin heap_sort +[[bin]] +name = "heap_sort" +path = "chapter_sorting/heap_sort.rs" + +# Run Command: cargo run --bin counting_sort +[[bin]] +name = "counting_sort" +path = "chapter_sorting/counting_sort.rs" + +# Run Command: cargo run --bin radix_sort +[[bin]] +name = "radix_sort" +path = "chapter_sorting/radix_sort.rs" + +# Run Command: cargo run --bin array_stack +[[bin]] +name = "array_stack" +path = "chapter_stack_and_queue/array_stack.rs" + +# Run Command: cargo run --bin array_queue +[[bin]] +name = "array_queue" +path = "chapter_stack_and_queue/array_queue.rs" + +# Run Command: cargo run --bin array_binary_tree +[[bin]] +name = "array_binary_tree" +path = "chapter_tree/array_binary_tree.rs" + +# Run Command: cargo run --bin avl_tree +[[bin]] +name = "avl_tree" +path = "chapter_tree/avl_tree.rs" + +# Run Command: cargo run --bin binary_search_tree +[[bin]] +name = "binary_search_tree" +path = "chapter_tree/binary_search_tree.rs" + +# Run Command: cargo run --bin binary_tree_bfs +[[bin]] +name = "binary_tree_bfs" +path = "chapter_tree/binary_tree_bfs.rs" + +# Run Command: cargo run --bin binary_tree_dfs +[[bin]] +name = "binary_tree_dfs" +path = "chapter_tree/binary_tree_dfs.rs" + +# Run Command: cargo run --bin binary_tree +[[bin]] +name = "binary_tree" +path = "chapter_tree/binary_tree.rs" + +# Run Command: cargo run --bin heap +[[bin]] +name = "heap" +path = "chapter_heap/heap.rs" + +# Run Command: cargo run --bin my_heap +[[bin]] +name = "my_heap" +path = "chapter_heap/my_heap.rs" + +# Run Command: cargo run --bin top_k +[[bin]] +name = "top_k" +path = "chapter_heap/top_k.rs" + +# Run Command: cargo run --bin graph_adjacency_list +[[bin]] +name = "graph_adjacency_list" +path = "chapter_graph/graph_adjacency_list.rs" + +# Run Command: cargo run --bin graph_adjacency_matrix +[[bin]] +name = "graph_adjacency_matrix" +path = "chapter_graph/graph_adjacency_matrix.rs" + +# Run Command: cargo run --bin graph_bfs +[[bin]] +name = "graph_bfs" +path = "chapter_graph/graph_bfs.rs" + +# Run Command: cargo run --bin graph_dfs +[[bin]] +name = "graph_dfs" +path = "chapter_graph/graph_dfs.rs" + +# Run Command: cargo run --bin linear_search +[[bin]] +name = "linear_search" +path = "chapter_searching/linear_search.rs" + +# Run Command: cargo run --bin hashing_search +[[bin]] +name = "hashing_search" +path = "chapter_searching/hashing_search.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs +[[bin]] +name = "climbing_stairs_dfs" +path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs_mem +[[bin]] +name = "climbing_stairs_dfs_mem" +path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" + +# Run Command: cargo run --bin climbing_stairs_dp +[[bin]] +name = "climbing_stairs_dp" +path = "chapter_dynamic_programming/climbing_stairs_dp.rs" + +# Run Command: cargo run --bin min_cost_climbing_stairs_dp +[[bin]] +name = "min_cost_climbing_stairs_dp" +path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_constraint_dp +[[bin]] +name = "climbing_stairs_constraint_dp" +path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_backtrack +[[bin]] +name = "climbing_stairs_backtrack" +path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" + +# Run Command: cargo run --bin subset_sum_i_naive +[[bin]] +name = "subset_sum_i_naive" +path = "chapter_backtracking/subset_sum_i_naive.rs" + +# Run Command: cargo run --bin subset_sum_i +[[bin]] +name = "subset_sum_i" +path = "chapter_backtracking/subset_sum_i.rs" + +# Run Command: cargo run --bin subset_sum_ii +[[bin]] +name = "subset_sum_ii" +path = "chapter_backtracking/subset_sum_ii.rs" + +# Run Command: cargo run --bin coin_change +[[bin]] +name = "coin_change" +path = "chapter_dynamic_programming/coin_change.rs" + +# Run Command: cargo run --bin coin_change_ii +[[bin]] +name = "coin_change_ii" +path = "chapter_dynamic_programming/coin_change_ii.rs" + +# Run Command: cargo run --bin unbounded_knapsack +[[bin]] +name = "unbounded_knapsack" +path = "chapter_dynamic_programming/unbounded_knapsack.rs" + +# Run Command: cargo run --bin knapsack +[[bin]] +name = "knapsack" +path = "chapter_dynamic_programming/knapsack.rs" + +# Run Command: cargo run --bin min_path_sum +[[bin]] +name = "min_path_sum" +path = "chapter_dynamic_programming/min_path_sum.rs" + +# Run Command: cargo run --bin edit_distance +[[bin]] +name = "edit_distance" +path = "chapter_dynamic_programming/edit_distance.rs" + +# Run Command: cargo run --bin n_queens +[[bin]] +name = "n_queens" +path = "chapter_backtracking/n_queens.rs" + +# Run Command: cargo run --bin permutations_i +[[bin]] +name = "permutations_i" +path = "chapter_backtracking/permutations_i.rs" + +# Run Command: cargo run --bin permutations_ii +[[bin]] +name = "permutations_ii" +path = "chapter_backtracking/permutations_ii.rs" + +# Run Command: cargo run --bin preorder_traversal_i_compact +[[bin]] +name = "preorder_traversal_i_compact" +path = "chapter_backtracking/preorder_traversal_i_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_ii_compact +[[bin]] +name = "preorder_traversal_ii_compact" +path = "chapter_backtracking/preorder_traversal_ii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_compact +[[bin]] +name = "preorder_traversal_iii_compact" +path = "chapter_backtracking/preorder_traversal_iii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_template +[[bin]] +name = "preorder_traversal_iii_template" +path = "chapter_backtracking/preorder_traversal_iii_template.rs" + +# Run Command: cargo run --bin binary_search_recur +[[bin]] +name = "binary_search_recur" +path = "chapter_divide_and_conquer/binary_search_recur.rs" + +# Run Command: cargo run --bin hanota +[[bin]] +name = "hanota" +path = "chapter_divide_and_conquer/hanota.rs" + +# Run Command: cargo run --bin build_tree +[[bin]] +name = "build_tree" +path = "chapter_divide_and_conquer/build_tree.rs" + +# Run Command: cargo run --bin coin_change_greedy +[[bin]] +name = "coin_change_greedy" +path = "chapter_greedy/coin_change_greedy.rs" + +# Run Command: cargo run --bin fractional_knapsack +[[bin]] +name = "fractional_knapsack" +path = "chapter_greedy/fractional_knapsack.rs" + +# Run Command: cargo run --bin max_capacity +[[bin]] +name = "max_capacity" +path = "chapter_greedy/max_capacity.rs" + +# Run Command: cargo run --bin max_product_cutting +[[bin]] +name = "max_product_cutting" +path = "chapter_greedy/max_product_cutting.rs" + +[dependencies] +rand = "0.8.5" diff --git a/codes/rust/chapter_array_and_linkedlist/array.rs b/codes/rust/chapter_array_and_linkedlist/array.rs new file mode 100644 index 0000000000..29241d2ec2 --- /dev/null +++ b/codes/rust/chapter_array_and_linkedlist/array.rs @@ -0,0 +1,111 @@ +/* + * File: array.rs + * Created Time: 2023-01-15 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::Rng; + +/* 随机访问元素 */ +fn random_access(nums: &[i32]) -> i32 { + // 在区间 [0, nums.len()) 中随机抽取一个数字 + let random_index = rand::thread_rng().gen_range(0..nums.len()); + // 获取并返回随机元素 + let random_num = nums[random_index]; + random_num +} + +/* 扩展数组长度 */ +fn extend(nums: &[i32], enlarge: usize) -> Vec { + // 初始化一个扩展长度后的数组 + let mut res: Vec = vec![0; nums.len() + enlarge]; + // 将原数组中的所有元素复制到新 + res[0..nums.len()].copy_from_slice(nums); + + // 返回扩展后的新数组 + res +} + +/* 在数组的索引 index 处插入元素 num */ +fn insert(nums: &mut [i32], num: i32, index: usize) { + // 把索引 index 以及之后的所有元素向后移动一位 + for i in (index + 1..nums.len()).rev() { + nums[i] = nums[i - 1]; + } + // 将 num 赋给 index 处的元素 + nums[index] = num; +} + +/* 删除索引 index 处的元素 */ +fn remove(nums: &mut [i32], index: usize) { + // 把索引 index 之后的所有元素向前移动一位 + for i in index..nums.len() - 1 { + nums[i] = nums[i + 1]; + } +} + +/* 遍历数组 */ +fn traverse(nums: &[i32]) { + let mut _count = 0; + // 通过索引遍历数组 + for i in 0..nums.len() { + _count += nums[i]; + } + // 直接遍历数组元素 + _count = 0; + for &num in nums { + _count += num; + } +} + +/* 在数组中查找指定元素 */ +fn find(nums: &[i32], target: i32) -> Option { + for i in 0..nums.len() { + if nums[i] == target { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + /* 初始化数组 */ + let arr: [i32; 5] = [0; 5]; + print!("数组 arr = "); + print_util::print_array(&arr); + // 在 Rust 中,指定长度时([i32; 5])为数组,不指定长度时(&[i32])为切片 + // 由于 Rust 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 + // Vector 是 Rust 一般情况下用作动态数组的类型 + // 为了方便实现扩容 extend() 方法,以下将 vector 看作数组(array) + let nums: Vec = vec![1, 3, 2, 5, 4]; + print!("\n数组 nums = "); + print_util::print_array(&nums); + + // 随机访问 + let random_num = random_access(&nums); + println!("\n在 nums 中获取随机元素 {}", random_num); + + // 长度扩展 + let mut nums: Vec = extend(&nums, 3); + print!("将数组长度扩展至 8 ,得到 nums = "); + print_util::print_array(&nums); + + // 插入元素 + insert(&mut nums, 6, 3); + print!("\n在索引 3 处插入数字 6 ,得到 nums = "); + print_util::print_array(&nums); + + // 删除元素 + remove(&mut nums, 2); + print!("\n删除索引 2 处的元素,得到 nums = "); + print_util::print_array(&nums); + + // 遍历数组 + traverse(&nums); + + // 查找元素 + let index = find(&nums, 3).unwrap(); + println!("\n在 nums 中查找元素 3 ,得到索引 = {}", index); +} diff --git a/codes/rust/chapter_array_and_linkedlist/linked_list.rs b/codes/rust/chapter_array_and_linkedlist/linked_list.rs new file mode 100644 index 0000000000..99ac5c5302 --- /dev/null +++ b/codes/rust/chapter_array_and_linkedlist/linked_list.rs @@ -0,0 +1,100 @@ +/* + * File: linked_list.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; +use std::cell::RefCell; +use std::rc::Rc; + +/* 在链表的节点 n0 之后插入节点 P */ +#[allow(non_snake_case)] +pub fn insert(n0: &Rc>>, P: Rc>>) { + let n1 = n0.borrow_mut().next.take(); + P.borrow_mut().next = n1; + n0.borrow_mut().next = Some(P); +} + +/* 删除链表的节点 n0 之后的首个节点 */ +#[allow(non_snake_case)] +pub fn remove(n0: &Rc>>) { + // n0 -> P -> n1 + let P = n0.borrow_mut().next.take(); + if let Some(node) = P { + let n1 = node.borrow_mut().next.take(); + n0.borrow_mut().next = n1; + } +} + +/* 访问链表中索引为 index 的节点 */ +pub fn access(head: Rc>>, index: i32) -> Option>>> { + fn dfs( + head: Option<&Rc>>>, + index: i32, + ) -> Option>>> { + if index <= 0 { + return head.cloned(); + } + + if let Some(node) = head { + dfs(node.borrow().next.as_ref(), index - 1) + } else { + None + } + } + + dfs(Some(head).as_ref(), index) +} + +/* 在链表中查找值为 target 的首个节点 */ +pub fn find(head: Rc>>, target: T) -> i32 { + fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { + if let Some(node) = head { + if node.borrow().val == target { + return idx; + } + return find(node.borrow().next.as_ref(), target, idx + 1); + } else { + -1 + } + } + + find(Some(head).as_ref(), target, 0) +} + +/* Driver Code */ +fn main() { + /* 初始化链表 */ + // 初始化各个节点 + let n0 = ListNode::new(1); + let n1 = ListNode::new(3); + let n2 = ListNode::new(2); + let n3 = ListNode::new(5); + let n4 = ListNode::new(4); + // 构建节点之间的引用 + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + print!("初始化的链表为 "); + print_util::print_linked_list(&n0); + + /* 插入节点 */ + insert(&n0, ListNode::new(0)); + print!("插入节点后的链表为 "); + print_util::print_linked_list(&n0); + + /* 删除节点 */ + remove(&n0); + print!("删除节点后的链表为 "); + print_util::print_linked_list(&n0); + + /* 访问节点 */ + let node = access(n0.clone(), 3); + println!("链表中索引 3 处的节点的值 = {}", node.unwrap().borrow().val); + + /* 查找节点 */ + let index = find(n0.clone(), 2); + println!("链表中值为 2 的节点的索引 = {}", index); +} diff --git a/codes/rust/chapter_array_and_linkedlist/list.rs b/codes/rust/chapter_array_and_linkedlist/list.rs new file mode 100644 index 0000000000..d2f267be3b --- /dev/null +++ b/codes/rust/chapter_array_and_linkedlist/list.rs @@ -0,0 +1,71 @@ +/* + * File: list.rs + * Created Time: 2023-01-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; + +/* Driver Code */ +fn main() { + // 初始化列表 + let mut nums: Vec = vec![1, 3, 2, 5, 4]; + print!("列表 nums = "); + print_util::print_array(&nums); + + // 访问元素 + let num = nums[1]; + println!("\n访问索引 1 处的元素,得到 num = {num}"); + + // 更新元素 + nums[1] = 0; + print!("将索引 1 处的元素更新为 0 ,得到 nums = "); + print_util::print_array(&nums); + + // 清空列表 + nums.clear(); + print!("\n清空列表后 nums = "); + print_util::print_array(&nums); + + // 在尾部添加元素 + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + print!("\n添加元素后 nums = "); + print_util::print_array(&nums); + + // 在中间插入元素 + nums.insert(3, 6); + print!("\n在索引 3 处插入数字 6 ,得到 nums = "); + print_util::print_array(&nums); + + // 删除元素 + nums.remove(3); + print!("\n删除索引 3 处的元素,得到 nums = "); + print_util::print_array(&nums); + + // 通过索引遍历列表 + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + // 直接遍历列表元素 + _count = 0; + for x in &nums { + _count += x; + } + + // 拼接两个列表 + let mut nums1 = vec![6, 8, 7, 10, 9]; + nums.append(&mut nums1); // append(移动) 之后 nums1 为空! + + // nums.extend(&nums1); // extend(借用) nums1 能继续使用 + print!("\n将列表 nums1 拼接到 nums 之后,得到 nums = "); + print_util::print_array(&nums); + + // 排序列表 + nums.sort(); + print!("\n排序列表后 nums = "); + print_util::print_array(&nums); +} diff --git a/codes/rust/chapter_array_and_linkedlist/my_list.rs b/codes/rust/chapter_array_and_linkedlist/my_list.rs new file mode 100644 index 0000000000..1ed7073065 --- /dev/null +++ b/codes/rust/chapter_array_and_linkedlist/my_list.rs @@ -0,0 +1,164 @@ +/* + * File: my_list.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* 列表类 */ +#[allow(dead_code)] +struct MyList { + arr: Vec, // 数组(存储列表元素) + capacity: usize, // 列表容量 + size: usize, // 列表长度(当前元素数量) + extend_ratio: usize, // 每次列表扩容的倍数 +} + +#[allow(unused, unused_comparisons)] +impl MyList { + /* 构造方法 */ + pub fn new(capacity: usize) -> Self { + let mut vec = vec![0; capacity]; + Self { + arr: vec, + capacity, + size: 0, + extend_ratio: 2, + } + } + + /* 获取列表长度(当前元素数量)*/ + pub fn size(&self) -> usize { + return self.size; + } + + /* 获取列表容量 */ + pub fn capacity(&self) -> usize { + return self.capacity; + } + + /* 访问元素 */ + pub fn get(&self, index: usize) -> i32 { + // 索引如果越界,则抛出异常,下同 + if index >= self.size { + panic!("索引越界") + }; + return self.arr[index]; + } + + /* 更新元素 */ + pub fn set(&mut self, index: usize, num: i32) { + if index >= self.size { + panic!("索引越界") + }; + self.arr[index] = num; + } + + /* 在尾部添加元素 */ + pub fn add(&mut self, num: i32) { + // 元素数量超出容量时,触发扩容机制 + if self.size == self.capacity() { + self.extend_capacity(); + } + self.arr[self.size] = num; + // 更新元素数量 + self.size += 1; + } + + /* 在中间插入元素 */ + pub fn insert(&mut self, index: usize, num: i32) { + if index >= self.size() { + panic!("索引越界") + }; + // 元素数量超出容量时,触发扩容机制 + if self.size == self.capacity() { + self.extend_capacity(); + } + // 将索引 index 以及之后的元素都向后移动一位 + for j in (index..self.size).rev() { + self.arr[j + 1] = self.arr[j]; + } + self.arr[index] = num; + // 更新元素数量 + self.size += 1; + } + + /* 删除元素 */ + pub fn remove(&mut self, index: usize) -> i32 { + if index >= self.size() { + panic!("索引越界") + }; + let num = self.arr[index]; + // 将将索引 index 之后的元素都向前移动一位 + for j in index..self.size - 1 { + self.arr[j] = self.arr[j + 1]; + } + // 更新元素数量 + self.size -= 1; + // 返回被删除的元素 + return num; + } + + /* 列表扩容 */ + pub fn extend_capacity(&mut self) { + // 新建一个长度为原数组 extend_ratio 倍的新数组,并将原数组复制到新数组 + let new_capacity = self.capacity * self.extend_ratio; + self.arr.resize(new_capacity, 0); + // 更新列表容量 + self.capacity = new_capacity; + } + + /* 将列表转换为数组 */ + pub fn to_array(&self) -> Vec { + // 仅转换有效长度范围内的列表元素 + let mut arr = Vec::new(); + for i in 0..self.size { + arr.push(self.get(i)); + } + arr + } +} + +/* Driver Code */ +fn main() { + /* 初始化列表 */ + let mut nums = MyList::new(10); + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print!("列表 nums = "); + print_util::print_array(&nums.to_array()); + print!(" ,容量 = {} ,长度 = {}", nums.capacity(), nums.size()); + + /* 在中间插入元素 */ + nums.insert(3, 6); + print!("\n在索引 3 处插入数字 6 ,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 删除元素 */ + nums.remove(3); + print!("\n删除索引 3 处的元素,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 访问元素 */ + let num = nums.get(1); + println!("\n访问索引 1 处的元素,得到 num = {num}"); + + /* 更新元素 */ + nums.set(1, 0); + print!("将索引 1 处的元素更新为 0 ,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 测试扩容机制 */ + for i in 0..10 { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + nums.add(i); + } + print!("\n扩容后的列表 nums = "); + print_util::print_array(&nums.to_array()); + print!(" ,容量 = {} ,长度 = {}", nums.capacity(), nums.size()); +} diff --git a/codes/rust/chapter_backtracking/n_queens.rs b/codes/rust/chapter_backtracking/n_queens.rs new file mode 100644 index 0000000000..cf727a13eb --- /dev/null +++ b/codes/rust/chapter_backtracking/n_queens.rs @@ -0,0 +1,76 @@ +/* + * File: n_queens.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯算法:n 皇后 */ +fn backtrack( + row: usize, + n: usize, + state: &mut Vec>, + res: &mut Vec>>, + cols: &mut [bool], + diags1: &mut [bool], + diags2: &mut [bool], +) { + // 当放置完所有行时,记录解 + if row == n { + res.push(state.clone()); + return; + } + // 遍历所有列 + for col in 0..n { + // 计算该格子对应的主对角线和次对角线 + let diag1 = row + n - 1 - col; + let diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 尝试:将皇后放置在该格子 + state[row][col] = "Q".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = "#".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); + } + } +} + +/* 求解 n 皇后 */ +fn n_queens(n: usize) -> Vec>> { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; + let mut cols = vec![false; n]; // 记录列是否有皇后 + let mut diags1 = vec![false; 2 * n - 1]; // 记录主对角线上是否有皇后 + let mut diags2 = vec![false; 2 * n - 1]; // 记录次对角线上是否有皇后 + let mut res: Vec>> = Vec::new(); + + backtrack( + 0, + n, + &mut state, + &mut res, + &mut cols, + &mut diags1, + &mut diags2, + ); + + res +} + +/* Driver Code */ +pub fn main() { + let n: usize = 4; + let res = n_queens(n); + + println!("输入棋盘长宽为 {n}"); + println!("皇后放置方案共有 {} 种", res.len()); + for state in res.iter() { + println!("--------------------"); + for row in state.iter() { + println!("{:?}", row); + } + } +} diff --git a/codes/rust/chapter_backtracking/permutations_i.rs b/codes/rust/chapter_backtracking/permutations_i.rs new file mode 100644 index 0000000000..3e2eec41f3 --- /dev/null +++ b/codes/rust/chapter_backtracking/permutations_i.rs @@ -0,0 +1,46 @@ +/* + * File: permutations_i.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯算法:全排列 I */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 当状态长度等于元素数量时,记录解 + if state.len() == choices.len() { + res.push(state); + return; + } + // 遍历所有选择 + for i in 0..choices.len() { + let choice = choices[i]; + // 剪枝:不允许重复选择元素 + if !selected[i] { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.push(choice); + // 进行下一轮选择 + backtrack(state.clone(), choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop(); + } + } +} + +/* 全排列 I */ +fn permutations_i(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); // 状态(子集) + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 3]; + + let res = permutations_i(&mut nums); + + println!("输入数组 nums = {:?}", &nums); + println!("所有排列 res = {:?}", &res); +} diff --git a/codes/rust/chapter_backtracking/permutations_ii.rs b/codes/rust/chapter_backtracking/permutations_ii.rs new file mode 100644 index 0000000000..d2db569289 --- /dev/null +++ b/codes/rust/chapter_backtracking/permutations_ii.rs @@ -0,0 +1,50 @@ +/* + * File: permutations_ii.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::collections::HashSet; + +/* 回溯算法:全排列 II */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 当状态长度等于元素数量时,记录解 + if state.len() == choices.len() { + res.push(state); + return; + } + // 遍历所有选择 + let mut duplicated = HashSet::::new(); + for i in 0..choices.len() { + let choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if !selected[i] && !duplicated.contains(&choice) { + // 尝试:做出选择,更新状态 + duplicated.insert(choice); // 记录选择过的元素值 + selected[i] = true; + state.push(choice); + // 进行下一轮选择 + backtrack(state.clone(), choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop(); + } + } +} + +/* 全排列 II */ +fn permutations_ii(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 2]; + + let res = permutations_ii(&mut nums); + + println!("输入数组 nums = {:?}", &nums); + println!("所有排列 res = {:?}", &res); +} diff --git a/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs b/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs new file mode 100644 index 0000000000..fc69939522 --- /dev/null +++ b/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs @@ -0,0 +1,41 @@ +/* + * File: preorder_traversal_i_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前序遍历:例题一 */ +fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { + if root.is_none() { + return; + } + if let Some(node) = root { + if node.borrow().val == 7 { + // 记录解 + res.push(node.clone()); + } + pre_order(res, node.borrow().left.as_ref()); + pre_order(res, node.borrow().right.as_ref()); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二叉树"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序遍历 + let mut res = Vec::new(); + pre_order(&mut res, root.as_ref()); + + println!("\n输出所有值为 7 的节点"); + let mut vals = Vec::new(); + for node in res { + vals.push(node.borrow().val) + } + println!("{:?}", vals); +} diff --git a/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs b/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs new file mode 100644 index 0000000000..67ce5aee80 --- /dev/null +++ b/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs @@ -0,0 +1,52 @@ +/* + * File: preorder_traversal_ii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前序遍历:例题二 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + if root.is_none() { + return; + } + if let Some(node) = root { + // 尝试 + path.push(node.clone()); + if node.borrow().val == 7 { + // 记录解 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // 回退 + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二叉树"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序遍历 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\n输出所有根节点到节点 7 的路径"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs b/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs new file mode 100644 index 0000000000..9e54e66bd0 --- /dev/null +++ b/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs @@ -0,0 +1,53 @@ +/* + * File: preorder_traversal_iii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前序遍历:例题三 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + // 剪枝 + if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { + return; + } + if let Some(node) = root { + // 尝试 + path.push(node.clone()); + if node.borrow().val == 7 { + // 记录解 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // 回退 + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二叉树"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序遍历 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs b/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs new file mode 100644 index 0000000000..e7681b11b5 --- /dev/null +++ b/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs @@ -0,0 +1,88 @@ +/* + * File: preorder_traversal_iii_template.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 判断当前状态是否为解 */ +fn is_solution(state: &mut Vec>>) -> bool { + return !state.is_empty() && state.last().unwrap().borrow().val == 7; +} + +/* 记录解 */ +fn record_solution( + state: &mut Vec>>, + res: &mut Vec>>>, +) { + res.push(state.clone()); +} + +/* 判断在当前状态下,该选择是否合法 */ +fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { + return choice.is_some() && choice.unwrap().borrow().val != 3; +} + +/* 更新状态 */ +fn make_choice(state: &mut Vec>>, choice: Rc>) { + state.push(choice); +} + +/* 恢复状态 */ +fn undo_choice(state: &mut Vec>>, _: Rc>) { + state.pop(); +} + +/* 回溯算法:例题三 */ +fn backtrack( + state: &mut Vec>>, + choices: &Vec>>>, + res: &mut Vec>>>, +) { + // 检查是否为解 + if is_solution(state) { + // 记录解 + record_solution(state, res); + } + // 遍历所有选择 + for &choice in choices.iter() { + // 剪枝:检查选择是否合法 + if is_valid(state, choice) { + // 尝试:做出选择,更新状态 + make_choice(state, choice.unwrap().clone()); + // 进行下一轮选择 + backtrack( + state, + &vec![ + choice.unwrap().borrow().left.as_ref(), + choice.unwrap().borrow().right.as_ref(), + ], + res, + ); + // 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice.unwrap().clone()); + } + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二叉树"); + print_util::print_tree(root.as_ref().unwrap()); + + // 回溯算法 + let mut res = Vec::new(); + backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); + + println!("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/codes/rust/chapter_backtracking/subset_sum_i.rs b/codes/rust/chapter_backtracking/subset_sum_i.rs new file mode 100644 index 0000000000..052181d60d --- /dev/null +++ b/codes/rust/chapter_backtracking/subset_sum_i.rs @@ -0,0 +1,56 @@ +/* + * File: subset_sum_i.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯算法:子集和 I */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 子集和等于 target 时,记录解 + if target == 0 { + res.push(state.clone()); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in start..choices.len() { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 { + break; + } + // 尝试:做出选择,更新 target, start + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 I */ +fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 状态(子集) + nums.sort(); // 对 nums 进行排序 + let start = 0; // 遍历起始点 + let mut res = Vec::new(); // 结果列表(子集列表) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i(&mut nums, target); + + println!("输入数组 nums = {:?}, target = {}", &nums, target); + println!("所有和等于 {} 的子集 res = {:?}", target, &res); +} diff --git a/codes/rust/chapter_backtracking/subset_sum_i_naive.rs b/codes/rust/chapter_backtracking/subset_sum_i_naive.rs new file mode 100644 index 0000000000..ab28a2f6a6 --- /dev/null +++ b/codes/rust/chapter_backtracking/subset_sum_i_naive.rs @@ -0,0 +1,54 @@ +/* + * File: subset_sum_i_naive.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯算法:子集和 I */ +fn backtrack( + state: &mut Vec, + target: i32, + total: i32, + choices: &[i32], + res: &mut Vec>, +) { + // 子集和等于 target 时,记录解 + if total == target { + res.push(state.clone()); + return; + } + // 遍历所有选择 + for i in 0..choices.len() { + // 剪枝:若子集和超过 target ,则跳过该选择 + if total + choices[i] > target { + continue; + } + // 尝试:做出选择,更新元素和 total + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 I(包含重复子集) */ +fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 状态(子集) + let total = 0; // 子集和 + let mut res = Vec::new(); // 结果列表(子集列表) + backtrack(&mut state, target, total, nums, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i_naive(&nums, target); + + println!("输入数组 nums = {:?}, target = {}", &nums, target); + println!("所有和等于 {} 的子集 res = {:?}", target, &res); + println!("请注意,该方法输出的结果包含重复集合"); +} diff --git a/codes/rust/chapter_backtracking/subset_sum_ii.rs b/codes/rust/chapter_backtracking/subset_sum_ii.rs new file mode 100644 index 0000000000..389bae711d --- /dev/null +++ b/codes/rust/chapter_backtracking/subset_sum_ii.rs @@ -0,0 +1,61 @@ +/* + * File: subset_sum_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯算法:子集和 II */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 子集和等于 target 时,记录解 + if target == 0 { + res.push(state.clone()); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in start..choices.len() { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start && choices[i] == choices[i - 1] { + continue; + } + // 尝试:做出选择,更新 target, start + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 II */ +fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 状态(子集) + nums.sort(); // 对 nums 进行排序 + let start = 0; // 遍历起始点 + let mut res = Vec::new(); // 结果列表(子集列表) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 4, 5]; + let target = 9; + + let res = subset_sum_ii(&mut nums, target); + + println!("输入数组 nums = {:?}, target = {}", &nums, target); + println!("所有和等于 {} 的子集 res = {:?}", target, &res); +} diff --git a/codes/rust/chapter_computational_complexity/iteration.rs b/codes/rust/chapter_computational_complexity/iteration.rs new file mode 100644 index 0000000000..3117419414 --- /dev/null +++ b/codes/rust/chapter_computational_complexity/iteration.rs @@ -0,0 +1,74 @@ +/* + * File: iteration.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* for 循环 */ +fn for_loop(n: i32) -> i32 { + let mut res = 0; + // 循环求和 1, 2, ..., n-1, n + for i in 1..=n { + res += i; + } + res +} + +/* while 循环 */ +fn while_loop(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 初始化条件变量 + + // 循环求和 1, 2, ..., n-1, n + while i <= n { + res += i; + i += 1; // 更新条件变量 + } + res +} + +/* while 循环(两次更新) */ +fn while_loop_ii(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 初始化条件变量 + + // 循环求和 1, 4, 10, ... + while i <= n { + res += i; + // 更新条件变量 + i += 1; + i *= 2; + } + res +} + +/* 双层 for 循环 */ +fn nested_for_loop(n: i32) -> String { + let mut res = vec![]; + // 循环 i = 1, 2, ..., n-1, n + for i in 1..=n { + // 循环 j = 1, 2, ..., n-1, n + for j in 1..=n { + res.push(format!("({}, {}), ", i, j)); + } + } + res.join("") +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = for_loop(n); + println!("\nfor 循环的求和结果 res = {res}"); + + res = while_loop(n); + println!("\nwhile 循环的求和结果 res = {res}"); + + res = while_loop_ii(n); + println!("\nwhile 循环(两次更新)求和结果 res = {}", res); + + let res = nested_for_loop(n); + println!("\n双层 for 循环的遍历结果 {res}"); +} diff --git a/codes/rust/chapter_computational_complexity/recursion.rs b/codes/rust/chapter_computational_complexity/recursion.rs new file mode 100644 index 0000000000..6035c0362e --- /dev/null +++ b/codes/rust/chapter_computational_complexity/recursion.rs @@ -0,0 +1,76 @@ +/* + * File: recursion.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 递归 */ +fn recur(n: i32) -> i32 { + // 终止条件 + if n == 1 { + return 1; + } + // 递:递归调用 + let res = recur(n - 1); + // 归:返回结果 + n + res +} + +/* 使用迭代模拟递归 */ +fn for_loop_recur(n: i32) -> i32 { + // 使用一个显式的栈来模拟系统调用栈 + let mut stack = Vec::new(); + let mut res = 0; + // 递:递归调用 + for i in (1..=n).rev() { + // 通过“入栈操作”模拟“递” + stack.push(i); + } + // 归:返回结果 + while !stack.is_empty() { + // 通过“出栈操作”模拟“归” + res += stack.pop().unwrap(); + } + // res = 1+2+3+...+n + res +} + +/* 尾递归 */ +fn tail_recur(n: i32, res: i32) -> i32 { + // 终止条件 + if n == 0 { + return res; + } + // 尾递归调用 + tail_recur(n - 1, res + n) +} + +/* 斐波那契数列:递归 */ +fn fib(n: i32) -> i32 { + // 终止条件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1; + } + // 递归调用 f(n) = f(n-1) + f(n-2) + let res = fib(n - 1) + fib(n - 2); + // 返回结果 + res +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = recur(n); + println!("\n递归函数的求和结果 res = {res}"); + + res = for_loop_recur(n); + println!("\n使用迭代模拟递归求和结果 res = {res}"); + + res = tail_recur(n, 0); + println!("\n尾递归函数的求和结果 res = {res}"); + + res = fib(n); + println!("\n斐波那契数列的第 {n} 项为 {res}"); +} diff --git a/codes/rust/chapter_computational_complexity/space_complexity.rs b/codes/rust/chapter_computational_complexity/space_complexity.rs new file mode 100644 index 0000000000..d1583fe252 --- /dev/null +++ b/codes/rust/chapter_computational_complexity/space_complexity.rs @@ -0,0 +1,114 @@ +/* + * File: space_complexity.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode, TreeNode}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* 函数 */ +fn function() -> i32 { + // 执行某些操作 + return 0; +} + +/* 常数阶 */ +#[allow(unused)] +fn constant(n: i32) { + // 常量、变量、对象占用 O(1) 空间 + const A: i32 = 0; + let b = 0; + let nums = vec![0; 10000]; + let node = ListNode::new(0); + // 循环中的变量占用 O(1) 空间 + for i in 0..n { + let c = 0; + } + // 循环中的函数占用 O(1) 空间 + for i in 0..n { + function(); + } +} + +/* 线性阶 */ +#[allow(unused)] +fn linear(n: i32) { + // 长度为 n 的数组占用 O(n) 空间 + let mut nums = vec![0; n as usize]; + // 长度为 n 的列表占用 O(n) 空间 + let mut nodes = Vec::new(); + for i in 0..n { + nodes.push(ListNode::new(i)) + } + // 长度为 n 的哈希表占用 O(n) 空间 + let mut map = HashMap::new(); + for i in 0..n { + map.insert(i, i.to_string()); + } +} + +/* 线性阶(递归实现) */ +fn linear_recur(n: i32) { + println!("递归 n = {}", n); + if n == 1 { + return; + }; + linear_recur(n - 1); +} + +/* 平方阶 */ +#[allow(unused)] +fn quadratic(n: i32) { + // 矩阵占用 O(n^2) 空间 + let num_matrix = vec![vec![0; n as usize]; n as usize]; + // 二维列表占用 O(n^2) 空间 + let mut num_list = Vec::new(); + for i in 0..n { + let mut tmp = Vec::new(); + for j in 0..n { + tmp.push(0); + } + num_list.push(tmp); + } +} + +/* 平方阶(递归实现) */ +fn quadratic_recur(n: i32) -> i32 { + if n <= 0 { + return 0; + }; + // 数组 nums 长度为 n, n-1, ..., 2, 1 + let nums = vec![0; n as usize]; + println!("递归 n = {} 中的 nums 长度 = {}", n, nums.len()); + return quadratic_recur(n - 1); +} + +/* 指数阶(建立满二叉树) */ +fn build_tree(n: i32) -> Option>> { + if n == 0 { + return None; + }; + let root = TreeNode::new(0); + root.borrow_mut().left = build_tree(n - 1); + root.borrow_mut().right = build_tree(n - 1); + return Some(root); +} + +/* Driver Code */ +fn main() { + let n = 5; + // 常数阶 + constant(n); + // 线性阶 + linear(n); + linear_recur(n); + // 平方阶 + quadratic(n); + quadratic_recur(n); + // 指数阶 + let root = build_tree(n); + print_util::print_tree(&root.unwrap()); +} diff --git a/codes/rust/chapter_computational_complexity/time_complexity.rs b/codes/rust/chapter_computational_complexity/time_complexity.rs index a6aa60b319..8fb4121a27 100644 --- a/codes/rust/chapter_computational_complexity/time_complexity.rs +++ b/codes/rust/chapter_computational_complexity/time_complexity.rs @@ -1,15 +1,21 @@ -#![allow(unused_variables)] +/* + * File: time_complexity.rs + * Created Time: 2023-01-10 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ /* 常数阶 */ fn constant(n: i32) -> i32 { + _ = n; let mut count = 0; - let size = 100000; + let size = 100_000; for _ in 0..size { - count += 1 + count += 1; } count } +/* 线性阶 */ fn linear(n: i32) -> i32 { let mut count = 0; for _ in 0..n { @@ -28,9 +34,10 @@ fn array_traversal(nums: &[i32]) -> i32 { count } +/* 平方阶 */ fn quadratic(n: i32) -> i32 { let mut count = 0; - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for _ in 0..n { for _ in 0..n { count += 1; @@ -42,9 +49,10 @@ fn quadratic(n: i32) -> i32 { /* 平方阶(冒泡排序) */ fn bubble_sort(nums: &mut [i32]) -> i32 { let mut count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + + // 外循环:未排序区间为 [0, i] for i in (1..nums.len()).rev() { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0..i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] @@ -62,7 +70,7 @@ fn bubble_sort(nums: &mut [i32]) -> i32 { fn exponential(n: i32) -> i32 { let mut count = 0; let mut base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for _ in 0..n { for _ in 0..base { count += 1 @@ -84,7 +92,6 @@ fn exp_recur(n: i32) -> i32 { /* 对数阶(循环实现) */ fn logarithmic(mut n: i32) -> i32 { let mut count = 0; - while n > 1 { n = n / 2; count += 1; @@ -92,6 +99,7 @@ fn logarithmic(mut n: i32) -> i32 { count } +/* 对数阶(递归实现) */ fn log_recur(n: i32) -> i32 { if n <= 1 { return 0; @@ -100,15 +108,15 @@ fn log_recur(n: i32) -> i32 { } /* 线性对数阶 */ -fn linear_log_recur(n: f64) -> i32 { - if n <= 1.0 { +fn linear_log_recur(n: i32) -> i32 { + if n <= 1 { return 1; } - let mut count = linear_log_recur(n / 2.0) + linear_log_recur(n / 2.0); - for _ in 0 ..n as i32 { + let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); + for _ in 0..n { count += 1; } - return count + return count; } /* 阶乘阶(递归实现) */ @@ -131,33 +139,32 @@ fn main() { println!("输入数据大小 n = {}", n); let mut count = constant(n); - println!("常数阶的计算操作数量 = {}", count); + println!("常数阶的操作数量 = {}", count); count = linear(n); - println!("线性阶的计算操作数量 = {}", count); + println!("线性阶的操作数量 = {}", count); count = array_traversal(&vec![0; n as usize]); - println!("线性阶(遍历数组)的计算操作数量 = {}", count); + println!("线性阶(遍历数组)的操作数量 = {}", count); count = quadratic(n); - println!("平方阶的计算操作数量 = {}", count); - + println!("平方阶的操作数量 = {}", count); let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] count = bubble_sort(&mut nums); - println!("平方阶(冒泡排序)的计算操作数量 = {}", count); + println!("平方阶(冒泡排序)的操作数量 = {}", count); count = exponential(n); - println!("指数阶(循环实现)的计算操作数量 = {}", count); + println!("指数阶(循环实现)的操作数量 = {}", count); count = exp_recur(n); - println!("指数阶(递归实现)的计算操作数量 = {}", count); + println!("指数阶(递归实现)的操作数量 = {}", count); count = logarithmic(n); - println!("对数阶(循环实现)的计算操作数量 = {}", count); + println!("对数阶(循环实现)的操作数量 = {}", count); count = log_recur(n); - println!("对数阶(递归实现)的计算操作数量 = {}", count); + println!("对数阶(递归实现)的操作数量 = {}", count); - count = linear_log_recur(n.into()); - println!("线性对数阶(递归实现)的计算操作数量 = {}", count); + count = linear_log_recur(n); + println!("线性对数阶(递归实现)的操作数量 = {}", count); count = factorial_recur(n); - println!("阶乘阶(递归实现)的计算操作数量 = {}", count); -} \ No newline at end of file + println!("阶乘阶(递归实现)的操作数量 = {}", count); +} diff --git a/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs b/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs new file mode 100644 index 0000000000..df776625f8 --- /dev/null +++ b/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs @@ -0,0 +1,42 @@ +/* + * File: worst_best_time_complexity.rs + * Created Time: 2023-01-13 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::seq::SliceRandom; +use rand::thread_rng; + +/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ +fn random_numbers(n: i32) -> Vec { + // 生成数组 nums = { 1, 2, 3, ..., n } + let mut nums = (1..=n).collect::>(); + // 随机打乱数组元素 + nums.shuffle(&mut thread_rng()); + nums +} + +/* 查找数组 nums 中数字 1 所在索引 */ +fn find_one(nums: &[i32]) -> Option { + for i in 0..nums.len() { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if nums[i] == 1 { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + for _ in 0..10 { + let n = 100; + let nums = random_numbers(n); + let index = find_one(&nums).unwrap(); + print!("\n数组 [ 1, 2, ..., n ] 被打乱后 = "); + print_util::print_array(&nums); + println!("\n数字 1 的索引为 {}", index); + } +} diff --git a/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs b/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs new file mode 100644 index 0000000000..b0864cbca6 --- /dev/null +++ b/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs @@ -0,0 +1,41 @@ +/* + * File: binary_search_recur.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分查找:问题 f(i, j) */ +fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { + // 若区间为空,代表无目标元素,则返回 -1 + if i > j { + return -1; + } + let m: i32 = i + (j - i) / 2; + if nums[m as usize] < target { + // 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if nums[m as usize] > target { + // 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目标元素,返回其索引 + return m; + } +} + +/* 二分查找 */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + let n = nums.len() as i32; + // 求解问题 f(0, n-1) + dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分查找(双闭区间) + let index = binary_search(&nums, target); + println!("目标元素 6 的索引 = {index}"); +} diff --git a/codes/rust/chapter_divide_and_conquer/build_tree.rs b/codes/rust/chapter_divide_and_conquer/build_tree.rs new file mode 100644 index 0000000000..e13ae641bb --- /dev/null +++ b/codes/rust/chapter_divide_and_conquer/build_tree.rs @@ -0,0 +1,56 @@ +/* + * File: build_tree.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; +use std::collections::HashMap; +use std::{cell::RefCell, rc::Rc}; + +/* 构建二叉树:分治 */ +fn dfs( + preorder: &[i32], + inorder_map: &HashMap, + i: i32, + l: i32, + r: i32, +) -> Option>> { + // 子树区间为空时终止 + if r - l < 0 { + return None; + } + // 初始化根节点 + let root = TreeNode::new(preorder[i as usize]); + // 查询 m ,从而划分左右子树 + let m = inorder_map.get(&preorder[i as usize]).unwrap(); + // 子问题:构建左子树 + root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); + // 子问题:构建右子树 + root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); + // 返回根节点 + Some(root) +} + +/* 构建二叉树 */ +fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { + // 初始化哈希表,存储 inorder 元素到索引的映射 + let mut inorder_map: HashMap = HashMap::new(); + for i in 0..inorder.len() { + inorder_map.insert(inorder[i], i as i32); + } + let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); + root +} + +/* Driver Code */ +fn main() { + let preorder = [3, 9, 2, 1, 7]; + let inorder = [9, 3, 1, 2, 7]; + println!("中序遍历 = {:?}", preorder); + println!("前序遍历 = {:?}", inorder); + + let root = build_tree(&preorder, &inorder); + println!("构建的二叉树为:"); + print_util::print_tree(root.as_ref().unwrap()); +} diff --git a/codes/rust/chapter_divide_and_conquer/hanota.rs b/codes/rust/chapter_divide_and_conquer/hanota.rs new file mode 100644 index 0000000000..92880a0bcd --- /dev/null +++ b/codes/rust/chapter_divide_and_conquer/hanota.rs @@ -0,0 +1,55 @@ +/* + * File: hanota.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +#![allow(non_snake_case)] + +/* 移动一个圆盘 */ +fn move_pan(src: &mut Vec, tar: &mut Vec) { + // 从 src 顶部拿出一个圆盘 + let pan = src.pop().unwrap(); + // 将圆盘放入 tar 顶部 + tar.push(pan); +} + +/* 求解汉诺塔问题 f(i) */ +fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1 { + move_pan(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move_pan(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解汉诺塔问题 */ +fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { + let n = A.len() as i32; + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +pub fn main() { + let mut A = vec![5, 4, 3, 2, 1]; + let mut B = Vec::new(); + let mut C = Vec::new(); + println!("初始状态下:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); + + solve_hanota(&mut A, &mut B, &mut C); + + println!("圆盘移动完成后:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); +} diff --git a/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs b/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs new file mode 100644 index 0000000000..03211b5db1 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs @@ -0,0 +1,41 @@ +/* + * File: climbing_stairs_backtrack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯 */ +fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { + // 当爬到第 n 阶时,方案数量加 1 + if state == n { + res[0] = res[0] + 1; + } + // 遍历所有选择 + for &choice in choices { + // 剪枝:不允许越过第 n 阶 + if state + choice > n { + continue; + } + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬楼梯:回溯 */ +fn climbing_stairs_backtrack(n: usize) -> i32 { + let choices = vec![1, 2]; // 可选择向上爬 1 阶或 2 阶 + let state = 0; // 从第 0 阶开始爬 + let mut res = Vec::new(); + res.push(0); // 使用 res[0] 记录方案数量 + backtrack(&choices, state, n as i32, &mut res); + res[0] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_backtrack(n); + println!("爬 {n} 阶楼梯共有 {res} 种方案"); +} diff --git a/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs b/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs new file mode 100644 index 0000000000..24078307bc --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs @@ -0,0 +1,33 @@ +/* + * File: climbing_stairs_constraint_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 带约束爬楼梯:动态规划 */ +fn climbing_stairs_constraint_dp(n: usize) -> i32 { + if n == 1 || n == 2 { + return 1; + }; + // 初始化 dp 表,用于存储子问题的解 + let mut dp = vec![vec![-1; 3]; n + 1]; + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for i in 3..=n { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + dp[n][1] + dp[n][2] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_constraint_dp(n); + println!("爬 {n} 阶楼梯共有 {res} 种方案"); +} diff --git a/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs b/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs new file mode 100644 index 0000000000..8695f59250 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs @@ -0,0 +1,29 @@ +/* + * File: climbing_stairs_dfs.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 搜索 */ +fn dfs(i: usize) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i as i32; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1) + dfs(i - 2); + count +} + +/* 爬楼梯:搜索 */ +fn climbing_stairs_dfs(n: usize) -> i32 { + dfs(n) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs(n); + println!("爬 {n} 阶楼梯共有 {res} 种方案"); +} diff --git a/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs b/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs new file mode 100644 index 0000000000..43d95896bf --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs @@ -0,0 +1,37 @@ +/* + * File: climbing_stairs_dfs_mem.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 记忆化搜索 */ +fn dfs(i: usize, mem: &mut [i32]) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i as i32; + } + // 若存在记录 dp[i] ,则直接返回之 + if mem[i] != -1 { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + count +} + +/* 爬楼梯:记忆化搜索 */ +fn climbing_stairs_dfs_mem(n: usize) -> i32 { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + let mut mem = vec![-1; n + 1]; + dfs(n, &mut mem) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs_mem(n); + println!("爬 {n} 阶楼梯共有 {res} 种方案"); +} diff --git a/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs b/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs new file mode 100644 index 0000000000..ec948e7eaf --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs @@ -0,0 +1,48 @@ +/* + * File: climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 爬楼梯:动态规划 */ +fn climbing_stairs_dp(n: usize) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if n == 1 || n == 2 { + return n as i32; + } + // 初始化 dp 表,用于存储子问题的解 + let mut dp = vec![-1; n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for i in 3..=n { + dp[i] = dp[i - 1] + dp[i - 2]; + } + dp[n] +} + +/* 爬楼梯:空间优化后的动态规划 */ +fn climbing_stairs_dp_comp(n: usize) -> i32 { + if n == 1 || n == 2 { + return n as i32; + } + let (mut a, mut b) = (1, 2); + for _ in 3..=n { + let tmp = b; + b = a + b; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dp(n); + println!("爬 {n} 阶楼梯共有 {res} 种方案"); + + let res = climbing_stairs_dp_comp(n); + println!("爬 {n} 阶楼梯共有 {res} 种方案"); +} diff --git a/codes/rust/chapter_dynamic_programming/coin_change.rs b/codes/rust/chapter_dynamic_programming/coin_change.rs new file mode 100644 index 0000000000..33922a2d51 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/coin_change.rs @@ -0,0 +1,75 @@ +/* + * File: coin_change.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 零钱兑换:动态规划 */ +fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // 初始化 dp 表 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 状态转移:首行首列 + for a in 1..=amt { + dp[0][a] = max; + } + // 状态转移:其余行和列 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); + } + } + } + if dp[n][amt] != max { + return dp[n][amt] as i32; + } else { + -1 + } +} + +/* 零钱兑换:空间优化后的动态规划 */ +fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // 初始化 dp 表 + let mut dp = vec![0; amt + 1]; + dp.fill(max); + dp[0] = 0; + // 状态转移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); + } + } + } + if dp[amt] != max { + return dp[amt] as i32; + } else { + -1 + } +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 4; + + // 动态规划 + let res = coin_change_dp(&coins, amt); + println!("凑到目标金额所需的最少硬币数量为 {res}"); + + // 空间优化后的动态规划 + let res = coin_change_dp_comp(&coins, amt); + println!("凑到目标金额所需的最少硬币数量为 {res}"); +} diff --git a/codes/rust/chapter_dynamic_programming/coin_change_ii.rs b/codes/rust/chapter_dynamic_programming/coin_change_ii.rs new file mode 100644 index 0000000000..12702bdf37 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/coin_change_ii.rs @@ -0,0 +1,64 @@ +/* + * File: coin_change_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 零钱兑换 II:动态规划 */ +fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 初始化首列 + for i in 0..=n { + dp[i][0] = 1; + } + // 状态转移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; + } + } + } + dp[n][amt] +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // 初始化 dp 表 + let mut dp = vec![0; amt + 1]; + dp[0] = 1; + // 状态转移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; + } + } + } + dp[amt] +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 5; + + // 动态规划 + let res = coin_change_ii_dp(&coins, amt); + println!("凑出目标金额的硬币组合数量为 {res}"); + + // 空间优化后的动态规划 + let res = coin_change_ii_dp_comp(&coins, amt); + println!("凑出目标金额的硬币组合数量为 {res}"); +} diff --git a/codes/rust/chapter_dynamic_programming/edit_distance.rs b/codes/rust/chapter_dynamic_programming/edit_distance.rs new file mode 100644 index 0000000000..d2dcad92c7 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/edit_distance.rs @@ -0,0 +1,145 @@ +/* + * File: edit_distance.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 编辑距离:暴力搜索 */ +fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { + // 若 s 和 t 都为空,则返回 0 + if i == 0 && j == 0 { + return 0; + } + // 若 s 为空,则返回 t 长度 + if i == 0 { + return j as i32; + } + // 若 t 为空,则返回 s 长度 + if j == 0 { + return i as i32; + } + // 若两字符相等,则直接跳过此两字符 + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs(s, t, i - 1, j - 1); + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + let insert = edit_distance_dfs(s, t, i, j - 1); + let delete = edit_distance_dfs(s, t, i - 1, j); + let replace = edit_distance_dfs(s, t, i - 1, j - 1); + // 返回最少编辑步数 + std::cmp::min(std::cmp::min(insert, delete), replace) + 1 +} + +/* 编辑距离:记忆化搜索 */ +fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { + // 若 s 和 t 都为空,则返回 0 + if i == 0 && j == 0 { + return 0; + } + // 若 s 为空,则返回 t 长度 + if i == 0 { + return j as i32; + } + // 若 t 为空,则返回 s 长度 + if j == 0 { + return i as i32; + } + // 若已有记录,则直接返回之 + if mem[i][j] != -1 { + return mem[i][j]; + } + // 若两字符相等,则直接跳过此两字符 + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); + let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); + let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; + mem[i][j] +} + +/* 编辑距离:动态规划 */ +fn edit_distance_dp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![vec![0; m + 1]; n + 1]; + // 状态转移:首行首列 + for i in 1..=n { + dp[i][0] = i as i32; + } + for j in 1..m { + dp[0][j] = j as i32; + } + // 状态转移:其余行和列 + for i in 1..=n { + for j in 1..=m { + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = + std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + dp[n][m] +} + +/* 编辑距离:空间优化后的动态规划 */ +fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![0; m + 1]; + // 状态转移:首行 + for j in 1..m { + dp[j] = j as i32; + } + // 状态转移:其余行 + for i in 1..=n { + // 状态转移:首列 + let mut leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i as i32; + // 状态转移:其余列 + for j in 1..=m { + let temp = dp[j]; + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + dp[m] +} + +/* Driver Code */ +pub fn main() { + let s = "bag"; + let t = "pack"; + let (n, m) = (s.len(), t.len()); + + // 暴力搜索 + let res = edit_distance_dfs(s, t, n, m); + println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); + + // 记忆搜索 + let mut mem = vec![vec![0; m + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); + println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); + + // 动态规划 + let res = edit_distance_dp(s, t); + println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); + + // 空间优化后的动态规划 + let res = edit_distance_dp_comp(s, t); + println!("将 {s} 更改为 {t} 最少需要编辑 {res} 步"); +} diff --git a/codes/rust/chapter_dynamic_programming/knapsack.rs b/codes/rust/chapter_dynamic_programming/knapsack.rs new file mode 100644 index 0000000000..9e99b4c044 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/knapsack.rs @@ -0,0 +1,113 @@ +/* + * File: knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 0-1 背包:暴力搜索 */ +fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 || c == 0 { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if wgt[i - 1] > c as i32 { + return knapsack_dfs(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsack_dfs(wgt, val, i - 1, c); + let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + std::cmp::max(no, yes) +} + +/* 0-1 背包:记忆化搜索 */ +fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 || c == 0 { + return 0; + } + // 若已有记录,则直接返回 + if mem[i][c] != -1 { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if wgt[i - 1] > c as i32 { + return knapsack_dfs_mem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); + let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = std::cmp::max(no, yes); + mem[i][c] +} + +/* 0-1 背包:动态规划 */ +fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 状态转移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = std::cmp::max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], + ); + } + } + } + dp[n][cap] +} + +/* 0-1 背包:空间优化后的动态规划 */ +fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![0; cap + 1]; + // 状态转移 + for i in 1..=n { + // 倒序遍历 + for c in (1..=cap).rev() { + if wgt[i - 1] <= c as i32 { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap: usize = 50; + let n = wgt.len(); + + // 暴力搜索 + let res = knapsack_dfs(&wgt, &val, n, cap); + println!("不超过背包容量的最大物品价值为 {res}"); + + // 记忆搜索 + let mut mem = vec![vec![0; cap + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); + println!("不超过背包容量的最大物品价值为 {res}"); + + // 动态规划 + let res = knapsack_dp(&wgt, &val, cap); + println!("不超过背包容量的最大物品价值为 {res}"); + + // 空间优化后的动态规划 + let res = knapsack_dp_comp(&wgt, &val, cap); + println!("不超过背包容量的最大物品价值为 {res}"); +} diff --git a/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs b/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs new file mode 100644 index 0000000000..146068e594 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs @@ -0,0 +1,52 @@ +/* + * File: min_cost_climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::cmp; + +/* 爬楼梯最小代价:动态规划 */ +fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + } + // 初始化 dp 表,用于存储子问题的解 + let mut dp = vec![-1; n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for i in 3..=n { + dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; + } + dp[n] +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + }; + let (mut a, mut b) = (cost[1], cost[2]); + for i in 3..=n { + let tmp = b; + b = cmp::min(a, tmp) + cost[i]; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + println!("输入楼梯的代价列表为 {:?}", &cost); + + let res = min_cost_climbing_stairs_dp(&cost); + println!("爬完楼梯的最低代价为 {res}"); + + let res = min_cost_climbing_stairs_dp_comp(&cost); + println!("爬完楼梯的最低代价为 {res}"); +} diff --git a/codes/rust/chapter_dynamic_programming/min_path_sum.rs b/codes/rust/chapter_dynamic_programming/min_path_sum.rs new file mode 100644 index 0000000000..bb7b9c8e10 --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/min_path_sum.rs @@ -0,0 +1,120 @@ +/* + * File: min_path_sum.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 最小路径和:暴力搜索 */ +fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { + // 若为左上角单元格,则终止搜索 + if i == 0 && j == 0 { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return i32::MAX; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + let up = min_path_sum_dfs(grid, i - 1, j); + let left = min_path_sum_dfs(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + std::cmp::min(left, up) + grid[i as usize][j as usize] +} + +/* 最小路径和:记忆化搜索 */ +fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { + // 若为左上角单元格,则终止搜索 + if i == 0 && j == 0 { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return i32::MAX; + } + // 若已有记录,则直接返回 + if mem[i as usize][j as usize] != -1 { + return mem[i as usize][j as usize]; + } + // 左边和上边单元格的最小路径代价 + let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); + let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; + mem[i as usize][j as usize] +} + +/* 最小路径和:动态规划 */ +fn min_path_sum_dp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // 初始化 dp 表 + let mut dp = vec![vec![0; m]; n]; + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for j in 1..m { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for i in 1..n { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for i in 1..n { + for j in 1..m { + dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + dp[n - 1][m - 1] +} + +/* 最小路径和:空间优化后的动态规划 */ +fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // 初始化 dp 表 + let mut dp = vec![0; m]; + // 状态转移:首行 + dp[0] = grid[0][0]; + for j in 1..m { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for i in 1..n { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for j in 1..m { + dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + dp[m - 1] +} + +/* Driver Code */ +pub fn main() { + let grid = vec![ + vec![1, 3, 1, 5], + vec![2, 2, 4, 2], + vec![5, 3, 2, 1], + vec![4, 3, 5, 2], + ]; + let (n, m) = (grid.len(), grid[0].len()); + + // 暴力搜索 + let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); + println!("从左上角到右下角的最小路径和为 {res}"); + + // 记忆化搜索 + let mut mem = vec![vec![0; m]; n]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); + println!("从左上角到右下角的最小路径和为 {res}"); + + // 动态规划 + let res = min_path_sum_dp(&grid); + println!("从左上角到右下角的最小路径和为 {res}"); + + // 空间优化后的动态规划 + let res = min_path_sum_dp_comp(&grid); + println!("从左上角到右下角的最小路径和为 {res}"); +} diff --git a/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs b/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs new file mode 100644 index 0000000000..670f845afe --- /dev/null +++ b/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs @@ -0,0 +1,60 @@ +/* + * File: unbounded_knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 完全背包:动态规划 */ +fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 状态转移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空间优化后的动态规划 */ +fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![0; cap + 1]; + // 状态转移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [1, 2, 3]; + let val = [5, 11, 15]; + let cap: usize = 4; + + // 动态规划 + let res = unbounded_knapsack_dp(&wgt, &val, cap); + println!("不超过背包容量的最大物品价值为 {res}"); + + // 空间优化后的动态规划 + let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); + println!("不超过背包容量的最大物品价值为 {res}"); +} diff --git a/codes/rust/chapter_graph/graph_adjacency_list.rs b/codes/rust/chapter_graph/graph_adjacency_list.rs new file mode 100644 index 0000000000..5e014d10c8 --- /dev/null +++ b/codes/rust/chapter_graph/graph_adjacency_list.rs @@ -0,0 +1,142 @@ +/* + * File: graph_adjacency_list.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; + +use std::collections::HashMap; + +/* 基于邻接表实现的无向图类型 */ +pub struct GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + pub adj_list: HashMap>, +} + +impl GraphAdjList { + /* 构造方法 */ + pub fn new(edges: Vec<[Vertex; 2]>) -> Self { + let mut graph = GraphAdjList { + adj_list: HashMap::new(), + }; + // 添加所有顶点和边 + for edge in edges { + graph.add_vertex(edge[0]); + graph.add_vertex(edge[1]); + graph.add_edge(edge[0], edge[1]); + } + + graph + } + + /* 获取顶点数量 */ + #[allow(unused)] + pub fn size(&self) -> usize { + self.adj_list.len() + } + + /* 添加边 */ + pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 添加边 vet1 - vet2 + self.adj_list.get_mut(&vet1).unwrap().push(vet2); + self.adj_list.get_mut(&vet2).unwrap().push(vet1); + } + + /* 删除边 */ + #[allow(unused)] + pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 删除边 vet1 - vet2 + self.adj_list + .get_mut(&vet1) + .unwrap() + .retain(|&vet| vet != vet2); + self.adj_list + .get_mut(&vet2) + .unwrap() + .retain(|&vet| vet != vet1); + } + + /* 添加顶点 */ + pub fn add_vertex(&mut self, vet: Vertex) { + if self.adj_list.contains_key(&vet) { + return; + } + // 在邻接表中添加一个新链表 + self.adj_list.insert(vet, vec![]); + } + + /* 删除顶点 */ + #[allow(unused)] + pub fn remove_vertex(&mut self, vet: Vertex) { + if !self.adj_list.contains_key(&vet) { + panic!("value error"); + } + // 在邻接表中删除顶点 vet 对应的链表 + self.adj_list.remove(&vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for list in self.adj_list.values_mut() { + list.retain(|&v| v != vet); + } + } + + /* 打印邻接表 */ + pub fn print(&self) { + println!("邻接表 ="); + for (vertex, list) in &self.adj_list { + let list = list.iter().map(|vertex| vertex.val).collect::>(); + println!("{}: {:?},", vertex.val, list); + } + } +} + +/* Driver Code */ +#[allow(unused)] +fn main() { + /* 初始化无向图 */ + let v = vals_to_vets(vec![1, 3, 2, 5, 4]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + + let mut graph = GraphAdjList::new(edges); + println!("\n初始化后,图为"); + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.add_edge(v[0], v[2]); + println!("\n添加边 1-2 后,图为"); + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.remove_edge(v[0], v[1]); + println!("\n删除边 1-3 后,图为"); + graph.print(); + + /* 添加顶点 */ + let v5 = Vertex { val: 6 }; + graph.add_vertex(v5); + println!("\n添加顶点 6 后,图为"); + graph.print(); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.remove_vertex(v[1]); + println!("\n删除顶点 3 后,图为"); + graph.print(); +} diff --git a/codes/rust/chapter_graph/graph_adjacency_matrix.rs b/codes/rust/chapter_graph/graph_adjacency_matrix.rs new file mode 100644 index 0000000000..ac02fa8151 --- /dev/null +++ b/codes/rust/chapter_graph/graph_adjacency_matrix.rs @@ -0,0 +1,136 @@ +/* + * File: graph_adjacency_matrix.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 基于邻接矩阵实现的无向图类型 */ +pub struct GraphAdjMat { + // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + pub vertices: Vec, + // 邻接矩阵,行列索引对应“顶点索引” + pub adj_mat: Vec>, +} + +impl GraphAdjMat { + /* 构造方法 */ + pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { + let mut graph = GraphAdjMat { + vertices: vec![], + adj_mat: vec![], + }; + // 添加顶点 + for val in vertices { + graph.add_vertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for edge in edges { + graph.add_edge(edge[0], edge[1]) + } + + graph + } + + /* 获取顶点数量 */ + pub fn size(&self) -> usize { + self.vertices.len() + } + + /* 添加顶点 */ + pub fn add_vertex(&mut self, val: i32) { + let n = self.size(); + // 向顶点列表中添加新顶点的值 + self.vertices.push(val); + // 在邻接矩阵中添加一行 + self.adj_mat.push(vec![0; n]); + // 在邻接矩阵中添加一列 + for row in &mut self.adj_mat { + row.push(0); + } + } + + /* 删除顶点 */ + pub fn remove_vertex(&mut self, index: usize) { + if index >= self.size() { + panic!("index error") + } + // 在顶点列表中移除索引 index 的顶点 + self.vertices.remove(index); + // 在邻接矩阵中删除索引 index 的行 + self.adj_mat.remove(index); + // 在邻接矩阵中删除索引 index 的列 + for row in &mut self.adj_mat { + row.remove(index); + } + } + + /* 添加边 */ + pub fn add_edge(&mut self, i: usize, j: usize) { + // 参数 i, j 对应 vertices 元素索引 + // 索引越界与相等处理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + self.adj_mat[i][j] = 1; + self.adj_mat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + pub fn remove_edge(&mut self, i: usize, j: usize) { + // 参数 i, j 对应 vertices 元素索引 + // 索引越界与相等处理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + self.adj_mat[i][j] = 0; + self.adj_mat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + pub fn print(&self) { + println!("顶点列表 = {:?}", self.vertices); + println!("邻接矩阵 ="); + println!("["); + for row in &self.adj_mat { + println!(" {:?},", row); + } + println!("]") + } +} + +/* Driver Code */ +fn main() { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + let vertices = vec![1, 3, 2, 5, 4]; + let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; + let mut graph = GraphAdjMat::new(vertices, edges); + println!("\n初始化后,图为"); + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.add_edge(0, 2); + println!("\n添加边 1-2 后,图为"); + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.remove_edge(0, 1); + println!("\n删除边 1-3 后,图为"); + graph.print(); + + /* 添加顶点 */ + graph.add_vertex(6); + println!("\n添加顶点 6 后,图为"); + graph.print(); + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.remove_vertex(1); + println!("\n删除顶点 3 后,图为"); + graph.print(); +} diff --git a/codes/rust/chapter_graph/graph_bfs.rs b/codes/rust/chapter_graph/graph_bfs.rs new file mode 100644 index 0000000000..f4706ba437 --- /dev/null +++ b/codes/rust/chapter_graph/graph_bfs.rs @@ -0,0 +1,70 @@ +/* + * File: graph_bfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::{HashSet, VecDeque}; + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 顶点遍历序列 + let mut res = vec![]; + // 哈希集合,用于记录已被访问过的顶点 + let mut visited = HashSet::new(); + visited.insert(start_vet); + // 队列用于实现 BFS + let mut que = VecDeque::new(); + que.push_back(start_vet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while !que.is_empty() { + let vet = que.pop_front().unwrap(); // 队首顶点出队 + res.push(vet); // 记录访问顶点 + + // 遍历该顶点的所有邻接顶点 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳过已被访问的顶点 + } + que.push_back(adj_vet); // 只入队未访问的顶点 + visited.insert(adj_vet); // 标记该顶点已被访问 + } + } + } + // 返回顶点遍历序列 + res +} + +/* Driver Code */ +fn main() { + /* 初始化无向图 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let edges = vec![ + [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]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初始化后,图为"); + graph.print(); + + /* 广度优先遍历 */ + let res = graph_bfs(graph, v[0]); + println!("\n广度优先遍历(BFS)顶点序列为"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/codes/rust/chapter_graph/graph_dfs.rs b/codes/rust/chapter_graph/graph_dfs.rs new file mode 100644 index 0000000000..2a2c18a706 --- /dev/null +++ b/codes/rust/chapter_graph/graph_dfs.rs @@ -0,0 +1,61 @@ +/* + * File: graph_dfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::HashSet; + +/* 深度优先遍历辅助函数 */ +fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { + res.push(vet); // 记录访问顶点 + visited.insert(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adj_vet); + } + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 顶点遍历序列 + let mut res = vec![]; + // 哈希集合,用于记录已被访问过的顶点 + let mut visited = HashSet::new(); + dfs(&graph, &mut visited, &mut res, start_vet); + + res +} + +/* Driver Code */ +fn main() { + /* 初始化无向图 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初始化后,图为"); + graph.print(); + + /* 深度优先遍历 */ + let res = graph_dfs(graph, v[0]); + println!("\n深度优先遍历(DFS)顶点序列为"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/codes/rust/chapter_greedy/coin_change_greedy.rs b/codes/rust/chapter_greedy/coin_change_greedy.rs new file mode 100644 index 0000000000..f00c483b88 --- /dev/null +++ b/codes/rust/chapter_greedy/coin_change_greedy.rs @@ -0,0 +1,54 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 零钱兑换:贪心 */ +fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { + // 假设 coins 列表有序 + let mut i = coins.len() - 1; + let mut count = 0; + // 循环进行贪心选择,直到无剩余金额 + while amt > 0 { + // 找到小于且最接近剩余金额的硬币 + while i > 0 && coins[i] > amt { + i -= 1; + } + // 选择 coins[i] + amt -= coins[i]; + count += 1; + } + // 若未找到可行方案,则返回 -1 + if amt == 0 { + count + } else { + -1 + } +} + +/* Driver Code */ +fn main() { + // 贪心:能够保证找到全局最优解 + let coins = [1, 5, 10, 20, 50, 100]; + let amt = 186; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("凑到 {} 所需的最少硬币数量为 {}", amt, res); + + // 贪心:无法保证找到全局最优解 + let coins = [1, 20, 50]; + let amt = 60; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("凑到 {} 所需的最少硬币数量为 {}", amt, res); + println!("实际上需要的最少数量为 3 ,即 20 + 20 + 20"); + + // 贪心:无法保证找到全局最优解 + let coins = [1, 49, 50]; + let amt = 98; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("凑到 {} 所需的最少硬币数量为 {}", amt, res); + println!("实际上需要的最少数量为 2 ,即 49 + 49"); +} diff --git a/codes/rust/chapter_greedy/fractional_knapsack.rs b/codes/rust/chapter_greedy/fractional_knapsack.rs new file mode 100644 index 0000000000..6e3ebe333c --- /dev/null +++ b/codes/rust/chapter_greedy/fractional_knapsack.rs @@ -0,0 +1,59 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 物品 */ +struct Item { + w: i32, // 物品重量 + v: i32, // 物品价值 +} + +impl Item { + fn new(w: i32, v: i32) -> Self { + Self { w, v } + } +} + +/* 分数背包:贪心 */ +fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { + // 创建物品列表,包含两个属性:重量、价值 + let mut items = wgt + .iter() + .zip(val.iter()) + .map(|(&w, &v)| Item::new(w, v)) + .collect::>(); + // 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort_by(|a, b| { + (b.v as f64 / b.w as f64) + .partial_cmp(&(a.v as f64 / a.w as f64)) + .unwrap() + }); + // 循环贪心选择 + let mut res = 0.0; + for item in &items { + if item.w <= cap { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v as f64; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += item.v as f64 / item.w as f64 * cap as f64; + // 已无剩余容量,因此跳出循环 + break; + } + } + res +} + +/* Driver Code */ +fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap = 50; + + // 贪心算法 + let res = fractional_knapsack(&wgt, &val, cap); + println!("不超过背包容量的最大物品价值为 {}", res); +} diff --git a/codes/rust/chapter_greedy/max_capacity.rs b/codes/rust/chapter_greedy/max_capacity.rs new file mode 100644 index 0000000000..631022179f --- /dev/null +++ b/codes/rust/chapter_greedy/max_capacity.rs @@ -0,0 +1,36 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大容量:贪心 */ +fn max_capacity(ht: &[i32]) -> i32 { + // 初始化 i, j,使其分列数组两端 + let mut i = 0; + let mut j = ht.len() - 1; + // 初始最大容量为 0 + let mut res = 0; + // 循环贪心选择,直至两板相遇 + while i < j { + // 更新最大容量 + let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; + res = std::cmp::max(res, cap); + // 向内移动短板 + if ht[i] < ht[j] { + i += 1; + } else { + j -= 1; + } + } + res +} + +/* Driver Code */ +fn main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 贪心算法 + let res = max_capacity(&ht); + println!("最大容量为 {}", res); +} diff --git a/codes/rust/chapter_greedy/max_product_cutting.rs b/codes/rust/chapter_greedy/max_product_cutting.rs new file mode 100644 index 0000000000..c8fe012402 --- /dev/null +++ b/codes/rust/chapter_greedy/max_product_cutting.rs @@ -0,0 +1,35 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大切分乘积:贪心 */ +fn max_product_cutting(n: i32) -> i32 { + // 当 n <= 3 时,必须切分出一个 1 + if n <= 3 { + return 1 * (n - 1); + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + let a = n / 3; + let b = n % 3; + if b == 1 { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + 3_i32.pow(a as u32 - 1) * 2 * 2 + } else if b == 2 { + // 当余数为 2 时,不做处理 + 3_i32.pow(a as u32) * 2 + } else { + // 当余数为 0 时,不做处理 + 3_i32.pow(a as u32) + } +} + +/* Driver Code */ +fn main() { + let n = 58; + + // 贪心算法 + let res = max_product_cutting(n); + println!("最大切分乘积为 {}", res); +} diff --git a/codes/rust/chapter_hashing/array_hash_map.rs b/codes/rust/chapter_hashing/array_hash_map.rs new file mode 100644 index 0000000000..2eff4f435d --- /dev/null +++ b/codes/rust/chapter_hashing/array_hash_map.rs @@ -0,0 +1,124 @@ +/** + * File: array_hash_map.rs + * Created Time: 2023-2-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com) + */ + +/* 键值对 */ +#[derive(Debug, Clone, PartialEq)] +pub struct Pair { + pub key: i32, + pub val: String, +} +/* 基于数组实现的哈希表 */ +pub struct ArrayHashMap { + buckets: Vec>, +} + +impl ArrayHashMap { + pub fn new() -> ArrayHashMap { + // 初始化数组,包含 100 个桶 + Self { + buckets: vec![None; 100], + } + } + + /* 哈希函数 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % 100 + } + + /* 查询操作 */ + pub fn get(&self, key: i32) -> Option<&String> { + let index = self.hash_func(key); + self.buckets[index].as_ref().map(|pair| &pair.val) + } + + /* 添加操作 */ + pub fn put(&mut self, key: i32, val: &str) { + let index = self.hash_func(key); + self.buckets[index] = Some(Pair { + key, + val: val.to_string(), + }); + } + + /* 删除操作 */ + pub fn remove(&mut self, key: i32) { + let index = self.hash_func(key); + // 置为 None ,代表删除 + self.buckets[index] = None; + } + + /* 获取所有键值对 */ + pub fn entry_set(&self) -> Vec<&Pair> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref()) + .collect() + } + + /* 获取所有键 */ + pub fn key_set(&self) -> Vec<&i32> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) + .collect() + } + + /* 获取所有值 */ + pub fn value_set(&self) -> Vec<&String> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) + .collect() + } + + /* 打印哈希表 */ + pub fn print(&self) { + for pair in self.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + } +} + +fn main() { + /* 初始化哈希表 */ + let mut map = ArrayHashMap::new(); + /*添加操作 */ + // 在哈希表中添加键值对(key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + println!("\n添加完成后,哈希表为\nKey -> Value"); + map.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + let name = map.get(15937).unwrap(); + println!("\n输入学号 15937 ,查询到姓名 {}", name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + println!("\n删除 10583 后,哈希表为\nKey -> Value"); + map.print(); + + /* 遍历哈希表 */ + println!("\n遍历键值对 Key->Value"); + for pair in map.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + + println!("\n单独遍历键 Key"); + for key in map.key_set() { + println!("{}", key); + } + + println!("\n单独遍历值 Value"); + for val in map.value_set() { + println!("{}", val); + } +} diff --git a/codes/rust/chapter_hashing/build_in_hash.rs b/codes/rust/chapter_hashing/build_in_hash.rs new file mode 100644 index 0000000000..456f9791a4 --- /dev/null +++ b/codes/rust/chapter_hashing/build_in_hash.rs @@ -0,0 +1,49 @@ +/* + * File: build_in_hash.rs + * Created Time: 2023-7-6 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::ListNode; + +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +/* Driver Code */ +fn main() { + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + println!("整数 {} 的哈希值为 {}", num, hash_num); + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + println!("布尔量 {} 的哈希值为 {}", bol, hash_bol); + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + println!("小数 {} 的哈希值为 {}", dec, hash_dec); + + let str = "Hello 算法"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + println!("字符串 {} 的哈希值为 {}", str, hash_str); + + let arr = (&12836, &"小哈"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + println!("元组 {:?} 的哈希值为 {}", arr, hash_tup); + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + println!("节点对象 {:?} 的哈希值为{}", node, hash); +} diff --git a/codes/rust/chapter_hashing/hash_map.rs b/codes/rust/chapter_hashing/hash_map.rs new file mode 100644 index 0000000000..53200d0554 --- /dev/null +++ b/codes/rust/chapter_hashing/hash_map.rs @@ -0,0 +1,48 @@ +/* + * File: hash_map.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::HashMap; + +/* Driver Code */ +pub fn main() { + // 初始化哈希表 + let mut map = HashMap::new(); + + // 添加操作 + // 在哈希表中添加键值对 (key, value) + map.insert(12836, "小哈"); + map.insert(15937, "小啰"); + map.insert(16750, "小算"); + map.insert(13276, "小法"); + map.insert(10583, "小鸭"); + println!("\n添加完成后,哈希表为\nKey -> Value"); + print_util::print_hash_map(&map); + + // 查询操作 + // 向哈希表中输入键 key ,得到值 value + let name = map.get(&15937).copied().unwrap(); + println!("\n输入学号 15937 ,查询到姓名 {name}"); + + // 删除操作 + // 在哈希表中删除键值对 (key, value) + _ = map.remove(&10583); + println!("\n删除 10583 后,哈希表为\nKey -> Value"); + print_util::print_hash_map(&map); + + // 遍历哈希表 + println!("\n遍历键值对 Key->Value"); + print_util::print_hash_map(&map); + println!("\n单独遍历键 Key"); + for key in map.keys() { + println!("{key}"); + } + println!("\n单独遍历值 value"); + for value in map.values() { + println!("{value}"); + } +} diff --git a/codes/rust/chapter_hashing/hash_map_chaining.rs b/codes/rust/chapter_hashing/hash_map_chaining.rs new file mode 100644 index 0000000000..ae3fe1ca37 --- /dev/null +++ b/codes/rust/chapter_hashing/hash_map_chaining.rs @@ -0,0 +1,160 @@ +/* + * File: hash_map_chaining.rs + * Created Time: 2023-07-07 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +#[derive(Clone)] +/* 键值对 */ +struct Pair { + key: i32, + val: String, +} + +/* 链式地址哈希表 */ +struct HashMapChaining { + size: usize, + capacity: usize, + load_thres: f32, + extend_ratio: usize, + buckets: Vec>, +} + +impl HashMapChaining { + /* 构造方法 */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![vec![]; 4], + } + } + + /* 哈希函数 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % self.capacity + } + + /* 负载因子 */ + fn load_factor(&self) -> f32 { + self.size as f32 / self.capacity as f32 + } + + /* 删除操作 */ + fn remove(&mut self, key: i32) -> Option { + let index = self.hash_func(key); + + // 遍历桶,从中删除键值对 + for (i, p) in self.buckets[index].iter_mut().enumerate() { + if p.key == key { + let pair = self.buckets[index].remove(i); + self.size -= 1; + return Some(pair.val); + } + } + + // 若未找到 key ,则返回 None + None + } + + /* 扩容哈希表 */ + fn extend(&mut self) { + // 暂存原哈希表 + let buckets_tmp = std::mem::take(&mut self.buckets); + + // 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio; + self.buckets = vec![Vec::new(); self.capacity as usize]; + self.size = 0; + + // 将键值对从原哈希表搬运至新哈希表 + for bucket in buckets_tmp { + for pair in bucket { + self.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + fn print(&self) { + for bucket in &self.buckets { + let mut res = Vec::new(); + for pair in bucket { + res.push(format!("{} -> {}", pair.key, pair.val)); + } + println!("{:?}", res); + } + } + + /* 添加操作 */ + fn put(&mut self, key: i32, val: String) { + // 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres { + self.extend(); + } + + let index = self.hash_func(key); + + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in self.buckets[index].iter_mut() { + if pair.key == key { + pair.val = val; + return; + } + } + + // 若无该 key ,则将键值对添加至尾部 + let pair = Pair { key, val }; + self.buckets[index].push(pair); + self.size += 1; + } + + /* 查询操作 */ + fn get(&self, key: i32) -> Option<&str> { + let index = self.hash_func(key); + + // 遍历桶,若找到 key ,则返回对应 val + for pair in self.buckets[index].iter() { + if pair.key == key { + return Some(&pair.val); + } + } + + // 若未找到 key ,则返回 None + None + } +} + +/* Driver Code */ +pub fn main() { + /* 初始化哈希表 */ + let mut map = HashMapChaining::new(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈".to_string()); + map.put(15937, "小啰".to_string()); + map.put(16750, "小算".to_string()); + map.put(13276, "小法".to_string()); + map.put(10583, "小鸭".to_string()); + println!("\n添加完成后,哈希表为\nKey -> Value"); + map.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + println!( + "\n输入学号 13276,查询到姓名 {}", + match map.get(13276) { + Some(value) => value, + None => "Not a valid Key", + } + ); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(12836); + println!("\n删除 12836 后,哈希表为\nKey -> Value"); + map.print(); +} diff --git a/codes/rust/chapter_hashing/hash_map_open_addressing.rs b/codes/rust/chapter_hashing/hash_map_open_addressing.rs new file mode 100644 index 0000000000..5ed34fc435 --- /dev/null +++ b/codes/rust/chapter_hashing/hash_map_open_addressing.rs @@ -0,0 +1,181 @@ +/* + * File: hash_map_open_addressing.rs + * Created Time: 2023-07-16 + * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) + */ +#![allow(non_snake_case)] +#![allow(unused)] + +mod array_hash_map; + +use array_hash_map::Pair; + +/* 开放寻址哈希表 */ +struct HashMapOpenAddressing { + size: usize, // 键值对数量 + capacity: usize, // 哈希表容量 + load_thres: f64, // 触发扩容的负载因子阈值 + extend_ratio: usize, // 扩容倍数 + buckets: Vec>, // 桶数组 + TOMBSTONE: Option, // 删除标记 +} + +impl HashMapOpenAddressing { + /* 构造方法 */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![None; 4], + TOMBSTONE: Some(Pair { + key: -1, + val: "-1".to_string(), + }), + } + } + + /* 哈希函数 */ + fn hash_func(&self, key: i32) -> usize { + (key % self.capacity as i32) as usize + } + + /* 负载因子 */ + fn load_factor(&self) -> f64 { + self.size as f64 / self.capacity as f64 + } + + /* 搜索 key 对应的桶索引 */ + fn find_bucket(&mut self, key: i32) -> usize { + let mut index = self.hash_func(key); + let mut first_tombstone = -1; + // 线性探测,当遇到空桶时跳出 + while self.buckets[index].is_some() { + // 若遇到 key,返回对应的桶索引 + if self.buckets[index].as_ref().unwrap().key == key { + // 若之前遇到了删除标记,则将建值对移动至该索引 + if first_tombstone != -1 { + self.buckets[first_tombstone as usize] = self.buckets[index].take(); + self.buckets[index] = self.TOMBSTONE.clone(); + return first_tombstone as usize; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { + first_tombstone = index as i32; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % self.capacity; + } + // 若 key 不存在,则返回添加点的索引 + if first_tombstone == -1 { + index + } else { + first_tombstone as usize + } + } + + /* 查询操作 */ + fn get(&mut self, key: i32) -> Option<&str> { + // 搜索 key 对应的桶索引 + let index = self.find_bucket(key); + // 若找到键值对,则返回对应 val + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + return self.buckets[index].as_ref().map(|pair| &pair.val as &str); + } + // 若键值对不存在,则返回 null + None + } + + /* 添加操作 */ + fn put(&mut self, key: i32, val: String) { + // 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres { + self.extend(); + } + // 搜索 key 对应的桶索引 + let index = self.find_bucket(key); + // 若找到键值对,则覆盖 val 并返回 + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index].as_mut().unwrap().val = val; + return; + } + // 若键值对不存在,则添加该键值对 + self.buckets[index] = Some(Pair { key, val }); + self.size += 1; + } + + /* 删除操作 */ + fn remove(&mut self, key: i32) { + // 搜索 key 对应的桶索引 + let index = self.find_bucket(key); + // 若找到键值对,则用删除标记覆盖它 + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index] = self.TOMBSTONE.clone(); + self.size -= 1; + } + } + + /* 扩容哈希表 */ + fn extend(&mut self) { + // 暂存原哈希表 + let buckets_tmp = self.buckets.clone(); + // 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio; + self.buckets = vec![None; self.capacity]; + self.size = 0; + + // 将键值对从原哈希表搬运至新哈希表 + for pair in buckets_tmp { + if pair.is_none() || pair == self.TOMBSTONE { + continue; + } + let pair = pair.unwrap(); + + self.put(pair.key, pair.val); + } + } + /* 打印哈希表 */ + fn print(&self) { + for pair in &self.buckets { + if pair.is_none() { + println!("null"); + } else if pair == &self.TOMBSTONE { + println!("TOMBSTONE"); + } else { + let pair = pair.as_ref().unwrap(); + println!("{} -> {}", pair.key, pair.val); + } + } + } +} + +/* Driver Code */ +fn main() { + /* 初始化哈希表 */ + let mut hashmap = HashMapOpenAddressing::new(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + hashmap.put(12836, "小哈".to_string()); + hashmap.put(15937, "小啰".to_string()); + hashmap.put(16750, "小算".to_string()); + hashmap.put(13276, "小法".to_string()); + hashmap.put(10583, "小鸭".to_string()); + + println!("\n添加完成后,哈希表为\nKey -> Value"); + hashmap.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 val + let name = hashmap.get(13276).unwrap(); + println!("\n输入学号 13276 ,查询到姓名 {}", name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, val) + hashmap.remove(16750); + println!("\n删除 16750 后,哈希表为\nKey -> Value"); + hashmap.print(); +} diff --git a/codes/rust/chapter_hashing/simple_hash.rs b/codes/rust/chapter_hashing/simple_hash.rs new file mode 100644 index 0000000000..1106101bbf --- /dev/null +++ b/codes/rust/chapter_hashing/simple_hash.rs @@ -0,0 +1,70 @@ +/* + * File: simple_hash.rs + * Created Time: 2023-09-07 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 加法哈希 */ +fn add_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* 乘法哈希 */ +fn mul_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (31 * hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* 异或哈希 */ +fn xor_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash ^= c as i64; + } + + (hash & MODULUS) as i32 +} + +/* 旋转哈希 */ +fn rot_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; + } + + hash as i32 +} + +/* Driver Code */ +fn main() { + let key = "Hello 算法"; + + let hash = add_hash(key); + println!("加法哈希值为 {hash}"); + + let hash = mul_hash(key); + println!("乘法哈希值为 {hash}"); + + let hash = xor_hash(key); + println!("异或哈希值为 {hash}"); + + let hash = rot_hash(key); + println!("旋转哈希值为 {hash}"); +} diff --git a/codes/rust/chapter_heap/heap.rs b/codes/rust/chapter_heap/heap.rs new file mode 100644 index 0000000000..ef4f350084 --- /dev/null +++ b/codes/rust/chapter_heap/heap.rs @@ -0,0 +1,73 @@ +/* + * File: heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::BinaryHeap; + +fn test_push(heap: &mut BinaryHeap, val: i32, flag: i32) { + heap.push(flag * val); // 元素入堆 + println!("\n元素 {} 入堆后", val); + print_util::print_heap(heap.iter().map(|&val| flag * val).collect()); +} + +fn test_pop(heap: &mut BinaryHeap, flag: i32) { + let val = heap.pop().unwrap(); + println!("\n堆顶元素 {} 出堆后", flag * val); + print_util::print_heap(heap.iter().map(|&val| flag * val).collect()); +} + +/* Driver Code */ +fn main() { + /* 初始化堆 */ + // 初始化小顶堆 + #[allow(unused_assignments)] + let mut min_heap = BinaryHeap::new(); + // Rust 的 BinaryHeap 是大顶堆,当入队时将元素值乘以 -1 将其反转,当出队时将元素值乘以 -1 将其还原 + let min_heap_flag = -1; + // 初始化大顶堆 + let mut max_heap = BinaryHeap::new(); + let max_heap_flag = 1; + + println!("\n以下测试样例为大顶堆"); + + /* 元素入堆 */ + test_push(&mut max_heap, 1, max_heap_flag); + test_push(&mut max_heap, 3, max_heap_flag); + test_push(&mut max_heap, 2, max_heap_flag); + test_push(&mut max_heap, 5, max_heap_flag); + test_push(&mut max_heap, 4, max_heap_flag); + + /* 获取堆顶元素 */ + let peek = max_heap.peek().unwrap() * max_heap_flag; + println!("\n堆顶元素为 {}", peek); + + /* 堆顶元素出堆 */ + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + + /* 获取堆大小 */ + let size = max_heap.len(); + println!("\n堆元素数量为 {}", size); + + /* 判断堆是否为空 */ + let is_empty = max_heap.is_empty(); + println!("\n堆是否为空 {}", is_empty); + + /* 输入列表并建堆 */ + // 时间复杂度为 O(n) ,而非 O(nlogn) + min_heap = BinaryHeap::from( + vec![1, 3, 2, 5, 4] + .into_iter() + .map(|val| min_heap_flag * val) + .collect::>(), + ); + println!("\n输入列表并建立小顶堆后"); + print_util::print_heap(min_heap.iter().map(|&val| min_heap_flag * val).collect()); +} diff --git a/codes/rust/chapter_heap/my_heap.rs b/codes/rust/chapter_heap/my_heap.rs new file mode 100644 index 0000000000..18b67c6229 --- /dev/null +++ b/codes/rust/chapter_heap/my_heap.rs @@ -0,0 +1,165 @@ +/* + * File: my_heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 大顶堆 */ +struct MaxHeap { + // 使用 vector 而非数组,这样无须考虑扩容问题 + max_heap: Vec, +} + +impl MaxHeap { + /* 构造方法,根据输入列表建堆 */ + fn new(nums: Vec) -> Self { + // 将列表元素原封不动添加进堆 + let mut heap = MaxHeap { max_heap: nums }; + // 堆化除叶节点以外的其他所有节点 + for i in (0..=Self::parent(heap.size() - 1)).rev() { + heap.sift_down(i); + } + heap + } + + /* 获取左子节点的索引 */ + fn left(i: usize) -> usize { + 2 * i + 1 + } + + /* 获取右子节点的索引 */ + fn right(i: usize) -> usize { + 2 * i + 2 + } + + /* 获取父节点的索引 */ + fn parent(i: usize) -> usize { + (i - 1) / 2 // 向下整除 + } + + /* 交换元素 */ + fn swap(&mut self, i: usize, j: usize) { + self.max_heap.swap(i, j); + } + + /* 获取堆大小 */ + fn size(&self) -> usize { + self.max_heap.len() + } + + /* 判断堆是否为空 */ + fn is_empty(&self) -> bool { + self.max_heap.is_empty() + } + + /* 访问堆顶元素 */ + fn peek(&self) -> Option { + self.max_heap.first().copied() + } + + /* 元素入堆 */ + fn push(&mut self, val: i32) { + // 添加节点 + self.max_heap.push(val); + // 从底至顶堆化 + self.sift_up(self.size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + fn sift_up(&mut self, mut i: usize) { + loop { + // 节点 i 已经是堆顶节点了,结束堆化 + if i == 0 { + break; + } + // 获取节点 i 的父节点 + let p = Self::parent(i); + // 当“节点无须修复”时,结束堆化 + if self.max_heap[i] <= self.max_heap[p] { + break; + } + // 交换两节点 + self.swap(i, p); + // 循环向上堆化 + i = p; + } + } + + /* 元素出堆 */ + fn pop(&mut self) -> i32 { + // 判空处理 + if self.is_empty() { + panic!("index out of bounds"); + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + self.swap(0, self.size() - 1); + // 删除节点 + let val = self.max_heap.pop().unwrap(); + // 从顶至底堆化 + self.sift_down(0); + // 返回堆顶元素 + val + } + + /* 从节点 i 开始,从顶至底堆化 */ + fn sift_down(&mut self, mut i: usize) { + loop { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let (l, r, mut ma) = (Self::left(i), Self::right(i), i); + if l < self.size() && self.max_heap[l] > self.max_heap[ma] { + ma = l; + } + if r < self.size() && self.max_heap[r] > self.max_heap[ma] { + ma = r; + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i { + break; + } + // 交换两节点 + self.swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + + /* 打印堆(二叉树) */ + fn print(&self) { + print_util::print_heap(self.max_heap.clone()); + } +} + +/* Driver Code */ +fn main() { + /* 初始化大顶堆 */ + let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + println!("\n输入列表并建堆后"); + max_heap.print(); + + /* 获取堆顶元素 */ + let peek = max_heap.peek(); + if let Some(peek) = peek { + println!("\n堆顶元素为 {}", peek); + } + + /* 元素入堆 */ + let val = 7; + max_heap.push(val); + println!("\n元素 {} 入堆后", val); + max_heap.print(); + + /* 堆顶元素出堆 */ + let peek = max_heap.pop(); + println!("\n堆顶元素 {} 出堆后", peek); + max_heap.print(); + + /* 获取堆大小 */ + let size = max_heap.size(); + println!("\n堆元素数量为 {}", size); + + /* 判断堆是否为空 */ + let is_empty = max_heap.is_empty(); + println!("\n堆是否为空 {}", is_empty); +} diff --git a/codes/rust/chapter_heap/top_k.rs b/codes/rust/chapter_heap/top_k.rs new file mode 100644 index 0000000000..d79076147f --- /dev/null +++ b/codes/rust/chapter_heap/top_k.rs @@ -0,0 +1,39 @@ +/* + * File: top_k.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cmp::Reverse; +use std::collections::BinaryHeap; + +/* 基于堆查找数组中最大的 k 个元素 */ +fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { + // BinaryHeap 是大顶堆,使用 Reverse 将元素取反,从而实现小顶堆 + let mut heap = BinaryHeap::>::new(); + // 将数组的前 k 个元素入堆 + for &num in nums.iter().take(k) { + heap.push(Reverse(num)); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for &num in nums.iter().skip(k) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if num > heap.peek().unwrap().0 { + heap.pop(); + heap.push(Reverse(num)); + } + } + heap +} + +/* Driver Code */ +fn main() { + let nums = vec![1, 7, 6, 3, 2]; + let k = 3; + + let res = top_k_heap(nums, k); + println!("最大的 {} 个元素为", k); + print_util::print_heap(res.into_iter().map(|item| item.0).collect()); +} diff --git a/codes/rust/chapter_searching/binary_search.rs b/codes/rust/chapter_searching/binary_search.rs new file mode 100644 index 0000000000..b60742e0fc --- /dev/null +++ b/codes/rust/chapter_searching/binary_search.rs @@ -0,0 +1,65 @@ +/* + * File: binary_search.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分查找(双闭区间) */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + let mut i = 0; + let mut j = nums.len() as i32 - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while i <= j { + let m = i + (j - i) / 2; // 计算中点索引 m + if nums[m as usize] < target { + // 此情况说明 target 在区间 [m+1, j] 中 + i = m + 1; + } else if nums[m as usize] > target { + // 此情况说明 target 在区间 [i, m-1] 中 + j = m - 1; + } else { + // 找到目标元素,返回其索引 + return m; + } + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* 二分查找(左闭右开区间) */ +fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + let mut i = 0; + let mut j = nums.len() as i32; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while i < j { + let m = i + (j - i) / 2; // 计算中点索引 m + if nums[m as usize] < target { + // 此情况说明 target 在区间 [m+1, j) 中 + i = m + 1; + } else if nums[m as usize] > target { + // 此情况说明 target 在区间 [i, m) 中 + j = m; + } else { + // 找到目标元素,返回其索引 + return m; + } + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分查找(双闭区间) + let mut index = binary_search(&nums, target); + println!("目标元素 6 的索引 = {index}"); + + // 二分查找(左闭右开区间) + index = binary_search_lcro(&nums, target); + println!("目标元素 6 的索引 = {index}"); +} diff --git a/codes/rust/chapter_searching/binary_search_edge.rs b/codes/rust/chapter_searching/binary_search_edge.rs new file mode 100644 index 0000000000..5d8195ab33 --- /dev/null +++ b/codes/rust/chapter_searching/binary_search_edge.rs @@ -0,0 +1,50 @@ +/* + * File: binary_search_edge.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ + +mod binary_search_insertion; + +use binary_search_insertion::binary_search_insertion; + +/* 二分查找最左一个 target */ +fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { + // 等价于查找 target 的插入点 + let i = binary_search_insertion(nums, target); + // 未找到 target ,返回 -1 + if i == nums.len() as i32 || nums[i as usize] != target { + return -1; + } + // 找到 target ,返回索引 i + i +} + +/* 二分查找最右一个 target */ +fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { + // 转化为查找最左一个 target + 1 + let i = binary_search_insertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + let j = i - 1; + // 未找到 target ,返回 -1 + if j == -1 || nums[j as usize] != target { + return -1; + } + // 找到 target ,返回索引 j + j +} + +/* Driver Code */ +fn main() { + // 包含重复元素的数组 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n数组 nums = {:?}", nums); + + // 二分查找左边界和右边界 + for target in [6, 7] { + let index = binary_search_left_edge(&nums, target); + println!("最左一个元素 {} 的索引为 {}", target, index); + let index = binary_search_right_edge(&nums, target); + println!("最右一个元素 {} 的索引为 {}", target, index); + } +} diff --git a/codes/rust/chapter_searching/binary_search_insertion.rs b/codes/rust/chapter_searching/binary_search_insertion.rs new file mode 100644 index 0000000000..d0c584e3ca --- /dev/null +++ b/codes/rust/chapter_searching/binary_search_insertion.rs @@ -0,0 +1,61 @@ +/* + * File: binary_search_insertion.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ +#![allow(unused)] + +/* 二分查找插入点(无重复元素) */ +fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化双闭区间 [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // 计算中点索引 m + if nums[m as usize] < target { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if nums[m as usize] > target { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + return m; + } + } + // 未找到 target ,返回插入点 i + i +} + +/* 二分查找插入点(存在重复元素) */ +pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化双闭区间 [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // 计算中点索引 m + if nums[m as usize] < target { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if nums[m as usize] > target { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + i +} + +/* Driver Code */ +fn main() { + // 无重复元素的数组 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + println!("\n数组 nums = {:?}", nums); + // 二分查找插入点 + for target in [6, 9] { + let index = binary_search_insertion_simple(&nums, target); + println!("元素 {} 的插入点的索引为 {}", target, index); + } + + // 包含重复元素的数组 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n数组 nums = {:?}", nums); + // 二分查找插入点 + for target in [2, 6, 20] { + let index = binary_search_insertion(&nums, target); + println!("元素 {} 的插入点的索引为 {}", target, index); + } +} diff --git a/codes/rust/chapter_searching/hashing_search.rs b/codes/rust/chapter_searching/hashing_search.rs new file mode 100644 index 0000000000..b2229edcf2 --- /dev/null +++ b/codes/rust/chapter_searching/hashing_search.rs @@ -0,0 +1,50 @@ +/* + * File: hashing_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* 哈希查找(数组) */ +fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 None + map.get(&target) +} + +/* 哈希查找(链表) */ +fn hashing_search_linked_list( + map: &HashMap>>>, + target: i32, +) -> Option<&Rc>>> { + // 哈希表的 key: 目标节点值,value: 节点对象 + // 若哈希表中无此 key ,返回 None + map.get(&target) +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* 哈希查找(数组) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化哈希表 + let mut map = HashMap::new(); + for (i, num) in nums.iter().enumerate() { + map.insert(*num, i); // key: 元素,value: 索引 + } + let index = hashing_search_array(&map, target); + println!("目标元素 3 的索引 = {}", index.unwrap()); + + /* 哈希查找(链表) */ + let head = ListNode::arr_to_linked_list(&nums); + // 初始化哈希表 + // let mut map1 = HashMap::new(); + let map1 = ListNode::linked_list_to_hashmap(head); + let node = hashing_search_linked_list(&map1, target); + println!("目标节点值 3 的对应节点对象为 {:?}", node); +} diff --git a/codes/rust/chapter_searching/linear_search.rs b/codes/rust/chapter_searching/linear_search.rs new file mode 100644 index 0000000000..25f6547bb6 --- /dev/null +++ b/codes/rust/chapter_searching/linear_search.rs @@ -0,0 +1,54 @@ +/* + * File: linear_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* 线性查找(数组) */ +fn linear_search_array(nums: &[i32], target: i32) -> i32 { + // 遍历数组 + for (i, num) in nums.iter().enumerate() { + // 找到目标元素,返回其索引 + if num == &target { + return i as i32; + } + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* 线性查找(链表) */ +fn linear_search_linked_list( + head: Rc>>, + target: i32, +) -> Option>>> { + // 找到目标节点,返回之 + if head.borrow().val == target { + return Some(head); + }; + // 找到目标节点,返回之 + if let Some(node) = &head.borrow_mut().next { + return linear_search_linked_list(node.clone(), target); + } + // 未找到目标节点,返回 None + return None; +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* 在数组中执行线性查找 */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + let index = linear_search_array(&nums, target); + println!("目标元素 3 的索引 = {}", index); + + /* 在链表中执行线性查找 */ + let head = ListNode::arr_to_linked_list(&nums); + let node = linear_search_linked_list(head.unwrap(), target); + println!("目标节点值 3 的对应节点对象为 {:?}", node); +} diff --git a/codes/rust/chapter_searching/two_sum.rs b/codes/rust/chapter_searching/two_sum.rs new file mode 100644 index 0000000000..5f38d372ab --- /dev/null +++ b/codes/rust/chapter_searching/two_sum.rs @@ -0,0 +1,52 @@ +/* + * File: two_sum.rs + * Created Time: 2023-01-14 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::HashMap; + +/* 方法一:暴力枚举 */ +pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { + let size = nums.len(); + // 两层循环,时间复杂度为 O(n^2) + for i in 0..size - 1 { + for j in i + 1..size { + if nums[i] + nums[j] == target { + return Some(vec![i as i32, j as i32]); + } + } + } + None +} + +/* 方法二:辅助哈希表 */ +pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { + // 辅助哈希表,空间复杂度为 O(n) + let mut dic = HashMap::new(); + // 单层循环,时间复杂度为 O(n) + for (i, num) in nums.iter().enumerate() { + match dic.get(&(target - num)) { + Some(v) => return Some(vec![*v as i32, i as i32]), + None => dic.insert(num, i as i32), + }; + } + None +} + +fn main() { + // ======= Test Case ======= + let nums = vec![2, 7, 11, 15]; + let target = 13; + + // ====== Driver Code ====== + // 方法一 + let res = two_sum_brute_force(&nums, target).unwrap(); + print!("方法一 res = "); + print_util::print_array(&res); + // 方法二 + let res = two_sum_hash_table(&nums, target).unwrap(); + print!("\n方法二 res = "); + print_util::print_array(&res); +} diff --git a/codes/rust/chapter_sorting/bubble_sort.rs b/codes/rust/chapter_sorting/bubble_sort.rs new file mode 100644 index 0000000000..972eb69028 --- /dev/null +++ b/codes/rust/chapter_sorting/bubble_sort.rs @@ -0,0 +1,53 @@ +/* + * File: bubble_sort.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* 冒泡排序 */ +fn bubble_sort(nums: &mut [i32]) { + // 外循环:未排序区间为 [0, i] + for i in (1..nums.len()).rev() { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交换 nums[j] 与 nums[j + 1] + nums.swap(j, j + 1); + } + } + } +} + +/* 冒泡排序(标志优化) */ +fn bubble_sort_with_flag(nums: &mut [i32]) { + // 外循环:未排序区间为 [0, i] + for i in (1..nums.len()).rev() { + let mut flag = false; // 初始化标志位 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交换 nums[j] 与 nums[j + 1] + nums.swap(j, j + 1); + flag = true; // 记录交换元素 + } + } + if !flag { + break; // 此轮“冒泡”未交换任何元素,直接跳出 + }; + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + bubble_sort(&mut nums); + print!("冒泡排序完成后 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [4, 1, 3, 1, 5, 2]; + bubble_sort_with_flag(&mut nums1); + print!("\n冒泡排序完成后 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/codes/rust/chapter_sorting/bucket_sort.rs b/codes/rust/chapter_sorting/bucket_sort.rs new file mode 100644 index 0000000000..c959559ac2 --- /dev/null +++ b/codes/rust/chapter_sorting/bucket_sort.rs @@ -0,0 +1,43 @@ +/* + * File: bucket_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 桶排序 */ +fn bucket_sort(nums: &mut [f64]) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + let k = nums.len() / 2; + let mut buckets = vec![vec![]; k]; + // 1. 将数组元素分配到各个桶中 + for &num in nums.iter() { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + let i = (num * k as f64) as usize; + // 将 num 添加进桶 i + buckets[i].push(num); + } + // 2. 对各个桶执行排序 + for bucket in &mut buckets { + // 使用内置排序函数,也可以替换成其他排序算法 + bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); + } + // 3. 遍历桶合并结果 + let mut i = 0; + for bucket in buckets.iter() { + for &num in bucket.iter() { + nums[i] = num; + i += 1; + } + } +} + +/* Driver Code */ +fn main() { + // 设输入数据为浮点数,范围为 [0, 1) + let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucket_sort(&mut nums); + print!("桶排序完成后 nums = "); + print_util::print_array(&nums); +} diff --git a/codes/rust/chapter_sorting/counting_sort.rs b/codes/rust/chapter_sorting/counting_sort.rs new file mode 100644 index 0000000000..ed478afb5a --- /dev/null +++ b/codes/rust/chapter_sorting/counting_sort.rs @@ -0,0 +1,70 @@ +/* + * File: counting_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +fn counting_sort_naive(nums: &mut [i32]) { + // 1. 统计数组最大元素 m + let m = *nums.iter().max().unwrap(); + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + let mut counter = vec![0; m as usize + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + let mut i = 0; + for num in 0..m + 1 { + for _ in 0..counter[num as usize] { + nums[i] = num; + i += 1; + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +fn counting_sort(nums: &mut [i32]) { + // 1. 统计数组最大元素 m + let m = *nums.iter().max().unwrap() as usize; + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + let mut counter = vec![0; m + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i in 0..m { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + let n = nums.len(); + let mut res = vec![0; n]; + for i in (0..n).rev() { + let num = nums[i]; + res[counter[num as usize] - 1] = num; // 将 num 放置到对应索引处 + counter[num as usize] -= 1; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + nums.copy_from_slice(&res) +} + +/* Driver Code */ +fn main() { + let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort_naive(&mut nums); + print!("计数排序(无法排序对象)完成后 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort(&mut nums1); + print!("\n计数排序完成后 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/codes/rust/chapter_sorting/heap_sort.rs b/codes/rust/chapter_sorting/heap_sort.rs new file mode 100644 index 0000000000..a70c2c8ab8 --- /dev/null +++ b/codes/rust/chapter_sorting/heap_sort.rs @@ -0,0 +1,54 @@ +/* + * File: heap_sort.rs + * Created Time: 2023-07-04 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { + loop { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let mut 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; + } + // 交换两节点 + nums.swap(i, ma); + // 循环向下堆化 + i = ma; + } +} + +/* 堆排序 */ +fn heap_sort(nums: &mut [i32]) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for i in (0..nums.len() / 2).rev() { + sift_down(nums, nums.len(), i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for i in (1..nums.len()).rev() { + // 交换根节点与最右叶节点(交换首元素与尾元素) + nums.swap(0, i); + // 以根节点为起点,从顶至底进行堆化 + sift_down(nums, i, 0); + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + heap_sort(&mut nums); + print!("堆排序完成后 nums = "); + print_util::print_array(&nums); +} diff --git a/codes/rust/chapter_sorting/insertion_sort.rs b/codes/rust/chapter_sorting/insertion_sort.rs new file mode 100644 index 0000000000..8772e7d021 --- /dev/null +++ b/codes/rust/chapter_sorting/insertion_sort.rs @@ -0,0 +1,29 @@ +/* + * File: insertion_sort.rs + * Created Time: 2023-02-13 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* 插入排序 */ +fn insertion_sort(nums: &mut [i32]) { + // 外循环:已排序区间为 [0, i-1] + for i in 1..nums.len() { + let (base, mut j) = (nums[i], (i - 1) as i32); + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while j >= 0 && nums[j as usize] > base { + nums[(j + 1) as usize] = nums[j as usize]; // 将 nums[j] 向右移动一位 + j -= 1; + } + nums[(j + 1) as usize] = base; // 将 base 赋值到正确位置 + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + insertion_sort(&mut nums); + print!("插入排序完成后 nums = "); + print_util::print_array(&nums); +} diff --git a/codes/rust/chapter_sorting/merge_sort.rs b/codes/rust/chapter_sorting/merge_sort.rs new file mode 100644 index 0000000000..6b28e8b011 --- /dev/null +++ b/codes/rust/chapter_sorting/merge_sort.rs @@ -0,0 +1,66 @@ +/** + * File: merge_sort.rs + * Created Time: 2023-02-14 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +/* 合并左子数组和右子数组 */ +fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + let tmp_size = right - left + 1; + let mut tmp = vec![0; tmp_size]; + // 初始化左子数组和右子数组的起始索引 + let (mut i, mut j, mut k) = (left, mid + 1, 0); + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i]; + i += 1; + } else { + tmp[k] = nums[j]; + j += 1; + } + k += 1; + } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while i <= mid { + tmp[k] = nums[i]; + k += 1; + i += 1; + } + while j <= right { + tmp[k] = nums[j]; + k += 1; + j += 1; + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for k in 0..tmp_size { + nums[left + k] = tmp[k]; + } +} + +/* 归并排序 */ +fn merge_sort(nums: &mut [i32], left: usize, right: usize) { + // 终止条件 + if left >= right { + return; // 当子数组长度为 1 时终止递归 + } + + // 划分阶段 + let mid = left + (right - left) / 2; // 计算中点 + merge_sort(nums, left, mid); // 递归左子数组 + merge_sort(nums, mid + 1, right); // 递归右子数组 + + // 合并阶段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +fn main() { + /* 归并排序 */ + let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; + let right = nums.len() - 1; + merge_sort(&mut nums, 0, right); + println!("归并排序完成后 nums = {:?}", nums); +} diff --git a/codes/rust/chapter_sorting/quick_sort.rs b/codes/rust/chapter_sorting/quick_sort.rs new file mode 100644 index 0000000000..c3e0b15b9b --- /dev/null +++ b/codes/rust/chapter_sorting/quick_sort.rs @@ -0,0 +1,148 @@ +/** + * File: quick_sort.rs + * Created Time: 2023-02-16 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +/* 快速排序 */ +struct QuickSort; + +impl QuickSort { + /* 哨兵划分 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 以 nums[left] 为基准数 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 从右向左找首个小于基准数的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 从左向右找首个大于基准数的元素 + } + nums.swap(i, j); // 交换这两个元素 + } + nums.swap(i, left); // 将基准数交换至两子数组的分界线 + i // 返回基准数的索引 + } + + /* 快速排序 */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 子数组长度为 1 时终止递归 + if left >= right { + return; + } + // 哨兵划分 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 递归左子数组、右子数组 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* 快速排序(中位基准数优化) */ +struct QuickSortMedian; + +impl QuickSortMedian { + /* 选取三个候选元素的中位数 */ + fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { + let (l, m, r) = (nums[left], nums[mid], 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 之间 + } + right + } + + /* 哨兵划分(三数取中值) */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 选取三个候选元素的中位数 + let med = Self::median_three(nums, left, (left + right) / 2, right); + // 将中位数交换至数组最左端 + nums.swap(left, med); + // 以 nums[left] 为基准数 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 从右向左找首个小于基准数的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 从左向右找首个大于基准数的元素 + } + nums.swap(i, j); // 交换这两个元素 + } + nums.swap(i, left); // 将基准数交换至两子数组的分界线 + i // 返回基准数的索引 + } + + /* 快速排序 */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 子数组长度为 1 时终止递归 + if left >= right { + return; + } + // 哨兵划分 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 递归左子数组、右子数组 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* 快速排序(尾递归优化) */ +struct QuickSortTailCall; + +impl QuickSortTailCall { + /* 哨兵划分 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 以 nums[left] 为基准数 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 从右向左找首个小于基准数的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 从左向右找首个大于基准数的元素 + } + nums.swap(i, j); // 交换这两个元素 + } + nums.swap(i, left); // 将基准数交换至两子数组的分界线 + i // 返回基准数的索引 + } + + /* 快速排序(尾递归优化) */ + pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { + // 子数组长度为 1 时终止 + while left < right { + // 哨兵划分操作 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 对两个子数组中较短的那个执行快速排序 + if pivot - left < right - pivot { + Self::quick_sort(left, pivot - 1, nums); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] + } else { + Self::quick_sort(pivot + 1, right, nums); // 递归排序右子数组 + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +fn main() { + /* 快速排序 */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序完成后 nums = {:?}", nums); + + /* 快速排序(中位基准数优化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序(中位基准数优化)完成后 nums = {:?}", nums); + + /* 快速排序(尾递归优化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序(尾递归优化)完成后 nums = {:?}", nums); +} diff --git a/codes/rust/chapter_sorting/radix_sort.rs b/codes/rust/chapter_sorting/radix_sort.rs new file mode 100644 index 0000000000..f213c4273c --- /dev/null +++ b/codes/rust/chapter_sorting/radix_sort.rs @@ -0,0 +1,63 @@ +/* + * File: radix_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +fn digit(num: i32, exp: i32) -> usize { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return ((num / exp) % 10) as usize; +} + +/* 计数排序(根据 nums 第 k 位排序) */ +fn counting_sort_digit(nums: &mut [i32], exp: i32) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + let mut counter = [0; 10]; + let n = nums.len(); + // 统计 0~9 各数字的出现次数 + for i in 0..n { + let d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d] += 1; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for i in 1..10 { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + let mut res = vec![0; n]; + for i in (0..n).rev() { + let d = digit(nums[i], exp); + let j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d] -= 1; // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + nums.copy_from_slice(&res); +} + +/* 基数排序 */ +fn radix_sort(nums: &mut [i32]) { + // 获取数组的最大元素,用于判断最大位数 + let m = *nums.into_iter().max().unwrap(); + // 按照从低位到高位的顺序遍历 + let mut exp = 1; + while exp <= m { + counting_sort_digit(nums, exp); + exp *= 10; + } +} + +/* Driver Code */ +fn main() { + // 基数排序 + let mut nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, + 63832996, + ]; + radix_sort(&mut nums); + print!("基数排序完成后 nums = "); + print_util::print_array(&nums); +} diff --git a/codes/rust/chapter_sorting/selection_sort.rs b/codes/rust/chapter_sorting/selection_sort.rs new file mode 100644 index 0000000000..87265a5417 --- /dev/null +++ b/codes/rust/chapter_sorting/selection_sort.rs @@ -0,0 +1,35 @@ +/* + * File: selection_sort.rs + * Created Time: 2023-05-30 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* 选择排序 */ +fn selection_sort(nums: &mut [i32]) { + if nums.is_empty() { + return; + } + let n = nums.len(); + // 外循环:未排序区间为 [i, n-1] + for i in 0..n - 1 { + // 内循环:找到未排序区间内的最小元素 + let mut k = i; + for j in i + 1..n { + if nums[j] < nums[k] { + k = j; // 记录最小元素的索引 + } + } + // 将该最小元素与未排序区间的首个元素交换 + nums.swap(i, k); + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + selection_sort(&mut nums); + print!("\n选择排序完成后 nums = "); + print_util::print_array(&nums); +} diff --git a/codes/rust/chapter_stack_and_queue/array_deque.rs b/codes/rust/chapter_stack_and_queue/array_deque.rs new file mode 100644 index 0000000000..905c371fb9 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/array_deque.rs @@ -0,0 +1,160 @@ +/* + * File: array_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; +/* 基于环形数组实现的双向队列 */ +struct ArrayDeque { + nums: Vec, // 用于存储双向队列元素的数组 + front: usize, // 队首指针,指向队首元素 + que_size: usize, // 双向队列长度 +} + +impl ArrayDeque { + /* 构造方法 */ + pub fn new(capacity: usize) -> Self { + Self { + nums: vec![0; capacity], + front: 0, + que_size: 0, + } + } + + /* 获取双向队列的容量 */ + pub fn capacity(&self) -> usize { + self.nums.len() + } + + /* 获取双向队列的长度 */ + pub fn size(&self) -> usize { + self.que_size + } + + /* 判断双向队列是否为空 */ + pub fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 计算环形数组索引 */ + fn index(&self, i: i32) -> usize { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return ((i + self.capacity() as i32) % self.capacity() as i32) as usize; + } + + /* 队首入队 */ + pub fn push_first(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("双向队列已满"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + self.front = self.index(self.front as i32 - 1); + // 将 num 添加至队首 + self.nums[self.front] = num; + self.que_size += 1; + } + + /* 队尾入队 */ + pub fn push_last(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("双向队列已满"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + let rear = self.index(self.front as i32 + self.que_size as i32); + // 将 num 添加至队尾 + self.nums[rear] = num; + self.que_size += 1; + } + + /* 队首出队 */ + fn pop_first(&mut self) -> i32 { + let num = self.peek_first(); + // 队首指针向后移动一位 + self.front = self.index(self.front as i32 + 1); + self.que_size -= 1; + num + } + + /* 队尾出队 */ + fn pop_last(&mut self) -> i32 { + let num = self.peek_last(); + self.que_size -= 1; + num + } + + /* 访问队首元素 */ + fn peek_first(&self) -> i32 { + if self.is_empty() { + panic!("双向队列为空") + }; + self.nums[self.front] + } + + /* 访问队尾元素 */ + fn peek_last(&self) -> i32 { + if self.is_empty() { + panic!("双向队列为空") + }; + // 计算尾元素索引 + let last = self.index(self.front as i32 + self.que_size as i32 - 1); + self.nums[last] + } + + /* 返回数组用于打印 */ + fn to_array(&self) -> Vec { + // 仅转换有效长度范围内的列表元素 + let mut res = vec![0; self.que_size]; + let mut j = self.front; + for i in 0..self.que_size { + res[i] = self.nums[self.index(j as i32)]; + j += 1; + } + res + } +} + +/* Driver Code */ +fn main() { + /* 初始化双向队列 */ + let mut deque = ArrayDeque::new(10); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("双向队列 deque = "); + print_util::print_array(&deque.to_array()); + + /* 访问元素 */ + let peek_first = deque.peek_first(); + print!("\n队首元素 peek_first = {}", peek_first); + let peek_last = deque.peek_last(); + print!("\n队尾元素 peek_last = {}", peek_last); + + /* 元素入队 */ + deque.push_last(4); + print!("\n元素 4 队尾入队后 deque = "); + print_util::print_array(&deque.to_array()); + deque.push_first(1); + print!("\n元素 1 队首入队后 deque = "); + print_util::print_array(&deque.to_array()); + + /* 元素出队 */ + let pop_last = deque.pop_last(); + print!("\n队尾出队元素 = {},队尾出队后 deque = ", pop_last); + print_util::print_array(&deque.to_array()); + let pop_first = deque.pop_first(); + print!("\n队首出队元素 = {},队首出队后 deque = ", pop_first); + print_util::print_array(&deque.to_array()); + + /* 获取双向队列的长度 */ + let size = deque.size(); + print!("\n双向队列长度 size = {}", size); + + /* 判断双向队列是否为空 */ + let is_empty = deque.is_empty(); + print!("\n双向队列是否为空 = {}", is_empty); +} diff --git a/codes/rust/chapter_stack_and_queue/array_queue.rs b/codes/rust/chapter_stack_and_queue/array_queue.rs new file mode 100644 index 0000000000..6f2af57318 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/array_queue.rs @@ -0,0 +1,125 @@ +/* + * File: array_queue.rs + * Created Time: 2023-02-06 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +/* 基于环形数组实现的队列 */ +struct ArrayQueue { + nums: Vec, // 用于存储队列元素的数组 + front: i32, // 队首指针,指向队首元素 + que_size: i32, // 队列长度 + que_capacity: i32, // 队列容量 +} + +impl ArrayQueue { + /* 构造方法 */ + fn new(capacity: i32) -> ArrayQueue { + ArrayQueue { + nums: vec![0; capacity as usize], + front: 0, + que_size: 0, + que_capacity: capacity, + } + } + + /* 获取队列的容量 */ + fn capacity(&self) -> i32 { + self.que_capacity + } + + /* 获取队列的长度 */ + fn size(&self) -> i32 { + self.que_size + } + + /* 判断队列是否为空 */ + fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 入队 */ + fn push(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("队列已满"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + let rear = (self.front + self.que_size) % self.que_capacity; + // 将 num 添加至队尾 + self.nums[rear as usize] = num; + self.que_size += 1; + } + + /* 出队 */ + fn pop(&mut self) -> i32 { + let num = self.peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + self.front = (self.front + 1) % self.que_capacity; + self.que_size -= 1; + num + } + + /* 访问队首元素 */ + fn peek(&self) -> i32 { + if self.is_empty() { + panic!("index out of bounds"); + } + self.nums[self.front as usize] + } + + /* 返回数组 */ + fn to_vector(&self) -> Vec { + let cap = self.que_capacity; + let mut j = self.front; + let mut arr = vec![0; self.que_size as usize]; + for i in 0..self.que_size { + arr[i as usize] = self.nums[(j % cap) as usize]; + j += 1; + } + arr + } +} + +/* Driver Code */ +fn main() { + /* 初始化队列 */ + let capacity = 10; + let mut queue = ArrayQueue::new(capacity); + + /* 元素入队 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + println!("队列 queue = {:?}", queue.to_vector()); + + /* 访问队首元素 */ + let peek = queue.peek(); + println!("队首元素 peek = {}", peek); + + /* 元素出队 */ + let pop = queue.pop(); + println!( + "出队元素 pop = {:?},出队后 queue = {:?}", + pop, + queue.to_vector() + ); + + /* 获取队列的长度 */ + let size = queue.size(); + println!("队列长度 size = {}", size); + + /* 判断队列是否为空 */ + let is_empty = queue.is_empty(); + println!("队列是否为空 = {}", is_empty); + + /* 测试环形数组 */ + for i in 0..10 { + queue.push(i); + queue.pop(); + println!("第 {:?} 轮入队 + 出队后 queue = {:?}", i, queue.to_vector()); + } +} diff --git a/codes/rust/chapter_stack_and_queue/array_stack.rs b/codes/rust/chapter_stack_and_queue/array_stack.rs new file mode 100644 index 0000000000..19c902207a --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/array_stack.rs @@ -0,0 +1,86 @@ +/* + * File: array_stack.rs + * Created Time: 2023-02-05 + * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* 基于数组实现的栈 */ +struct ArrayStack { + stack: Vec, +} + +impl ArrayStack { + /* 初始化栈 */ + fn new() -> ArrayStack { + ArrayStack:: { + stack: Vec::::new(), + } + } + + /* 获取栈的长度 */ + fn size(&self) -> usize { + self.stack.len() + } + + /* 判断栈是否为空 */ + fn is_empty(&self) -> bool { + self.size() == 0 + } + + /* 入栈 */ + fn push(&mut self, num: T) { + self.stack.push(num); + } + + /* 出栈 */ + fn pop(&mut self) -> Option { + self.stack.pop() + } + + /* 访问栈顶元素 */ + fn peek(&self) -> Option<&T> { + if self.is_empty() { + panic!("栈为空") + }; + self.stack.last() + } + + /* 返回 &Vec */ + fn to_array(&self) -> &Vec { + &self.stack + } +} + +/* Driver Code */ +fn main() { + // 初始化栈 + let mut stack = ArrayStack::::new(); + + // 元素入栈 + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("栈 stack = "); + print_util::print_array(stack.to_array()); + + //访问栈顶元素 + let peek = stack.peek().unwrap(); + print!("\n栈顶元素 peek = {}", peek); + + // 元素出栈 + let pop = stack.pop().unwrap(); + print!("\n出栈元素 pop = {pop},出栈后 stack = "); + print_util::print_array(stack.to_array()); + + // 获取栈的长度 + let size = stack.size(); + print!("\n栈的长度 size = {size}"); + + // 判断是否为空 + let is_empty = stack.is_empty(); + print!("\n栈是否为空 = {is_empty}"); +} diff --git a/codes/rust/chapter_stack_and_queue/deque.rs b/codes/rust/chapter_stack_and_queue/deque.rs new file mode 100644 index 0000000000..28b3ad38f2 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/deque.rs @@ -0,0 +1,49 @@ +/* + * File: deque.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // 初始化双向队列 + let mut deque: VecDeque = VecDeque::new(); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + print!("双向队列 deque = "); + print_util::print_queue(&deque); + + // 访问元素 + let peek_first = deque.front().unwrap(); + print!("\n队首元素 peekFirst = {peek_first}"); + let peek_last = deque.back().unwrap(); + print!("\n队尾元素 peekLast = {peek_last}"); + + /* 元素入队 */ + deque.push_back(4); + print!("\n元素 4 队尾入队后 deque = "); + print_util::print_queue(&deque); + deque.push_front(1); + print!("\n元素 1 队首入队后 deque = "); + print_util::print_queue(&deque); + + // 元素出队 + let pop_last = deque.pop_back().unwrap(); + print!("\n队尾出队元素 = {pop_last},队尾出队后 deque = "); + print_util::print_queue(&deque); + let pop_first = deque.pop_front().unwrap(); + print!("\n队首出队元素 = {pop_first},队首出队后 deque = "); + print_util::print_queue(&deque); + + // 获取双向队列的长度 + let size = deque.len(); + print!("\n双向队列长度 size = {size}"); + + // 判断双向队列是否为空 + let is_empty = deque.is_empty(); + print!("\n双向队列是否为空 = {is_empty}"); +} diff --git a/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs b/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs new file mode 100644 index 0000000000..06d026a036 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs @@ -0,0 +1,218 @@ +/* + * File: linkedlist_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 双向链表节点 */ +pub struct ListNode { + pub val: T, // 节点值 + pub next: Option>>>, // 后继节点指针 + pub prev: Option>>>, // 前驱节点指针 +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { + val, + next: None, + prev: None, + })) + } +} + +/* 基于双向链表实现的双向队列 */ +#[allow(dead_code)] +pub struct LinkedListDeque { + front: Option>>>, // 头节点 front + rear: Option>>>, // 尾节点 rear + que_size: usize, // 双向队列的长度 +} + +impl LinkedListDeque { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 获取双向队列的长度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判断双向队列是否为空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入队操作 */ + pub fn push(&mut self, num: T, is_front: bool) { + let node = ListNode::new(num); + // 队首入队操作 + if is_front { + match self.front.take() { + // 若链表为空,则令 front 和 rear 都指向 node + None => { + self.rear = Some(node.clone()); + self.front = Some(node); + } + // 将 node 添加至链表头部 + Some(old_front) => { + old_front.borrow_mut().prev = Some(node.clone()); + node.borrow_mut().next = Some(old_front); + self.front = Some(node); // 更新头节点 + } + } + } + // 队尾入队操作 + else { + match self.rear.take() { + // 若链表为空,则令 front 和 rear 都指向 node + None => { + self.front = Some(node.clone()); + self.rear = Some(node); + } + // 将 node 添加至链表尾部 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(node.clone()); + node.borrow_mut().prev = Some(old_rear); + self.rear = Some(node); // 更新尾节点 + } + } + } + self.que_size += 1; // 更新队列长度 + } + + /* 队首入队 */ + pub fn push_first(&mut self, num: T) { + self.push(num, true); + } + + /* 队尾入队 */ + pub fn push_last(&mut self, num: T) { + self.push(num, false); + } + + /* 出队操作 */ + pub fn pop(&mut self, is_front: bool) -> Option { + // 若队列为空,直接返回 None + if self.is_empty() { + return None; + }; + // 队首出队操作 + if is_front { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + new_front.borrow_mut().prev.take(); + self.front = Some(new_front); // 更新头节点 + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; // 更新队列长度 + old_front.borrow().val + }) + } + // 队尾出队操作 + else { + self.rear.take().map(|old_rear| { + match old_rear.borrow_mut().prev.take() { + Some(new_rear) => { + new_rear.borrow_mut().next.take(); + self.rear = Some(new_rear); // 更新尾节点 + } + None => { + self.front.take(); + } + } + self.que_size -= 1; // 更新队列长度 + old_rear.borrow().val + }) + } + } + + /* 队首出队 */ + pub fn pop_first(&mut self) -> Option { + return self.pop(true); + } + + /* 队尾出队 */ + pub fn pop_last(&mut self) -> Option { + return self.pop(false); + } + + /* 访问队首元素 */ + pub fn peek_first(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 访问队尾元素 */ + pub fn peek_last(&self) -> Option<&Rc>>> { + self.rear.as_ref() + } + + /* 返回数组用于打印 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + res + } +} + +/* Driver Code */ +fn main() { + /* 初始化双向队列 */ + let mut deque = LinkedListDeque::new(); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("双向队列 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 访问元素 */ + let peek_first = deque.peek_first().unwrap().borrow().val; + print!("\n队首元素 peek_first = {}", peek_first); + let peek_last = deque.peek_last().unwrap().borrow().val; + print!("\n队尾元素 peek_last = {}", peek_last); + + /* 元素入队 */ + deque.push_last(4); + print!("\n元素 4 队尾入队后 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + deque.push_first(1); + print!("\n元素 1 队首入队后 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 元素出队 */ + let pop_last = deque.pop_last().unwrap(); + print!("\n队尾出队元素 = {},队尾出队后 deque = ", pop_last); + print_util::print_array(&deque.to_array(deque.peek_first())); + let pop_first = deque.pop_first().unwrap(); + print!("\n队首出队元素 = {},队首出队后 deque = ", pop_first); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 获取双向队列的长度 */ + let size = deque.size(); + print!("\n双向队列长度 size = {}", size); + + /* 判断双向队列是否为空 */ + let is_empty = deque.is_empty(); + print!("\n双向队列是否为空 = {}", is_empty); +} diff --git a/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs b/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs new file mode 100644 index 0000000000..50230164d6 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs @@ -0,0 +1,126 @@ +/* + * File: linkedlist_queue.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 基于链表实现的队列 */ +#[allow(dead_code)] +pub struct LinkedListQueue { + front: Option>>>, // 头节点 front + rear: Option>>>, // 尾节点 rear + que_size: usize, // 队列的长度 +} + +impl LinkedListQueue { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 获取队列的长度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判断队列是否为空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入队 */ + pub fn push(&mut self, num: T) { + // 在尾节点后添加 num + let new_rear = ListNode::new(num); + match self.rear.take() { + // 如果队列不为空,则将该节点添加到尾节点后 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + // 如果队列为空,则令头、尾节点都指向该节点 + None => { + self.front = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + } + self.que_size += 1; + } + + /* 出队 */ + pub fn pop(&mut self) -> Option { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + self.front = Some(new_front); + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; + old_front.borrow().val + }) + } + + /* 访问队首元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 将链表转化为 Array 并返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + + res + } +} + +/* Driver Code */ +fn main() { + /* 初始化队列 */ + let mut queue = LinkedListQueue::new(); + + /* 元素入队 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print!("队列 queue = "); + print_util::print_array(&queue.to_array(queue.peek())); + + /* 访问队首元素 */ + let peek = queue.peek().unwrap().borrow().val; + print!("\n队首元素 peek = {}", peek); + + /* 元素出队 */ + let pop = queue.pop().unwrap(); + print!("\n出队元素 pop = {},出队后 queue = ", pop); + print_util::print_array(&queue.to_array(queue.peek())); + + /* 获取队列的长度 */ + let size = queue.size(); + print!("\n队列长度 size = {}", size); + + /* 判断队列是否为空 */ + let is_empty = queue.is_empty(); + print!("\n队列是否为空 = {}", is_empty); +} diff --git a/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs b/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs new file mode 100644 index 0000000000..be14156922 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs @@ -0,0 +1,101 @@ +/* + * File: linkedlist_stack.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 基于链表实现的栈 */ +#[allow(dead_code)] +pub struct LinkedListStack { + stack_peek: Option>>>, // 将头节点作为栈顶 + stk_size: usize, // 栈的长度 +} + +impl LinkedListStack { + pub fn new() -> Self { + Self { + stack_peek: None, + stk_size: 0, + } + } + + /* 获取栈的长度 */ + pub fn size(&self) -> usize { + return self.stk_size; + } + + /* 判断栈是否为空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入栈 */ + pub fn push(&mut self, num: T) { + let node = ListNode::new(num); + node.borrow_mut().next = self.stack_peek.take(); + self.stack_peek = Some(node); + self.stk_size += 1; + } + + /* 出栈 */ + pub fn pop(&mut self) -> Option { + self.stack_peek.take().map(|old_head| { + self.stack_peek = old_head.borrow_mut().next.take(); + self.stk_size -= 1; + + old_head.borrow().val + }) + } + + /* 访问栈顶元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.stack_peek.as_ref() + } + + /* 将 List 转化为 Array 并返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.push(node.borrow().val); + return nums; + } + return Vec::new(); + } +} + +/* Driver Code */ +fn main() { + /* 初始化栈 */ + let mut stack = LinkedListStack::new(); + + /* 元素入栈 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("栈 stack = "); + print_util::print_array(&stack.to_array(stack.peek())); + + /* 访问栈顶元素 */ + let peek = stack.peek().unwrap().borrow().val; + print!("\n栈顶元素 peek = {}", peek); + + /* 元素出栈 */ + let pop = stack.pop().unwrap(); + print!("\n出栈元素 pop = {},出栈后 stack = ", pop); + print_util::print_array(&stack.to_array(stack.peek())); + + /* 获取栈的长度 */ + let size = stack.size(); + print!("\n栈的长度 size = {}", size); + + /* 判断是否为空 */ + let is_empty = stack.is_empty(); + print!("\n栈是否为空 = {}", is_empty); +} diff --git a/codes/rust/chapter_stack_and_queue/queue.rs b/codes/rust/chapter_stack_and_queue/queue.rs new file mode 100644 index 0000000000..e2fd8400c1 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/queue.rs @@ -0,0 +1,41 @@ +/* + * File: queue.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // 初始化队列 + let mut queue: VecDeque = VecDeque::new(); + + // 元素入队 + queue.push_back(1); + queue.push_back(3); + queue.push_back(2); + queue.push_back(5); + queue.push_back(4); + print!("队列 queue = "); + print_util::print_queue(&queue); + + // 访问队首元素 + let peek = queue.front().unwrap(); + println!("\n队首元素 peek = {peek}"); + + // 元素出队 + let pop = queue.pop_front().unwrap(); + print!("出队元素 pop = {pop},出队后 queue = "); + print_util::print_queue(&queue); + + // 获取队列的长度 + let size = queue.len(); + print!("\n队列长度 size = {size}"); + + // 判断队列是否为空 + let is_empty = queue.is_empty(); + print!("\n队列是否为空 = {is_empty}"); +} diff --git a/codes/rust/chapter_stack_and_queue/stack.rs b/codes/rust/chapter_stack_and_queue/stack.rs new file mode 100644 index 0000000000..b2df65b5b9 --- /dev/null +++ b/codes/rust/chapter_stack_and_queue/stack.rs @@ -0,0 +1,40 @@ +/* + * File: stack.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* Driver Code */ +pub fn main() { + // 初始化栈 + // 在 rust 中,推荐将 Vec 当作栈来使用 + let mut stack: Vec = Vec::new(); + + // 元素入栈 + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("栈 stack = "); + print_util::print_array(&stack); + + // 访问栈顶元素 + let peek = stack.last().unwrap(); + print!("\n栈顶元素 peek = {peek}"); + + // 元素出栈 + let pop = stack.pop().unwrap(); + print!("\n出栈元素 pop = {pop},出栈后 stack = "); + print_util::print_array(&stack); + + // 获取栈的长度 + let size = stack.len(); + print!("\n栈的长度 size = {size}"); + + // 判断栈是否为空 + let is_empty = stack.is_empty(); + print!("\n栈是否为空 = {is_empty}"); +} diff --git a/codes/rust/chapter_tree/array_binary_tree.rs b/codes/rust/chapter_tree/array_binary_tree.rs new file mode 100644 index 0000000000..296f247df9 --- /dev/null +++ b/codes/rust/chapter_tree/array_binary_tree.rs @@ -0,0 +1,192 @@ +/* + * File: array_binary_tree.rs + * Created Time: 2023-07-25 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, tree_node}; + +/* 数组表示下的二叉树类 */ +struct ArrayBinaryTree { + tree: Vec>, +} + +impl ArrayBinaryTree { + /* 构造方法 */ + fn new(arr: Vec>) -> Self { + Self { tree: arr } + } + + /* 列表容量 */ + fn size(&self) -> i32 { + self.tree.len() as i32 + } + + /* 获取索引为 i 节点的值 */ + fn val(&self, i: i32) -> Option { + // 若索引越界,则返回 None ,代表空位 + if i < 0 || i >= self.size() { + None + } else { + self.tree[i as usize] + } + } + + /* 获取索引为 i 节点的左子节点的索引 */ + fn left(&self, i: i32) -> i32 { + 2 * i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + fn right(&self, i: i32) -> i32 { + 2 * i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + fn parent(&self, i: i32) -> i32 { + (i - 1) / 2 + } + + /* 层序遍历 */ + fn level_order(&self) -> Vec { + self.tree.iter().filter_map(|&x| x).collect() + } + + /* 深度优先遍历 */ + fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { + if self.val(i).is_none() { + return; + } + let val = self.val(i).unwrap(); + // 前序遍历 + if order == "pre" { + res.push(val); + } + self.dfs(self.left(i), order, res); + // 中序遍历 + if order == "in" { + res.push(val); + } + self.dfs(self.right(i), order, res); + // 后序遍历 + if order == "post" { + res.push(val); + } + } + + /* 前序遍历 */ + fn pre_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "pre", &mut res); + res + } + + /* 中序遍历 */ + fn in_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "in", &mut res); + res + } + + /* 后序遍历 */ + fn post_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "post", &mut res); + res + } +} + +/* Driver Code */ +fn main() { + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + let arr = vec![ + Some(1), + Some(2), + Some(3), + Some(4), + None, + Some(6), + Some(7), + Some(8), + Some(9), + None, + None, + Some(12), + None, + None, + Some(15), + ]; + + let root = tree_node::vec_to_tree(arr.clone()).unwrap(); + println!("\n初始化二叉树\n"); + println!("二叉树的数组表示:"); + println!( + "[{}]", + arr.iter() + .map(|&val| if let Some(val) = val { + format!("{val}") + } else { + "null".to_string() + }) + .collect::>() + .join(", ") + ); + println!("二叉树的链表表示:"); + print_util::print_tree(&root); + + // 数组表示下的二叉树类 + let abt = ArrayBinaryTree::new(arr); + + // 访问节点 + let i = 1; + let l = abt.left(i); + let r = abt.right(i); + let p = abt.parent(i); + println!( + "\n当前节点的索引为 {} ,值为 {}", + i, + if let Some(val) = abt.val(i) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其左子节点的索引为 {} ,值为 {}", + l, + if let Some(val) = abt.val(l) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其右子节点的索引为 {} ,值为 {}", + r, + if let Some(val) = abt.val(r) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其父节点的索引为 {} ,值为 {}", + p, + if let Some(val) = abt.val(p) { + format!("{val}") + } else { + "null".to_string() + } + ); + + // 遍历树 + let mut res = abt.level_order(); + println!("\n层序遍历为:{:?}", res); + res = abt.pre_order(); + println!("前序遍历为:{:?}", res); + res = abt.in_order(); + println!("中序遍历为:{:?}", res); + res = abt.post_order(); + println!("后序遍历为:{:?}", res); +} diff --git a/codes/rust/chapter_tree/avl_tree.rs b/codes/rust/chapter_tree/avl_tree.rs new file mode 100644 index 0000000000..faa3787efc --- /dev/null +++ b/codes/rust/chapter_tree/avl_tree.rs @@ -0,0 +1,297 @@ +/* + * File: avl_tree.rs + * Created Time: 2023-07-14 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +type OptionTreeNodeRc = Option>>; + +/* AVL 树 */ +struct AVLTree { + root: OptionTreeNodeRc, // 根节点 +} + +impl AVLTree { + /* 构造方法 */ + fn new() -> Self { + Self { root: None } + } + + /* 获取节点高度 */ + fn height(node: OptionTreeNodeRc) -> i32 { + // 空节点高度为 -1 ,叶节点高度为 0 + match node { + Some(node) => node.borrow().height, + None => -1, + } + } + + /* 更新节点高度 */ + fn update_height(node: OptionTreeNodeRc) { + if let Some(node) = node { + let left = node.borrow().left.clone(); + let right = node.borrow().right.clone(); + // 节点高度等于最高子树高度 + 1 + node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; + } + } + + /* 获取平衡因子 */ + fn balance_factor(node: OptionTreeNodeRc) -> i32 { + match node { + // 空节点平衡因子为 0 + None => 0, + // 节点平衡因子 = 左子树高度 - 右子树高度 + Some(node) => { + Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) + } + } + } + + /* 右旋操作 */ + fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().left.clone().unwrap(); + let grand_child = child.borrow().right.clone(); + // 以 child 为原点,将 node 向右旋转 + child.borrow_mut().right = Some(node.clone()); + node.borrow_mut().left = grand_child; + // 更新节点高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋转后子树的根节点 + Some(child) + } + None => None, + } + } + + /* 左旋操作 */ + fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().right.clone().unwrap(); + let grand_child = child.borrow().left.clone(); + // 以 child 为原点,将 node 向左旋转 + child.borrow_mut().left = Some(node.clone()); + node.borrow_mut().right = grand_child; + // 更新节点高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋转后子树的根节点 + Some(child) + } + None => None, + } + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + // 获取节点 node 的平衡因子 + let balance_factor = Self::balance_factor(node.clone()); + // 左偏树 + if balance_factor > 1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().left.clone()) >= 0 { + // 右旋 + Self::right_rotate(Some(node)) + } else { + // 先左旋后右旋 + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::left_rotate(left); + Self::right_rotate(Some(node)) + } + } + // 右偏树 + else if balance_factor < -1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().right.clone()) <= 0 { + // 左旋 + Self::left_rotate(Some(node)) + } else { + // 先右旋后左旋 + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::right_rotate(right); + Self::left_rotate(Some(node)) + } + } else { + // 平衡树,无须旋转,直接返回 + node + } + } + + /* 插入节点 */ + fn insert(&mut self, val: i32) { + self.root = Self::insert_helper(self.root.clone(), val); + } + + /* 递归插入节点(辅助方法) */ + fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查找插入位置并插入节点 */ + match { + let node_val = node.borrow().val; + node_val + } + .cmp(&val) + { + Ordering::Greater => { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::insert_helper(left, val); + } + Ordering::Less => { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::insert_helper(right, val); + } + Ordering::Equal => { + return Some(node); // 重复节点不插入,直接返回 + } + } + Self::update_height(Some(node.clone())); // 更新节点高度 + + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子树的根节点 + Some(node) + } + None => Some(TreeNode::new(val)), + } + } + + /* 删除节点 */ + fn remove(&self, val: i32) { + Self::remove_helper(self.root.clone(), val); + } + + /* 递归删除节点(辅助方法) */ + fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查找节点并删除 */ + if val < node.borrow().val { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::remove_helper(left, val); + } else if val > node.borrow().val { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, val); + } else if node.borrow().left.is_none() || node.borrow().right.is_none() { + let child = if node.borrow().left.is_some() { + node.borrow().left.clone() + } else { + node.borrow().right.clone() + }; + match child { + // 子节点数量 = 0 ,直接删除 node 并返回 + None => { + return None; + } + // 子节点数量 = 1 ,直接删除 node + Some(child) => node = child, + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + let mut temp = node.borrow().right.clone().unwrap(); + loop { + let temp_left = temp.borrow().left.clone(); + if temp_left.is_none() { + break; + } + temp = temp_left.unwrap(); + } + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); + node.borrow_mut().val = temp.borrow().val; + } + Self::update_height(Some(node.clone())); // 更新节点高度 + + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子树的根节点 + Some(node) + } + None => None, + } + } + + /* 查找节点 */ + fn search(&self, val: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // 循环查找,越过叶节点后跳出 + while let Some(current) = cur.clone() { + match current.borrow().val.cmp(&val) { + // 目标节点在 cur 的右子树中 + Ordering::Less => { + cur = current.borrow().right.clone(); + } + // 目标节点在 cur 的左子树中 + Ordering::Greater => { + cur = current.borrow().left.clone(); + } + // 找到目标节点,跳出循环 + Ordering::Equal => { + break; + } + } + } + // 返回目标节点 + cur + } +} + +/* Driver Code */ +fn main() { + fn test_insert(tree: &mut AVLTree, val: i32) { + tree.insert(val); + println!("\n插入节点 {} 后,AVL 树为", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + fn test_remove(tree: &mut AVLTree, val: i32) { + tree.remove(val); + println!("\n删除节点 {} 后,AVL 树为", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + /* 初始化空 AVL 树 */ + let mut avl_tree = AVLTree::new(); + + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + test_insert(&mut avl_tree, 1); + test_insert(&mut avl_tree, 2); + test_insert(&mut avl_tree, 3); + test_insert(&mut avl_tree, 4); + test_insert(&mut avl_tree, 5); + test_insert(&mut avl_tree, 8); + test_insert(&mut avl_tree, 7); + test_insert(&mut avl_tree, 9); + test_insert(&mut avl_tree, 10); + test_insert(&mut avl_tree, 6); + + /* 插入重复节点 */ + test_insert(&mut avl_tree, 7); + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + test_remove(&mut avl_tree, 8); // 删除度为 0 的节点 + test_remove(&mut avl_tree, 5); // 删除度为 1 的节点 + test_remove(&mut avl_tree, 4); // 删除度为 2 的节点 + + /* 查询节点 */ + let node = avl_tree.search(7); + if let Some(node) = node { + println!( + "\n查找到的节点对象为 {:?},节点值 = {}", + &*node.borrow(), + node.borrow().val + ); + } +} diff --git a/codes/rust/chapter_tree/binary_search_tree.rs b/codes/rust/chapter_tree/binary_search_tree.rs new file mode 100644 index 0000000000..720d9c642b --- /dev/null +++ b/codes/rust/chapter_tree/binary_search_tree.rs @@ -0,0 +1,195 @@ +/* + * File: binary_search_tree.rs + * Created Time: 2023-04-20 + * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +use hello_algo_rust::include::TreeNode; + +type OptionTreeNodeRc = Option>>; + +/* 二叉搜索树 */ +pub struct BinarySearchTree { + root: OptionTreeNodeRc, +} + +impl BinarySearchTree { + /* 构造方法 */ + pub fn new() -> Self { + // 初始化空树 + Self { root: None } + } + + /* 获取二叉树根节点 */ + pub fn get_root(&self) -> OptionTreeNodeRc { + self.root.clone() + } + + /* 查找节点 */ + pub fn search(&self, num: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // 循环查找,越过叶节点后跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 目标节点在 cur 的右子树中 + Ordering::Greater => cur = node.borrow().right.clone(), + // 目标节点在 cur 的左子树中 + Ordering::Less => cur = node.borrow().left.clone(), + // 找到目标节点,跳出循环 + Ordering::Equal => break, + } + } + + // 返回目标节点 + cur + } + + /* 插入节点 */ + pub fn insert(&mut self, num: i32) { + // 若树为空,则初始化根节点 + if self.root.is_none() { + self.root = Some(TreeNode::new(num)); + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 循环查找,越过叶节点后跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到重复节点,直接返回 + Ordering::Equal => return, + // 插入位置在 cur 的右子树中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 插入位置在 cur 的左子树中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 插入节点 + let pre = pre.unwrap(); + let node = Some(TreeNode::new(num)); + if num > pre.borrow().val { + pre.borrow_mut().right = node; + } else { + pre.borrow_mut().left = node; + } + } + + /* 删除节点 */ + pub fn remove(&mut self, num: i32) { + // 若树为空,直接提前返回 + if self.root.is_none() { + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 循环查找,越过叶节点后跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到待删除节点,跳出循环 + Ordering::Equal => break, + // 待删除节点在 cur 的右子树中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 待删除节点在 cur 的左子树中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 若无待删除节点,则直接返回 + if cur.is_none() { + return; + } + let cur = cur.unwrap(); + let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); + match (left_child.clone(), right_child.clone()) { + // 子节点数量 = 0 or 1 + (None, None) | (Some(_), None) | (None, Some(_)) => { + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + let child = left_child.or(right_child); + let pre = pre.unwrap(); + // 删除节点 cur + if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { + let left = pre.borrow().left.clone(); + if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { + pre.borrow_mut().left = child; + } else { + pre.borrow_mut().right = child; + } + } else { + // 若删除节点为根节点,则重新指定根节点 + self.root = child; + } + } + // 子节点数量 = 2 + (Some(_), Some(_)) => { + // 获取中序遍历中 cur 的下一个节点 + let mut tmp = cur.borrow().right.clone(); + while let Some(node) = tmp.clone() { + if node.borrow().left.is_some() { + tmp = node.borrow().left.clone(); + } else { + break; + } + } + let tmp_val = tmp.unwrap().borrow().val; + // 递归删除节点 tmp + self.remove(tmp_val); + // 用 tmp 覆盖 cur + cur.borrow_mut().val = tmp_val; + } + } + } +} + +/* Driver Code */ +fn main() { + /* 初始化二叉搜索树 */ + let mut bst = BinarySearchTree::new(); + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for &num in &nums { + bst.insert(num); + } + println!("\n初始化的二叉树为\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* 查找结点 */ + let node = bst.search(7); + println!( + "\n查找到的节点对象为 {:?},节点值 = {}", + node.clone().unwrap(), + node.clone().unwrap().borrow().val + ); + + /* 插入节点 */ + bst.insert(16); + println!("\n插入节点 16 后,二叉树为\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* 删除节点 */ + bst.remove(1); + println!("\n删除节点 1 后,二叉树为\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(2); + println!("\n删除节点 2 后,二叉树为\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(4); + println!("\n删除节点 4 后,二叉树为\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); +} diff --git a/codes/rust/chapter_tree/binary_tree.rs b/codes/rust/chapter_tree/binary_tree.rs new file mode 100644 index 0000000000..5843845da5 --- /dev/null +++ b/codes/rust/chapter_tree/binary_tree.rs @@ -0,0 +1,38 @@ +/** + * File: binary_tree.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ +use std::rc::Rc; +use hello_algo_rust::include::{print_util, TreeNode}; + +/* Driver Code */ +fn main() { + /* 初始化二叉树 */ + // 初始化节点 + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // 构建节点之间的引用(指针) + n1.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().right = Some(Rc::clone(&n3)); + n2.borrow_mut().left = Some(Rc::clone(&n4)); + n2.borrow_mut().right = Some(Rc::clone(&n5)); + println!("\n初始化二叉树\n"); + print_util::print_tree(&n1); + + // 插入节点与删除节点 + let p = TreeNode::new(0); + // 在 n1 -> n2 中间插入节点 P + p.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().left = Some(Rc::clone(&p)); + println!("\n插入节点 P 后\n"); + print_util::print_tree(&n1); + // 删除节点 P + drop(p); + n1.borrow_mut().left = Some(Rc::clone(&n2)); + println!("\n删除节点 P 后\n"); + print_util::print_tree(&n1); +} diff --git a/codes/rust/chapter_tree/binary_tree_bfs.rs b/codes/rust/chapter_tree/binary_tree_bfs.rs new file mode 100644 index 0000000000..e50c2df952 --- /dev/null +++ b/codes/rust/chapter_tree/binary_tree_bfs.rs @@ -0,0 +1,45 @@ +/* + * File: binary_tree_bfs.rs + * Created Time: 2023-04-07 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::collections::VecDeque; +use std::{cell::RefCell, rc::Rc}; + +/* 层序遍历 */ +fn level_order(root: &Rc>) -> Vec { + // 初始化队列,加入根节点 + let mut que = VecDeque::new(); + que.push_back(root.clone()); + // 初始化一个列表,用于保存遍历序列 + let mut vec = Vec::new(); + + while let Some(node) = que.pop_front() { + // 队列出队 + vec.push(node.borrow().val); // 保存节点值 + if let Some(left) = node.borrow().left.as_ref() { + que.push_back(left.clone()); // 左子节点入队 + } + if let Some(right) = node.borrow().right.as_ref() { + que.push_back(right.clone()); // 右子节点入队 + }; + } + vec +} + +/* Driver Code */ +fn main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); + println!("初始化二叉树\n"); + print_util::print_tree(&root); + + /* 层序遍历 */ + let vec = level_order(&root); + print!("\n层序遍历的节点打印序列 = {:?}", vec); +} diff --git a/codes/rust/chapter_tree/binary_tree_dfs.rs b/codes/rust/chapter_tree/binary_tree_dfs.rs new file mode 100644 index 0000000000..77533d52f9 --- /dev/null +++ b/codes/rust/chapter_tree/binary_tree_dfs.rs @@ -0,0 +1,87 @@ +/* + * File: binary_tree_dfs.rs + * Created Time: 2023-04-06 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 前序遍历 */ +fn pre_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 访问优先级:根节点 -> 左子树 -> 右子树 + let node = node.borrow(); + res.push(node.val); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* 中序遍历 */ +fn in_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 访问优先级:左子树 -> 根节点 -> 右子树 + let node = node.borrow(); + dfs(node.left.as_ref(), res); + res.push(node.val); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* 后序遍历 */ +fn post_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 访问优先级:左子树 -> 右子树 -> 根节点 + let node = node.borrow(); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + res.push(node.val); + } + } + + dfs(root, &mut result); + + result +} + +/* Driver Code */ +fn main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); + println!("初始化二叉树\n"); + print_util::print_tree(root.as_ref().unwrap()); + + /* 前序遍历 */ + let vec = pre_order(root.as_ref()); + println!("\n前序遍历的节点打印序列 = {:?}", vec); + + /* 中序遍历 */ + let vec = in_order(root.as_ref()); + println!("\n中序遍历的节点打印序列 = {:?}", vec); + + /* 后序遍历 */ + let vec = post_order(root.as_ref()); + print!("\n后序遍历的节点打印序列 = {:?}", vec); +} diff --git a/codes/rust/src/include/list_node.rs b/codes/rust/src/include/list_node.rs new file mode 100644 index 0000000000..003ab29893 --- /dev/null +++ b/codes/rust/src/include/list_node.rs @@ -0,0 +1,57 @@ +/* + * File: list_node.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) + */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct ListNode { + pub val: T, + pub next: Option>>>, +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { val, next: None })) + } + + /* 将数组反序列化为链表 */ + pub fn arr_to_linked_list(array: &[T]) -> Option>>> + where + T: Copy + Clone, + { + let mut head = None; + // insert in reverse order + for item in array.iter().rev() { + let node = Rc::new(RefCell::new(ListNode { + val: *item, + next: head.take(), + })); + head = Some(node); + } + head + } + + /* 将链表转化为哈希表 */ + pub fn linked_list_to_hashmap( + linked_list: Option>>>, + ) -> HashMap>>> + where + T: std::hash::Hash + Eq + Copy + Clone, + { + let mut hashmap = HashMap::new(); + let mut node = linked_list; + + while let Some(cur) = node { + let borrow = cur.borrow(); + hashmap.insert(borrow.val.clone(), cur.clone()); + node = borrow.next.clone(); + } + + hashmap + } +} diff --git a/codes/rust/src/include/mod.rs b/codes/rust/src/include/mod.rs new file mode 100644 index 0000000000..6cba6f9a52 --- /dev/null +++ b/codes/rust/src/include/mod.rs @@ -0,0 +1,16 @@ +/* + * File: include.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) + */ + +pub mod list_node; +pub mod print_util; +pub mod tree_node; +pub mod vertex; + +// rexport to include +pub use list_node::*; +pub use print_util::*; +pub use tree_node::*; +pub use vertex::*; diff --git a/codes/rust/src/include/print_util.rs b/codes/rust/src/include/print_util.rs new file mode 100644 index 0000000000..eccc659dd8 --- /dev/null +++ b/codes/rust/src/include/print_util.rs @@ -0,0 +1,103 @@ +/* + * File: print_util.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use std::cell::{Cell, RefCell}; +use std::fmt::Display; +use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; + +use super::list_node::ListNode; +use super::tree_node::{TreeNode, vec_to_tree}; + +struct Trunk<'a, 'b> { + prev: Option<&'a Trunk<'a, 'b>>, + str: Cell<&'b str>, +} + +/* 打印数组 */ +pub fn print_array(nums: &[T]) { + print!("["); + if nums.len() > 0 { + for (i, num) in nums.iter().enumerate() { + print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); + } + } else { + print!("]"); + } +} + +/* 打印哈希表 */ +pub fn print_hash_map(map: &HashMap) { + for (key, value) in map { + println!("{key} -> {value}"); + } +} + +/* 打印队列(双向队列) */ +pub fn print_queue(queue: &VecDeque) { + print!("["); + let iter = queue.iter(); + for (i, data) in iter.enumerate() { + print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); + } +} + +/* 打印链表 */ +pub fn print_linked_list(head: &Rc>>) { + print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); + if let Some(node) = &head.borrow().next { + return print_linked_list(node); + } +} + +/* 打印二叉树 */ +pub fn print_tree(root: &Rc>) { + _print_tree(Some(root), None, false); +} + +/* 打印二叉树 */ +fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { + if let Some(node) = root { + let mut prev_str = " "; + let trunk = Trunk { prev, str: Cell::new(prev_str) }; + _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); + + if prev.is_none() { + trunk.str.set("———"); + } else if is_right { + trunk.str.set("/———"); + prev_str = " |"; + } else { + trunk.str.set("\\———"); + prev.as_ref().unwrap().str.set(prev_str); + } + + show_trunks(Some(&trunk)); + println!(" {}", node.borrow().val); + if let Some(prev) = prev { + prev.str.set(prev_str); + } + trunk.str.set(" |"); + + _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); + } +} + +fn show_trunks(trunk: Option<&Trunk>) { + if let Some(trunk) = trunk { + show_trunks(trunk.prev); + print!("{}", trunk.str.get()); + } +} + +/* 打印堆 */ +pub fn print_heap(heap: Vec) { + println!("堆的数组表示:{:?}", heap); + println!("堆的树状表示:"); + if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { + print_tree(&root); + } +} diff --git a/codes/rust/src/include/tree_node.rs b/codes/rust/src/include/tree_node.rs new file mode 100644 index 0000000000..3cb37cfc51 --- /dev/null +++ b/codes/rust/src/include/tree_node.rs @@ -0,0 +1,92 @@ +/* + * File: tree_node.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) + */ + +use std::cell::RefCell; +use std::rc::Rc; + +/* 二叉树节点类型 */ +#[derive(Debug)] +pub struct TreeNode { + pub val: i32, + pub height: i32, + pub parent: Option>>, + pub left: Option>>, + pub right: Option>>, +} + +impl TreeNode { + /* 构造方法 */ + pub fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + parent: None, + left: None, + right: None, + })) + } +} + +#[macro_export] +macro_rules! op_vec { + ( $( $x:expr ),* ) => { + vec![ + $( Option::from($x).map(|x| x) ),* + ] + }; +} + +// 序列化编码规则请参考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二叉树的数组表示: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二叉树的链表表示: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* 将列表反序列化为二叉树:递归 */ +fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { + if i >= arr.len() || arr[i].is_none() { + return None; + } + let root = TreeNode::new(arr[i].unwrap()); + root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); + root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); + Some(root) +} + +/* 将列表反序列化为二叉树 */ +pub fn vec_to_tree(arr: Vec>) -> Option>> { + vec_to_tree_dfs(&arr, 0) +} + +/* 将二叉树序列化为列表:递归 */ +fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { + if let Some(root) = root { + // i + 1 is the minimum valid size to access index i + while res.len() < i + 1 { + res.push(None); + } + res[i] = Some(root.borrow().val); + tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); + tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); + } +} + +/* 将二叉树序列化为列表 */ +pub fn tree_to_vec(root: Option>>) -> Vec> { + let mut res = vec![]; + tree_to_vec_dfs(root.as_ref(), 0, &mut res); + res +} diff --git a/codes/rust/src/include/vertex.rs b/codes/rust/src/include/vertex.rs new file mode 100644 index 0000000000..ca1a5a8214 --- /dev/null +++ b/codes/rust/src/include/vertex.rs @@ -0,0 +1,27 @@ +/* + * File: vertex.rs + * Created Time: 2023-07-13 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 顶点类型 */ +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Vertex { + pub val: i32, +} + +impl From for Vertex { + fn from(value: i32) -> Self { + Self { val: value } + } +} + +/* 输入值列表 vals ,返回顶点列表 vets */ +pub fn vals_to_vets(vals: Vec) -> Vec { + vals.into_iter().map(|val| val.into()).collect() +} + +/* 输入顶点列表 vets ,返回值列表 vals */ +pub fn vets_to_vals(vets: Vec) -> Vec { + vets.into_iter().map(|vet| vet.val).collect() +} diff --git a/codes/rust/src/lib.rs b/codes/rust/src/lib.rs new file mode 100644 index 0000000000..2883b91047 --- /dev/null +++ b/codes/rust/src/lib.rs @@ -0,0 +1 @@ +pub mod include; diff --git a/codes/swift/Package.resolved b/codes/swift/Package.resolved new file mode 100644 index 0000000000..159c83d262 --- /dev/null +++ b/codes/swift/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "branch" : "release/1.1", + "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" + } + } + ], + "version" : 2 +} diff --git a/codes/swift/Package.swift b/codes/swift/Package.swift index 89c03d9f64..5326dc1387 100644 --- a/codes/swift/Package.swift +++ b/codes/swift/Package.swift @@ -5,36 +5,202 @@ import PackageDescription let package = Package( name: "HelloAlgo", products: [ + // chapter_computational_complexity + .executable(name: "iteration", targets: ["iteration"]), + .executable(name: "recursion", targets: ["recursion"]), .executable(name: "time_complexity", targets: ["time_complexity"]), .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), .executable(name: "space_complexity", targets: ["space_complexity"]), - .executable(name: "leetcode_two_sum", targets: ["leetcode_two_sum"]), + // chapter_array_and_linkedlist .executable(name: "array", targets: ["array"]), .executable(name: "linked_list", targets: ["linked_list"]), .executable(name: "list", targets: ["list"]), .executable(name: "my_list", targets: ["my_list"]), + // chapter_stack_and_queue .executable(name: "stack", targets: ["stack"]), .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), .executable(name: "array_stack", targets: ["array_stack"]), .executable(name: "queue", targets: ["queue"]), .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), .executable(name: "array_queue", targets: ["array_queue"]), + .executable(name: "deque", targets: ["deque"]), + .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), + .executable(name: "array_deque", targets: ["array_deque"]), + // chapter_hashing + .executable(name: "hash_map", targets: ["hash_map"]), + .executable(name: "array_hash_map", targets: ["array_hash_map"]), + .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), + .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), + .executable(name: "simple_hash", targets: ["simple_hash"]), + .executable(name: "built_in_hash", targets: ["built_in_hash"]), + // chapter_tree + .executable(name: "binary_tree", targets: ["binary_tree"]), + .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), + .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), + .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), + .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), + .executable(name: "avl_tree", targets: ["avl_tree"]), + // chapter_heap + .executable(name: "heap", targets: ["heap"]), + .executable(name: "my_heap", targets: ["my_heap"]), + .executable(name: "top_k", targets: ["top_k"]), + // chapter_graph + .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), + .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), + .executable(name: "graph_bfs", targets: ["graph_bfs"]), + .executable(name: "graph_dfs", targets: ["graph_dfs"]), + // chapter_searching + .executable(name: "binary_search", targets: ["binary_search"]), + .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), + .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), + .executable(name: "two_sum", targets: ["two_sum"]), + .executable(name: "linear_search", targets: ["linear_search"]), + .executable(name: "hashing_search", targets: ["hashing_search"]), + // chapter_sorting + .executable(name: "selection_sort", targets: ["selection_sort"]), + .executable(name: "bubble_sort", targets: ["bubble_sort"]), + .executable(name: "insertion_sort", targets: ["insertion_sort"]), + .executable(name: "quick_sort", targets: ["quick_sort"]), + .executable(name: "merge_sort", targets: ["merge_sort"]), + .executable(name: "heap_sort", targets: ["heap_sort"]), + .executable(name: "bucket_sort", targets: ["bucket_sort"]), + .executable(name: "counting_sort", targets: ["counting_sort"]), + .executable(name: "radix_sort", targets: ["radix_sort"]), + // chapter_divide_and_conquer + .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), + .executable(name: "build_tree", targets: ["build_tree"]), + .executable(name: "hanota", targets: ["hanota"]), + // chapter_backtracking + .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), + .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), + .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), + .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), + .executable(name: "permutations_i", targets: ["permutations_i"]), + .executable(name: "permutations_ii", targets: ["permutations_ii"]), + .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), + .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), + .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), + .executable(name: "n_queens", targets: ["n_queens"]), + // chapter_dynamic_programming + .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), + .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), + .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), + .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), + .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), + .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), + .executable(name: "min_path_sum", targets: ["min_path_sum"]), + .executable(name: "knapsack", targets: ["knapsack"]), + .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), + .executable(name: "coin_change", targets: ["coin_change"]), + .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), + .executable(name: "edit_distance", targets: ["edit_distance"]), + // chapter_greedy + .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), + .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), + .executable(name: "max_capacity", targets: ["max_capacity"]), + .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), ], targets: [ + // helper .target(name: "utils", path: "utils"), + .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), + .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), + // chapter_computational_complexity + .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), + .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), - .executableTarget(name: "leetcode_two_sum", path: "chapter_computational_complexity", sources: ["leetcode_two_sum.swift"]), + // chapter_array_and_linkedlist .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), + // chapter_stack_and_queue .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), + .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), + .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), + .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), + // chapter_hashing + .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), + .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), + .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), + .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), + .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), + .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), + // chapter_tree + .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), + .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), + .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), + .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), + .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), + .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), + // chapter_heap + .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), + .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), + .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), + // chapter_graph + .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), + .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), + .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), + .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), + // chapter_searching + .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), + .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), + .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), + .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), + .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), + .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), + // chapter_sorting + .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), + .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), + .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), + .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), + .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), + .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), + .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), + .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), + .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), + // chapter_divide_and_conquer + .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), + .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), + .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), + // chapter_backtracking + .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), + .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), + .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), + .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), + .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), + .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), + .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), + .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), + // chapter_dynamic_programming + .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), + .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), + .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), + .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), + .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), + .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), + .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), + .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), + .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), + .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), + .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), + .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), + // chapter_greedy + .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), + .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), + .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), + .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), ] ) diff --git a/codes/swift/chapter_array_and_linkedlist/array.swift b/codes/swift/chapter_array_and_linkedlist/array.swift index 344dc5abcd..5b0024931a 100644 --- a/codes/swift/chapter_array_and_linkedlist/array.swift +++ b/codes/swift/chapter_array_and_linkedlist/array.swift @@ -4,7 +4,7 @@ * Author: nuomi1 (nuomi1@qq.com) */ -/* 随机返回一个数组元素 */ +/* 随机访问元素 */ func randomAccess(nums: [Int]) -> Int { // 在区间 [0, nums.count) 中随机抽取一个数字 let randomIndex = nums.indices.randomElement()! @@ -28,18 +28,17 @@ func extend(nums: [Int], enlarge: Int) -> [Int] { /* 在数组的索引 index 处插入元素 num */ func insert(nums: inout [Int], num: Int, index: Int) { // 把索引 index 以及之后的所有元素向后移动一位 - for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { + for i in nums.indices.dropFirst(index).reversed() { nums[i] = nums[i - 1] } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num } -/* 删除索引 index 处元素 */ +/* 删除索引 index 处的元素 */ func remove(nums: inout [Int], index: Int) { - let count = nums.count // 把索引 index 之后的所有元素向前移动一位 - for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) { + for i in nums.indices.dropFirst(index).dropLast() { nums[i] = nums[i + 1] } } @@ -48,12 +47,17 @@ func remove(nums: inout [Int], index: Int) { func traverse(nums: [Int]) { var count = 0 // 通过索引遍历数组 - for _ in nums.indices { - count += 1 + for i in nums.indices { + count += nums[i] + } + // 直接遍历数组元素 + for num in nums { + count += num } - // 直接遍历数组 - for _ in nums { - count += 1 + // 同时遍历数据索引和元素 + for (i, num) in nums.enumerated() { + count += nums[i] + count += num } } diff --git a/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/codes/swift/chapter_array_and_linkedlist/linked_list.swift index 90218b26b9..907db057dc 100644 --- a/codes/swift/chapter_array_and_linkedlist/linked_list.swift +++ b/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -6,14 +6,14 @@ import utils -/* 在链表的结点 n0 之后插入结点 P */ +/* 在链表的节点 n0 之后插入节点 P */ func insert(n0: ListNode, P: ListNode) { let n1 = n0.next - n0.next = P P.next = n1 + n0.next = P } -/* 删除链表的结点 n0 之后的首个结点 */ +/* 删除链表的节点 n0 之后的首个节点 */ func remove(n0: ListNode) { if n0.next == nil { return @@ -22,10 +22,9 @@ func remove(n0: ListNode) { let P = n0.next let n1 = P?.next n0.next = n1 - P?.next = nil } -/* 访问链表中索引为 index 的结点 */ +/* 访问链表中索引为 index 的节点 */ func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { @@ -37,7 +36,7 @@ func access(head: ListNode, index: Int) -> ListNode? { return head } -/* 在链表中查找值为 target 的首个结点 */ +/* 在链表中查找值为 target 的首个节点 */ func find(head: ListNode, target: Int) -> Int { var head: ListNode? = head var index = 0 @@ -56,13 +55,13 @@ enum LinkedList { /* Driver Code */ static func main() { /* 初始化链表 */ - // 初始化各个结点 + // 初始化各个节点 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) let n3 = ListNode(x: 5) let n4 = ListNode(x: 4) - // 构建引用指向 + // 构建节点之间的引用 n0.next = n1 n1.next = n2 n2.next = n3 @@ -70,22 +69,22 @@ enum LinkedList { print("初始化的链表为") PrintUtil.printLinkedList(head: n0) - /* 插入结点 */ + /* 插入节点 */ insert(n0: n0, P: ListNode(x: 0)) - print("插入结点后的链表为") + print("插入节点后的链表为") PrintUtil.printLinkedList(head: n0) - /* 删除结点 */ + /* 删除节点 */ remove(n0: n0) - print("删除结点后的链表为") + print("删除节点后的链表为") PrintUtil.printLinkedList(head: n0) - /* 访问结点 */ + /* 访问节点 */ let node = access(head: n0, index: 3) - print("链表中索引 3 处的结点的值 = \(node!.val)") + print("链表中索引 3 处的节点的值 = \(node!.val)") - /* 查找结点 */ + /* 查找节点 */ let index = find(head: n0, target: 2) - print("链表中值为 2 的结点的索引 = \(index)") + print("链表中值为 2 的节点的索引 = \(index)") } } diff --git a/codes/swift/chapter_array_and_linkedlist/list.swift b/codes/swift/chapter_array_and_linkedlist/list.swift index 27801d13fc..2b815e1a78 100644 --- a/codes/swift/chapter_array_and_linkedlist/list.swift +++ b/codes/swift/chapter_array_and_linkedlist/list.swift @@ -9,56 +9,55 @@ enum List { /* Driver Code */ static func main() { /* 初始化列表 */ - var list = [1, 3, 2, 5, 4] - print("列表 list = \(list)") + var nums = [1, 3, 2, 5, 4] + print("列表 nums = \(nums)") /* 访问元素 */ - let num = list[1] + let num = nums[1] print("访问索引 1 处的元素,得到 num = \(num)") /* 更新元素 */ - list[1] = 0 - print("将索引 1 处的元素更新为 0 ,得到 list = \(list)") + nums[1] = 0 + print("将索引 1 处的元素更新为 0 ,得到 nums = \(nums)") /* 清空列表 */ - list.removeAll() - print("清空列表后 list = \(list)") + nums.removeAll() + print("清空列表后 nums = \(nums)") - /* 尾部添加元素 */ - list.append(1) - list.append(3) - list.append(2) - list.append(5) - list.append(4) - print("添加元素后 list = \(list)") + /* 在尾部添加元素 */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("添加元素后 nums = \(nums)") - /* 中间插入元素 */ - list.insert(6, at: 3) - print("在索引 3 处插入数字 6 ,得到 list = \(list)") + /* 在中间插入元素 */ + nums.insert(6, at: 3) + print("在索引 3 处插入数字 6 ,得到 nums = \(nums)") /* 删除元素 */ - list.remove(at: 3) - print("删除索引 3 处的元素,得到 list = \(list)") + nums.remove(at: 3) + print("删除索引 3 处的元素,得到 nums = \(nums)") /* 通过索引遍历列表 */ var count = 0 - for _ in list.indices { - count += 1 + for i in nums.indices { + count += nums[i] } - /* 直接遍历列表元素 */ count = 0 - for _ in list { - count += 1 + for x in nums { + count += x } /* 拼接两个列表 */ - let list1 = [6, 8, 7, 10, 9] - list.append(contentsOf: list1) - print("将列表 list1 拼接到 list 之后,得到 list = \(list)") + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) + print("将列表 nums1 拼接到 nums 之后,得到 nums = \(nums)") /* 排序列表 */ - list.sort() - print("排序列表后 list = \(list)") + nums.sort() + print("排序列表后 nums = \(nums)") } } diff --git a/codes/swift/chapter_array_and_linkedlist/my_list.swift b/codes/swift/chapter_array_and_linkedlist/my_list.swift index 423b020273..f170dc3d49 100644 --- a/codes/swift/chapter_array_and_linkedlist/my_list.swift +++ b/codes/swift/chapter_array_and_linkedlist/my_list.swift @@ -4,19 +4,22 @@ * Author: nuomi1 (nuomi1@qq.com) */ -/* 列表类简易实现 */ +/* 列表类 */ class MyList { - private var nums: [Int] // 数组(存储列表元素) - private var _capacity = 10 // 列表容量 - private var _size = 0 // 列表长度(即当前元素数量) - private let extendRatio = 2 // 每次列表扩容的倍数 + private var arr: [Int] // 数组(存储列表元素) + private var _capacity: Int // 列表容量 + private var _size: Int // 列表长度(当前元素数量) + private let extendRatio: Int // 每次列表扩容的倍数 - /* 构造函数 */ + /* 构造方法 */ init() { - nums = Array(repeating: 0, count: _capacity) + _capacity = 10 + _size = 0 + extendRatio = 2 + arr = Array(repeating: 0, count: _capacity) } - /* 获取列表长度(即当前元素数量)*/ + /* 获取列表长度(当前元素数量)*/ func size() -> Int { _size } @@ -29,45 +32,45 @@ class MyList { /* 访问元素 */ func get(index: Int) -> Int { // 索引如果越界则抛出错误,下同 - if index >= _size { + if index < 0 || index >= size() { fatalError("索引越界") } - return nums[index] + return arr[index] } /* 更新元素 */ func set(index: Int, num: Int) { - if index >= _size { + if index < 0 || index >= size() { fatalError("索引越界") } - nums[index] = num + arr[index] = num } - /* 尾部添加元素 */ + /* 在尾部添加元素 */ func add(num: Int) { // 元素数量超出容量时,触发扩容机制 - if _size == _capacity { + if size() == capacity() { extendCapacity() } - nums[_size] = num + arr[size()] = num // 更新元素数量 _size += 1 } - /* 中间插入元素 */ + /* 在中间插入元素 */ func insert(index: Int, num: Int) { - if index >= _size { + if index < 0 || index >= size() { fatalError("索引越界") } // 元素数量超出容量时,触发扩容机制 - if _size == _capacity { + if size() == capacity() { extendCapacity() } // 将索引 index 以及之后的元素都向后移动一位 - for j in sequence(first: _size - 1, next: { $0 >= index + 1 ? $0 - 1 : nil }) { - nums[j + 1] = nums[j] + for j in (index ..< size()).reversed() { + arr[j + 1] = arr[j] } - nums[index] = num + arr[index] = num // 更新元素数量 _size += 1 } @@ -75,35 +78,31 @@ class MyList { /* 删除元素 */ @discardableResult func remove(index: Int) -> Int { - if index >= _size { + if index < 0 || index >= size() { fatalError("索引越界") } - let num = nums[index] - // 将索引 index 之后的元素都向前移动一位 - for j in index ..< (_size - 1) { - nums[j] = nums[j + 1] + let num = arr[index] + // 将将索引 index 之后的元素都向前移动一位 + for j in index ..< (size() - 1) { + arr[j] = arr[j + 1] } // 更新元素数量 _size -= 1 - // 返回被删除元素 + // 返回被删除的元素 return num } /* 列表扩容 */ func extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) + // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组复制到新数组 + arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) // 更新列表容量 - _capacity = nums.count + _capacity = arr.count } /* 将列表转换为数组 */ func toArray() -> [Int] { - var nums = Array(repeating: 0, count: _size) - for i in 0 ..< _size { - nums[i] = get(index: i) - } - return nums + Array(arr.prefix(size())) } } @@ -112,36 +111,36 @@ enum _MyList { /* Driver Code */ static func main() { /* 初始化列表 */ - let list = MyList() - /* 尾部添加元素 */ - list.add(num: 1) - list.add(num: 3) - list.add(num: 2) - list.add(num: 5) - list.add(num: 4) - print("列表 list = \(list.toArray()) ,容量 = \(list.capacity()) ,长度 = \(list.size())") - - /* 中间插入元素 */ - list.insert(index: 3, num: 6) - print("在索引 3 处插入数字 6 ,得到 list = \(list.toArray())") + let nums = MyList() + /* 在尾部添加元素 */ + nums.add(num: 1) + nums.add(num: 3) + nums.add(num: 2) + nums.add(num: 5) + nums.add(num: 4) + print("列表 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,长度 = \(nums.size())") + + /* 在中间插入元素 */ + nums.insert(index: 3, num: 6) + print("在索引 3 处插入数字 6 ,得到 nums = \(nums.toArray())") /* 删除元素 */ - list.remove(index: 3) - print("删除索引 3 处的元素,得到 list = \(list.toArray())") + nums.remove(index: 3) + print("删除索引 3 处的元素,得到 nums = \(nums.toArray())") /* 访问元素 */ - let num = list.get(index: 1) + let num = nums.get(index: 1) print("访问索引 1 处的元素,得到 num = \(num)") /* 更新元素 */ - list.set(index: 1, num: 0) - print("将索引 1 处的元素更新为 0 ,得到 list = \(list.toArray())") + nums.set(index: 1, num: 0) + print("将索引 1 处的元素更新为 0 ,得到 nums = \(nums.toArray())") /* 测试扩容机制 */ for i in 0 ..< 10 { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list.add(num: i) + nums.add(num: i) } - print("扩容后的列表 list = \(list.toArray()) ,容量 = \(list.capacity()) ,长度 = \(list.size())") + print("扩容后的列表 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,长度 = \(nums.size())") } } diff --git a/codes/swift/chapter_backtracking/n_queens.swift b/codes/swift/chapter_backtracking/n_queens.swift new file mode 100644 index 0000000000..bb4e5b4ae9 --- /dev/null +++ b/codes/swift/chapter_backtracking/n_queens.swift @@ -0,0 +1,67 @@ +/** + * File: n_queens.swift + * Created Time: 2023-05-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯算法:n 皇后 */ +func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { + // 当放置完所有行时,记录解 + if row == n { + res.append(state) + return + } + // 遍历所有列 + for col in 0 ..< n { + // 计算该格子对应的主对角线和次对角线 + let diag1 = row - col + n - 1 + let diag2 = row + col + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 尝试:将皇后放置在该格子 + state[row][col] = "Q" + cols[col] = true + diags1[diag1] = true + diags2[diag2] = true + // 放置下一行 + backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + // 回退:将该格子恢复为空位 + state[row][col] = "#" + cols[col] = false + diags1[diag1] = false + diags2[diag2] = false + } + } +} + +/* 求解 n 皇后 */ +func nQueens(n: Int) -> [[[String]]] { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + var state = Array(repeating: Array(repeating: "#", count: n), count: n) + var cols = Array(repeating: false, count: n) // 记录列是否有皇后 + var diags1 = Array(repeating: false, count: 2 * n - 1) // 记录主对角线上是否有皇后 + var diags2 = Array(repeating: false, count: 2 * n - 1) // 记录次对角线上是否有皇后 + var res: [[[String]]] = [] + + backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + + return res +} + +@main +enum NQueens { + /* Driver Code */ + static func main() { + let n = 4 + let res = nQueens(n: n) + + print("输入棋盘长宽为 \(n)") + print("皇后放置方案共有 \(res.count) 种") + for state in res { + print("--------------------") + for row in state { + print(row) + } + } + } +} diff --git a/codes/swift/chapter_backtracking/permutations_i.swift b/codes/swift/chapter_backtracking/permutations_i.swift new file mode 100644 index 0000000000..defe7511ed --- /dev/null +++ b/codes/swift/chapter_backtracking/permutations_i.swift @@ -0,0 +1,50 @@ +/** + * File: permutations_i.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯算法:全排列 I */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 当状态长度等于元素数量时,记录解 + if state.count == choices.count { + res.append(state) + return + } + // 遍历所有选择 + for (i, choice) in choices.enumerated() { + // 剪枝:不允许重复选择元素 + if !selected[i] { + // 尝试:做出选择,更新状态 + selected[i] = true + state.append(choice) + // 进行下一轮选择 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.removeLast() + } + } +} + +/* 全排列 I */ +func permutationsI(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsI { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsI(nums: nums) + + print("输入数组 nums = \(nums)") + print("所有排列 res = \(res)") + } +} diff --git a/codes/swift/chapter_backtracking/permutations_ii.swift b/codes/swift/chapter_backtracking/permutations_ii.swift new file mode 100644 index 0000000000..cc36bd6e7e --- /dev/null +++ b/codes/swift/chapter_backtracking/permutations_ii.swift @@ -0,0 +1,52 @@ +/** + * File: permutations_ii.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯算法:全排列 II */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 当状态长度等于元素数量时,记录解 + if state.count == choices.count { + res.append(state) + return + } + // 遍历所有选择 + var duplicated: Set = [] + for (i, choice) in choices.enumerated() { + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if !selected[i], !duplicated.contains(choice) { + // 尝试:做出选择,更新状态 + duplicated.insert(choice) // 记录选择过的元素值 + selected[i] = true + state.append(choice) + // 进行下一轮选择 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.removeLast() + } + } +} + +/* 全排列 II */ +func permutationsII(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsII { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsII(nums: nums) + + print("输入数组 nums = \(nums)") + print("所有排列 res = \(res)") + } +} diff --git a/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift b/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift new file mode 100644 index 0000000000..3174ca5801 --- /dev/null +++ b/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var res: [TreeNode] = [] + +/* 前序遍历:例题一 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + if root.val == 7 { + // 记录解 + res.append(root) + } + preOrder(root: root.left) + preOrder(root: root.right) +} + +@main +enum PreorderTraversalICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + PrintUtil.printTree(root: root) + + // 前序遍历 + res = [] + preOrder(root: root) + + print("\n输出所有值为 7 的节点") + var vals: [Int] = [] + for node in res { + vals.append(node.val) + } + print(vals) + } +} diff --git a/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift b/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift new file mode 100644 index 0000000000..c12b292e64 --- /dev/null +++ b/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前序遍历:例题二 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 尝试 + path.append(root) + if root.val == 7 { + // 记录解 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() +} + +@main +enum PreorderTraversalIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + PrintUtil.printTree(root: root) + + // 前序遍历 + path = [] + res = [] + preOrder(root: root) + + print("\n输出所有根节点到节点 7 的路径") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift b/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift new file mode 100644 index 0000000000..e28491196f --- /dev/null +++ b/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前序遍历:例题三 */ +func preOrder(root: TreeNode?) { + // 剪枝 + guard let root = root, root.val != 3 else { + return + } + // 尝试 + path.append(root) + if root.val == 7 { + // 记录解 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() +} + +@main +enum PreorderTraversalIIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + PrintUtil.printTree(root: root) + + // 前序遍历 + path = [] + res = [] + preOrder(root: root) + + print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift b/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift new file mode 100644 index 0000000000..f33a084ec3 --- /dev/null +++ b/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 判断当前状态是否为解 */ +func isSolution(state: [TreeNode]) -> Bool { + !state.isEmpty && state.last!.val == 7 +} + +/* 记录解 */ +func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { + res.append(state) +} + +/* 判断在当前状态下,该选择是否合法 */ +func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { + choice != nil && choice!.val != 3 +} + +/* 更新状态 */ +func makeChoice(state: inout [TreeNode], choice: TreeNode) { + state.append(choice) +} + +/* 恢复状态 */ +func undoChoice(state: inout [TreeNode], choice: TreeNode) { + state.removeLast() +} + +/* 回溯算法:例题三 */ +func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { + // 检查是否为解 + if isSolution(state: state) { + recordSolution(state: state, res: &res) + } + // 遍历所有选择 + for choice in choices { + // 剪枝:检查选择是否合法 + if isValid(state: state, choice: choice) { + // 尝试:做出选择,更新状态 + makeChoice(state: &state, choice: choice) + // 进行下一轮选择 + backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state: &state, choice: choice) + } + } +} + +@main +enum PreorderTraversalIIITemplate { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + PrintUtil.printTree(root: root) + + // 回溯算法 + var state: [TreeNode] = [] + var res: [[TreeNode]] = [] + backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) + + print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/codes/swift/chapter_backtracking/subset_sum_i.swift b/codes/swift/chapter_backtracking/subset_sum_i.swift new file mode 100644 index 0000000000..b19fd112c8 --- /dev/null +++ b/codes/swift/chapter_backtracking/subset_sum_i.swift @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯算法:子集和 I */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等于 target 时,记录解 + if target == 0 { + res.append(state) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in choices.indices.dropFirst(start) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 { + break + } + // 尝试:做出选择,更新 target, start + state.append(choices[i]) + // 进行下一轮选择 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) + // 回退:撤销选择,恢复到之前的状态 + state.removeLast() + } +} + +/* 求解子集和 I */ +func subsetSumI(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状态(子集) + let nums = nums.sorted() // 对 nums 进行排序 + let start = 0 // 遍历起始点 + var res: [[Int]] = [] // 结果列表(子集列表) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumI { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumI(nums: nums, target: target) + + print("输入数组 nums = \(nums), target = \(target)") + print("所有和等于 \(target) 的子集 res = \(res)") + } +} diff --git a/codes/swift/chapter_backtracking/subset_sum_i_naive.swift b/codes/swift/chapter_backtracking/subset_sum_i_naive.swift new file mode 100644 index 0000000000..57e6a6d89c --- /dev/null +++ b/codes/swift/chapter_backtracking/subset_sum_i_naive.swift @@ -0,0 +1,51 @@ +/** + * File: subset_sum_i_naive.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯算法:子集和 I */ +func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { + // 子集和等于 target 时,记录解 + if total == target { + res.append(state) + return + } + // 遍历所有选择 + for i in choices.indices { + // 剪枝:若子集和超过 target ,则跳过该选择 + if total + choices[i] > target { + continue + } + // 尝试:做出选择,更新元素和 total + state.append(choices[i]) + // 进行下一轮选择 + backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) + // 回退:撤销选择,恢复到之前的状态 + state.removeLast() + } +} + +/* 求解子集和 I(包含重复子集) */ +func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状态(子集) + let total = 0 // 子集和 + var res: [[Int]] = [] // 结果列表(子集列表) + backtrack(state: &state, target: target, total: total, choices: nums, res: &res) + return res +} + +@main +enum SubsetSumINaive { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumINaive(nums: nums, target: target) + + print("输入数组 nums = \(nums), target = \(target)") + print("所有和等于 \(target) 的子集 res = \(res)") + print("请注意,该方法输出的结果包含重复集合") + } +} diff --git a/codes/swift/chapter_backtracking/subset_sum_ii.swift b/codes/swift/chapter_backtracking/subset_sum_ii.swift new file mode 100644 index 0000000000..91cc39dda9 --- /dev/null +++ b/codes/swift/chapter_backtracking/subset_sum_ii.swift @@ -0,0 +1,58 @@ +/** + * File: subset_sum_ii.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯算法:子集和 II */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等于 target 时,记录解 + if target == 0 { + res.append(state) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in choices.indices.dropFirst(start) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 { + break + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start, choices[i] == choices[i - 1] { + continue + } + // 尝试:做出选择,更新 target, start + state.append(choices[i]) + // 进行下一轮选择 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) + // 回退:撤销选择,恢复到之前的状态 + state.removeLast() + } +} + +/* 求解子集和 II */ +func subsetSumII(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状态(子集) + let nums = nums.sorted() // 对 nums 进行排序 + let start = 0 // 遍历起始点 + var res: [[Int]] = [] // 结果列表(子集列表) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumII { + /* Driver Code */ + static func main() { + let nums = [4, 4, 5] + let target = 9 + + let res = subsetSumII(nums: nums, target: target) + + print("输入数组 nums = \(nums), target = \(target)") + print("所有和等于 \(target) 的子集 res = \(res)") + } +} diff --git a/codes/swift/chapter_computational_complexity/iteration.swift b/codes/swift/chapter_computational_complexity/iteration.swift new file mode 100644 index 0000000000..8f331c0b0b --- /dev/null +++ b/codes/swift/chapter_computational_complexity/iteration.swift @@ -0,0 +1,75 @@ +/** + * File: iteration.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* for 循环 */ +func forLoop(n: Int) -> Int { + var res = 0 + // 循环求和 1, 2, ..., n-1, n + for i in 1 ... n { + res += i + } + return res +} + +/* while 循环 */ +func whileLoop(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while i <= n { + res += i + i += 1 // 更新条件变量 + } + return res +} + +/* while 循环(两次更新) */ +func whileLoopII(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while i <= n { + res += i + // 更新条件变量 + i += 1 + i *= 2 + } + return res +} + +/* 双层 for 循环 */ +func nestedForLoop(n: Int) -> String { + var res = "" + // 循环 i = 1, 2, ..., n-1, n + for i in 1 ... n { + // 循环 j = 1, 2, ..., n-1, n + for j in 1 ... n { + res.append("(\(i), \(j)), ") + } + } + return res +} + +@main +enum Iteration { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = forLoop(n: n) + print("\nfor 循环的求和结果 res = \(res)") + + res = whileLoop(n: n) + print("\nwhile 循环的求和结果 res = \(res)") + + res = whileLoopII(n: n) + print("\nwhile 循环(两次更新)求和结果 res = \(res)") + + let resStr = nestedForLoop(n: n) + print("\n双层 for 循环的遍历结果 \(resStr)") + } +} diff --git a/codes/swift/chapter_computational_complexity/leetcode_two_sum.swift b/codes/swift/chapter_computational_complexity/leetcode_two_sum.swift deleted file mode 100644 index e82872d38e..0000000000 --- a/codes/swift/chapter_computational_complexity/leetcode_two_sum.swift +++ /dev/null @@ -1,46 +0,0 @@ -/** - * File: leetcode_two_sum.swift - * Created Time: 2023-01-03 - * Author: nuomi1 (nuomi1@qq.com) - */ - -func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { - // 两层循环,时间复杂度 O(n^2) - for i in nums.indices.dropLast() { - for j in nums.indices.dropFirst(i + 1) { - if nums[i] + nums[j] == target { - return [i, j] - } - } - } - return [0] -} - -func twoSumHashTable(nums: [Int], target: Int) -> [Int] { - // 辅助哈希表,空间复杂度 O(n) - var dic: [Int: Int] = [:] - // 单层循环,时间复杂度 O(n) - for i in nums.indices { - if let j = dic[target - nums[i]] { - return [j, i] - } - dic[nums[i]] = i - } - return [0] -} - -@main -enum LeetcodeTwoSum { - static func main() { - // ======= Test Case ======= - let nums = [2, 7, 11, 15] - let target = 9 - // ====== Driver Code ====== - // 方法一 - var res = twoSumBruteForce(nums: nums, target: target) - print("方法一 res = \(res)") - // 方法二 - res = twoSumHashTable(nums: nums, target: target) - print("方法二 res = \(res)") - } -} diff --git a/codes/swift/chapter_computational_complexity/recursion.swift b/codes/swift/chapter_computational_complexity/recursion.swift new file mode 100644 index 0000000000..87c755356d --- /dev/null +++ b/codes/swift/chapter_computational_complexity/recursion.swift @@ -0,0 +1,79 @@ +/** + * File: recursion.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 递归 */ +func recur(n: Int) -> Int { + // 终止条件 + if n == 1 { + return 1 + } + // 递:递归调用 + let res = recur(n: n - 1) + // 归:返回结果 + return n + res +} + +/* 使用迭代模拟递归 */ +func forLoopRecur(n: Int) -> Int { + // 使用一个显式的栈来模拟系统调用栈 + var stack: [Int] = [] + var res = 0 + // 递:递归调用 + for i in (1 ... n).reversed() { + // 通过“入栈操作”模拟“递” + stack.append(i) + } + // 归:返回结果 + while !stack.isEmpty { + // 通过“出栈操作”模拟“归” + res += stack.removeLast() + } + // res = 1+2+3+...+n + return res +} + +/* 尾递归 */ +func tailRecur(n: Int, res: Int) -> Int { + // 终止条件 + if n == 0 { + return res + } + // 尾递归调用 + return tailRecur(n: n - 1, res: res + n) +} + +/* 斐波那契数列:递归 */ +func fib(n: Int) -> Int { + // 终止条件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // 递归调用 f(n) = f(n-1) + f(n-2) + let res = fib(n: n - 1) + fib(n: n - 2) + // 返回结果 f(n) + return res +} + +@main +enum Recursion { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = recursion.recur(n: n) + print("\n递归函数的求和结果 res = \(res)") + + res = recursion.forLoopRecur(n: n) + print("\n使用迭代模拟递归求和结果 res = \(res)") + + res = recursion.tailRecur(n: n, res: 0) + print("\n尾递归函数的求和结果 res = \(res)") + + res = recursion.fib(n: n) + print("\n斐波那契数列的第 \(n) 项为 \(res)") + } +} diff --git a/codes/swift/chapter_computational_complexity/space_complexity.swift b/codes/swift/chapter_computational_complexity/space_complexity.swift index fdd29f8e28..e35429ba45 100644 --- a/codes/swift/chapter_computational_complexity/space_complexity.swift +++ b/codes/swift/chapter_computational_complexity/space_complexity.swift @@ -9,7 +9,7 @@ import utils /* 函数 */ @discardableResult func function() -> Int { - // do something + // 执行某些操作 return 0 } diff --git a/codes/swift/chapter_computational_complexity/time_complexity.swift b/codes/swift/chapter_computational_complexity/time_complexity.swift index 1b1027ef29..4356e4baaa 100644 --- a/codes/swift/chapter_computational_complexity/time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -36,7 +36,7 @@ func arrayTraversal(nums: [Int]) -> Int { /* 平方阶 */ func quadratic(n: Int) -> Int { var count = 0 - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for _ in 0 ..< n { for _ in 0 ..< n { count += 1 @@ -48,9 +48,9 @@ func quadratic(n: Int) -> Int { /* 平方阶(冒泡排序) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) { - // 内循环:冒泡操作 + // 外循环:未排序区间为 [0, i] + for i in nums.indices.dropFirst().reversed() { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] @@ -68,7 +68,7 @@ func bubbleSort(nums: inout [Int]) -> Int { func exponential(n: Int) -> Int { var count = 0 var base = 1 - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for _ in 0 ..< n { for _ in 0 ..< base { count += 1 @@ -107,12 +107,12 @@ func logRecur(n: Int) -> Int { } /* 线性对数阶 */ -func linearLogRecur(n: Double) -> Int { +func linearLogRecur(n: Int) -> Int { if n <= 1 { return 1 } var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) - for _ in 0 ..< Int(n) { + for _ in stride(from: 0, to: n, by: 1) { count += 1 } return count @@ -140,33 +140,33 @@ enum TimeComplexity { print("输入数据大小 n = \(n)") var count = constant(n: n) - print("常数阶的计算操作数量 = \(count)") + print("常数阶的操作数量 = \(count)") count = linear(n: n) - print("线性阶的计算操作数量 = \(count)") + print("线性阶的操作数量 = \(count)") count = arrayTraversal(nums: Array(repeating: 0, count: n)) - print("线性阶(遍历数组)的计算操作数量 = \(count)") + print("线性阶(遍历数组)的操作数量 = \(count)") count = quadratic(n: n) - print("平方阶的计算操作数量 = \(count)") - var nums = Array(sequence(first: n, next: { $0 > 0 + 1 ? $0 - 1 : nil })) // [n,n-1,...,2,1] + print("平方阶的操作数量 = \(count)") + var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) - print("平方阶(冒泡排序)的计算操作数量 = \(count)") + print("平方阶(冒泡排序)的操作数量 = \(count)") count = exponential(n: n) - print("指数阶(循环实现)的计算操作数量 = \(count)") + print("指数阶(循环实现)的操作数量 = \(count)") count = expRecur(n: n) - print("指数阶(递归实现)的计算操作数量 = \(count)") + print("指数阶(递归实现)的操作数量 = \(count)") count = logarithmic(n: n) - print("对数阶(循环实现)的计算操作数量 = \(count)") + print("对数阶(循环实现)的操作数量 = \(count)") count = logRecur(n: n) - print("对数阶(递归实现)的计算操作数量 = \(count)") + print("对数阶(递归实现)的操作数量 = \(count)") - count = linearLogRecur(n: Double(n)) - print("线性对数阶(递归实现)的计算操作数量 = \(count)") + count = linearLogRecur(n: n) + print("线性对数阶(递归实现)的操作数量 = \(count)") count = factorialRecur(n: n) - print("阶乘阶(递归实现)的计算操作数量 = \(count)") + print("阶乘阶(递归实现)的操作数量 = \(count)") } } diff --git a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift index 3dac661d29..1844f1bdef 100644 --- a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -16,6 +16,8 @@ func randomNumbers(n: Int) -> [Int] { /* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums: [Int]) -> Int { for i in nums.indices { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if nums[i] == 1 { return i } diff --git a/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift b/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift new file mode 100644 index 0000000000..9aa25b1331 --- /dev/null +++ b/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift @@ -0,0 +1,44 @@ +/** + * File: binary_search_recur.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分查找:问题 f(i, j) */ +func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { + // 若区间为空,代表无目标元素,则返回 -1 + if i > j { + return -1 + } + // 计算中点索引 m + let m = (i + j) / 2 + if nums[m] < target { + // 递归子问题 f(m+1, j) + return dfs(nums: nums, target: target, i: m + 1, j: j) + } else if nums[m] > target { + // 递归子问题 f(i, m-1) + return dfs(nums: nums, target: target, i: i, j: m - 1) + } else { + // 找到目标元素,返回其索引 + return m + } +} + +/* 二分查找 */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 求解问题 f(0, n-1) + dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) +} + +@main +enum BinarySearchRecur { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + // 二分查找(双闭区间) + let index = binarySearch(nums: nums, target: target) + print("目标元素 6 的索引 = \(index)") + } +} diff --git a/codes/swift/chapter_divide_and_conquer/build_tree.swift b/codes/swift/chapter_divide_and_conquer/build_tree.swift new file mode 100644 index 0000000000..86193ddeaf --- /dev/null +++ b/codes/swift/chapter_divide_and_conquer/build_tree.swift @@ -0,0 +1,47 @@ +/** + * File: build_tree.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 构建二叉树:分治 */ +func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { + // 子树区间为空时终止 + if r - l < 0 { + return nil + } + // 初始化根节点 + let root = TreeNode(x: preorder[i]) + // 查询 m ,从而划分左右子树 + let m = inorderMap[preorder[i]]! + // 子问题:构建左子树 + root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) + // 子问题:构建右子树 + root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) + // 返回根节点 + return root +} + +/* 构建二叉树 */ +func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { + // 初始化哈希表,存储 inorder 元素到索引的映射 + let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } + return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) +} + +@main +enum BuildTree { + /* Driver Code */ + static func main() { + let preorder = [3, 9, 2, 1, 7] + let inorder = [9, 3, 1, 2, 7] + print("前序遍历 = \(preorder)") + print("中序遍历 = \(inorder)") + + let root = buildTree(preorder: preorder, inorder: inorder) + print("构建的二叉树为:") + PrintUtil.printTree(root: root) + } +} diff --git a/codes/swift/chapter_divide_and_conquer/hanota.swift b/codes/swift/chapter_divide_and_conquer/hanota.swift new file mode 100644 index 0000000000..1ba70fcbbf --- /dev/null +++ b/codes/swift/chapter_divide_and_conquer/hanota.swift @@ -0,0 +1,58 @@ +/** + * File: hanota.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 移动一个圆盘 */ +func move(src: inout [Int], tar: inout [Int]) { + // 从 src 顶部拿出一个圆盘 + let pan = src.popLast()! + // 将圆盘放入 tar 顶部 + tar.append(pan) +} + +/* 求解汉诺塔问题 f(i) */ +func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1 { + move(src: &src, tar: &tar) + return + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src: &src, tar: &tar) + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) +} + +/* 求解汉诺塔问题 */ +func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { + let n = A.count + // 列表尾部是柱子顶部 + // 将 src 顶部 n 个圆盘借助 B 移到 C + dfs(i: n, src: &A, buf: &B, tar: &C) +} + +@main +enum Hanota { + /* Driver Code */ + static func main() { + // 列表尾部是柱子顶部 + var A = [5, 4, 3, 2, 1] + var B: [Int] = [] + var C: [Int] = [] + print("初始状态下:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + + solveHanota(A: &A, B: &B, C: &C) + + print("圆盘移动完成后:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift new file mode 100644 index 0000000000..b5356ee5ec --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯 */ +func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { + // 当爬到第 n 阶时,方案数量加 1 + if state == n { + res[0] += 1 + } + // 遍历所有选择 + for choice in choices { + // 剪枝:不允许越过第 n 阶 + if state + choice > n { + continue + } + // 尝试:做出选择,更新状态 + backtrack(choices: choices, state: state + choice, n: n, res: &res) + // 回退 + } +} + +/* 爬楼梯:回溯 */ +func climbingStairsBacktrack(n: Int) -> Int { + let choices = [1, 2] // 可选择向上爬 1 阶或 2 阶 + let state = 0 // 从第 0 阶开始爬 + var res: [Int] = [] + res.append(0) // 使用 res[0] 记录方案数量 + backtrack(choices: choices, state: state, n: n, res: &res) + return res[0] +} + +@main +enum ClimbingStairsBacktrack { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsBacktrack(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift new file mode 100644 index 0000000000..176b2717d0 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 带约束爬楼梯:动态规划 */ +func climbingStairsConstraintDP(n: Int) -> Int { + if n == 1 || n == 2 { + return 1 + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in 3 ... n { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +@main +enum ClimbingStairsConstraintDP { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsConstraintDP(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift new file mode 100644 index 0000000000..1b1d3ffc93 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 搜索 */ +func dfs(i: Int) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1) + dfs(i: i - 2) + return count +} + +/* 爬楼梯:搜索 */ +func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) +} + +@main +enum ClimbingStairsDFS { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFS(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift new file mode 100644 index 0000000000..af9797fbca --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dfs_mem.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 记忆化搜索 */ +func dfs(i: Int, mem: inout [Int]) -> Int { + // 已知 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] + let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) + // 记录 dp[i] + mem[i] = count + return count +} + +/* 爬楼梯:记忆化搜索 */ +func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) +} + +@main +enum ClimbingStairsDFSMem { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFSMem(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift b/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift new file mode 100644 index 0000000000..4814ec5ee4 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬楼梯:动态规划 */ +func climbingStairsDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in 3 ... n { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 爬楼梯:空间优化后的动态规划 */ +func climbingStairsDPComp(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + var a = 1 + var b = 2 + for _ in 3 ... n { + (a, b) = (b, a + b) + } + return b +} + +@main +enum ClimbingStairsDP { + /* Driver Code */ + static func main() { + let n = 9 + + var res = climbingStairsDP(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + + res = climbingStairsDPComp(n: n) + print("爬 \(n) 阶楼梯共有 \(res) 种方案") + } +} diff --git a/codes/swift/chapter_dynamic_programming/coin_change.swift b/codes/swift/chapter_dynamic_programming/coin_change.swift new file mode 100644 index 0000000000..6558b6aa4b --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/coin_change.swift @@ -0,0 +1,69 @@ +/** + * File: coin_change.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零钱兑换:动态规划 */ +func coinChangeDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 状态转移:首行首列 + for a in 1 ... amt { + dp[0][a] = MAX + } + // 状态转移:其余行和列 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1 +} + +/* 零钱兑换:空间优化后的动态规划 */ +func coinChangeDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: MAX, count: amt + 1) + dp[0] = 0 + // 状态转移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return dp[amt] != MAX ? dp[amt] : -1 +} + +@main +enum CoinChange { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 4 + + // 动态规划 + var res = coinChangeDP(coins: coins, amt: amt) + print("凑到目标金额所需的最少硬币数量为 \(res)") + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins: coins, amt: amt) + print("凑到目标金额所需的最少硬币数量为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/coin_change_ii.swift b/codes/swift/chapter_dynamic_programming/coin_change_ii.swift new file mode 100644 index 0000000000..b1229f7287 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/coin_change_ii.swift @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零钱兑换 II:动态规划 */ +func coinChangeIIDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 初始化首列 + for i in 0 ... n { + dp[i][0] = 1 + } + // 状态转移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: amt + 1) + dp[0] = 1 + // 状态转移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +@main +enum CoinChangeII { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 5 + + // 动态规划 + var res = coinChangeIIDP(coins: coins, amt: amt) + print("凑出目标金额的硬币组合数量为 \(res)") + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(coins: coins, amt: amt) + print("凑出目标金额的硬币组合数量为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/edit_distance.swift b/codes/swift/chapter_dynamic_programming/edit_distance.swift new file mode 100644 index 0000000000..a108523329 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/edit_distance.swift @@ -0,0 +1,147 @@ +/** + * File: edit_distance.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 编辑距离:暴力搜索 */ +func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { + // 若 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.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 返回最少编辑步数 + return min(min(insert, delete), replace) + 1 +} + +/* 编辑距离:记忆化搜索 */ +func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若 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.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 记录并返回最少编辑步数 + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 编辑距离:动态规划 */ +func editDistanceDP(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) + // 状态转移:首行首列 + for i in 1 ... n { + dp[i][0] = i + } + for j in 1 ... m { + dp[0][j] = j + } + // 状态转移:其余行和列 + for i in 1 ... n { + for j in 1 ... m { + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* 编辑距离:空间优化后的动态规划 */ +func editDistanceDPComp(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: 0, count: m + 1) + // 状态转移:首行 + for j in 1 ... m { + dp[j] = j + } + // 状态转移:其余行 + for i in 1 ... n { + // 状态转移:首列 + var leftup = dp[0] // 暂存 dp[i-1, j-1] + dp[0] = i + // 状态转移:其余列 + for j in 1 ... m { + let temp = dp[j] + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m] +} + +@main +enum EditDistance { + /* Driver Code */ + static func main() { + let s = "bag" + let t = "pack" + let n = s.utf8CString.count + let m = t.utf8CString.count + + // 暴力搜索 + var res = editDistanceDFS(s: s, t: t, i: n, j: m) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + + // 记忆化搜索 + var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) + res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + + // 动态规划 + res = editDistanceDP(s: s, t: t) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + + // 空间优化后的动态规划 + res = editDistanceDPComp(s: s, t: t) + print("将 \(s) 更改为 \(t) 最少需要编辑 \(res) 步") + } +} diff --git a/codes/swift/chapter_dynamic_programming/knapsack.swift b/codes/swift/chapter_dynamic_programming/knapsack.swift new file mode 100644 index 0000000000..ec677a3c2b --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/knapsack.swift @@ -0,0 +1,110 @@ +/** + * File: knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 0-1 背包:暴力搜索 */ +func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超过背包容量,则只能选择不放入背包 + if wgt[i - 1] > c { + return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 返回两种方案中价值更大的那一个 + return max(no, yes) +} + +/* 0-1 背包:记忆化搜索 */ +func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { + // 若已选完所有物品或背包无剩余容量,则返回价值 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: wgt, val: val, mem: &mem, i: i - 1, c: c) + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* 0-1 背包:动态规划 */ +func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状态转移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空间优化后的动态规划 */ +func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 状态转移 + for i in 1 ... n { + // 倒序遍历 + for c in (1 ... cap).reversed() { + if wgt[i - 1] <= c { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum Knapsack { + /* Driver Code */ + static func main() { + let wgt = [10, 20, 30, 40, 50] + let val = [50, 120, 150, 210, 240] + let cap = 50 + let n = wgt.count + + // 暴力搜索 + var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 记忆化搜索 + var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) + res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 动态规划 + res = knapsackDP(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift b/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift new file mode 100644 index 0000000000..5f7cb9a269 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬楼梯最小代价:动态规划 */ +func minCostClimbingStairsDP(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 状态转移:从较小子问题逐步求解较大子问题 + for i in 3 ... n { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +func minCostClimbingStairsDPComp(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + var (a, b) = (cost[1], cost[2]) + for i in 3 ... n { + (a, b) = (b, min(a, b) + cost[i]) + } + return b +} + +@main +enum MinCostClimbingStairsDP { + /* Driver Code */ + static func main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print("输入楼梯的代价列表为 \(cost)") + + var res = minCostClimbingStairsDP(cost: cost) + print("爬完楼梯的最低代价为 \(res)") + + res = minCostClimbingStairsDPComp(cost: cost) + print("爬完楼梯的最低代价为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/min_path_sum.swift b/codes/swift/chapter_dynamic_programming/min_path_sum.swift new file mode 100644 index 0000000000..5c7823d5c1 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/min_path_sum.swift @@ -0,0 +1,123 @@ +/** + * File: min_path_sum.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最小路径和:暴力搜索 */ +func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { + // 若为左上角单元格,则终止搜索 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return .max + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + let up = minPathSumDFS(grid: grid, i: i - 1, j: j) + let left = minPathSumDFS(grid: grid, i: i, j: j - 1) + // 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j] +} + +/* 最小路径和:记忆化搜索 */ +func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若为左上角单元格,则终止搜索 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return .max + } + // 若已有记录,则直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左边和上边单元格的最小路径代价 + let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) + let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* 最小路径和:动态规划 */ +func minPathSumDP(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: m), count: n) + dp[0][0] = grid[0][0] + // 状态转移:首行 + for j in 1 ..< m { + dp[0][j] = dp[0][j - 1] + grid[0][j] + } + // 状态转移:首列 + for i in 1 ..< n { + dp[i][0] = dp[i - 1][0] + grid[i][0] + } + // 状态转移:其余行和列 + for i in 1 ..< n { + for j in 1 ..< m { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + } + } + return dp[n - 1][m - 1] +} + +/* 最小路径和:空间优化后的动态规划 */ +func minPathSumDPComp(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: m) + // 状态转移:首行 + dp[0] = grid[0][0] + for j in 1 ..< m { + dp[j] = dp[j - 1] + grid[0][j] + } + // 状态转移:其余行 + for i in 1 ..< n { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + // 状态转移:其余列 + for j in 1 ..< m { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + } + } + return dp[m - 1] +} + +@main +enum MinPathSum { + /* Driver Code */ + static func main() { + let grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ] + let n = grid.count + let m = grid[0].count + + // 暴力搜索 + var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) + print("从左上角到右下角的最小路径和为 \(res)") + + // 记忆化搜索 + var mem = Array(repeating: Array(repeating: -1, count: m), count: n) + res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) + print("从左上角到右下角的最小路径和为 \(res)") + + // 动态规划 + res = minPathSumDP(grid: grid) + print("从左上角到右下角的最小路径和为 \(res)") + + // 空间优化后的动态规划 + res = minPathSumDPComp(grid: grid) + print("从左上角到右下角的最小路径和为 \(res)") + } +} diff --git a/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift b/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift new file mode 100644 index 0000000000..39bc3df1c1 --- /dev/null +++ b/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 完全背包:动态规划 */ +func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状态转移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 完全背包:空间优化后的动态规划 */ +func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 状态转移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum UnboundedKnapsack { + /* Driver Code */ + static func main() { + let wgt = [1, 2, 3] + let val = [5, 11, 15] + let cap = 4 + + // 动态规划 + var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + } +} diff --git a/codes/swift/chapter_graph/graph_adjacency_list.swift b/codes/swift/chapter_graph/graph_adjacency_list.swift new file mode 100644 index 0000000000..e388c605c1 --- /dev/null +++ b/codes/swift/chapter_graph/graph_adjacency_list.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基于邻接表实现的无向图类 */ +public class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + public private(set) var adjList: [Vertex: [Vertex]] + + /* 构造方法 */ + public init(edges: [[Vertex]]) { + adjList = [:] + // 添加所有顶点和边 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 获取顶点数量 */ + public func size() -> Int { + adjList.count + } + + /* 添加边 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("参数错误") + } + // 添加边 vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 删除边 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("参数错误") + } + // 删除边 vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 添加顶点 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 在邻接表中添加一个新链表 + adjList[vet] = [] + } + + /* 删除顶点 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("参数错误") + } + // 在邻接表中删除顶点 vet 对应的链表 + adjList.removeValue(forKey: vet) + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 打印邻接表 */ + public func print() { + Swift.print("邻接表 =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* 初始化无向图 */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + let graph = GraphAdjList(edges: edges) + print("\n初始化后,图为") + graph.print() + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\n添加边 1-2 后,图为") + graph.print() + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\n删除边 1-3 后,图为") + graph.print() + + /* 添加顶点 */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\n添加顶点 6 后,图为") + graph.print() + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(vet: v[1]) + print("\n删除顶点 3 后,图为") + graph.print() + } +} + +#endif diff --git a/codes/swift/chapter_graph/graph_adjacency_list_target.swift b/codes/swift/chapter_graph/graph_adjacency_list_target.swift new file mode 120000 index 0000000000..77ec834d08 --- /dev/null +++ b/codes/swift/chapter_graph/graph_adjacency_list_target.swift @@ -0,0 +1 @@ +graph_adjacency_list.swift \ No newline at end of file diff --git a/codes/swift/chapter_graph/graph_adjacency_matrix.swift b/codes/swift/chapter_graph/graph_adjacency_matrix.swift new file mode 100644 index 0000000000..a600fb2172 --- /dev/null +++ b/codes/swift/chapter_graph/graph_adjacency_matrix.swift @@ -0,0 +1,130 @@ +/** + * File: graph_adjacency_matrix.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + private var vertices: [Int] // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + private var adjMat: [[Int]] // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造方法 */ + init(vertices: [Int], edges: [[Int]]) { + self.vertices = [] + adjMat = [] + // 添加顶点 + for val in vertices { + addVertex(val: val) + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for e in edges { + addEdge(i: e[0], j: e[1]) + } + } + + /* 获取顶点数量 */ + func size() -> Int { + vertices.count + } + + /* 添加顶点 */ + func addVertex(val: Int) { + let n = size() + // 向顶点列表中添加新顶点的值 + vertices.append(val) + // 在邻接矩阵中添加一行 + let newRow = Array(repeating: 0, count: n) + adjMat.append(newRow) + // 在邻接矩阵中添加一列 + for i in adjMat.indices { + adjMat[i].append(0) + } + } + + /* 删除顶点 */ + func removeVertex(index: Int) { + if index >= size() { + fatalError("越界") + } + // 在顶点列表中移除索引 index 的顶点 + vertices.remove(at: index) + // 在邻接矩阵中删除索引 index 的行 + adjMat.remove(at: index) + // 在邻接矩阵中删除索引 index 的列 + for i in adjMat.indices { + adjMat[i].remove(at: index) + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + func addEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + func removeEdge(i: Int, j: Int) { + // 索引越界与相等处理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 打印邻接矩阵 */ + func print() { + Swift.print("顶点列表 = ", terminator: "") + Swift.print(vertices) + Swift.print("邻接矩阵 =") + PrintUtil.printMatrix(matrix: adjMat) + } +} + +@main +enum GraphAdjacencyMatrix { + /* Driver Code */ + static func main() { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + let vertices = [1, 3, 2, 5, 4] + let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] + let graph = GraphAdjMat(vertices: vertices, edges: edges) + print("\n初始化后,图为") + graph.print() + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.addEdge(i: 0, j: 2) + print("\n添加边 1-2 后,图为") + graph.print() + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.removeEdge(i: 0, j: 1) + print("\n删除边 1-3 后,图为") + graph.print() + + /* 添加顶点 */ + graph.addVertex(val: 6) + print("\n添加顶点 6 后,图为") + graph.print() + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.removeVertex(index: 1) + print("\n删除顶点 3 后,图为") + graph.print() + } +} diff --git a/codes/swift/chapter_graph/graph_bfs.swift b/codes/swift/chapter_graph/graph_bfs.swift new file mode 100644 index 0000000000..549fb98b92 --- /dev/null +++ b/codes/swift/chapter_graph/graph_bfs.swift @@ -0,0 +1,56 @@ +/** + * File: graph_bfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希集合,用于记录已被访问过的顶点 + var visited: Set = [startVet] + // 队列用于实现 BFS + var que: [Vertex] = [startVet] + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while !que.isEmpty { + let vet = que.removeFirst() // 队首顶点出队 + res.append(vet) // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问的顶点 + } + que.append(adjVet) // 只入队未访问的顶点 + visited.insert(adjVet) // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res +} + +@main +enum GraphBFS { + /* Driver Code */ + static func main() { + /* 初始化无向图 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + let edges = [ + [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]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初始化后,图为") + graph.print() + + /* 广度优先遍历 */ + let res = graphBFS(graph: graph, startVet: v[0]) + print("\n广度优先遍历(BFS)顶点序列为") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/codes/swift/chapter_graph/graph_dfs.swift b/codes/swift/chapter_graph/graph_dfs.swift new file mode 100644 index 0000000000..0a2d801506 --- /dev/null +++ b/codes/swift/chapter_graph/graph_dfs.swift @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 深度优先遍历辅助函数 */ +func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // 记录访问顶点 + visited.insert(vet) // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希集合,用于记录已被访问过的顶点 + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res +} + +@main +enum GraphDFS { + /* Driver Code */ + static func main() { + /* 初始化无向图 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) + let edges = [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初始化后,图为") + graph.print() + + /* 深度优先遍历 */ + let res = graphDFS(graph: graph, startVet: v[0]) + print("\n深度优先遍历(DFS)顶点序列为") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/codes/swift/chapter_greedy/coin_change_greedy.swift b/codes/swift/chapter_greedy/coin_change_greedy.swift new file mode 100644 index 0000000000..e6d25c0584 --- /dev/null +++ b/codes/swift/chapter_greedy/coin_change_greedy.swift @@ -0,0 +1,54 @@ +/** + * File: coin_change_greedy.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零钱兑换:贪心 */ +func coinChangeGreedy(coins: [Int], amt: Int) -> Int { + // 假设 coins 列表有序 + var i = coins.count - 1 + var count = 0 + var amt = amt + // 循环进行贪心选择,直到无剩余金额 + while amt > 0 { + // 找到小于且最接近剩余金额的硬币 + while i > 0 && coins[i] > amt { + i -= 1 + } + // 选择 coins[i] + amt -= coins[i] + count += 1 + } + // 若未找到可行方案,则返回 -1 + return amt == 0 ? count : -1 +} + +@main +enum CoinChangeGreedy { + /* Driver Code */ + static func main() { + // 贪心:能够保证找到全局最优解 + var coins = [1, 5, 10, 20, 50, 100] + var amt = 186 + var res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("凑到 \(amt) 所需的最少硬币数量为 \(res)") + + // 贪心:无法保证找到全局最优解 + coins = [1, 20, 50] + amt = 60 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("凑到 \(amt) 所需的最少硬币数量为 \(res)") + print("实际上需要的最少数量为 3 ,即 20 + 20 + 20") + + // 贪心:无法保证找到全局最优解 + coins = [1, 49, 50] + amt = 98 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("凑到 \(amt) 所需的最少硬币数量为 \(res)") + print("实际上需要的最少数量为 2 ,即 49 + 49") + } +} diff --git a/codes/swift/chapter_greedy/fractional_knapsack.swift b/codes/swift/chapter_greedy/fractional_knapsack.swift new file mode 100644 index 0000000000..650eb95a3d --- /dev/null +++ b/codes/swift/chapter_greedy/fractional_knapsack.swift @@ -0,0 +1,57 @@ +/** + * File: fractional_knapsack.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 物品 */ +class Item { + var w: Int // 物品重量 + var v: Int // 物品价值 + + init(w: Int, v: Int) { + self.w = w + self.v = v + } +} + +/* 分数背包:贪心 */ +func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { + // 创建物品列表,包含两个属性:重量、价值 + var items = zip(wgt, val).map { Item(w: $0, v: $1) } + // 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } + // 循环贪心选择 + var res = 0.0 + var cap = cap + for item in items { + if item.w <= cap { + // 若剩余容量充足,则将当前物品整个装进背包 + res += Double(item.v) + cap -= item.w + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += Double(item.v) / Double(item.w) * Double(cap) + // 已无剩余容量,因此跳出循环 + break + } + } + return res +} + +@main +enum FractionalKnapsack { + /* Driver Code */ + static func main() { + // 物品重量 + let wgt = [10, 20, 30, 40, 50] + // 物品价值 + let val = [50, 120, 150, 210, 240] + // 背包容量 + let cap = 50 + + // 贪心算法 + let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) + print("不超过背包容量的最大物品价值为 \(res)") + } +} diff --git a/codes/swift/chapter_greedy/max_capacity.swift b/codes/swift/chapter_greedy/max_capacity.swift new file mode 100644 index 0000000000..dde37ca97a --- /dev/null +++ b/codes/swift/chapter_greedy/max_capacity.swift @@ -0,0 +1,38 @@ +/** + * File: max_capacity.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最大容量:贪心 */ +func maxCapacity(ht: [Int]) -> Int { + // 初始化 i, j,使其分列数组两端 + var i = ht.startIndex, j = ht.endIndex - 1 + // 初始最大容量为 0 + var res = 0 + // 循环贪心选择,直至两板相遇 + while i < j { + // 更新最大容量 + let cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 向内移动短板 + if ht[i] < ht[j] { + i += 1 + } else { + j -= 1 + } + } + return res +} + +@main +enum MaxCapacity { + /* Driver Code */ + static func main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4] + + // 贪心算法 + let res = maxCapacity(ht: ht) + print("最大容量为 \(res)") + } +} diff --git a/codes/swift/chapter_greedy/max_product_cutting.swift b/codes/swift/chapter_greedy/max_product_cutting.swift new file mode 100644 index 0000000000..ca78832a7a --- /dev/null +++ b/codes/swift/chapter_greedy/max_product_cutting.swift @@ -0,0 +1,43 @@ +/** + * File: max_product_cutting.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import Foundation + +func pow(_ x: Int, _ y: Int) -> Int { + Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) +} + +/* 最大切分乘积:贪心 */ +func maxProductCutting(n: Int) -> Int { + // 当 n <= 3 时,必须切分出一个 1 + if n <= 3 { + return 1 * (n - 1) + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + let a = n / 3 + let 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) +} + +@main +enum MaxProductCutting { + static func main() { + let n = 58 + + // 贪心算法 + let res = maxProductCutting(n: n) + print("最大切分乘积为 \(res)") + } +} diff --git a/codes/swift/chapter_hashing/array_hash_map.swift b/codes/swift/chapter_hashing/array_hash_map.swift new file mode 100644 index 0000000000..f65cdf9e18 --- /dev/null +++ b/codes/swift/chapter_hashing/array_hash_map.swift @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基于数组实现的哈希表 */ +class ArrayHashMap { + private var buckets: [Pair?] + + init() { + // 初始化数组,包含 100 个桶 + buckets = Array(repeating: nil, count: 100) + } + + /* 哈希函数 */ + private func hashFunc(key: Int) -> Int { + let index = key % 100 + return index + } + + /* 查询操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let pair = buckets[index] + return pair?.val + } + + /* 添加操作 */ + func put(key: Int, val: String) { + let pair = Pair(key: key, val: val) + let index = hashFunc(key: key) + buckets[index] = pair + } + + /* 删除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + // 置为 nil ,代表删除 + buckets[index] = nil + } + + /* 获取所有键值对 */ + func pairSet() -> [Pair] { + buckets.compactMap { $0 } + } + + /* 获取所有键 */ + func keySet() -> [Int] { + buckets.compactMap { $0?.key } + } + + /* 获取所有值 */ + func valueSet() -> [String] { + buckets.compactMap { $0?.val } + } + + /* 打印哈希表 */ + func print() { + for pair in pairSet() { + Swift.print("\(pair.key) -> \(pair.val)") + } + } +} + +@main +enum _ArrayHashMap { + /* Driver Code */ + static func main() { + /* 初始化哈希表 */ + let map = ArrayHashMap() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小啰") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + map.print() + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + let name = map.get(key: 15937)! + print("\n输入学号 15937 ,查询到姓名 \(name)") + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(key: 10583) + print("\n删除 10583 后,哈希表为\nKey -> Value") + map.print() + + /* 遍历哈希表 */ + print("\n遍历键值对 Key->Value") + for pair in map.pairSet() { + print("\(pair.key) -> \(pair.val)") + } + print("\n单独遍历键 Key") + for key in map.keySet() { + print(key) + } + print("\n单独遍历值 Value") + for val in map.valueSet() { + print(val) + } + } +} diff --git a/codes/swift/chapter_hashing/built_in_hash.swift b/codes/swift/chapter_hashing/built_in_hash.swift new file mode 100644 index 0000000000..b6384aa367 --- /dev/null +++ b/codes/swift/chapter_hashing/built_in_hash.swift @@ -0,0 +1,37 @@ +/** + * File: built_in_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BuiltInHash { + /* Driver Code */ + static func main() { + let num = 3 + let hashNum = num.hashValue + print("整数 \(num) 的哈希值为 \(hashNum)") + + let bol = true + let hashBol = bol.hashValue + print("布尔量 \(bol) 的哈希值为 \(hashBol)") + + let dec = 3.14159 + let hashDec = dec.hashValue + print("小数 \(dec) 的哈希值为 \(hashDec)") + + let str = "Hello 算法" + let hashStr = str.hashValue + print("字符串 \(str) 的哈希值为 \(hashStr)") + + let arr = [AnyHashable(12836), AnyHashable("小哈")] + let hashTup = arr.hashValue + print("数组 \(arr) 的哈希值为 \(hashTup)") + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + print("节点对象 \(obj) 的哈希值为 \(hashObj)") + } +} diff --git a/codes/swift/chapter_hashing/hash_map.swift b/codes/swift/chapter_hashing/hash_map.swift new file mode 100644 index 0000000000..a0a584132e --- /dev/null +++ b/codes/swift/chapter_hashing/hash_map.swift @@ -0,0 +1,51 @@ +/** + * File: hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum HashMap { + /* Driver Code */ + static func main() { + /* 初始化哈希表 */ + var map: [Int: String] = [:] + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map[12836] = "小哈" + map[15937] = "小啰" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鸭" + print("\n添加完成后,哈希表为\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + let name = map[15937]! + print("\n输入学号 15937 ,查询到姓名 \(name)") + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.removeValue(forKey: 10583) + print("\n删除 10583 后,哈希表为\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* 遍历哈希表 */ + print("\n遍历键值对 Key->Value") + for (key, value) in map { + print("\(key) -> \(value)") + } + print("\n单独遍历键 Key") + for key in map.keys { + print(key) + } + print("\n单独遍历值 Value") + for value in map.values { + print(value) + } + } +} diff --git a/codes/swift/chapter_hashing/hash_map_chaining.swift b/codes/swift/chapter_hashing/hash_map_chaining.swift new file mode 100644 index 0000000000..878ce4164f --- /dev/null +++ b/codes/swift/chapter_hashing/hash_map_chaining.swift @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 链式地址哈希表 */ +class HashMapChaining { + var size: Int // 键值对数量 + var capacity: Int // 哈希表容量 + var loadThres: Double // 触发扩容的负载因子阈值 + var extendRatio: Int // 扩容倍数 + var buckets: [[Pair]] // 桶数组 + + /* 构造方法 */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: [], count: capacity) + } + + /* 哈希函数 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 负载因子 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* 查询操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 遍历桶,若找到 key ,则返回对应 val + for pair in bucket { + if pair.key == key { + return pair.val + } + } + // 若未找到 key ,则返回 nil + return nil + } + + /* 添加操作 */ + func put(key: Int, val: String) { + // 当负载因子超过阈值时,执行扩容 + if loadFactor() > loadThres { + extend() + } + let index = hashFunc(key: key) + let bucket = buckets[index] + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in bucket { + if pair.key == key { + pair.val = val + return + } + } + // 若无该 key ,则将键值对添加至尾部 + let pair = Pair(key: key, val: val) + buckets[index].append(pair) + size += 1 + } + + /* 删除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 遍历桶,从中删除键值对 + for (pairIndex, pair) in bucket.enumerated() { + if pair.key == key { + buckets[index].remove(at: pairIndex) + size -= 1 + break + } + } + } + + /* 扩容哈希表 */ + func extend() { + // 暂存原哈希表 + let bucketsTmp = buckets + // 初始化扩容后的新哈希表 + capacity *= extendRatio + buckets = Array(repeating: [], count: capacity) + size = 0 + // 将键值对从原哈希表搬运至新哈希表 + for bucket in bucketsTmp { + for pair in bucket { + put(key: pair.key, val: pair.val) + } + } + } + + /* 打印哈希表 */ + func print() { + for bucket in buckets { + let res = bucket.map { "\($0.key) -> \($0.val)" } + Swift.print(res) + } + } +} + +@main +enum _HashMapChaining { + /* Driver Code */ + static func main() { + /* 初始化哈希表 */ + let map = HashMapChaining() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小啰") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + map.print() + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + let name = map.get(key: 13276) + print("\n输入学号 13276 ,查询到姓名 \(name!)") + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(key: 12836) + print("\n删除 12836 后,哈希表为\nKey -> Value") + map.print() + } +} diff --git a/codes/swift/chapter_hashing/hash_map_open_addressing.swift b/codes/swift/chapter_hashing/hash_map_open_addressing.swift new file mode 100644 index 0000000000..e65bca3708 --- /dev/null +++ b/codes/swift/chapter_hashing/hash_map_open_addressing.swift @@ -0,0 +1,164 @@ +/** + * File: hash_map_open_addressing.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + var size: Int // 键值对数量 + var capacity: Int // 哈希表容量 + var loadThres: Double // 触发扩容的负载因子阈值 + var extendRatio: Int // 扩容倍数 + var buckets: [Pair?] // 桶数组 + var TOMBSTONE: Pair // 删除标记 + + /* 构造方法 */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: nil, count: capacity) + TOMBSTONE = Pair(key: -1, val: "-1") + } + + /* 哈希函数 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 负载因子 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* 搜索 key 对应的桶索引 */ + func findBucket(key: Int) -> Int { + var index = hashFunc(key: key) + var firstTombstone = -1 + // 线性探测,当遇到空桶时跳出 + while buckets[index] != nil { + // 若遇到 key ,返回对应的桶索引 + if buckets[index]!.key == key { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if firstTombstone != -1 { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 返回移动后的桶索引 + } + return index // 返回桶索引 + } + // 记录遇到的首个删除标记 + if firstTombstone == -1 && buckets[index] == TOMBSTONE { + firstTombstone = index + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % capacity + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone == -1 ? index : firstTombstone + } + + /* 查询操作 */ + func get(key: Int) -> String? { + // 搜索 key 对应的桶索引 + let index = findBucket(key: key) + // 若找到键值对,则返回对应 val + if buckets[index] != nil, buckets[index] != TOMBSTONE { + return buckets[index]!.val + } + // 若键值对不存在,则返回 null + return nil + } + + /* 添加操作 */ + func put(key: Int, val: String) { + // 当负载因子超过阈值时,执行扩容 + if loadFactor() > loadThres { + extend() + } + // 搜索 key 对应的桶索引 + let index = findBucket(key: key) + // 若找到键值对,则覆盖 val 并返回 + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index]!.val = val + return + } + // 若键值对不存在,则添加该键值对 + buckets[index] = Pair(key: key, val: val) + size += 1 + } + + /* 删除操作 */ + func remove(key: Int) { + // 搜索 key 对应的桶索引 + let index = findBucket(key: key) + // 若找到键值对,则用删除标记覆盖它 + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index] = TOMBSTONE + size -= 1 + } + } + + /* 扩容哈希表 */ + func extend() { + // 暂存原哈希表 + let bucketsTmp = buckets + // 初始化扩容后的新哈希表 + capacity *= extendRatio + buckets = Array(repeating: nil, count: capacity) + size = 0 + // 将键值对从原哈希表搬运至新哈希表 + for pair in bucketsTmp { + if let pair, pair != TOMBSTONE { + put(key: pair.key, val: pair.val) + } + } + } + + /* 打印哈希表 */ + func print() { + for pair in buckets { + if pair == nil { + Swift.print("null") + } else if pair == TOMBSTONE { + Swift.print("TOMBSTONE") + } else { + Swift.print("\(pair!.key) -> \(pair!.val)") + } + } + } +} + +@main +enum _HashMapOpenAddressing { + /* Driver Code */ + static func main() { + /* 初始化哈希表 */ + let map = HashMapOpenAddressing() + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小啰") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + map.print() + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + let name = map.get(key: 13276) + print("\n输入学号 13276 ,查询到姓名 \(name!)") + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(key: 16750) + print("\n删除 16750 后,哈希表为\nKey -> Value") + map.print() + } +} diff --git a/codes/swift/chapter_hashing/simple_hash.swift b/codes/swift/chapter_hashing/simple_hash.swift new file mode 100644 index 0000000000..bafc6aac7e --- /dev/null +++ b/codes/swift/chapter_hashing/simple_hash.swift @@ -0,0 +1,73 @@ +/** + * File: simple_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 加法哈希 */ +func addHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* 乘法哈希 */ +func mulHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (31 * hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* 异或哈希 */ +func xorHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash ^= Int(scalar.value) + } + } + return hash & MODULUS +} + +/* 旋转哈希 */ +func rotHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS + } + } + return hash +} + +@main +enum SimpleHash { + /* Driver Code */ + static func main() { + let key = "Hello 算法" + + var hash = addHash(key: key) + print("加法哈希值为 \(hash)") + + hash = mulHash(key: key) + print("乘法哈希值为 \(hash)") + + hash = xorHash(key: key) + print("异或哈希值为 \(hash)") + + hash = rotHash(key: key) + print("旋转哈希值为 \(hash)") + } +} diff --git a/codes/swift/chapter_heap/heap.swift b/codes/swift/chapter_heap/heap.swift new file mode 100644 index 0000000000..fc85880a0b --- /dev/null +++ b/codes/swift/chapter_heap/heap.swift @@ -0,0 +1,62 @@ +/** + * File: heap.swift + * Created Time: 2024-03-17 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +func testPush(heap: inout Heap, val: Int) { + heap.insert(val) + print("\n元素 \(val) 入堆后\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +func testPop(heap: inout Heap) { + let val = heap.removeMax() + print("\n堆顶元素 \(val) 出堆后\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +@main +enum _Heap { + /* Driver Code */ + static func main() { + /* 初始化堆 */ + // Swift 的 Heap 类型同时支持最大堆和最小堆 + var heap = Heap() + + /* 元素入堆 */ + testPush(heap: &heap, val: 1) + testPush(heap: &heap, val: 3) + testPush(heap: &heap, val: 2) + testPush(heap: &heap, val: 5) + testPush(heap: &heap, val: 4) + + /* 获取堆顶元素 */ + let peek = heap.max() + print("\n堆顶元素为 \(peek!)\n") + + /* 堆顶元素出堆 */ + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + + /* 获取堆大小 */ + let size = heap.count + print("\n堆元素数量为 \(size)\n") + + /* 判断堆是否为空 */ + let isEmpty = heap.isEmpty + print("\n堆是否为空 \(isEmpty)\n") + + /* 输入列表并建堆 */ + // 时间复杂度为 O(n) ,而非 O(nlogn) + let heap2 = Heap([1, 3, 2, 5, 4]) + print("\n输入列表并建立堆后") + PrintUtil.printHeap(queue: heap2.unordered) + } +} diff --git a/codes/swift/chapter_heap/my_heap.swift b/codes/swift/chapter_heap/my_heap.swift new file mode 100644 index 0000000000..cc388d47b7 --- /dev/null +++ b/codes/swift/chapter_heap/my_heap.swift @@ -0,0 +1,163 @@ +/** + * File: my_heap.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 大顶堆 */ +class MaxHeap { + private var maxHeap: [Int] + + /* 构造方法,根据输入列表建堆 */ + init(nums: [Int]) { + // 将列表元素原封不动添加进堆 + maxHeap = nums + // 堆化除叶节点以外的其他所有节点 + for i in (0 ... parent(i: size() - 1)).reversed() { + siftDown(i: i) + } + } + + /* 获取左子节点的索引 */ + private func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 获取右子节点的索引 */ + private func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 获取父节点的索引 */ + private func parent(i: Int) -> Int { + (i - 1) / 2 // 向下整除 + } + + /* 交换元素 */ + private func swap(i: Int, j: Int) { + maxHeap.swapAt(i, j) + } + + /* 获取堆大小 */ + func size() -> Int { + maxHeap.count + } + + /* 判断堆是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 访问堆顶元素 */ + func peek() -> Int { + maxHeap[0] + } + + /* 元素入堆 */ + func push(val: Int) { + // 添加节点 + maxHeap.append(val) + // 从底至顶堆化 + siftUp(i: size() - 1) + } + + /* 从节点 i 开始,从底至顶堆化 */ + private func siftUp(i: Int) { + var i = i + while true { + // 获取节点 i 的父节点 + let p = parent(i: i) + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 || maxHeap[i] <= maxHeap[p] { + break + } + // 交换两节点 + swap(i: i, j: p) + // 循环向上堆化 + i = p + } + } + + /* 元素出堆 */ + func pop() -> Int { + // 判空处理 + if isEmpty() { + fatalError("堆为空") + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(i: 0, j: size() - 1) + // 删除节点 + let val = maxHeap.remove(at: size() - 1) + // 从顶至底堆化 + siftDown(i: 0) + // 返回堆顶元素 + return val + } + + /* 从节点 i 开始,从顶至底堆化 */ + private func siftDown(i: Int) { + var i = i + while true { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let l = left(i: i) + let r = right(i: i) + var ma = i + if l < size(), maxHeap[l] > maxHeap[ma] { + ma = l + } + if r < size(), maxHeap[r] > maxHeap[ma] { + ma = r + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i { + break + } + // 交换两节点 + swap(i: i, j: ma) + // 循环向下堆化 + i = ma + } + } + + /* 打印堆(二叉树) */ + func print() { + let queue = maxHeap + PrintUtil.printHeap(queue: queue) + } +} + +@main +enum MyHeap { + /* Driver Code */ + static func main() { + /* 初始化大顶堆 */ + let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\n输入列表并建堆后") + maxHeap.print() + + /* 获取堆顶元素 */ + var peek = maxHeap.peek() + print("\n堆顶元素为 \(peek)") + + /* 元素入堆 */ + let val = 7 + maxHeap.push(val: val) + print("\n元素 \(val) 入堆后") + maxHeap.print() + + /* 堆顶元素出堆 */ + peek = maxHeap.pop() + print("\n堆顶元素 \(peek) 出堆后") + maxHeap.print() + + /* 获取堆大小 */ + let size = maxHeap.size() + print("\n堆元素数量为 \(size)") + + /* 判断堆是否为空 */ + let isEmpty = maxHeap.isEmpty() + print("\n堆是否为空 \(isEmpty)") + } +} diff --git a/codes/swift/chapter_heap/top_k.swift b/codes/swift/chapter_heap/top_k.swift new file mode 100644 index 0000000000..bd13e7169a --- /dev/null +++ b/codes/swift/chapter_heap/top_k.swift @@ -0,0 +1,36 @@ +/** + * File: top_k.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +/* 基于堆查找数组中最大的 k 个元素 */ +func topKHeap(nums: [Int], k: Int) -> [Int] { + // 初始化一个小顶堆,并将前 k 个元素建堆 + var heap = Heap(nums.prefix(k)) + // 从第 k+1 个元素开始,保持堆的长度为 k + for i in nums.indices.dropFirst(k) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > heap.min()! { + _ = heap.removeMin() + heap.insert(nums[i]) + } + } + return heap.unordered +} + +@main +enum TopK { + /* Driver Code */ + static func main() { + let nums = [1, 7, 6, 3, 2] + let k = 3 + + let res = topKHeap(nums: nums, k: k) + print("最大的 \(k) 个元素为") + PrintUtil.printHeap(queue: res) + } +} diff --git a/codes/swift/chapter_searching/binary_search.swift b/codes/swift/chapter_searching/binary_search.swift new file mode 100644 index 0000000000..21d5e7fc21 --- /dev/null +++ b/codes/swift/chapter_searching/binary_search.swift @@ -0,0 +1,62 @@ +/** + * File: binary_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分查找(双闭区间) */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + var i = nums.startIndex + var j = nums.endIndex - 1 + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while i <= j { + let 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 +} + +/* 二分查找(左闭右开区间) */ +func binarySearchLCRO(nums: [Int], target: Int) -> Int { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + var i = nums.startIndex + var j = nums.endIndex + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while i < j { + let 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 +} + +@main +enum BinarySearch { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + /* 二分查找(双闭区间) */ + var index = binarySearch(nums: nums, target: target) + print("目标元素 6 的索引 = \(index)") + + /* 二分查找(左闭右开区间) */ + index = binarySearchLCRO(nums: nums, target: target) + print("目标元素 6 的索引 = \(index)") + } +} diff --git a/codes/swift/chapter_searching/binary_search_edge.swift b/codes/swift/chapter_searching/binary_search_edge.swift new file mode 100644 index 0000000000..0bc50dfed8 --- /dev/null +++ b/codes/swift/chapter_searching/binary_search_edge.swift @@ -0,0 +1,51 @@ +/** + * File: binary_search_edge.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import binary_search_insertion_target + +/* 二分查找最左一个 target */ +func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { + // 等价于查找 target 的插入点 + let i = binarySearchInsertion(nums: nums, target: target) + // 未找到 target ,返回 -1 + if i == nums.endIndex || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分查找最右一个 target */ +func binarySearchRightEdge(nums: [Int], target: Int) -> Int { + // 转化为查找最左一个 target + 1 + let i = binarySearchInsertion(nums: nums, target: target + 1) + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + let j = i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j +} + +@main +enum BinarySearchEdge { + /* Driver Code */ + static func main() { + // 包含重复元素的数组 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n数组 nums = \(nums)") + + // 二分查找左边界和右边界 + for target in [6, 7] { + var index = binarySearchLeftEdge(nums: nums, target: target) + print("最左一个元素 \(target) 的索引为 \(index)") + index = binarySearchRightEdge(nums: nums, target: target) + print("最右一个元素 \(target) 的索引为 \(index)") + } + } +} diff --git a/codes/swift/chapter_searching/binary_search_insertion.swift b/codes/swift/chapter_searching/binary_search_insertion.swift new file mode 100644 index 0000000000..ced9f1d879 --- /dev/null +++ b/codes/swift/chapter_searching/binary_search_insertion.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分查找插入点(无重复元素) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // 初始化双闭区间 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let 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 +} + +/* 二分查找插入点(存在重复元素) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // 初始化双闭区间 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let 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 +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // 无重复元素的数组 + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\n数组 nums = \(nums)") + // 二分查找插入点 + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("元素 \(target) 的插入点的索引为 \(index)") + } + + // 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n数组 nums = \(nums)") + // 二分查找插入点 + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("元素 \(target) 的插入点的索引为 \(index)") + } + } +} + +#endif diff --git a/codes/swift/chapter_searching/binary_search_insertion_target.swift b/codes/swift/chapter_searching/binary_search_insertion_target.swift new file mode 120000 index 0000000000..3f1a7b7611 --- /dev/null +++ b/codes/swift/chapter_searching/binary_search_insertion_target.swift @@ -0,0 +1 @@ +binary_search_insertion.swift \ No newline at end of file diff --git a/codes/swift/chapter_searching/hashing_search.swift b/codes/swift/chapter_searching/hashing_search.swift new file mode 100644 index 0000000000..a8be99daec --- /dev/null +++ b/codes/swift/chapter_searching/hashing_search.swift @@ -0,0 +1,50 @@ +/** + * File: hashing_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 哈希查找(数组) */ +func hashingSearchArray(map: [Int: Int], target: Int) -> Int { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 -1 + return map[target, default: -1] +} + +/* 哈希查找(链表) */ +func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { + // 哈希表的 key: 目标节点值,value: 节点对象 + // 若哈希表中无此 key ,返回 null + return map[target] +} + +@main +enum HashingSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* 哈希查找(数组) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + // 初始化哈希表 + var map: [Int: Int] = [:] + for i in nums.indices { + map[nums[i]] = i // key: 元素,value: 索引 + } + let index = hashingSearchArray(map: map, target: target) + print("目标元素 3 的索引 = \(index)") + + /* 哈希查找(链表) */ + var head = ListNode.arrToLinkedList(arr: nums) + // 初始化哈希表 + var map1: [Int: ListNode] = [:] + while head != nil { + map1[head!.val] = head! // key: 节点值,value: 节点 + head = head?.next + } + let node = hashingSearchLinkedList(map: map1, target: target) + print("目标节点值 3 的对应节点对象为 \(node!)") + } +} diff --git a/codes/swift/chapter_searching/linear_search.swift b/codes/swift/chapter_searching/linear_search.swift new file mode 100644 index 0000000000..1544f77442 --- /dev/null +++ b/codes/swift/chapter_searching/linear_search.swift @@ -0,0 +1,53 @@ +/** + * File: linear_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 线性查找(数组) */ +func linearSearchArray(nums: [Int], target: Int) -> Int { + // 遍历数组 + for i in nums.indices { + // 找到目标元素,返回其索引 + if nums[i] == target { + return i + } + } + // 未找到目标元素,返回 -1 + return -1 +} + +/* 线性查找(链表) */ +func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { + var head = head + // 遍历链表 + while head != nil { + // 找到目标节点,返回之 + if head?.val == target { + return head + } + head = head?.next + } + // 未找到目标节点,返回 null + return nil +} + +@main +enum LinearSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* 在数组中执行线性查找 */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + let index = linearSearchArray(nums: nums, target: target) + print("目标元素 3 的索引 = \(index)") + + /* 在链表中执行线性查找 */ + let head = ListNode.arrToLinkedList(arr: nums) + let node = linearSearchLinkedList(head: head, target: target) + print("目标节点值 3 的对应节点对象为 \(node!)") + } +} diff --git a/codes/swift/chapter_searching/two_sum.swift b/codes/swift/chapter_searching/two_sum.swift new file mode 100644 index 0000000000..f0508854a2 --- /dev/null +++ b/codes/swift/chapter_searching/two_sum.swift @@ -0,0 +1,49 @@ +/** + * File: two_sum.swift + * Created Time: 2023-01-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 方法一:暴力枚举 */ +func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { + // 两层循环,时间复杂度为 O(n^2) + for i in nums.indices.dropLast() { + for j in nums.indices.dropFirst(i + 1) { + if nums[i] + nums[j] == target { + return [i, j] + } + } + } + return [0] +} + +/* 方法二:辅助哈希表 */ +func twoSumHashTable(nums: [Int], target: Int) -> [Int] { + // 辅助哈希表,空间复杂度为 O(n) + var dic: [Int: Int] = [:] + // 单层循环,时间复杂度为 O(n) + for i in nums.indices { + if let j = dic[target - nums[i]] { + return [j, i] + } + dic[nums[i]] = i + } + return [0] +} + +@main +enum LeetcodeTwoSum { + /* Driver Code */ + static func main() { + // ======= Test Case ======= + let nums = [2, 7, 11, 15] + let target = 13 + // ====== Driver Code ====== + // 方法一 + var res = twoSumBruteForce(nums: nums, target: target) + print("方法一 res = \(res)") + // 方法二 + res = twoSumHashTable(nums: nums, target: target) + print("方法二 res = \(res)") + } +} diff --git a/codes/swift/chapter_sorting/bubble_sort.swift b/codes/swift/chapter_sorting/bubble_sort.swift new file mode 100644 index 0000000000..8069cf67b0 --- /dev/null +++ b/codes/swift/chapter_sorting/bubble_sort.swift @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 冒泡排序 */ +func bubbleSort(nums: inout [Int]) { + // 外循环:未排序区间为 [0, i] + for i in nums.indices.dropFirst().reversed() { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交换 nums[j] 与 nums[j + 1] + nums.swapAt(j, j + 1) + } + } + } +} + +/* 冒泡排序(标志优化)*/ +func bubbleSortWithFlag(nums: inout [Int]) { + // 外循环:未排序区间为 [0, i] + for i in nums.indices.dropFirst().reversed() { + var flag = false // 初始化标志位 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交换 nums[j] 与 nums[j + 1] + nums.swapAt(j, j + 1) + flag = true // 记录交换元素 + } + } + if !flag { // 此轮“冒泡”未交换任何元素,直接跳出 + break + } + } +} + +@main +enum BubbleSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + bubbleSort(nums: &nums) + print("冒泡排序完成后 nums = \(nums)") + + var nums1 = [4, 1, 3, 1, 5, 2] + bubbleSortWithFlag(nums: &nums1) + print("冒泡排序完成后 nums1 = \(nums1)") + } +} diff --git a/codes/swift/chapter_sorting/bucket_sort.swift b/codes/swift/chapter_sorting/bucket_sort.swift new file mode 100644 index 0000000000..2be527fb7c --- /dev/null +++ b/codes/swift/chapter_sorting/bucket_sort.swift @@ -0,0 +1,43 @@ +/** + * File: bucket_sort.swift + * Created Time: 2023-03-27 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 桶排序 */ +func bucketSort(nums: inout [Double]) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + let k = nums.count / 2 + var buckets = (0 ..< k).map { _ in [Double]() } + // 1. 将数组元素分配到各个桶中 + for num in nums { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + let i = Int(num * Double(k)) + // 将 num 添加进桶 i + buckets[i].append(num) + } + // 2. 对各个桶执行排序 + for i in buckets.indices { + // 使用内置排序函数,也可以替换成其他排序算法 + buckets[i].sort() + } + // 3. 遍历桶合并结果 + var i = nums.startIndex + for bucket in buckets { + for num in bucket { + nums[i] = num + i += 1 + } + } +} + +@main +enum BucketSort { + /* Driver Code */ + static func main() { + // 设输入数据为浮点数,范围为 [0, 1) + var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucketSort(nums: &nums) + print("桶排序完成后 nums = \(nums)") + } +} diff --git a/codes/swift/chapter_sorting/counting_sort.swift b/codes/swift/chapter_sorting/counting_sort.swift new file mode 100644 index 0000000000..f702848a20 --- /dev/null +++ b/codes/swift/chapter_sorting/counting_sort.swift @@ -0,0 +1,70 @@ +/** + * File: counting_sort.swift + * Created Time: 2023-03-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +func countingSortNaive(nums: inout [Int]) { + // 1. 统计数组最大元素 m + let m = nums.max()! + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 遍历 counter ,将各元素填入原数组 nums + var i = 0 + for num in 0 ..< m + 1 { + for _ in 0 ..< counter[num] { + nums[i] = num + i += 1 + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +func countingSort(nums: inout [Int]) { + // 1. 统计数组最大元素 m + let m = nums.max()! + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i in 0 ..< m { + counter[i + 1] += counter[i] + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let num = nums[i] + res[counter[num] - 1] = num // 将 num 放置到对应索引处 + counter[num] -= 1 // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for i in nums.indices { + nums[i] = res[i] + } +} + +@main +enum CountingSort { + /* Driver Code */ + static func main() { + var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSortNaive(nums: &nums) + print("计数排序(无法排序对象)完成后 nums = \(nums)") + + var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSort(nums: &nums1) + print("计数排序完成后 nums1 = \(nums1)") + } +} diff --git a/codes/swift/chapter_sorting/heap_sort.swift b/codes/swift/chapter_sorting/heap_sort.swift new file mode 100644 index 0000000000..ca1d726657 --- /dev/null +++ b/codes/swift/chapter_sorting/heap_sort.swift @@ -0,0 +1,55 @@ +/** + * File: heap_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +func siftDown(nums: inout [Int], n: Int, i: Int) { + var i = i + while true { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let l = 2 * i + 1 + let r = 2 * i + 2 + var 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 + } + // 交换两节点 + nums.swapAt(i, ma) + // 循环向下堆化 + i = ma + } +} + +/* 堆排序 */ +func heapSort(nums: inout [Int]) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { + siftDown(nums: &nums, n: nums.count, i: i) + } + // 从堆中提取最大元素,循环 n-1 轮 + for i in nums.indices.dropFirst().reversed() { + // 交换根节点与最右叶节点(交换首元素与尾元素) + nums.swapAt(0, i) + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums: &nums, n: i, i: 0) + } +} + +@main +enum HeapSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + heapSort(nums: &nums) + print("堆排序完成后 nums = \(nums)") + } +} diff --git a/codes/swift/chapter_sorting/insertion_sort.swift b/codes/swift/chapter_sorting/insertion_sort.swift new file mode 100644 index 0000000000..4bc8441e06 --- /dev/null +++ b/codes/swift/chapter_sorting/insertion_sort.swift @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 插入排序 */ +func insertionSort(nums: inout [Int]) { + // 外循环:已排序区间为 [0, i-1] + for i in nums.indices.dropFirst() { + let base = nums[i] + var j = i - 1 + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while j >= 0, nums[j] > base { + nums[j + 1] = nums[j] // 将 nums[j] 向右移动一位 + j -= 1 + } + nums[j + 1] = base // 将 base 赋值到正确位置 + } +} + +@main +enum InsertionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + insertionSort(nums: &nums) + print("插入排序完成后 nums = \(nums)") + } +} diff --git a/codes/swift/chapter_sorting/merge_sort.swift b/codes/swift/chapter_sorting/merge_sort.swift new file mode 100644 index 0000000000..abc84fb1ec --- /dev/null +++ b/codes/swift/chapter_sorting/merge_sort.swift @@ -0,0 +1,65 @@ +/** + * File: merge_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 合并左子数组和右子数组 */ +func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + var tmp = Array(repeating: 0, count: right - left + 1) + // 初始化左子数组和右子数组的起始索引 + var i = left, j = mid + 1, k = 0 + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while i <= mid, j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i += 1 + } else { + tmp[k] = nums[j] + j += 1 + } + k += 1 + } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while i <= mid { + tmp[k] = nums[i] + i += 1 + k += 1 + } + while j <= right { + tmp[k] = nums[j] + j += 1 + k += 1 + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for k in tmp.indices { + nums[left + k] = tmp[k] + } +} + +/* 归并排序 */ +func mergeSort(nums: inout [Int], left: Int, right: Int) { + // 终止条件 + if left >= right { // 当子数组长度为 1 时终止递归 + return + } + // 划分阶段 + let mid = left + (right - left) / 2 // 计算中点 + mergeSort(nums: &nums, left: left, right: mid) // 递归左子数组 + mergeSort(nums: &nums, left: mid + 1, right: right) // 递归右子数组 + // 合并阶段 + merge(nums: &nums, left: left, mid: mid, right: right) +} + +@main +enum MergeSort { + /* Driver Code */ + static func main() { + /* 归并排序 */ + var nums = [7, 3, 2, 6, 0, 1, 5, 4] + mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("归并排序完成后 nums = \(nums)") + } +} diff --git a/codes/swift/chapter_sorting/quick_sort.swift b/codes/swift/chapter_sorting/quick_sort.swift new file mode 100644 index 0000000000..d4bcf9393e --- /dev/null +++ b/codes/swift/chapter_sorting/quick_sort.swift @@ -0,0 +1,114 @@ +/** + * File: quick_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 快速排序类 */ +/* 哨兵划分 */ +func partition(nums: inout [Int], left: Int, right: Int) -> Int { + // 以 nums[left] 为基准数 + var i = left + var j = right + while i < j { + while i < j, nums[j] >= nums[left] { + j -= 1 // 从右向左找首个小于基准数的元素 + } + while i < j, nums[i] <= nums[left] { + i += 1 // 从左向右找首个大于基准数的元素 + } + nums.swapAt(i, j) // 交换这两个元素 + } + nums.swapAt(i, left) // 将基准数交换至两子数组的分界线 + return i // 返回基准数的索引 +} + +/* 快速排序 */ +func quickSort(nums: inout [Int], left: Int, right: Int) { + // 子数组长度为 1 时终止递归 + if left >= right { + return + } + // 哨兵划分 + let pivot = partition(nums: &nums, left: left, right: right) + // 递归左子数组、右子数组 + quickSort(nums: &nums, left: left, right: pivot - 1) + quickSort(nums: &nums, left: pivot + 1, right: right) +} + +/* 快速排序类(中位基准数优化) */ +/* 选取三个候选元素的中位数 */ +func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { + let l = nums[left] + let m = nums[mid] + let 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 +} + +/* 哨兵划分(三数取中值) */ +func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { + // 选取三个候选元素的中位数 + let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) + // 将中位数交换至数组最左端 + nums.swapAt(left, med) + return partition(nums: &nums, left: left, right: right) +} + +/* 快速排序(中位基准数优化) */ +func quickSortMedian(nums: inout [Int], left: Int, right: Int) { + // 子数组长度为 1 时终止递归 + if left >= right { + return + } + // 哨兵划分 + let pivot = partitionMedian(nums: &nums, left: left, right: right) + // 递归左子数组、右子数组 + quickSortMedian(nums: &nums, left: left, right: pivot - 1) + quickSortMedian(nums: &nums, left: pivot + 1, right: right) +} + +/* 快速排序(尾递归优化) */ +func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { + var left = left + var right = right + // 子数组长度为 1 时终止 + while left < right { + // 哨兵划分操作 + let pivot = partition(nums: &nums, left: left, right: right) + // 对两个子数组中较短的那个执行快速排序 + if (pivot - left) < (right - pivot) { + quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 递归排序左子数组 + left = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] + } else { + quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 递归排序右子数组 + right = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] + } + } +} + +@main +enum QuickSort { + /* Driver Code */ + static func main() { + /* 快速排序 */ + var nums = [2, 4, 1, 0, 3, 5] + quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("快速排序完成后 nums = \(nums)") + + /* 快速排序(中位基准数优化) */ + var nums1 = [2, 4, 1, 0, 3, 5] + quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) + print("快速排序(中位基准数优化)完成后 nums1 = \(nums1)") + + /* 快速排序(尾递归优化) */ + var nums2 = [2, 4, 1, 0, 3, 5] + quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) + print("快速排序(尾递归优化)完成后 nums2 = \(nums2)") + } +} diff --git a/codes/swift/chapter_sorting/radix_sort.swift b/codes/swift/chapter_sorting/radix_sort.swift new file mode 100644 index 0000000000..2ef0fd8f8e --- /dev/null +++ b/codes/swift/chapter_sorting/radix_sort.swift @@ -0,0 +1,79 @@ +/** + * File: radix_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +func digit(num: Int, exp: Int) -> Int { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + (num / exp) % 10 +} + +/* 计数排序(根据 nums 第 k 位排序) */ +func countingSortDigit(nums: inout [Int], exp: Int) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + var counter = Array(repeating: 0, count: 10) + // 统计 0~9 各数字的出现次数 + for i in nums.indices { + let d = digit(num: nums[i], exp: exp) // 获取 nums[i] 第 k 位,记为 d + counter[d] += 1 // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for i in 1 ..< 10 { + counter[i] += counter[i - 1] + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let d = digit(num: nums[i], exp: exp) + let j = counter[d] - 1 // 获取 d 在数组中的索引 j + res[j] = nums[i] // 将当前元素填入索引 j + counter[d] -= 1 // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for i in nums.indices { + nums[i] = res[i] + } +} + +/* 基数排序 */ +func radixSort(nums: inout [Int]) { + // 获取数组的最大元素,用于判断最大位数 + var m = Int.min + for num in nums { + if num > m { + m = num + } + } + // 按照从低位到高位的顺序遍历 + for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums: &nums, exp: exp) + } +} + +@main +enum RadixSort { + /* Driver Code */ + static func main() { + // 基数排序 + var nums = [ + 10_546_151, + 35_663_510, + 42_865_989, + 34_862_445, + 81_883_077, + 88_906_420, + 72_429_244, + 30_524_779, + 82_060_337, + 63_832_996, + ] + radixSort(nums: &nums) + print("基数排序完成后 nums = \(nums)") + } +} diff --git a/codes/swift/chapter_sorting/selection_sort.swift b/codes/swift/chapter_sorting/selection_sort.swift new file mode 100644 index 0000000000..42f3c8cb8b --- /dev/null +++ b/codes/swift/chapter_sorting/selection_sort.swift @@ -0,0 +1,31 @@ +/** + * File: selection_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 选择排序 */ +func selectionSort(nums: inout [Int]) { + // 外循环:未排序区间为 [i, n-1] + for i in nums.indices.dropLast() { + // 内循环:找到未排序区间内的最小元素 + var k = i + for j in nums.indices.dropFirst(i + 1) { + if nums[j] < nums[k] { + k = j // 记录最小元素的索引 + } + } + // 将该最小元素与未排序区间的首个元素交换 + nums.swapAt(i, k) + } +} + +@main +enum SelectionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + selectionSort(nums: &nums) + print("选择排序完成后 nums = \(nums)") + } +} diff --git a/codes/swift/chapter_stack_and_queue/array_deque.swift b/codes/swift/chapter_stack_and_queue/array_deque.swift new file mode 100644 index 0000000000..549395bfef --- /dev/null +++ b/codes/swift/chapter_stack_and_queue/array_deque.swift @@ -0,0 +1,148 @@ +/** + * File: array_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 基于环形数组实现的双向队列 */ +class ArrayDeque { + private var nums: [Int] // 用于存储双向队列元素的数组 + private var front: Int // 队首指针,指向队首元素 + private var _size: Int // 双向队列长度 + + /* 构造方法 */ + init(capacity: Int) { + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* 获取双向队列的容量 */ + func capacity() -> Int { + nums.count + } + + /* 获取双向队列的长度 */ + func size() -> Int { + _size + } + + /* 判断双向队列是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 计算环形数组索引 */ + private func index(i: Int) -> Int { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + (i + capacity()) % capacity() + } + + /* 队首入队 */ + func pushFirst(num: Int) { + if size() == capacity() { + print("双向队列已满") + return + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + front = index(i: front - 1) + // 将 num 添加至队首 + nums[front] = num + _size += 1 + } + + /* 队尾入队 */ + func pushLast(num: Int) { + if size() == capacity() { + print("双向队列已满") + return + } + // 计算队尾指针,指向队尾索引 + 1 + let rear = index(i: front + size()) + // 将 num 添加至队尾 + nums[rear] = num + _size += 1 + } + + /* 队首出队 */ + func popFirst() -> Int { + let num = peekFirst() + // 队首指针向后移动一位 + front = index(i: front + 1) + _size -= 1 + return num + } + + /* 队尾出队 */ + func popLast() -> Int { + let num = peekLast() + _size -= 1 + return num + } + + /* 访问队首元素 */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + return nums[front] + } + + /* 访问队尾元素 */ + func peekLast() -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + // 计算尾元素索引 + let last = index(i: front + size() - 1) + return nums[last] + } + + /* 返回数组用于打印 */ + func toArray() -> [Int] { + // 仅转换有效长度范围内的列表元素 + (front ..< front + size()).map { nums[index(i: $0)] } + } +} + +@main +enum _ArrayDeque { + /* Driver Code */ + static func main() { + /* 初始化双向队列 */ + let deque = ArrayDeque(capacity: 10) + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("双向队列 deque = \(deque.toArray())") + + /* 访问元素 */ + let peekFirst = deque.peekFirst() + print("队首元素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("队尾元素 peekLast = \(peekLast)") + + /* 元素入队 */ + deque.pushLast(num: 4) + print("元素 4 队尾入队后 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("元素 1 队首入队后 deque = \(deque.toArray())") + + /* 元素出队 */ + let popLast = deque.popLast() + print("队尾出队元素 = \(popLast),队尾出队后 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("队首出队元素 = \(popFirst),队首出队后 deque = \(deque.toArray())") + + /* 获取双向队列的长度 */ + let size = deque.size() + print("双向队列长度 size = \(size)") + + /* 判断双向队列是否为空 */ + let isEmpty = deque.isEmpty() + print("双向队列是否为空 = \(isEmpty)") + } +} diff --git a/codes/swift/chapter_stack_and_queue/array_queue.swift b/codes/swift/chapter_stack_and_queue/array_queue.swift index d76dbc9c50..8bfe82e162 100644 --- a/codes/swift/chapter_stack_and_queue/array_queue.swift +++ b/codes/swift/chapter_stack_and_queue/array_queue.swift @@ -7,12 +7,14 @@ /* 基于环形数组实现的队列 */ class ArrayQueue { private var nums: [Int] // 用于存储队列元素的数组 - private var front = 0 // 头指针,指向队首 - private var rear = 0 // 尾指针,指向队尾 + 1 + private var front: Int // 队首指针,指向队首元素 + private var _size: Int // 队列长度 init(capacity: Int) { // 初始化数组 nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 } /* 获取队列的容量 */ @@ -22,34 +24,35 @@ class ArrayQueue { /* 获取队列的长度 */ func size() -> Int { - let capacity = capacity() - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity + rear - front) % capacity + _size } /* 判断队列是否为空 */ func isEmpty() -> Bool { - rear - front == 0 + size() == 0 } /* 入队 */ - func offer(num: Int) { + func push(num: Int) { if size() == capacity() { print("队列已满") return } - // 尾结点后添加 num + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + let rear = (front + size()) % capacity() + // 将 num 添加至队尾 nums[rear] = num - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity() + _size += 1 } /* 出队 */ @discardableResult - func poll() -> Int { + func pop() -> Int { let num = peek() - // 队头指针向后移动一位,若越过尾部则返回到数组头部 + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 front = (front + 1) % capacity() + _size -= 1 return num } @@ -63,14 +66,8 @@ class ArrayQueue { /* 返回数组 */ func toArray() -> [Int] { - let size = size() - let capacity = capacity() // 仅转换有效长度范围内的列表元素 - var res = Array(repeating: 0, count: size) - for (i, j) in sequence(first: (0, front), next: { $0 < size - 1 ? ($0 + 1, $1 + 1) : nil }) { - res[i] = nums[j % capacity] - } - return res + (front ..< front + size()).map { nums[$0 % capacity()] } } } @@ -83,11 +80,11 @@ enum _ArrayQueue { let queue = ArrayQueue(capacity: capacity) /* 元素入队 */ - queue.offer(num: 1) - queue.offer(num: 3) - queue.offer(num: 2) - queue.offer(num: 5) - queue.offer(num: 4) + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) print("队列 queue = \(queue.toArray())") /* 访问队首元素 */ @@ -95,8 +92,8 @@ enum _ArrayQueue { print("队首元素 peek = \(peek)") /* 元素出队 */ - let poll = queue.poll() - print("出队元素 poll = \(poll),出队后 queue = \(queue.toArray())") + let pop = queue.pop() + print("出队元素 pop = \(pop),出队后 queue = \(queue.toArray())") /* 获取队列的长度 */ let size = queue.size() @@ -108,8 +105,8 @@ enum _ArrayQueue { /* 测试环形数组 */ for i in 0 ..< 10 { - queue.offer(num: i) - queue.poll() + queue.push(num: i) + queue.pop() print("第 \(i) 轮入队 + 出队后 queue = \(queue.toArray())") } } diff --git a/codes/swift/chapter_stack_and_queue/deque.swift b/codes/swift/chapter_stack_and_queue/deque.swift new file mode 100644 index 0000000000..8245caf555 --- /dev/null +++ b/codes/swift/chapter_stack_and_queue/deque.swift @@ -0,0 +1,44 @@ +/** + * File: deque.swift + * Created Time: 2023-01-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Deque { + /* Driver Code */ + static func main() { + /* 初始化双向队列 */ + // Swift 没有内置的双向队列类,可以把 Array 当作双向队列来使用 + var deque: [Int] = [] + + /* 元素入队 */ + deque.append(2) + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) + deque.insert(1, at: 0) + print("双向队列 deque = \(deque)") + + /* 访问元素 */ + let peekFirst = deque.first! + print("队首元素 peekFirst = \(peekFirst)") + let peekLast = deque.last! + print("队尾元素 peekLast = \(peekLast)") + + /* 元素出队 */ + // 使用 Array 模拟时 popFirst 的复杂度为 O(n) + let popFirst = deque.removeFirst() + print("队首出队元素 popFirst = \(popFirst),队首出队后 deque = \(deque)") + let popLast = deque.removeLast() + print("队尾出队元素 popLast = \(popLast),队尾出队后 deque = \(deque)") + + /* 获取双向队列的长度 */ + let size = deque.count + print("双向队列长度 size = \(size)") + + /* 判断双向队列是否为空 */ + let isEmpty = deque.isEmpty + print("双向队列是否为空 = \(isEmpty)") + } +} diff --git a/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift b/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift new file mode 100644 index 0000000000..d5318294eb --- /dev/null +++ b/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift @@ -0,0 +1,180 @@ +/** + * File: linkedlist_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 双向链表节点 */ +class ListNode { + var val: Int // 节点值 + var next: ListNode? // 后继节点引用 + weak var prev: ListNode? // 前驱节点引用 + + init(val: Int) { + self.val = val + } +} + +/* 基于双向链表实现的双向队列 */ +class LinkedListDeque { + private var front: ListNode? // 头节点 front + private var rear: ListNode? // 尾节点 rear + private var _size: Int // 双向队列的长度 + + init() { + _size = 0 + } + + /* 获取双向队列的长度 */ + func size() -> Int { + _size + } + + /* 判断双向队列是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入队操作 */ + private func push(num: Int, isFront: Bool) { + let node = ListNode(val: num) + // 若链表为空,则令 front 和 rear 都指向 node + if isEmpty() { + front = node + rear = node + } + // 队首入队操作 + else if isFront { + // 将 node 添加至链表头部 + front?.prev = node + node.next = front + front = node // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾节点 + } + _size += 1 // 更新队列长度 + } + + /* 队首入队 */ + func pushFirst(num: Int) { + push(num: num, isFront: true) + } + + /* 队尾入队 */ + func pushLast(num: Int) { + push(num: num, isFront: false) + } + + /* 出队操作 */ + private func pop(isFront: Bool) -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + let val: Int + // 队首出队操作 + if isFront { + val = front!.val // 暂存头节点值 + // 删除头节点 + let fNext = front?.next + if fNext != nil { + fNext?.prev = nil + front?.next = nil + } + front = fNext // 更新头节点 + } + // 队尾出队操作 + else { + val = rear!.val // 暂存尾节点值 + // 删除尾节点 + let rPrev = rear?.prev + if rPrev != nil { + rPrev?.next = nil + rear?.prev = nil + } + rear = rPrev // 更新尾节点 + } + _size -= 1 // 更新队列长度 + return val + } + + /* 队首出队 */ + func popFirst() -> Int { + pop(isFront: true) + } + + /* 队尾出队 */ + func popLast() -> Int { + pop(isFront: false) + } + + /* 访问队首元素 */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + return front!.val + } + + /* 访问队尾元素 */ + func peekLast() -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + return rear!.val + } + + /* 返回数组用于打印 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListDeque { + /* Driver Code */ + static func main() { + /* 初始化双向队列 */ + let deque = LinkedListDeque() + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("双向队列 deque = \(deque.toArray())") + + /* 访问元素 */ + let peekFirst = deque.peekFirst() + print("队首元素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("队尾元素 peekLast = \(peekLast)") + + /* 元素入队 */ + deque.pushLast(num: 4) + print("元素 4 队尾入队后 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("元素 1 队首入队后 deque = \(deque.toArray())") + + /* 元素出队 */ + let popLast = deque.popLast() + print("队尾出队元素 = \(popLast),队尾出队后 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("队首出队元素 = \(popFirst),队首出队后 deque = \(deque.toArray())") + + /* 获取双向队列的长度 */ + let size = deque.size() + print("双向队列长度 size = \(size)") + + /* 判断双向队列是否为空 */ + let isEmpty = deque.isEmpty() + print("双向队列是否为空 = \(isEmpty)") + } +} diff --git a/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift b/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift index 1d671742ba..8a7212d9e6 100644 --- a/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift +++ b/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift @@ -8,11 +8,13 @@ import utils /* 基于链表实现的队列 */ class LinkedListQueue { - private var front: ListNode? // 头结点 - private var rear: ListNode? // 尾结点 - private var _size = 0 + private var front: ListNode? // 头节点 + private var rear: ListNode? // 尾节点 + private var _size: Int - init() {} + init() { + _size = 0 + } /* 获取队列的长度 */ func size() -> Int { @@ -25,15 +27,15 @@ class LinkedListQueue { } /* 入队 */ - func offer(num: Int) { - // 尾结点后添加 num + func push(num: Int) { + // 在尾节点后添加 num let node = ListNode(x: num) - // 如果队列为空,则令头、尾结点都指向该结点 + // 如果队列为空,则令头、尾节点都指向该节点 if front == nil { front = node rear = node } - // 如果队列不为空,则将该结点添加到尾结点后 + // 如果队列不为空,则将该节点添加到尾节点后 else { rear?.next = node rear = node @@ -43,9 +45,9 @@ class LinkedListQueue { /* 出队 */ @discardableResult - func poll() -> Int { + func pop() -> Int { let num = peek() - // 删除头结点 + // 删除头节点 front = front?.next _size -= 1 return num @@ -79,11 +81,11 @@ enum _LinkedListQueue { let queue = LinkedListQueue() /* 元素入队 */ - queue.offer(num: 1) - queue.offer(num: 3) - queue.offer(num: 2) - queue.offer(num: 5) - queue.offer(num: 4) + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) print("队列 queue = \(queue.toArray())") /* 访问队首元素 */ @@ -91,8 +93,8 @@ enum _LinkedListQueue { print("队首元素 peek = \(peek)") /* 元素出队 */ - let poll = queue.poll() - print("出队元素 poll = \(poll),出队后 queue = \(queue.toArray())") + let pop = queue.pop() + print("出队元素 pop = \(pop),出队后 queue = \(queue.toArray())") /* 获取队列的长度 */ let size = queue.size() diff --git a/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift b/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift index 97f7dabccc..24d4866a93 100644 --- a/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift +++ b/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift @@ -8,10 +8,12 @@ import utils /* 基于链表实现的栈 */ class LinkedListStack { - private var _peek: ListNode? // 将头结点作为栈顶 - private var _size = 0 // 栈的长度 + private var _peek: ListNode? // 将头节点作为栈顶 + private var _size: Int // 栈的长度 - init() {} + init() { + _size = 0 + } /* 获取栈的长度 */ func size() -> Int { @@ -51,8 +53,8 @@ class LinkedListStack { /* 将 List 转化为 Array 并返回 */ func toArray() -> [Int] { var node = _peek - var res = Array(repeating: 0, count: _size) - for i in sequence(first: res.count - 1, next: { $0 >= 0 + 1 ? $0 - 1 : nil }) { + var res = Array(repeating: 0, count: size()) + for i in res.indices.reversed() { res[i] = node!.val node = node?.next } diff --git a/codes/swift/chapter_stack_and_queue/queue.swift b/codes/swift/chapter_stack_and_queue/queue.swift index f13af36f53..e7b746e35b 100644 --- a/codes/swift/chapter_stack_and_queue/queue.swift +++ b/codes/swift/chapter_stack_and_queue/queue.swift @@ -25,8 +25,9 @@ enum Queue { print("队首元素 peek = \(peek)") /* 元素出队 */ + // 使用 Array 模拟时 pop 的复杂度为 O(n) let pool = queue.removeFirst() - print("出队元素 poll = \(pool),出队后 queue = \(queue)") + print("出队元素 pop = \(pool),出队后 queue = \(queue)") /* 获取队列的长度 */ let size = queue.count diff --git a/codes/swift/chapter_tree/array_binary_tree.swift b/codes/swift/chapter_tree/array_binary_tree.swift new file mode 100644 index 0000000000..18ebe41a2e --- /dev/null +++ b/codes/swift/chapter_tree/array_binary_tree.swift @@ -0,0 +1,141 @@ +/** + * File: array_binary_tree.swift + * Created Time: 2023-07-23 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree { + private var tree: [Int?] + + /* 构造方法 */ + init(arr: [Int?]) { + tree = arr + } + + /* 列表容量 */ + func size() -> Int { + tree.count + } + + /* 获取索引为 i 节点的值 */ + func val(i: Int) -> Int? { + // 若索引越界,则返回 null ,代表空位 + if i < 0 || i >= size() { + return nil + } + return tree[i] + } + + /* 获取索引为 i 节点的左子节点的索引 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + func parent(i: Int) -> Int { + (i - 1) / 2 + } + + /* 层序遍历 */ + func levelOrder() -> [Int] { + var res: [Int] = [] + // 直接遍历数组 + for i in 0 ..< size() { + if let val = val(i: i) { + res.append(val) + } + } + return res + } + + /* 深度优先遍历 */ + private func dfs(i: Int, order: String, res: inout [Int]) { + // 若为空位,则返回 + guard let val = val(i: i) else { + return + } + // 前序遍历 + if order == "pre" { + res.append(val) + } + dfs(i: left(i: i), order: order, res: &res) + // 中序遍历 + if order == "in" { + res.append(val) + } + dfs(i: right(i: i), order: order, res: &res) + // 后序遍历 + if order == "post" { + res.append(val) + } + } + + /* 前序遍历 */ + func preOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "pre", res: &res) + return res + } + + /* 中序遍历 */ + func inOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "in", res: &res) + return res + } + + /* 后序遍历 */ + func postOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "post", res: &res) + return res + } +} + +@main +enum _ArrayBinaryTree { + /* Driver Code */ + static func main() { + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + + let root = TreeNode.listToTree(arr: arr) + print("\n初始化二叉树\n") + print("二叉树的数组表示:") + print(arr) + print("二叉树的链表表示:") + PrintUtil.printTree(root: root) + + // 数组表示下的二叉树类 + let abt = ArrayBinaryTree(arr: arr) + + // 访问节点 + let i = 1 + let l = abt.left(i: i) + let r = abt.right(i: i) + let p = abt.parent(i: i) + print("\n当前节点的索引为 \(i) ,值为 \(abt.val(i: i) as Any)") + print("其左子节点的索引为 \(l) ,值为 \(abt.val(i: l) as Any)") + print("其右子节点的索引为 \(r) ,值为 \(abt.val(i: r) as Any)") + print("其父节点的索引为 \(p) ,值为 \(abt.val(i: p) as Any)") + + // 遍历树 + var res = abt.levelOrder() + print("\n层序遍历为:\(res)") + res = abt.preOrder() + print("前序遍历为:\(res)") + res = abt.inOrder() + print("中序遍历为:\(res)") + res = abt.postOrder() + print("后序遍历为:\(res)") + } +} diff --git a/codes/swift/chapter_tree/avl_tree.swift b/codes/swift/chapter_tree/avl_tree.swift new file mode 100644 index 0000000000..52da21c139 --- /dev/null +++ b/codes/swift/chapter_tree/avl_tree.swift @@ -0,0 +1,230 @@ +/** + * File: avl_tree.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* AVL 树 */ +class AVLTree { + fileprivate var root: TreeNode? // 根节点 + + init() {} + + /* 获取节点高度 */ + func height(node: TreeNode?) -> Int { + // 空节点高度为 -1 ,叶节点高度为 0 + node?.height ?? -1 + } + + /* 更新节点高度 */ + private func updateHeight(node: TreeNode?) { + // 节点高度等于最高子树高度 + 1 + node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 + } + + /* 获取平衡因子 */ + func balanceFactor(node: TreeNode?) -> Int { + // 空节点平衡因子为 0 + guard let node = node else { return 0 } + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node: node.left) - height(node: node.right) + } + + /* 右旋操作 */ + private func rightRotate(node: TreeNode?) -> TreeNode? { + let child = node?.left + let grandChild = child?.right + // 以 child 为原点,将 node 向右旋转 + child?.right = node + node?.left = grandChild + // 更新节点高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋转后子树的根节点 + return child + } + + /* 左旋操作 */ + private func leftRotate(node: TreeNode?) -> TreeNode? { + let child = node?.right + let grandChild = child?.left + // 以 child 为原点,将 node 向左旋转 + child?.left = node + node?.right = grandChild + // 更新节点高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋转后子树的根节点 + return child + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + private func rotate(node: TreeNode?) -> TreeNode? { + // 获取节点 node 的平衡因子 + let balanceFactor = balanceFactor(node: node) + // 左偏树 + if balanceFactor > 1 { + if self.balanceFactor(node: node?.left) >= 0 { + // 右旋 + return rightRotate(node: node) + } else { + // 先左旋后右旋 + node?.left = leftRotate(node: node?.left) + return rightRotate(node: node) + } + } + // 右偏树 + if balanceFactor < -1 { + if self.balanceFactor(node: node?.right) <= 0 { + // 左旋 + return leftRotate(node: node) + } else { + // 先右旋后左旋 + node?.right = rightRotate(node: node?.right) + return leftRotate(node: node) + } + } + // 平衡树,无须旋转,直接返回 + return node + } + + /* 插入节点 */ + func insert(val: Int) { + root = insertHelper(node: root, val: val) + } + + /* 递归插入节点(辅助方法) */ + private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return TreeNode(x: val) + } + /* 1. 查找插入位置并插入节点 */ + if val < node!.val { + node?.left = insertHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = insertHelper(node: node?.right, val: val) + } else { + return node // 重复节点不插入,直接返回 + } + updateHeight(node: node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node: node) + // 返回子树的根节点 + return node + } + + /* 删除节点 */ + func remove(val: Int) { + root = removeHelper(node: root, val: val) + } + + /* 递归删除节点(辅助方法) */ + private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return nil + } + /* 1. 查找节点并删除 */ + if val < node!.val { + node?.left = removeHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = removeHelper(node: node?.right, val: val) + } else { + if node?.left == nil || node?.right == nil { + let child = node?.left ?? node?.right + // 子节点数量 = 0 ,直接删除 node 并返回 + if child == nil { + return nil + } + // 子节点数量 = 1 ,直接删除 node + else { + node = child + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node?.right + while temp?.left != nil { + temp = temp?.left + } + node?.right = removeHelper(node: node?.right, val: temp!.val) + node?.val = temp!.val + } + } + updateHeight(node: node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node: node) + // 返回子树的根节点 + return node + } + + /* 查找节点 */ + func search(val: Int) -> TreeNode? { + var cur = root + while cur != nil { + // 目标节点在 cur 的右子树中 + if cur!.val < val { + cur = cur?.right + } + // 目标节点在 cur 的左子树中 + else if cur!.val > val { + cur = cur?.left + } + // 找到目标节点,跳出循环 + else { + break + } + } + // 返回目标节点 + return cur + } +} + +@main +enum _AVLTree { + static func testInsert(tree: AVLTree, val: Int) { + tree.insert(val: val) + print("\n插入节点 \(val) 后,AVL 树为") + PrintUtil.printTree(root: tree.root) + } + + static func testRemove(tree: AVLTree, val: Int) { + tree.remove(val: val) + print("\n删除节点 \(val) 后,AVL 树为") + PrintUtil.printTree(root: tree.root) + } + + /* Driver Code */ + static func main() { + /* 初始化空 AVL 树 */ + let avlTree = AVLTree() + + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + testInsert(tree: avlTree, val: 1) + testInsert(tree: avlTree, val: 2) + testInsert(tree: avlTree, val: 3) + testInsert(tree: avlTree, val: 4) + testInsert(tree: avlTree, val: 5) + testInsert(tree: avlTree, val: 8) + testInsert(tree: avlTree, val: 7) + testInsert(tree: avlTree, val: 9) + testInsert(tree: avlTree, val: 10) + testInsert(tree: avlTree, val: 6) + + /* 插入重复节点 */ + testInsert(tree: avlTree, val: 7) + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(tree: avlTree, val: 8) // 删除度为 0 的节点 + testRemove(tree: avlTree, val: 5) // 删除度为 1 的节点 + testRemove(tree: avlTree, val: 4) // 删除度为 2 的节点 + + /* 查询节点 */ + let node = avlTree.search(val: 7) + print("\n查找到的节点对象为 \(node!),节点值 = \(node!.val)") + } +} diff --git a/codes/swift/chapter_tree/binary_search_tree.swift b/codes/swift/chapter_tree/binary_search_tree.swift new file mode 100644 index 0000000000..51141e9036 --- /dev/null +++ b/codes/swift/chapter_tree/binary_search_tree.swift @@ -0,0 +1,173 @@ +/** + * File: binary_search_tree.swift + * Created Time: 2023-01-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 二叉搜索树 */ +class BinarySearchTree { + private var root: TreeNode? + + /* 构造方法 */ + init() { + // 初始化空树 + root = nil + } + + /* 获取二叉树根节点 */ + func getRoot() -> TreeNode? { + root + } + + /* 查找节点 */ + func search(num: Int) -> TreeNode? { + var cur = root + // 循环查找,越过叶节点后跳出 + while cur != nil { + // 目标节点在 cur 的右子树中 + if cur!.val < num { + cur = cur?.right + } + // 目标节点在 cur 的左子树中 + else if cur!.val > num { + cur = cur?.left + } + // 找到目标节点,跳出循环 + else { + break + } + } + // 返回目标节点 + return cur + } + + /* 插入节点 */ + func insert(num: Int) { + // 若树为空,则初始化根节点 + if root == nil { + root = TreeNode(x: num) + return + } + var cur = root + var pre: TreeNode? + // 循环查找,越过叶节点后跳出 + while cur != nil { + // 找到重复节点,直接返回 + if cur!.val == num { + return + } + pre = cur + // 插入位置在 cur 的右子树中 + if cur!.val < num { + cur = cur?.right + } + // 插入位置在 cur 的左子树中 + else { + cur = cur?.left + } + } + // 插入节点 + let node = TreeNode(x: num) + if pre!.val < num { + pre?.right = node + } else { + pre?.left = node + } + } + + /* 删除节点 */ + func remove(num: Int) { + // 若树为空,直接提前返回 + if root == nil { + return + } + var cur = root + var pre: TreeNode? + // 循环查找,越过叶节点后跳出 + while cur != nil { + // 找到待删除节点,跳出循环 + if cur!.val == num { + break + } + pre = cur + // 待删除节点在 cur 的右子树中 + if cur!.val < num { + cur = cur?.right + } + // 待删除节点在 cur 的左子树中 + else { + cur = cur?.left + } + } + // 若无待删除节点,则直接返回 + if cur == nil { + return + } + // 子节点数量 = 0 or 1 + if cur?.left == nil || cur?.right == nil { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + let child = cur?.left ?? cur?.right + // 删除节点 cur + if cur !== root { + if pre?.left === cur { + pre?.left = child + } else { + pre?.right = child + } + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur?.right + while tmp?.left != nil { + tmp = tmp?.left + } + // 递归删除节点 tmp + remove(num: tmp!.val) + // 用 tmp 覆盖 cur + cur?.val = tmp!.val + } + } +} + +@main +enum _BinarySearchTree { + /* Driver Code */ + static func main() { + /* 初始化二叉搜索树 */ + let bst = BinarySearchTree() + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + for num in nums { + bst.insert(num: num) + } + print("\n初始化的二叉树为\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* 查找节点 */ + let node = bst.search(num: 7) + print("\n查找到的节点对象为 \(node!),节点值 = \(node!.val)") + + /* 插入节点 */ + bst.insert(num: 16) + print("\n插入节点 16 后,二叉树为\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* 删除节点 */ + bst.remove(num: 1) + print("\n删除节点 1 后,二叉树为\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 2) + print("\n删除节点 2 后,二叉树为\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 4) + print("\n删除节点 4 后,二叉树为\n") + PrintUtil.printTree(root: bst.getRoot()) + } +} diff --git a/codes/swift/chapter_tree/binary_tree.swift b/codes/swift/chapter_tree/binary_tree.swift new file mode 100644 index 0000000000..d9f71f0a36 --- /dev/null +++ b/codes/swift/chapter_tree/binary_tree.swift @@ -0,0 +1,40 @@ +/** + * File: binary_tree.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BinaryTree { + /* Driver Code */ + static func main() { + /* 初始化二叉树 */ + // 初始化节点 + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\n初始化二叉树\n") + PrintUtil.printTree(root: n1) + + /* 插入与删除节点 */ + let P = TreeNode(x: 0) + // 在 n1 -> n2 中间插入节点 P + n1.left = P + P.left = n2 + print("\n插入节点 P 后\n") + PrintUtil.printTree(root: n1) + // 删除节点 P + n1.left = n2 + print("\n删除节点 P 后\n") + PrintUtil.printTree(root: n1) + } +} diff --git a/codes/swift/chapter_tree/binary_tree_bfs.swift b/codes/swift/chapter_tree/binary_tree_bfs.swift new file mode 100644 index 0000000000..799aa29c2c --- /dev/null +++ b/codes/swift/chapter_tree/binary_tree_bfs.swift @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 层序遍历 */ +func levelOrder(root: TreeNode) -> [Int] { + // 初始化队列,加入根节点 + var queue: [TreeNode] = [root] + // 初始化一个列表,用于保存遍历序列 + var list: [Int] = [] + while !queue.isEmpty { + let node = queue.removeFirst() // 队列出队 + list.append(node.val) // 保存节点值 + if let left = node.left { + queue.append(left) // 左子节点入队 + } + if let right = node.right { + queue.append(right) // 右子节点入队 + } + } + return list +} + +@main +enum BinaryTreeBFS { + /* Driver Code */ + static func main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n初始化二叉树\n") + PrintUtil.printTree(root: node) + + /* 层序遍历 */ + let list = levelOrder(root: node) + print("\n层序遍历的节点打印序列 = \(list)") + } +} diff --git a/codes/swift/chapter_tree/binary_tree_dfs.swift b/codes/swift/chapter_tree/binary_tree_dfs.swift new file mode 100644 index 0000000000..75dcadedf7 --- /dev/null +++ b/codes/swift/chapter_tree/binary_tree_dfs.swift @@ -0,0 +1,70 @@ +/** + * File: binary_tree_dfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +// 初始化列表,用于存储遍历序列 +var list: [Int] = [] + +/* 前序遍历 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.append(root.val) + preOrder(root: root.left) + preOrder(root: root.right) +} + +/* 中序遍历 */ +func inOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root: root.left) + list.append(root.val) + inOrder(root: root.right) +} + +/* 后序遍历 */ +func postOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root: root.left) + postOrder(root: root.right) + list.append(root.val) +} + +@main +enum BinaryTreeDFS { + /* Driver Code */ + static func main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n初始化二叉树\n") + PrintUtil.printTree(root: root) + + /* 前序遍历 */ + list.removeAll() + preOrder(root: root) + print("\n前序遍历的节点打印序列 = \(list)") + + /* 中序遍历 */ + list.removeAll() + inOrder(root: root) + print("\n中序遍历的节点打印序列 = \(list)") + + /* 后序遍历 */ + list.removeAll() + postOrder(root: root) + print("\n后序遍历的节点打印序列 = \(list)") + } +} diff --git a/codes/swift/utils/ListNode.swift b/codes/swift/utils/ListNode.swift index 5fa3671eb6..331c081d03 100644 --- a/codes/swift/utils/ListNode.swift +++ b/codes/swift/utils/ListNode.swift @@ -4,11 +4,30 @@ * Author: nuomi1 (nuomi1@qq.com) */ -public class ListNode { - public var val: Int // 结点值 - public var next: ListNode? // 后继结点引用 +public class ListNode: Hashable { + public var val: Int // 节点值 + public var next: ListNode? // 后继节点引用 public init(x: Int) { val = x } + + public static func == (lhs: ListNode, rhs: ListNode) -> Bool { + lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + hasher.combine(next.map { ObjectIdentifier($0) }) + } + + public static func arrToLinkedList(arr: [Int]) -> ListNode? { + let dum = ListNode(x: 0) + var head: ListNode? = dum + for val in arr { + head?.next = ListNode(x: val) + head = head?.next + } + return dum.next + } } diff --git a/codes/swift/utils/Pair.swift b/codes/swift/utils/Pair.swift new file mode 100644 index 0000000000..b968251718 --- /dev/null +++ b/codes/swift/utils/Pair.swift @@ -0,0 +1,20 @@ +/** + * File: Pair.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 键值对 */ +public class Pair: Equatable { + public var key: Int + public var val: String + + public init(key: Int, val: String) { + self.key = key + self.val = val + } + + public static func == (lhs: Pair, rhs: Pair) -> Bool { + lhs.key == rhs.key && lhs.val == rhs.val + } +} diff --git a/codes/swift/utils/PrintUtil.swift b/codes/swift/utils/PrintUtil.swift index cbeab1225e..217b31a0e1 100644 --- a/codes/swift/utils/PrintUtil.swift +++ b/codes/swift/utils/PrintUtil.swift @@ -26,10 +26,10 @@ public enum PrintUtil { } public static func printTree(root: TreeNode?) { - printTree(root: root, prev: nil, isLeft: false) + printTree(root: root, prev: nil, isRight: false) } - private static func printTree(root: TreeNode?, prev: Trunk?, isLeft: Bool) { + private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { if root == nil { return } @@ -37,11 +37,11 @@ public enum PrintUtil { var prevStr = " " let trunk = Trunk(prev: prev, str: prevStr) - printTree(root: root?.right, prev: trunk, isLeft: true) + printTree(root: root?.right, prev: trunk, isRight: true) if prev == nil { trunk.str = "———" - } else if isLeft { + } else if isRight { trunk.str = "/———" prevStr = " |" } else { @@ -57,7 +57,7 @@ public enum PrintUtil { } trunk.str = " |" - printTree(root: root?.left, prev: trunk, isLeft: false) + printTree(root: root?.left, prev: trunk, isRight: false) } private static func showTrunks(p: Trunk?) { @@ -68,4 +68,26 @@ public enum PrintUtil { showTrunks(p: p?.prev) print(p!.str, terminator: "") } + + public static func printHashMap(map: [K: V]) { + for (key, value) in map { + print("\(key) -> \(value)") + } + } + + public static func printHeap(queue: [Int]) { + print("堆的数组表示:", terminator: "") + print(queue) + print("堆的树状表示:") + let root = TreeNode.listToTree(arr: queue) + printTree(root: root) + } + + public static func printMatrix(matrix: [[T]]) { + print("[") + for row in matrix { + print(" \(row),") + } + print("]") + } } diff --git a/codes/swift/utils/TreeNode.swift b/codes/swift/utils/TreeNode.swift index 161cebd57e..fe6e7c9962 100644 --- a/codes/swift/utils/TreeNode.swift +++ b/codes/swift/utils/TreeNode.swift @@ -4,14 +4,68 @@ * Author: nuomi1 (nuomi1@qq.com) */ +/* 二叉树节点类 */ public class TreeNode { - public var val: Int // 结点值 - public var height: Int // 结点高度 - public var left: TreeNode? // 左子结点引用 - public var right: TreeNode? // 右子结点引用 + public var val: Int // 节点值 + public var height: Int // 节点高度 + public var left: TreeNode? // 左子节点引用 + public var right: TreeNode? // 右子节点引用 + /* 构造方法 */ public init(x: Int) { val = x height = 0 } + + // 序列化编码规则请参考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二叉树的数组表示: + // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + // 二叉树的链表表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 将列表反序列化为二叉树:递归 */ + private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { + if i < 0 || i >= arr.count || arr[i] == nil { + return nil + } + let root = TreeNode(x: arr[i]!) + root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) + root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) + return root + } + + /* 将列表反序列化为二叉树 */ + public static func listToTree(arr: [Int?]) -> TreeNode? { + listToTreeDFS(arr: arr, i: 0) + } + + /* 将二叉树序列化为列表:递归 */ + private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { + if root == nil { + return + } + while i >= res.count { + res.append(nil) + } + res[i] = root?.val + treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) + treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) + } + + /* 将二叉树序列化为列表 */ + public static func treeToList(root: TreeNode?) -> [Int?] { + var res: [Int?] = [] + treeToListDFS(root: root, i: 0, res: &res) + return res + } } diff --git a/codes/swift/utils/Vertex.swift b/codes/swift/utils/Vertex.swift new file mode 100644 index 0000000000..1eedf44fe5 --- /dev/null +++ b/codes/swift/utils/Vertex.swift @@ -0,0 +1,32 @@ +/** + * File: Vertex.swift + * Created Time: 2023-02-19 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 顶点类 */ +public class Vertex: Hashable { + public var val: Int + + public init(val: Int) { + self.val = val + } + + public static func == (lhs: Vertex, rhs: Vertex) -> Bool { + lhs.val == rhs.val + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + } + + /* 输入值列表 vals ,返回顶点列表 vets */ + public static func valsToVets(vals: [Int]) -> [Vertex] { + vals.map { Vertex(val: $0) } + } + + /* 输入顶点列表 vets ,返回值列表 vals */ + public static func vetsToVals(vets: [Vertex]) -> [Int] { + vets.map { $0.val } + } +} diff --git a/codes/typescript/.gitignore b/codes/typescript/.gitignore new file mode 100644 index 0000000000..d5f19d89b3 --- /dev/null +++ b/codes/typescript/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/codes/typescript/.prettierrc b/codes/typescript/.prettierrc new file mode 100644 index 0000000000..3f4aa8cb65 --- /dev/null +++ b/codes/typescript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/codes/typescript/chapter_array_and_linkedlist/array.ts b/codes/typescript/chapter_array_and_linkedlist/array.ts index a85816b973..c5f629fe9a 100644 --- a/codes/typescript/chapter_array_and_linkedlist/array.ts +++ b/codes/typescript/chapter_array_and_linkedlist/array.ts @@ -4,7 +4,7 @@ * Author: Justin (xiefahit@gmail.com) */ -/* 随机返回一个数组元素 */ +/* 随机访问元素 */ function randomAccess(nums: number[]): number { // 在区间 [0, nums.length) 中随机抽取一个数字 const random_index = Math.floor(Math.random() * nums.length); @@ -15,7 +15,7 @@ function randomAccess(nums: number[]): number { /* 扩展数组长度 */ // 请注意,TypeScript 的 Array 是动态数组,可以直接扩展 -// 为了方便学习,本函数将 Array 看作是长度不可变的数组 +// 为了方便学习,本函数将 Array 看作长度不可变的数组 function extend(nums: number[], enlarge: number): number[] { // 初始化一个扩展长度后的数组 const res = new Array(nums.length + enlarge).fill(0); @@ -33,11 +33,11 @@ function insert(nums: number[], num: number, index: number): void { for (let i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num; } -/* 删除索引 index 处元素 */ +/* 删除索引 index 处的元素 */ function remove(nums: number[], index: number): void { // 把索引 index 之后的所有元素向前移动一位 for (let i = index; i < nums.length - 1; i++) { @@ -50,11 +50,11 @@ function traverse(nums: number[]): void { let count = 0; // 通过索引遍历数组 for (let i = 0; i < nums.length; i++) { - count++; + count += nums[i]; } - // 直接遍历数组 - for (let num of nums) { - count += 1; + // 直接遍历数组元素 + for (const num of nums) { + count += num; } } @@ -70,13 +70,13 @@ function find(nums: number[], target: number): number { /* Driver Code */ /* 初始化数组 */ -let arr: number[] = new Array(5).fill(0); +const arr: number[] = new Array(5).fill(0); console.log('数组 arr =', arr); let nums: number[] = [1, 3, 2, 5, 4]; console.log('数组 nums =', nums); /* 随机访问 */ -const random_num = randomAccess(nums); +let random_num = randomAccess(nums); console.log('在 nums 中获取随机元素', random_num); /* 长度扩展 */ @@ -95,7 +95,7 @@ console.log('删除索引 2 处的元素,得到 nums =', nums); traverse(nums); /* 查找元素 */ -var index: number = find(nums, 3); +let index = find(nums, 3); console.log('在 nums 中查找元素 3 ,得到索引 =', index); export {}; diff --git a/codes/typescript/chapter_array_and_linkedlist/linked_list.ts b/codes/typescript/chapter_array_and_linkedlist/linked_list.ts index b5eb988ae5..48f588d22a 100644 --- a/codes/typescript/chapter_array_and_linkedlist/linked_list.ts +++ b/codes/typescript/chapter_array_and_linkedlist/linked_list.ts @@ -4,17 +4,17 @@ * Author: Justin (xiefahit@gmail.com) */ -import ListNode from '../module/ListNode'; -import { printLinkedList } from '../module/PrintUtil'; +import { ListNode } from '../modules/ListNode'; +import { printLinkedList } from '../modules/PrintUtil'; -/* 在链表的结点 n0 之后插入结点 P */ +/* 在链表的节点 n0 之后插入节点 P */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; - n0.next = P; P.next = n1; + n0.next = P; } -/* 删除链表的结点 n0 之后的首个结点 */ +/* 删除链表的节点 n0 之后的首个节点 */ function remove(n0: ListNode): void { if (!n0.next) { return; @@ -25,7 +25,7 @@ function remove(n0: ListNode): void { n0.next = n1; } -/* 访问链表中索引为 index 的结点 */ +/* 访问链表中索引为 index 的节点 */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { if (!head) { @@ -36,7 +36,7 @@ function access(head: ListNode | null, index: number): ListNode | null { return head; } -/* 在链表中查找值为 target 的首个结点 */ +/* 在链表中查找值为 target 的首个节点 */ function find(head: ListNode | null, target: number): number { let index = 0; while (head !== null) { @@ -51,13 +51,13 @@ function find(head: ListNode | null, target: number): number { /* Driver Code */ /* 初始化链表 */ -// 初始化各个结点 +// 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); -// 构建引用指向 +// 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; @@ -65,22 +65,22 @@ n3.next = n4; console.log('初始化的链表为'); printLinkedList(n0); -/* 插入结点 */ +/* 插入节点 */ insert(n0, new ListNode(0)); -console.log('插入结点后的链表为'); +console.log('插入节点后的链表为'); printLinkedList(n0); -/* 删除结点 */ +/* 删除节点 */ remove(n0); -console.log('删除结点后的链表为'); +console.log('删除节点后的链表为'); printLinkedList(n0); -/* 访问结点 */ +/* 访问节点 */ const node = access(n0, 3); -console.log(`链表中索引 3 处的结点的值 = ${node?.val}`); +console.log(`链表中索引 3 处的节点的值 = ${node?.val}`); -/* 查找结点 */ +/* 查找节点 */ const index = find(n0, 2); -console.log(`链表中值为 2 的结点的索引 = ${index}`); +console.log(`链表中值为 2 的节点的索引 = ${index}`); export {}; diff --git a/codes/typescript/chapter_array_and_linkedlist/list.ts b/codes/typescript/chapter_array_and_linkedlist/list.ts index e1d2d518a8..a88c30be0c 100644 --- a/codes/typescript/chapter_array_and_linkedlist/list.ts +++ b/codes/typescript/chapter_array_and_linkedlist/list.ts @@ -5,56 +5,55 @@ */ /* 初始化列表 */ -const list: number[] = [1, 3, 2, 5, 4]; -console.log(`列表 list = ${list}`); +const nums: number[] = [1, 3, 2, 5, 4]; +console.log(`列表 nums = ${nums}`); /* 访问元素 */ -const num: number = list[1]; +const num: number = nums[1]; console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ -list[1] = 0; -console.log(`将索引 1 处的元素更新为 0 ,得到 list = ${list}`); +nums[1] = 0; +console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums}`); /* 清空列表 */ -list.length = 0; -console.log(`清空列表后 list = ${list}`); +nums.length = 0; +console.log(`清空列表后 nums = ${nums}`); -/* 尾部添加元素 */ -list.push(1); -list.push(3); -list.push(2); -list.push(5); -list.push(4); -console.log(`添加元素后 list = ${list}`); +/* 在尾部添加元素 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`添加元素后 nums = ${nums}`); -/* 中间插入元素 */ -list.splice(3, 0, 6); -console.log(`在索引 3 处插入数字 6 ,得到 list = ${list}`); +/* 在中间插入元素 */ +nums.splice(3, 0, 6); +console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums}`); /* 删除元素 */ -list.splice(3, 1); -console.log(`删除索引 3 处的元素,得到 list = ${list}`); +nums.splice(3, 1); +console.log(`删除索引 3 处的元素,得到 nums = ${nums}`); /* 通过索引遍历列表 */ let count = 0; -for (let i = 0; i < list.length; i++) { - count++; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; } - /* 直接遍历列表元素 */ count = 0; -for (const n of list) { - count++; +for (const x of nums) { + count += x; } /* 拼接两个列表 */ -const list1: number[] = [6, 8, 7, 10, 9]; -list.push(...list1); -console.log(`将列表 list1 拼接到 list 之后,得到 list = ${list}`); +const nums1: number[] = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`将列表 nums1 拼接到 nums 之后,得到 nums = ${nums}`); /* 排序列表 */ -list.sort((a, b) => a - b); -console.log(`排序列表后 list = ${list}`); +nums.sort((a, b) => a - b); +console.log(`排序列表后 nums = ${nums}`); export {}; diff --git a/codes/typescript/chapter_array_and_linkedlist/my_list.ts b/codes/typescript/chapter_array_and_linkedlist/my_list.ts index e60075daf0..674479934a 100644 --- a/codes/typescript/chapter_array_and_linkedlist/my_list.ts +++ b/codes/typescript/chapter_array_and_linkedlist/my_list.ts @@ -4,19 +4,19 @@ * Author: Justin (xiefahit@gmail.com) */ -/* 列表类简易实现 */ +/* 列表类 */ class MyList { - private nums: Array; // 数组(存储列表元素) + private arr: Array; // 数组(存储列表元素) private _capacity: number = 10; // 列表容量 - private _size: number = 0; // 列表长度(即当前元素数量) + private _size: number = 0; // 列表长度(当前元素数量) private extendRatio: number = 2; // 每次列表扩容的倍数 - /* 构造函数 */ + /* 构造方法 */ constructor() { - this.nums = new Array(this._capacity); + this.arr = new Array(this._capacity); } - /* 获取列表长度(即当前元素数量)*/ + /* 获取列表长度(当前元素数量)*/ public size(): number { return this._size; } @@ -28,120 +28,114 @@ class MyList { /* 访问元素 */ public get(index: number): number { - // 索引如果越界则抛出异常,下同 - if (index >= this._size) { - throw new Error('索引越界'); - } - return this.nums[index]; + // 索引如果越界,则抛出异常,下同 + if (index < 0 || index >= this._size) throw new Error('索引越界'); + return this.arr[index]; } /* 更新元素 */ public set(index: number, num: number): void { - if (index >= this._size) throw new Error('索引越界'); - this.nums[index] = num; + if (index < 0 || index >= this._size) throw new Error('索引越界'); + this.arr[index] = num; } - /* 尾部添加元素 */ + /* 在尾部添加元素 */ public add(num: number): void { // 如果长度等于容量,则需要扩容 - if (this._size === this._capacity) { - this.extendCapacity(); - } + if (this._size === this._capacity) this.extendCapacity(); // 将新元素添加到列表尾部 - this.nums[this._size] = num; + this.arr[this._size] = num; this._size++; } - /* 中间插入元素 */ + /* 在中间插入元素 */ public insert(index: number, num: number): void { - if (index >= this._size) { - throw new Error('索引越界'); - } + if (index < 0 || index >= this._size) throw new Error('索引越界'); // 元素数量超出容量时,触发扩容机制 if (this._size === this._capacity) { this.extendCapacity(); } // 将索引 index 以及之后的元素都向后移动一位 for (let j = this._size - 1; j >= index; j--) { - this.nums[j + 1] = this.nums[j]; + this.arr[j + 1] = this.arr[j]; } // 更新元素数量 - this.nums[index] = num; + this.arr[index] = num; this._size++; } /* 删除元素 */ public remove(index: number): number { - if (index >= this._size) throw new Error('索引越界'); - let num = this.nums[index]; - // 将索引 index 之后的元素都向前移动一位 + if (index < 0 || index >= this._size) throw new Error('索引越界'); + let num = this.arr[index]; + // 将将索引 index 之后的元素都向前移动一位 for (let j = index; j < this._size - 1; j++) { - this.nums[j] = this.nums[j + 1]; + this.arr[j] = this.arr[j + 1]; } // 更新元素数量 this._size--; - // 返回被删除元素 + // 返回被删除的元素 return num; } /* 列表扩容 */ public extendCapacity(): void { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - this.nums = this.nums.concat( + // 新建一个长度为 size 的数组,并将原数组复制到新数组 + this.arr = this.arr.concat( new Array(this.capacity() * (this.extendRatio - 1)) ); // 更新列表容量 - this._capacity = this.nums.length; + this._capacity = this.arr.length; } /* 将列表转换为数组 */ public toArray(): number[] { let size = this.size(); // 仅转换有效长度范围内的列表元素 - let nums = new Array(size); + const arr = new Array(size); for (let i = 0; i < size; i++) { - nums[i] = this.get(i); + arr[i] = this.get(i); } - return nums; + return arr; } } /* Driver Code */ /* 初始化列表 */ -let list = new MyList(); -/* 尾部添加元素 */ -list.add(1); -list.add(3); -list.add(2); -list.add(5); -list.add(4); +const nums = new MyList(); +/* 在尾部添加元素 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); console.log( - `列表 list = ${list.toArray()} ,容量 = ${list.capacity()} ,长度 = ${list.size()}` + `列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); -/* 中间插入元素 */ -list.insert(3, 6); -console.log(`在索引 3 处插入数字 6 ,得到 list = ${list.toArray()}`); +/* 在中间插入元素 */ +nums.insert(3, 6); +console.log(`在索引 3 处插入数字 6 ,得到 nums = ${nums.toArray()}`); /* 删除元素 */ -list.remove(3); -console.log(`删除索引 3 处的元素,得到 list = ${list.toArray()}`); +nums.remove(3); +console.log(`删除索引 3 处的元素,得到 nums = ${nums.toArray()}`); /* 访问元素 */ -let num = list.get(1); +const num = nums.get(1); console.log(`访问索引 1 处的元素,得到 num = ${num}`); /* 更新元素 */ -list.set(1, 0); -console.log(`将索引 1 处的元素更新为 0 ,得到 list = ${list.toArray()}`); +nums.set(1, 0); +console.log(`将索引 1 处的元素更新为 0 ,得到 nums = ${nums.toArray()}`); /* 测试扩容机制 */ for (let i = 0; i < 10; i++) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - list.add(i); + nums.add(i); } console.log( - `扩容后的列表 list = ${list.toArray()} ,容量 = ${list.capacity()} ,长度 = ${list.size()}` + `扩容后的列表 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,长度 = ${nums.size()}` ); export {}; diff --git a/codes/typescript/chapter_backtracking/n_queens.ts b/codes/typescript/chapter_backtracking/n_queens.ts new file mode 100644 index 0000000000..640d168751 --- /dev/null +++ b/codes/typescript/chapter_backtracking/n_queens.ts @@ -0,0 +1,65 @@ +/** + * File: n_queens.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯算法:n 皇后 */ +function backtrack( + row: number, + n: number, + state: string[][], + res: string[][][], + cols: boolean[], + diags1: boolean[], + diags2: boolean[] +): void { + // 当放置完所有行时,记录解 + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // 遍历所有列 + for (let col = 0; col < n; col++) { + // 计算该格子对应的主对角线和次对角线 + const diag1 = row - col + n - 1; + const diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 尝试:将皇后放置在该格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +function nQueens(n: number): string[][][] { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 记录列是否有皇后 + const diags1 = Array(2 * n - 1).fill(false); // 记录主对角线上是否有皇后 + const diags2 = Array(2 * n - 1).fill(false); // 记录次对角线上是否有皇后 + const res: string[][][] = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`输入棋盘长宽为 ${n}`); +console.log(`皇后放置方案共有 ${res.length} 种`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); + +export {}; diff --git a/codes/typescript/chapter_backtracking/permutations_i.ts b/codes/typescript/chapter_backtracking/permutations_i.ts new file mode 100644 index 0000000000..33900dd5e8 --- /dev/null +++ b/codes/typescript/chapter_backtracking/permutations_i.ts @@ -0,0 +1,49 @@ +/** + * File: permutations_i.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯算法:全排列 I */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 当状态长度等于元素数量时,记录解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 遍历所有选择 + choices.forEach((choice, i) => { + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.push(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 I */ +function permutationsI(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 3]; +const res: number[][] = permutationsI(nums); + +console.log(`输入数组 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/codes/typescript/chapter_backtracking/permutations_ii.ts b/codes/typescript/chapter_backtracking/permutations_ii.ts new file mode 100644 index 0000000000..782a45a6eb --- /dev/null +++ b/codes/typescript/chapter_backtracking/permutations_ii.ts @@ -0,0 +1,51 @@ +/** + * File: permutations_ii.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯算法:全排列 II */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 当状态长度等于元素数量时,记录解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 遍历所有选择 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.has(choice)) { + // 尝试:做出选择,更新状态 + duplicated.add(choice); // 记录选择过的元素值 + selected[i] = true; + state.push(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 II */ +function permutationsII(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 2]; +const res: number[][] = permutationsII(nums); + +console.log(`输入数组 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts b/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts new file mode 100644 index 0000000000..831176135d --- /dev/null +++ b/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts @@ -0,0 +1,36 @@ +/** + * File: preorder_traversal_i_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序遍历:例题一 */ +function preOrder(root: TreeNode | null, res: TreeNode[]): void { + if (root === null) { + return; + } + if (root.val === 7) { + // 记录解 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 前序遍历 +const res: TreeNode[] = []; +preOrder(root, res); + +console.log('\n输出所有值为 7 的节点'); +console.log(res.map((node) => node.val)); + +export {}; diff --git a/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts b/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts new file mode 100644 index 0000000000..47b557fd52 --- /dev/null +++ b/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序遍历:例题二 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + if (root === null) { + return; + } + // 尝试 + path.push(root); + if (root.val === 7) { + // 记录解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 前序遍历 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\n输出所有根节点到节点 7 的路径'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts b/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts new file mode 100644 index 0000000000..33641c0894 --- /dev/null +++ b/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts @@ -0,0 +1,48 @@ +/** + * File: preorder_traversal_iii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序遍历:例题三 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + // 剪枝 + if (root === null || root.val === 3) { + return; + } + // 尝试 + path.push(root); + if (root.val === 7) { + // 记录解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 前序遍历 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts b/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts new file mode 100644 index 0000000000..c2b1ac8de2 --- /dev/null +++ b/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts @@ -0,0 +1,75 @@ +/** + * File: preorder_traversal_iii_template.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 判断当前状态是否为解 */ +function isSolution(state: TreeNode[]): boolean { + return state && state[state.length - 1]?.val === 7; +} + +/* 记录解 */ +function recordSolution(state: TreeNode[], res: TreeNode[][]): void { + res.push([...state]); +} + +/* 判断在当前状态下,该选择是否合法 */ +function isValid(state: TreeNode[], choice: TreeNode): boolean { + return choice !== null && choice.val !== 3; +} + +/* 更新状态 */ +function makeChoice(state: TreeNode[], choice: TreeNode): void { + state.push(choice); +} + +/* 恢复状态 */ +function undoChoice(state: TreeNode[]): void { + state.pop(); +} + +/* 回溯算法:例题三 */ +function backtrack( + state: TreeNode[], + choices: TreeNode[], + res: TreeNode[][] +): void { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + for (const choice of choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + backtrack(state, [choice.left, choice.right], res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二叉树'); +printTree(root); + +// 回溯算法 +const res: TreeNode[][] = []; +backtrack([], [root], res); + +console.log('\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/codes/typescript/chapter_backtracking/subset_sum_i.ts b/codes/typescript/chapter_backtracking/subset_sum_i.ts new file mode 100644 index 0000000000..c0286bd2cc --- /dev/null +++ b/codes/typescript/chapter_backtracking/subset_sum_i.ts @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯算法:子集和 I */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 子集和等于 target 时,记录解 + if (target === 0) { + res.push([...state]); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 I */ +function subsetSumI(nums: number[], target: number): number[][] { + const state = []; // 状态(子集) + nums.sort((a, b) => a - b); // 对 nums 进行排序 + const start = 0; // 遍历起始点 + const res = []; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts b/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts new file mode 100644 index 0000000000..5a3d8248f4 --- /dev/null +++ b/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts @@ -0,0 +1,52 @@ +/** + * File: subset_sum_i_naive.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯算法:子集和 I */ +function backtrack( + state: number[], + target: number, + total: number, + choices: number[], + res: number[][] +): void { + // 子集和等于 target 时,记录解 + if (total === target) { + res.push([...state]); + return; + } + // 遍历所有选择 + for (let i = 0; i < choices.length; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 I(包含重复子集) */ +function subsetSumINaive(nums: number[], target: number): number[][] { + const state = []; // 状态(子集) + const total = 0; // 子集和 + const res = []; // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); +console.log('请注意,该方法输出的结果包含重复集合'); + +export {}; diff --git a/codes/typescript/chapter_backtracking/subset_sum_ii.ts b/codes/typescript/chapter_backtracking/subset_sum_ii.ts new file mode 100644 index 0000000000..a499c2e82b --- /dev/null +++ b/codes/typescript/chapter_backtracking/subset_sum_ii.ts @@ -0,0 +1,59 @@ +/** + * File: subset_sum_ii.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯算法:子集和 II */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 子集和等于 target 时,记录解 + if (target === 0) { + res.push([...state]); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.push(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop(); + } +} + +/* 求解子集和 II */ +function subsetSumII(nums: number[], target: number): number[][] { + const state = []; // 状态(子集) + nums.sort((a, b) => a - b); // 对 nums 进行排序 + const start = 0; // 遍历起始点 + const res = []; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`输入数组 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等于 ${target} 的子集 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/codes/typescript/chapter_computational_complexity/iteration.ts b/codes/typescript/chapter_computational_complexity/iteration.ts new file mode 100644 index 0000000000..ef1be9b151 --- /dev/null +++ b/codes/typescript/chapter_computational_complexity/iteration.ts @@ -0,0 +1,72 @@ +/** + * File: iteration.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for 循环 */ +function forLoop(n: number): number { + let res = 0; + // 循环求和 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 循环 */ +function whileLoop(n: number): number { + let res = 0; + let i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; +} + +/* while 循环(两次更新) */ +function whileLoopII(n: number): number { + let res = 0; + let i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; +} + +/* 双层 for 循环 */ +function nestedForLoop(n: number): string { + let res = ''; + // 循环 i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = forLoop(n); +console.log(`for 循环的求和结果 res = ${res}`); + +res = whileLoop(n); +console.log(`while 循环的求和结果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while 循环(两次更新)求和结果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`双层 for 循环的遍历结果 ${resStr}`); + +export {}; diff --git a/codes/typescript/chapter_computational_complexity/leetcode_two_sum.ts b/codes/typescript/chapter_computational_complexity/leetcode_two_sum.ts deleted file mode 100644 index 47c52d80d3..0000000000 --- a/codes/typescript/chapter_computational_complexity/leetcode_two_sum.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * File: leetcode_two_sum.ts - * Created Time: 2022-12-15 - * Author: gyt95 (gytkwan@gmail.com) - */ - -function twoSumBruteForce(nums: number[], target: number): number[] { - const n = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (let i = 0; i < n; i++) { - for (let j = i + 1; j < n; j++) { - if (nums[i] + nums[j] === target) { - return [i, j]; - } - } - } - return []; -}; - -function twoSumHashTable(nums: number[], target: number): number[] { - // 辅助哈希表,空间复杂度 O(n) - let m: Map = new Map(); - // 单层循环,时间复杂度 O(n) - for (let i = 0; i < nums.length; i++) { - let index = m.get(nums[i]); - if (index !== undefined) { - return [index, i]; - } else { - m.set(target - nums[i], i); - } - } - return []; -}; - -/* Driver Code */ -// 方法一 -const nums = [2, 7, 11, 15], target = 9; - -let res = twoSumBruteForce(nums, target); -console.log("方法一 res = ", res); - -// 方法二 -res = twoSumHashTable(nums, target); -console.log("方法二 res = ", res); \ No newline at end of file diff --git a/codes/typescript/chapter_computational_complexity/recursion.ts b/codes/typescript/chapter_computational_complexity/recursion.ts new file mode 100644 index 0000000000..bd7618574d --- /dev/null +++ b/codes/typescript/chapter_computational_complexity/recursion.ts @@ -0,0 +1,70 @@ +/** + * File: recursion.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 递归 */ +function recur(n: number): number { + // 终止条件 + if (n === 1) return 1; + // 递:递归调用 + const res = recur(n - 1); + // 归:返回结果 + return n + res; +} + +/* 使用迭代模拟递归 */ +function forLoopRecur(n: number): number { + // 使用一个显式的栈来模拟系统调用栈 + const stack: number[] = []; + let res: number = 0; + // 递:递归调用 + for (let i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack.push(i); + } + // 归:返回结果 + while (stack.length) { + // 通过“出栈操作”模拟“归” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾递归 */ +function tailRecur(n: number, res: number): number { + // 终止条件 + if (n === 0) return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); +} + +/* 斐波那契数列:递归 */ +function fib(n: number): number { + // 终止条件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = recur(n); +console.log(`递归函数的求和结果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`使用迭代模拟递归的求和结果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`尾递归函数的求和结果 res = ${res}`); + +res = fib(n); +console.log(`斐波那契数列的第 ${n} 项为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_computational_complexity/space_complexity.ts b/codes/typescript/chapter_computational_complexity/space_complexity.ts new file mode 100644 index 0000000000..ab48497818 --- /dev/null +++ b/codes/typescript/chapter_computational_complexity/space_complexity.ts @@ -0,0 +1,103 @@ +/** + * File: space_complexity.ts + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 函数 */ +function constFunc(): number { + // 执行某些操作 + return 0; +} + +/* 常数阶 */ +function constant(n: number): void { + // 常量、变量、对象占用 O(1) 空间 + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // 循环中的变量占用 O(1) 空间 + for (let i = 0; i < n; i++) { + const c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 线性阶 */ +function linear(n: number): void { + // 长度为 n 的数组占用 O(n) 空间 + const nums = new Array(n); + // 长度为 n 的列表占用 O(n) 空间 + const nodes: ListNode[] = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 线性阶(递归实现) */ +function linearRecur(n: number): void { + console.log(`递归 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 平方阶 */ +function quadratic(n: number): void { + // 矩阵占用 O(n^2) 空间 + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二维列表占用 O(n^2) 空间 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 平方阶(递归实现) */ +function quadraticRecur(n: number): number { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指数阶(建立满二叉树) */ +function buildTree(n: number): TreeNode | null { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 常数阶 +constant(n); +// 线性阶 +linear(n); +linearRecur(n); +// 平方阶 +quadratic(n); +quadraticRecur(n); +// 指数阶 +const root = buildTree(n); +printTree(root); diff --git a/codes/typescript/chapter_computational_complexity/time_complexity.ts b/codes/typescript/chapter_computational_complexity/time_complexity.ts index e5dde67c66..9b7bc4481c 100644 --- a/codes/typescript/chapter_computational_complexity/time_complexity.ts +++ b/codes/typescript/chapter_computational_complexity/time_complexity.ts @@ -32,7 +32,7 @@ function arrayTraversal(nums: number[]): number { /* 平方阶 */ function quadratic(n: number): number { let count = 0; - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { count++; @@ -44,9 +44,9 @@ function quadratic(n: number): number { /* 平方阶(冒泡排序) */ function bubbleSort(nums: number[]): number { let count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -64,7 +64,7 @@ function bubbleSort(nums: number[]): number { function exponential(n: number): number { let count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) for (let i = 0; i < n; i++) { for (let j = 0; j < base; j++) { count++; @@ -77,7 +77,7 @@ function exponential(n: number): number { /* 指数阶(递归实现) */ function expRecur(n: number): number { - if (n == 1) return 1; + if (n === 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } @@ -109,7 +109,7 @@ function linearLogRecur(n: number): number { /* 阶乘阶(递归实现) */ function factorialRecur(n: number): number { - if (n == 0) return 1; + if (n === 0) return 1; let count = 0; // 从 1 个分裂出 n 个 for (let i = 0; i < n; i++) { @@ -121,35 +121,37 @@ function factorialRecur(n: number): number { /* Driver Code */ // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 const n = 8; -console.log("输入数据大小 n = " + n); +console.log('输入数据大小 n = ' + n); let count = constant(n); -console.log("常数阶的计算操作数量 = " + count); +console.log('常数阶的操作数量 = ' + count); count = linear(n); -console.log("线性阶的计算操作数量 = " + count); +console.log('线性阶的操作数量 = ' + count); count = arrayTraversal(new Array(n)); -console.log("线性阶(遍历数组)的计算操作数量 = " + count); +console.log('线性阶(遍历数组)的操作数量 = ' + count); count = quadratic(n); -console.log("平方阶的计算操作数量 = " + count); +console.log('平方阶的操作数量 = ' + count); var nums = new Array(n); for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); -console.log("平方阶(冒泡排序)的计算操作数量 = " + count); +console.log('平方阶(冒泡排序)的操作数量 = ' + count); count = exponential(n); -console.log("指数阶(循环实现)的计算操作数量 = " + count); +console.log('指数阶(循环实现)的操作数量 = ' + count); count = expRecur(n); -console.log("指数阶(递归实现)的计算操作数量 = " + count); +console.log('指数阶(递归实现)的操作数量 = ' + count); count = logarithmic(n); -console.log("对数阶(循环实现)的计算操作数量 = " + count); +console.log('对数阶(循环实现)的操作数量 = ' + count); count = logRecur(n); -console.log("对数阶(递归实现)的计算操作数量 = " + count); +console.log('对数阶(递归实现)的操作数量 = ' + count); count = linearLogRecur(n); -console.log("线性对数阶(递归实现)的计算操作数量 = " + count); +console.log('线性对数阶(递归实现)的操作数量 = ' + count); count = factorialRecur(n); -console.log("阶乘阶(递归实现)的计算操作数量 = " + count); +console.log('阶乘阶(递归实现)的操作数量 = ' + count); + +export {}; diff --git a/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts b/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts index a7318634f5..8d392f3c1f 100644 --- a/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts +++ b/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts @@ -1,4 +1,4 @@ -/* +/** * File: worst_best_time_complexity.ts * Created Time: 2023-01-05 * Author: RiverTwilight (contact@rene.wang) @@ -6,15 +6,15 @@ /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ function randomNumbers(n: number): number[] { - let nums = Array(n); + const nums = Array(n); // 生成数组 nums = { 1, 2, 3, ..., n } for (let i = 0; i < n; i++) { nums[i] = i + 1; } // 随机打乱数组元素 for (let i = 0; i < n; i++) { - let r = Math.floor(Math.random() * (i + 1)); - let temp = nums[i]; + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; nums[i] = nums[r]; nums[r] = temp; } @@ -24,6 +24,8 @@ function randomNumbers(n: number): number[] { /* 查找数组 nums 中数字 1 所在索引 */ function findOne(nums: number[]): number { for (let i = 0; i < nums.length; i++) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] === 1) { return i; } @@ -32,14 +34,12 @@ function findOne(nums: number[]): number { } /* Driver Code */ -function main(): void { - for (let i = 0; i < 10; i++) { - let n = 100; - let nums = randomNumbers(n); - let index = findOne(nums); - console.log( - "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" - ); - console.log("数字 1 的索引为 " + index); - } +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n数组 [ 1, 2, ..., n ] 被打乱后 = [' + nums.join(', ') + ']'); + console.log('数字 1 的索引为 ' + index); } + +export {}; diff --git a/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts b/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts new file mode 100644 index 0000000000..233c6a501b --- /dev/null +++ b/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts @@ -0,0 +1,41 @@ +/** + * File: binary_search_recur.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分查找:问题 f(i, j) */ +function dfs(nums: number[], target: number, i: number, j: number): number { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + const m = i + ((j - i) >> 1); + 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; + } +} + +/* 二分查找 */ +function binarySearch(nums: number[], target: number): number { + const n = nums.length; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分查找(双闭区间) +const index = binarySearch(nums, target); +console.log(`目标元素 6 的索引 = ${index}`); + +export {}; diff --git a/codes/typescript/chapter_divide_and_conquer/build_tree.ts b/codes/typescript/chapter_divide_and_conquer/build_tree.ts new file mode 100644 index 0000000000..d47b954504 --- /dev/null +++ b/codes/typescript/chapter_divide_and_conquer/build_tree.ts @@ -0,0 +1,50 @@ +/** + * File: build_tree.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { printTree } from '../modules/PrintUtil'; +import { TreeNode } from '../modules/TreeNode'; + +/* 构建二叉树:分治 */ +function dfs( + preorder: number[], + inorderMap: Map, + i: number, + l: number, + r: number +): TreeNode | null { + // 子树区间为空时终止 + if (r - l < 0) return null; + // 初始化根节点 + const root: TreeNode = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + const m = inorderMap.get(preorder[i]); + // 子问题:构建左子树 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; +} + +/* 构建二叉树 */ +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + // 初始化哈希表,存储 inorder 元素到索引的映射 + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前序遍历 = ' + JSON.stringify(preorder)); +console.log('中序遍历 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('构建的二叉树为:'); +printTree(root); diff --git a/codes/typescript/chapter_divide_and_conquer/hanota.ts b/codes/typescript/chapter_divide_and_conquer/hanota.ts new file mode 100644 index 0000000000..3cdec72c76 --- /dev/null +++ b/codes/typescript/chapter_divide_and_conquer/hanota.ts @@ -0,0 +1,52 @@ +/** + * File: hanota.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 移动一个圆盘 */ +function move(src: number[], tar: number[]): void { + // 从 src 顶部拿出一个圆盘 + const pan = src.pop(); + // 将圆盘放入 tar 顶部 + tar.push(pan); +} + +/* 求解汉诺塔问题 f(i) */ +function dfs(i: number, src: number[], buf: number[], tar: number[]): void { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i === 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解汉诺塔问题 */ +function solveHanota(A: number[], B: number[], C: number[]): void { + const n = A.length; + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +// 列表尾部是柱子顶部 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初始状态下:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('圆盘移动完成后:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts b/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts new file mode 100644 index 0000000000..0139a96cbb --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_backtrack.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯 */ +function backtrack( + choices: number[], + state: number, + n: number, + res: Map<0, any> +): void { + // 当爬到第 n 阶时,方案数量加 1 + if (state === n) res.set(0, res.get(0) + 1); + // 遍历所有选择 + for (const choice of choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) continue; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬楼梯:回溯 */ +function climbingStairsBacktrack(n: number): number { + const choices = [1, 2]; // 可选择向上爬 1 阶或 2 阶 + const state = 0; // 从第 0 阶开始爬 + const res = new Map(); + res.set(0, 0); // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts b/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts new file mode 100644 index 0000000000..85af4ad8b2 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_constraint_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 带约束爬楼梯:动态规划 */ +function climbingStairsConstraintDP(n: number): number { + if (n === 1 || n === 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = Array.from({ length: n + 1 }, () => new Array(3)); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts b/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts new file mode 100644 index 0000000000..36f71088f7 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts @@ -0,0 +1,26 @@ +/** + * File: climbing_stairs_dfs.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 搜索 */ +function dfs(i: number): number { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬楼梯:搜索 */ +function climbingStairsDFS(n: number): number { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts b/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts new file mode 100644 index 0000000000..13d0d6fe2f --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs_mem.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 记忆化搜索 */ +function dfs(i: number, mem: number[]): number { + // 已知 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] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; +} + +/* 爬楼梯:记忆化搜索 */ +function climbingStairsDFSMem(n: number): number { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts b/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts new file mode 100644 index 0000000000..05daf7997c --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_dp.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 爬楼梯:动态规划 */ +function climbingStairsDP(n: number): number { + if (n === 1 || n === 2) return n; + // 初始化 dp 表,用于存储子问题的解 + const dp = new Array(n + 1).fill(-1); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬楼梯:空间优化后的动态规划 */ +function climbingStairsDPComp(n: number): number { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); +res = climbingStairsDPComp(n); +console.log(`爬 ${n} 阶楼梯共有 ${res} 种方案`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/coin_change.ts b/codes/typescript/chapter_dynamic_programming/coin_change.ts new file mode 100644 index 0000000000..27d90aeba5 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/coin_change.ts @@ -0,0 +1,68 @@ +/** + * File: coin_change.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换:动态规划 */ +function coinChangeDP(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 状态转移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行和列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零钱兑换:空间优化后的动态规划 */ +function coinChangeDPComp(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 动态规划 +let res = coinChangeDP(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); + +// 空间优化后的动态规划 +res = coinChangeDPComp(coins, amt); +console.log(`凑到目标金额所需的最少硬币数量为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts b/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts new file mode 100644 index 0000000000..7dd02e9941 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零钱兑换 II:动态规划 */ +function coinChangeIIDP(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +function coinChangeIIDPComp(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 动态规划 +let res = coinChangeIIDP(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); + +// 空间优化后的动态规划 +res = coinChangeIIDPComp(coins, amt); +console.log(`凑出目标金额的硬币组合数量为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/edit_distance.ts b/codes/typescript/chapter_dynamic_programming/edit_distance.ts new file mode 100644 index 0000000000..4d163a1d35 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/edit_distance.ts @@ -0,0 +1,148 @@ +/** + * File: edit_distance.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 编辑距离:暴力搜索 */ +function editDistanceDFS(s: string, t: string, i: number, j: number): number { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return Math.min(insert, del, replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +function editDistanceDFSMem( + s: string, + t: string, + mem: Array>, + i: number, + j: number +): number { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +function editDistanceDP(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => 0) + ); + // 状态转移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行和列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 编辑距离:空间优化后的动态规划 */ +function editDistanceDPComp(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 状态转移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (let i = 1; i <= n; i++) { + // 状态转移:首列 + let leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜索 +let res = editDistanceDFS(s, t, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 记忆化搜索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => -1) +); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 动态规划 +res = editDistanceDP(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +// 空间优化后的动态规划 +res = editDistanceDPComp(s, t); +console.log(`将 ${s} 更改为 ${t} 最少需要编辑 ${res} 步`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/knapsack.ts b/codes/typescript/chapter_dynamic_programming/knapsack.ts new file mode 100644 index 0000000000..a840b15487 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/knapsack.ts @@ -0,0 +1,134 @@ +/** + * File: knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜索 */ +function knapsackDFS( + wgt: Array, + val: Array, + i: number, + c: number +): number { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return Math.max(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +function knapsackDFSMem( + wgt: Array, + val: Array, + mem: Array>, + i: number, + c: number +): number { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +function knapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空间优化后的动态规划 */ +function knapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 状态转移 + for (let i = 1; i <= n; i++) { + // 倒序遍历 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜索 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 动态规划 +res = knapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 空间优化后的动态规划 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts b/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts new file mode 100644 index 0000000000..48710b1ce0 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬楼梯最小代价:动态规划 */ +function minCostClimbingStairsDP(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用于存储子问题的解 + const dp = new Array(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +function minCostClimbingStairsDPComp(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log(`输入楼梯的代价列表为:${cost}`); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完楼梯的最低代价为:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完楼梯的最低代价为:${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/min_path_sum.ts b/codes/typescript/chapter_dynamic_programming/min_path_sum.ts new file mode 100644 index 0000000000..f0c149ccbc --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/min_path_sum.ts @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路径和:暴力搜索 */ +function minPathSumDFS( + grid: Array>, + i: number, + j: number +): number { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路径和:记忆化搜索 */ +function minPathSumDFSMem( + grid: Array>, + mem: Array>, + i: number, + j: number +): number { + // 若为左上角单元格,则终止搜索 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +function minPathSumDP(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (let i = 1; i < n; i++) { + for (let j: number = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路径和:空间优化后的动态规划 */ +function minPathSumDPComp(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 状态转移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (let i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 暴力搜索 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 记忆化搜索 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 动态规划 +res = minPathSumDP(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +// 空间优化后的动态规划 +res = minPathSumDPComp(grid); +console.log(`从左上角到右下角的最小路径和为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts b/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts new file mode 100644 index 0000000000..8abc5a6d69 --- /dev/null +++ b/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts @@ -0,0 +1,73 @@ +/** + * File: unbounded_knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:动态规划 */ +function unboundedKnapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空间优化后的动态规划 */ +function unboundedKnapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 状态转移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 动态规划 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +// 空间优化后的动态规划 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_graph/graph_adjacency_list.ts b/codes/typescript/chapter_graph/graph_adjacency_list.ts new file mode 100644 index 0000000000..4c7692312f --- /dev/null +++ b/codes/typescript/chapter_graph/graph_adjacency_list.ts @@ -0,0 +1,139 @@ +/** + * File: graph_adjacency_list.ts + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { Vertex } from '../modules/Vertex'; + +/* 基于邻接表实现的无向图类 */ +class GraphAdjList { + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + adjList: Map; + + /* 构造方法 */ + constructor(edges: Vertex[][]) { + this.adjList = new Map(); + // 添加所有顶点和边 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + size(): number { + return this.adjList.size; + } + + /* 添加边 */ + addEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 添加边 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 删除边 */ + removeEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 删除边 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 添加顶点 */ + addVertex(vet: Vertex): void { + if (this.adjList.has(vet)) return; + // 在邻接表中添加一个新链表 + this.adjList.set(vet, []); + } + + /* 删除顶点 */ + removeVertex(vet: Vertex): void { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在邻接表中删除顶点 vet 对应的链表 + this.adjList.delete(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (const set of this.adjList.values()) { + const index: number = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 打印邻接表 */ + print(): void { + console.log('邻接表 ='); + for (const [key, value] of this.adjList.entries()) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* 初始化无向图 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初始化后,图为'); + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 即 v0, v2 + graph.addEdge(v0, v2); + console.log('\n添加边 1-2 后,图为'); + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 即 v0, v1 + graph.removeEdge(v0, v1); + console.log('\n删除边 1-3 后,图为'); + graph.print(); + + /* 添加顶点 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n添加顶点 6 后,图为'); + graph.print(); + + /* 删除顶点 */ + // 顶点 3 即 v1 + graph.removeVertex(v1); + console.log('\n删除顶点 3 后,图为'); + graph.print(); +} + +export { GraphAdjList }; diff --git a/codes/typescript/chapter_graph/graph_adjacency_matrix.ts b/codes/typescript/chapter_graph/graph_adjacency_matrix.ts new file mode 100644 index 0000000000..e734bc2de1 --- /dev/null +++ b/codes/typescript/chapter_graph/graph_adjacency_matrix.ts @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.ts + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + vertices: number[]; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + adjMat: number[][]; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + constructor(vertices: number[], edges: number[][]) { + this.vertices = []; + this.adjMat = []; + // 添加顶点 + for (const val of vertices) { + this.addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + size(): number { + return this.vertices.length; + } + + /* 添加顶点 */ + addVertex(val: number): void { + const n: number = this.size(); + // 向顶点列表中添加新顶点的值 + this.vertices.push(val); + // 在邻接矩阵中添加一行 + const newRow: number[] = []; + for (let j: number = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在邻接矩阵中添加一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 删除顶点 */ + removeVertex(index: number): void { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在顶点列表中移除索引 index 的顶点 + this.vertices.splice(index, 1); + + // 在邻接矩阵中删除索引 index 的行 + this.adjMat.splice(index, 1); + // 在邻接矩阵中删除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + addEdge(i: number, j: number): void { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + removeEdge(i: number, j: number): void { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + print(): void { + console.log('顶点列表 = ', this.vertices); + console.log('邻接矩阵 =', this.adjMat); + } +} + +/* Driver Code */ +/* 初始化无向图 */ +// 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 +const vertices: number[] = [1, 3, 2, 5, 4]; +const edges: number[][] = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); +console.log('\n初始化后,图为'); +graph.print(); + +/* 添加边 */ +// 顶点 1, 2 的索引分别为 0, 2 +graph.addEdge(0, 2); +console.log('\n添加边 1-2 后,图为'); +graph.print(); + +/* 删除边 */ +// 顶点 1, 3 的索引分别为 0, 1 +graph.removeEdge(0, 1); +console.log('\n删除边 1-3 后,图为'); +graph.print(); + +/* 添加顶点 */ +graph.addVertex(6); +console.log('\n添加顶点 6 后,图为'); +graph.print(); + +/* 删除顶点 */ +// 顶点 3 的索引为 1 +graph.removeVertex(1); +console.log('\n删除顶点 3 后,图为'); +graph.print(); + +export {}; diff --git a/codes/typescript/chapter_graph/graph_bfs.ts b/codes/typescript/chapter_graph/graph_bfs.ts new file mode 100644 index 0000000000..5a98bfa7d0 --- /dev/null +++ b/codes/typescript/chapter_graph/graph_bfs.ts @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { GraphAdjList } from './graph_adjacency_list'; +import { Vertex } from '../modules/Vertex'; + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 顶点遍历序列 + const res: Vertex[] = []; + // 哈希集合,用于记录已被访问过的顶点 + const visited: Set = new Set(); + visited.add(startVet); + // 队列用于实现 BFS + const que = [startVet]; + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.length) { + const vet = que.shift(); // 队首顶点出队 + res.push(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + que.push(adjVet); // 只入队未访问 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; +} + +/* Driver Code */ +/* 初始化无向图 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化后,图为'); +graph.print(); + +/* 广度优先遍历 */ +const res = graphBFS(graph, v[0]); +console.log('\n广度优先遍历(BFS)顶点序列为'); +console.log(Vertex.vetsToVals(res)); diff --git a/codes/typescript/chapter_graph/graph_dfs.ts b/codes/typescript/chapter_graph/graph_dfs.ts new file mode 100644 index 0000000000..542198a329 --- /dev/null +++ b/codes/typescript/chapter_graph/graph_dfs.ts @@ -0,0 +1,58 @@ +/** + * File: graph_dfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { Vertex } from '../modules/Vertex'; +import { GraphAdjList } from './graph_adjacency_list'; + +/* 深度优先遍历辅助函数 */ +function dfs( + graph: GraphAdjList, + visited: Set, + res: Vertex[], + vet: Vertex +): void { + res.push(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳过已被访问的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 顶点遍历序列 + const res: Vertex[] = []; + // 哈希集合,用于记录已被访问过的顶点 + const visited: Set = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 初始化无向图 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化后,图为'); +graph.print(); + +/* 深度优先遍历 */ +const res = graphDFS(graph, v[0]); +console.log('\n深度优先遍历(DFS)顶点序列为'); +console.log(Vertex.vetsToVals(res)); diff --git a/codes/typescript/chapter_greedy/coin_change_greedy.ts b/codes/typescript/chapter_greedy/coin_change_greedy.ts new file mode 100644 index 0000000000..303ef7cc8e --- /dev/null +++ b/codes/typescript/chapter_greedy/coin_change_greedy.ts @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 零钱兑换:贪心 */ +function coinChangeGreedy(coins: number[], amt: number): number { + // 假设 coins 数组有序 + let i = coins.length - 1; + let count = 0; + // 循环进行贪心选择,直到无剩余金额 + while (amt > 0) { + // 找到小于且最接近剩余金额的硬币 + while (i > 0 && coins[i] > amt) { + i--; + } + // 选择 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,则返回 -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 贪心:能够保证找到全局最优解 +let coins: number[] = [1, 5, 10, 20, 50, 100]; +let amt: number = 186; +let res: number = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); + +// 贪心:无法保证找到全局最优解 +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); +console.log('实际上需要的最少数量为 3 ,即 20 + 20 + 20'); + +// 贪心:无法保证找到全局最优解 +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`凑到 ${amt} 所需的最少硬币数量为 ${res}`); +console.log('实际上需要的最少数量为 2 ,即 49 + 49'); + +export {}; diff --git a/codes/typescript/chapter_greedy/fractional_knapsack.ts b/codes/typescript/chapter_greedy/fractional_knapsack.ts new file mode 100644 index 0000000000..55d6b45509 --- /dev/null +++ b/codes/typescript/chapter_greedy/fractional_knapsack.ts @@ -0,0 +1,50 @@ +/** + * File: fractional_knapsack.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 物品 */ +class Item { + w: number; // 物品重量 + v: number; // 物品价值 + + constructor(w: number, v: number) { + this.w = w; + this.v = v; + } +} + +/* 分数背包:贪心 */ +function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { + // 创建物品列表,包含两个属性:重量、价值 + const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); + // 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 循环贪心选择 + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (item.v / item.w) * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; +} + +/* Driver Code */ +const wgt: number[] = [10, 20, 30, 40, 50]; +const val: number[] = [50, 120, 150, 210, 240]; +const cap: number = 50; + +// 贪心算法 +const res: number = fractionalKnapsack(wgt, val, cap); +console.log(`不超过背包容量的最大物品价值为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_greedy/max_capacity.ts b/codes/typescript/chapter_greedy/max_capacity.ts new file mode 100644 index 0000000000..edde65f64b --- /dev/null +++ b/codes/typescript/chapter_greedy/max_capacity.ts @@ -0,0 +1,36 @@ +/** + * File: max_capacity.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:贪心 */ +function maxCapacity(ht: number[]): number { + // 初始化 i, j,使其分列数组两端 + let i = 0, + j = ht.length - 1; + // 初始最大容量为 0 + let res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + const cap: number = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 贪心算法 +const res: number = maxCapacity(ht); +console.log(`最大容量为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_greedy/max_product_cutting.ts b/codes/typescript/chapter_greedy/max_product_cutting.ts new file mode 100644 index 0000000000..8eb0bff5c5 --- /dev/null +++ b/codes/typescript/chapter_greedy/max_product_cutting.ts @@ -0,0 +1,35 @@ +/** + * File: max_product_cutting.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切分乘积:贪心 */ +function maxProductCutting(n: number): number { + // 当 n <= 3 时,必须切分出一个 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + let a: number = Math.floor(n / 3); + let b: number = n % 3; + if (b === 1) { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 当余数为 2 时,不做处理 + return Math.pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return Math.pow(3, a); +} + +/* Driver Code */ +let n: number = 58; + +// 贪心算法 +let res: number = maxProductCutting(n); +console.log(`最大切分乘积为 ${res}`); + +export {}; diff --git a/codes/typescript/chapter_hashing/array_hash_map.ts b/codes/typescript/chapter_hashing/array_hash_map.ts index 027cdc8e1b..97534ccb78 100644 --- a/codes/typescript/chapter_hashing/array_hash_map.ts +++ b/codes/typescript/chapter_hashing/array_hash_map.ts @@ -5,7 +5,7 @@ */ /* 键值对 Number -> String */ -class Entry { +class Pair { public key: number; public val: string; @@ -15,14 +15,13 @@ class Entry { } } -/* 基于数组简易实现的哈希表 */ +/* 基于数组实现的哈希表 */ class ArrayHashMap { - - private readonly bucket: (Entry | null)[]; + private readonly buckets: (Pair | null)[]; constructor() { - // 初始化一个长度为 100 的桶(数组) - this.bucket = (new Array(100)).fill(null); + // 初始化数组,包含 100 个桶 + this.buckets = new Array(100).fill(null); } /* 哈希函数 */ @@ -33,30 +32,30 @@ class ArrayHashMap { /* 查询操作 */ public get(key: number): string | null { let index = this.hashFunc(key); - let entry = this.bucket[index]; - if (entry === null) return null; - return entry.val; + let pair = this.buckets[index]; + if (pair === null) return null; + return pair.val; } /* 添加操作 */ public set(key: number, val: string) { let index = this.hashFunc(key); - this.bucket[index] = new Entry(key, val); + this.buckets[index] = new Pair(key, val); } /* 删除操作 */ public delete(key: number) { let index = this.hashFunc(key); // 置为 null ,代表删除 - this.bucket[index] = null; + this.buckets[index] = null; } /* 获取所有键值对 */ - public entries(): (Entry | null)[] { - let arr: (Entry | null)[] = []; - for (let i = 0; i < this.bucket.length; i++) { - if (this.bucket[i]) { - arr.push(this.bucket[i]); + public entries(): (Pair | null)[] { + let arr: (Pair | null)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i]); } } return arr; @@ -65,9 +64,9 @@ class ArrayHashMap { /* 获取所有键 */ public keys(): (number | undefined)[] { let arr: (number | undefined)[] = []; - for (let i = 0; i < this.bucket.length; i++) { - if (this.bucket[i]) { - arr.push(this.bucket[i]?.key); + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].key); } } return arr; @@ -76,9 +75,9 @@ class ArrayHashMap { /* 获取所有值 */ public values(): (string | undefined)[] { let arr: (string | undefined)[] = []; - for (let i = 0; i < this.bucket.length; i++) { - if (this.bucket[i]) { - arr.push(this.bucket[i]?.val); + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].val); } } return arr; @@ -86,10 +85,9 @@ class ArrayHashMap { /* 打印哈希表 */ public print() { - let entrySet = this.entries(); - for (const entry of entrySet) { - if (!entry) continue; - console.info(`${entry.key} -> ${entry.val}`); + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); } } } @@ -108,7 +106,7 @@ console.info('\n添加完成后,哈希表为\nKey -> Value'); map.print(); /* 查询操作 */ -// 向哈希表输入键 key ,得到值 value +// 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); @@ -120,9 +118,9 @@ map.print(); /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); -for (const entry of map.entries()) { - if (!entry) continue; - console.info(entry.key + ' -> ' + entry.val); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); } console.info('\n单独遍历键 Key'); for (const key of map.keys()) { diff --git a/codes/typescript/chapter_hashing/hash_map.ts b/codes/typescript/chapter_hashing/hash_map.ts index 8d73da0c09..54f52de900 100644 --- a/codes/typescript/chapter_hashing/hash_map.ts +++ b/codes/typescript/chapter_hashing/hash_map.ts @@ -19,7 +19,7 @@ console.info('\n添加完成后,哈希表为\nKey -> Value'); console.info(map); /* 查询操作 */ -// 向哈希表输入键 key ,得到值 value +// 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); @@ -42,3 +42,5 @@ console.info('\n单独遍历值 Value'); for (const v of map.values()) { console.info(v); } + +export {}; diff --git a/codes/typescript/chapter_hashing/hash_map_chaining.ts b/codes/typescript/chapter_hashing/hash_map_chaining.ts new file mode 100644 index 0000000000..9df6a3e163 --- /dev/null +++ b/codes/typescript/chapter_hashing/hash_map_chaining.ts @@ -0,0 +1,146 @@ +/** + * File: hash_map_chaining.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 键值对 Number -> String */ +class Pair { + key: number; + val: string; + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 链式地址哈希表 */ +class HashMapChaining { + #size: number; // 键值对数量 + #capacity: number; // 哈希表容量 + #loadThres: number; // 触发扩容的负载因子阈值 + #extendRatio: number; // 扩容倍数 + #buckets: Pair[][]; // 桶数组 + + /* 构造方法 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 哈希函数 */ + #hashFunc(key: number): number { + return key % this.#capacity; + } + + /* 负载因子 */ + #loadFactor(): number { + return this.#size / this.#capacity; + } + + /* 查询操作 */ + get(key: number): string | null { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若找到 key ,则返回对应 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key ,则返回 null + return null; + } + + /* 添加操作 */ + put(key: number, val: string): void { + // 当负载因子超过阈值时,执行扩容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 删除操作 */ + remove(key: number): void { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 遍历桶,从中删除键值对 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* 扩容哈希表 */ + #extend(): void { + // 暂存原哈希表 + const bucketsTmp = this.#buckets; + // 初始化扩容后的新哈希表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print(): void { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化哈希表 */ +const map = new HashMapChaining(); + +/* 添加操作 */ +// 在哈希表中添加键值对 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小啰'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +map.print(); + +/* 查询操作 */ +// 向哈希表中输入键 key ,得到值 value +const name = map.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +/* 删除操作 */ +// 在哈希表中删除键值对 (key, value) +map.remove(12836); +console.log('\n删除 12836 后,哈希表为\nKey -> Value'); +map.print(); + +export {}; diff --git a/codes/typescript/chapter_hashing/hash_map_open_addressing.ts b/codes/typescript/chapter_hashing/hash_map_open_addressing.ts new file mode 100644 index 0000000000..bd2631ad3c --- /dev/null +++ b/codes/typescript/chapter_hashing/hash_map_open_addressing.ts @@ -0,0 +1,182 @@ +/** + * File: hash_map_open_addressing.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* 键值对 Number -> String */ +class Pair { + key: number; + val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + private size: number; // 键值对数量 + private capacity: number; // 哈希表容量 + private loadThres: number; // 触发扩容的负载因子阈值 + private extendRatio: number; // 扩容倍数 + private buckets: Array; // 桶数组 + private TOMBSTONE: Pair; // 删除标记 + + /* 构造方法 */ + constructor() { + this.size = 0; // 键值对数量 + this.capacity = 4; // 哈希表容量 + this.loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + this.extendRatio = 2; // 扩容倍数 + this.buckets = Array(this.capacity).fill(null); // 桶数组 + this.TOMBSTONE = new Pair(-1, '-1'); // 删除标记 + } + + /* 哈希函数 */ + private hashFunc(key: number): number { + return key % this.capacity; + } + + /* 负载因子 */ + private loadFactor(): number { + return this.size / this.capacity; + } + + /* 搜索 key 对应的桶索引 */ + private findBucket(key: number): number { + let index = this.hashFunc(key); + let firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (this.buckets[index] !== null) { + // 若遇到 key ,返回对应的桶索引 + if (this.buckets[index]!.key === key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone !== -1) { + this.buckets[firstTombstone] = this.buckets[index]; + this.buckets[index] = this.TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if ( + firstTombstone === -1 && + this.buckets[index] === this.TOMBSTONE + ) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % this.capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 查询操作 */ + get(key: number): string | null { + // 搜索 key 对应的桶索引 + const index = this.findBucket(key); + // 若找到键值对,则返回对应 val + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + return this.buckets[index]!.val; + } + // 若键值对不存在,则返回 null + return null; + } + + /* 添加操作 */ + put(key: number, val: string): void { + // 当负载因子超过阈值时,执行扩容 + if (this.loadFactor() > this.loadThres) { + this.extend(); + } + // 搜索 key 对应的桶索引 + const index = this.findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index]!.val = val; + return; + } + // 若键值对不存在,则添加该键值对 + this.buckets[index] = new Pair(key, val); + this.size++; + } + + /* 删除操作 */ + remove(key: number): void { + // 搜索 key 对应的桶索引 + const index = this.findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index] = this.TOMBSTONE; + this.size--; + } + } + + /* 扩容哈希表 */ + private extend(): void { + // 暂存原哈希表 + const bucketsTmp = this.buckets; + // 初始化扩容后的新哈希表 + this.capacity *= this.extendRatio; + this.buckets = Array(this.capacity).fill(null); + this.size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print(): void { + for (const pair of this.buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// 初始化哈希表 +const hashmap = new HashMapOpenAddressing(); + +// 添加操作 +// 在哈希表中添加键值对 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小啰'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +hashmap.print(); + +// 查询操作 +// 向哈希表中输入键 key ,得到值 val +const name = hashmap.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +// 删除操作 +// 在哈希表中删除键值对 (key, val) +hashmap.remove(16750); +console.log('\n删除 16750 后,哈希表为\nKey -> Value'); +hashmap.print(); + +export {}; diff --git a/codes/typescript/chapter_hashing/simple_hash.ts b/codes/typescript/chapter_hashing/simple_hash.ts new file mode 100644 index 0000000000..54c1fa2832 --- /dev/null +++ b/codes/typescript/chapter_hashing/simple_hash.ts @@ -0,0 +1,60 @@ +/** + * File: simple_hash.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法哈希 */ +function addHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法哈希 */ +function mulHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 异或哈希 */ +function xorHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* 旋转哈希 */ +function rotHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 算法'; + +let hash = addHash(key); +console.log('加法哈希值为 ' + hash); + +hash = mulHash(key); +console.log('乘法哈希值为 ' + hash); + +hash = xorHash(key); +console.log('异或哈希值为 ' + hash); + +hash = rotHash(key); +console.log('旋转哈希值为 ' + hash); diff --git a/codes/typescript/chapter_heap/my_heap.ts b/codes/typescript/chapter_heap/my_heap.ts new file mode 100644 index 0000000000..d9c300c98b --- /dev/null +++ b/codes/typescript/chapter_heap/my_heap.ts @@ -0,0 +1,155 @@ +/** + * File: my_heap.ts + * Created Time: 2023-02-07 + * Author: Justin (xiefahit@gmail.com) + */ + +import { printHeap } from '../modules/PrintUtil'; + +/* 最大堆类 */ +class MaxHeap { + private maxHeap: number[]; + /* 构造方法,建立空堆或根据输入列表建堆 */ + constructor(nums?: number[]) { + // 将列表元素原封不动添加进堆 + this.maxHeap = nums === undefined ? [] : [...nums]; + // 堆化除叶节点以外的其他所有节点 + for (let i = this.parent(this.size() - 1); i >= 0; i--) { + this.siftDown(i); + } + } + + /* 获取左子节点的索引 */ + private left(i: number): number { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + private right(i: number): number { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + private parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 交换元素 */ + private swap(i: number, j: number): void { + const tmp = this.maxHeap[i]; + this.maxHeap[i] = this.maxHeap[j]; + this.maxHeap[j] = tmp; + } + + /* 获取堆大小 */ + public size(): number { + return this.maxHeap.length; + } + + /* 判断堆是否为空 */ + public isEmpty(): boolean { + return this.size() === 0; + } + + /* 访问堆顶元素 */ + public peek(): number { + return this.maxHeap[0]; + } + + /* 元素入堆 */ + public push(val: number): void { + // 添加节点 + this.maxHeap.push(val); + // 从底至顶堆化 + this.siftUp(this.size() - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + private siftUp(i: number): void { + while (true) { + // 获取节点 i 的父节点 + const p = this.parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; + // 交换两节点 + this.swap(i, p); + // 循环向上堆化 + i = p; + } + } + + /* 元素出堆 */ + public pop(): number { + // 判空处理 + if (this.isEmpty()) throw new RangeError('Heap is empty.'); + // 交换根节点与最右叶节点(交换首元素与尾元素) + this.swap(0, this.size() - 1); + // 删除节点 + const val = this.maxHeap.pop(); + // 从顶至底堆化 + this.siftDown(0); + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + private siftDown(i: number): void { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + const l = this.left(i), + r = this.right(i); + let ma = i; + if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; + if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma === i) break; + // 交换两节点 + this.swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + + /* 打印堆(二叉树) */ + public print(): void { + printHeap(this.maxHeap); + } + + /* 取出堆中元素 */ + public getMaxHeap(): number[] { + return this.maxHeap; + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* 初始化大顶堆 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\n输入列表并建堆后'); + maxHeap.print(); + + /* 获取堆顶元素 */ + let peek = maxHeap.peek(); + console.log(`\n堆顶元素为 ${peek}`); + + /* 元素入堆 */ + const val = 7; + maxHeap.push(val); + console.log(`\n元素 ${val} 入堆后`); + maxHeap.print(); + + /* 堆顶元素出堆 */ + peek = maxHeap.pop(); + console.log(`\n堆顶元素 ${peek} 出堆后`); + maxHeap.print(); + + /* 获取堆大小 */ + const size = maxHeap.size(); + console.log(`\n堆元素数量为 ${size}`); + + /* 判断堆是否为空 */ + const isEmpty = maxHeap.isEmpty(); + console.log(`\n堆是否为空 ${isEmpty}`); +} + +export { MaxHeap }; diff --git a/codes/typescript/chapter_heap/top_k.ts b/codes/typescript/chapter_heap/top_k.ts new file mode 100644 index 0000000000..a06f008f07 --- /dev/null +++ b/codes/typescript/chapter_heap/top_k.ts @@ -0,0 +1,58 @@ +/** + * File: top_k.ts + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { MaxHeap } from './my_heap'; + +/* 元素入堆 */ +function pushMinHeap(maxHeap: MaxHeap, val: number): void { + // 元素取反 + maxHeap.push(-val); +} + +/* 元素出堆 */ +function popMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.pop(); +} + +/* 访问堆顶元素 */ +function peekMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.peek(); +} + +/* 取出堆中元素 */ +function getMinHeap(maxHeap: MaxHeap): number[] { + // 元素取反 + return maxHeap.getMaxHeap().map((num: number) => -num); +} + +/* 基于堆查找数组中最大的 k 个元素 */ +function topKHeap(nums: number[], k: number): number[] { + // 初始化小顶堆 + // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆 + const maxHeap = new MaxHeap([]); + // 将数组的前 k 个元素入堆 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (let i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆中元素 + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大的 ${k} 个元素为`, res); diff --git a/codes/typescript/chapter_searching/binary_search.ts b/codes/typescript/chapter_searching/binary_search.ts index 2a03994b4e..5e69960caa 100644 --- a/codes/typescript/chapter_searching/binary_search.ts +++ b/codes/typescript/chapter_searching/binary_search.ts @@ -1,54 +1,65 @@ -/* -* File: binary_search.ts -* Created Time: 2022-12-27 -* Author: Daniel (better.sunjian@gmail.com) -*/ +/** + * File: binary_search.ts + * Created Time: 2022-12-27 + * Author: Daniel (better.sunjian@gmail.com) + */ /* 二分查找(双闭区间) */ -const binarySearch = function (nums: number[], target: number): number { +function binarySearch(nums: number[], target: number): number { // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - let i = 0, j = nums.length - 1; + let i = 0, + j = nums.length - 1; // 循环,当搜索区间为空时跳出(当 i > j 时为空) while (i <= j) { - const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m - if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 + // 计算中点索引 m + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // 此情况说明 target 在区间 [m+1, j] 中 i = m + 1; - } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 + } else if (nums[m] > target) { + // 此情况说明 target 在区间 [i, m-1] 中 j = m - 1; - } else { // 找到目标元素,返回其索引 + } else { + // 找到目标元素,返回其索引 return m; } } return -1; // 未找到目标元素,返回 -1 } -/* 二分查找(左闭右开) */ -const binarySearch1 = function (nums: number[], target: number): number { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - let i = 0, j = nums.length; +/* 二分查找(左闭右开区间) */ +function binarySearchLCRO(nums: number[], target: number): number { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + let i = 0, + j = nums.length; // 循环,当搜索区间为空时跳出(当 i = j 时为空) while (i < j) { - const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m - if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 + // 计算中点索引 m + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // 此情况说明 target 在区间 [m+1, j) 中 i = m + 1; - } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m] 中 + } else if (nums[m] > target) { + // 此情况说明 target 在区间 [i, m) 中 j = m; - } else { // 找到目标元素,返回其索引 + } else { + // 找到目标元素,返回其索引 return m; } } return -1; // 未找到目标元素,返回 -1 } - /* Driver Code */ const target = 6; -const nums = [ 1, 3, 6, 8, 12, 15, 23, 67, 70, 92 ]; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; /* 二分查找(双闭区间) */ let index = binarySearch(nums, target); console.info('目标元素 6 的索引 = %d', index); -/* 二分查找(左闭右开) */ -index = binarySearch1(nums, target); +/* 二分查找(左闭右开区间) */ +index = binarySearchLCRO(nums, target); console.info('目标元素 6 的索引 = %d', index); + +export {}; diff --git a/codes/typescript/chapter_searching/binary_search_edge.ts b/codes/typescript/chapter_searching/binary_search_edge.ts new file mode 100644 index 0000000000..08e91f07d2 --- /dev/null +++ b/codes/typescript/chapter_searching/binary_search_edge.ts @@ -0,0 +1,46 @@ +/** + * File: binary_search_edge.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ +import { binarySearchInsertion } from './binary_search_insertion'; + +/* 二分查找最左一个 target */ +function binarySearchLeftEdge(nums: Array, target: number): number { + // 等价于查找 target 的插入点 + const i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分查找最右一个 target */ +function binarySearchRightEdge(nums: Array, target: number): number { + // 转化为查找最左一个 target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + const j = i - 1; + // 未找到 target ,返回 -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +// 包含重复元素的数组 +let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n数组 nums = ' + nums); +// 二分查找左边界和右边界 +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('最左一个元素 ' + target + ' 的索引为 ' + index); + index = binarySearchRightEdge(nums, target); + console.log('最右一个元素 ' + target + ' 的索引为 ' + index); +} + +export {}; diff --git a/codes/typescript/chapter_searching/binary_search_insertion.ts b/codes/typescript/chapter_searching/binary_search_insertion.ts new file mode 100644 index 0000000000..bb470e28d1 --- /dev/null +++ b/codes/typescript/chapter_searching/binary_search_insertion.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分查找插入点(无重复元素) */ +function binarySearchInsertionSimple( + nums: Array, + target: number +): number { + let i = 0, + j = nums.length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* 二分查找插入点(存在重复元素) */ +function binarySearchInsertion(nums: Array, target: number): number { + let i = 0, + j = nums.length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* Driver Code */ +// 无重复元素的数组 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n数组 nums = ' + nums); +// 二分查找插入点 +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('元素 ' + target + ' 的插入点的索引为 ' + index); +} + +// 包含重复元素的数组 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n数组 nums = ' + nums); +// 二分查找插入点 +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('元素 ' + target + ' 的插入点的索引为 ' + index); +} + +export { binarySearchInsertion }; diff --git a/codes/typescript/chapter_searching/hashing_search.ts b/codes/typescript/chapter_searching/hashing_search.ts index 7f93d71789..e20217c5c9 100644 --- a/codes/typescript/chapter_searching/hashing_search.ts +++ b/codes/typescript/chapter_searching/hashing_search.ts @@ -1,51 +1,50 @@ /** - * File: hashing_search.js + * File: hashing_search.ts * Created Time: 2022-12-29 * Author: Zhuo Qinyue (1403450829@qq.com) */ -import { printLinkedList } from "../module/PrintUtil"; -import ListNode from "../module/ListNode"; - +import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* 哈希查找(数组) */ -function hashingSearch(map: Map, target: number): number { +function hashingSearchArray(map: Map, target: number): number { // 哈希表的 key: 目标元素,value: 索引 // 若哈希表中无此 key ,返回 -1 - return map.has(target) ? map.get(target) as number : -1; + return map.has(target) ? (map.get(target) as number) : -1; } /* 哈希查找(链表) */ -function hashingSearch1(map: Map, target: number): ListNode | null { - // 哈希表的 key: 目标结点值,value: 结点对象 +function hashingSearchLinkedList( + map: Map, + target: number +): ListNode | null { + // 哈希表的 key: 目标节点值,value: 节点对象 // 若哈希表中无此 key ,返回 null - return map.has(target) ? map.get(target) as ListNode : null; + return map.has(target) ? (map.get(target) as ListNode) : null; } -function main() { - const target = 3; +/* Driver Code */ +const target = 3; - /* 哈希查找(数组) */ - const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; - // 初始化哈希表 - const map = new Map(); - for (let i = 0; i < nums.length; i++) { - map.set(nums[i], i); // key: 元素,value: 索引 - } - const index = hashingSearch(map, target); - console.log("目标元素 3 的索引 = " + index); +/* 哈希查找(数组) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// 初始化哈希表 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 元素,value: 索引 +} +const index = hashingSearchArray(map, target); +console.log('目标元素 3 的索引 = ' + index); - /* 哈希查找(链表) */ - let head = new ListNode().arrToLinkedList(nums) - // 初始化哈希表 - const map1 = new Map(); - while (head != null) { - map1.set(head.val, head); // key: 结点值,value: 结点 - head = head.next; - } - const node = hashingSearch1(map1, target); - console.log("目标结点值 3 的对应结点对象为"); - printLinkedList(node); +/* 哈希查找(链表) */ +let head = arrToLinkedList(nums); +// 初始化哈希表 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: 节点值,value: 节点 + head = head.next; } +const node = hashingSearchLinkedList(map1, target); +console.log('目标节点值 3 的对应节点对象为', node); -main(); +export {}; diff --git a/codes/typescript/chapter_searching/linear_search.ts b/codes/typescript/chapter_searching/linear_search.ts index f0de33f788..e2f166be9d 100644 --- a/codes/typescript/chapter_searching/linear_search.ts +++ b/codes/typescript/chapter_searching/linear_search.ts @@ -4,7 +4,7 @@ * Author: Daniel (better.sunjian@gmail.com) */ -import ListNode from '../module/ListNode.ts'; +import { ListNode, arrToLinkedList } from '../modules/ListNode'; /* 线性查找(数组)*/ function linearSearchArray(nums: number[], target: number): number { @@ -20,16 +20,19 @@ function linearSearchArray(nums: number[], target: number): number { } /* 线性查找(链表)*/ -function linearSearchLinkedList(head: ListNode | null, target: number): ListNode | null { +function linearSearchLinkedList( + head: ListNode | null, + target: number +): ListNode | null { // 遍历链表 while (head) { - // 找到目标结点,返回之 + // 找到目标节点,返回之 if (head.val === target) { return head; } head = head.next; } - // 未找到目标结点,返回 null + // 未找到目标节点,返回 null return null; } @@ -37,11 +40,13 @@ function linearSearchLinkedList(head: ListNode | null, target: number): ListNode const target = 3; /* 在数组中执行线性查找 */ -const nums = [ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 ]; +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; const index = linearSearchArray(nums, target); console.log('目标元素 3 的索引 =', index); /* 在链表中执行线性查找 */ -const head = ListNode.arrToLinkedList(nums); +const head = arrToLinkedList(nums); const node = linearSearchLinkedList(head, target); -console.log('目标结点值 3 的对应结点对象为', node); +console.log('目标节点值 3 的对应节点对象为', node); + +export {}; diff --git a/codes/typescript/chapter_searching/two_sum.ts b/codes/typescript/chapter_searching/two_sum.ts new file mode 100644 index 0000000000..eb55ea1019 --- /dev/null +++ b/codes/typescript/chapter_searching/two_sum.ts @@ -0,0 +1,49 @@ +/** + * File: two_sum.ts + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法一:暴力枚举 */ +function twoSumBruteForce(nums: number[], target: number): number[] { + const n = nums.length; + // 两层循环,时间复杂度为 O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法二:辅助哈希表 */ +function twoSumHashTable(nums: number[], target: number): number[] { + // 辅助哈希表,空间复杂度为 O(n) + let m: Map = new Map(); + // 单层循环,时间复杂度为 O(n) + for (let i = 0; i < nums.length; i++) { + let index = m.get(target - nums[i]); + if (index !== undefined) { + return [index, i]; + } else { + m.set(nums[i], i); + } + } + return []; +} + +/* Driver Code */ +// 方法一 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法一 res = ', res); + +// 方法二 +res = twoSumHashTable(nums, target); +console.log('方法二 res = ', res); + +export {}; diff --git a/codes/typescript/chapter_sorting/bubble_sort.ts b/codes/typescript/chapter_sorting/bubble_sort.ts index 74e5e6d5b1..936b8e9a9c 100644 --- a/codes/typescript/chapter_sorting/bubble_sort.ts +++ b/codes/typescript/chapter_sorting/bubble_sort.ts @@ -1,14 +1,14 @@ /** - * File: quick_sort.ts + * File: bubble_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 冒泡排序 */ function bubbleSort(nums: number[]): void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -22,10 +22,10 @@ function bubbleSort(nums: number[]): void { /* 冒泡排序(标志优化)*/ function bubbleSortWithFlag(nums: number[]): void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] for (let i = nums.length - 1; i > 0; i--) { let flag = false; // 初始化标志位 - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (let j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -35,17 +35,17 @@ function bubbleSortWithFlag(nums: number[]): void { flag = true; // 记录交换元素 } } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; bubbleSort(nums); -console.log('排序后数组 nums =', nums); +console.log('冒泡排序完成后 nums =', nums); const nums1 = [4, 1, 3, 1, 5, 2]; bubbleSortWithFlag(nums1); -console.log('排序后数组 nums =', nums1); +console.log('冒泡排序完成后 nums =', nums1); export {}; diff --git a/codes/typescript/chapter_sorting/bucket_sort.ts b/codes/typescript/chapter_sorting/bucket_sort.ts new file mode 100644 index 0000000000..78369cf824 --- /dev/null +++ b/codes/typescript/chapter_sorting/bucket_sort.ts @@ -0,0 +1,41 @@ +/** + * File: bucket_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 桶排序 */ +function bucketSort(nums: number[]): void { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + const k = nums.length / 2; + const buckets: number[][] = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 将数组元素分配到各个桶中 + for (const num of nums) { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + const i = Math.floor(num * k); + // 将 num 添加进桶 i + buckets[i].push(num); + } + // 2. 对各个桶执行排序 + for (const bucket of buckets) { + // 使用内置排序函数,也可以替换成其他排序算法 + bucket.sort((a, b) => a - b); + } + // 3. 遍历桶合并结果 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('桶排序完成后 nums =', nums); + +export {}; diff --git a/codes/typescript/chapter_sorting/counting_sort.ts b/codes/typescript/chapter_sorting/counting_sort.ts new file mode 100644 index 0000000000..cdf60c0a44 --- /dev/null +++ b/codes/typescript/chapter_sorting/counting_sort.ts @@ -0,0 +1,73 @@ +/** + * File: counting_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +function countingSortNaive(nums: number[]): void { + // 1. 统计数组最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +function countingSort(nums: number[]): void { + // 1. 统计数组最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + const n = nums.length; + const res: number[] = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('计数排序(无法排序对象)完成后 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('计数排序完成后 nums1 =', nums1); + +export {}; diff --git a/codes/typescript/chapter_sorting/heap_sort.ts b/codes/typescript/chapter_sorting/heap_sort.ts new file mode 100644 index 0000000000..c082166d12 --- /dev/null +++ b/codes/typescript/chapter_sorting/heap_sort.ts @@ -0,0 +1,51 @@ +/** + * File: heap_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +function siftDown(nums: number[], n: number, i: number): void { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let 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; + } + // 交换两节点 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // 循环向下堆化 + i = ma; + } +} + +/* 堆排序 */ +function heapSort(nums: number[]): void { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (let i = nums.length - 1; i > 0; i--) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('堆排序完成后 nums =', nums); + +export {}; diff --git a/codes/typescript/chapter_sorting/insertion_sort.ts b/codes/typescript/chapter_sorting/insertion_sort.ts index 82d913b9b6..e49932d84e 100644 --- a/codes/typescript/chapter_sorting/insertion_sort.ts +++ b/codes/typescript/chapter_sorting/insertion_sort.ts @@ -1,27 +1,27 @@ /** - * File: quick_sort.ts + * File: insertion_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ /* 插入排序 */ function insertionSort(nums: number[]): void { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] + // 外循环:已排序区间为 [0, i-1] for (let i = 1; i < nums.length; i++) { const base = nums[i]; let j = i - 1; - // 内循环:将 base 插入到左边的正确位置 + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 j--; } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 + nums[j + 1] = base; // 将 base 赋值到正确位置 } } /* Driver Code */ const nums = [4, 1, 3, 1, 5, 2]; insertionSort(nums); -console.log('排序后数组 nums =', nums); +console.log('插入排序完成后 nums =', nums); export {}; diff --git a/codes/typescript/chapter_sorting/merge_sort.ts b/codes/typescript/chapter_sorting/merge_sort.ts index a4cb5e1cb8..affef4d78f 100644 --- a/codes/typescript/chapter_sorting/merge_sort.ts +++ b/codes/typescript/chapter_sorting/merge_sort.ts @@ -1,36 +1,37 @@ /** - * File: quick_sort.ts + * File: merge_sort.ts * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) */ -/** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ +/* 合并左子数组和右子数组 */ function merge(nums: number[], left: number, mid: number, right: number): void { - // 初始化辅助数组 - let tmp = nums.slice(left, right + 1); - // 左子数组的起始索引和结束索引 - let leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - let rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - let i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (let k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) { - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if (j > rightEnd || tmp[i] <= tmp[j]) { - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + const tmp = new Array(right - left + 1); + // 初始化左子数组和右子数组的起始索引 + let i = left, + j = mid + 1, + k = 0; + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; } else { - nums[k] = tmp[j++]; + tmp[k++] = nums[j++]; } } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } } /* 归并排序 */ @@ -38,7 +39,7 @@ function mergeSort(nums: number[], left: number, right: number): void { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 - let mid = Math.floor((left + right) / 2); // 计算中点 + let mid = Math.floor(left + (right - left) / 2); // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 diff --git a/codes/typescript/chapter_sorting/quick_sort.ts b/codes/typescript/chapter_sorting/quick_sort.ts index 6379e08fc9..f3ae3d8f31 100644 --- a/codes/typescript/chapter_sorting/quick_sort.ts +++ b/codes/typescript/chapter_sorting/quick_sort.ts @@ -15,8 +15,9 @@ class QuickSort { /* 哨兵划分 */ partition(nums: number[], left: number, right: number): number { - // 以 nums[left] 作为基准数 - let i = left, j = right; + // 以 nums[left] 为基准数 + let i = left, + j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j -= 1; // 从右向左找首个小于基准数的元素 @@ -54,27 +55,37 @@ class QuickSortMedian { nums[j] = tmp; } - /* 选取三个元素的中位数 */ - medianThree(nums: number[], left: number, mid: number, right: number): number { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if (Number(nums[left] > nums[mid]) ^ Number(nums[left] > nums[right])) { - return left; - } else if (Number(nums[mid] < nums[left]) ^ Number(nums[mid] < nums[right])) { - return mid; - } else { - return right; - } + /* 选取三个候选元素的中位数 */ + medianThree( + nums: number[], + left: number, + mid: number, + right: number + ): number { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m 在 l 和 r 之间 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l 在 m 和 r 之间 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; } /* 哨兵划分(三数取中值) */ partition(nums: number[], left: number, right: number): number { // 选取三个候选元素的中位数 - let med = this.medianThree(nums, left, Math.floor((left + right) / 2), right); + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); // 将中位数交换至数组最左端 this.swap(nums, left, med); - // 以 nums[left] 作为基准数 - let i = left, j = right; + // 以 nums[left] 为基准数 + let i = left, + j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 从右向左找首个小于基准数的元素 @@ -113,8 +124,9 @@ class QuickSortTailCall { /* 哨兵划分 */ partition(nums: number[], left: number, right: number): number { - // 以 nums[left] 作为基准数 - let i = left, j = right; + // 以 nums[left] 为基准数 + let i = left, + j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) { j--; // 从右向左找首个小于基准数的元素 @@ -134,13 +146,13 @@ class QuickSortTailCall { while (left < right) { // 哨兵划分操作 let pivot = this.partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 + // 对两个子数组中较短的那个执行快速排序 if (pivot - left < right - pivot) { this.quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] } else { this.quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] } } } @@ -148,20 +160,20 @@ class QuickSortTailCall { /* Driver Code */ /* 快速排序 */ -const nums = [4, 1, 3, 1, 5, 2]; +const nums = [2, 4, 1, 0, 3, 5]; const quickSort = new QuickSort(); quickSort.quickSort(nums, 0, nums.length - 1); console.log('快速排序完成后 nums =', nums); /* 快速排序(中位基准数优化) */ -const nums1 = [4, 1, 3, 1, 5, 2]; -const quickSortMedian = new QuickSort(); +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); quickSortMedian.quickSort(nums1, 0, nums1.length - 1); console.log('快速排序(中位基准数优化)完成后 nums =', nums1); /* 快速排序(尾递归优化) */ -const nums2 = [4, 1, 3, 1, 5, 2]; -const quickSortTailCall = new QuickSort(); +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); console.log('快速排序(尾递归优化)完成后 nums =', nums2); diff --git a/codes/typescript/chapter_sorting/radix_sort.ts b/codes/typescript/chapter_sorting/radix_sort.ts new file mode 100644 index 0000000000..109a1e59f6 --- /dev/null +++ b/codes/typescript/chapter_sorting/radix_sort.ts @@ -0,0 +1,68 @@ +/** + * File: radix_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +function digit(num: number, exp: number): number { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return Math.floor(num / exp) % 10; +} + +/* 计数排序(根据 nums 第 k 位排序) */ +function countingSortDigit(nums: number[], exp: number): void { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + const counter = new Array(10).fill(0); + const n = nums.length; + // 统计 0~9 各数字的出现次数 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d]--; // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基数排序 */ +function radixSort(nums: number[]): void { + // 获取数组的最大元素,用于判断最大位数 + let m = Number.MIN_VALUE; + for (const num of nums) { + if (num > m) { + m = num; + } + } + // 按照从低位到高位的顺序遍历 + for (let exp = 1; exp <= m; exp *= 10) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基数排序完成后 nums =', nums); + +export {}; diff --git a/codes/typescript/chapter_sorting/selection_sort.ts b/codes/typescript/chapter_sorting/selection_sort.ts new file mode 100644 index 0000000000..1f0817824e --- /dev/null +++ b/codes/typescript/chapter_sorting/selection_sort.ts @@ -0,0 +1,29 @@ +/** + * File: selection_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 选择排序 */ +function selectionSort(nums: number[]): void { + let n = nums.length; + // 外循环:未排序区间为 [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 内循环:找到未排序区间内的最小元素 + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 记录最小元素的索引 + } + } + // 将该最小元素与未排序区间的首个元素交换 + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('选择排序完成后 nums =', nums); + +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/array_deque.ts b/codes/typescript/chapter_stack_and_queue/array_deque.ts new file mode 100644 index 0000000000..554731f217 --- /dev/null +++ b/codes/typescript/chapter_stack_and_queue/array_deque.ts @@ -0,0 +1,158 @@ +/** + * File: array_deque.ts + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基于环形数组实现的双向队列 */ +class ArrayDeque { + private nums: number[]; // 用于存储双向队列元素的数组 + private front: number; // 队首指针,指向队首元素 + private queSize: number; // 双向队列长度 + + /* 构造方法 */ + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = 0; + this.queSize = 0; + } + + /* 获取双向队列的容量 */ + capacity(): number { + return this.nums.length; + } + + /* 获取双向队列的长度 */ + size(): number { + return this.queSize; + } + + /* 判断双向队列是否为空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 计算环形数组索引 */ + index(i: number): number { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + this.capacity()) % this.capacity(); + } + + /* 队首入队 */ + pushFirst(num: number): void { + if (this.queSize === this.capacity()) { + console.log('双向队列已满'); + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + this.front = this.index(this.front - 1); + // 将 num 添加至队首 + this.nums[this.front] = num; + this.queSize++; + } + + /* 队尾入队 */ + pushLast(num: number): void { + if (this.queSize === this.capacity()) { + console.log('双向队列已满'); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + const rear: number = this.index(this.front + this.queSize); + // 将 num 添加至队尾 + this.nums[rear] = num; + this.queSize++; + } + + /* 队首出队 */ + popFirst(): number { + const num: number = this.peekFirst(); + // 队首指针向后移动一位 + this.front = this.index(this.front + 1); + this.queSize--; + return num; + } + + /* 队尾出队 */ + popLast(): number { + const num: number = this.peekLast(); + this.queSize--; + return num; + } + + /* 访问队首元素 */ + peekFirst(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.nums[this.front]; + } + + /* 访问队尾元素 */ + peekLast(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 计算尾元素索引 + const last = this.index(this.front + this.queSize - 1); + return this.nums[last]; + } + + /* 返回数组用于打印 */ + toArray(): number[] { + // 仅转换有效长度范围内的列表元素 + const res: number[] = []; + for (let i = 0, j = this.front; i < this.queSize; i++, j++) { + res[i] = this.nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 初始化双向队列 */ +const capacity = 5; +const deque: ArrayDeque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('双向队列 deque = [' + deque.toArray() + ']'); + +/* 访问元素 */ +const peekFirst = deque.peekFirst(); +console.log('队首元素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('队尾元素 peekLast = ' + peekLast); + +/* 元素入队 */ +deque.pushLast(4); +console.log('元素 4 队尾入队后 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('元素 1 队首入队后 deque = [' + deque.toArray() + ']'); + +/* 元素出队 */ +const popLast = deque.popLast(); +console.log( + '队尾出队元素 = ' + + popLast + + ',队尾出队后 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '队首出队元素 = ' + + popFirst + + ',队首出队后 deque = [' + + deque.toArray() + + ']' +); + +/* 获取双向队列的长度 */ +const size = deque.size(); +console.log('双向队列长度 size = ' + size); + +/* 判断双向队列是否为空 */ +const isEmpty = deque.isEmpty(); +console.log('双向队列是否为空 = ' + isEmpty); + +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/array_queue.ts b/codes/typescript/chapter_stack_and_queue/array_queue.ts index 1f67c648dd..daf31fa4b7 100644 --- a/codes/typescript/chapter_stack_and_queue/array_queue.ts +++ b/codes/typescript/chapter_stack_and_queue/array_queue.ts @@ -4,108 +4,106 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ - /* 基于环形数组实现的队列 */ class ArrayQueue { - private queue: number[]; // 用于存储队列元素的数组 - private front: number = 0; // 头指针,指向队首 - private rear: number = 0; // 尾指针,指向队尾 + 1 - private CAPACITY: number = 1e5; + private nums: number[]; // 用于存储队列元素的数组 + private front: number; // 队首指针,指向队首元素 + private queSize: number; // 队列长度 - constructor(capacity?: number) { - this.queue = new Array(capacity ?? this.CAPACITY); + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = this.queSize = 0; } /* 获取队列的容量 */ get capacity(): number { - return this.queue.length; + return this.nums.length; } /* 获取队列的长度 */ get size(): number { - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (this.capacity + this.rear - this.front) % this.capacity; + return this.queSize; } /* 判断队列是否为空 */ - empty(): boolean { - return this.rear - this.front == 0; + isEmpty(): boolean { + return this.queSize === 0; } /* 入队 */ - offer(num: number): void { - if (this.size == this.capacity) - throw new Error("队列已满"); - // 尾结点后添加 num - this.queue[this.rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - this.rear = (this.rear + 1) % this.capacity; + push(num: number): void { + if (this.size === this.capacity) { + console.log('队列已满'); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + const rear = (this.front + this.queSize) % this.capacity; + // 将 num 添加至队尾 + this.nums[rear] = num; + this.queSize++; } /* 出队 */ - poll(): number { + pop(): number { const num = this.peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 this.front = (this.front + 1) % this.capacity; + this.queSize--; return num; } /* 访问队首元素 */ peek(): number { - if (this.empty()) - throw new Error("队列为空"); - return this.queue[this.front]; + if (this.isEmpty()) throw new Error('队列为空'); + return this.nums[this.front]; } /* 返回 Array */ toArray(): number[] { - const siz = this.size; - const cap = this.capacity; // 仅转换有效长度范围内的列表元素 - const arr = new Array(siz); - for (let i = 0, j = this.front; i < siz; i++, j++) { - arr[i] = this.queue[j % cap]; + const arr = new Array(this.size); + for (let i = 0, j = this.front; i < this.size; i++, j++) { + arr[i] = this.nums[j % this.capacity]; } return arr; } } +/* Driver Code */ /* 初始化队列 */ const capacity = 10; const queue = new ArrayQueue(capacity); /* 元素入队 */ -queue.offer(1); -queue.offer(3); -queue.offer(2); -queue.offer(5); -queue.offer(4); -console.log("队列 queue = "); -console.log(queue.toArray()); +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('队列 queue =', queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); -console.log("队首元素 peek = " + peek); +console.log('队首元素 peek = ' + peek); /* 元素出队 */ -const poll = queue.poll(); -console.log("出队元素 poll = " + poll + ",出队后 queue = "); -console.log(queue.toArray()); +const pop = queue.pop(); +console.log('出队元素 pop = ' + pop + ',出队后 queue =', queue.toArray()); /* 获取队列的长度 */ const size = queue.size; -console.log("队列长度 size = " + size); +console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ -const empty = queue.empty(); -console.log("队列是否为空 = " + empty); +const isEmpty = queue.isEmpty(); +console.log('队列是否为空 = ' + isEmpty); /* 测试环形数组 */ for (let i = 0; i < 10; i++) { - queue.offer(i); - queue.poll(); - console.log("第 " + i + " 轮入队 + 出队后 queue = "); - console.log(queue.toArray()); + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 轮入队 + 出队后 queue =', queue.toArray()); } -export { }; \ No newline at end of file +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/array_stack.ts b/codes/typescript/chapter_stack_and_queue/array_stack.ts index 2b9f8468da..9ff140c57a 100644 --- a/codes/typescript/chapter_stack_and_queue/array_stack.ts +++ b/codes/typescript/chapter_stack_and_queue/array_stack.ts @@ -4,7 +4,6 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ - /* 基于数组实现的栈 */ class ArrayStack { private stack: number[]; @@ -18,7 +17,7 @@ class ArrayStack { } /* 判断栈是否为空 */ - empty(): boolean { + isEmpty(): boolean { return this.stack.length === 0; } @@ -29,15 +28,13 @@ class ArrayStack { /* 出栈 */ pop(): number | undefined { - if (this.empty()) - throw new Error('栈为空'); + if (this.isEmpty()) throw new Error('栈为空'); return this.stack.pop(); } /* 访问栈顶元素 */ top(): number | undefined { - if (this.empty()) - throw new Error('栈为空'); + if (this.isEmpty()) throw new Error('栈为空'); return this.stack[this.stack.length - 1]; } @@ -45,11 +42,9 @@ class ArrayStack { toArray() { return this.stack; } -}; - +} /* Driver Code */ - /* 初始化栈 */ const stack = new ArrayStack(); @@ -59,24 +54,24 @@ stack.push(3); stack.push(2); stack.push(5); stack.push(4); -console.log("栈 stack = "); +console.log('栈 stack = '); console.log(stack.toArray()); /* 访问栈顶元素 */ const top = stack.top(); -console.log("栈顶元素 top = " + top); +console.log('栈顶元素 top = ' + top); /* 元素出栈 */ const pop = stack.pop(); -console.log("出栈元素 pop = " + pop + ",出栈后 stack = "); +console.log('出栈元素 pop = ' + pop + ',出栈后 stack = '); console.log(stack.toArray()); /* 获取栈的长度 */ const size = stack.size; -console.log("栈的长度 size = " + size); +console.log('栈的长度 size = ' + size); /* 判断是否为空 */ -const empty = stack.empty(); -console.log("栈是否为空 = " + empty); +const isEmpty = stack.isEmpty(); +console.log('栈是否为空 = ' + isEmpty); -export { }; +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/deque.ts b/codes/typescript/chapter_stack_and_queue/deque.ts new file mode 100644 index 0000000000..41dfd50dea --- /dev/null +++ b/codes/typescript/chapter_stack_and_queue/deque.ts @@ -0,0 +1,46 @@ +/** + * File: deque.ts + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 初始化双向队列 */ +// TypeScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 +const deque: number[] = []; + +/* 元素入队 */ +deque.push(2); +deque.push(5); +deque.push(4); +// 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) +deque.unshift(3); +deque.unshift(1); +console.log('双向队列 deque = ', deque); + +/* 访问元素 */ +const peekFirst: number = deque[0]; +console.log('队首元素 peekFirst = ' + peekFirst); +const peekLast: number = deque[deque.length - 1]; +console.log('队尾元素 peekLast = ' + peekLast); + +/* 元素出队 */ +// 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) +const popFront: number = deque.shift() as number; +console.log( + '队首出队元素 popFront = ' + popFront + ',队首出队后 deque = ' + deque +); +const popBack: number = deque.pop() as number; +console.log( + '队尾出队元素 popBack = ' + popBack + ',队尾出队后 deque = ' + deque +); + +/* 获取双向队列的长度 */ +const size: number = deque.length; +console.log('双向队列长度 size = ' + size); + +/* 判断双向队列是否为空 */ +const isEmpty: boolean = size === 0; +console.log('双向队列是否为空 = ' + isEmpty); + +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts b/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts new file mode 100644 index 0000000000..dd1c50bba9 --- /dev/null +++ b/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.ts + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 双向链表节点 */ +class ListNode { + prev: ListNode; // 前驱节点引用 (指针) + next: ListNode; // 后继节点引用 (指针) + val: number; // 节点值 + + constructor(val: number) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 基于双向链表实现的双向队列 */ +class LinkedListDeque { + private front: ListNode; // 头节点 front + private rear: ListNode; // 尾节点 rear + private queSize: number; // 双向队列的长度 + + constructor() { + this.front = null; + this.rear = null; + this.queSize = 0; + } + + /* 队尾入队操作 */ + pushLast(val: number): void { + const node: ListNode = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 将 node 添加至链表尾部 + this.rear.next = node; + node.prev = this.rear; + this.rear = node; // 更新尾节点 + } + this.queSize++; + } + + /* 队首入队操作 */ + pushFirst(val: number): void { + const node: ListNode = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 将 node 添加至链表头部 + this.front.prev = node; + node.next = this.front; + this.front = node; // 更新头节点 + } + this.queSize++; + } + + /* 队尾出队操作 */ + popLast(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.rear.val; // 存储尾节点值 + // 删除尾节点 + let temp: ListNode = this.rear.prev; + if (temp !== null) { + temp.next = null; + this.rear.prev = null; + } + this.rear = temp; // 更新尾节点 + this.queSize--; + return value; + } + + /* 队首出队操作 */ + popFirst(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.front.val; // 存储尾节点值 + // 删除头节点 + let temp: ListNode = this.front.next; + if (temp !== null) { + temp.prev = null; + this.front.next = null; + } + this.front = temp; // 更新头节点 + this.queSize--; + return value; + } + + /* 访问队尾元素 */ + peekLast(): number { + return this.queSize === 0 ? null : this.rear.val; + } + + /* 访问队首元素 */ + peekFirst(): number { + return this.queSize === 0 ? null : this.front.val; + } + + /* 获取双向队列的长度 */ + size(): number { + return this.queSize; + } + + /* 判断双向队列是否为空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 打印双向队列 */ + print(): void { + const arr: number[] = []; + let temp: ListNode = this.front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 初始化双向队列 */ +const linkedListDeque: LinkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('双向队列 linkedListDeque = '); +linkedListDeque.print(); + +/* 访问元素 */ +const peekFirst: number = linkedListDeque.peekFirst(); +console.log('队首元素 peekFirst = ' + peekFirst); +const peekLast: number = linkedListDeque.peekLast(); +console.log('队尾元素 peekLast = ' + peekLast); + +/* 元素入队 */ +linkedListDeque.pushLast(4); +console.log('元素 4 队尾入队后 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('元素 1 队首入队后 linkedListDeque = '); +linkedListDeque.print(); + +/* 元素出队 */ +const popLast: number = linkedListDeque.popLast(); +console.log('队尾出队元素 = ' + popLast + ',队尾出队后 linkedListDeque = '); +linkedListDeque.print(); +const popFirst: number = linkedListDeque.popFirst(); +console.log('队首出队元素 = ' + popFirst + ',队首出队后 linkedListDeque = '); +linkedListDeque.print(); + +/* 获取双向队列的长度 */ +const size: number = linkedListDeque.size(); +console.log('双向队列长度 size = ' + size); + +/* 判断双向队列是否为空 */ +const isEmpty: boolean = linkedListDeque.isEmpty(); +console.log('双向队列是否为空 = ' + isEmpty); diff --git a/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts b/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts index 6dabdbc0c8..44cf99808b 100644 --- a/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts +++ b/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts @@ -4,12 +4,12 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ -import ListNode from "../module/ListNode" +import { ListNode } from '../modules/ListNode'; /* 基于链表实现的队列 */ class LinkedListQueue { - private front: ListNode | null; // 头结点 front - private rear: ListNode | null; // 尾结点 rear + private front: ListNode | null; // 头节点 front + private rear: ListNode | null; // 尾节点 rear private queSize: number = 0; constructor() { @@ -28,14 +28,14 @@ class LinkedListQueue { } /* 入队 */ - offer(num: number): void { - // 尾结点后添加 num + push(num: number): void { + // 在尾节点后添加 num const node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 + // 如果队列为空,则令头、尾节点都指向该节点 if (!this.front) { this.front = node; this.rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 + // 如果队列不为空,则将该节点添加到尾节点后 } else { this.rear!.next = node; this.rear = node; @@ -44,11 +44,10 @@ class LinkedListQueue { } /* 出队 */ - poll(): number { + pop(): number { const num = this.peek(); - if (!this.front) - throw new Error("队列为空") - // 删除头结点 + if (!this.front) throw new Error('队列为空'); + // 删除头节点 this.front = this.front.next; this.queSize--; return num; @@ -56,8 +55,7 @@ class LinkedListQueue { /* 访问队首元素 */ peek(): number { - if (this.size === 0) - throw new Error("队列为空"); + if (this.size === 0) throw new Error('队列为空'); return this.front!.val; } @@ -73,30 +71,32 @@ class LinkedListQueue { } } - +/* Driver Code */ /* 初始化队列 */ const queue = new LinkedListQueue(); /* 元素入队 */ -queue.offer(1); -queue.offer(3); -queue.offer(2); -queue.offer(5); -queue.offer(4); -console.log("队列 queue = " + queue.toArray()); +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('队列 queue = ' + queue.toArray()); /* 访问队首元素 */ const peek = queue.peek(); -console.log("队首元素 peek = " + peek); +console.log('队首元素 peek = ' + peek); /* 元素出队 */ -const poll = queue.poll(); -console.log("出队元素 poll = " + poll + ",出队后 queue = " + queue.toArray()); +const pop = queue.pop(); +console.log('出队元素 pop = ' + pop + ',出队后 queue = ' + queue.toArray()); /* 获取队列的长度 */ const size = queue.size; -console.log("队列长度 size = " + size); +console.log('队列长度 size = ' + size); /* 判断队列是否为空 */ const isEmpty = queue.isEmpty(); -console.log("队列是否为空 = " + isEmpty); +console.log('队列是否为空 = ' + isEmpty); + +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts b/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts index 1df4b4997b..9b8f0617e4 100644 --- a/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts +++ b/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts @@ -4,12 +4,12 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ -import ListNode from "../module/ListNode" +import { ListNode } from '../modules/ListNode'; /* 基于链表实现的栈 */ class LinkedListStack { - private stackPeek: ListNode | null; // 将头结点作为栈顶 - private stkSize: number = 0; // 栈的长度 + private stackPeek: ListNode | null; // 将头节点作为栈顶 + private stkSize: number = 0; // 栈的长度 constructor() { this.stackPeek = null; @@ -22,7 +22,7 @@ class LinkedListStack { /* 判断栈是否为空 */ isEmpty(): boolean { - return this.size == 0; + return this.size === 0; } /* 入栈 */ @@ -36,8 +36,7 @@ class LinkedListStack { /* 出栈 */ pop(): number { const num = this.peek(); - if (!this.stackPeek) - throw new Error("栈为空"); + if (!this.stackPeek) throw new Error('栈为空'); this.stackPeek = this.stackPeek.next; this.stkSize--; return num; @@ -45,8 +44,7 @@ class LinkedListStack { /* 访问栈顶元素 */ peek(): number { - if (!this.stackPeek) - throw new Error("栈为空"); + if (!this.stackPeek) throw new Error('栈为空'); return this.stackPeek.val; } @@ -62,7 +60,7 @@ class LinkedListStack { } } - +/* Driver Code */ /* 初始化栈 */ const stack = new LinkedListStack(); @@ -72,20 +70,22 @@ stack.push(3); stack.push(2); stack.push(5); stack.push(4); -console.log("栈 stack = " + stack.toArray()); +console.log('栈 stack = ' + stack.toArray()); /* 访问栈顶元素 */ const peek = stack.peek(); -console.log("栈顶元素 peek = " + peek); +console.log('栈顶元素 peek = ' + peek); /* 元素出栈 */ const pop = stack.pop(); -console.log("出栈元素 pop = " + pop + ",出栈后 stack = " + stack.toArray()); +console.log('出栈元素 pop = ' + pop + ',出栈后 stack = ' + stack.toArray()); /* 获取栈的长度 */ const size = stack.size; -console.log("栈的长度 size = " + size); +console.log('栈的长度 size = ' + size); /* 判断是否为空 */ const isEmpty = stack.isEmpty(); -console.log("栈是否为空 = " + isEmpty); +console.log('栈是否为空 = ' + isEmpty); + +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/queue.ts b/codes/typescript/chapter_stack_and_queue/queue.ts index 0b7444f643..c6b737600c 100644 --- a/codes/typescript/chapter_stack_and_queue/queue.ts +++ b/codes/typescript/chapter_stack_and_queue/queue.ts @@ -4,8 +4,9 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ +/* Driver Code */ /* 初始化队列 */ -// TypeScript 没有内置的队列,可以把 Array 当作队列来使用 +// TypeScript 没有内置的队列,可以把 Array 当作队列来使用 const queue: number[] = []; /* 元素入队 */ @@ -14,18 +15,23 @@ queue.push(3); queue.push(2); queue.push(5); queue.push(4); +console.log('队列 queue =', queue); /* 访问队首元素 */ const peek = queue[0]; +console.log('队首元素 peek =', peek); /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) -const poll = queue.shift(); +const pop = queue.shift(); +console.log('出队元素 pop =', pop, ',出队后 queue = ', queue); /* 获取队列的长度 */ const size = queue.length; +console.log('队列长度 size =', size); /* 判断队列是否为空 */ -const empty = queue.length === 0; +const isEmpty = queue.length === 0; +console.log('队列是否为空 = ', isEmpty); -export { }; +export {}; diff --git a/codes/typescript/chapter_stack_and_queue/stack.ts b/codes/typescript/chapter_stack_and_queue/stack.ts index a6d2a51885..f4b92ecf68 100644 --- a/codes/typescript/chapter_stack_and_queue/stack.ts +++ b/codes/typescript/chapter_stack_and_queue/stack.ts @@ -4,8 +4,9 @@ * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) */ +/* Driver Code */ /* 初始化栈 */ -// Typescript 没有内置的栈类,可以把 Array 当作栈来使用 +// TypeScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack: number[] = []; /* 元素入栈 */ @@ -14,17 +15,23 @@ stack.push(3); stack.push(2); stack.push(5); stack.push(4); +console.log('栈 stack =', stack); /* 访问栈顶元素 */ const peek = stack[stack.length - 1]; +console.log('栈顶元素 peek =', peek); /* 元素出栈 */ const pop = stack.pop(); +console.log('出栈元素 pop =', pop); +console.log('出栈后 stack =', stack); /* 获取栈的长度 */ const size = stack.length; +console.log('栈的长度 size =', size); /* 判断是否为空 */ -const is_empty = stack.length === 0; +const isEmpty = stack.length === 0; +console.log('栈是否为空 =', isEmpty); -export { }; \ No newline at end of file +export {}; diff --git a/codes/typescript/chapter_tree/array_binary_tree.ts b/codes/typescript/chapter_tree/array_binary_tree.ts new file mode 100644 index 0000000000..f77ceb4995 --- /dev/null +++ b/codes/typescript/chapter_tree/array_binary_tree.ts @@ -0,0 +1,151 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-09 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +type Order = 'pre' | 'in' | 'post'; + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree { + #tree: (number | null)[]; + + /* 构造方法 */ + constructor(arr: (number | null)[]) { + this.#tree = arr; + } + + /* 列表容量 */ + size(): number { + return this.#tree.length; + } + + /* 获取索引为 i 节点的值 */ + val(i: number): number | null { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + left(i: number): number { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + right(i: number): number { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 层序遍历 */ + levelOrder(): number[] { + let res = []; + // 直接遍历数组 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度优先遍历 */ + #dfs(i: number, order: Order, res: (number | null)[]): void { + // 若为空位,则返回 + if (this.val(i) === null) return; + // 前序遍历 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序遍历 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 后序遍历 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序遍历 */ + preOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序遍历 */ + inOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 后序遍历 */ + postOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 初始化二叉树 +// 这里借助了一个从数组直接生成二叉树的函数 +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n初始化二叉树\n'); +console.log('二叉树的数组表示:'); +console.log(arr); +console.log('二叉树的链表表示:'); +printTree(root); + +// 数组表示下的二叉树类 +const abt = new ArrayBinaryTree(arr); + +// 访问节点 +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n当前节点的索引为 ' + i + ' ,值为 ' + abt.val(i)); +console.log( + '其左子节点的索引为 ' + l + ' ,值为 ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + '其右子节点的索引为 ' + r + ' ,值为 ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + '其父节点的索引为 ' + p + ' ,值为 ' + (p === null ? 'null' : abt.val(p)) +); + +// 遍历树 +let res = abt.levelOrder(); +console.log('\n层序遍历为:' + res); +res = abt.preOrder(); +console.log('前序遍历为:' + res); +res = abt.inOrder(); +console.log('中序遍历为:' + res); +res = abt.postOrder(); +console.log('后序遍历为:' + res); + +export {}; diff --git a/codes/typescript/chapter_tree/avl_tree.ts b/codes/typescript/chapter_tree/avl_tree.ts new file mode 100644 index 0000000000..ba4ea84895 --- /dev/null +++ b/codes/typescript/chapter_tree/avl_tree.ts @@ -0,0 +1,222 @@ +/** + * File: avl_tree.ts + * Created Time: 2023-02-06 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* AVL 树*/ +class AVLTree { + root: TreeNode; + /* 构造方法 */ + constructor() { + this.root = null; //根节点 + } + + /* 获取节点高度 */ + height(node: TreeNode): number { + // 空节点高度为 -1 ,叶节点高度为 0 + return node === null ? -1 : node.height; + } + + /* 更新节点高度 */ + private updateHeight(node: TreeNode): void { + // 节点高度等于最高子树高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 获取平衡因子 */ + balanceFactor(node: TreeNode): number { + // 空节点平衡因子为 0 + if (node === null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return this.height(node.left) - this.height(node.right); + } + + /* 右旋操作 */ + private rightRotate(node: TreeNode): TreeNode { + const child = node.left; + const grandChild = child.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新节点高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 左旋操作 */ + private leftRotate(node: TreeNode): TreeNode { + const child = node.right; + const grandChild = child.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新节点高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + private rotate(node: TreeNode): TreeNode { + // 获取节点 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏树 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.rightRotate(node); + } else { + // 先左旋后右旋 + node.left = this.leftRotate(node.left); + return this.rightRotate(node); + } + } + // 右偏树 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.leftRotate(node); + } else { + // 先右旋后左旋 + node.right = this.rightRotate(node.right); + return this.leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + + /* 插入节点 */ + insert(val: number): void { + this.root = this.insertHelper(this.root, val); + } + + /* 递归插入节点(辅助方法) */ + private insertHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node.val) { + node.left = this.insertHelper(node.left, val); + } else if (val > node.val) { + node.right = this.insertHelper(node.right, val); + } else { + return node; // 重复节点不插入,直接返回 + } + this.updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.rotate(node); + // 返回子树的根节点 + return node; + } + + /* 删除节点 */ + remove(val: number): void { + this.root = this.removeHelper(this.root, val); + } + + /* 递归删除节点(辅助方法) */ + private removeHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return null; + /* 1. 查找节点并删除 */ + if (val < node.val) { + node.left = this.removeHelper(node.left, val); + } else if (val > node.val) { + node.right = this.removeHelper(node.right, val); + } else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child === null) { + return null; + } else { + // 子节点数量 = 1 ,直接删除 node + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = this.rotate(node); + // 返回子树的根节点 + return node; + } + + /* 查找节点 */ + search(val: number): TreeNode { + let cur = this.root; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + if (cur.val < val) { + // 目标节点在 cur 的右子树中 + cur = cur.right; + } else if (cur.val > val) { + // 目标节点在 cur 的左子树中 + cur = cur.left; + } else { + // 找到目标节点,跳出循环 + break; + } + } + // 返回目标节点 + return cur; + } +} + +function testInsert(tree: AVLTree, val: number): void { + tree.insert(val); + console.log('\n插入节点 ' + val + ' 后,AVL 树为'); + printTree(tree.root); +} + +function testRemove(tree: AVLTree, val: number): void { + tree.remove(val); + console.log('\n删除节点 ' + val + ' 后,AVL 树为'); + printTree(tree.root); +} + +/* Driver Code */ +/* 初始化空 AVL 树 */ +const avlTree = new AVLTree(); +/* 插入节点 */ +// 请关注插入节点后,AVL 树是如何保持平衡的 +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 插入重复节点 */ +testInsert(avlTree, 7); + +/* 删除节点 */ +// 请关注删除节点后,AVL 树是如何保持平衡的 +testRemove(avlTree, 8); // 删除度为 0 的节点 +testRemove(avlTree, 5); // 删除度为 1 的节点 +testRemove(avlTree, 4); // 删除度为 2 的节点 + +/* 查询节点 */ +const node = avlTree.search(7); +console.log('\n查找到的节点对象为', node, ',节点值 = ' + node.val); + +export {}; diff --git a/codes/typescript/chapter_tree/binary_search_tree.ts b/codes/typescript/chapter_tree/binary_search_tree.ts index 9adf8cd15d..c5608aed87 100644 --- a/codes/typescript/chapter_tree/binary_search_tree.ts +++ b/codes/typescript/chapter_tree/binary_search_tree.ts @@ -4,169 +4,143 @@ * Author: Justin (xiefahit@gmail.com) */ -import { TreeNode } from '../module/TreeNode'; -import { printTree } from '../module/PrintUtil'; +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; /* 二叉搜索树 */ -let root: TreeNode | null; +class BinarySearchTree { + private root: TreeNode | null; -function BinarySearchTree(nums: number[]): void { - nums.sort((a, b) => a - b); // 排序数组 - root = buildTree(nums, 0, nums.length - 1); // 构建二叉搜索树 -} - -/* 获取二叉树根结点 */ -function getRoot(): TreeNode | null { - return root; -} + /* 构造方法 */ + constructor() { + // 初始化空树 + this.root = null; + } -/* 构建二叉搜索树 */ -function buildTree(nums: number[], i: number, j: number): TreeNode | null { - if (i > j) { - return null; + /* 获取二叉树根节点 */ + getRoot(): TreeNode | null { + return this.root; } - // 将数组中间结点作为根结点 - let mid = Math.floor((i + j) / 2); - let root = new TreeNode(nums[mid]); - // 递归建立左子树和右子树 - root.left = buildTree(nums, i, mid - 1); - root.right = buildTree(nums, mid + 1, j); - return root; -} -/* 查找结点 */ -function search(num: number): TreeNode | null { - let cur = root; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - if (cur.val < num) { - cur = cur.right; // 目标结点在 root 的右子树中 - } else if (cur.val > num) { - cur = cur.left; // 目标结点在 root 的左子树中 - } else { - break; // 找到目标结点,跳出循环 + /* 查找节点 */ + search(num: number): TreeNode | null { + let cur = this.root; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 目标节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 目标节点在 cur 的左子树中 + else if (cur.val > num) cur = cur.left; + // 找到目标节点,跳出循环 + else break; } + // 返回目标节点 + return cur; } - // 返回目标结点 - return cur; -} -/* 插入结点 */ -function insert(num: number): TreeNode | null { - // 若树为空,直接提前返回 - if (root === null) { - return null; - } - let cur = root, - pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - if (cur.val === num) { - return null; // 找到重复结点,直接返回 + /* 插入节点 */ + insert(num: number): void { + // 若树为空,则初始化根节点 + if (this.root === null) { + this.root = new TreeNode(num); + return; } - pre = cur; - if (cur.val < num) { - cur = cur.right as TreeNode; // 插入位置在 root 的右子树中 - } else { - cur = cur.left as TreeNode; // 插入位置在 root 的左子树中 + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到重复节点,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子树中 + else cur = cur.left; } + // 插入节点 + const node = new TreeNode(num); + if (pre!.val < num) pre!.right = node; + else pre!.left = node; } - // 插入结点 val - let node = new TreeNode(num); - if (pre!.val < num) { - pre!.right = node; - } else { - pre!.left = node; - } - return node; -} -/* 删除结点 */ -function remove(num: number): TreeNode | null { - // 若树为空,直接提前返回 - if (root === null) { - return null; - } - let cur = root, - pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到待删除结点,跳出循环 - if (cur.val === num) { - break; + /* 删除节点 */ + remove(num: number): void { + // 若树为空,直接提前返回 + if (this.root === null) return; + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 循环查找,越过叶节点后跳出 + while (cur !== null) { + // 找到待删除节点,跳出循环 + if (cur.val === num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.val < num) cur = cur.right; + // 待删除节点在 cur 的左子树中 + else cur = cur.left; } - pre = cur; - if (cur.val < num) { - cur = cur.right as TreeNode; // 待删除结点在 root 的右子树中 - } else { - cur = cur.left as TreeNode; // 待删除结点在 root 的左子树中 + // 若无待删除节点,则直接返回 + if (cur === null) return; + // 子节点数量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + const child: TreeNode | null = + cur.left !== null ? cur.left : cur.right; + // 删除节点 cur + if (cur !== this.root) { + if (pre!.left === cur) pre!.left = child; + else pre!.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + this.root = child; + } } - } - // 若无待删除结点,则直接返回 - if (cur === null) { - return null; - } - // 子结点数量 = 0 or 1 - if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur - if (pre!.left === cur) { - pre!.left = child; - } else { - pre!.right = child; + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + let tmp: TreeNode | null = cur.right; + while (tmp!.left !== null) { + tmp = tmp!.left; + } + // 递归删除节点 tmp + this.remove(tmp!.val); + // 用 tmp 覆盖 cur + cur.val = tmp!.val; } } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - let next = getInOrderNext(cur.right); - let tmp = next!.val; - // 递归删除结点 nex - remove(next!.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - return cur; -} - -/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ -function getInOrderNext(root: TreeNode | null): TreeNode | null { - if (root === null) { - return null; - } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left !== null) { - root = root.left; - } - return root; } /* Driver Code */ /* 初始化二叉搜索树 */ -const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; -BinarySearchTree(nums); +const bst = new BinarySearchTree(); +// 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} console.log('\n初始化的二叉树为\n'); -printTree(getRoot()); +printTree(bst.getRoot()); -/* 查找结点 */ -let node = search(7); -console.log('\n查找到的结点对象为 ' + node + ',结点值 = ' + node!.val); +/* 查找节点 */ +const node = bst.search(7); +console.log( + '\n查找到的节点对象为 ' + node + ',节点值 = ' + (node ? node.val : 'null') +); -/* 插入结点 */ -node = insert(16); -console.log('\n插入结点 16 后,二叉树为\n'); -printTree(getRoot()); +/* 插入节点 */ +bst.insert(16); +console.log('\n插入节点 16 后,二叉树为\n'); +printTree(bst.getRoot()); -/* 删除结点 */ -remove(1); -console.log('\n删除结点 1 后,二叉树为\n'); -printTree(getRoot()); -remove(2); -console.log('\n删除结点 2 后,二叉树为\n'); -printTree(getRoot()); -remove(4); -console.log('\n删除结点 4 后,二叉树为\n'); -printTree(getRoot()); +/* 删除节点 */ +bst.remove(1); +console.log('\n删除节点 1 后,二叉树为\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\n删除节点 2 后,二叉树为\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\n删除节点 4 后,二叉树为\n'); +printTree(bst.getRoot()); export {}; diff --git a/codes/typescript/chapter_tree/binary_tree.ts b/codes/typescript/chapter_tree/binary_tree.ts index c7e16bd766..2d1a485aa1 100644 --- a/codes/typescript/chapter_tree/binary_tree.ts +++ b/codes/typescript/chapter_tree/binary_tree.ts @@ -4,17 +4,17 @@ * Author: Justin (xiefahit@gmail.com) */ -import { TreeNode } from '../module/TreeNode'; -import { printTree } from '../module/PrintUtil'; +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; /* 初始化二叉树 */ -// 初始化结点 +// 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); -// 构建引用指向(即指针) +// 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; @@ -22,14 +22,16 @@ n2.right = n5; console.log('\n初始化二叉树\n'); printTree(n1); -/* 插入与删除结点 */ +/* 插入与删除节点 */ const P = new TreeNode(0); -// 在 n1 -> n2 中间插入结点 P +// 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; -console.log('\n插入结点 P 后\n'); +console.log('\n插入节点 P 后\n'); printTree(n1); -// 删除结点 P +// 删除节点 P n1.left = n2; -console.log('\n删除结点 P 后\n'); +console.log('\n删除节点 P 后\n'); printTree(n1); + +export {}; diff --git a/codes/typescript/chapter_tree/binary_tree_bfs.ts b/codes/typescript/chapter_tree/binary_tree_bfs.ts index 7ec4a2a5c0..5afff7e755 100644 --- a/codes/typescript/chapter_tree/binary_tree_bfs.ts +++ b/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -4,24 +4,24 @@ * Author: Justin (xiefahit@gmail.com) */ -import { type TreeNode } from '../module/TreeNode'; -import { arrToTree } from '../module/TreeNode'; -import { printTree } from '../module/PrintUtil'; +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; /* 层序遍历 */ -function hierOrder(root: TreeNode | null): number[] { - // 初始化队列,加入根结点 +function levelOrder(root: TreeNode | null): number[] { + // 初始化队列,加入根节点 const queue = [root]; // 初始化一个列表,用于保存遍历序列 const list: number[] = []; while (queue.length) { let node = queue.shift() as TreeNode; // 队列出队 - list.push(node.val); // 保存结点 + list.push(node.val); // 保存节点值 if (node.left) { - queue.push(node.left); // 左子结点入队 + queue.push(node.left); // 左子节点入队 } if (node.right) { - queue.push(node.right); // 右子结点入队 + queue.push(node.right); // 右子节点入队 } } return list; @@ -30,10 +30,12 @@ function hierOrder(root: TreeNode | null): number[] { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); /* 层序遍历 */ -const list = hierOrder(root); -console.log('\n层序遍历的结点打印序列 = ' + list); +const list = levelOrder(root); +console.log('\n层序遍历的节点打印序列 = ' + list); + +export {}; diff --git a/codes/typescript/chapter_tree/binary_tree_dfs.ts b/codes/typescript/chapter_tree/binary_tree_dfs.ts index a61855af80..23c8437779 100644 --- a/codes/typescript/chapter_tree/binary_tree_dfs.ts +++ b/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -4,9 +4,9 @@ * Author: Justin (xiefahit@gmail.com) */ -import { type TreeNode } from '../module/TreeNode'; -import { arrToTree } from '../module/TreeNode'; -import { printTree } from '../module/PrintUtil'; +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; // 初始化列表,用于存储遍历序列 const list: number[] = []; @@ -16,7 +16,7 @@ function preOrder(root: TreeNode | null): void { if (root === null) { return; } - // 访问优先级:根结点 -> 左子树 -> 右子树 + // 访问优先级:根节点 -> 左子树 -> 右子树 list.push(root.val); preOrder(root.left); preOrder(root.right); @@ -27,7 +27,7 @@ function inOrder(root: TreeNode | null): void { if (root === null) { return; } - // 访问优先级:左子树 -> 根结点 -> 右子树 + // 访问优先级:左子树 -> 根节点 -> 右子树 inOrder(root.left); list.push(root.val); inOrder(root.right); @@ -38,7 +38,7 @@ function postOrder(root: TreeNode | null): void { if (root === null) { return; } - // 访问优先级:左子树 -> 右子树 -> 根结点 + // 访问优先级:左子树 -> 右子树 -> 根节点 postOrder(root.left); postOrder(root.right); list.push(root.val); @@ -54,14 +54,16 @@ printTree(root); /* 前序遍历 */ list.length = 0; preOrder(root); -console.log('\n前序遍历的结点打印序列 = ' + list); +console.log('\n前序遍历的节点打印序列 = ' + list); /* 中序遍历 */ list.length = 0; inOrder(root); -console.log('\n中序遍历的结点打印序列 = ' + list); +console.log('\n中序遍历的节点打印序列 = ' + list); /* 后序遍历 */ list.length = 0; postOrder(root); -console.log('\n后序遍历的结点打印序列 = ' + list); +console.log('\n后序遍历的节点打印序列 = ' + list); + +export {}; diff --git a/codes/typescript/module/ListNode.ts b/codes/typescript/module/ListNode.ts deleted file mode 100644 index fae30d48af..0000000000 --- a/codes/typescript/module/ListNode.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * File: ListNode.ts - * Created Time: 2022-12-10 - * Author: Justin (xiefahit@gmail.com) - */ - -/** - * Definition for a singly-linked list node - */ -export default class ListNode { - val: number; - next: ListNode | null; - constructor(val?: number, next?: ListNode | null) { - this.val = val === undefined ? 0 : val; - this.next = next === undefined ? null : next; - } - - /** - * Generate a linked list with an array - * @param arr - * @return - */ - arrToLinkedList(arr: number[]): ListNode | null { - const dum: ListNode = new ListNode(0); - let head = dum; - for (const val of arr) { - head.next = new ListNode(val); - head = head.next; - } - return dum.next; - } -} diff --git a/codes/typescript/module/PrintUtil.ts b/codes/typescript/module/PrintUtil.ts deleted file mode 100644 index 59f1af0e51..0000000000 --- a/codes/typescript/module/PrintUtil.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * File: PrintUtil.ts - * Created Time: 2022-12-13 - * Author: Justin (xiefahit@gmail.com) - */ - -import ListNode from './ListNode'; -import { TreeNode } from './TreeNode'; - -class Trunk { - prev: Trunk | null; - str: string; - - constructor(prev: Trunk | null, str: string) { - this.prev = prev; - this.str = str; - } -} - -/** - * Print a linked list - * @param head - */ -function printLinkedList(head: ListNode | null): void { - const list: string[] = []; - while (head !== null) { - list.push(head.val.toString()); - head = head.next; - } - console.log(list.join(' -> ')); -} - -/** - * The interface of the tree printer - * This tree printer is borrowed from TECHIE DELIGHT - * https://www.techiedelight.com/c-program-print-binary-tree/ - * @param root - */ -function printTree(root: TreeNode | null) { - printTreeHelper(root, null, false); -} - -/** - * Print a binary tree - * @param root - * @param prev - * @param isLeft - */ -function printTreeHelper(root: TreeNode | null, prev: Trunk | null, isLeft: boolean) { - if (root === null) { - return; - } - - let prev_str = ' '; - const trunk = new Trunk(prev, prev_str); - - printTreeHelper(root.right, trunk, true); - - if (prev === null) { - trunk.str = '———'; - } else if (isLeft) { - trunk.str = '/———'; - prev_str = ' |'; - } else { - trunk.str = '\\———'; - prev.str = prev_str; - } - - showTrunks(trunk); - console.log(' ' + root.val); - - if (prev) { - prev.str = prev_str; - } - trunk.str = ' |'; - - printTreeHelper(root.left, trunk, false); -} - -/** - * Helper function to print branches of the binary tree - * @param p - */ -function showTrunks(p: Trunk | null) { - if (p === null) { - return; - } - - showTrunks(p.prev); - process.stdout.write(p.str); - // ts-node to execute, we need to install type definitions for node - // solve: npm i --save-dev @types/node - // restart the vscode -} - -export { printLinkedList, printTree }; diff --git a/codes/typescript/module/TreeNode.ts b/codes/typescript/module/TreeNode.ts deleted file mode 100644 index 0e34250063..0000000000 --- a/codes/typescript/module/TreeNode.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * File: TreeNode.ts - * Created Time: 2022-12-13 - * Author: Justin (xiefahit@gmail.com) - */ - -/** - * Definition for a binary tree node. - */ -class TreeNode { - val: number; - left: TreeNode | null; - right: TreeNode | null; - - constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.left = left === undefined ? null : left; // 左子结点指针 - this.right = right === undefined ? null : right; // 右子结点指针 - } -} - -/** - * Generate a binary tree given an array - * @param arr - * @return - */ -function arrToTree(arr: (number | null)[]): TreeNode | null { - if (arr.length === 0) { - return null; - } - - const root = new TreeNode(arr[0] as number); - const queue = [root]; - let i = 0; - while (queue.length) { - let node = queue.shift() as TreeNode; - if (++i >= arr.length) break; - if (arr[i] !== null) { - node.left = new TreeNode(arr[i] as number); - queue.push(node.left); - } - if (++i >= arr.length) break; - if (arr[i] !== null) { - node.right = new TreeNode(arr[i] as number); - queue.push(node.right); - } - } - return root; -} - -export { TreeNode, arrToTree }; diff --git a/codes/typescript/modules/ListNode.ts b/codes/typescript/modules/ListNode.ts new file mode 100644 index 0000000000..b42905f585 --- /dev/null +++ b/codes/typescript/modules/ListNode.ts @@ -0,0 +1,28 @@ +/** + * File: ListNode.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 链表节点 */ +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* 将数组反序列化为链表 */ +function arrToLinkedList(arr: number[]): ListNode | null { + const dum: ListNode = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +export { ListNode, arrToLinkedList }; diff --git a/codes/typescript/modules/PrintUtil.ts b/codes/typescript/modules/PrintUtil.ts new file mode 100644 index 0000000000..2245e12fc9 --- /dev/null +++ b/codes/typescript/modules/PrintUtil.ts @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from './ListNode'; +import { TreeNode, arrToTree } from './TreeNode'; + +/* 打印链表 */ +function printLinkedList(head: ListNode | null): void { + const list: string[] = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +class Trunk { + prev: Trunk | null; + str: string; + + constructor(prev: Trunk | null, str: string) { + this.prev = prev; + this.str = str; + } +} + +/** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root: TreeNode | null) { + printTreeHelper(root, null, false); +} + +/* 打印二叉树 */ +function printTreeHelper( + root: TreeNode | null, + prev: Trunk | null, + isRight: boolean +) { + if (root === null) { + return; + } + + let prev_str = ' '; + const trunk = new Trunk(prev, prev_str); + + printTreeHelper(root.right, trunk, true); + + if (prev === null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTreeHelper(root.left, trunk, false); +} + +function showTrunks(p: Trunk | null) { + if (p === null) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* 打印堆 */ +function printHeap(arr: number[]): void { + console.log('堆的数组表示:'); + console.log(arr); + console.log('堆的树状表示:'); + const root = arrToTree(arr); + printTree(root); +} + +export { printLinkedList, printTree, printHeap }; diff --git a/codes/typescript/modules/TreeNode.ts b/codes/typescript/modules/TreeNode.ts new file mode 100644 index 0000000000..26e39f3721 --- /dev/null +++ b/codes/typescript/modules/TreeNode.ts @@ -0,0 +1,37 @@ +/** + * File: TreeNode.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 二叉树节点 */ +class TreeNode { + val: number; // 节点值 + height: number; // 节点高度 + left: TreeNode | null; // 左子节点指针 + right: TreeNode | null; // 右子节点指针 + constructor( + val?: number, + height?: number, + left?: TreeNode | null, + right?: TreeNode | null + ) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } +} + +/* 将数组反序列化为二叉树 */ +function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +export { TreeNode, arrToTree }; diff --git a/codes/typescript/modules/Vertex.ts b/codes/typescript/modules/Vertex.ts new file mode 100644 index 0000000000..98e9dc9adf --- /dev/null +++ b/codes/typescript/modules/Vertex.ts @@ -0,0 +1,33 @@ +/** + * File: Vertex.ts + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 顶点类 */ +class Vertex { + val: number; + constructor(val: number) { + this.val = val; + } + + /* 输入值列表 vals ,返回顶点列表 vets */ + public static valsToVets(vals: number[]): Vertex[] { + const vets: Vertex[] = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 输入顶点列表 vets ,返回值列表 vals */ + public static vetsToVals(vets: Vertex[]): number[] { + const vals: number[] = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +export { Vertex }; diff --git a/codes/typescript/package.json b/codes/typescript/package.json new file mode 100644 index 0000000000..8c33947d44 --- /dev/null +++ b/codes/typescript/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "scripts": { + "check": "tsc" + }, + "devDependencies": { + "@types/node": "^20.12.7", + "typescript": "^5.4.5" + } +} diff --git a/codes/typescript/tsconfig.json b/codes/typescript/tsconfig.json new file mode 100644 index 0000000000..954efb8e7f --- /dev/null +++ b/codes/typescript/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "esnext", + "moduleResolution": "node", + "types": ["@types/node"], + "noEmit": true, + "target": "esnext", + }, + "include": ["chapter_*/*.ts"], + "exclude": ["node_modules"] +} diff --git a/codes/zig/.gitignore b/codes/zig/.gitignore index 4a0641ed7f..8bc911a308 100644 --- a/codes/zig/.gitignore +++ b/codes/zig/.gitignore @@ -1,2 +1,2 @@ -zig-cache/ -zig-out/ \ No newline at end of file +zig-out/ +zig-cache/ \ No newline at end of file diff --git a/codes/zig/build.zig b/codes/zig/build.zig index a8738fae97..080f84166d 100644 --- a/codes/zig/build.zig +++ b/codes/zig/build.zig @@ -1,190 +1,221 @@ // File: build.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); -// Zig Version: 0.10.0 -// Build Command: zig build -pub fn build(b: *std.build.Builder) void { +// Zig Version: 0.11.0 +// Zig Build Command: zig build -Doptimize=ReleaseSafe +// Zig Run Command: zig build run_* -Doptimize=ReleaseSafe +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); - const mode = b.standardReleaseOptions(); + const optimize = b.standardOptimizeOption(.{}); - // Section: "Time Complexity" + const group_name_path = .{ // Source File: "chapter_computational_complexity/time_complexity.zig" - // Run Command: zig build run_time_complexity - const exe_time_complexity = b.addExecutable("time_complexity", "chapter_computational_complexity/time_complexity.zig"); - exe_time_complexity.addPackagePath("include", "include/include.zig"); - exe_time_complexity.setTarget(target); - exe_time_complexity.setBuildMode(mode); - exe_time_complexity.install(); - const run_cmd_time_complexity = exe_time_complexity.run(); - run_cmd_time_complexity.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_time_complexity.addArgs(args); - const run_step_time_complexity = b.step("run_time_complexity", "Run time_complexity"); - run_step_time_complexity.dependOn(&run_cmd_time_complexity.step); + // Run Command: zig build run_time_complexity -Doptimize=ReleaseSafe + .{ .name = "time_complexity", .path = "chapter_computational_complexity/time_complexity.zig" }, // Source File: "chapter_computational_complexity/worst_best_time_complexity.zig" - // Run Command: zig build run_worst_best_time_complexity - const exe_worst_best_time_complexity = b.addExecutable("worst_best_time_complexity", "chapter_computational_complexity/worst_best_time_complexity.zig"); - exe_worst_best_time_complexity.addPackagePath("include", "include/include.zig"); - exe_worst_best_time_complexity.setTarget(target); - exe_worst_best_time_complexity.setBuildMode(mode); - exe_worst_best_time_complexity.install(); - const run_cmd_worst_best_time_complexity = exe_worst_best_time_complexity.run(); - run_cmd_worst_best_time_complexity.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_worst_best_time_complexity.addArgs(args); - const run_step_worst_best_time_complexity = b.step("run_worst_best_time_complexity", "Run worst_best_time_complexity"); - run_step_worst_best_time_complexity.dependOn(&run_cmd_worst_best_time_complexity.step); - - // Section: "Space Complexity" + // Run Command: zig build run_worst_best_time_complexity -Doptimize=ReleaseSafe + .{ .name = "worst_best_time_complexity", .path = "chapter_computational_complexity/worst_best_time_complexity.zig" }, + // Source File: "chapter_computational_complexity/space_complexity.zig" - // Run Command: zig build run_space_complexity - const exe_space_complexity = b.addExecutable("space_complexity", "chapter_computational_complexity/space_complexity.zig"); - exe_space_complexity.addPackagePath("include", "include/include.zig"); - exe_space_complexity.setTarget(target); - exe_space_complexity.setBuildMode(mode); - exe_space_complexity.install(); - const run_cmd_space_complexity = exe_space_complexity.run(); - run_cmd_space_complexity.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_space_complexity.addArgs(args); - const run_step_space_complexity = b.step("run_space_complexity", "Run space_complexity"); - run_step_space_complexity.dependOn(&run_cmd_space_complexity.step); - - // Section: "Space Time Tradeoff" - // Source File: "chapter_computational_complexity/leetcode_two_sum.zig" - // Run Command: zig build run_leetcode_two_sum - const exe_leetcode_two_sum = b.addExecutable("leetcode_two_sum", "chapter_computational_complexity/leetcode_two_sum.zig"); - exe_leetcode_two_sum.addPackagePath("include", "include/include.zig"); - exe_leetcode_two_sum.setTarget(target); - exe_leetcode_two_sum.setBuildMode(mode); - exe_leetcode_two_sum.install(); - const run_cmd_leetcode_two_sum = exe_leetcode_two_sum.run(); - run_cmd_leetcode_two_sum.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_leetcode_two_sum.addArgs(args); - const run_step_leetcode_two_sum = b.step("run_leetcode_two_sum", "Run leetcode_two_sum"); - run_step_leetcode_two_sum.dependOn(&run_cmd_leetcode_two_sum.step); - - // Section: "Array" + // Run Command: zig build run_space_complexity -Doptimize=ReleaseSafe + .{ .name = "space_complexity", .path = "chapter_computational_complexity/space_complexity.zig" }, + + // Source File: "chapter_computational_complexity/iteration.zig" + // Run Command: zig build run_iteration -Doptimize=ReleaseFast + .{ .name = "iteration", .path = "chapter_computational_complexity/iteration.zig" }, + + // Source File: "chapter_computational_complexity/recursion.zig" + // Run Command: zig build run_recursion -Doptimize=ReleaseFast + .{ .name = "recursion", .path = "chapter_computational_complexity/recursion.zig" }, + // Source File: "chapter_array_and_linkedlist/array.zig" - // Run Command: zig build run_array - const exe_array = b.addExecutable("array", "chapter_array_and_linkedlist/array.zig"); - exe_array.addPackagePath("include", "include/include.zig"); - exe_array.setTarget(target); - exe_array.setBuildMode(mode); - exe_array.install(); - const run_cmd_array = exe_array.run(); - run_cmd_array.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_array.addArgs(args); - const run_step_array = b.step("run_array", "Run array"); - run_step_array.dependOn(&run_cmd_array.step); - - // Section: "LinkedList" + // Run Command: zig build run_array -Doptimize=ReleaseSafe + .{ .name = "array", .path = "chapter_array_and_linkedlist/array.zig" }, + // Source File: "chapter_array_and_linkedlist/linked_list.zig" - // Run Command: zig build run_linked_list - const exe_linked_list = b.addExecutable("linked_list", "chapter_array_and_linkedlist/linked_list.zig"); - exe_linked_list.addPackagePath("include", "include/include.zig"); - exe_linked_list.setTarget(target); - exe_linked_list.setBuildMode(mode); - exe_linked_list.install(); - const run_cmd_linked_list = exe_linked_list.run(); - run_cmd_linked_list.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_linked_list.addArgs(args); - const run_step_linked_list = b.step("run_linked_list", "Run linked_list"); - run_step_linked_list.dependOn(&run_cmd_linked_list.step); - - // Section: "List" + // Run Command: zig build run_linked_list -Doptimize=ReleaseSafe + .{ .name = "linked_list", .path = "chapter_array_and_linkedlist/linked_list.zig" }, + // Source File: "chapter_array_and_linkedlist/list.zig" - // Run Command: zig build run_list - const exe_list = b.addExecutable("list", "chapter_array_and_linkedlist/list.zig"); - exe_list.addPackagePath("include", "include/include.zig"); - exe_list.setTarget(target); - exe_list.setBuildMode(mode); - exe_list.install(); - const run_cmd_list = exe_list.run(); - run_cmd_list.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_list.addArgs(args); - const run_step_list = b.step("run_list", "Run list"); - run_step_list.dependOn(&run_cmd_list.step); + // Run Command: zig build run_list -Doptimize=ReleaseSafe + .{ .name = "list", .path = "chapter_array_and_linkedlist/list.zig" }, // Source File: "chapter_array_and_linkedlist/my_list.zig" - // Run Command: zig build run_my_list - const exe_my_list = b.addExecutable("my_list", "chapter_array_and_linkedlist/my_list.zig"); - exe_my_list.addPackagePath("include", "include/include.zig"); - exe_my_list.setTarget(target); - exe_my_list.setBuildMode(mode); - exe_my_list.install(); - const run_cmd_my_list = exe_my_list.run(); - run_cmd_my_list.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_my_list.addArgs(args); - const run_step_my_list = b.step("run_my_list", "Run my_list"); - run_step_my_list.dependOn(&run_cmd_my_list.step); - - // Section: "Stack" + // Run Command: zig build run_my_list -Doptimize=ReleaseSafe + .{ .name = "my_list", .path = "chapter_array_and_linkedlist/my_list.zig" }, + // Source File: "chapter_stack_and_queue/stack.zig" - // Run Command: zig build run_stack - const exe_stack = b.addExecutable("stack", "chapter_stack_and_queue/stack.zig"); - exe_stack.addPackagePath("include", "include/include.zig"); - exe_stack.setTarget(target); - exe_stack.setBuildMode(mode); - exe_stack.install(); - const run_cmd_stack = exe_stack.run(); - run_cmd_stack.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_stack.addArgs(args); - const run_step_stack = b.step("run_stack", "Run stack"); - run_step_stack.dependOn(&run_cmd_stack.step); + // Run Command: zig build run_stack -Doptimize=ReleaseSafe + .{ .name = "stack", .path = "chapter_stack_and_queue/stack.zig" }, // Source File: "chapter_stack_and_queue/linkedlist_stack.zig" - // Run Command: zig build run_linkedlist_stack - const exe_linkedlist_stack = b.addExecutable("linkedlist_stack", "chapter_stack_and_queue/linkedlist_stack.zig"); - exe_linkedlist_stack.addPackagePath("include", "include/include.zig"); - exe_linkedlist_stack.setTarget(target); - exe_linkedlist_stack.setBuildMode(mode); - exe_linkedlist_stack.install(); - const run_cmd_linkedlist_stack = exe_linkedlist_stack.run(); - run_cmd_linkedlist_stack.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_linkedlist_stack.addArgs(args); - const run_step_linkedlist_stack = b.step("run_linkedlist_stack", "Run linkedlist_stack"); - run_step_linkedlist_stack.dependOn(&run_cmd_linkedlist_stack.step); + // Run Command: zig build run_linkedlist_stack -Doptimize=ReleaseSafe + .{ .name = "linkedlist_stack", .path = "chapter_stack_and_queue/linkedlist_stack.zig" }, // Source File: "chapter_stack_and_queue/array_stack.zig" - // Run Command: zig build run_array_stack - const exe_array_stack = b.addExecutable("array_stack", "chapter_stack_and_queue/array_stack.zig"); - exe_array_stack.addPackagePath("include", "include/include.zig"); - exe_array_stack.setTarget(target); - exe_array_stack.setBuildMode(mode); - exe_array_stack.install(); - const run_cmd_array_stack = exe_linkedlist_stack.run(); - run_cmd_array_stack.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_array_stack.addArgs(args); - const run_step_array_stack = b.step("run_array_stack", "Run array_stack"); - run_step_array_stack.dependOn(&run_cmd_array_stack.step); - - // Section: "Bubble Sort" + // Run Command: zig build run_array_stack -Doptimize=ReleaseSafe + .{ .name = "array_stack", .path = "chapter_stack_and_queue/array_stack.zig" }, + + // Source File: "chapter_stack_and_queue/queue.zig" + // Run Command: zig build run_queue -Doptimize=ReleaseSafe + .{ .name = "queue", .path = "chapter_stack_and_queue/queue.zig" }, + + // Source File: "chapter_stack_and_queue/array_queue.zig" + // Run Command: zig build run_array_queue -Doptimize=ReleaseSafe + .{ .name = "array_queue", .path = "chapter_stack_and_queue/array_queue.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_queue.zig" + // Run Command: zig build run_linkedlist_queue -Doptimize=ReleaseSafe + .{ .name = "linkedlist_queue", .path = "chapter_stack_and_queue/linkedlist_queue.zig" }, + + // Source File: "chapter_stack_and_queue/deque.zig" + // Run Command: zig build run_deque -Doptimize=ReleaseSafe + .{ .name = "deque", .path = "chapter_stack_and_queue/deque.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_deque.zig" + // Run Command: zig build run_linkedlist_deque -Doptimize=ReleaseSafe + .{ .name = "linkedlist_deque", .path = "chapter_stack_and_queue/linkedlist_deque.zig" }, + + // Source File: "chapter_hashing/hash_map.zig" + // Run Command: zig build run_hash_map -Doptimize=ReleaseSafe + .{ .name = "hash_map", .path = "chapter_hashing/hash_map.zig" }, + + // Source File: "chapter_hashing/array_hash_map.zig" + // Run Command: zig build run_array_hash_map -Doptimize=ReleaseSafe + .{ .name = "array_hash_map", .path = "chapter_hashing/array_hash_map.zig" }, + + // Source File: "chapter_tree/binary_tree.zig" + // Run Command: zig build run_binary_tree -Doptimize=ReleaseSafe + .{ .name = "binary_tree", .path = "chapter_tree/binary_tree.zig" }, + + // Source File: "chapter_tree/binary_tree_bfs.zig" + // Run Command: zig build run_binary_tree_bfs -Doptimize=ReleaseSafe + .{ .name = "binary_tree_bfs", .path = "chapter_tree/binary_tree_bfs.zig" }, + + // Source File: "chapter_tree/binary_tree_dfs.zig" + // Run Command: zig build run_binary_tree_dfs -Doptimize=ReleaseSafe + .{ .name = "binary_tree_dfs", .path = "chapter_tree/binary_tree_dfs.zig" }, + + // Source File: "chapter_tree/binary_search_tree.zig" + // Run Command: zig build run_binary_search_tree -Doptimize=ReleaseSafe + .{ .name = "binary_search_tree", .path = "chapter_tree/binary_search_tree.zig" }, + + // Source File: "chapter_tree/avl_tree.zig" + // Run Command: zig build run_avl_tree -Doptimize=ReleaseSafe + .{ .name = "avl_tree", .path = "chapter_tree/avl_tree.zig" }, + + // Source File: "chapter_heap/heap.zig" + // Run Command: zig build run_heap -Doptimize=ReleaseSafe + .{ .name = "heap", .path = "chapter_heap/heap.zig" }, + + // Source File: "chapter_heap/my_heap.zig" + // Run Command: zig build run_my_heap -Doptimize=ReleaseSafe + .{ .name = "my_heap", .path = "chapter_heap/my_heap.zig" }, + + // Source File: "chapter_searching/linear_search.zig" + // Run Command: zig build run_linear_search -Doptimize=ReleaseSafe + .{ .name = "linear_search", .path = "chapter_searching/linear_search.zig" }, + + // Source File: "chapter_searching/binary_search.zig" + // Run Command: zig build run_binary_search -Doptimize=ReleaseSafe + .{ .name = "binary_search", .path = "chapter_searching/binary_search.zig" }, + + // Source File: "chapter_searching/hashing_search.zig" + // Run Command: zig build run_hashing_search -Doptimize=ReleaseSafe + .{ .name = "hashing_search", .path = "chapter_searching/hashing_search.zig" }, + + // Source File: "chapter_searching/two_sum.zig" + // Run Command: zig build run_two_sum -Doptimize=ReleaseSafe + .{ .name = "two_sum", .path = "chapter_searching/two_sum.zig" }, + // Source File: "chapter_sorting/bubble_sort.zig" - // Run Command: zig build run_bubble_sort - const exe_bubble_sort = b.addExecutable("bubble_sort", "chapter_sorting/bubble_sort.zig"); - exe_bubble_sort.addPackagePath("include", "include/include.zig"); - exe_bubble_sort.setTarget(target); - exe_bubble_sort.setBuildMode(mode); - exe_bubble_sort.install(); - const run_cmd_bubble_sort = exe_bubble_sort.run(); - run_cmd_bubble_sort.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_bubble_sort.addArgs(args); - const run_step_bubble_sort = b.step("run_bubble_sort", "Run bubble_sort"); - run_step_bubble_sort.dependOn(&run_cmd_bubble_sort.step); - - // Section: "Insertion Sort" + // Run Command: zig build run_bubble_sort -Doptimize=ReleaseSafe + .{ .name = "bubble_sort", .path = "chapter_sorting/bubble_sort.zig" }, + // Source File: "chapter_sorting/insertion_sort.zig" - // Run Command: zig build run_insertion_sort - const exe_insertion_sort = b.addExecutable("insertion_sort", "chapter_sorting/insertion_sort.zig"); - exe_insertion_sort.addPackagePath("include", "include/include.zig"); - exe_insertion_sort.setTarget(target); - exe_insertion_sort.setBuildMode(mode); - exe_insertion_sort.install(); - const run_cmd_insertion_sort = exe_insertion_sort.run(); - run_cmd_insertion_sort.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd_insertion_sort.addArgs(args); - const run_step_insertion_sort = b.step("run_insertion_sort", "Run insertion_sort"); - run_step_insertion_sort.dependOn(&run_cmd_insertion_sort.step); + // Run Command: zig build run_insertion_sort -Doptimize=ReleaseSafe + .{ .name = "insertion_sort", .path = "chapter_sorting/insertion_sort.zig" }, + + // Source File: "chapter_sorting/quick_sort.zig" + // Run Command: zig build run_quick_sort -Doptimize=ReleaseSafe + .{ .name = "quick_sort", .path = "chapter_sorting/quick_sort.zig" }, + + // Source File: "chapter_sorting/merge_sort.zig" + // Run Command: zig build run_merge_sort -Doptimize=ReleaseSafe + .{ .name = "merge_sort", .path = "chapter_sorting/merge_sort.zig" }, + + // Source File: "chapter_sorting/radix_sort.zig" + // Run Command: zig build run_radix_sort -Doptimize=ReleaseSafe + .{ .name = "radix_sort", .path = "chapter_sorting/radix_sort.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_backtrack.zig" + // Run Command: zig build run_climbing_stairs_backtrack -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_backtrack", .path = "chapter_dynamic_programming/climbing_stairs_backtrack.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_constraint_dp.zig" + // Run Command: zig build run_climbing_stairs_constraint_dp -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_constraint_dp", .path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dfs_mem.zig" + // Run Command: zig build run_climbing_stairs_dfs_mem -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dfs_mem", .path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dfs.zig" + // Run Command: zig build run_climbing_stairs_dfs -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dfs", .path = "chapter_dynamic_programming/climbing_stairs_dfs.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dp.zig" + // Run Command: zig build run_climbing_stairs_dp -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dp", .path = "chapter_dynamic_programming/climbing_stairs_dp.zig" }, + + // Source File: "chapter_dynamic_programming/coin_change_ii.zig" + // Run Command: zig build run_coin_change_ii -Doptimize=ReleaseSafe + .{ .name = "coin_change_ii", .path = "chapter_dynamic_programming/coin_change_ii.zig" }, + + // Source File: "chapter_dynamic_programming/coin_change.zig" + // Run Command: zig build run_coin_change -Doptimize=ReleaseSafe + .{ .name = "coin_change", .path = "chapter_dynamic_programming/coin_change.zig" }, + + // Source File: "chapter_dynamic_programming/edit_distance.zig" + // Run Command: zig build run_edit_distance -Doptimize=ReleaseSafe + .{ .name = "edit_distance", .path = "chapter_dynamic_programming/edit_distance.zig" }, + + // Source File: "chapter_dynamic_programming/knapsack.zig" + // Run Command: zig build run_knapsack -Doptimize=ReleaseSafe + .{ .name = "knapsack", .path = "chapter_dynamic_programming/knapsack.zig" }, + + // Source File: "chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig" + // Run Command: zig build run_min_cost_climbing_stairs_dp -Doptimize=ReleaseSafe + .{ .name = "min_cost_climbing_stairs_dp", .path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig" }, + + // Source File: "chapter_dynamic_programming/min_path_sum.zig" + // Run Command: zig build run_min_path_sum -Doptimize=ReleaseSafe + .{ .name = "min_path_sum", .path = "chapter_dynamic_programming/min_path_sum.zig" }, + + // Source File: "chapter_dynamic_programming/unbounded_knapsack.zig" + // Run Command: zig build run_unbounded_knapsack -Doptimize=ReleaseSafe + .{ .name = "unbounded_knapsack", .path = "chapter_dynamic_programming/unbounded_knapsack.zig" }, + }; + + inline for (group_name_path) |name_path| { + const exe = b.addExecutable(.{ + .name = name_path.name, + .root_source_file = .{ .path = name_path.path }, + .target = target, + .optimize = optimize, + }); + exe.addModule("include", b.addModule("", .{ + .source_file = .{ .path = "include/include.zig" }, + })); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + const run_step = b.step("run_" ++ name_path.name, "Run the app"); + run_step.dependOn(&run_cmd.step); + } } diff --git a/codes/zig/chapter_array_and_linkedlist/array.zig b/codes/zig/chapter_array_and_linkedlist/array.zig index 8c830cf4d8..a04091be00 100644 --- a/codes/zig/chapter_array_and_linkedlist/array.zig +++ b/codes/zig/chapter_array_and_linkedlist/array.zig @@ -1,11 +1,11 @@ // File: array.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); -// 随机返回一个数组元素 +// 随机访问元素 pub fn randomAccess(nums: []i32) i32 { // 在区间 [0, nums.len) 中随机抽取一个整数 var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len); @@ -18,7 +18,7 @@ pub fn randomAccess(nums: []i32) i32 { pub fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 { // 初始化一个扩展长度后的数组 var res = try mem_allocator.alloc(i32, nums.len + enlarge); - std.mem.set(i32, res, 0); + @memset(res, 0); // 将原数组中的所有元素复制到新数组 std.mem.copy(i32, res, nums); // 返回扩展后的新数组 @@ -32,11 +32,11 @@ pub fn insert(nums: []i32, num: i32, index: usize) void { while (i > index) : (i -= 1) { nums[i] = nums[i - 1]; } - // 将 num 赋给 index 处元素 + // 将 num 赋给 index 处的元素 nums[index] = num; } -// 删除索引 index 处元素 +// 删除索引 index 处的元素 pub fn remove(nums: []i32, index: usize) void { // 把索引 index 之后的所有元素向前移动一位 var i = index; @@ -51,45 +51,46 @@ pub fn traverse(nums: []i32) void { // 通过索引遍历数组 var i: i32 = 0; while (i < nums.len) : (i += 1) { - count += 1; + count += nums[i]; } count = 0; - // 直接遍历数组 - for (nums) |_| { - count += 1; + // 直接遍历数组元素 + for (nums) |num| { + count += num; } } // 在数组中查找指定元素 pub fn find(nums: []i32, target: i32) i32 { - for (nums) |num, i| { - if (num == target) return @intCast(i32, i); + for (nums, 0..) |num, i| { + if (num == target) return @intCast(i); } return -1; } // Driver Code pub fn main() !void { + // 初始化内存分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + // 初始化数组 - const size: i32 = 5; - var arr = [_]i32{0} ** size; + var arr = [_]i32{0} ** 5; std.debug.print("数组 arr = ", .{}); inc.PrintUtil.printArray(i32, &arr); var array = [_]i32{ 1, 3, 2, 5, 4 }; + var known_at_runtime_zero: usize = 0; + var nums = array[known_at_runtime_zero..]; std.debug.print("\n数组 nums = ", .{}); - inc.PrintUtil.printArray(i32, &array); + inc.PrintUtil.printArray(i32, nums); // 随机访问 - var randomNum = randomAccess(&array); + var randomNum = randomAccess(nums); std.debug.print("\n在 nums 中获取随机元素 {}", .{randomNum}); // 长度扩展 - var known_at_runtime_zero: usize = 0; - var nums: []i32 = array[known_at_runtime_zero..array.len]; - var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer mem_arena.deinit(); - const mem_allocator = mem_arena.allocator(); nums = try extend(mem_allocator, nums, 3); std.debug.print("\n将数组长度扩展至 8 ,得到 nums = ", .{}); inc.PrintUtil.printArray(i32, nums); @@ -111,7 +112,6 @@ pub fn main() !void { var index = find(nums, 3); std.debug.print("\n在 nums 中查找元素 3 ,得到索引 = {}\n", .{index}); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_array_and_linkedlist/linked_list.zig b/codes/zig/chapter_array_and_linkedlist/linked_list.zig index fbaa3f8d77..19c49ab621 100644 --- a/codes/zig/chapter_array_and_linkedlist/linked_list.zig +++ b/codes/zig/chapter_array_and_linkedlist/linked_list.zig @@ -1,18 +1,18 @@ // File: linked_list.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); -// 在链表的结点 n0 之后插入结点 P +// 在链表的节点 n0 之后插入节点 P pub fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void { var n1 = n0.?.next; - n0.?.next = P; P.?.next = n1; + n0.?.next = P; } -// 删除链表的结点 n0 之后的首个结点 +// 删除链表的节点 n0 之后的首个节点 pub fn remove(n0: ?*inc.ListNode(i32)) void { if (n0.?.next == null) return; // n0 -> P -> n1 @@ -21,7 +21,7 @@ pub fn remove(n0: ?*inc.ListNode(i32)) void { n0.?.next = n1; } -// 访问链表中索引为 index 的结点 +// 访问链表中索引为 index 的节点 pub fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) { var head = node; var i: i32 = 0; @@ -32,7 +32,7 @@ pub fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) { return head; } -// 在链表中查找值为 target 的首个结点 +// 在链表中查找值为 target 的首个节点 pub fn find(node: ?*inc.ListNode(i32), target: i32) i32 { var head = node; var index: i32 = 0; @@ -47,13 +47,13 @@ pub fn find(node: ?*inc.ListNode(i32), target: i32) i32 { // Driver Code pub fn main() !void { // 初始化链表 - // 初始化各个结点 + // 初始化各个节点 var n0 = inc.ListNode(i32){.val = 1}; var n1 = inc.ListNode(i32){.val = 3}; var n2 = inc.ListNode(i32){.val = 2}; var n3 = inc.ListNode(i32){.val = 5}; var n4 = inc.ListNode(i32){.val = 4}; - // 构建引用指向 + // 构建节点之间的引用 n0.next = &n1; n1.next = &n2; n2.next = &n3; @@ -61,25 +61,24 @@ pub fn main() !void { std.debug.print("初始化的链表为", .{}); try inc.PrintUtil.printLinkedList(i32, &n0); - // 插入结点 + // 插入节点 var tmp = inc.ListNode(i32){.val = 0}; insert(&n0, &tmp); - std.debug.print("插入结点后的链表为", .{}); + std.debug.print("插入节点后的链表为", .{}); try inc.PrintUtil.printLinkedList(i32, &n0); - // 删除结点 + // 删除节点 remove(&n0); - std.debug.print("删除结点后的链表为", .{}); + std.debug.print("删除节点后的链表为", .{}); try inc.PrintUtil.printLinkedList(i32, &n0); - // 访问结点 + // 访问节点 var node = access(&n0, 3); - std.debug.print("链表中索引 3 处的结点的值 = {}\n", .{node.?.val}); + std.debug.print("链表中索引 3 处的节点的值 = {}\n", .{node.?.val}); - // 查找结点 + // 查找节点 var index = find(&n0, 2); - std.debug.print("链表中值为 2 的结点的索引 = {}\n", .{index}); + std.debug.print("链表中值为 2 的节点的索引 = {}\n", .{index}); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } \ No newline at end of file diff --git a/codes/zig/chapter_array_and_linkedlist/list.zig b/codes/zig/chapter_array_and_linkedlist/list.zig index 608bce96ca..8615363320 100644 --- a/codes/zig/chapter_array_and_linkedlist/list.zig +++ b/codes/zig/chapter_array_and_linkedlist/list.zig @@ -1,6 +1,6 @@ // File: list.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); @@ -8,74 +8,71 @@ const inc = @import("include"); // Driver Code pub fn main() !void { // 初始化列表 - var list = std.ArrayList(i32).init(std.heap.page_allocator); + var nums = std.ArrayList(i32).init(std.heap.page_allocator); // 延迟释放内存 - defer list.deinit(); - try list.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); - std.debug.print("列表 list = ", .{}); - inc.PrintUtil.printList(i32, list); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("列表 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); // 访问元素 - var num = list.items[1]; + var num = nums.items[1]; std.debug.print("\n访问索引 1 处的元素,得到 num = {}", .{num}); // 更新元素 - list.items[1] = 0; - std.debug.print("\n将索引 1 处的元素更新为 0 ,得到 list = ", .{}); - inc.PrintUtil.printList(i32, list); + nums.items[1] = 0; + std.debug.print("\n将索引 1 处的元素更新为 0 ,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); // 清空列表 - list.clearRetainingCapacity(); - std.debug.print("\n清空列表后 list = ", .{}); - inc.PrintUtil.printList(i32, list); + nums.clearRetainingCapacity(); + std.debug.print("\n清空列表后 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); - // 尾部添加元素 - try list.append(1); - try list.append(3); - try list.append(2); - try list.append(5); - try list.append(4); - std.debug.print("\n添加元素后 list = ", .{}); - inc.PrintUtil.printList(i32, list); + // 在尾部添加元素 + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + std.debug.print("\n添加元素后 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); - // 中间插入元素 - try list.insert(3, 6); - std.debug.print("\n在索引 3 处插入数字 6 ,得到 list = ", .{}); - inc.PrintUtil.printList(i32, list); + // 在中间插入元素 + try nums.insert(3, 6); + std.debug.print("\n在索引 3 处插入数字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); // 删除元素 - var value = list.orderedRemove(3); - _ = value; - std.debug.print("\n删除索引 3 处的元素,得到 list = ", .{}); - inc.PrintUtil.printList(i32, list); + _ = nums.orderedRemove(3); + std.debug.print("\n删除索引 3 处的元素,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); // 通过索引遍历列表 var count: i32 = 0; var i: i32 = 0; - while (i < list.items.len) : (i += 1) { - count += 1; + while (i < nums.items.len) : (i += 1) { + count += nums[i]; } - // 直接遍历列表元素 count = 0; - for (list.items) |_| { - count += 1; + for (nums.items) |x| { + count += x; } // 拼接两个列表 - var list1 = std.ArrayList(i32).init(std.heap.page_allocator); - defer list1.deinit(); - try list1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); - try list.insertSlice(list.items.len, list1.items); - std.debug.print("\n将列表 list1 拼接到 list 之后,得到 list = ", .{}); - inc.PrintUtil.printList(i32, list); + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); + std.debug.print("\n将列表 nums1 拼接到 nums 之后,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); // 排序列表 - std.sort.sort(i32, list.items, {}, comptime std.sort.asc(i32)); - std.debug.print("\n排序列表后 list = ", .{}); - inc.PrintUtil.printList(i32, list); + std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + std.debug.print("\n排序列表后 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_array_and_linkedlist/my_list.zig b/codes/zig/chapter_array_and_linkedlist/my_list.zig index f018e61ca0..0b99e94180 100644 --- a/codes/zig/chapter_array_and_linkedlist/my_list.zig +++ b/codes/zig/chapter_array_and_linkedlist/my_list.zig @@ -1,22 +1,21 @@ // File: my_list.zig // Created Time: 2023-01-08 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); -// 列表类简易实现 -// 编译期泛型 +// 列表类 pub fn MyList(comptime T: type) type { return struct { const Self = @This(); - nums: []T = undefined, // 数组(存储列表元素) - numsCapacity: usize = 10, // 列表容量 - numSize: usize = 0, // 列表长度(即当前元素数量) - extendRatio: usize = 2, // 每次列表扩容的倍数 + arr: []T = undefined, // 数组(存储列表元素) + arrCapacity: usize = 10, // 列表容量 + numSize: usize = 0, // 列表长度(当前元素数量) + extendRatio: usize = 2, // 每次列表扩容的倍数 mem_arena: ?std.heap.ArenaAllocator = null, - mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造函数(分配内存+初始化列表) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { @@ -24,8 +23,8 @@ pub fn MyList(comptime T: type) type { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } - self.nums = try self.mem_allocator.alloc(T, self.numsCapacity); - std.mem.set(T, self.nums, @as(T, 0)); + self.arr = try self.mem_allocator.alloc(T, self.arrCapacity); + @memset(self.arr, @as(T, 0)); } // 析构函数(释放内存) @@ -34,91 +33,91 @@ pub fn MyList(comptime T: type) type { self.mem_arena.?.deinit(); } - // 获取列表长度(即当前元素数量) + // 获取列表长度(当前元素数量) pub fn size(self: *Self) usize { return self.numSize; } // 获取列表容量 pub fn capacity(self: *Self) usize { - return self.numsCapacity; + return self.arrCapacity; } // 访问元素 pub fn get(self: *Self, index: usize) T { - // 索引如果越界则抛出异常,下同 - if (index >= self.size()) @panic("索引越界"); - return self.nums[index]; + // 索引如果越界,则抛出异常,下同 + if (index < 0 or index >= self.size()) @panic("索引越界"); + return self.arr[index]; } // 更新元素 pub fn set(self: *Self, index: usize, num: T) void { - // 索引如果越界则抛出异常,下同 - if (index >= self.size()) @panic("索引越界"); - self.nums[index] = num; + // 索引如果越界,则抛出异常,下同 + if (index < 0 or index >= self.size()) @panic("索引越界"); + self.arr[index] = num; } - // 尾部添加元素 + // 在尾部添加元素 pub fn add(self: *Self, num: T) !void { // 元素数量超出容量时,触发扩容机制 if (self.size() == self.capacity()) try self.extendCapacity(); - self.nums[self.size()] = num; + self.arr[self.size()] = num; // 更新元素数量 self.numSize += 1; } - // 中间插入元素 + // 在中间插入元素 pub fn insert(self: *Self, index: usize, num: T) !void { - if (index >= self.size()) @panic("索引越界"); + if (index < 0 or index >= self.size()) @panic("索引越界"); // 元素数量超出容量时,触发扩容机制 if (self.size() == self.capacity()) try self.extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 + // 将索引 index 以及之后的元素都向后移动一位 var j = self.size() - 1; while (j >= index) : (j -= 1) { - self.nums[j + 1] = self.nums[j]; + self.arr[j + 1] = self.arr[j]; } - self.nums[index] = num; + self.arr[index] = num; // 更新元素数量 self.numSize += 1; } // 删除元素 pub fn remove(self: *Self, index: usize) T { - if (index >= self.size()) @panic("索引越界"); - var num = self.nums[index]; - // 索引 i 之后的元素都向前移动一位 + if (index < 0 or index >= self.size()) @panic("索引越界"); + var num = self.arr[index]; + // 将索引 index 之后的元素都向前移动一位 var j = index; while (j < self.size() - 1) : (j += 1) { - self.nums[j] = self.nums[j + 1]; + self.arr[j] = self.arr[j + 1]; } // 更新元素数量 self.numSize -= 1; - // 返回被删除元素 + // 返回被删除的元素 return num; } // 列表扩容 pub fn extendCapacity(self: *Self) !void { - // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组 + // 新建一个长度为 size * extendRatio 的数组,并将原数组复制到新数组 var newCapacity = self.capacity() * self.extendRatio; var extend = try self.mem_allocator.alloc(T, newCapacity); - std.mem.set(T, extend, @as(T, 0)); + @memset(extend, @as(T, 0)); // 将原数组中的所有元素复制到新数组 - std.mem.copy(T, extend, self.nums); - self.nums = extend; + std.mem.copy(T, extend, self.arr); + self.arr = extend; // 更新列表容量 - self.numsCapacity = newCapacity; + self.arrCapacity = newCapacity; } // 将列表转换为数组 pub fn toArray(self: *Self) ![]T { // 仅转换有效长度范围内的列表元素 - var nums = try self.mem_allocator.alloc(T, self.size()); - std.mem.set(T, nums, @as(T, 0)); - for (nums) |*num, i| { + var arr = try self.mem_allocator.alloc(T, self.size()); + @memset(arr, @as(T, 0)); + for (arr, 0..) |*num, i| { num.* = self.get(i); } - return nums; + return arr; } }; } @@ -126,52 +125,49 @@ pub fn MyList(comptime T: type) type { // Driver Code pub fn main() !void { // 初始化列表 - var list = MyList(i32){}; - try list.init(std.heap.page_allocator); + var nums = MyList(i32){}; + try nums.init(std.heap.page_allocator); // 延迟释放内存 - defer list.deinit(); - - // 尾部添加元素 - try list.add(1); - try list.add(3); - try list.add(2); - try list.add(5); - try list.add(4); - std.debug.print("列表 list = ", .{}); - inc.PrintUtil.printArray(i32, try list.toArray()); - std.debug.print(" ,容量 = {} ,长度 = {}", .{list.capacity(), list.size()}); - - // 中间插入元素 - try list.insert(3, 6); - std.debug.print("\n在索引 3 处插入数字 6 ,得到 list = ", .{}); - inc.PrintUtil.printArray(i32, try list.toArray()); + defer nums.deinit(); + + // 在尾部添加元素 + try nums.add(1); + try nums.add(3); + try nums.add(2); + try nums.add(5); + try nums.add(4); + std.debug.print("列表 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + std.debug.print(" ,容量 = {} ,长度 = {}", .{nums.capacity(), nums.size()}); + + // 在中间插入元素 + try nums.insert(3, 6); + std.debug.print("\n在索引 3 处插入数字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); // 删除元素 - _ = list.remove(3); - std.debug.print("\n删除索引 3 处的元素,得到 list = ", .{}); - inc.PrintUtil.printArray(i32, try list.toArray()); + _ = nums.remove(3); + std.debug.print("\n删除索引 3 处的元素,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); // 访问元素 - var num = list.get(1); + var num = nums.get(1); std.debug.print("\n访问索引 1 处的元素,得到 num = {}", .{num}); // 更新元素 - list.set(1, 0); - std.debug.print("\n将索引 1 处的元素更新为 0 ,得到 list = ", .{}); - inc.PrintUtil.printArray(i32, try list.toArray()); + nums.set(1, 0); + std.debug.print("\n将索引 1 处的元素更新为 0 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); // 测试扩容机制 - list.set(1, 0); var i: i32 = 0; while (i < 10) : (i += 1) { // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 - try list.add(i); + try nums.add(i); } - std.debug.print("\n扩容后的列表 list = ", .{}); - inc.PrintUtil.printArray(i32, try list.toArray()); - std.debug.print(" ,容量 = {} ,长度 = {}\n", .{list.capacity(), list.size()}); + std.debug.print("\n扩容后的列表 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + std.debug.print(" ,容量 = {} ,长度 = {}\n", .{nums.capacity(), nums.size()}); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } - diff --git a/codes/zig/chapter_computational_complexity/iteration.zig b/codes/zig/chapter_computational_complexity/iteration.zig new file mode 100644 index 0000000000..14e2426e4a --- /dev/null +++ b/codes/zig/chapter_computational_complexity/iteration.zig @@ -0,0 +1,77 @@ +// File: iteration.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com) + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +// for 循环 +fn forLoop(n: usize) i32 { + var res: i32 = 0; + // 循环求和 1, 2, ..., n-1, n + for (1..n+1) |i| { + res = res + @as(i32, @intCast(i)); + } + return res; +} + +// while 循环 +fn whileLoop(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += @intCast(i); + i += 1; + } + return res; +} + +// while 循环(两次更新) +fn whileLoopII(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += @intCast(i); + // 更新条件变量 + i += 1; + i *= 2; + } + return res; +} + +// 双层 for 循环 +fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { + var res = std.ArrayList(u8).init(allocator); + defer res.deinit(); + var buffer: [20]u8 = undefined; + // 循环 i = 1, 2, ..., n-1, n + for (1..n+1) |i| { + // 循环 j = 1, 2, ..., n-1, n + for (1..n+1) |j| { + var _str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{i, j}); + try res.appendSlice(_str); + } + } + return res.toOwnedSlice(); +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + var res: i32 = 0; + + res = forLoop(n); + std.debug.print("\nfor 循环的求和结果 res = {}\n", .{res}); + + res = whileLoop(n); + std.debug.print("\nwhile 循环的求和结果 res = {}\n", .{res}); + + res = whileLoopII(n); + std.debug.print("\nwhile 循环(两次更新)求和结果 res = {}\n", .{res}); + + const allocator = std.heap.page_allocator; + const resStr = try nestedForLoop(allocator, n); + std.debug.print("\n双层 for 循环的遍历结果 {s}\n", .{resStr}); +} diff --git a/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig b/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig deleted file mode 100644 index 66b96f95e1..0000000000 --- a/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig +++ /dev/null @@ -1,61 +0,0 @@ -// File: leetcode_two_sum.zig -// Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) - -const std = @import("std"); -const inc = @import("include"); - -const SolutionBruteForce = struct { - pub fn twoSum(self: *SolutionBruteForce, nums: []i32, target: i32) [2]i32 { - _ = self; - var size: usize = nums.len; - var i: usize = 0; - // 两层循环,时间复杂度 O(n^2) - while (i < size - 1) : (i += 1) { - var j = i + 1; - while (j < size) : (j += 1) { - if (nums[i] + nums[j] == target) { - return [_]i32{@intCast(i32, i), @intCast(i32, j)}; - } - } - } - return undefined; - } -}; - -const SolutionHashMap = struct { - pub fn twoSum(self: *SolutionHashMap, nums: []i32, target: i32) ![2]i32 { - _ = self; - var size: usize = nums.len; - // 辅助哈希表,空间复杂度 O(n) - var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); - defer dic.deinit(); - var i: usize = 0; - // 单层循环,时间复杂度 O(n) - while (i < size) : (i += 1) { - if (dic.contains(target - nums[i])) { - return [_]i32{dic.get(target - nums[i]).?, @intCast(i32, i)}; - } - try dic.put(nums[i], @intCast(i32, i)); - } - return undefined; - } -}; - -// Driver Code -pub fn main() !void { - // ======= Test Case ======= - var nums = [_]i32{ 2, 7, 11, 15 }; - var target: i32 = 9; - // 方法一 - var slt1 = SolutionBruteForce{}; - var res = slt1.twoSum(&nums, target); - std.debug.print("方法一 res = ", .{}); - inc.PrintUtil.printArray(i32, &res); - // 方法二 - var slt2 = SolutionHashMap{}; - res = try slt2.twoSum(&nums, target); - std.debug.print("方法二 res = ", .{}); - inc.PrintUtil.printArray(i32, &res); -} - diff --git a/codes/zig/chapter_computational_complexity/recursion.zig b/codes/zig/chapter_computational_complexity/recursion.zig new file mode 100644 index 0000000000..d0f586c78b --- /dev/null +++ b/codes/zig/chapter_computational_complexity/recursion.zig @@ -0,0 +1,78 @@ +// File: recursion.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com) + +const std = @import("std"); + +// 递归函数 +fn recur(n: i32) i32 { + // 终止条件 + if (n == 1) { + return 1; + } + // 递:递归调用 + var res: i32 = recur(n - 1); + // 归:返回结果 + return n + res; +} + +// 使用迭代模拟递归 +fn forLoopRecur(comptime n: i32) i32 { + // 使用一个显式的栈来模拟系统调用栈 + var stack: [n]i32 = undefined; + var res: i32 = 0; + // 递:递归调用 + var i: usize = n; + while (i > 0) { + stack[i - 1] = @intCast(i); + i -= 1; + } + // 归:返回结果 + var index: usize = n; + while (index > 0) { + index -= 1; + res += stack[index]; + } + // res = 1+2+3+...+n + return res; +} + +// 尾递归函数 +fn tailRecur(n: i32, res: i32) i32 { + // 终止条件 + if (n == 0) { + return res; + } + // 尾递归调用 + return tailRecur(n - 1, res + n); +} + +// 斐波那契数列 +fn fib(n: i32) i32 { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 or n == 2) { + return n - 1; + } + // 递归调用 f(n) = f(n-1) + f(n-2) + var res: i32 = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + var res: i32 = 0; + + res = recur(n); + std.debug.print("\n递归函数的求和结果 res = {}\n", .{recur(n)}); + + res = forLoopRecur(n); + std.debug.print("\n使用迭代模拟递归的求和结果 res = {}\n", .{forLoopRecur(n)}); + + res = tailRecur(n, 0); + std.debug.print("\n尾递归函数的求和结果 res = {}\n", .{tailRecur(n, 0)}); + + res = fib(n); + std.debug.print("\n斐波那契数列的第 {} 项为 {}\n", .{n, fib(n)}); +} diff --git a/codes/zig/chapter_computational_complexity/space_complexity.zig b/codes/zig/chapter_computational_complexity/space_complexity.zig index 9798821a57..3ce186498d 100644 --- a/codes/zig/chapter_computational_complexity/space_complexity.zig +++ b/codes/zig/chapter_computational_complexity/space_complexity.zig @@ -1,13 +1,13 @@ // File: space_complexity.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 函数 fn function() i32 { - // do something + // 执行某些操作 return 0; } @@ -120,6 +120,5 @@ pub fn main() !void { }; try inc.PrintUtil.printTree(root, null, false); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } \ No newline at end of file diff --git a/codes/zig/chapter_computational_complexity/time_complexity.zig b/codes/zig/chapter_computational_complexity/time_complexity.zig index 0479500527..3d2ea188b9 100644 --- a/codes/zig/chapter_computational_complexity/time_complexity.zig +++ b/codes/zig/chapter_computational_complexity/time_complexity.zig @@ -1,6 +1,6 @@ // File: time_complexity.zig // Created Time: 2022-12-28 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); @@ -40,7 +40,7 @@ fn arrayTraversal(nums: []i32) i32 { fn quadratic(n: i32) i32 { var count: i32 = 0; var i: i32 = 0; - // 循环次数与数组长度成平方关系 + // 循环次数与数据大小 n 成平方关系 while (i < n) : (i += 1) { var j: i32 = 0; while (j < n) : (j += 1) { @@ -53,11 +53,11 @@ fn quadratic(n: i32) i32 { // 平方阶(冒泡排序) fn bubbleSort(nums: []i32) i32 { var count: i32 = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - var i: i32 = @intCast(i32, nums.len ) - 1; + // 外循环:未排序区间为 [0, i] + var i: i32 = @as(i32, @intCast(nums.len)) - 1; while (i > 0) : (i -= 1) { var j: usize = 0; - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -72,11 +72,11 @@ fn bubbleSort(nums: []i32) i32 { } // 指数阶(循环实现) -fn exponential(n: i32) i32{ +fn exponential(n: i32) i32 { var count: i32 = 0; var bas: i32 = 1; var i: i32 = 0; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) while (i < n) : (i += 1) { var j: i32 = 0; while (j < bas) : (j += 1) { @@ -89,14 +89,13 @@ fn exponential(n: i32) i32{ } // 指数阶(递归实现) -fn expRecur(n: i32) i32{ +fn expRecur(n: i32) i32 { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } // 对数阶(循环实现) -fn logarithmic(n: f32) i32 -{ +fn logarithmic(n: i32) i32 { var count: i32 = 0; var n_var = n; while (n_var > 1) @@ -108,19 +107,16 @@ fn logarithmic(n: f32) i32 } // 对数阶(递归实现) -fn logRecur(n: f32) i32 -{ +fn logRecur(n: i32) i32 { if (n <= 1) return 0; return logRecur(n / 2) + 1; } // 线性对数阶 -fn linearLogRecur(n: f32) i32 -{ +fn linearLogRecur(n: i32) i32 { if (n <= 1) return 1; - var count: i32 = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - var i: f32 = 0; + var count: i32 = linearLogRecur(n / 2) + linearLogRecur(n / 2); + var i: i32 = 0; while (i < n) : (i += 1) { count += 1; } @@ -146,36 +142,38 @@ pub fn main() !void { std.debug.print("输入数据大小 n = {}\n", .{n}); var count = constant(n); - std.debug.print("常数阶的计算操作数量 = {}\n", .{count}); + std.debug.print("常数阶的操作数量 = {}\n", .{count}); count = linear(n); - std.debug.print("线性阶的计算操作数量 = {}\n", .{count}); + std.debug.print("线性阶的操作数量 = {}\n", .{count}); var nums = [_]i32{0}**n; count = arrayTraversal(&nums); - std.debug.print("线性阶(遍历数组)的计算操作数量 = {}\n", .{count}); + std.debug.print("线性阶(遍历数组)的操作数量 = {}\n", .{count}); count = quadratic(n); - std.debug.print("平方阶的计算操作数量 = {}\n", .{count}); - for (nums) |*num, i| { - num.* = n - @intCast(i32, i); // [n,n-1,...,2,1] + std.debug.print("平方阶的操作数量 = {}\n", .{count}); + for (&nums, 0..) |*num, i| { + num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] } count = bubbleSort(&nums); - std.debug.print("平方阶(冒泡排序)的计算操作数量 = {}\n", .{count}); + std.debug.print("平方阶(冒泡排序)的操作数量 = {}\n", .{count}); count = exponential(n); - std.debug.print("指数阶(循环实现)的计算操作数量 = {}\n", .{count}); + std.debug.print("指数阶(循环实现)的操作数量 = {}\n", .{count}); count = expRecur(n); - std.debug.print("指数阶(递归实现)的计算操作数量 = {}\n", .{count}); + std.debug.print("指数阶(递归实现)的操作数量 = {}\n", .{count}); - count = logarithmic(@as(f32, n)); - std.debug.print("对数阶(循环实现)的计算操作数量 = {}\n", .{count}); - count = logRecur(@as(f32, n)); - std.debug.print("对数阶(递归实现)的计算操作数量 = {}\n", .{count}); + count = logarithmic(n); + std.debug.print("对数阶(循环实现)的操作数量 = {}\n", .{count}); + count = logRecur(n); + std.debug.print("对数阶(递归实现)的操作数量 = {}\n", .{count}); - count = linearLogRecur(@as(f32, n)); - std.debug.print("线性对数阶(递归实现)的计算操作数量 = {}\n", .{count}); + count = linearLogRecur(n); + std.debug.print("线性对数阶(递归实现)的操作数量 = {}\n", .{count}); count = factorialRecur(n); - std.debug.print("阶乘阶(递归实现)的计算操作数量 = {}\n", .{count}); + std.debug.print("阶乘阶(递归实现)的操作数量 = {}\n", .{count}); + + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig b/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig index 0cdd61ebc9..efc2361026 100644 --- a/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig +++ b/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig @@ -1,6 +1,6 @@ // File: worst_best_time_complexity.zig // Created Time: 2022-12-28 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); @@ -9,8 +9,8 @@ const inc = @import("include"); pub fn randomNumbers(comptime n: usize) [n]i32 { var nums: [n]i32 = undefined; // 生成数组 nums = { 1, 2, 3, ..., n } - for (nums) |*num, i| { - num.* = @intCast(i32, i) + 1; + for (&nums, 0..) |*num, i| { + num.* = @as(i32, @intCast(i)) + 1; } // 随机打乱数组元素 const rand = std.crypto.random; @@ -20,8 +20,10 @@ pub fn randomNumbers(comptime n: usize) [n]i32 { // 查找数组 nums 中数字 1 所在索引 pub fn findOne(nums: []i32) i32 { - for (nums) |num, i| { - if (num == 1) return @intCast(i32, i); + for (nums, 0..) |num, i| { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if (num == 1) return @intCast(i); } return -1; } @@ -37,5 +39,7 @@ pub fn main() !void { inc.PrintUtil.printArray(i32, &nums); std.debug.print("数字 1 的索引为 {}\n", .{index}); } + + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig b/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig new file mode 100644 index 0000000000..ec3efc9892 --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig @@ -0,0 +1,44 @@ +// File: climbing_stairs_backtrack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 回溯 +fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) { + res.items[0] = res.items[0] + 1; + } + // 遍历所有选择 + for (choices) |choice| { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) { + continue; + } + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +// 爬楼梯:回溯 +fn climbingStairsBacktrack(n: usize) !i32 { + var choices = [_]i32{ 1, 2 }; // 可选择向上爬 1 阶或 2 阶 + var state: i32 = 0; // 从第 0 阶开始爬 + var res = std.ArrayList(i32).init(std.heap.page_allocator); + defer res.deinit(); + try res.append(0); // 使用 res[0] 记录方案数量 + backtrack(&choices, state, @intCast(n), res); + return res.items[0]; +} + +// Driver Code +pub fn main() !void { + var n: usize = 9; + + var res = try climbingStairsBacktrack(n); + std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig b/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig new file mode 100644 index 0000000000..d3628e2369 --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig @@ -0,0 +1,35 @@ +// File: climbing_stairs_constraint_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 带约束爬楼梯:动态规划 +fn climbingStairsConstraintDP(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsConstraintDP(n); + std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig b/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig new file mode 100644 index 0000000000..568a4471de --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig @@ -0,0 +1,31 @@ +// File: climbing_stairs_dfs.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 搜索 +fn dfs(i: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1) + dfs(i - 2); + return count; +} + +// 爬楼梯:搜索 +fn climbingStairsDFS(comptime n: usize) i32 { + return dfs(n); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFS(n); + std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig b/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig new file mode 100644 index 0000000000..aa1b0006ae --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig @@ -0,0 +1,39 @@ +// File: climbing_stairs_dfs_mem.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 记忆化搜索 +fn dfs(i: usize, mem: []i32) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // 若存在记录 dp[i] ,则直接返回之 + if (mem[i] != -1) { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; +} + +// 爬楼梯:记忆化搜索 +fn climbingStairsDFSMem(comptime n: usize) i32 { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + var mem = [_]i32{ -1 } ** (n + 1); + return dfs(n, &mem); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFSMem(n); + std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig b/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig new file mode 100644 index 0000000000..606777f435 --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig @@ -0,0 +1,51 @@ +// File: climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 爬楼梯:动态规划 +fn climbingStairsDP(comptime n: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (n == 1 or n == 2) { + return @intCast(n); + } + // 初始化 dp 表,用于存储子问题的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +// 爬楼梯:空间优化后的动态规划 +fn climbingStairsDPComp(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return @intCast(n); + } + var a: i32 = 1; + var b: i32 = 2; + for (3..n + 1) |_| { + var tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDP(n); + std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); + + res = climbingStairsDPComp(n); + std.debug.print("爬 {} 阶楼梯共有 {} 种方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/coin_change.zig b/codes/zig/chapter_dynamic_programming/coin_change.zig new file mode 100644 index 0000000000..9da872a3db --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/coin_change.zig @@ -0,0 +1,77 @@ +// File: coin_change.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 零钱兑换:动态规划 +fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 状态转移:首行首列 + for (1..amt + 1) |a| { + dp[0][a] = max; + } + // 状态转移:其余行和列 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[n][amt] != max) { + return @intCast(dp[n][amt]); + } else { + return -1; + } +} + +// 零钱兑换:空间优化后的动态规划 +fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + @memset(&dp, max); + dp[0] = 0; + // 状态转移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[amt] != max) { + return @intCast(dp[amt]); + } else { + return -1; + } +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 4; + + // 动态规划 + var res = coinChangeDP(&coins, amt); + std.debug.print("凑到目标金额所需的最少硬币数量为 {}\n", .{res}); + + // 空间优化后的动态规划 + res = coinChangeDPComp(&coins, amt); + std.debug.print("凑到目标金额所需的最少硬币数量为 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/coin_change_ii.zig b/codes/zig/chapter_dynamic_programming/coin_change_ii.zig new file mode 100644 index 0000000000..5ecad22c00 --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/coin_change_ii.zig @@ -0,0 +1,66 @@ +// File: coin_change_ii.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 零钱兑换 II:动态规划 +fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 初始化首列 + for (0..n + 1) |i| { + dp[i][0] = 1; + } + // 状态转移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[n][amt]; +} + +// 零钱兑换 II:空间优化后的动态规划 +fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + dp[0] = 1; + // 状态转移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[amt]; +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 5; + + // 动态规划 + var res = coinChangeIIDP(&coins, amt); + std.debug.print("凑出目标金额的硬币组合数量为 {}\n", .{res}); + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(&coins, amt); + std.debug.print("凑出目标金额的硬币组合数量为 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/edit_distance.zig b/codes/zig/chapter_dynamic_programming/edit_distance.zig new file mode 100644 index 0000000000..c85eff4782 --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/edit_distance.zig @@ -0,0 +1,146 @@ +// File: edit_distance.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 编辑距离:暴力搜索 +fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 and j == 0) { + return 0; + } + // 若 s 为空,则返回 t 长度 + if (i == 0) { + return @intCast(j); + } + // 若 t 为空,则返回 s 长度 + if (j == 0) { + return @intCast(i); + } + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) { + return editDistanceDFS(s, t, i - 1, j - 1); + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + var insert = editDistanceDFS(s, t, i, j - 1); + var delete = editDistanceDFS(s, t, i - 1, j); + var replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return @min(@min(insert, delete), replace) + 1; +} + +// 编辑距离:记忆化搜索 +fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 and j == 0) { + return 0; + } + // 若 s 为空,则返回 t 长度 + if (i == 0) { + return @intCast(j); + } + // 若 t 为空,则返回 s 长度 + if (j == 0) { + return @intCast(i); + } + // 若已有记录,则直接返回之 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) { + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + } + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + var insert = editDistanceDFSMem(s, t, mem, i, j - 1); + var delete = editDistanceDFSMem(s, t, mem, i - 1, j); + var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = @min(@min(insert, delete), replace) + 1; + return mem[i][j]; +} + +// 编辑距离:动态规划 +fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); + // 状态转移:首行首列 + for (1..n + 1) |i| { + dp[i][0] = @intCast(i); + } + for (1..m + 1) |j| { + dp[0][j] = @intCast(j); + } + // 状态转移:其余行和列 + for (1..n + 1) |i| { + for (1..m + 1) |j| { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +// 编辑距离:空间优化后的动态规划 +fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_]i32{0} ** (m + 1); + // 状态转移:首行 + for (1..m + 1) |j| { + dp[j] = @intCast(j); + } + // 状态转移:其余行 + for (1..n + 1) |i| { + // 状态转移:首列 + var leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = @intCast(i); + // 状态转移:其余列 + for (1..m + 1) |j| { + var temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +// Driver Code +pub fn main() !void { + const s = "bag"; + const t = "pack"; + comptime var n = s.len; + comptime var m = t.len; + + // 暴力搜索 + var res = editDistanceDFS(s, t, n, m); + std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); + + // 记忆搜索 + var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); + res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); + std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); + + // 动态规划 + res = editDistanceDP(s, t); + std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t); + std.debug.print("将 {s} 更改为 {s} 最少需要编辑 {} 步\n", .{ s, t, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/knapsack.zig b/codes/zig/chapter_dynamic_programming/knapsack.zig new file mode 100644 index 0000000000..1eb951fcba --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/knapsack.zig @@ -0,0 +1,110 @@ +// File: knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 0-1 背包:暴力搜索 +fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + var no = knapsackDFS(wgt, val, i - 1, c); + var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return @max(no, yes); +} + +// 0-1 背包:记忆化搜索 +fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + var no = knapsackDFSMem(wgt, val, mem, i - 1, c); + var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = @max(no, yes); + return mem[i][c]; +} + +// 0-1 背包:动态规划 +fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 状态转移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 0-1 背包:空间优化后的动态规划 +fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { + var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 状态转移 + for (1..n + 1) |i| { + // 倒序遍历 + var c = cap; + while (c > 0) : (c -= 1) { + if (wgt[i - 1] < c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; + comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; + comptime var cap = 50; + comptime var n = wgt.len; + + // 暴力搜索 + var res = knapsackDFS(&wgt, &val, n, cap); + std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); + + // 记忆搜索 + var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); + res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); + std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); + + // 动态规划 + res = knapsackDP(&wgt, &val, cap); + std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); + + // 空间优化后的动态规划 + res = knapsackDPComp(&wgt, &val, cap); + std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig b/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig new file mode 100644 index 0000000000..14245ac85e --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig @@ -0,0 +1,54 @@ +// File: min_cost_climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 爬楼梯最小代价:动态规划 +fn minCostClimbingStairsDP(comptime cost: []i32) i32 { + comptime var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + // 初始化 dp 表,用于存储子问题的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +// 爬楼梯最小代价:空间优化后的动态规划 +fn minCostClimbingStairsDPComp(cost: []i32) i32 { + var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + var a = cost[1]; + var b = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + var tmp = b; + b = @min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + std.debug.print("输入楼梯的代价列表为 {any}\n", .{cost}); + + var res = minCostClimbingStairsDP(&cost); + std.debug.print("输入楼梯的代价列表为 {}\n", .{res}); + + res = minCostClimbingStairsDPComp(&cost); + std.debug.print("输入楼梯的代价列表为 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/min_path_sum.zig b/codes/zig/chapter_dynamic_programming/min_path_sum.zig new file mode 100644 index 0000000000..2462606457 --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/min_path_sum.zig @@ -0,0 +1,122 @@ +// File: min_path_sum.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 最小路径和:暴力搜索 +fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { + // 若为左上角单元格,则终止搜索 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + var up = minPathSumDFS(grid, i - 1, j); + var left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小路径和:记忆化搜索 +fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { + // 若为左上角单元格,则终止搜索 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 若已有记录,则直接返回 + if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + var up = minPathSumDFSMem(grid, mem, i - 1, j); + var left = minPathSumDFSMem(grid, mem, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小路径和:动态规划 +fn minPathSumDP(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_][m]i32{[_]i32{0} ** m} ** n; + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (1..m) |j| { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (1..n) |i| { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (1..n) |i| { + for (1..m) |j| { + dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +// 最小路径和:空间优化后的动态规划 +fn minPathSumDPComp(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_]i32{0} ** m; + // 状态转移:首行 + dp[0] = grid[0][0]; + for (1..m) |j| { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (1..n) |i| { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + for (1..m) |j| { + dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +// Driver Code +pub fn main() !void { + comptime var grid = [_][4]i32{ + [_]i32{ 1, 3, 1, 5 }, + [_]i32{ 2, 2, 4, 2 }, + [_]i32{ 5, 3, 2, 1 }, + [_]i32{ 4, 3, 5, 2 }, + }; + comptime var n = grid.len; + comptime var m = grid[0].len; + + // 暴力搜索 + var res = minPathSumDFS(&grid, n - 1, m - 1); + std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); + + // 记忆化搜索 + var mem = [_][m]i32{[_]i32{-1} ** m} ** n; + res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); + std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); + + // 动态规划 + res = minPathSumDP(&grid); + std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); + + // 空间优化后的动态规划 + res = minPathSumDPComp(&grid); + std.debug.print("从左上角到右下角的最小路径和为 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig b/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig new file mode 100644 index 0000000000..f7f3abee3c --- /dev/null +++ b/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig @@ -0,0 +1,62 @@ +// File: unbounded_knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 完全背包:动态规划 +fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 状态转移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 完全背包:空间优化后的动态规划 +fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 状态转移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 1, 2, 3 }; + comptime var val = [_]i32{ 5, 11, 15 }; + comptime var cap = 4; + + // 动态规划 + var res = unboundedKnapsackDP(&wgt, &val, cap); + std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(&wgt, &val, cap); + std.debug.print("不超过背包容量的最大物品价值为 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_hashing/array_hash_map.zig b/codes/zig/chapter_hashing/array_hash_map.zig new file mode 100644 index 0000000000..e9c527b34f --- /dev/null +++ b/codes/zig/chapter_hashing/array_hash_map.zig @@ -0,0 +1,162 @@ +// File: array_hash_map.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 键值对 +const Pair = struct { + key: usize = undefined, + val: []const u8 = undefined, + + pub fn init(key: usize, val: []const u8) Pair { + return Pair { + .key = key, + .val = val, + }; + } +}; + +// 基于数组实现的哈希表 +pub fn ArrayHashMap(comptime T: type) type { + return struct { + bucket: ?std.ArrayList(?T) = null, + mem_allocator: std.mem.Allocator = undefined, + + const Self = @This(); + + // 构造函数 + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + self.mem_allocator = allocator; + // 初始化一个长度为 100 的桶(数组) + self.bucket = std.ArrayList(?T).init(self.mem_allocator); + var i: i32 = 0; + while (i < 100) : (i += 1) { + try self.bucket.?.append(null); + } + } + + // 析构函数 + pub fn deinit(self: *Self) void { + if (self.bucket != null) self.bucket.?.deinit(); + } + + // 哈希函数 + fn hashFunc(key: usize) usize { + var index = key % 100; + return index; + } + + // 查询操作 + pub fn get(self: *Self, key: usize) []const u8 { + var index = hashFunc(key); + var pair = self.bucket.?.items[index]; + return pair.?.val; + } + + // 添加操作 + pub fn put(self: *Self, key: usize, val: []const u8) !void { + var pair = Pair.init(key, val); + var index = hashFunc(key); + self.bucket.?.items[index] = pair; + } + + // 删除操作 + pub fn remove(self: *Self, key: usize) !void { + var index = hashFunc(key); + // 置为 null ,代表删除 + self.bucket.?.items[index] = null; + } + + // 获取所有键值对 + pub fn pairSet(self: *Self) !std.ArrayList(T) { + var entry_set = std.ArrayList(T).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try entry_set.append(item.?); + } + return entry_set; + } + + // 获取所有键 + pub fn keySet(self: *Self) !std.ArrayList(usize) { + var key_set = std.ArrayList(usize).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try key_set.append(item.?.key); + } + return key_set; + } + + // 获取所有值 + pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { + var value_set = std.ArrayList([]const u8).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try value_set.append(item.?.val); + } + return value_set; + } + + // 打印哈希表 + pub fn print(self: *Self) !void { + var entry_set = try self.pairSet(); + defer entry_set.deinit(); + for (entry_set.items) |item| { + std.debug.print("{} -> {s}\n", .{item.key, item.val}); + } + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化哈希表 + var map = ArrayHashMap(Pair){}; + try map.init(std.heap.page_allocator); + defer map.deinit(); + + // 添加操作 + // 在哈希表中添加键值对 (key, value) + try map.put(12836, "小哈"); + try map.put(15937, "小啰"); + try map.put(16750, "小算"); + try map.put(13276, "小法"); + try map.put(10583, "小鸭"); + std.debug.print("\n添加完成后,哈希表为\nKey -> Value\n", .{}); + try map.print(); + + // 查询操作 + // 向哈希表中输入键 key ,得到值 value + var name = map.get(15937); + std.debug.print("\n输入学号 15937 ,查询到姓名 {s}\n", .{name}); + + // 删除操作 + // 在哈希表中删除键值对 (key, value) + try map.remove(10583); + std.debug.print("\n删除 10583 后,哈希表为\nKey -> Value\n", .{}); + try map.print(); + + // 遍历哈希表 + std.debug.print("\n遍历键值对 Key->Value\n", .{}); + var entry_set = try map.pairSet(); + for (entry_set.items) |kv| { + std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); + } + defer entry_set.deinit(); + std.debug.print("\n单独遍历键 Key\n", .{}); + var key_set = try map.keySet(); + for (key_set.items) |key| { + std.debug.print("{}\n", .{key}); + } + defer key_set.deinit(); + std.debug.print("\n单独遍历值 value\n", .{}); + var value_set = try map.valueSet(); + for (value_set.items) |val| { + std.debug.print("{s}\n", .{val}); + } + defer value_set.deinit(); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_hashing/hash_map.zig b/codes/zig/chapter_hashing/hash_map.zig new file mode 100644 index 0000000000..ba3807beee --- /dev/null +++ b/codes/zig/chapter_hashing/hash_map.zig @@ -0,0 +1,54 @@ +// File: hash_map.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化哈希表 + var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); + // 延迟释放内存 + defer map.deinit(); + + // 添加操作 + // 在哈希表中添加键值对 (key, value) + try map.put(12836, "小哈"); + try map.put(15937, "小啰"); + try map.put(16750, "小算"); + try map.put(13276, "小法"); + try map.put(10583, "小鸭"); + std.debug.print("\n添加完成后,哈希表为\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // 查询操作 + // 向哈希表中输入键 key ,得到值 value + var name = map.get(15937).?; + std.debug.print("\n输入学号 15937 ,查询到姓名 {s}\n", .{name}); + + // 删除操作 + // 在哈希表中删除键值对 (key, value) + _ = map.remove(10583); + std.debug.print("\n删除 10583 后,哈希表为\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // 遍历哈希表 + std.debug.print("\n遍历键值对 Key->Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + std.debug.print("\n单独遍历键 Key\n", .{}); + var it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{}\n", .{kv.key_ptr.*}); + } + + std.debug.print("\n单独遍历值 value\n", .{}); + it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{s}\n", .{kv.value_ptr.*}); + } + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/codes/zig/chapter_heap/heap.zig b/codes/zig/chapter_heap/heap.zig new file mode 100644 index 0000000000..f4bb13cea5 --- /dev/null +++ b/codes/zig/chapter_heap/heap.zig @@ -0,0 +1,80 @@ +// File: heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +fn lessThan(context: void, a: i32, b: i32) std.math.Order { + _ = context; + return std.math.order(a, b); +} + +fn greaterThan(context: void, a: i32, b: i32) std.math.Order { + return lessThan(context, a, b).invert(); +} + +fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { + try heap.add(val); //元素入堆 + std.debug.print("\n元素 {} 入堆后\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { + var val = heap.remove(); //堆顶元素出堆 + std.debug.print("\n堆顶元素 {} 出堆后\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +// Driver Code +pub fn main() !void { + // 初始化内存分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化堆 + // 初始化小顶堆 + const PQlt = std.PriorityQueue(i32, void, lessThan); + var min_heap = PQlt.init(std.heap.page_allocator, {}); + defer min_heap.deinit(); + // 初始化大顶堆 + const PQgt = std.PriorityQueue(i32, void, greaterThan); + var max_heap = PQgt.init(std.heap.page_allocator, {}); + defer max_heap.deinit(); + + std.debug.print("\n以下测试样例为大顶堆", .{}); + + // 元素入堆 + try testPush(i32, mem_allocator, &max_heap, 1); + try testPush(i32, mem_allocator, &max_heap, 3); + try testPush(i32, mem_allocator, &max_heap, 2); + try testPush(i32, mem_allocator, &max_heap, 5); + try testPush(i32, mem_allocator, &max_heap, 4); + + // 获取堆顶元素 + var peek = max_heap.peek().?; + std.debug.print("\n堆顶元素为 {}\n", .{peek}); + + // 堆顶元素出堆 + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + + // 获取堆的大小 + var size = max_heap.len; + std.debug.print("\n堆元素数量为 {}\n", .{size}); + + // 判断堆是否为空 + var is_empty = if (max_heap.len == 0) true else false; + std.debug.print("\n堆是否为空 {}\n", .{is_empty}); + + // 输入列表并建堆 + try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("\n输入列表并建立小顶堆后\n", .{}); + try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_heap/my_heap.zig b/codes/zig/chapter_heap/my_heap.zig new file mode 100644 index 0000000000..86f8f36198 --- /dev/null +++ b/codes/zig/chapter_heap/my_heap.zig @@ -0,0 +1,186 @@ +// File: my_heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 堆类简易实现 +pub fn MaxHeap(comptime T: type) type { + return struct { + const Self = @This(); + + max_heap: ?std.ArrayList(T) = null, // 使用列表而非数组,这样无须考虑扩容问题 + + // 构造方法,根据输入列表建堆 + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { + if (self.max_heap != null) return; + self.max_heap = std.ArrayList(T).init(allocator); + // 将列表元素原封不动添加进堆 + try self.max_heap.?.appendSlice(nums); + // 堆化除叶节点以外的其他所有节点 + var i: usize = parent(self.size() - 1) + 1; + while (i > 0) : (i -= 1) { + try self.siftDown(i - 1); + } + } + + // 析构方法,释放内存 + pub fn deinit(self: *Self) void { + if (self.max_heap != null) self.max_heap.?.deinit(); + } + + // 获取左子节点的索引 + fn left(i: usize) usize { + return 2 * i + 1; + } + + // 获取右子节点的索引 + fn right(i: usize) usize { + return 2 * i + 2; + } + + // 获取父节点的索引 + fn parent(i: usize) usize { + // return (i - 1) / 2; // 向下整除 + return @divFloor(i - 1, 2); + } + + // 交换元素 + fn swap(self: *Self, i: usize, j: usize) !void { + var tmp = self.max_heap.?.items[i]; + try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); + try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); + } + + // 获取堆大小 + pub fn size(self: *Self) usize { + return self.max_heap.?.items.len; + } + + // 判断堆是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问堆顶元素 + pub fn peek(self: *Self) T { + return self.max_heap.?.items[0]; + } + + // 元素入堆 + pub fn push(self: *Self, val: T) !void { + // 添加节点 + try self.max_heap.?.append(val); + // 从底至顶堆化 + try self.siftUp(self.size() - 1); + } + + // 从节点 i 开始,从底至顶堆化 + fn siftUp(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 获取节点 i 的父节点 + var p = parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; + // 交换两节点 + try self.swap(i, p); + // 循环向上堆化 + i = p; + } + } + + // 元素出堆 + pub fn pop(self: *Self) !T { + // 判断处理 + if (self.isEmpty()) unreachable; + // 交换根节点与最右叶节点(交换首元素与尾元素) + try self.swap(0, self.size() - 1); + // 删除节点 + var val = self.max_heap.?.pop(); + // 从顶至底堆化 + try self.siftDown(0); + // 返回堆顶元素 + return val; + } + + // 从节点 i 开始,从顶至底堆化 + fn siftDown(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + var l = left(i); + var r = right(i); + var ma = i; + if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; + if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break; + // 交换两节点 + try self.swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + + fn lessThan(context: void, a: T, b: T) std.math.Order { + _ = context; + return std.math.order(a, b); + } + + fn greaterThan(context: void, a: T, b: T) std.math.Order { + return lessThan(context, a, b).invert(); + } + + // 打印堆(二叉树) + pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { + const PQgt = std.PriorityQueue(T, void, greaterThan); + var queue = PQgt.init(std.heap.page_allocator, {}); + defer queue.deinit(); + try queue.addSlice(self.max_heap.?.items); + try inc.PrintUtil.printHeap(T, mem_allocator, queue); + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化内存分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化大顶堆 + var max_heap = MaxHeap(i32){}; + try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); + defer max_heap.deinit(); + std.debug.print("\n输入列表并建堆后\n", .{}); + try max_heap.print(mem_allocator); + + // 获取堆顶元素 + var peek = max_heap.peek(); + std.debug.print("\n堆顶元素为 {}\n", .{peek}); + + // 元素入堆 + const val = 7; + try max_heap.push(val); + std.debug.print("\n元素 {} 入堆后\n", .{val}); + try max_heap.print(mem_allocator); + + // 堆顶元素出堆 + peek = try max_heap.pop(); + std.debug.print("\n堆顶元素 {} 出堆后\n", .{peek}); + try max_heap.print(mem_allocator); + + // 获取堆的大小 + var size = max_heap.size(); + std.debug.print("\n堆元素数量为 {}", .{size}); + + // 判断堆是否为空 + var is_empty = max_heap.isEmpty(); + std.debug.print("\n堆是否为空 {}\n", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/codes/zig/chapter_searching/binary_search.zig b/codes/zig/chapter_searching/binary_search.zig new file mode 100644 index 0000000000..193d679a49 --- /dev/null +++ b/codes/zig/chapter_searching/binary_search.zig @@ -0,0 +1,64 @@ +// File: binary_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二分查找(双闭区间) +fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + var i: usize = 0; + var j: usize = nums.items.len - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) { + var m = i + (j - i) / 2; // 计算中点索引 m + if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 + j = m - 1; + } else { // 找到目标元素,返回其索引 + return @intCast(m); + } + } + // 未找到目标元素,返回 -1 + return -1; +} + +// 二分查找(左闭右开区间) +fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + var i: usize = 0; + var j: usize = nums.items.len; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i <= j) { + var m = i + (j - i) / 2; // 计算中点索引 m + if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j) 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m) 中 + j = m; + } else { // 找到目标元素,返回其索引 + return @intCast(m); + } + } + // 未找到目标元素,返回 -1 + return -1; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 6; + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); + + // 二分查找(双闭区间) + var index = binarySearch(i32, nums, target); + std.debug.print("目标元素 6 的索引 = {}\n", .{index}); + + // 二分查找(左闭右开区间) + index = binarySearchLCRO(i32, nums, target); + std.debug.print("目标元素 6 的索引 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_searching/hashing_search.zig b/codes/zig/chapter_searching/hashing_search.zig new file mode 100644 index 0000000000..708cd0a2c8 --- /dev/null +++ b/codes/zig/chapter_searching/hashing_search.zig @@ -0,0 +1,57 @@ +// File: hashing_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 哈希查找(数组) +fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 -1 + if (map.getKey(target) == null) return -1; + return map.get(target).?; +} + +// 哈希查找(链表) +fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { + // 哈希表的 key: 目标节点值,value: 节点对象 + // 若哈希表中无此 key ,返回 null + if (map.getKey(target) == null) return null; + return map.get(target); +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // 哈希查找(数组) + var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // 初始化哈希表 + var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer map.deinit(); + for (nums, 0..) |num, i| { + try map.put(num, @as(i32, @intCast(i))); // key: 元素,value: 索引 + } + var index = hashingSearchArray(i32, map, target); + std.debug.print("目标元素 3 的索引 = {}\n", .{index}); + + // 哈希查找(链表) + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); + // 初始化哈希表 + var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); + defer map1.deinit(); + while (head != null) { + try map1.put(head.?.val, head.?); + head = head.?.next; + } + var node = hashingSearchLinkedList(i32, map1, target); + std.debug.print("目标节点值 3 的对应节点对象为 ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/codes/zig/chapter_searching/linear_search.zig b/codes/zig/chapter_searching/linear_search.zig new file mode 100644 index 0000000000..76b2839faf --- /dev/null +++ b/codes/zig/chapter_searching/linear_search.zig @@ -0,0 +1,54 @@ +// File: linear_search.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 线性查找(数组) +fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 遍历数组 + for (nums.items, 0..) |num, i| { + // 找到目标元素, 返回其索引 + if (num == target) { + return @intCast(i); + } + } + // 未找到目标元素,返回 -1 + return -1; +} + +// 线性查找(链表) +pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { + var head = node; + // 遍历链表 + while (head != null) { + // 找到目标节点,返回之 + if (head.?.val == target) return head; + head = head.?.next; + } + return null; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // 在数组中执行线性查找 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); + var index = linearSearchArray(i32, nums, target); + std.debug.print("目标元素 3 的索引 = {}\n", .{index}); + + // 在链表中执行线性查找 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); + var node = linearSearchLinkedList(i32, head, target); + std.debug.print("目标节点值 3 的对应节点对象为 ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_searching/two_sum.zig b/codes/zig/chapter_searching/two_sum.zig new file mode 100644 index 0000000000..ce5c898afe --- /dev/null +++ b/codes/zig/chapter_searching/two_sum.zig @@ -0,0 +1,58 @@ +// File: two_sum.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 方法一:暴力枚举 +pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { + var size: usize = nums.len; + var i: usize = 0; + // 两层循环,时间复杂度为 O(n^2) + while (i < size - 1) : (i += 1) { + var j = i + 1; + while (j < size) : (j += 1) { + if (nums[i] + nums[j] == target) { + return [_]i32{@intCast(i), @intCast(j)}; + } + } + } + return null; +} + +// 方法二:辅助哈希表 +pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { + var size: usize = nums.len; + // 辅助哈希表,空间复杂度为 O(n) + var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer dic.deinit(); + var i: usize = 0; + // 单层循环,时间复杂度为 O(n) + while (i < size) : (i += 1) { + if (dic.contains(target - nums[i])) { + return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; + } + try dic.put(nums[i], @intCast(i)); + } + return null; +} + + +pub fn main() !void { + // ======= Test Case ======= + var nums = [_]i32{ 2, 7, 11, 15 }; + var target: i32 = 9; + + // ====== Driver Code ====== + // 方法一 + var res = twoSumBruteForce(&nums, target).?; + std.debug.print("方法一 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + // 方法二 + res = (try twoSumHashTable(&nums, target)).?; + std.debug.print("\n方法二 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_sorting/bubble_sort.zig b/codes/zig/chapter_sorting/bubble_sort.zig index 8631909632..676a7a830b 100644 --- a/codes/zig/chapter_sorting/bubble_sort.zig +++ b/codes/zig/chapter_sorting/bubble_sort.zig @@ -1,17 +1,17 @@ -// File: time_complexity.zig +// File: bubble_sort.zig // Created Time: 2023-01-08 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 冒泡排序 fn bubbleSort(nums: []i32) void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var j: usize = 0; - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -25,12 +25,12 @@ fn bubbleSort(nums: []i32) void { // 冒泡排序(标志优化) fn bubbleSortWithFlag(nums: []i32) void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + // 外循环:未排序区间为 [0, i] var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var flag = false; // 初始化标志位 var j: usize = 0; - // 内循环:冒泡操作 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -40,7 +40,7 @@ fn bubbleSortWithFlag(nums: []i32) void { flag = true; } } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + if (!flag) break; // 此轮“冒泡”未交换任何元素,直接跳出 } } @@ -56,7 +56,6 @@ pub fn main() !void { std.debug.print("\n冒泡排序完成后 nums1 = ", .{}); inc.PrintUtil.printArray(i32, &nums1); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_sorting/insertion_sort.zig b/codes/zig/chapter_sorting/insertion_sort.zig index 3c7594c246..0db476a87d 100644 --- a/codes/zig/chapter_sorting/insertion_sort.zig +++ b/codes/zig/chapter_sorting/insertion_sort.zig @@ -1,22 +1,22 @@ -// File: time_complexity.zig +// File: insertion_sort.zig // Created Time: 2023-01-08 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 插入排序 fn insertionSort(nums: []i32) void { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] + // 外循环:已排序区间为 [0, i-1] var i: usize = 1; while (i < nums.len) : (i += 1) { var base = nums[i]; var j: usize = i; - // 内循环:将 base 插入到左边的正确位置 + // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 while (j >= 1 and nums[j - 1] > base) : (j -= 1) { - nums[j] = nums[j - 1]; // 1. 将 nums[j] 向右移动一位 + nums[j] = nums[j - 1]; // 将 nums[j] 向右移动一位 } - nums[j] = base; // 2. 将 base 赋值到正确位置 + nums[j] = base; // 将 base 赋值到正确位置 } } @@ -27,7 +27,5 @@ pub fn main() !void { std.debug.print("插入排序完成后 nums = ", .{}); inc.PrintUtil.printArray(i32, &nums); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; -} - + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_sorting/merge_sort.zig b/codes/zig/chapter_sorting/merge_sort.zig new file mode 100644 index 0000000000..ac0d6245b3 --- /dev/null +++ b/codes/zig/chapter_sorting/merge_sort.zig @@ -0,0 +1,67 @@ +// File: merge_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 合并左子数组和右子数组 +// 左子数组区间 [left, mid] +// 右子数组区间 [mid + 1, right] +fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { + // 初始化辅助数组 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var tmp = try mem_allocator.alloc(i32, right + 1 - left); + std.mem.copy(i32, tmp, nums[left..right+1]); + // 左子数组的起始索引和结束索引 + var leftStart = left - left; + var leftEnd = mid - left; + // 右子数组的起始索引和结束索引 + var rightStart = mid + 1 - left; + var rightEnd = right - left; + // i, j 分别指向左子数组、右子数组的首元素 + var i = leftStart; + var j = rightStart; + // 通过覆盖原数组 nums 来合并左子数组和右子数组 + var k = left; + while (k <= right) : (k += 1) { + // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ + if (i > leftEnd) { + nums[k] = tmp[j]; + j += 1; + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ + } else if (j > rightEnd or tmp[i] <= tmp[j]) { + nums[k] = tmp[i]; + i += 1; + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + } else { + nums[k] = tmp[j]; + j += 1; + } + } +} + +// 归并排序 +fn mergeSort(nums: []i32, left: usize, right: usize) !void { + // 终止条件 + if (left >= right) return; // 当子数组长度为 1 时终止递归 + // 划分阶段 + var mid = left + (right - left) / 2; // 计算中点 + try mergeSort(nums, left, mid); // 递归左子数组 + try mergeSort(nums, mid + 1, right); // 递归右子数组 + // 合并阶段 + try merge(nums, left, mid, right); +} + +// Driver Code +pub fn main() !void { + // 归并排序 + var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; + try mergeSort(&nums, 0, nums.len - 1); + std.debug.print("归并排序完成后 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_sorting/quick_sort.zig b/codes/zig/chapter_sorting/quick_sort.zig new file mode 100644 index 0000000000..7f0cf59233 --- /dev/null +++ b/codes/zig/chapter_sorting/quick_sort.zig @@ -0,0 +1,162 @@ +// File: quick_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 快速排序类 +const QuickSort = struct { + + // 元素交换 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 哨兵划分 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 以 nums[left] 为基准数 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + // 快速排序 + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 子数组长度为 1 时终止递归 + if (left >= right) return; + // 哨兵划分 + var pivot = partition(nums, left, right); + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// 快速排序类(中位基准数优化) +const QuickSortMedian = struct { + + // 元素交换 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 选取三个候选元素的中位数 + pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { + var l = nums[left]; + var m = nums[mid]; + var 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; + } + + // 哨兵划分(三数取中值) + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 选取三个候选元素的中位数 + var med = medianThree(nums, left, (left + right) / 2, right); + // 将中位数交换至数组最左端 + swap(nums, left, med); + // 以 nums[left] 为基准数 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + // 快速排序 + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 子数组长度为 1 时终止递归 + if (left >= right) return; + // 哨兵划分 + var pivot = partition(nums, left, right); + if (pivot == 0) return; + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// 快速排序类(尾递归优化) +const QuickSortTailCall = struct { + + // 元素交换 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 哨兵划分 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 以 nums[left] 为基准数 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + // 快速排序(尾递归优化) + pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { + var left = left_; + var right = right_; + // 子数组长度为 1 时终止递归 + while (left < right) { + // 哨兵划分操作 + var pivot = partition(nums, left, right); + // 对两个子数组中较短的那个执行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 递归排序右子数组 + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] + } + } + } +}; + +// Driver Code +pub fn main() !void { + // 快速排序 + var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(&nums, 0, nums.len - 1); + std.debug.print("快速排序完成后 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + // 快速排序(中位基准数优化) + var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); + std.debug.print("\n快速排序(中位基准数优化)完成后 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + // 快速排序(尾递归优化) + var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); + std.debug.print("\n快速排序(尾递归优化)完成后 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums2); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_sorting/radix_sort.zig b/codes/zig/chapter_sorting/radix_sort.zig new file mode 100644 index 0000000000..13de9a626c --- /dev/null +++ b/codes/zig/chapter_sorting/radix_sort.zig @@ -0,0 +1,77 @@ +// File: radix_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 获取元素 num 的第 k 位,其中 exp = 10^(k-1) +fn digit(num: i32, exp: i32) i32 { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return @mod(@divFloor(num, exp), 10); +} + +// 计数排序(根据 nums 第 k 位排序) +fn countingSortDigit(nums: []i32, exp: i32) !void { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var counter = try mem_allocator.alloc(usize, 10); + @memset(counter, 0); + var n = nums.len; + // 统计 0~9 各数字的出现次数 + for (nums) |num| { + var d: u32 = @bitCast(digit(num, exp)); // 获取 nums[i] 第 k 位,记为 d + counter[d] += 1; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + var i: usize = 1; + while (i < 10) : (i += 1) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + var res = try mem_allocator.alloc(i32, n); + i = n - 1; + while (i >= 0) : (i -= 1) { + var d: u32 = @bitCast(digit(nums[i], exp)); + var j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d] -= 1; // 将 d 的数量减 1 + if (i == 0) break; + } + // 使用结果覆盖原数组 nums + i = 0; + while (i < n) : (i += 1) { + nums[i] = res[i]; + } +} + +// 基数排序 +fn radixSort(nums: []i32) !void { + // 获取数组的最大元素,用于判断最大位数 + var m: i32 = std.math.minInt(i32); + for (nums) |num| { + if (num > m) m = num; + } + // 按照从低位到高位的顺序遍历 + var exp: i32 = 1; + while (exp <= m) : (exp *= 10) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + try countingSortDigit(nums, exp); + } +} + +// Driver Code +pub fn main() !void { + // 基数排序 + var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; + try radixSort(&nums); + std.debug.print("基数排序完成后 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_stack_and_queue/array_queue.zig b/codes/zig/chapter_stack_and_queue/array_queue.zig new file mode 100644 index 0000000000..f0d1b86b21 --- /dev/null +++ b/codes/zig/chapter_stack_and_queue/array_queue.zig @@ -0,0 +1,140 @@ +// File: array_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基于环形数组实现的队列 +pub fn ArrayQueue(comptime T: type) type { + return struct { + const Self = @This(); + + nums: []T = undefined, // 用于存储队列元素的数组 + cap: usize = 0, // 队列容量 + front: usize = 0, // 队首指针,指向队首元素 + queSize: usize = 0, // 尾指针,指向队尾 + 1 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化数组) + pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.cap = cap; + self.nums = try self.mem_allocator.alloc(T, self.cap); + @memset(self.nums, @as(T, 0)); + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取队列的容量 + pub fn capacity(self: *Self) usize { + return self.cap; + } + + // 获取队列的长度 + pub fn size(self: *Self) usize { + return self.queSize; + } + + // 判断队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.queSize == 0; + } + + // 入队 + pub fn push(self: *Self, num: T) !void { + if (self.size() == self.capacity()) { + std.debug.print("队列已满\n", .{}); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + var rear = (self.front + self.queSize) % self.capacity(); + // 在尾节点后添加 num + self.nums[rear] = num; + self.queSize += 1; + } + + // 出队 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + self.front = (self.front + 1) % self.capacity(); + self.queSize -= 1; + return num; + } + + // 访问队首元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("队列为空"); + return self.nums[self.front]; + } + + // 返回数组 + pub fn toArray(self: *Self) ![]T { + // 仅转换有效长度范围内的列表元素 + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + var j: usize = self.front; + while (i < self.size()) : ({ i += 1; j += 1; }) { + res[i] = self.nums[j % self.capacity()]; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化队列 + var capacity: usize = 10; + var queue = ArrayQueue(i32){}; + try queue.init(std.heap.page_allocator, capacity); + defer queue.deinit(); + + // 元素入队 + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("队列 queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 访问队首元素 + var peek = queue.peek(); + std.debug.print("\n队首元素 peek = {}", .{peek}); + + // 元素出队 + var pop = queue.pop(); + std.debug.print("\n出队元素 pop = {},出队后 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 获取队列的长度 + var size = queue.size(); + std.debug.print("\n队列长度 size = {}", .{size}); + + // 判断队列是否为空 + var is_empty = queue.isEmpty(); + std.debug.print("\n队列是否为空 = {}", .{is_empty}); + + // 测试环形数组 + var i: i32 = 0; + while (i < 10) : (i += 1) { + try queue.push(i); + _ = queue.pop(); + std.debug.print("\n第 {} 轮入队 + 出队后 queue = ", .{i}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + } + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_stack_and_queue/array_stack.zig b/codes/zig/chapter_stack_and_queue/array_stack.zig index 868d9d94d9..59b39e3729 100644 --- a/codes/zig/chapter_stack_and_queue/array_stack.zig +++ b/codes/zig/chapter_stack_and_queue/array_stack.zig @@ -1,26 +1,25 @@ -// File: stack.zig +// File: array_stack.zig // Created Time: 2023-01-08 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基于数组实现的栈 -// 编译期泛型 pub fn ArrayStack(comptime T: type) type { return struct { const Self = @This(); stack: ?std.ArrayList(T) = null, - // 构造函数(分配内存+初始化栈) + // 构造方法(分配内存+初始化栈) pub fn init(self: *Self, allocator: std.mem.Allocator) void { if (self.stack == null) { self.stack = std.ArrayList(T).init(allocator); } } - // 析构函数(释放内存) + // 析构方法(释放内存) pub fn deinit(self: *Self) void { if (self.stack == null) return; self.stack.?.deinit(); @@ -32,13 +31,13 @@ pub fn ArrayStack(comptime T: type) type { } // 判断栈是否为空 - pub fn empty(self: *Self) bool { + pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 访问栈顶元素 - pub fn top(self: *Self) T { - if (self.size() == 0) @panic("栈为空"); + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("栈为空"); return self.stack.?.items[self.size() - 1]; } @@ -78,11 +77,11 @@ pub fn main() !void { inc.PrintUtil.printList(i32, stack.toList()); // 访问栈顶元素 - var top = stack.top(); - std.debug.print("\n栈顶元素 top = {}", .{top}); + var peek = stack.peek(); + std.debug.print("\n栈顶元素 peek = {}", .{peek}); // 元素出栈 - top = stack.pop(); + var top = stack.pop(); std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{top}); inc.PrintUtil.printList(i32, stack.toList()); @@ -91,9 +90,8 @@ pub fn main() !void { std.debug.print("\n栈的长度 size = {}", .{size}); // 判断栈是否为空 - var empty = stack.empty(); - std.debug.print("\n栈是否为空 = {}", .{empty}); + var is_empty = stack.isEmpty(); + std.debug.print("\n栈是否为空 = {}", .{is_empty}); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_stack_and_queue/deque.zig b/codes/zig/chapter_stack_and_queue/deque.zig new file mode 100644 index 0000000000..4112ce01c2 --- /dev/null +++ b/codes/zig/chapter_stack_and_queue/deque.zig @@ -0,0 +1,51 @@ +// File: deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化双向队列 + const L = std.TailQueue(i32); + var deque = L{}; + + // 元素入队 + var node1 = L.Node{ .data = 2 }; + var node2 = L.Node{ .data = 5 }; + var node3 = L.Node{ .data = 4 }; + var node4 = L.Node{ .data = 3 }; + var node5 = L.Node{ .data = 1 }; + deque.append(&node1); // 添加至队尾 + deque.append(&node2); + deque.append(&node3); + deque.prepend(&node4); // 添加至队首 + deque.prepend(&node5); + std.debug.print("双向队列 deque = ", .{}); + inc.PrintUtil.printQueue(i32, deque); + + // 访问元素 + var peek_first = deque.first.?.data; // 队首元素 + std.debug.print("\n队首元素 peek_first = {}", .{peek_first}); + var peek_last = deque.last.?.data; // 队尾元素 + std.debug.print("\n队尾元素 peek_last = {}", .{peek_last}); + + // 元素出队 + var pop_first = deque.popFirst().?.data; // 队首元素出队 + std.debug.print("\n队首出队元素 pop_first = {},队首出队后 deque = ", .{pop_first}); + inc.PrintUtil.printQueue(i32, deque); + var pop_last = deque.pop().?.data; // 队尾元素出队 + std.debug.print("\n队尾出队元素 pop_last = {},队尾出队后 deque = ", .{pop_last}); + inc.PrintUtil.printQueue(i32, deque); + + // 获取双向队列的长度 + var size = deque.len; + std.debug.print("\n双向队列长度 size = {}", .{size}); + + // 判断双向队列是否为空 + var is_empty = if (deque.len == 0) true else false; + std.debug.print("\n双向队列是否为空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig b/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig new file mode 100644 index 0000000000..7a42ce7483 --- /dev/null +++ b/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig @@ -0,0 +1,207 @@ +// File: linkedlist_deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 双向链表节点 +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // 节点值 + next: ?*Self = null, // 后继节点指针 + prev: ?*Self = null, // 前驱节点指针 + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; +} + +// 基于双向链表实现的双向队列 +pub fn LinkedListDeque(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*ListNode(T) = null, // 头节点 front + rear: ?*ListNode(T) = null, // 尾节点 rear + que_size: usize = 0, // 双向队列的长度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化队列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取双向队列的长度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判断双向队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 入队操作 + pub fn push(self: *Self, num: T, is_front: bool) !void { + var node = try self.mem_allocator.create(ListNode(T)); + node.init(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (self.isEmpty()) { + self.front = node; + self.rear = node; + // 队首入队操作 + } else if (is_front) { + // 将 node 添加至链表头部 + self.front.?.prev = node; + node.next = self.front; + self.front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + self.rear.?.next = node; + node.prev = self.rear; + self.rear = node; // 更新尾节点 + } + self.que_size += 1; // 更新队列长度 + } + + // 队首入队 + pub fn pushFirst(self: *Self, num: T) !void { + try self.push(num, true); + } + + // 队尾入队 + pub fn pushLast(self: *Self, num: T) !void { + try self.push(num, false); + } + + // 出队操作 + pub fn pop(self: *Self, is_front: bool) T { + if (self.isEmpty()) @panic("双向队列为空"); + var val: T = undefined; + // 队首出队操作 + if (is_front) { + val = self.front.?.val; // 暂存头节点值 + // 删除头节点 + var fNext = self.front.?.next; + if (fNext != null) { + fNext.?.prev = null; + self.front.?.next = null; + } + self.front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = self.rear.?.val; // 暂存尾节点值 + // 删除尾节点 + var rPrev = self.rear.?.prev; + if (rPrev != null) { + rPrev.?.next = null; + self.rear.?.prev = null; + } + self.rear = rPrev; // 更新尾节点 + } + self.que_size -= 1; // 更新队列长度 + return val; + } + + // 队首出队 + pub fn popFirst(self: *Self) T { + return self.pop(true); + } + + // 队尾出队 + pub fn popLast(self: *Self) T { + return self.pop(false); + } + + // 访问队首元素 + pub fn peekFirst(self: *Self) T { + if (self.isEmpty()) @panic("双向队列为空"); + return self.front.?.val; + } + + // 访问队尾元素 + pub fn peekLast(self: *Self) T { + if (self.isEmpty()) @panic("双向队列为空"); + return self.rear.?.val; + } + + // 返回数组用于打印 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化双向队列 + var deque = LinkedListDeque(i32){}; + try deque.init(std.heap.page_allocator); + defer deque.deinit(); + try deque.pushLast(3); + try deque.pushLast(2); + try deque.pushLast(5); + std.debug.print("双向队列 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 访问元素 + var peek_first = deque.peekFirst(); + std.debug.print("\n队首元素 peek_first = {}", .{peek_first}); + var peek_last = deque.peekLast(); + std.debug.print("\n队尾元素 peek_last = {}", .{peek_last}); + + // 元素入队 + try deque.pushLast(4); + std.debug.print("\n元素 4 队尾入队后 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + try deque.pushFirst(1); + std.debug.print("\n元素 1 队首入队后 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 元素出队 + var pop_last = deque.popLast(); + std.debug.print("\n队尾出队元素 = {},队尾出队后 deque = ", .{pop_last}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + var pop_first = deque.popFirst(); + std.debug.print("\n队首出队元素 = {},队首出队后 deque = ", .{pop_first}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 获取双向队列的长度 + var size = deque.size(); + std.debug.print("\n双向队列长度 size = {}", .{size}); + + // 判断双向队列是否为空 + var is_empty = deque.isEmpty(); + std.debug.print("\n双向队列是否为空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig b/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig new file mode 100644 index 0000000000..711fa1bb77 --- /dev/null +++ b/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig @@ -0,0 +1,127 @@ +// File: linkedlist_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基于链表实现的队列 +pub fn LinkedListQueue(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*inc.ListNode(T) = null, // 头节点 front + rear: ?*inc.ListNode(T) = null, // 尾节点 rear + que_size: usize = 0, // 队列的长度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化队列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取队列的长度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判断队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问队首元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("队列为空"); + return self.front.?.val; + } + + // 入队 + pub fn push(self: *Self, num: T) !void { + // 在尾节点后添加 num + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (self.front == null) { + self.front = node; + self.rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else { + self.rear.?.next = node; + self.rear = node; + } + self.que_size += 1; + } + + // 出队 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 删除头节点 + self.front = self.front.?.next; + self.que_size -= 1; + return num; + } + + // 将链表转换为数组 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化队列 + var queue = LinkedListQueue(i32){}; + try queue.init(std.heap.page_allocator); + defer queue.deinit(); + + // 元素入队 + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("队列 queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 访问队首元素 + var peek = queue.peek(); + std.debug.print("\n队首元素 peek = {}", .{peek}); + + // 元素出队 + var pop = queue.pop(); + std.debug.print("\n出队元素 pop = {},出队后 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 获取队列的长度 + var size = queue.size(); + std.debug.print("\n队列长度 size = {}", .{size}); + + // 判断队列是否为空 + var is_empty = queue.isEmpty(); + std.debug.print("\n队列是否为空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig b/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig index 633583b451..5b5cd6ff89 100644 --- a/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig +++ b/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig @@ -1,20 +1,19 @@ // File: linkedlist_stack.zig // Created Time: 2023-01-08 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); // 基于链表实现的栈 -// 编译期泛型 pub fn LinkedListStack(comptime T: type) type { return struct { const Self = @This(); - stackTop: ?*inc.ListNode(T) = null, // 将头结点作为栈顶 - stkSize: usize = 0, // 栈的长度 + stack_top: ?*inc.ListNode(T) = null, // 将头节点作为栈顶 + stk_size: usize = 0, // 栈的长度 mem_arena: ?std.heap.ArenaAllocator = null, - mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 // 构造函数(分配内存+初始化栈) pub fn init(self: *Self, allocator: std.mem.Allocator) !void { @@ -22,8 +21,8 @@ pub fn LinkedListStack(comptime T: type) type { self.mem_arena = std.heap.ArenaAllocator.init(allocator); self.mem_allocator = self.mem_arena.?.allocator(); } - self.stackTop = null; - self.stkSize = 0; + self.stack_top = null; + self.stk_size = 0; } // 析构函数(释放内存) @@ -34,42 +33,42 @@ pub fn LinkedListStack(comptime T: type) type { // 获取栈的长度 pub fn size(self: *Self) usize { - return self.stkSize; + return self.stk_size; } // 判断栈是否为空 - pub fn empty(self: *Self) bool { + pub fn isEmpty(self: *Self) bool { return self.size() == 0; } // 访问栈顶元素 - pub fn top(self: *Self) T { + pub fn peek(self: *Self) T { if (self.size() == 0) @panic("栈为空"); - return self.stackTop.?.val; + return self.stack_top.?.val; } // 入栈 pub fn push(self: *Self, num: T) !void { var node = try self.mem_allocator.create(inc.ListNode(T)); node.init(num); - node.next = self.stackTop; - self.stackTop = node; - self.stkSize += 1; + node.next = self.stack_top; + self.stack_top = node; + self.stk_size += 1; } // 出栈 pub fn pop(self: *Self) T { - var num = self.top(); - self.stackTop = self.stackTop.?.next; - self.stkSize -= 1; + var num = self.peek(); + self.stack_top = self.stack_top.?.next; + self.stk_size -= 1; return num; } // 将栈转换为数组 pub fn toArray(self: *Self) ![]T { - var node = self.stackTop; + var node = self.stack_top; var res = try self.mem_allocator.alloc(T, self.size()); - std.mem.set(T, res, @as(T, 0)); + @memset(res, @as(T, 0)); var i: usize = 0; while (i < res.len) : (i += 1) { res[res.len - i - 1] = node.?.val; @@ -98,12 +97,12 @@ pub fn main() !void { inc.PrintUtil.printArray(i32, try stack.toArray()); // 访问栈顶元素 - var top = stack.top(); - std.debug.print("\n栈顶元素 top = {}", .{top}); + var peek = stack.peek(); + std.debug.print("\n栈顶元素 top = {}", .{peek}); // 元素出栈 - top = stack.pop(); - std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{top}); + var pop = stack.pop(); + std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{pop}); inc.PrintUtil.printArray(i32, try stack.toArray()); // 获取栈的长度 @@ -111,10 +110,9 @@ pub fn main() !void { std.debug.print("\n栈的长度 size = {}", .{size}); // 判断栈是否为空 - var empty = stack.empty(); - std.debug.print("\n栈是否为空 = {}", .{empty}); + var is_empty = stack.isEmpty(); + std.debug.print("\n栈是否为空 = {}", .{is_empty}); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_stack_and_queue/queue.zig b/codes/zig/chapter_stack_and_queue/queue.zig new file mode 100644 index 0000000000..61c306b1c5 --- /dev/null +++ b/codes/zig/chapter_stack_and_queue/queue.zig @@ -0,0 +1,46 @@ +// File: queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化队列 + const L = std.TailQueue(i32); + var queue = L{}; + + // 元素入队 + var node1 = L.Node{ .data = 1 }; + var node2 = L.Node{ .data = 3 }; + var node3 = L.Node{ .data = 2 }; + var node4 = L.Node{ .data = 5 }; + var node5 = L.Node{ .data = 4 }; + queue.append(&node1); + queue.append(&node2); + queue.append(&node3); + queue.append(&node4); + queue.append(&node5); + std.debug.print("队列 queue = ", .{}); + inc.PrintUtil.printQueue(i32, queue); + + // 访问队首元素 + var peek = queue.first.?.data; + std.debug.print("\n队首元素 peek = {}", .{peek}); + + // 元素出队 + var pop = queue.popFirst().?.data; + std.debug.print("\n出队元素 pop = {},出队后 queue = ", .{pop}); + inc.PrintUtil.printQueue(i32, queue); + + // 获取队列的长度 + var size = queue.len; + std.debug.print("\n队列长度 size = {}", .{size}); + + // 判断队列是否为空 + var is_empty = if (queue.len == 0) true else false; + std.debug.print("\n队列是否为空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_stack_and_queue/stack.zig b/codes/zig/chapter_stack_and_queue/stack.zig index 95ed427b08..6d64a52761 100644 --- a/codes/zig/chapter_stack_and_queue/stack.zig +++ b/codes/zig/chapter_stack_and_queue/stack.zig @@ -1,6 +1,6 @@ // File: stack.zig // Created Time: 2023-01-08 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); const inc = @import("include"); @@ -23,12 +23,12 @@ pub fn main() !void { inc.PrintUtil.printList(i32, stack); // 访问栈顶元素 - var top = stack.items[stack.items.len - 1]; - std.debug.print("\n栈顶元素 top = {}", .{top}); + var peek = stack.items[stack.items.len - 1]; + std.debug.print("\n栈顶元素 peek = {}", .{peek}); // 元素出栈 - top = stack.pop(); - std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{top}); + var pop = stack.pop(); + std.debug.print("\n出栈元素 pop = {},出栈后 stack = ", .{pop}); inc.PrintUtil.printList(i32, stack); // 获取栈的长度 @@ -36,9 +36,8 @@ pub fn main() !void { std.debug.print("\n栈的长度 size = {}", .{size}); // 判断栈是否为空 - var empty = if (stack.items.len == 0) true else false; - std.debug.print("\n栈是否为空 = {}", .{empty}); + var is_empty = if (stack.items.len == 0) true else false; + std.debug.print("\n栈是否为空 = {}", .{is_empty}); - const getchar = try std.io.getStdIn().reader().readByte(); - _ = getchar; + _ = try std.io.getStdIn().reader().readByte(); } diff --git a/codes/zig/chapter_tree/avl_tree.zig b/codes/zig/chapter_tree/avl_tree.zig new file mode 100644 index 0000000000..f34fac17db --- /dev/null +++ b/codes/zig/chapter_tree/avl_tree.zig @@ -0,0 +1,249 @@ +// File: avl_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// AVL 树 +pub fn AVLTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, // 根节点 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造方法 + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + } + + // 析构方法 + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取节点高度 + fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { + _ = self; + // 空节点高度为 -1 ,叶节点高度为 0 + return if (node == null) -1 else node.?.height; + } + + // 更新节点高度 + fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { + // 节点高度等于最高子树高度 + 1 + node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; + } + + // 获取平衡因子 + fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return self.height(node.?.left) - self.height(node.?.right); + } + + // 右旋操作 + fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.left; + var grandChild = child.?.right; + // 以 child 为原点,将 node 向右旋转 + child.?.right = node; + node.?.left = grandChild; + // 更新节点高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + // 左旋操作 + fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.right; + var grandChild = child.?.left; + // 以 child 为原点,将 node 向左旋转 + child.?.left = node; + node.?.right = grandChild; + // 更新节点高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + // 执行旋转操作,使该子树重新恢复平衡 + fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + // 获取节点 node 的平衡因子 + var balance_factor = self.balanceFactor(node); + // 左偏树 + if (balance_factor > 1) { + if (self.balanceFactor(node.?.left) >= 0) { + // 右旋 + return self.rightRotate(node); + } else { + // 先左旋后右旋 + node.?.left = self.leftRotate(node.?.left); + return self.rightRotate(node); + } + } + // 右偏树 + if (balance_factor < -1) { + if (self.balanceFactor(node.?.right) <= 0) { + // 左旋 + return self.leftRotate(node); + } else { + // 先右旋后左旋 + node.?.right = self.rightRotate(node.?.right); + return self.leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + + // 插入节点 + fn insert(self: *Self, val: T) !void { + self.root = (try self.insertHelper(self.root, val)).?; + } + + // 递归插入节点(辅助方法) + fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { + var node = node_; + if (node == null) { + var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); + tmp_node.init(val); + return tmp_node; + } + // 1. 查找插入位置并插入节点 + if (val < node.?.val) { + node.?.left = try self.insertHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = try self.insertHelper(node.?.right, val); + } else { + return node; // 重复节点不插入,直接返回 + } + self.updateHeight(node); // 更新节点高度 + // 2. 执行旋转操作,使该子树重新恢复平衡 + node = self.rotate(node); + // 返回子树的根节点 + return node; + } + + // 删除节点 + fn remove(self: *Self, val: T) void { + self.root = self.removeHelper(self.root, val).?; + } + + // 递归删除节点(辅助方法) + fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { + var node = node_; + if (node == null) return null; + // 1. 查找节点并删除 + if (val < node.?.val) { + node.?.left = self.removeHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = self.removeHelper(node.?.right, val); + } else { + if (node.?.left == null or node.?.right == null) { + var child = if (node.?.left != null) node.?.left else node.?.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) { + return null; + // 子节点数量 = 1 ,直接删除 node + } else { + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node.?.right; + while (temp.?.left != null) { + temp = temp.?.left; + } + node.?.right = self.removeHelper(node.?.right, temp.?.val); + node.?.val = temp.?.val; + } + } + self.updateHeight(node); // 更新节点高度 + // 2. 执行旋转操作,使该子树重新恢复平衡 + node = self.rotate(node); + // 返回子树的根节点 + return node; + } + + // 查找节点 + fn search(self: *Self, val: T) ?*inc.TreeNode(T) { + var cur = self.root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.?.val < val) { + cur = cur.?.right; + // 目标节点在 cur 的左子树中 + } else if (cur.?.val > val) { + cur = cur.?.left; + // 找到目标节点,跳出循环 + } else { + break; + } + } + // 返回目标节点 + return cur; + } + }; +} + +pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { + var tree = tree_; + try tree.insert(val); + std.debug.print("\n插入节点 {} 后,AVL 树为\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { + var tree = tree_; + tree.remove(val); + std.debug.print("\n删除节点 {} 后,AVL 树为\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +// Driver Code +pub fn main() !void { + // 初始化空 AVL 树 + var avl_tree = AVLTree(i32){}; + avl_tree.init(std.heap.page_allocator); + defer avl_tree.deinit(); + + // 插入节点 + // 请关注插入节点后,AVL 树是如何保持平衡的 + try testInsert(i32, &avl_tree, 1); + try testInsert(i32, &avl_tree, 2); + try testInsert(i32, &avl_tree, 3); + try testInsert(i32, &avl_tree, 4); + try testInsert(i32, &avl_tree, 5); + try testInsert(i32, &avl_tree, 8); + try testInsert(i32, &avl_tree, 7); + try testInsert(i32, &avl_tree, 9); + try testInsert(i32, &avl_tree, 10); + try testInsert(i32, &avl_tree, 6); + + // 插入重复节点 + try testInsert(i32, &avl_tree, 7); + + // 删除节点 + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(i32, &avl_tree, 8); // 删除度为 0 的节点 + testRemove(i32, &avl_tree, 5); // 删除度为 1 的节点 + testRemove(i32, &avl_tree, 4); // 删除度为 2 的节点 + + // 查找节点 + var node = avl_tree.search(7).?; + std.debug.print("\n查找到的节点对象为 {any},节点值 = {}\n", .{node, node.val}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_tree/binary_search_tree.zig b/codes/zig/chapter_tree/binary_search_tree.zig new file mode 100644 index 0000000000..bfc4a6e590 --- /dev/null +++ b/codes/zig/chapter_tree/binary_search_tree.zig @@ -0,0 +1,182 @@ +// File: binary_search_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二叉搜索树 +pub fn BinarySearchTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造方法 + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // 排序数组 + self.root = try self.buildTree(nums, 0, nums.len - 1); // 构建二叉搜索树 + } + + // 析构方法 + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 构建二叉搜索树 + fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { + if (i > j) return null; + // 将数组中间节点作为根节点 + var mid = i + (j - i) / 2; + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(nums[mid]); + // 递归建立左子树和右子树 + if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); + node.right = try self.buildTree(nums, mid + 1, j); + return node; + } + + // 获取二叉树根节点 + fn getRoot(self: *Self) ?*inc.TreeNode(T) { + return self.root; + } + + // 查找节点 + fn search(self: *Self, num: T) ?*inc.TreeNode(T) { + var cur = self.root; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 目标节点在 cur 的右子树中 + if (cur.?.val < num) { + cur = cur.?.right; + // 目标节点在 cur 的左子树中 + } else if (cur.?.val > num) { + cur = cur.?.left; + // 找到目标节点,跳出循环 + } else { + break; + } + } + // 返回目标节点 + return cur; + } + + // 插入节点 + fn insert(self: *Self, num: T) !void { + // 若树为空,则初始化根节点 + if (self.root == null) { + self.root = try self.mem_allocator.create(inc.TreeNode(T)); + return; + } + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.?.val == num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.?.val < num) { + cur = cur.?.right; + // 插入位置在 cur 的左子树中 + } else { + cur = cur.?.left; + } + } + // 插入节点 + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(num); + if (pre.?.val < num) { + pre.?.right = node; + } else { + pre.?.left = node; + } + } + + // 删除节点 + fn remove(self: *Self, num: T) void { + // 若树为空,直接提前返回 + if (self.root == null) return; + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到待删除节点,跳出循环 + if (cur.?.val == num) break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur.?.val < num) { + cur = cur.?.right; + // 待删除节点在 cur 的左子树中 + } else { + cur = cur.?.left; + } + } + // 若无待删除节点,则直接返回 + if (cur == null) return; + // 子节点数量 = 0 or 1 + if (cur.?.left == null or cur.?.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + var child = if (cur.?.left != null) cur.?.left else cur.?.right; + // 删除节点 cur + if (pre.?.left == cur) { + pre.?.left = child; + } else { + pre.?.right = child; + } + // 子节点数量 = 2 + } else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur.?.right; + while (tmp.?.left != null) { + tmp = tmp.?.left; + } + var tmp_val = tmp.?.val; + // 递归删除节点 tmp + self.remove(tmp.?.val); + // 用 tmp 覆盖 cur + cur.?.val = tmp_val; + } + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化二叉树 + var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + var bst = BinarySearchTree(i32){}; + try bst.init(std.heap.page_allocator, &nums); + defer bst.deinit(); + std.debug.print("初始化的二叉树为\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // 查找节点 + var node = bst.search(7); + std.debug.print("\n查找到的节点对象为 {any},节点值 = {}\n", .{node, node.?.val}); + + // 插入节点 + try bst.insert(16); + std.debug.print("\n插入节点 16 后,二叉树为\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // 删除节点 + bst.remove(1); + std.debug.print("\n删除节点 1 后,二叉树为\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(2); + std.debug.print("\n删除节点 2 后,二叉树为\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(4); + std.debug.print("\n删除节点 4 后,二叉树为\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/codes/zig/chapter_tree/binary_tree.zig b/codes/zig/chapter_tree/binary_tree.zig new file mode 100644 index 0000000000..5632ea2f0a --- /dev/null +++ b/codes/zig/chapter_tree/binary_tree.zig @@ -0,0 +1,39 @@ +// File: binary_tree.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化二叉树 + // 初始化节点 + var n1 = inc.TreeNode(i32){ .val = 1 }; + var n2 = inc.TreeNode(i32){ .val = 2 }; + var n3 = inc.TreeNode(i32){ .val = 3 }; + var n4 = inc.TreeNode(i32){ .val = 4 }; + var n5 = inc.TreeNode(i32){ .val = 5 }; + // 构建节点之间的引用(指针) + n1.left = &n2; + n1.right = &n3; + n2.left = &n4; + n2.right = &n5; + std.debug.print("初始化二叉树\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + // 插入与删除节点 + var p = inc.TreeNode(i32){ .val = 0 }; + // 在 n1 -> n2 中间插入节点 P + n1.left = &p; + p.left = &n2; + std.debug.print("插入节点 P 后\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + // 删除节点 + n1.left = &n2; + std.debug.print("删除节点 P 后\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/codes/zig/chapter_tree/binary_tree_bfs.zig b/codes/zig/chapter_tree/binary_tree_bfs.zig new file mode 100644 index 0000000000..632bcead8f --- /dev/null +++ b/codes/zig/chapter_tree/binary_tree_bfs.zig @@ -0,0 +1,57 @@ +// File: binary_tree_bfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 层序遍历 +fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { + // 初始化队列,加入根节点 + const L = std.TailQueue(*inc.TreeNode(T)); + var queue = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + queue.append(root_node); + // 初始化一个列表,用于保存遍历序列 + var list = std.ArrayList(T).init(std.heap.page_allocator); + while (queue.len > 0) { + var queue_node = queue.popFirst().?; // 队列出队 + var node = queue_node.data; + try list.append(node.val); // 保存节点值 + if (node.left != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + queue.append(tmp_node); // 左子节点入队 + } + if (node.right != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + queue.append(tmp_node); // 右子节点入队 + } + } + return list; +} + +// Driver Code +pub fn main() !void { + // 初始化内存分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("初始化二叉树\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // 层序遍历 + var list = try levelOrder(i32, mem_allocator, root.?); + defer list.deinit(); + std.debug.print("\n层序遍历的节点打印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/chapter_tree/binary_tree_dfs.zig b/codes/zig/chapter_tree/binary_tree_dfs.zig new file mode 100644 index 0000000000..d293ce58f3 --- /dev/null +++ b/codes/zig/chapter_tree/binary_tree_dfs.zig @@ -0,0 +1,70 @@ +// File: binary_tree_dfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +var list = std.ArrayList(i32).init(std.heap.page_allocator); + +// 前序遍历 +fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + try list.append(root.?.val); + try preOrder(T, root.?.left); + try preOrder(T, root.?.right); +} + +// 中序遍历 +fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + try inOrder(T, root.?.left); + try list.append(root.?.val); + try inOrder(T, root.?.right); +} + +// 后序遍历 +fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + try postOrder(T, root.?.left); + try postOrder(T, root.?.right); + try list.append(root.?.val); +} + +// Driver Code +pub fn main() !void { + // 初始化内存分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化二叉树 + // 这里借助了一个从数组直接生成二叉树的函数 + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("初始化二叉树\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // 前序遍历 + list.clearRetainingCapacity(); + try preOrder(i32, root); + std.debug.print("\n前序遍历的节点打印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 中序遍历 + list.clearRetainingCapacity(); + try inOrder(i32, root); + std.debug.print("\n中序遍历的节点打印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 后序遍历 + list.clearRetainingCapacity(); + try postOrder(i32, root); + std.debug.print("\n后续遍历的节点打印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/codes/zig/include/ListNode.zig b/codes/zig/include/ListNode.zig index ae0246c09d..f88f222fa4 100644 --- a/codes/zig/include/ListNode.zig +++ b/codes/zig/include/ListNode.zig @@ -1,11 +1,10 @@ // File: ListNode.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); -// Definition for a singly-linked list node -// 编译期泛型 +// 链表节点 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); @@ -16,6 +15,35 @@ pub fn ListNode(comptime T: type) type { // Initialize a list node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; + self.next = null; } }; +} + +// 将列表反序列化为链表 +pub fn listToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (list.items) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} + +// 将数组反序列化为链表 +pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (arr) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; } \ No newline at end of file diff --git a/codes/zig/include/PrintUtil.zig b/codes/zig/include/PrintUtil.zig index 4d2d208932..638082943c 100644 --- a/codes/zig/include/PrintUtil.zig +++ b/codes/zig/include/PrintUtil.zig @@ -1,16 +1,18 @@ // File: PrintUtil.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); -const ListNode = @import("ListNode.zig").ListNode; -const TreeNode = @import("TreeNode.zig").TreeNode; +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; -// Print an array +// 打印数组 pub fn printArray(comptime T: type, nums: []T) void { std.debug.print("[", .{}); if (nums.len > 0) { - for (nums) |num, j| { + for (nums, 0..) |num, j| { std.debug.print("{}{s}", .{num, if (j == nums.len-1) "]" else ", " }); } } else { @@ -18,34 +20,67 @@ pub fn printArray(comptime T: type, nums: []T) void { } } -// Print a list +// 打印列表 pub fn printList(comptime T: type, list: std.ArrayList(T)) void { std.debug.print("[", .{}); if (list.items.len > 0) { - for (list.items) |value, i| { + for (list.items, 0..) |value, i| { std.debug.print("{}{s}", .{value, if (i == list.items.len-1) "]" else ", " }); } } else { std.debug.print("]", .{}); } - } -// Print a linked list +// 打印链表 pub fn printLinkedList(comptime T: type, node: ?*ListNode(T)) !void { if (node == null) return; - var list = std.ArrayList(i32).init(std.heap.page_allocator); + var list = std.ArrayList(T).init(std.heap.page_allocator); defer list.deinit(); var head = node; while (head != null) { try list.append(head.?.val); head = head.?.next; } - for (list.items) |value, i| { + for (list.items, 0..) |value, i| { std.debug.print("{}{s}", .{value, if (i == list.items.len-1) "\n" else "->" }); } } +// 打印队列 +pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { + var node = queue.first; + std.debug.print("[", .{}); + var i: i32 = 0; + while (node != null) : (i += 1) { + var data = node.?.data; + std.debug.print("{}{s}", .{data, if (i == queue.len - 1) "]" else ", " }); + node = node.?.next; + } +} + +// 打印哈希表 +pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { + var it = map.iterator(); + while (it.next()) |kv| { + var key = kv.key_ptr.*; + var value = kv.value_ptr.*; + std.debug.print("{} -> {s}\n", .{key, value}); + } +} + +// 打印堆 +pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { + var arr = queue.items; + var len = queue.len; + std.debug.print("堆的数组表示:", .{}); + printArray(T, arr[0..len]); + std.debug.print("\n堆的树状表示:\n", .{}); + var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); + try printTree(root, null, false); +} + +// 打印二叉树 // This tree printer is borrowed from TECHIE DELIGHT // https://www.techiedelight.com/c-program-print-binary-tree/ const Trunk = struct { @@ -58,16 +93,14 @@ const Trunk = struct { } }; -// Helper function to print branches of the binary tree pub fn showTrunks(p: ?*Trunk) void { if (p == null) return; showTrunks(p.?.prev); std.debug.print("{s}", .{p.?.str}); } -// The interface of the tree printer -// Print a binary tree -pub fn printTree(root: ?*TreeNode(i32), prev: ?*Trunk, isLeft: bool) !void { +// 打印二叉树 +pub fn printTree(root: ?*TreeNode(i32), prev: ?*Trunk, isRight: bool) !void { if (root == null) { return; } @@ -79,7 +112,7 @@ pub fn printTree(root: ?*TreeNode(i32), prev: ?*Trunk, isLeft: bool) !void { if (prev == null) { trunk.str = "———"; - } else if (isLeft) { + } else if (isRight) { trunk.str = "/———"; prev_str = " |"; } else { diff --git a/codes/zig/include/TreeNode.zig b/codes/zig/include/TreeNode.zig index 12af9e8f35..ebcdbd3f78 100644 --- a/codes/zig/include/TreeNode.zig +++ b/codes/zig/include/TreeNode.zig @@ -1,22 +1,63 @@ // File: TreeNode.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) const std = @import("std"); -// Definition for a binary tree node -// 编译期泛型 +// 二叉树节点 pub fn TreeNode(comptime T: type) type { return struct { const Self = @This(); - val: T = undefined, - left: ?*Self = null, - right: ?*Self = null, + val: T = undefined, // 节点值 + height: i32 = undefined, // 节点高度 + left: ?*Self = null, // 左子节点指针 + right: ?*Self = null, // 右子节点指针 // Initialize a tree node with specific value pub fn init(self: *Self, x: i32) void { self.val = x; + self.height = 0; + self.left = null; + self.right = null; } }; +} + +// 将数组反序列化为二叉树 +pub fn arrToTree(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { + if (arr.len == 0) return null; + var root = try mem_allocator.create(TreeNode(T)); + root.init(arr[0]); + const L = std.TailQueue(*TreeNode(T)); + var que = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + que.append(root_node); + var index: usize = 0; + while (que.len > 0) { + var que_node = que.popFirst().?; + var node = que_node.data; + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try mem_allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.left = tmp; + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + que.append(tmp_node); + } + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try mem_allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.right = tmp; + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + que.append(tmp_node); + } + } + return root; } \ No newline at end of file diff --git a/codes/zig/include/include.zig b/codes/zig/include/include.zig index 20dfca90f9..cb98ffc0d3 100644 --- a/codes/zig/include/include.zig +++ b/codes/zig/include/include.zig @@ -1,7 +1,9 @@ // File: include.zig // Created Time: 2023-01-07 -// Author: sjinzh (sjinzh@gmail.com) +// Author: codingonion (coderonion@gmail.com) pub const PrintUtil = @import("PrintUtil.zig"); -pub const ListNode = @import("ListNode.zig").ListNode; -pub const TreeNode = @import("TreeNode.zig").TreeNode; \ No newline at end of file +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; \ No newline at end of file diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index 975d7b2f90..0000000000 --- a/deploy.sh +++ /dev/null @@ -1,8 +0,0 @@ -rm -rf ./site -mkdocs build --clean -cd site -git init -git add -A -git commit -m "deploy" -git push -f git@github.com:krahets/hello-algo.git master:gh-pages -cd - diff --git a/docker-compose.yml b/docker-compose.yml index 6a472b173a..d0c43a9f5e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ version: '3' services: hello-algo: build: . + image: hello-algo + container_name: hello-algo ports: - "8000:8000" - \ No newline at end of file diff --git a/docs/assets/avatar/avatar_Dr-XYZ.jpg b/docs/assets/avatar/avatar_Dr-XYZ.jpg new file mode 100644 index 0000000000..d29b06aafc Binary files /dev/null and b/docs/assets/avatar/avatar_Dr-XYZ.jpg differ diff --git a/docs/assets/avatar/avatar_Gonglja.jpg b/docs/assets/avatar/avatar_Gonglja.jpg new file mode 100644 index 0000000000..3bbcc65048 Binary files /dev/null and b/docs/assets/avatar/avatar_Gonglja.jpg differ diff --git a/docs/assets/avatar/avatar_K3v123.jpg b/docs/assets/avatar/avatar_K3v123.jpg new file mode 100644 index 0000000000..2ae215d624 Binary files /dev/null and b/docs/assets/avatar/avatar_K3v123.jpg differ diff --git a/docs/assets/avatar/avatar_Phoenix0415.jpg b/docs/assets/avatar/avatar_Phoenix0415.jpg new file mode 100644 index 0000000000..3de4ccd947 Binary files /dev/null and b/docs/assets/avatar/avatar_Phoenix0415.jpg differ diff --git a/docs/assets/avatar/avatar_QiLOL.jpg b/docs/assets/avatar/avatar_QiLOL.jpg new file mode 100644 index 0000000000..4d916dbc8c Binary files /dev/null and b/docs/assets/avatar/avatar_QiLOL.jpg differ diff --git a/docs/assets/avatar/avatar_RafaelCaso.jpg b/docs/assets/avatar/avatar_RafaelCaso.jpg new file mode 100644 index 0000000000..b9ba65d94c Binary files /dev/null and b/docs/assets/avatar/avatar_RafaelCaso.jpg differ diff --git a/docs/assets/avatar/avatar_Reanon.jpg b/docs/assets/avatar/avatar_Reanon.jpg new file mode 100644 index 0000000000..cd426218f4 Binary files /dev/null and b/docs/assets/avatar/avatar_Reanon.jpg differ diff --git a/docs/assets/avatar/avatar_SamJin98.jpg b/docs/assets/avatar/avatar_SamJin98.jpg new file mode 100644 index 0000000000..8321f13c6c Binary files /dev/null and b/docs/assets/avatar/avatar_SamJin98.jpg differ diff --git a/docs/assets/avatar/avatar_Shyam-Chen.jpg b/docs/assets/avatar/avatar_Shyam-Chen.jpg new file mode 100644 index 0000000000..5273edf375 Binary files /dev/null and b/docs/assets/avatar/avatar_Shyam-Chen.jpg differ diff --git a/docs/assets/avatar/avatar_coderonion.jpg b/docs/assets/avatar/avatar_coderonion.jpg new file mode 100644 index 0000000000..53410ddc4d Binary files /dev/null and b/docs/assets/avatar/avatar_coderonion.jpg differ diff --git a/docs/assets/avatar/avatar_curtishd.jpg b/docs/assets/avatar/avatar_curtishd.jpg new file mode 100644 index 0000000000..40d47610b5 Binary files /dev/null and b/docs/assets/avatar/avatar_curtishd.jpg differ diff --git a/docs/assets/avatar/avatar_gvenusleo.jpg b/docs/assets/avatar/avatar_gvenusleo.jpg new file mode 100644 index 0000000000..40f4dd251d Binary files /dev/null and b/docs/assets/avatar/avatar_gvenusleo.jpg differ diff --git a/docs/assets/avatar/avatar_hpstory.jpg b/docs/assets/avatar/avatar_hpstory.jpg new file mode 100644 index 0000000000..fa98c89abf Binary files /dev/null and b/docs/assets/avatar/avatar_hpstory.jpg differ diff --git a/docs/assets/avatar/avatar_justin-tse.jpg b/docs/assets/avatar/avatar_justin-tse.jpg new file mode 100644 index 0000000000..d3260f3183 Binary files /dev/null and b/docs/assets/avatar/avatar_justin-tse.jpg differ diff --git a/docs/assets/avatar/avatar_khoaxuantu.jpg b/docs/assets/avatar/avatar_khoaxuantu.jpg new file mode 100644 index 0000000000..f7e1e8f3cc Binary files /dev/null and b/docs/assets/avatar/avatar_khoaxuantu.jpg differ diff --git a/docs/assets/avatar/avatar_krahets.jpg b/docs/assets/avatar/avatar_krahets.jpg new file mode 100644 index 0000000000..9ab8259e78 Binary files /dev/null and b/docs/assets/avatar/avatar_krahets.jpg differ diff --git a/docs/assets/avatar/avatar_magentaqin.jpg b/docs/assets/avatar/avatar_magentaqin.jpg new file mode 100644 index 0000000000..e8b7ddee5a Binary files /dev/null and b/docs/assets/avatar/avatar_magentaqin.jpg differ diff --git a/docs/assets/avatar/avatar_night-cruise.jpg b/docs/assets/avatar/avatar_night-cruise.jpg new file mode 100644 index 0000000000..4dd6f70f79 Binary files /dev/null and b/docs/assets/avatar/avatar_night-cruise.jpg differ diff --git a/docs/assets/avatar/avatar_nuomi1.jpg b/docs/assets/avatar/avatar_nuomi1.jpg new file mode 100644 index 0000000000..d7d3a67aa1 Binary files /dev/null and b/docs/assets/avatar/avatar_nuomi1.jpg differ diff --git a/docs/assets/avatar/avatar_pengchzn.jpg b/docs/assets/avatar/avatar_pengchzn.jpg new file mode 100644 index 0000000000..73732dc836 Binary files /dev/null and b/docs/assets/avatar/avatar_pengchzn.jpg differ diff --git a/docs/assets/avatar/avatar_rongyi.jpg b/docs/assets/avatar/avatar_rongyi.jpg new file mode 100644 index 0000000000..6ef6f0e0b5 Binary files /dev/null and b/docs/assets/avatar/avatar_rongyi.jpg differ diff --git a/docs/assets/avatar/avatar_thomasq0.jpg b/docs/assets/avatar/avatar_thomasq0.jpg new file mode 100644 index 0000000000..beeb6d96c9 Binary files /dev/null and b/docs/assets/avatar/avatar_thomasq0.jpg differ diff --git a/docs/assets/avatar/avatar_yanedie.jpg b/docs/assets/avatar/avatar_yanedie.jpg new file mode 100644 index 0000000000..8867a24fcf Binary files /dev/null and b/docs/assets/avatar/avatar_yanedie.jpg differ diff --git a/docs/assets/avatar/avatar_yudongjin.jpg b/docs/assets/avatar/avatar_yudongjin.jpg new file mode 100644 index 0000000000..64f75e1343 Binary files /dev/null and b/docs/assets/avatar/avatar_yudongjin.jpg differ diff --git a/docs/assets/avatar/avatar_yuelinxin.jpg b/docs/assets/avatar/avatar_yuelinxin.jpg new file mode 100644 index 0000000000..f1ab8b96a8 Binary files /dev/null and b/docs/assets/avatar/avatar_yuelinxin.jpg differ diff --git "a/docs/assets/course/hello-algo-0.1-\350\257\276\347\250\213\347\256\200\344\273\213.pdf" "b/docs/assets/course/hello-algo-0.1-\350\257\276\347\250\213\347\256\200\344\273\213.pdf" new file mode 100644 index 0000000000..9904b865ce Binary files /dev/null and "b/docs/assets/course/hello-algo-0.1-\350\257\276\347\250\213\347\256\200\344\273\213.pdf" differ diff --git a/docs/assets/covers/chapter_appendix.jpg b/docs/assets/covers/chapter_appendix.jpg new file mode 100644 index 0000000000..f88d5ccc7d Binary files /dev/null and b/docs/assets/covers/chapter_appendix.jpg differ diff --git a/docs/assets/covers/chapter_array_and_linkedlist.jpg b/docs/assets/covers/chapter_array_and_linkedlist.jpg new file mode 100644 index 0000000000..624f21b3b9 Binary files /dev/null and b/docs/assets/covers/chapter_array_and_linkedlist.jpg differ diff --git a/docs/assets/covers/chapter_backtracking.jpg b/docs/assets/covers/chapter_backtracking.jpg new file mode 100644 index 0000000000..bfc2687682 Binary files /dev/null and b/docs/assets/covers/chapter_backtracking.jpg differ diff --git a/docs/assets/covers/chapter_complexity_analysis.jpg b/docs/assets/covers/chapter_complexity_analysis.jpg new file mode 100644 index 0000000000..67e75e007c Binary files /dev/null and b/docs/assets/covers/chapter_complexity_analysis.jpg differ diff --git a/docs/assets/covers/chapter_data_structure.jpg b/docs/assets/covers/chapter_data_structure.jpg new file mode 100644 index 0000000000..61892bd518 Binary files /dev/null and b/docs/assets/covers/chapter_data_structure.jpg differ diff --git a/docs/assets/covers/chapter_divide_and_conquer.jpg b/docs/assets/covers/chapter_divide_and_conquer.jpg new file mode 100644 index 0000000000..96613abe18 Binary files /dev/null and b/docs/assets/covers/chapter_divide_and_conquer.jpg differ diff --git a/docs/assets/covers/chapter_dynamic_programming.jpg b/docs/assets/covers/chapter_dynamic_programming.jpg new file mode 100644 index 0000000000..416f3b447f Binary files /dev/null and b/docs/assets/covers/chapter_dynamic_programming.jpg differ diff --git a/docs/assets/covers/chapter_graph.jpg b/docs/assets/covers/chapter_graph.jpg new file mode 100644 index 0000000000..e02c1a5891 Binary files /dev/null and b/docs/assets/covers/chapter_graph.jpg differ diff --git a/docs/assets/covers/chapter_greedy.jpg b/docs/assets/covers/chapter_greedy.jpg new file mode 100644 index 0000000000..09bba4d2a9 Binary files /dev/null and b/docs/assets/covers/chapter_greedy.jpg differ diff --git a/docs/assets/covers/chapter_hashing.jpg b/docs/assets/covers/chapter_hashing.jpg new file mode 100644 index 0000000000..7289be32e2 Binary files /dev/null and b/docs/assets/covers/chapter_hashing.jpg differ diff --git a/docs/assets/covers/chapter_heap.jpg b/docs/assets/covers/chapter_heap.jpg new file mode 100644 index 0000000000..63630beef1 Binary files /dev/null and b/docs/assets/covers/chapter_heap.jpg differ diff --git a/docs/assets/covers/chapter_hello_algo.jpg b/docs/assets/covers/chapter_hello_algo.jpg new file mode 100644 index 0000000000..8e347b3a4b Binary files /dev/null and b/docs/assets/covers/chapter_hello_algo.jpg differ diff --git a/docs/assets/covers/chapter_introduction.jpg b/docs/assets/covers/chapter_introduction.jpg new file mode 100644 index 0000000000..73356b8860 Binary files /dev/null and b/docs/assets/covers/chapter_introduction.jpg differ diff --git a/docs/assets/covers/chapter_preface.jpg b/docs/assets/covers/chapter_preface.jpg new file mode 100644 index 0000000000..8414897ad8 Binary files /dev/null and b/docs/assets/covers/chapter_preface.jpg differ diff --git a/docs/assets/covers/chapter_searching.jpg b/docs/assets/covers/chapter_searching.jpg new file mode 100644 index 0000000000..0a52261117 Binary files /dev/null and b/docs/assets/covers/chapter_searching.jpg differ diff --git a/docs/assets/covers/chapter_sorting.jpg b/docs/assets/covers/chapter_sorting.jpg new file mode 100644 index 0000000000..63b252e76f Binary files /dev/null and b/docs/assets/covers/chapter_sorting.jpg differ diff --git a/docs/assets/covers/chapter_stack_and_queue.jpg b/docs/assets/covers/chapter_stack_and_queue.jpg new file mode 100644 index 0000000000..e8cd51bfc6 Binary files /dev/null and b/docs/assets/covers/chapter_stack_and_queue.jpg differ diff --git a/docs/assets/covers/chapter_tree.jpg b/docs/assets/covers/chapter_tree.jpg new file mode 100644 index 0000000000..7d472d3fca Binary files /dev/null and b/docs/assets/covers/chapter_tree.jpg differ diff --git a/docs/assets/hero/astronaut.png b/docs/assets/hero/astronaut.png new file mode 100644 index 0000000000..2d63a9f7a4 Binary files /dev/null and b/docs/assets/hero/astronaut.png differ diff --git a/docs/assets/hero/chapter_array_and_linkedlist.png b/docs/assets/hero/chapter_array_and_linkedlist.png new file mode 100644 index 0000000000..376c1da626 Binary files /dev/null and b/docs/assets/hero/chapter_array_and_linkedlist.png differ diff --git a/docs/assets/hero/chapter_backtracking.png b/docs/assets/hero/chapter_backtracking.png new file mode 100644 index 0000000000..af259be6fb Binary files /dev/null and b/docs/assets/hero/chapter_backtracking.png differ diff --git a/docs/assets/hero/chapter_computational_complexity.png b/docs/assets/hero/chapter_computational_complexity.png new file mode 100644 index 0000000000..7e5bf9eaf8 Binary files /dev/null and b/docs/assets/hero/chapter_computational_complexity.png differ diff --git a/docs/assets/hero/chapter_divide_and_conquer.png b/docs/assets/hero/chapter_divide_and_conquer.png new file mode 100644 index 0000000000..63794b27c8 Binary files /dev/null and b/docs/assets/hero/chapter_divide_and_conquer.png differ diff --git a/docs/assets/hero/chapter_dynamic_programming.png b/docs/assets/hero/chapter_dynamic_programming.png new file mode 100644 index 0000000000..77b05dc493 Binary files /dev/null and b/docs/assets/hero/chapter_dynamic_programming.png differ diff --git a/docs/assets/hero/chapter_graph.png b/docs/assets/hero/chapter_graph.png new file mode 100644 index 0000000000..667ecb8222 Binary files /dev/null and b/docs/assets/hero/chapter_graph.png differ diff --git a/docs/assets/hero/chapter_greedy.png b/docs/assets/hero/chapter_greedy.png new file mode 100644 index 0000000000..2ffc334481 Binary files /dev/null and b/docs/assets/hero/chapter_greedy.png differ diff --git a/docs/assets/hero/chapter_hashing.png b/docs/assets/hero/chapter_hashing.png new file mode 100644 index 0000000000..cf375aa7ce Binary files /dev/null and b/docs/assets/hero/chapter_hashing.png differ diff --git a/docs/assets/hero/chapter_heap.png b/docs/assets/hero/chapter_heap.png new file mode 100644 index 0000000000..97e3bef911 Binary files /dev/null and b/docs/assets/hero/chapter_heap.png differ diff --git a/docs/assets/hero/chapter_searching.png b/docs/assets/hero/chapter_searching.png new file mode 100644 index 0000000000..c694bd1450 Binary files /dev/null and b/docs/assets/hero/chapter_searching.png differ diff --git a/docs/assets/hero/chapter_sorting.png b/docs/assets/hero/chapter_sorting.png new file mode 100644 index 0000000000..1863d864db Binary files /dev/null and b/docs/assets/hero/chapter_sorting.png differ diff --git a/docs/assets/hero/chapter_stack_and_queue.png b/docs/assets/hero/chapter_stack_and_queue.png new file mode 100644 index 0000000000..c8581279a6 Binary files /dev/null and b/docs/assets/hero/chapter_stack_and_queue.png differ diff --git a/docs/assets/hero/chapter_tree.png b/docs/assets/hero/chapter_tree.png new file mode 100644 index 0000000000..117fc603a6 Binary files /dev/null and b/docs/assets/hero/chapter_tree.png differ diff --git a/docs/assets/hero/cover_render.png b/docs/assets/hero/cover_render.png new file mode 100644 index 0000000000..ec1364272a Binary files /dev/null and b/docs/assets/hero/cover_render.png differ diff --git a/docs/assets/hero/ground.png b/docs/assets/hero/ground.png new file mode 100644 index 0000000000..eece5b0bd9 Binary files /dev/null and b/docs/assets/hero/ground.png differ diff --git a/docs/assets/hero/links.png b/docs/assets/hero/links.png new file mode 100644 index 0000000000..a0189f949c Binary files /dev/null and b/docs/assets/hero/links.png differ diff --git a/docs/assets/hero/pdf_ipad.png b/docs/assets/hero/pdf_ipad.png new file mode 100644 index 0000000000..82697ed347 Binary files /dev/null and b/docs/assets/hero/pdf_ipad.png differ diff --git a/docs/assets/hero/universe_bg.png b/docs/assets/hero/universe_bg.png new file mode 100644 index 0000000000..9da001555b Binary files /dev/null and b/docs/assets/hero/universe_bg.png differ diff --git a/docs/assets/hero/web_mac_iphone.png b/docs/assets/hero/web_mac_iphone.png new file mode 100644 index 0000000000..cc8e28d0fb Binary files /dev/null and b/docs/assets/hero/web_mac_iphone.png differ diff --git a/docs/assets/images/favicon.png b/docs/assets/images/favicon.png deleted file mode 100644 index 285fefc970..0000000000 Binary files a/docs/assets/images/favicon.png and /dev/null differ diff --git a/docs/assets/images/logo.png b/docs/assets/images/logo.png deleted file mode 100644 index 6fc6dab37c..0000000000 Binary files a/docs/assets/images/logo.png and /dev/null differ diff --git a/docs/assets/images/profile.png b/docs/assets/images/profile.png deleted file mode 100644 index aeabdeb913..0000000000 Binary files a/docs/assets/images/profile.png and /dev/null differ diff --git a/docs/chapter_appendix/contribution.assets/edit_markdown.png b/docs/chapter_appendix/contribution.assets/edit_markdown.png new file mode 100644 index 0000000000..c11dce4fbe Binary files /dev/null and b/docs/chapter_appendix/contribution.assets/edit_markdown.png differ diff --git a/docs/chapter_appendix/contribution.md b/docs/chapter_appendix/contribution.md new file mode 100644 index 0000000000..ad5a56545d --- /dev/null +++ b/docs/chapter_appendix/contribution.md @@ -0,0 +1,47 @@ +# 一起参与创作 + +由于笔者能力有限,书中难免存在一些遗漏和错误,请您谅解。如果您发现了笔误、链接失效、内容缺失、文字歧义、解释不清晰或行文结构不合理等问题,请协助我们进行修正,以给读者提供更优质的学习资源。 + +所有[撰稿人](https://github.com/krahets/hello-algo/graphs/contributors)的 GitHub ID 将在本书仓库、网页版和 PDF 版的主页上进行展示,以感谢他们对开源社区的无私奉献。 + +!!! success "开源的魅力" + + 纸质图书的两次印刷的间隔时间往往较久,内容更新非常不方便。 + + 而在本开源书中,内容更迭的时间被缩短至数日甚至几个小时。 + +### 内容微调 + +如下图所示,每个页面的右上角都有“编辑图标”。您可以按照以下步骤修改文本或代码。 + +1. 点击“编辑图标”,如果遇到“需要 Fork 此仓库”的提示,请同意该操作。 +2. 修改 Markdown 源文件内容,检查内容的正确性,并尽量保持排版格式的统一。 +3. 在页面底部填写修改说明,然后点击“Propose file change”按钮。页面跳转后,点击“Create pull request”按钮即可发起拉取请求。 + +![页面编辑按键](contribution.assets/edit_markdown.png) + +图片无法直接修改,需要通过新建 [Issue](https://github.com/krahets/hello-algo/issues) 或评论留言来描述问题,我们会尽快重新绘制并替换图片。 + +### 内容创作 + +如果您有兴趣参与此开源项目,包括将代码翻译成其他编程语言、扩展文章内容等,那么需要实施以下 Pull Request 工作流程。 + +1. 登录 GitHub ,将本书的[代码仓库](https://github.com/krahets/hello-algo) Fork 到个人账号下。 +2. 进入您的 Fork 仓库网页,使用 `git clone` 命令将仓库克隆至本地。 +3. 在本地进行内容创作,并进行完整测试,验证代码的正确性。 +4. 将本地所做更改 Commit ,然后 Push 至远程仓库。 +5. 刷新仓库网页,点击“Create pull request”按钮即可发起拉取请求。 + +### Docker 部署 + +在 `hello-algo` 根目录下,执行以下 Docker 脚本,即可在 `http://localhost:8000` 访问本项目: + +```shell +docker-compose up -d +``` + +使用以下命令即可删除部署: + +```shell +docker-compose down +``` diff --git a/docs/chapter_appendix/index.md b/docs/chapter_appendix/index.md new file mode 100644 index 0000000000..44aa38a957 --- /dev/null +++ b/docs/chapter_appendix/index.md @@ -0,0 +1,3 @@ +# 附录 + +![附录](../assets/covers/chapter_appendix.jpg) diff --git a/docs/chapter_appendix/installation.assets/vscode_extension_installation.png b/docs/chapter_appendix/installation.assets/vscode_extension_installation.png new file mode 100644 index 0000000000..3eabc83d05 Binary files /dev/null and b/docs/chapter_appendix/installation.assets/vscode_extension_installation.png differ diff --git a/docs/chapter_appendix/installation.assets/vscode_installation.png b/docs/chapter_appendix/installation.assets/vscode_installation.png new file mode 100644 index 0000000000..b0e09d459b Binary files /dev/null and b/docs/chapter_appendix/installation.assets/vscode_installation.png differ diff --git a/docs/chapter_appendix/installation.md b/docs/chapter_appendix/installation.md new file mode 100644 index 0000000000..b0722afcda --- /dev/null +++ b/docs/chapter_appendix/installation.md @@ -0,0 +1,68 @@ +# 编程环境安装 + +## 安装 IDE + +推荐使用开源、轻量的 VS Code 作为本地集成开发环境(IDE)。访问 [VS Code 官网](https://code.visualstudio.com/),根据操作系统选择相应版本的 VS Code 进行下载和安装。 + +![从官网下载 VS Code](installation.assets/vscode_installation.png) + +VS Code 拥有强大的扩展包生态系统,支持大多数编程语言的运行和调试。以 Python 为例,安装“Python Extension Pack”扩展包之后,即可进行 Python 代码调试。安装步骤如下图所示。 + +![安装 VS Code 扩展包](installation.assets/vscode_extension_installation.png) + +## 安装语言环境 + +### Python 环境 + +1. 下载并安装 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) ,需要 Python 3.10 或更新版本。 +2. 在 VS Code 的插件市场中搜索 `python` ,安装 Python Extension Pack 。 +3. (可选)在命令行输入 `pip install black` ,安装代码格式化工具。 + +### C/C++ 环境 + +1. Windows 系统需要安装 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)([配置教程](https://blog.csdn.net/qq_33698226/article/details/129031241));MacOS 自带 Clang ,无须安装。 +2. 在 VS Code 的插件市场中搜索 `c++` ,安装 C/C++ Extension Pack 。 +3. (可选)打开 Settings 页面,搜索 `Clang_format_fallback Style` 代码格式化选项,设置为 `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` 。 + +### Java 环境 + +1. 下载并安装 [OpenJDK](https://jdk.java.net/18/)(版本需满足 > JDK 9)。 +2. 在 VS Code 的插件市场中搜索 `java` ,安装 Extension Pack for Java 。 + +### C# 环境 + +1. 下载并安装 [.Net 8.0](https://dotnet.microsoft.com/en-us/download) 。 +2. 在 VS Code 的插件市场中搜索 `C# Dev Kit` ,安装 C# Dev Kit ([配置教程](https://code.visualstudio.com/docs/csharp/get-started))。 +3. 也可使用 Visual Studio([安装教程](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 + +### Go 环境 + +1. 下载并安装 [go](https://go.dev/dl/) 。 +2. 在 VS Code 的插件市场中搜索 `go` ,安装 Go 。 +3. 按快捷键 `Ctrl + Shift + P` 呼出命令栏,输入 go ,选择 `Go: Install/Update Tools` ,全部勾选并安装即可。 + +### Swift 环境 + +1. 下载并安装 [Swift](https://www.swift.org/download/) 。 +2. 在 VS Code 的插件市场中搜索 `swift` ,安装 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) 。 + +### JavaScript 环境 + +1. 下载并安装 [Node.js](https://nodejs.org/en/) 。 +2. (可选)在 VS Code 的插件市场中搜索 `Prettier` ,安装代码格式化工具。 + +### TypeScript 环境 + +1. 同 JavaScript 环境安装步骤。 +2. 安装 [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation) 。 +3. 在 VS Code 的插件市场中搜索 `typescript` ,安装 [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) 。 + +### Dart 环境 + +1. 下载并安装 [Dart](https://dart.dev/get-dart) 。 +2. 在 VS Code 的插件市场中搜索 `dart` ,安装 [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) 。 + +### Rust 环境 + +1. 下载并安装 [Rust](https://www.rust-lang.org/tools/install) 。 +2. 在 VS Code 的插件市场中搜索 `rust` ,安装 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 。 diff --git a/docs/chapter_appendix/terminology.md b/docs/chapter_appendix/terminology.md new file mode 100644 index 0000000000..5ff24b7cc5 --- /dev/null +++ b/docs/chapter_appendix/terminology.md @@ -0,0 +1,137 @@ +# 术语表 + +下表列出了书中出现的重要术语,值得注意以下几点。 + +- 建议记住名词的英文叫法,以便阅读英文文献。 +- 部分名词在简体中文和繁体中文下的叫法不同。 + +

  数据结构与算法的重要名词

+ +| English | 简体中文 | 繁体中文 | +| ------------------------------ | -------------- | -------------- | +| algorithm | 算法 | 演算法 | +| data structure | 数据结构 | 資料結構 | +| code | 代码 | 程式碼 | +| file | 文件 | 檔案 | +| function | 函数 | 函式 | +| method | 方法 | 方法 | +| variable | 变量 | 變數 | +| asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 | +| time complexity | 时间复杂度 | 時間複雜度 | +| space complexity | 空间复杂度 | 空間複雜度 | +| loop | 循环 | 迴圈 | +| iteration | 迭代 | 迭代 | +| recursion | 递归 | 遞迴 | +| tail recursion | 尾递归 | 尾遞迴 | +| recursion tree | 递归树 | 遞迴樹 | +| big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 | +| asymptotic upper bound | 渐近上界 | 漸近上界 | +| sign-magnitude | 原码 | 原碼 | +| 1’s complement | 反码 | 一補數 | +| 2’s complement | 补码 | 二補數 | +| array | 数组 | 陣列 | +| index | 索引 | 索引 | +| linked list | 链表 | 鏈結串列 | +| linked list node, list node | 链表节点 | 鏈結串列節點 | +| head node | 头节点 | 頭節點 | +| tail node | 尾节点 | 尾節點 | +| list | 列表 | 串列 | +| dynamic array | 动态数组 | 動態陣列 | +| hard disk | 硬盘 | 硬碟 | +| random-access memory (RAM) | 内存 | 記憶體 | +| cache memory | 缓存 | 快取 | +| cache miss | 缓存未命中 | 快取未命中 | +| cache hit rate | 缓存命中率 | 快取命中率 | +| stack | 栈 | 堆疊 | +| top of the stack | 栈顶 | 堆疊頂 | +| bottom of the stack | 栈底 | 堆疊底 | +| queue | 队列 | 佇列 | +| double-ended queue | 双向队列 | 雙向佇列 | +| front of the queue | 队首 | 佇列首 | +| rear of the queue | 队尾 | 佇列尾 | +| hash table | 哈希表 | 雜湊表 | +| hash set | 哈希集合 | 雜湊集合 | +| bucket | 桶 | 桶 | +| hash function | 哈希函数 | 雜湊函式 | +| hash collision | 哈希冲突 | 雜湊衝突 | +| load factor | 负载因子 | 負載因子 | +| separate chaining | 链式地址 | 鏈結位址 | +| open addressing | 开放寻址 | 開放定址 | +| linear probing | 线性探测 | 線性探查 | +| lazy deletion | 懒删除 | 懶刪除 | +| binary tree | 二叉树 | 二元樹 | +| tree node | 树节点 | 樹節點 | +| left-child node | 左子节点 | 左子節點 | +| right-child node | 右子节点 | 右子節點 | +| parent node | 父节点 | 父節點 | +| left subtree | 左子树 | 左子樹 | +| right subtree | 右子树 | 右子樹 | +| root node | 根节点 | 根節點 | +| leaf node | 叶节点 | 葉節點 | +| edge | 边 | 邊 | +| level | 层 | 層 | +| degree | 度 | 度 | +| height | 高度 | 高度 | +| depth | 深度 | 深度 | +| perfect binary tree | 完美二叉树 | 完美二元樹 | +| complete binary tree | 完全二叉树 | 完全二元樹 | +| full binary tree | 完满二叉树 | 完滿二元樹 | +| balanced binary tree | 平衡二叉树 | 平衡二元樹 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| AVL tree | AVL 树 | AVL 樹 | +| red-black tree | 红黑树 | 紅黑樹 | +| level-order traversal | 层序遍历 | 層序走訪 | +| breadth-first traversal | 广度优先遍历 | 廣度優先走訪 | +| depth-first traversal | 深度优先遍历 | 深度優先走訪 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 | +| balance factor | 平衡因子 | 平衡因子 | +| heap | 堆 | 堆積 | +| max heap | 大顶堆 | 大頂堆積 | +| min heap | 小顶堆 | 小頂堆積 | +| priority queue | 优先队列 | 優先佇列 | +| heapify | 堆化 | 堆積化 | +| top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 | +| graph | 图 | 圖 | +| vertex | 顶点 | 頂點 | +| undirected graph | 无向图 | 無向圖 | +| directed graph | 有向图 | 有向圖 | +| connected graph | 连通图 | 連通圖 | +| disconnected graph | 非连通图 | 非連通圖 | +| weighted graph | 有权图 | 有權圖 | +| adjacency | 邻接 | 鄰接 | +| path | 路径 | 路徑 | +| in-degree | 入度 | 入度 | +| out-degree | 出度 | 出度 | +| adjacency matrix | 邻接矩阵 | 鄰接矩陣 | +| adjacency list | 邻接表 | 鄰接表 | +| breadth-first search | 广度优先搜索 | 廣度優先搜尋 | +| depth-first search | 深度优先搜索 | 深度優先搜尋 | +| binary search | 二分查找 | 二分搜尋 | +| searching algorithm | 搜索算法 | 搜尋演算法 | +| sorting algorithm | 排序算法 | 排序演算法 | +| selection sort | 选择排序 | 選擇排序 | +| bubble sort | 冒泡排序 | 泡沫排序 | +| insertion sort | 插入排序 | 插入排序 | +| quick sort | 快速排序 | 快速排序 | +| merge sort | 归并排序 | 合併排序 | +| heap sort | 堆排序 | 堆積排序 | +| bucket sort | 桶排序 | 桶排序 | +| counting sort | 计数排序 | 計數排序 | +| radix sort | 基数排序 | 基數排序 | +| divide and conquer | 分治 | 分治 | +| hanota problem | 汉诺塔问题 | 河內塔問題 | +| backtracking algorithm | 回溯算法 | 回溯演算法 | +| constraint | 约束 | 約束 | +| solution | 解 | 解 | +| state | 状态 | 狀態 | +| pruning | 剪枝 | 剪枝 | +| permutations problem | 全排列问题 | 全排列問題 | +| subset-sum problem | 子集和问题 | 子集合問題 | +| $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 | +| dynamic programming | 动态规划 | 動態規劃 | +| initial state | 初始状态 | 初始狀態 | +| state-transition equation | 状态转移方程 | 狀態轉移方程 | +| knapsack problem | 背包问题 | 背包問題 | +| edit distance problem | 编辑距离问题 | 編輯距離問題 | +| greedy algorithm | 贪心算法 | 貪婪演算法 | diff --git a/docs/chapter_array_and_linkedlist/array.assets/array_definition.png b/docs/chapter_array_and_linkedlist/array.assets/array_definition.png index f5cb45aa0b..3703111c4f 100644 Binary files a/docs/chapter_array_and_linkedlist/array.assets/array_definition.png and b/docs/chapter_array_and_linkedlist/array.assets/array_definition.png differ diff --git a/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png b/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png new file mode 100644 index 0000000000..05a9621a12 Binary files /dev/null and b/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png differ diff --git a/docs/chapter_array_and_linkedlist/array.assets/array_insert_remove_element.png b/docs/chapter_array_and_linkedlist/array.assets/array_insert_remove_element.png deleted file mode 100644 index 6e02d129bb..0000000000 Binary files a/docs/chapter_array_and_linkedlist/array.assets/array_insert_remove_element.png and /dev/null differ diff --git a/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png b/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png index 6565145f82..3f5fcde7df 100644 Binary files a/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png and b/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png differ diff --git a/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png b/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png new file mode 100644 index 0000000000..4b79529e7d Binary files /dev/null and b/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png differ diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md old mode 100644 new mode 100755 index 2251362d0e..98452f0b70 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -1,20 +1,34 @@ ---- -comments: true ---- - # 数组 -「数组 Array」是一种将 **相同类型元素** 存储在 **连续内存空间** 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。 +数组(array)是一种线性数据结构,其将相同类型的元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的索引(index)。下图展示了数组的主要概念和存储方式。 + +![数组定义与存储方式](array.assets/array_definition.png) + +## 数组常用操作 -![array_definition](array.assets/array_definition.png) +### 初始化数组 -

Fig. 数组定义与存储方式

+我们可以根据需求选用数组的两种初始化方式:无初始值、给定初始值。在未指定初始值的情况下,大多数编程语言会将数组元素初始化为 $0$ : -!!! note +=== "Python" - 观察上图,我们发现 **数组首元素的索引为 $0$** 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 $1$ 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。 + ```python title="array.py" + # 初始化数组 + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] + ``` -**数组有多种初始化写法**。根据实际需要,选代码最短的那一种就好。 +=== "C++" + + ```cpp title="array.cpp" + /* 初始化数组 */ + // 存储在栈上 + int arr[5]; + int nums[5] = { 1, 3, 2, 5, 4 }; + // 存储在堆上(需要手动释放空间) + int* arr1 = new int[5]; + int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; + ``` === "Java" @@ -24,20 +38,12 @@ comments: true int[] nums = { 1, 3, 2, 5, 4 }; ``` -=== "C++" +=== "C#" - ```cpp title="array.cpp" + ```csharp title="array.cs" /* 初始化数组 */ - int* arr = new int[5]; - int* nums = new int[5] { 1, 3, 2, 5, 4 }; - ``` - -=== "Python" - - ```python title="array.py" - """ 初始化数组 """ - arr = [0] * 5 # [ 0, 0, 0, 0, 0 ] - nums = [1, 3, 2, 5, 4] + int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] + int[] nums = [1, 3, 2, 5, 4]; ``` === "Go" @@ -51,36 +57,6 @@ comments: true nums := []int{1, 3, 2, 5, 4} ``` -=== "JavaScript" - - ```javascript title="array.js" - /* 初始化数组 */ - var arr = new Array(5).fill(0); - var nums = [1, 3, 2, 5, 4]; - ``` - -=== "TypeScript" - - ```typescript title="array.ts" - /* 初始化数组 */ - let arr: number[] = new Array(5).fill(0); - let nums: number[] = [1, 3, 2, 5, 4]; - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 初始化数组 */ - int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } - int[] nums = { 1, 3, 2, 5, 4 }; - ``` - === "Swift" ```swift title="array.swift" @@ -89,725 +65,171 @@ comments: true let nums = [1, 3, 2, 5, 4] ``` -## 数组优点 - -**在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 - -![array_memory_location_calculation](array.assets/array_memory_location_calculation.png) - -

Fig. 数组元素的内存地址计算

- -```java title="" -// 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引 -elementAddr = firtstElementAddr + elementLength * elementIndex -``` - -**为什么数组元素索引从 0 开始编号?** 根据地址计算公式,**索引本质上表示的是内存地址偏移量**,首个元素的地址偏移量是 $0$ ,那么索引是 $0$ 也就很自然了。 - -访问元素的高效性带来了许多便利。例如,我们可以在 $O(1)$ 时间内随机获取一个数组中的元素。 - -=== "Java" - - ```java title="array.java" - /* 随机返回一个数组元素 */ - int randomAccess(int[] nums) { - int randomIndex = ThreadLocalRandom.current(). - nextInt(0, nums.length); - int randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 随机返回一个数组元素 */ - int randomAccess(int* nums, int size) { - // 在区间 [0, size) 中随机抽取一个数字 - int randomIndex = rand() % size; - // 获取并返回随机元素 - int randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -=== "Python" - - ```python title="array.py" - """ 随机访问元素 """ - def randomAccess(nums): - # 在区间 [0, len(nums)) 中随机抽取一个数字 - random_index = random.randint(0, len(nums)) - # 获取并返回随机元素 - random_num = nums[random_index] - return random_num - ``` - -=== "Go" - - ```go title="array.go" - /* 随机返回一个数组元素 */ - func randomAccess(nums []int) (randomNum int) { - // 在区间 [0, nums.length) 中随机抽取一个数字 - randomIndex := rand.Intn(len(nums)) - // 获取并返回随机元素 - randomNum = nums[randomIndex] - return - } - ``` - -=== "JavaScript" +=== "JS" ```javascript title="array.js" - /* 随机返回一个数组元素 */ - function randomAccess(nums) { - // 在区间 [0, nums.length) 中随机抽取一个数字 - const random_index = Math.floor(Math.random() * nums.length); - // 获取并返回随机元素 - const random_num = nums[random_index]; - return random_num; - } + /* 初始化数组 */ + var arr = new Array(5).fill(0); + var nums = [1, 3, 2, 5, 4]; ``` -=== "TypeScript" +=== "TS" ```typescript title="array.ts" - /* 随机返回一个数组元素 */ - function randomAccess(nums: number[]): number { - // 在区间 [0, nums.length) 中随机抽取一个数字 - const random_index = Math.floor(Math.random() * nums.length); - // 获取并返回随机元素 - const random_num = nums[random_index]; - return random_num; - } - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 随机返回一个数组元素 */ - int RandomAccess(int[] nums) - { - Random random=new(); - int randomIndex = random.Next(nums.Length); - int randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 随机返回一个数组元素 */ - func randomAccess(nums: [Int]) -> Int { - // 在区间 [0, nums.count) 中随机抽取一个数字 - let randomIndex = nums.indices.randomElement()! - // 获取并返回随机元素 - let randomNum = nums[randomIndex] - return randomNum - } - ``` - -## 数组缺点 - -**数组在初始化后长度不可变**。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 - -=== "Java" - - ```java title="array.java" - /* 扩展数组长度 */ - int[] extend(int[] nums, int enlarge) { - // 初始化一个扩展长度后的数组 - int[] res = new int[nums.length + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < nums.length; i++) { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 扩展数组长度 */ - int* extend(int* nums, int size, int enlarge) { - // 初始化一个扩展长度后的数组 - int* res = new int[size + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < size; i++) { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "Python" - - ```python title="array.py" - """ 扩展数组长度 """ - # 请注意,Python 的 list 是动态数组,可以直接扩展 - # 为了方便学习,本函数将 list 看作是长度不可变的数组 - def extend(nums, enlarge): - # 初始化一个扩展长度后的数组 - res = [0] * (len(nums) + enlarge) - # 将原数组中的所有元素复制到新数组 - for i in range(len(nums)): - res[i] = nums[i] - # 返回扩展后的新数组 - return res - ``` - -=== "Go" - - ```go title="array.go" - /* 扩展数组长度 */ - func extend(nums []int, enlarge int) []int { - // 初始化一个扩展长度后的数组 - res := make([]int, len(nums)+enlarge) - // 将原数组中的所有元素复制到新数组 - for i, num := range nums { - res[i] = num - } - // 返回扩展后的新数组 - return res - } + /* 初始化数组 */ + let arr: number[] = new Array(5).fill(0); + let nums: number[] = [1, 3, 2, 5, 4]; ``` -=== "JavaScript" +=== "Dart" - ```javascript title="array.js" - /* 扩展数组长度 */ - function extend(nums, enlarge) { - // 初始化一个扩展长度后的数组 - const res = new Array(nums.length + enlarge).fill(0); - // 将原数组中的所有元素复制到新数组 - for (let i = 0; i < nums.length; i++) { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } + ```dart title="array.dart" + /* 初始化数组 */ + List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] + List nums = [1, 3, 2, 5, 4]; ``` -=== "TypeScript" +=== "Rust" - ```typescript title="array.ts" - /* 扩展数组长度 */ - function extend(nums: number[], enlarge: number): number[] { - // 初始化一个扩展长度后的数组 - const res = new Array(nums.length + enlarge).fill(0); - // 将原数组中的所有元素复制到新数组 - for (let i = 0; i < nums.length; i++) { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } + ```rust title="array.rs" + /* 初始化数组 */ + let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] + let slice: &[i32] = &[0; 5]; + // 在 Rust 中,指定长度时([i32; 5])为数组,不指定长度时(&[i32])为切片 + // 由于 Rust 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 + // Vector 是 Rust 一般情况下用作动态数组的类型 + // 为了方便实现扩容 extend() 方法,以下将 vector 看作数组(array) + let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 扩展数组长度 */ - int[] Extend(int[] nums, int enlarge) - { - // 初始化一个扩展长度后的数组 - int[] res = new int[nums.Length + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < nums.Length; i++) - { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 扩展数组长度 */ - func extend(nums: [Int], enlarge: Int) -> [Int] { - // 初始化一个扩展长度后的数组 - var res = Array(repeating: 0, count: nums.count + enlarge) - // 将原数组中的所有元素复制到新数组 - for i in nums.indices { - res[i] = nums[i] - } - // 返回扩展后的新数组 - return res - } - ``` - -**数组中插入或删除元素效率低下**。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: - -- **时间复杂度高**:数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 -- **丢失元素**:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。 -- **内存浪费**:我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。 - -![array_insert_remove_element](array.assets/array_insert_remove_element.png) - -

Fig. 在数组中插入与删除元素

- -=== "Java" - - ```java title="array.java" - /* 在数组的索引 index 处插入元素 num */ - void insert(int[] nums, int num, int index) { - // 把索引 index 以及之后的所有元素向后移动一位 - for (int i = nums.length - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - void remove(int[] nums, int index) { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 在数组的索引 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]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - void remove(int* nums, int size, int index) { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < size - 1; i++) { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "Python" - - ```python title="array.py" - """ 在数组的索引 index 处插入元素 num """ - def insert(nums, num, index): - # 把索引 index 以及之后的所有元素向后移动一位 - for i in range(len(nums) - 1, index, -1): - nums[i] = nums[i - 1] - # 将 num 赋给 index 处元素 - nums[index] = num - - """ 删除索引 index 处元素 """ - def remove(nums, index): - # 把索引 index 之后的所有元素向前移动一位 - for i in range(index, len(nums) - 1): - nums[i] = nums[i + 1] - ``` - -=== "Go" - - ```go title="array.go" - /* 在数组的索引 index 处插入元素 num */ - func insert(nums []int, num int, index int) { - // 把索引 index 以及之后的所有元素向后移动一位 - for i := len(nums) - 1; i > index; i-- { - nums[i] = nums[i-1] - } - // 将 num 赋给 index 处元素 - nums[index] = num - } - - /* 删除索引 index 处元素 */ - func remove(nums []int, index int) { - // 把索引 index 之后的所有元素向前移动一位 - for i := index; i < len(nums)-1; i++ { - nums[i] = nums[i+1] - } - } - ``` - -=== "JavaScript" - - ```javascript title="array.js" - /* 在数组的索引 index 处插入元素 num */ - function insert(nums, num, index) { - // 把索引 index 以及之后的所有元素向后移动一位 - for (let i = nums.length - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - function remove(nums, index) { - // 把索引 index 之后的所有元素向前移动一位 - for (let i = index; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } + /* 初始化数组 */ + int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } + int nums[5] = { 1, 3, 2, 5, 4 }; ``` -=== "TypeScript" +=== "Kotlin" - ```typescript title="array.ts" - /* 在数组的索引 index 处插入元素 num */ - function insert(nums: number[], num: number, index: number): void { - // 把索引 index 以及之后的所有元素向后移动一位 - for (let i = nums.length - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - function remove(nums: number[], index: number): void { - // 把索引 index 之后的所有元素向前移动一位 - for (let i = index; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } + ```kotlin title="array.kt" + /* 初始化数组 */ + var arr = IntArray(5) // { 0, 0, 0, 0, 0 } + var nums = intArrayOf(1, 3, 2, 5, 4) ``` -=== "C" +=== "Ruby" - ```c title="array.c" - + ```ruby title="array.rb" + # 初始化数组 + arr = Array.new(5, 0) + nums = [1, 3, 2, 5, 4] ``` -=== "C#" +=== "Zig" - ```csharp title="array.cs" - /* 在数组的索引 index 处插入元素 num */ - void Insert(int[] nums, int num, int index) - { - // 把索引 index 以及之后的所有元素向后移动一位 - for (int i = nums.Length - 1; i > index; i--) - { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - /* 删除索引 index 处元素 */ - void Remove(int[] nums, int index) - { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < nums.Length - 1; i++) - { - nums[i] = nums[i + 1]; - } - } + ```zig title="array.zig" + // 初始化数组 + var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 } + var nums = [_]i32{ 1, 3, 2, 5, 4 }; ``` -=== "Swift" +??? pythontutor "可视化运行" - ```swift title="array.swift" - /* 在数组的索引 index 处插入元素 num */ - func insert(nums: inout [Int], num: Int, index: Int) { - // 把索引 index 以及之后的所有元素向后移动一位 - for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { - nums[i] = nums[i - 1] - } - // 将 num 赋给 index 处元素 - nums[index] = num - } - - /* 删除索引 index 处元素 */ - func remove(nums: inout [Int], index: Int) { - let count = nums.count - // 把索引 index 之后的所有元素向前移动一位 - for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) { - nums[i] = nums[i + 1] - } - } - ``` + https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false -## 数组常用操作 +### 访问元素 -**数组遍历**。以下介绍两种常用的遍历方法。 +数组元素被存储在连续的内存空间中,这意味着计算数组元素的内存地址非常容易。给定数组内存地址(首元素内存地址)和某个元素的索引,我们可以使用下图所示的公式计算得到该元素的内存地址,从而直接访问该元素。 -=== "Java" +![数组元素的内存地址计算](array.assets/array_memory_location_calculation.png) - ```java title="array.java" - /* 遍历数组 */ - void traverse(int[] nums) { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < nums.length; i++) { - count++; - } - // 直接遍历数组 - for (int num : nums) { - count++; - } - } - ``` +观察上图,我们发现数组首个元素的索引为 $0$ ,这似乎有些反直觉,因为从 $1$ 开始计数会更自然。但从地址计算公式的角度看,**索引本质上是内存地址的偏移量**。首个元素的地址偏移量是 $0$ ,因此它的索引为 $0$ 是合理的。 -=== "C++" +在数组中访问元素非常高效,我们可以在 $O(1)$ 时间内随机访问数组中的任意一个元素。 - ```cpp title="array.cpp" - /* 遍历数组 */ - void traverse(int* nums, int size) { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < size; i++) { - count++; - } - } - ``` - -=== "Python" - - ```python title="array.py" - """ 遍历数组 """ - def traverse(nums): - count = 0 - # 通过索引遍历数组 - for i in range(len(nums)): - count += 1 - # 直接遍历数组 - for num in nums: - count += 1 - ``` - -=== "Go" +```src +[file]{array}-[class]{}-[func]{random_access} +``` - ```go title="array.go" - /* 遍历数组 */ - func traverse(nums []int) { - count := 0 - // 通过索引遍历数组 - for i := 0; i < len(nums); i++ { - count++ - } - // 直接遍历数组 - for range nums { - count++ - } - } - ``` +### 插入元素 -=== "JavaScript" +数组元素在内存中是“紧挨着的”,它们之间没有空间再存放任何数据。如下图所示,如果想在数组中间插入一个元素,则需要将该元素之后的所有元素都向后移动一位,之后再把元素赋值给该索引。 - ```javascript title="array.js" - /* 遍历数组 */ - function traverse(nums) { - let count = 0; - // 通过索引遍历数组 - for (let i = 0; i < nums.length; i++) { - count++; - } - // 直接遍历数组 - for (let num of nums) { - count += 1; - } - } - ``` +![数组插入元素示例](array.assets/array_insert_element.png) -=== "TypeScript" +值得注意的是,由于数组的长度是固定的,因此插入一个元素必定会导致数组尾部元素“丢失”。我们将这个问题的解决方案留在“列表”章节中讨论。 - ```typescript title="array.ts" - /* 遍历数组 */ - function traverse(nums: number[]): void { - let count = 0; - // 通过索引遍历数组 - for (let i = 0; i < nums.length; i++) { - count++; - } - // 直接遍历数组 - for(let num of nums){ - count += 1; - } - } - ``` - -=== "C" - - ```c title="array.c" - - ``` +```src +[file]{array}-[class]{}-[func]{insert} +``` -=== "C#" +### 删除元素 - ```csharp title="array.cs" - /* 遍历数组 */ - void Traverse(int[] nums) - { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < nums.Length; i++) - { - count++; - } - // 直接遍历数组 - foreach (int num in nums) - { - count++; - } - } - ``` +同理,如下图所示,若想删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。 -=== "Swift" +![数组删除元素示例](array.assets/array_remove_element.png) - ```swift title="array.swift" - /* 遍历数组 */ - func traverse(nums: [Int]) { - var count = 0 - // 通过索引遍历数组 - for _ in nums.indices { - count += 1 - } - // 直接遍历数组 - for _ in nums { - count += 1 - } - } - ``` +请注意,删除元素完成后,原先末尾的元素变得“无意义”了,所以我们无须特意去修改它。 -**数组查找**。通过遍历数组,查找数组内的指定元素,并输出对应索引。 +```src +[file]{array}-[class]{}-[func]{remove} +``` -=== "Java" +总的来看,数组的插入与删除操作有以下缺点。 - ```java title="array.java" - /* 在数组中查找指定元素 */ - int find(int[] nums, int target) { - for (int i = 0; i < nums.length; i++) { - if (nums[i] == target) - return i; - } - return -1; - } - ``` +- **时间复杂度高**:数组的插入和删除的平均时间复杂度均为 $O(n)$ ,其中 $n$ 为数组长度。 +- **丢失元素**:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会丢失。 +- **内存浪费**:我们可以初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是“无意义”的,但这样做会造成部分内存空间浪费。 -=== "C++" +### 遍历数组 - ```cpp title="array.cpp" - /* 在数组中查找指定元素 */ - int find(int* nums, int size, int target) { - for (int i = 0; i < size; i++) { - if (nums[i] == target) - return i; - } - return -1; - } - ``` +在大多数编程语言中,我们既可以通过索引遍历数组,也可以直接遍历获取数组中的每个元素: -=== "Python" +```src +[file]{array}-[class]{}-[func]{traverse} +``` - ```python title="array.py" - """ 在数组中查找指定元素 """ - def find(nums, target): - for i in range(len(nums)): - if nums[i] == target: - return i - return -1 - ``` +### 查找元素 -=== "Go" +在数组中查找指定元素需要遍历数组,每轮判断元素值是否匹配,若匹配则输出对应索引。 - ```go title="array.go" - /* 在数组中查找指定元素 */ - func find(nums []int, target int) (index int) { - index = -1 - for i := 0; i < len(nums); i++ { - if nums[i] == target { - index = i - break - } - } - return - } - ``` +因为数组是线性数据结构,所以上述查找操作被称为“线性查找”。 -=== "JavaScript" +```src +[file]{array}-[class]{}-[func]{find} +``` - ```javascript title="array.js" - /* 在数组中查找指定元素 */ - function find(nums, target) { - for (let i = 0; i < nums.length; i++) { - if (nums[i] == target) return i; - } - return -1; - } - ``` +### 扩容数组 -=== "TypeScript" +在复杂的系统环境中,程序难以保证数组之后的内存空间是可用的,从而无法安全地扩展数组容量。因此在大多数编程语言中,**数组的长度是不可变的**。 - ```typescript title="array.ts" - /* 在数组中查找指定元素 */ - function find(nums: number[], target: number): number { - for (let i = 0; i < nums.length; i++) { - if (nums[i] === target) { - return i; - } - } - return -1; - } - ``` +如果我们希望扩容数组,则需重新建立一个更大的数组,然后把原数组元素依次复制到新数组。这是一个 $O(n)$ 的操作,在数组很大的情况下非常耗时。代码如下所示: -=== "C" +```src +[file]{array}-[class]{}-[func]{extend} +``` - ```c title="array.c" - - ``` +## 数组的优点与局限性 -=== "C#" +数组存储在连续的内存空间内,且元素类型相同。这种做法包含丰富的先验信息,系统可以利用这些信息来优化数据结构的操作效率。 - ```csharp title="array.cs" - /* 在数组中查找指定元素 */ - int Find(int[] nums, int target) - { - for (int i = 0; i < nums.Length; i++) - { - if (nums[i] == target) - return i; - } - return -1; - } - ``` +- **空间效率高**:数组为数据分配了连续的内存块,无须额外的结构开销。 +- **支持随机访问**:数组允许在 $O(1)$ 时间内访问任何元素。 +- **缓存局部性**:当访问数组元素时,计算机不仅会加载它,还会缓存其周围的其他数据,从而借助高速缓存来提升后续操作的执行速度。 -=== "Swift" +连续空间存储是一把双刃剑,其存在以下局限性。 - ```swift title="array.swift" - /* 在数组中查找指定元素 */ - func find(nums: [Int], target: Int) -> Int { - for i in nums.indices { - if nums[i] == target { - return i - } - } - return -1 - } - ``` +- **插入与删除效率低**:当数组中元素较多时,插入与删除操作需要移动大量的元素。 +- **长度不可变**:数组在初始化后长度就固定了,扩容数组需要将所有数据复制到新数组,开销很大。 +- **空间浪费**:如果数组分配的大小超过实际所需,那么多余的空间就被浪费了。 ## 数组典型应用 -**随机访问**。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 - -**二分查找**。例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。 +数组是一种基础且常见的数据结构,既频繁应用在各类算法之中,也可用于实现各种复杂数据结构。 -**深度学习**。神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 +- **随机访问**:如果我们想随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现随机抽样。 +- **排序和搜索**:数组是排序和搜索算法最常用的数据结构。快速排序、归并排序、二分查找等都主要在数组上进行。 +- **查找表**:当需要快速查找一个元素或其对应关系时,可以使用数组作为查找表。假如我们想实现字符到 ASCII 码的映射,则可以将字符的 ASCII 码值作为索引,对应的元素存放在数组中的对应位置。 +- **机器学习**:神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 +- **数据结构实现**:数组可以用于实现栈、队列、哈希表、堆、图等数据结构。例如,图的邻接矩阵表示实际上是一个二维数组。 diff --git a/docs/chapter_array_and_linkedlist/index.md b/docs/chapter_array_and_linkedlist/index.md new file mode 100644 index 0000000000..c6fe528004 --- /dev/null +++ b/docs/chapter_array_and_linkedlist/index.md @@ -0,0 +1,9 @@ +# 数组与链表 + +![数组与链表](../assets/covers/chapter_array_and_linkedlist.jpg) + +!!! abstract + + 数据结构的世界如同一堵厚实的砖墙。 + + 数组的砖块整齐排列,逐个紧贴。链表的砖块分散各处,连接的藤蔓自由地穿梭于砖缝之间。 diff --git a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png index ec04fe46c4..b7c4a726c1 100644 Binary files a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png and b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png differ diff --git a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png index 66d82b4f28..da9deb32cb 100644 Binary files a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png and b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png differ diff --git a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png new file mode 100644 index 0000000000..64672d41e4 Binary files /dev/null and b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png differ diff --git a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_remove_node.png b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_remove_node.png deleted file mode 100644 index 3ab4da38ca..0000000000 Binary files a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_remove_node.png and /dev/null differ diff --git a/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png new file mode 100644 index 0000000000..68c72b97fc Binary files /dev/null and b/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png differ diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md old mode 100644 new mode 100755 index 8dd069788f..07750e6a9f --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -1,62 +1,72 @@ ---- -comments: true ---- - # 链表 -!!! note "引言" +内存空间是所有程序的公共资源,在一个复杂的系统运行环境下,空闲的内存空间可能散落在内存各处。我们知道,存储数组的内存空间必须是连续的,而当数组非常大时,内存可能无法提供如此大的连续空间。此时链表的灵活性优势就体现出来了。 - 内存空间是所有程序的公共资源,排除已占用的内存,空闲内存往往是散落在内存各处的。我们知道,存储数组需要内存空间连续,当我们需要申请一个很大的数组时,系统不一定存在这么大的连续内存空间。而链表则更加灵活,不需要内存是连续的,只要剩余内存空间大小够用即可。 +链表(linked list)是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。 -「链表 Linked List」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(一般称为结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加灵活,系统不必保证内存地址的连续性。 +链表的设计使得各个节点可以分散存储在内存各处,它们的内存地址无须连续。 -链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。 +![链表定义与存储方式](linked_list.assets/linkedlist_definition.png) -![linkedlist_definition](linked_list.assets/linkedlist_definition.png) +观察上图,链表的组成单位是节点(node)对象。每个节点都包含两项数据:节点的“值”和指向下一节点的“引用”。 -

Fig. 链表定义与存储方式

+- 链表的首个节点被称为“头节点”,最后一个节点被称为“尾节点”。 +- 尾节点指向的是“空”,它在 Java、C++ 和 Python 中分别被记为 `null`、`nullptr` 和 `None` 。 +- 在 C、C++、Go 和 Rust 等支持指针的语言中,上述“引用”应被替换为“指针”。 -=== "Java" +如以下代码所示,链表节点 `ListNode` 除了包含值,还需额外保存一个引用(指针)。因此在相同数据量下,**链表比数组占用更多的内存空间**。 - ```java title="" - /* 链表结点类 */ - class ListNode { - int val; // 结点值 - ListNode next; // 指向下一结点的指针(引用) - ListNode(int x) { val = x; } // 构造函数 - } +=== "Python" + + ```python title="" + class ListNode: + """链表节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.next: ListNode | None = None # 指向下一节点的引用 ``` === "C++" ```cpp title="" - /* 链表结点结构体 */ + /* 链表节点结构体 */ struct ListNode { - int val; // 结点值 - ListNode *next; // 指向下一结点的指针(引用) + int val; // 节点值 + ListNode *next; // 指向下一节点的指针 ListNode(int x) : val(x), next(nullptr) {} // 构造函数 }; ``` -=== "Python" +=== "Java" - ```python title="" - """ 链表结点类 """ - class ListNode: - def __init__(self, x): - self.val = x # 结点值 - self.next = None # 指向下一结点的指针(引用) + ```java title="" + /* 链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode next; // 指向下一节点的引用 + ListNode(int x) { val = x; } // 构造函数 + } + ``` + +=== "C#" + + ```csharp title="" + /* 链表节点类 */ + class ListNode(int x) { //构造函数 + int val = x; // 节点值 + ListNode? next; // 指向下一节点的引用 + } ``` === "Go" ```go title="" - /* 链表结点结构体 */ + /* 链表节点结构体 */ type ListNode struct { - Val int // 结点值 - Next *ListNode // 指向下一结点的指针(引用) + Val int // 节点值 + Next *ListNode // 指向下一节点的指针 } - + // NewListNode 构造函数,创建一个新的链表 func NewListNode(val int) *ListNode { return &ListNode{ @@ -66,804 +76,686 @@ comments: true } ``` -=== "JavaScript" +=== "Swift" - ```js title="" - /* 链表结点结构体 */ + ```swift title="" + /* 链表节点类 */ + class ListNode { + var val: Int // 节点值 + var next: ListNode? // 指向下一节点的引用 + + init(x: Int) { // 构造函数 + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 链表节点类 */ class ListNode { - val; - next; constructor(val, next) { - this.val = (val === undefined ? 0 : val); // 结点值 - this.next = (next === undefined ? null : next); // 指向下一结点的引用 + this.val = (val === undefined ? 0 : val); // 节点值 + this.next = (next === undefined ? null : next); // 指向下一节点的引用 } } ``` -=== "TypeScript" +=== "TS" ```typescript title="" - /* 链表结点结构体 */ + /* 链表节点类 */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向下一结点的引用 + this.val = val === undefined ? 0 : val; // 节点值 + this.next = next === undefined ? null : next; // 指向下一节点的引用 } } ``` +=== "Dart" + + ```dart title="" + /* 链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode? next; // 指向下一节点的引用 + ListNode(this.val, [this.next]); // 构造函数 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + /* 链表节点类 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 节点值 + next: Option>>, // 指向下一节点的指针 + } + ``` + === "C" ```c title="" + /* 链表节点结构体 */ + typedef struct ListNode { + int val; // 节点值 + struct ListNode *next; // 指向下一节点的指针 + } ListNode; + /* 构造函数 */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; + } ``` -=== "C#" +=== "Kotlin" - ```csharp title="" - /* 链表结点类 */ - class ListNode - { - int val; // 结点值 - ListNode next; // 指向下一结点的引用 - ListNode(int x) => val = x; //构造函数 + ```kotlin title="" + /* 链表节点类 */ + // 构造方法 + class ListNode(x: Int) { + val _val: Int = x // 节点值 + val next: ListNode? = null // 指向下一个节点的引用 } ``` -=== "Swift" +=== "Ruby" - ```swift title="" - /* 链表结点类 */ - class ListNode { - var val: Int // 结点值 - var next: ListNode? // 指向下一结点的指针(引用) + ```ruby title="" + # 链表节点类 + class ListNode + attr_accessor :val # 节点值 + attr_accessor :next # 指向下一节点的引用 - init(x: Int) { // 构造函数 - val = x - } - } + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end + end ``` -**尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。 +=== "Zig" -**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 + ```zig title="" + // 链表节点类 + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); -!!! tip + val: T = 0, // 节点值 + next: ?*Self = null, // 指向下一节点的指针 - 我们通常将头结点当作链表的代称,例如头结点 `head` 和链表 `head` 实际上是同义的。 + // 构造函数 + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; + } + ``` -=== "Java" +## 链表常用操作 - ```java title="linked_list.java" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - ListNode n0 = new ListNode(1); - ListNode n1 = new ListNode(3); - ListNode n2 = new ListNode(2); - ListNode n3 = new ListNode(5); - ListNode n4 = new ListNode(4); - // 构建引用指向 - n0.next = n1; - n1.next = n2; - n2.next = n3; - n3.next = n4; +### 初始化链表 + +建立链表分为两步,第一步是初始化各个节点对象,第二步是构建节点之间的引用关系。初始化完成后,我们就可以从链表的头节点出发,通过引用指向 `next` 依次访问所有节点。 + +=== "Python" + + ```python title="linked_list.py" + # 初始化链表 1 -> 3 -> 2 -> 5 -> 4 + # 初始化各个节点 + n0 = ListNode(1) + n1 = ListNode(3) + n2 = ListNode(2) + n3 = ListNode(5) + n4 = ListNode(4) + # 构建节点之间的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 ``` === "C++" ```cpp title="linked_list.cpp" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 ListNode* n0 = new ListNode(1); ListNode* n1 = new ListNode(3); ListNode* n2 = new ListNode(2); ListNode* n3 = new ListNode(5); ListNode* n4 = new ListNode(4); - // 构建引用指向 + // 构建节点之间的引用 n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; ``` -=== "Python" +=== "Java" - ```python title="linked_list.py" - """ 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """ - # 初始化各个结点 - n0 = ListNode(1) - n1 = ListNode(3) - n2 = ListNode(2) - n3 = ListNode(5) - n4 = ListNode(4) - # 构建引用指向 - n0.next = n1 - n1.next = n2 - n2.next = n3 - n3.next = n4 + ```java title="linked_list.java" + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个节点 + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // 构建节点之间的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个节点 + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // 构建节点之间的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; ``` === "Go" ```go title="linked_list.go" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) n3 := NewListNode(5) n4 := NewListNode(4) - - // 构建引用指向 + // 构建节点之间的引用 n0.Next = n1 n1.Next = n2 n2.Next = n3 n3.Next = n4 ``` -=== "JavaScript" +=== "Swift" - ```js title="linked_list.js" + ```swift title="linked_list.swift" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // 构建节点之间的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "JS" + + ```javascript title="linked_list.js" + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); - // 构建引用指向 + // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` -=== "TypeScript" +=== "TS" ```typescript title="linked_list.ts" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); const n3 = new ListNode(5); const n4 = new ListNode(4); - // 构建引用指向 + // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` +=== "Dart" + + ```dart title="linked_list.dart" + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */\ + // 初始化各个节点 + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // 构建节点之间的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Rust" + + ```rust title="linked_list.rs" + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个节点 + let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); + let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); + let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); + let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); + let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); + + // 构建节点之间的引用 + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + ``` + === "C" ```c title="linked_list.c" - + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个节点 + 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; ``` -=== "C#" +=== "Kotlin" - ```csharp title="linked_list.cs" + ```kotlin title="linked_list.kt" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - ListNode n0 = new ListNode(1); - ListNode n1 = new ListNode(3); - ListNode n2 = new ListNode(2); - ListNode n3 = new ListNode(5); - ListNode n4 = new ListNode(4); - // 构建引用指向 + // 初始化各个节点 + val n0 = ListNode(1) + val n1 = ListNode(3) + val n2 = ListNode(2) + val n3 = ListNode(5) + val n4 = ListNode(4) + // 构建节点之间的引用 n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; ``` -=== "Swift" +=== "Ruby" - ```swift title="linked_list.swift" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - let n0 = ListNode(x: 1) - let n1 = ListNode(x: 3) - let n2 = ListNode(x: 2) - let n3 = ListNode(x: 5) - let n4 = ListNode(x: 4) - // 构建引用指向 + ```ruby title="linked_list.rb" + # 初始化链表 1 -> 3 -> 2 -> 5 -> 4 + # 初始化各个节点 + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # 构建节点之间的引用 n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 ``` -## 链表优点 - -**在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 +=== "Zig" -![linkedlist_insert_remove_node](linked_list.assets/linkedlist_insert_remove_node.png) - -

Fig. 在链表中插入与删除结点

- -=== "Java" - - ```java title="linked_list.java" - /* 在链表的结点 n0 之后插入结点 P */ - void insert(ListNode n0, ListNode P) { - ListNode n1 = n0.next; - n0.next = P; - P.next = n1; - } - - /* 删除链表的结点 n0 之后的首个结点 */ - void remove(ListNode n0) { - if (n0.next == null) - return; - // n0 -> P -> n1 - ListNode P = n0.next; - ListNode n1 = P.next; - n0.next = n1; - } + ```zig title="linked_list.zig" + // 初始化链表 + // 初始化各个节点 + var n0 = inc.ListNode(i32){.val = 1}; + var n1 = inc.ListNode(i32){.val = 3}; + var n2 = inc.ListNode(i32){.val = 2}; + var n3 = inc.ListNode(i32){.val = 5}; + var n4 = inc.ListNode(i32){.val = 4}; + // 构建节点之间的引用 + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; ``` -=== "C++" +??? pythontutor "可视化运行" - ```cpp title="linked_list.cpp" - /* 在链表的结点 n0 之后插入结点 P */ - void insert(ListNode* n0, ListNode* P) { - ListNode* n1 = n0->next; - n0->next = P; - P->next = n1; - } + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false - /* 删除链表的结点 n0 之后的首个结点 */ - void remove(ListNode* n0) { - if (n0->next == nullptr) - return; - // n0 -> P -> n1 - ListNode* P = n0->next; - ListNode* n1 = P->next; - n0->next = n1; - // 释放内存 - delete P; - } - ``` +数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而链表是由多个独立的节点对象组成的。**我们通常将头节点当作链表的代称**,比如以上代码中的链表可记作链表 `n0` 。 -=== "Python" +### 插入节点 - ```python title="linked_list.py" - """ 在链表的结点 n0 之后插入结点 P """ - def insert(n0, P): - n1 = n0.next - n0.next = P - P.next = n1 - - """ 删除链表的结点 n0 之后的首个结点 """ - def remove(n0): - if not n0.next: - return - # n0 -> P -> n1 - P = n0.next - n1 = P.next - n0.next = n1 - ``` +在链表中插入节点非常容易。如下图所示,假设我们想在相邻的两个节点 `n0` 和 `n1` 之间插入一个新节点 `P` ,**则只需改变两个节点引用(指针)即可**,时间复杂度为 $O(1)$ 。 -=== "Go" +相比之下,在数组中插入元素的时间复杂度为 $O(n)$ ,在大数据量下的效率较低。 - ```go title="linked_list.go" - /* 在链表的结点 n0 之后插入结点 P */ - func insert(n0 *ListNode, P *ListNode) { - n1 := n0.Next - n0.Next = P - P.Next = n1 - } +![链表插入节点示例](linked_list.assets/linkedlist_insert_node.png) - /* 删除链表的结点 n0 之后的首个结点 */ - func removeNode(n0 *ListNode) { - if n0.Next == nil { - return - } - // n0 -> P -> n1 - P := n0.Next - n1 := P.Next - n0.Next = n1 - } - ``` +```src +[file]{linked_list}-[class]{}-[func]{insert} +``` -=== "JavaScript" +### 删除节点 - ```js title="linked_list.js" - /* 在链表的结点 n0 之后插入结点 P */ - function insert(n0, P) { - let n1 = n0.next; - n0.next = P; - P.next = n1; - } +如下图所示,在链表中删除节点也非常方便,**只需改变一个节点的引用(指针)即可**。 - /* 删除链表的结点 n0 之后的首个结点 */ - function remove(n0) { - if (!n0.next) - return; - // n0 -> P -> n1 - let P = n0.next; - let n1 = P.next; - n0.next = n1; - } - ``` +请注意,尽管在删除操作完成后节点 `P` 仍然指向 `n1` ,但实际上遍历此链表已经无法访问到 `P` ,这意味着 `P` 已经不再属于该链表了。 -=== "TypeScript" +![链表删除节点](linked_list.assets/linkedlist_remove_node.png) - ```typescript title="linked_list.ts" - /* 在链表的结点 n0 之后插入结点 P */ - function insert(n0: ListNode, P: ListNode): void { - const n1 = n0.next; - n0.next = P; - P.next = n1; - } - - /* 删除链表的结点 n0 之后的首个结点 */ - function remove(n0: ListNode): void { - if (!n0.next) { - return; - } - // n0 -> P -> n1 - const P = n0.next; - const n1 = P.next; - n0.next = n1; - } - ``` +```src +[file]{linked_list}-[class]{}-[func]{remove} +``` -=== "C" +### 访问节点 - ```c title="linked_list.c" +**在链表中访问节点的效率较低**。如上一节所述,我们可以在 $O(1)$ 时间下访问数组中的任意元素。链表则不然,程序需要从头节点出发,逐个向后遍历,直至找到目标节点。也就是说,访问链表的第 $i$ 个节点需要循环 $i - 1$ 轮,时间复杂度为 $O(n)$ 。 - ``` +```src +[file]{linked_list}-[class]{}-[func]{access} +``` -=== "C#" +### 查找节点 - ```csharp title="linked_list.cs" - // 在链表的结点 n0 之后插入结点 P - void Insert(ListNode n0, ListNode P) - { - ListNode n1 = n0.next; - n0.next = P; - P.next = n1; - } +遍历链表,查找其中值为 `target` 的节点,输出该节点在链表中的索引。此过程也属于线性查找。代码如下所示: - // 删除链表的结点 n0 之后的首个结点 - void Remove(ListNode n0) - { - if (n0.next == null) - return; - // n0 -> P -> n1 - ListNode P = n0.next; - ListNode n1 = P.next; - n0.next = n1; - } - ``` +```src +[file]{linked_list}-[class]{}-[func]{find} +``` -=== "Swift" +## 数组 vs. 链表 - ```swift title="linked_list.swift" - /* 在链表的结点 n0 之后插入结点 P */ - func insert(n0: ListNode, P: ListNode) { - let n1 = n0.next - n0.next = P - P.next = n1 - } +下表总结了数组和链表的各项特点并对比了操作效率。由于它们采用两种相反的存储策略,因此各种性质和操作效率也呈现对立的特点。 - /* 删除链表的结点 n0 之后的首个结点 */ - func remove(n0: ListNode) { - if n0.next == nil { - return - } - // n0 -> P -> n1 - let P = n0.next - let n1 = P?.next - n0.next = n1 - P?.next = nil - } - ``` +

  数组与链表的效率对比

-## 链表缺点 +| | 数组 | 链表 | +| -------- | ------------------------------ | -------------- | +| 存储方式 | 连续内存空间 | 分散内存空间 | +| 容量扩展 | 长度不可变 | 可灵活扩展 | +| 内存效率 | 元素占用内存少、但可能浪费空间 | 元素占用内存多 | +| 访问元素 | $O(1)$ | $O(n)$ | +| 添加元素 | $O(n)$ | $O(1)$ | +| 删除元素 | $O(n)$ | $O(1)$ | -**链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 - -=== "Java" +## 常见链表类型 - ```java title="linked_list.java" - /* 访问链表中索引为 index 的结点 */ - ListNode access(ListNode head, int index) { - for (int i = 0; i < index; i++) { - if (head == null) - return null; - head = head.next; - } - return head; - } - ``` +如下图所示,常见的链表类型包括三种。 -=== "C++" - - ```cpp title="linked_list.cpp" - /* 访问链表中索引为 index 的结点 */ - ListNode* access(ListNode* head, int index) { - for (int i = 0; i < index; i++) { - if (head == nullptr) - return nullptr; - head = head->next; - } - return head; - } - ``` +- **单向链表**:即前面介绍的普通链表。单向链表的节点包含值和指向下一节点的引用两项数据。我们将首个节点称为头节点,将最后一个节点称为尾节点,尾节点指向空 `None` 。 +- **环形链表**:如果我们令单向链表的尾节点指向头节点(首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。 +- **双向链表**:与单向链表相比,双向链表记录了两个方向的引用。双向链表的节点定义同时包含指向后继节点(下一个节点)和前驱节点(上一个节点)的引用(指针)。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。 === "Python" - ```python title="linked_list.py" - """ 访问链表中索引为 index 的结点 """ - def access(head, index): - for _ in range(index): - if not head: - return None - head = head.next - return head - ``` - -=== "Go" - - ```go title="linked_list.go" - /* 访问链表中索引为 index 的结点 */ - func access(head *ListNode, index int) *ListNode { - for i := 0; i < index; i++ { - if head == nil { - return nil - } - head = head.Next - } - return head - } + ```python title="" + class ListNode: + """双向链表节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.next: ListNode | None = None # 指向后继节点的引用 + self.prev: ListNode | None = None # 指向前驱节点的引用 ``` -=== "JavaScript" +=== "C++" - ```js title="linked_list.js" - /* 访问链表中索引为 index 的结点 */ - function access(head, index) { - for (let i = 0; i < index; i++) { - if (!head) - return null; - head = head.next; - } - return head; - } + ```cpp title="" + /* 双向链表节点结构体 */ + struct ListNode { + int val; // 节点值 + ListNode *next; // 指向后继节点的指针 + ListNode *prev; // 指向前驱节点的指针 + ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数 + }; ``` -=== "TypeScript" +=== "Java" - ```typescript title="linked_list.ts" - /* 访问链表中索引为 index 的结点 */ - function access(head: ListNode | null, index: number): ListNode | null { - for (let i = 0; i < index; i++) { - if (!head) { - return null; - } - head = head.next; - } - return head; + ```java title="" + /* 双向链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode next; // 指向后继节点的引用 + ListNode prev; // 指向前驱节点的引用 + ListNode(int x) { val = x; } // 构造函数 } ``` -=== "C" - - ```c title="linked_list.c" - - ``` - === "C#" - ```csharp title="linked_list.cs" - // 访问链表中索引为 index 的结点 - ListNode Access(ListNode head, int index) - { - for (int i = 0; i < index; i++) - { - if (head == null) - return null; - head = head.next; - } - return head; - } - ``` - -=== "Swift" - - ```swift title="linked_list.swift" - /* 访问链表中索引为 index 的结点 */ - func access(head: ListNode, index: Int) -> ListNode? { - var head: ListNode? = head - for _ in 0 ..< index { - if head == nil { - return nil - } - head = head?.next - } - return head + ```csharp title="" + /* 双向链表节点类 */ + class ListNode(int x) { // 构造函数 + int val = x; // 节点值 + ListNode next; // 指向后继节点的引用 + ListNode prev; // 指向前驱节点的引用 } ``` -**链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 - -## 链表常用操作 - -**遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 - -=== "Java" +=== "Go" - ```java title="linked_list.java" - /* 在链表中查找值为 target 的首个结点 */ - int find(ListNode head, int target) { - int index = 0; - while (head != null) { - if (head.val == target) - return index; - head = head.next; - index++; - } - return -1; + ```go title="" + /* 双向链表节点结构体 */ + type DoublyListNode struct { + Val int // 节点值 + Next *DoublyListNode // 指向后继节点的指针 + Prev *DoublyListNode // 指向前驱节点的指针 } - ``` - -=== "C++" - ```cpp title="linked_list.cpp" - /* 在链表中查找值为 target 的首个结点 */ - int find(ListNode* head, int target) { - int index = 0; - while (head != nullptr) { - if (head->val == target) - return index; - head = head->next; - index++; + // NewDoublyListNode 初始化 + func NewDoublyListNode(val int) *DoublyListNode { + return &DoublyListNode{ + Val: val, + Next: nil, + Prev: nil, } - return -1; } ``` -=== "Python" - - ```python title="linked_list.py" - """ 在链表中查找值为 target 的首个结点 """ - def find(head, target): - index = 0 - while head: - if head.val == target: - return index - head = head.next - index += 1 - return -1 - ``` +=== "Swift" -=== "Go" + ```swift title="" + /* 双向链表节点类 */ + class ListNode { + var val: Int // 节点值 + var next: ListNode? // 指向后继节点的引用 + var prev: ListNode? // 指向前驱节点的引用 - ```go title="linked_list.go" - /* 在链表中查找值为 target 的首个结点 */ - func find(head *ListNode, target int) int { - index := 0 - for head != nil { - if head.Val == target { - return index - } - head = head.Next - index++ + init(x: Int) { // 构造函数 + val = x } - return -1 } ``` -=== "JavaScript" +=== "JS" - ```js title="linked_list.js" - /* 在链表中查找值为 target 的首个结点 */ - function find(head, target) { - let index = 0; - while (head !== null) { - if (head.val === target) { - return index; - } - head = head.next; - index += 1; + ```javascript title="" + /* 双向链表节点类 */ + class ListNode { + constructor(val, next, prev) { + this.val = val === undefined ? 0 : val; // 节点值 + this.next = next === undefined ? null : next; // 指向后继节点的引用 + this.prev = prev === undefined ? null : prev; // 指向前驱节点的引用 } - return -1; } ``` -=== "TypeScript" +=== "TS" - ```typescript title="linked_list.ts" - /* 在链表中查找值为 target 的首个结点 */ - function find(head: ListNode | null, target: number): number { - let index = 0; - while (head !== null) { - if (head.val === target) { - return index; - } - head = head.next; - index += 1; + ```typescript title="" + /* 双向链表节点类 */ + class ListNode { + val: number; + next: ListNode | null; + prev: ListNode | null; + constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { + this.val = val === undefined ? 0 : val; // 节点值 + this.next = next === undefined ? null : next; // 指向后继节点的引用 + this.prev = prev === undefined ? null : prev; // 指向前驱节点的引用 } - return -1; } ``` -=== "C" - - ```c title="linked_list.c" - - ``` - -=== "C#" +=== "Dart" - ```csharp title="linked_list.cs" - // 在链表中查找值为 target 的首个结点 - int Find(ListNode head, int target) - { - int index = 0; - while (head != null) - { - if (head.val == target) - return index; - head = head.next; - index++; - } - return -1; + ```dart title="" + /* 双向链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode? next; // 指向后继节点的引用 + ListNode? prev; // 指向前驱节点的引用 + ListNode(this.val, [this.next, this.prev]); // 构造函数 } ``` -=== "Swift" +=== "Rust" - ```swift title="linked_list.swift" - /* 在链表中查找值为 target 的首个结点 */ - func find(head: ListNode, target: Int) -> Int { - var head: ListNode? = head - var index = 0 - while head != nil { - if head?.val == target { - return index + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 双向链表节点类型 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 节点值 + next: Option>>, // 指向后继节点的指针 + prev: Option>>, // 指向前驱节点的指针 + } + + /* 构造函数 */ + impl ListNode { + fn new(val: i32) -> Self { + ListNode { + val, + next: None, + prev: None, } - head = head?.next - index += 1 } - return -1 - } - ``` - -## 常见链表类型 - -**单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 - -**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。 - -**双向链表**。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。 - -=== "Java" - - ```java title="" - /* 双向链表结点类 */ - class ListNode { - int val; // 结点值 - ListNode next; // 指向后继结点的指针(引用) - ListNode prev; // 指向前驱结点的指针(引用) - ListNode(int x) { val = x; } // 构造函数 } ``` -=== "C++" - - ```cpp title="" - /* 链表结点结构体 */ - struct ListNode { - int val; // 结点值 - ListNode *next; // 指向后继结点的指针(引用) - ListNode *prev; // 指向前驱结点的指针(引用) - ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数 - }; - ``` - -=== "Python" - - ```python title="" - """ 双向链表结点类 """ - class ListNode: - def __init__(self, x): - self.val = x # 结点值 - self.next = None # 指向后继结点的指针(引用) - self.prev = None # 指向前驱结点的指针(引用) - ``` +=== "C" -=== "Go" + ```c title="" + /* 双向链表节点结构体 */ + typedef struct ListNode { + int val; // 节点值 + struct ListNode *next; // 指向后继节点的指针 + struct ListNode *prev; // 指向前驱节点的指针 + } ListNode; - ```go title="" - /* 双向链表结点结构体 */ - type DoublyListNode struct { - Val int // 结点值 - Next *DoublyListNode // 指向后继结点的指针(引用) - Prev *DoublyListNode // 指向前驱结点的指针(引用) - } - - // NewDoublyListNode 初始化 - func NewDoublyListNode(val int) *DoublyListNode { - return &DoublyListNode{ - Val: val, - Next: nil, - Prev: nil, - } + /* 构造函数 */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + node->prev = NULL; + return node; } ``` -=== "JavaScript" +=== "Kotlin" - ```js title="" - /* 双向链表结点类 */ - class ListNode { - val; - next; - prev; - constructor(val, next) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向后继结点的指针(引用) - this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用) - } + ```kotlin title="" + /* 双向链表节点类 */ + // 构造方法 + class ListNode(x: Int) { + val _val: Int = x // 节点值 + val next: ListNode? = null // 指向后继节点的引用 + val prev: ListNode? = null // 指向前驱节点的引用 } ``` -=== "TypeScript" +=== "Ruby" - ```typescript title="" - /* 双向链表结点类 */ - class ListNode { - val: number; - next: ListNode | null; - prev: ListNode | null; - constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向后继结点的指针(引用) - this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用) - } + ```ruby title="" + # 双向链表节点类 + class ListNode + attr_accessor :val # 节点值 + attr_accessor :next # 指向后继节点的引用 + attr_accessor :prev # 指向前驱节点的引用 + + def initialize(val=0, next_node=nil, prev_node=nil) + @val = val + @next = next_node + @prev = prev_node + end + end + ``` + +=== "Zig" + + ```zig title="" + // 双向链表节点类 + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, // 节点值 + next: ?*Self = null, // 指向后继节点的指针 + prev: ?*Self = null, // 指向前驱节点的指针 + + // 构造函数 + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; } ``` -=== "C" - - ```c title="" +![常见链表种类](linked_list.assets/linkedlist_common_types.png) - ``` - -=== "C#" +## 链表典型应用 - ```csharp title="" - /* 双向链表结点类 */ - class ListNode { - int val; // 结点值 - ListNode next; // 指向后继结点的指针(引用) - ListNode prev; // 指向前驱结点的指针(引用) - ListNode(int x) => val = x; // 构造函数 - } - ``` +单向链表通常用于实现栈、队列、哈希表和图等数据结构。 -=== "Swift" +- **栈与队列**:当插入和删除操作都在链表的一端进行时,它表现的特性为先进后出,对应栈;当插入操作在链表的一端进行,删除操作在链表的另一端进行,它表现的特性为先进先出,对应队列。 +- **哈希表**:链式地址是解决哈希冲突的主流方案之一,在该方案中,所有冲突的元素都会被放到一个链表中。 +- **图**:邻接表是表示图的一种常用方式,其中图的每个顶点都与一个链表相关联,链表中的每个元素都代表与该顶点相连的其他顶点。 - ```swift title="" - /* 双向链表结点类 */ - class ListNode { - var val: Int // 结点值 - var next: ListNode? // 指向后继结点的指针(引用) - var prev: ListNode? // 指向前驱结点的指针(引用) +双向链表常用于需要快速查找前一个和后一个元素的场景。 - init(x: Int) { // 构造函数 - val = x - } - } - ``` +- **高级数据结构**:比如在红黑树、B 树中,我们需要访问节点的父节点,这可以通过在节点中保存一个指向父节点的引用来实现,类似于双向链表。 +- **浏览器历史**:在网页浏览器中,当用户点击前进或后退按钮时,浏览器需要知道用户访问过的前一个和后一个网页。双向链表的特性使得这种操作变得简单。 +- **LRU 算法**:在缓存淘汰(LRU)算法中,我们需要快速找到最近最少使用的数据,以及支持快速添加和删除节点。这时候使用双向链表就非常合适。 -![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png) +环形链表常用于需要周期性操作的场景,比如操作系统的资源调度。 -

Fig. 常见链表类型

+- **时间片轮转调度算法**:在操作系统中,时间片轮转调度算法是一种常见的 CPU 调度算法,它需要对一组进程进行循环。每个进程被赋予一个时间片,当时间片用完时,CPU 将切换到下一个进程。这种循环操作可以通过环形链表来实现。 +- **数据缓冲区**:在某些数据缓冲区的实现中,也可能会使用环形链表。比如在音频、视频播放器中,数据流可能会被分成多个缓冲块并放入一个环形链表,以便实现无缝播放。 diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md old mode 100644 new mode 100755 index 1a47a0d134..f764bd501f --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -1,408 +1,642 @@ ---- -comments: true ---- - # 列表 -**由于长度不可变,数组的实用性大大降低**。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 +列表(list)是一个抽象的数据结构概念,它表示元素的有序集合,支持元素访问、修改、添加、删除和遍历等操作,无须使用者考虑容量限制的问题。列表可以基于链表或数组实现。 + +- 链表天然可以看作一个列表,其支持元素增删查改操作,并且可以灵活动态扩容。 +- 数组也支持元素增删查改,但由于其长度不可变,因此只能看作一个具有长度限制的列表。 + +当使用数组实现列表时,**长度不可变的性质会导致列表的实用性降低**。这是因为我们通常无法事先确定需要存储多少数据,从而难以选择合适的列表长度。若长度过小,则很可能无法满足使用需求;若长度过大,则会造成内存空间浪费。 -为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。 +为解决此问题,我们可以使用动态数组(dynamic array)来实现列表。它继承了数组的各项优点,并且可以在程序运行过程中进行动态扩容。 + +实际上,**许多编程语言中的标准库提供的列表是基于动态数组实现的**,例如 Python 中的 `list` 、Java 中的 `ArrayList` 、C++ 中的 `vector` 和 C# 中的 `List` 等。在接下来的讨论中,我们将把“列表”和“动态数组”视为等同的概念。 ## 列表常用操作 -**初始化列表**。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 +### 初始化列表 + +我们通常使用“无初始值”和“有初始值”这两种初始化方法: + +=== "Python" + + ```python title="list.py" + # 初始化列表 + # 无初始值 + nums1: list[int] = [] + # 有初始值 + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 初始化列表 */ + // 需注意,C++ 中 vector 即是本文描述的 nums + // 无初始值 + vector nums1; + // 有初始值 + vector nums = { 1, 3, 2, 5, 4 }; + ``` === "Java" ```java title="list.java" /* 初始化列表 */ // 无初始值 - List list1 = new ArrayList<>(); + List nums1 = new ArrayList<>(); // 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[]) Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; - List list = new ArrayList<>(Arrays.asList(numbers)); + List nums = new ArrayList<>(Arrays.asList(numbers)); ``` -=== "C++" +=== "C#" - ```cpp title="list.cpp" + ```csharp title="list.cs" /* 初始化列表 */ - // 需注意,C++ 中 vector 即是本文描述的 list // 无初始值 - vector list1; + List nums1 = []; // 有初始值 - vector list = { 1, 3, 2, 5, 4 }; + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; ``` -=== "Python" +=== "Go" - ```python title="list.py" - """ 初始化列表 """ - # 无初始值 - list1 = [] - # 有初始值 - list = [1, 3, 2, 5, 4] + ```go title="list_test.go" + /* 初始化列表 */ + // 无初始值 + nums1 := []int{} + // 有初始值 + nums := []int{1, 3, 2, 5, 4} ``` -=== "Go" +=== "Swift" - ```go title="list_test.go" + ```swift title="list.swift" /* 初始化列表 */ // 无初始值 - list1 := []int + let nums1: [Int] = [] // 有初始值 - list := []int{1, 3, 2, 5, 4} + var nums = [1, 3, 2, 5, 4] ``` -=== "JavaScript" +=== "JS" - ```js title="list.js" + ```javascript title="list.js" /* 初始化列表 */ // 无初始值 - const list1 = []; + const nums1 = []; // 有初始值 - const list = [1, 3, 2, 5, 4]; + const nums = [1, 3, 2, 5, 4]; ``` -=== "TypeScript" +=== "TS" ```typescript title="list.ts" /* 初始化列表 */ // 无初始值 - const list1: number[] = []; + const nums1: number[] = []; // 有初始值 - const list: number[] = [1, 3, 2, 5, 4]; + const nums: number[] = [1, 3, 2, 5, 4]; ``` -=== "C" - - ```c title="list.c" +=== "Dart" + ```dart title="list.dart" + /* 初始化列表 */ + // 无初始值 + List nums1 = []; + // 有初始值 + List nums = [1, 3, 2, 5, 4]; ``` -=== "C#" +=== "Rust" - ```csharp title="list.cs" + ```rust title="list.rs" /* 初始化列表 */ // 无初始值 - List list1 = new (); - // 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[]) - int[] numbers = new int[] { 1, 3, 2, 5, 4 }; - List list = numbers.ToList(); + let nums1: Vec = Vec::new(); + // 有初始值 + let nums: Vec = vec![1, 3, 2, 5, 4]; ``` -=== "Swift" +=== "C" - ```swift title="list.swift" + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" /* 初始化列表 */ // 无初始值 - let list1: [Int] = [] + var nums1 = listOf() // 有初始值 - var list = [1, 3, 2, 5, 4] + var numbers = arrayOf(1, 3, 2, 5, 4) + var nums = numbers.toMutableList() ``` -**访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 +=== "Ruby" -=== "Java" + ```ruby title="list.rb" + # 初始化列表 + # 无初始值 + nums1 = [] + # 有初始值 + nums = [1, 3, 2, 5, 4] + ``` - ```java title="list.java" - /* 访问元素 */ - int num = list.get(1); // 访问索引 1 处的元素 +=== "Zig" - /* 更新元素 */ - list.set(1, 0); // 将索引 1 处的元素更新为 0 + ```zig title="list.zig" + // 初始化列表 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + ``` + +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20%23%20%E6%97%A0%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 访问元素 + +列表本质上是数组,因此可以在 $O(1)$ 时间内访问和更新元素,效率很高。 + +=== "Python" + + ```python title="list.py" + # 访问元素 + num: int = nums[1] # 访问索引 1 处的元素 + + # 更新元素 + nums[1] = 0 # 将索引 1 处的元素更新为 0 ``` === "C++" ```cpp title="list.cpp" /* 访问元素 */ - int num = list[1]; // 访问索引 1 处的元素 + int num = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 + nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` -=== "Python" +=== "Java" - ```python title="list.py" - """ 访问元素 """ - num = list[1] # 访问索引 1 处的元素 + ```java title="list.java" + /* 访问元素 */ + int num = nums.get(1); // 访问索引 1 处的元素 + + /* 更新元素 */ + nums.set(1, 0); // 将索引 1 处的元素更新为 0 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 访问元素 */ + int num = nums[1]; // 访问索引 1 处的元素 - """ 更新元素 """ - list[1] = 0 # 将索引 1 处的元素更新为 0 + /* 更新元素 */ + nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "Go" ```go title="list_test.go" /* 访问元素 */ - num := list[1] // 访问索引 1 处的元素 + num := nums[1] // 访问索引 1 处的元素 /* 更新元素 */ - list[1] = 0 // 将索引 1 处的元素更新为 0 + nums[1] = 0 // 将索引 1 处的元素更新为 0 ``` -=== "JavaScript" +=== "Swift" - ```js title="list.js" + ```swift title="list.swift" /* 访问元素 */ - const num = list[1]; // 访问索引 1 处的元素 + let num = nums[1] // 访问索引 1 处的元素 /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 + nums[1] = 0 // 将索引 1 处的元素更新为 0 ``` -=== "TypeScript" +=== "JS" - ```typescript title="list.ts" + ```javascript title="list.js" /* 访问元素 */ - const num: number = list[1]; // 访问索引 1 处的元素 + const num = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 + nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` -=== "C" +=== "TS" - ```c title="list.c" + ```typescript title="list.ts" + /* 访问元素 */ + const num: number = nums[1]; // 访问索引 1 处的元素 + /* 更新元素 */ + nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` -=== "C#" +=== "Dart" - ```csharp title="list.cs" + ```dart title="list.dart" /* 访问元素 */ - int num = list[1]; // 访问索引 1 处的元素 + int num = nums[1]; // 访问索引 1 处的元素 /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 + nums[1] = 0; // 将索引 1 处的元素更新为 0 ``` -=== "Swift" +=== "Rust" - ```swift title="list.swift" + ```rust title="list.rs" /* 访问元素 */ - let num = list[1] // 访问索引 1 处的元素 + let num: i32 = nums[1]; // 访问索引 1 处的元素 + /* 更新元素 */ + nums[1] = 0; // 将索引 1 处的元素更新为 0 + ``` + +=== "C" + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 访问元素 */ + val num = nums[1] // 访问索引 1 处的元素 /* 更新元素 */ - list[1] = 0 // 将索引 1 处的元素更新为 0 + nums[1] = 0 // 将索引 1 处的元素更新为 0 ``` -**在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 +=== "Ruby" -=== "Java" + ```ruby title="list.rb" + # 访问元素 + num = nums[1] # 访问索引 1 处的元素 + # 更新元素 + nums[1] = 0 # 将索引 1 处的元素更新为 0 + ``` - ```java title="list.java" - /* 清空列表 */ - list.clear(); +=== "Zig" - /* 尾部添加元素 */ - list.add(1); - list.add(3); - list.add(2); - list.add(5); - list.add(4); + ```zig title="list.zig" + // 访问元素 + var num = nums.items[1]; // 访问索引 1 处的元素 - /* 中间插入元素 */ - list.add(3, 6); // 在索引 3 处插入数字 6 + // 更新元素 + nums.items[1] = 0; // 将索引 1 处的元素更新为 0 + ``` - /* 删除元素 */ - list.remove(3); // 删除索引 3 处的元素 +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%AE%BF%E9%97%AE%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E4%B8%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 插入与删除元素 + +相较于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但插入和删除元素的效率仍与数组相同,时间复杂度为 $O(n)$ 。 + +=== "Python" + + ```python title="list.py" + # 清空列表 + nums.clear() + + # 在尾部添加元素 + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + # 在中间插入元素 + nums.insert(3, 6) # 在索引 3 处插入数字 6 + + # 删除元素 + nums.pop(3) # 删除索引 3 处的元素 ``` === "C++" ```cpp title="list.cpp" /* 清空列表 */ - list.clear(); + nums.clear(); - /* 尾部添加元素 */ - list.push_back(1); - list.push_back(3); - list.push_back(2); - list.push_back(5); - list.push_back(4); + /* 在尾部添加元素 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); - /* 中间插入元素 */ - list.insert(list.begin() + 3, 6); // 在索引 3 处插入数字 6 + /* 在中间插入元素 */ + nums.insert(nums.begin() + 3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ - list.erase(list.begin() + 3); // 删除索引 3 处的元素 + nums.erase(nums.begin() + 3); // 删除索引 3 处的元素 ``` -=== "Python" +=== "Java" - ```python title="list.py" - """ 清空列表 """ - list.clear() + ```java title="list.java" + /* 清空列表 */ + nums.clear(); - """ 尾部添加元素 """ - list.append(1) - list.append(3) - list.append(2) - list.append(5) - list.append(4) + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); - """ 中间插入元素 """ - list.insert(3, 6) # 在索引 3 处插入数字 6 + /* 在中间插入元素 */ + nums.add(3, 6); // 在索引 3 处插入数字 6 - """ 删除元素 """ - list.pop(3) # 删除索引 3 处的元素 + /* 删除元素 */ + nums.remove(3); // 删除索引 3 处的元素 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 清空列表 */ + nums.Clear(); + + /* 在尾部添加元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + + /* 在中间插入元素 */ + nums.Insert(3, 6); // 在索引 3 处插入数字 6 + + /* 删除元素 */ + nums.RemoveAt(3); // 删除索引 3 处的元素 ``` === "Go" ```go title="list_test.go" /* 清空列表 */ - list = nil + nums = nil + + /* 在尾部添加元素 */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + + /* 在中间插入元素 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 处插入数字 6 + + /* 删除元素 */ + nums = append(nums[:3], nums[4:]...) // 删除索引 3 处的元素 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 清空列表 */ + nums.removeAll() - /* 尾部添加元素 */ - list = append(list, 1) - list = append(list, 3) - list = append(list, 2) - list = append(list, 5) - list = append(list, 4) + /* 在尾部添加元素 */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) - /* 中间插入元素 */ - list = append(list[:3], append([]int{6}, list[3:]...)...) // 在索引 3 处插入数字 6 + /* 在中间插入元素 */ + nums.insert(6, at: 3) // 在索引 3 处插入数字 6 /* 删除元素 */ - list = append(list[:3], list[4:]...) // 删除索引 3 处的元素 + nums.remove(at: 3) // 删除索引 3 处的元素 ``` -=== "JavaScript" +=== "JS" - ```js title="list.js" + ```javascript title="list.js" /* 清空列表 */ - list.length = 0; + nums.length = 0; - /* 尾部添加元素 */ - list.push(1); - list.push(3); - list.push(2); - list.push(5); - list.push(4); + /* 在尾部添加元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); - /* 中间插入元素 */ - list.splice(3, 0, 6); + /* 在中间插入元素 */ + nums.splice(3, 0, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ - list.splice(3, 1); + nums.splice(3, 1); // 删除索引 3 处的元素 ``` -=== "TypeScript" +=== "TS" ```typescript title="list.ts" /* 清空列表 */ - list.length = 0; + nums.length = 0; - /* 尾部添加元素 */ - list.push(1); - list.push(3); - list.push(2); - list.push(5); - list.push(4); + /* 在尾部添加元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); - /* 中间插入元素 */ - list.splice(3, 0, 6); + /* 在中间插入元素 */ + nums.splice(3, 0, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ - list.splice(3, 1); + nums.splice(3, 1); // 删除索引 3 处的元素 ``` -=== "C" +=== "Dart" - ```c title="list.c" + ```dart title="list.dart" + /* 清空列表 */ + nums.clear(); + + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + /* 在中间插入元素 */ + nums.insert(3, 6); // 在索引 3 处插入数字 6 + + /* 删除元素 */ + nums.removeAt(3); // 删除索引 3 处的元素 ``` -=== "C#" +=== "Rust" - ```csharp title="list.cs" + ```rust title="list.rs" /* 清空列表 */ - list.Clear(); + nums.clear(); - /* 尾部添加元素 */ - list.Add(1); - list.Add(3); - list.Add(2); - list.Add(5); - list.Add(4); + /* 在尾部添加元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); - /* 中间插入元素 */ - list.Insert(3, 6); + /* 在中间插入元素 */ + nums.insert(3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ - list.RemoveAt(3); + nums.remove(3); // 删除索引 3 处的元素 ``` -=== "Swift" +=== "C" - ```swift title="list.swift" + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" /* 清空列表 */ - list.removeAll() + nums.clear(); - /* 尾部添加元素 */ - list.append(1) - list.append(3) - list.append(2) - list.append(5) - list.append(4) + /* 在尾部添加元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); - /* 中间插入元素 */ - list.insert(6, at: 3) // 在索引 3 处插入数字 6 + /* 在中间插入元素 */ + nums.add(3, 6); // 在索引 3 处插入数字 6 /* 删除元素 */ - list.remove(at: 3) // 删除索引 3 处的元素 + nums.remove(3); // 删除索引 3 处的元素 ``` -**遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 +=== "Ruby" -=== "Java" + ```ruby title="list.rb" + # 清空列表 + nums.clear - ```java title="list.java" + # 在尾部添加元素 + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + + # 在中间插入元素 + nums.insert(3, 6) # 在索引 3 处插入数字 6 + + # 删除元素 + nums.delete_at(3) # 删除索引 3 处的元素 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 清空列表 + nums.clearRetainingCapacity(); + + // 在尾部添加元素 + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + + // 在中间插入元素 + try nums.insert(3, 6); // 在索引 3 处插入数字 6 + + // 删除元素 + _ = nums.orderedRemove(3); // 删除索引 3 处的元素 + ``` + +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283,%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 遍历列表 + +与数组一样,列表可以根据索引遍历,也可以直接遍历各元素。 + +=== "Python" + + ```python title="list.py" + # 通过索引遍历列表 + count = 0 + for i in range(len(nums)): + count += nums[i] + + # 直接遍历列表元素 + for num in nums: + count += num + ``` + +=== "C++" + + ```cpp title="list.cpp" /* 通过索引遍历列表 */ int count = 0; - for (int i = 0; i < list.size(); i++) { - count++; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; } /* 直接遍历列表元素 */ count = 0; - for (int n : list) { - count++; + for (int num : nums) { + count += num; } ``` -=== "C++" +=== "Java" - ```cpp title="list.cpp" + ```java title="list.java" /* 通过索引遍历列表 */ int count = 0; - for (int i = 0; i < list.size(); i++) { - count++; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); } /* 直接遍历列表元素 */ - count = 0; - for (int n : list) { - count++; + for (int num : nums) { + count += num; } ``` -=== "Python" +=== "C#" - ```python title="list.py" - """ 通过索引遍历列表 """ - count = 0 - for i in range(len(list)): - count += 1 + ```csharp title="list.cs" + /* 通过索引遍历列表 */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } - """ 直接遍历列表元素 """ - count = 0 - for n in list: - count += 1 + /* 直接遍历列表元素 */ + count = 0; + foreach (int num in nums) { + count += num; + } ``` === "Go" @@ -410,994 +644,391 @@ comments: true ```go title="list_test.go" /* 通过索引遍历列表 */ count := 0 - for i := 0; i < len(list); i++ { - count++ + for i := 0; i < len(nums); i++ { + count += nums[i] } /* 直接遍历列表元素 */ count = 0 - for range list { - count++ + for _, num := range nums { + count += num } ``` -=== "JavaScript" +=== "Swift" - ```js title="list.js" + ```swift title="list.swift" /* 通过索引遍历列表 */ - let count = 0; - for (let i = 0; i < list.length; i++) { - count++; + var count = 0 + for i in nums.indices { + count += nums[i] } /* 直接遍历列表元素 */ - count = 0; - for (const n of list) { - count++; + count = 0 + for num in nums { + count += num } ``` -=== "TypeScript" +=== "JS" - ```typescript title="list.ts" + ```javascript title="list.js" /* 通过索引遍历列表 */ let count = 0; - for (let i = 0; i < list.length; i++) { - count++; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; } /* 直接遍历列表元素 */ count = 0; - for (const n of list) { - count++; + for (const num of nums) { + count += num; } ``` -=== "C" +=== "TS" - ```c title="list.c" + ```typescript title="list.ts" + /* 通过索引遍历列表 */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + /* 直接遍历列表元素 */ + count = 0; + for (const num of nums) { + count += num; + } ``` -=== "C#" +=== "Dart" - ```csharp title="list.cs" + ```dart title="list.dart" /* 通过索引遍历列表 */ int count = 0; - for (int i = 0; i < list.Count(); i++) - { - count++; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; } /* 直接遍历列表元素 */ count = 0; - foreach (int n in list) - { - count++; + for (var num in nums) { + count += num; } ``` -=== "Swift" +=== "Rust" - ```swift title="list.swift" + ```rust title="list.rs" + // 通过索引遍历列表 + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + + // 直接遍历列表元素 + _count = 0; + for num in &nums { + _count += num; + } + ``` + +=== "C" + + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" /* 通过索引遍历列表 */ var count = 0 - for _ in list.indices { - count += 1 + for (i in nums.indices) { + count += nums[i] } /* 直接遍历列表元素 */ + for (num in nums) { + count += num + } + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 通过索引遍历列表 + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # 直接遍历列表元素 count = 0 - for _ in list { - count += 1 + for num in nums + count += num + end + ``` + +=== "Zig" + + ```zig title="list.zig" + // 通过索引遍历列表 + var count: i32 = 0; + var i: i32 = 0; + while (i < nums.items.len) : (i += 1) { + count += nums[i]; + } + + // 直接遍历列表元素 + count = 0; + for (nums.items) |num| { + count += num; } ``` -**拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 +??? pythontutor "可视化运行" -=== "Java" + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false - ```java title="list.java" - /* 拼接两个列表 */ - List list1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); - list.addAll(list1); // 将列表 list1 拼接到 list 之后 +### 拼接列表 + +给定一个新列表 `nums1` ,我们可以将其拼接到原列表的尾部。 + +=== "Python" + + ```python title="list.py" + # 拼接两个列表 + nums1: list[int] = [6, 8, 7, 10, 9] + nums += nums1 # 将列表 nums1 拼接到 nums 之后 ``` === "C++" ```cpp title="list.cpp" /* 拼接两个列表 */ - vector list1 = { 6, 8, 7, 10, 9 }; - // 将列表 list1 拼接到 list 之后 - list.insert(list.end(), list1.begin(), list1.end()); + vector nums1 = { 6, 8, 7, 10, 9 }; + // 将列表 nums1 拼接到 nums 之后 + nums.insert(nums.end(), nums1.begin(), nums1.end()); ``` -=== "Python" +=== "Java" - ```python title="list.py" - """ 拼接两个列表 """ - list1 = [6, 8, 7, 10, 9] - list += list1 # 将列表 list1 拼接到 list 之后 + ```java title="list.java" + /* 拼接两个列表 */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); // 将列表 nums1 拼接到 nums 之后 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 拼接两个列表 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); // 将列表 nums1 拼接到 nums 之后 ``` === "Go" ```go title="list_test.go" /* 拼接两个列表 */ - list1 := []int{6, 8, 7, 10, 9} - list = append(list, list1...) // 将列表 list1 拼接到 list 之后 + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // 将列表 nums1 拼接到 nums 之后 ``` -=== "JavaScript" +=== "Swift" - ```js title="list.js" + ```swift title="list.swift" /* 拼接两个列表 */ - const list1 = [6, 8, 7, 10, 9]; - list.push(...list1); // 将列表 list1 拼接到 list 之后 + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) // 将列表 nums1 拼接到 nums 之后 ``` -=== "TypeScript" +=== "JS" - ```typescript title="list.ts" + ```javascript title="list.js" /* 拼接两个列表 */ - const list1: number[] = [6, 8, 7, 10, 9]; - list.push(...list1); // 将列表 list1 拼接到 list 之后 + const nums1 = [6, 8, 7, 10, 9]; + nums.push(...nums1); // 将列表 nums1 拼接到 nums 之后 ``` -=== "C" - - ```c title="list.c" +=== "TS" + ```typescript title="list.ts" + /* 拼接两个列表 */ + const nums1: number[] = [6, 8, 7, 10, 9]; + nums.push(...nums1); // 将列表 nums1 拼接到 nums 之后 ``` -=== "C#" +=== "Dart" - ```csharp title="list.cs" + ```dart title="list.dart" /* 拼接两个列表 */ - List list1 = new() { 6, 8, 7, 10, 9 }; - list.AddRange(list1); // 将列表 list1 拼接到 list 之后 + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); // 将列表 nums1 拼接到 nums 之后 ``` -=== "Swift" +=== "Rust" - ```swift title="list.swift" + ```rust title="list.rs" /* 拼接两个列表 */ - let list1 = [6, 8, 7, 10, 9] - list.append(contentsOf: list1) // 将列表 list1 拼接到 list 之后 + let nums1: Vec = vec![6, 8, 7, 10, 9]; + nums.extend(nums1); ``` -**排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 - -=== "Java" +=== "C" - ```java title="list.java" - /* 排序列表 */ - Collections.sort(list); // 排序后,列表元素从小到大排列 + ```c title="list.c" + // C 未提供内置动态数组 ``` -=== "C++" +=== "Kotlin" - ```cpp title="list.cpp" - /* 排序列表 */ - sort(list.begin(), list.end()); // 排序后,列表元素从小到大排列 + ```kotlin title="list.kt" + /* 拼接两个列表 */ + val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() + nums.addAll(nums1) // 将列表 nums1 拼接到 nums 之后 ``` -=== "Python" +=== "Ruby" - ```python title="list.py" - """ 排序列表 """ - list.sort() # 排序后,列表元素从小到大排列 + ```ruby title="list.rb" + # 拼接两个列表 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 ``` -=== "Go" +=== "Zig" - ```go title="list_test.go" - /* 排序列表 */ - sort.Ints(list) // 排序后,列表元素从小到大排列 + ```zig title="list.zig" + // 拼接两个列表 + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); // 将列表 nums1 拼接到 nums 之后 ``` -=== "JavaScript" +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums1%20%3D%20%5B6,%208,%207,%2010,%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%90%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false - ```js title="list.js" - /* 排序列表 */ - list.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 +### 排序列表 + +完成列表排序后,我们便可以使用在数组类算法题中经常考查的“二分查找”和“双指针”算法。 + +=== "Python" + + ```python title="list.py" + # 排序列表 + nums.sort() # 排序后,列表元素从小到大排列 ``` -=== "TypeScript" +=== "C++" - ```typescript title="list.ts" + ```cpp title="list.cpp" /* 排序列表 */ - list.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 + sort(nums.begin(), nums.end()); // 排序后,列表元素从小到大排列 ``` -=== "C" - - ```c title="list.c" +=== "Java" + ```java title="list.java" + /* 排序列表 */ + Collections.sort(nums); // 排序后,列表元素从小到大排列 ``` === "C#" ```csharp title="list.cs" /* 排序列表 */ - list.Sort(); // 排序后,列表元素从小到大排列 + nums.Sort(); // 排序后,列表元素从小到大排列 ``` -=== "Swift" +=== "Go" - ```swift title="list.swift" + ```go title="list_test.go" /* 排序列表 */ - list.sort() // 排序后,列表元素从小到大排列 + sort.Ints(nums) // 排序后,列表元素从小到大排列 ``` -## 列表简易实现 * - -为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: - -- **初始容量**:选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。 -- **数量记录**:需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。 -- **扩容机制**:插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。 - -本示例是为了帮助读者对如何实现列表产生直观的认识。实际编程语言中,列表的实现远比以下代码复杂且标准,感兴趣的读者可以查阅源码学习。 - -=== "Java" +=== "Swift" - ```java title="my_list.java" - /* 列表类简易实现 */ - class MyList { - private int[] nums; // 数组(存储列表元素) - private int capacity = 10; // 列表容量 - private int size = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - public MyList() { - nums = new int[capacity]; - } - - /* 获取列表长度(即当前元素数量)*/ - public int size() { - return size; - } - - /* 获取列表容量 */ - public int capacity() { - return capacity; - } - - /* 访问元素 */ - public int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index >= size) - throw new IndexOutOfBoundsException("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - public void set(int index, int num) { - if (index >= size) - throw new IndexOutOfBoundsException("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - public void add(int num) { - // 元素数量超出容量时,触发扩容机制 - if (size == capacity()) - extendCapacity(); - nums[size] = num; - // 更新元素数量 - size++; - } - - /* 中间插入元素 */ - public void insert(int index, int num) { - if (index >= size) - throw new IndexOutOfBoundsException("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size == capacity()) - extendCapacity(); - // 将索引 index 以及之后的元素都向后移动一位 - for (int j = size - 1; j >= index; j--) { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - size++; - } - - /* 删除元素 */ - public int remove(int index) { - if (index >= size) - throw new IndexOutOfBoundsException("索引越界"); - int num = nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (int j = index; j < size - 1; j++) { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - public void extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - nums = Arrays.copyOf(nums, capacity() * extendRatio); - // 更新列表容量 - capacity = nums.length; - } - } + ```swift title="list.swift" + /* 排序列表 */ + nums.sort() // 排序后,列表元素从小到大排列 ``` -=== "C++" +=== "JS" - ```cpp title="my_list.cpp" - /* 列表类简易实现 */ - class MyList { - private: - int* nums; // 数组(存储列表元素) - int numsCapacity = 10; // 列表容量 - int numsSize = 0; // 列表长度(即当前元素数量) - int extendRatio = 2; // 每次列表扩容的倍数 - - public: - /* 构造函数 */ - MyList() { - nums = new int[numsCapacity]; - } - - /* 获取列表长度(即当前元素数量)*/ - int size() { - return numsSize; - } - - /* 获取列表容量 */ - int capacity() { - return numsCapacity; - } - - /* 访问元素 */ - int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index >= size()) - throw out_of_range("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - void set(int index, int num) { - if (index >= size()) - throw out_of_range("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - void add(int num) { - // 元素数量超出容量时,触发扩容机制 - if (size() == capacity()) - extendCapacity(); - nums[size()] = num; - // 更新元素数量 - numsSize++; - } - - /* 中间插入元素 */ - void insert(int index, int num) { - if (index >= size()) - throw out_of_range("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size() == capacity()) - extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 - for (int j = size() - 1; j >= index; j--) { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - numsSize++; - } - - /* 删除元素 */ - int remove(int index) { - if (index >= size()) - throw out_of_range("索引越界"); - int num = nums[index]; - // 索引 i 之后的元素都向前移动一位 - for (int j = index; j < size() - 1; j++) { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - numsSize--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - void extendCapacity() { - // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组 - int newCapacity = capacity() * extendRatio; - int* extend = new int[newCapacity]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < size(); i++) { - extend[i] = nums[i]; - } - int* temp = nums; - nums = extend; - delete[] temp; - numsCapacity = newCapacity; - } - }; + ```javascript title="list.js" + /* 排序列表 */ + nums.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 ``` -=== "Python" +=== "TS" - ```python title="my_list.py" - """ 列表类简易实现 """ - class MyList: - """ 构造函数 """ - def __init__(self): - self.__capacity = 10 # 列表容量 - self.__nums = [0] * self.__capacity # 数组(存储列表元素) - self.__size = 0 # 列表长度(即当前元素数量) - self.__extend_ratio = 2 # 每次列表扩容的倍数 - - """ 获取列表长度(即当前元素数量) """ - def size(self): - return self.__size - - """ 获取列表容量 """ - def capacity(self): - return self.__capacity - - """ 访问元素 """ - def get(self, index): - # 索引如果越界则抛出异常,下同 - assert index < self.__size, "索引越界" - return self.__nums[index] - - """ 更新元素 """ - def set(self, num, index): - assert index < self.__size, "索引越界" - self.__nums[index] = num - - """ 中间插入(尾部添加)元素 """ - def add(self, num, index=-1): - assert index < self.__size, "索引越界" - # 若不指定索引 index ,则向数组尾部添加元素 - if index == -1: - index = self.__size - # 元素数量超出容量时,触发扩容机制 - if self.__size == self.capacity(): - self.extend_capacity() - # 索引 i 以及之后的元素都向后移动一位 - for j in range(self.__size - 1, index - 1, -1): - self.__nums[j + 1] = self.__nums[j] - self.__nums[index] = num - # 更新元素数量 - self.__size += 1 - - """ 删除元素 """ - def remove(self, index): - assert index < self.__size, "索引越界" - # 索引 i 之后的元素都向前移动一位 - for j in range(index, self.__size - 1): - self.__nums[j] = self.__nums[j + 1] - # 更新元素数量 - self.__size -= 1 - - """ 列表扩容 """ - def extend_capacity(self): - # 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 - self.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1) - # 更新列表容量 - self.__capacity = len(self.__nums) + ```typescript title="list.ts" + /* 排序列表 */ + nums.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 ``` -=== "Go" +=== "Dart" - ```go title="my_list.go" - /* 列表类简易实现 */ - type myList struct { - numsCapacity int - nums []int - numsSize int - extendRatio int - } - - /* 构造函数 */ - func newMyList() *myList { - return &myList{ - numsCapacity: 10, // 列表容量 - nums: make([]int, 10), // 数组(存储列表元素) - numsSize: 0, // 列表长度(即当前元素数量) - extendRatio: 2, // 每次列表扩容的倍数 - } - } + ```dart title="list.dart" + /* 排序列表 */ + nums.sort(); // 排序后,列表元素从小到大排列 + ``` - /* 获取列表长度(即当前元素数量) */ - func (l *myList) size() int { - return l.numsSize - } +=== "Rust" - /* 获取列表容量 */ - func (l *myList) capacity() int { - return l.numsCapacity - } + ```rust title="list.rs" + /* 排序列表 */ + nums.sort(); // 排序后,列表元素从小到大排列 + ``` - /* 访问元素 */ - func (l *myList) get(index int) int { - // 索引如果越界则抛出异常,下同 - if index >= l.numsSize { - panic("索引越界") - } - return l.nums[index] - } +=== "C" - /* 更新元素 */ - func (l *myList) set(num, index int) { - if index >= l.numsSize { - panic("索引越界") - } - l.nums[index] = num - } + ```c title="list.c" + // C 未提供内置动态数组 + ``` - /* 尾部添加元素 */ - func (l *myList) add(num int) { - // 元素数量超出容量时,触发扩容机制 - if l.numsSize == l.numsCapacity { - l.extendCapacity() - } - l.nums[l.numsSize] = num - // 更新元素数量 - l.numsSize++ - } +=== "Kotlin" - /* 中间插入元素 */ - func (l *myList) insert(num, index int) { - if index >= l.numsSize { - panic("索引越界") - } - // 元素数量超出容量时,触发扩容机制 - if l.numsSize == l.numsCapacity { - l.extendCapacity() - } - // 索引 i 以及之后的元素都向后移动一位 - for j := l.numsSize - 1; j >= index; j-- { - l.nums[j+1] = l.nums[j] - } - l.nums[index] = num - // 更新元素数量 - l.numsSize++ - } + ```kotlin title="list.kt" + /* 排序列表 */ + nums.sort() // 排序后,列表元素从小到大排列 + ``` - /* 删除元素 */ - func (l *myList) remove(index int) int { - if index >= l.numsSize { - panic("索引越界") - } - num := l.nums[index] - // 索引 i 之后的元素都向前移动一位 - for j := index; j < l.numsSize-1; j++ { - l.nums[j] = l.nums[j+1] - } - // 更新元素数量 - l.numsSize-- - // 返回被删除元素 - return num - } +=== "Ruby" - /* 列表扩容 */ - func (l *myList) extendCapacity() { - // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 - l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...) - // 更新列表容量 - l.numsCapacity = len(l.nums) - } + ```ruby title="list.rb" + # 排序列表 + nums = nums.sort { |a, b| a <=> b } # 排序后,列表元素从小到大排列 ``` -=== "JavaScript" - - ```js title="my_list.js" - /* 列表类简易实现 */ - class MyList { - #nums = new Array(); // 数组(存储列表元素) - #capacity = 10; // 列表容量 - #size = 0; // 列表长度(即当前元素数量) - #extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - constructor() { - this.#nums = new Array(this.#capacity); - } - - /* 获取列表长度(即当前元素数量)*/ - size() { - return this.#size; - } - - /* 获取列表容量 */ - capacity() { - return this.#capacity; - } - - /* 访问元素 */ - get(index) { - // 索引如果越界则抛出异常,下同 - if (index >= this.#size) { - throw new Error('索引越界'); - } - return this.#nums[index]; - } - - /* 更新元素 */ - set(index, num) { - if (index >= this._size) throw new Error('索引越界'); - this.#nums[index] = num; - } - - /* 尾部添加元素 */ - add(num) { - // 如果长度等于容量,则需要扩容 - if (this.#size === this.#capacity) { - this.extendCapacity(); - } - // 将新元素添加到列表尾部 - this.#nums[this.#size] = num; - this.#size++; - } - - /* 中间插入元素 */ - insert(index, num) { - if (index >= this.#size) { - throw new Error('索引越界'); - } - // 元素数量超出容量时,触发扩容机制 - if (this.#size === this.#capacity) { - this.extendCapacity(); - } - // 将索引 index 以及之后的元素都向后移动一位 - for (let j = this.#size - 1; j >= index; j--) { - this.#nums[j + 1] = this.#nums[j]; - } - // 更新元素数量 - this.#nums[index] = num; - this.#size++; - } - - /* 删除元素 */ - remove(index) { - if (index >= this.#size) throw new Error('索引越界'); - let num = this.#nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (let j = index; j < this.#size - 1; j++) { - this.#nums[j] = this.#nums[j + 1]; - } - // 更新元素数量 - this.#size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - this.#nums = this.#nums.concat( - new Array(this.capacity() * (this.#extendRatio - 1)) - ); - // 更新列表容量 - this.#capacity = this.#nums.length; - } - } - ``` +=== "Zig" -=== "TypeScript" - - ```typescript title="my_list.ts" - /* 列表类简易实现 */ - class MyList { - private nums: Array; // 数组(存储列表元素) - private _capacity: number = 10; // 列表容量 - private _size: number = 0; // 列表长度(即当前元素数量) - private extendRatio: number = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - constructor() { - this.nums = new Array(this._capacity); - } - - /* 获取列表长度(即当前元素数量)*/ - public size(): number { - return this._size; - } - - /* 获取列表容量 */ - public capacity(): number { - return this._capacity; - } - - /* 访问元素 */ - public get(index: number): number { - // 索引如果越界则抛出异常,下同 - if (index >= this._size) { - throw new Error('索引越界'); - } - return this.nums[index]; - } - - /* 更新元素 */ - public set(index: number, num: number): void { - if (index >= this._size) throw new Error('索引越界'); - this.nums[index] = num; - } - - /* 尾部添加元素 */ - public add(num: number): void { - // 如果长度等于容量,则需要扩容 - if (this._size === this._capacity) { - this.extendCapacity(); - } - // 将新元素添加到列表尾部 - this.nums[this._size] = num; - this._size++; - } - - /* 中间插入元素 */ - public insert(index: number, num: number): void { - if (index >= this._size) { - throw new Error('索引越界'); - } - // 元素数量超出容量时,触发扩容机制 - if (this._size === this._capacity) { - this.extendCapacity(); - } - // 将索引 index 以及之后的元素都向后移动一位 - for (let j = this._size - 1; j >= index; j--) { - this.nums[j + 1] = this.nums[j]; - } - // 更新元素数量 - this.nums[index] = num; - this._size++; - } - - /* 删除元素 */ - public remove(index: number): number { - if (index >= this._size) throw new Error('索引越界'); - let num = this.nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (let j = index; j < this._size - 1; j++) { - this.nums[j] = this.nums[j + 1]; - } - // 更新元素数量 - this._size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - public extendCapacity(): void { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - this.nums = this.nums.concat( - new Array(this.capacity() * (this.extendRatio - 1)) - ); - // 更新列表容量 - this._capacity = this.nums.length; - } - } + ```zig title="list.zig" + // 排序列表 + std.sort.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); ``` -=== "C" +??? pythontutor "可视化运行" - ```c title="my_list.c" + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%90%8E%EF%BC%8C%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E4%BB%8E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false - ``` +## 列表实现 -=== "C#" +许多编程语言内置了列表,例如 Java、C++、Python 等。它们的实现比较复杂,各个参数的设定也非常考究,例如初始容量、扩容倍数等。感兴趣的读者可以查阅源码进行学习。 - ```csharp title="my_list.cs" - class MyList - { - private int[] nums; // 数组(存储列表元素) - private int capacity = 10; // 列表容量 - private int size = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - public MyList() - { - nums = new int[capacity]; - } - - /* 获取列表长度(即当前元素数量)*/ - public int Size() - { - return size; - } - - /* 获取列表容量 */ - public int Capacity() - { - return capacity; - } - - /* 访问元素 */ - public int Get(int index) - { - // 索引如果越界则抛出异常,下同 - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - public void Set(int index, int num) - { - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - public void Add(int num) - { - // 元素数量超出容量时,触发扩容机制 - if (size == Capacity()) - ExtendCapacity(); - nums[size] = num; - // 更新元素数量 - size++; - } - - /* 中间插入元素 */ - public void Insert(int index, int num) - { - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size == Capacity()) - ExtendCapacity(); - // 将索引 index 以及之后的元素都向后移动一位 - for (int j = size - 1; j >= index; j--) - { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - size++; - } - - /* 删除元素 */ - public int Remove(int index) - { - if (index >= size) - throw new IndexOutOfRangeException("索引越界"); - int num = nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (int j = index; j < size - 1; j++) - { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - public void ExtendCapacity() - { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - System.Array.Resize(ref nums, Capacity() * extendRatio); - // 更新列表容量 - capacity = nums.Length; - } - } - ``` +为了加深对列表工作原理的理解,我们尝试实现一个简易版列表,包括以下三个重点设计。 -=== "Swift" +- **初始容量**:选取一个合理的数组初始容量。在本示例中,我们选择 10 作为初始容量。 +- **数量记录**:声明一个变量 `size` ,用于记录列表当前元素数量,并随着元素插入和删除实时更新。根据此变量,我们可以定位列表尾部,以及判断是否需要扩容。 +- **扩容机制**:若插入元素时列表容量已满,则需要进行扩容。先根据扩容倍数创建一个更大的数组,再将当前数组的所有元素依次移动至新数组。在本示例中,我们规定每次将数组扩容至之前的 2 倍。 - ```swift title="my_list.swift" - /* 列表类简易实现 */ - class MyList { - private var nums: [Int] // 数组(存储列表元素) - private var _capacity = 10 // 列表容量 - private var _size = 0 // 列表长度(即当前元素数量) - private let extendRatio = 2 // 每次列表扩容的倍数 - - /* 构造函数 */ - init() { - nums = Array(repeating: 0, count: _capacity) - } - - /* 获取列表长度(即当前元素数量)*/ - func size() -> Int { - _size - } - - /* 获取列表容量 */ - func capacity() -> Int { - _capacity - } - - /* 访问元素 */ - func get(index: Int) -> Int { - // 索引如果越界则抛出错误,下同 - if index >= _size { - fatalError("索引越界") - } - return nums[index] - } - - /* 更新元素 */ - func set(index: Int, num: Int) { - if index >= _size { - fatalError("索引越界") - } - nums[index] = num - } - - /* 尾部添加元素 */ - func add(num: Int) { - // 元素数量超出容量时,触发扩容机制 - if _size == _capacity { - extendCapacity() - } - nums[_size] = num - // 更新元素数量 - _size += 1 - } - - /* 中间插入元素 */ - func insert(index: Int, num: Int) { - if index >= _size { - fatalError("索引越界") - } - // 元素数量超出容量时,触发扩容机制 - if _size == _capacity { - extendCapacity() - } - // 将索引 index 以及之后的元素都向后移动一位 - for j in sequence(first: _size - 1, next: { $0 >= index + 1 ? $0 - 1 : nil }) { - nums[j + 1] = nums[j] - } - nums[index] = num - // 更新元素数量 - _size += 1 - } - - /* 删除元素 */ - @discardableResult - func remove(index: Int) -> Int { - if index >= _size { - fatalError("索引越界") - } - let num = nums[index] - // 将索引 index 之后的元素都向前移动一位 - for j in index ..< (_size - 1) { - nums[j] = nums[j + 1] - } - // 更新元素数量 - _size -= 1 - // 返回被删除元素 - return num - } - - /* 列表扩容 */ - func extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) - // 更新列表容量 - _capacity = nums.count - } - - /* 将列表转换为数组 */ - func toArray() -> [Int] { - var nums = Array(repeating: 0, count: _size) - for i in 0 ..< _size { - nums[i] = get(index: i) - } - return nums - } - } - ``` +```src +[file]{my_list}-[class]{my_list}-[func]{} +``` diff --git a/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png b/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png new file mode 100644 index 0000000000..85d772f7f4 Binary files /dev/null and b/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png differ diff --git a/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png b/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png new file mode 100644 index 0000000000..e97efb87d8 Binary files /dev/null and b/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png differ diff --git a/docs/chapter_array_and_linkedlist/ram_and_cache.md b/docs/chapter_array_and_linkedlist/ram_and_cache.md new file mode 100644 index 0000000000..e4271f0280 --- /dev/null +++ b/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -0,0 +1,71 @@ +# 内存与缓存 * + +在本章的前两节中,我们探讨了数组和链表这两种基础且重要的数据结构,它们分别代表了“连续存储”和“分散存储”两种物理结构。 + +实际上,**物理结构在很大程度上决定了程序对内存和缓存的使用效率**,进而影响算法程序的整体性能。 + +## 计算机存储设备 + +计算机中包括三种类型的存储设备:硬盘(hard disk)内存(random-access memory, RAM)缓存(cache memory)。下表展示了它们在计算机系统中的不同角色和性能特点。 + +

  计算机的存储设备

+ +| | 硬盘 | 内存 | 缓存 | +| ------ | ---------------------------------------- | -------------------------------------- | ------------------------------------------------- | +| 用途 | 长期存储数据,包括操作系统、程序、文件等 | 临时存储当前运行的程序和正在处理的数据 | 存储经常访问的数据和指令,减少 CPU 访问内存的次数 | +| 易失性 | 断电后数据不会丢失 | 断电后数据会丢失 | 断电后数据会丢失 | +| 容量 | 较大,TB 级别 | 较小,GB 级别 | 非常小,MB 级别 | +| 速度 | 较慢,几百到几千 MB/s | 较快,几十 GB/s | 非常快,几十到几百 GB/s | +| 价格 | 较便宜,几毛到几元 / GB | 较贵,几十到几百元 / GB | 非常贵,随 CPU 打包计价 | + +我们可以将计算机存储系统想象为下图所示的金字塔结构。越靠近金字塔顶端的存储设备的速度越快、容量越小、成本越高。这种多层级的设计并非偶然,而是计算机科学家和工程师们经过深思熟虑的结果。 + +- **硬盘难以被内存取代**。首先,内存中的数据在断电后会丢失,因此它不适合长期存储数据;其次,内存的成本是硬盘的几十倍,这使得它难以在消费者市场普及。 +- **缓存的大容量和高速度难以兼得**。随着 L1、L2、L3 缓存的容量逐步增大,其物理尺寸会变大,与 CPU 核心之间的物理距离会变远,从而导致数据传输时间增加,元素访问延迟变高。在当前技术下,多层级的缓存结构是容量、速度和成本之间的最佳平衡点。 + +![计算机存储系统](ram_and_cache.assets/storage_pyramid.png) + +!!! tip + + 计算机的存储层次结构体现了速度、容量和成本三者之间的精妙平衡。实际上,这种权衡普遍存在于所有工业领域,它要求我们在不同的优势和限制之间找到最佳平衡点。 + +总的来说,**硬盘用于长期存储大量数据,内存用于临时存储程序运行中正在处理的数据,而缓存则用于存储经常访问的数据和指令**,以提高程序运行效率。三者共同协作,确保计算机系统高效运行。 + +如下图所示,在程序运行时,数据会从硬盘中被读取到内存中,供 CPU 计算使用。缓存可以看作 CPU 的一部分,**它通过智能地从内存加载数据**,给 CPU 提供高速的数据读取,从而显著提升程序的执行效率,减少对较慢的内存的依赖。 + +![硬盘、内存和缓存之间的数据流通](ram_and_cache.assets/computer_storage_devices.png) + +## 数据结构的内存效率 + +在内存空间利用方面,数组和链表各自具有优势和局限性。 + +一方面,**内存是有限的,且同一块内存不能被多个程序共享**,因此我们希望数据结构能够尽可能高效地利用空间。数组的元素紧密排列,不需要额外的空间来存储链表节点间的引用(指针),因此空间效率更高。然而,数组需要一次性分配足够的连续内存空间,这可能导致内存浪费,数组扩容也需要额外的时间和空间成本。相比之下,链表以“节点”为单位进行动态内存分配和回收,提供了更大的灵活性。 + +另一方面,在程序运行时,**随着反复申请与释放内存,空闲内存的碎片化程度会越来越高**,从而导致内存的利用效率降低。数组由于其连续的存储方式,相对不容易导致内存碎片化。相反,链表的元素是分散存储的,在频繁的插入与删除操作中,更容易导致内存碎片化。 + +## 数据结构的缓存效率 + +缓存虽然在空间容量上远小于内存,但它比内存快得多,在程序执行速度上起着至关重要的作用。由于缓存的容量有限,只能存储一小部分频繁访问的数据,因此当 CPU 尝试访问的数据不在缓存中时,就会发生缓存未命中(cache miss),此时 CPU 不得不从速度较慢的内存中加载所需数据。 + +显然,**“缓存未命中”越少,CPU 读写数据的效率就越高**,程序性能也就越好。我们将 CPU 从缓存中成功获取数据的比例称为缓存命中率(cache hit rate),这个指标通常用来衡量缓存效率。 + +为了尽可能达到更高的效率,缓存会采取以下数据加载机制。 + +- **缓存行**:缓存不是单个字节地存储与加载数据,而是以缓存行为单位。相比于单个字节的传输,缓存行的传输形式更加高效。 +- **预取机制**:处理器会尝试预测数据访问模式(例如顺序访问、固定步长跳跃访问等),并根据特定模式将数据加载至缓存之中,从而提升命中率。 +- **空间局部性**:如果一个数据被访问,那么它附近的数据可能近期也会被访问。因此,缓存在加载某一数据时,也会加载其附近的数据,以提高命中率。 +- **时间局部性**:如果一个数据被访问,那么它在不久的将来很可能再次被访问。缓存利用这一原理,通过保留最近访问过的数据来提高命中率。 + +实际上,**数组和链表对缓存的利用效率是不同的**,主要体现在以下几个方面。 + +- **占用空间**:链表元素比数组元素占用空间更多,导致缓存中容纳的有效数据量更少。 +- **缓存行**:链表数据分散在内存各处,而缓存是“按行加载”的,因此加载到无效数据的比例更高。 +- **预取机制**:数组比链表的数据访问模式更具“可预测性”,即系统更容易猜出即将被加载的数据。 +- **空间局部性**:数组被存储在集中的内存空间中,因此被加载数据附近的数据更有可能即将被访问。 + +总体而言,**数组具有更高的缓存命中率,因此它在操作效率上通常优于链表**。这使得在解决算法问题时,基于数组实现的数据结构往往更受欢迎。 + +需要注意的是,**高缓存效率并不意味着数组在所有情况下都优于链表**。实际应用中选择哪种数据结构,应根据具体需求来决定。例如,数组和链表都可以实现“栈”数据结构(下一章会详细介绍),但它们适用于不同场景。 + +- 在做算法题时,我们会倾向于选择基于数组实现的栈,因为它提供了更高的操作效率和随机访问的能力,代价仅是需要预先为数组分配一定的内存空间。 +- 如果数据量非常大、动态性很高、栈的预期大小难以估计,那么基于链表实现的栈更加合适。链表能够将大量数据分散存储于内存的不同部分,并且避免了数组扩容产生的额外开销。 diff --git a/docs/chapter_array_and_linkedlist/summary.md b/docs/chapter_array_and_linkedlist/summary.md index 60fe604f0d..191e8a00fa 100644 --- a/docs/chapter_array_and_linkedlist/summary.md +++ b/docs/chapter_array_and_linkedlist/summary.md @@ -1,41 +1,86 @@ ---- -comments: true ---- - # 小结 -- 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优点与缺点呈现出此消彼长的关系。 -- 数组支持随机访问、内存空间占用小;但插入与删除元素效率低,且初始化后长度不可变。 -- 链表可通过更改指针实现高效的结点插入与删除,并且可以灵活地修改长度;但结点访问效率低、占用内存多。常见的链表类型有单向链表、循环链表、双向链表。 -- 列表又称动态数组,是基于数组实现的一种数据结构,其保存了数组的优势,且可以灵活改变长度。列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费。 +### 重点回顾 + +- 数组和链表是两种基本的数据结构,分别代表数据在计算机内存中的两种存储方式:连续空间存储和分散空间存储。两者的特点呈现出互补的特性。 +- 数组支持随机访问、占用内存较少;但插入和删除元素效率低,且初始化后长度不可变。 +- 链表通过更改引用(指针)实现高效的节点插入与删除,且可以灵活调整长度;但节点访问效率低、占用内存较多。常见的链表类型包括单向链表、环形链表、双向链表。 +- 列表是一种支持增删查改的元素有序集合,通常基于动态数组实现。它保留了数组的优势,同时可以灵活调整长度。 +- 列表的出现大幅提高了数组的实用性,但可能导致部分内存空间浪费。 +- 程序运行时,数据主要存储在内存中。数组可提供更高的内存空间效率,而链表则在内存使用上更加灵活。 +- 缓存通过缓存行、预取机制以及空间局部性和时间局部性等数据加载机制,为 CPU 提供快速数据访问,显著提升程序的执行效率。 +- 由于数组具有更高的缓存命中率,因此它通常比链表更高效。在选择数据结构时,应根据具体需求和场景做出恰当选择。 + +### Q & A + +**Q**:数组存储在栈上和存储在堆上,对时间效率和空间效率是否有影响? + +存储在栈上和堆上的数组都被存储在连续内存空间内,数据操作效率基本一致。然而,栈和堆具有各自的特点,从而导致以下不同点。 + +1. 分配和释放效率:栈是一块较小的内存,分配由编译器自动完成;而堆内存相对更大,可以在代码中动态分配,更容易碎片化。因此,堆上的分配和释放操作通常比栈上的慢。 +2. 大小限制:栈内存相对较小,堆的大小一般受限于可用内存。因此堆更加适合存储大型数组。 +3. 灵活性:栈上的数组的大小需要在编译时确定,而堆上的数组的大小可以在运行时动态确定。 + +**Q**:为什么数组要求相同类型的元素,而在链表中却没有强调相同类型呢? + +链表由节点组成,节点之间通过引用(指针)连接,各个节点可以存储不同类型的数据,例如 `int`、`double`、`string`、`object` 等。 + +相对地,数组元素则必须是相同类型的,这样才能通过计算偏移量来获取对应元素位置。例如,数组同时包含 `int` 和 `long` 两种类型,单个元素分别占用 4 字节和 8 字节 ,此时就不能用以下公式计算偏移量了,因为数组中包含了两种“元素长度”。 + +```shell +# 元素内存地址 = 数组内存地址(首元素内存地址) + 元素长度 * 元素索引 +``` + +**Q**:删除节点 `P` 后,是否需要把 `P.next` 设为 `None` 呢? + +不修改 `P.next` 也可以。从该链表的角度看,从头节点遍历到尾节点已经不会遇到 `P` 了。这意味着节点 `P` 已经从链表中删除了,此时节点 `P` 指向哪里都不会对该链表产生影响。 + +从数据结构与算法(做题)的角度看,不断开没有关系,只要保证程序的逻辑是正确的就行。从标准库的角度看,断开更加安全、逻辑更加清晰。如果不断开,假设被删除节点未被正常回收,那么它会影响后继节点的内存回收。 + +**Q**:在链表中插入和删除操作的时间复杂度是 $O(1)$ 。但是增删之前都需要 $O(n)$ 的时间查找元素,那为什么时间复杂度不是 $O(n)$ 呢? + +如果是先查找元素、再删除元素,时间复杂度确实是 $O(n)$ 。然而,链表的 $O(1)$ 增删的优势可以在其他应用上得到体现。例如,双向队列适合使用链表实现,我们维护一个指针变量始终指向头节点、尾节点,每次插入与删除操作都是 $O(1)$ 。 + +**Q**:图“链表定义与存储方式”中,浅蓝色的存储节点指针是占用一块内存地址吗?还是和节点值各占一半呢? + +该示意图只是定性表示,定量表示需要根据具体情况进行分析。 + +- 不同类型的节点值占用的空间是不同的,比如 `int`、`long`、`double` 和实例对象等。 +- 指针变量占用的内存空间大小根据所使用的操作系统及编译环境而定,大多为 8 字节或 4 字节。 + +**Q**:在列表末尾添加元素是否时时刻刻都为 $O(1)$ ? + +如果添加元素时超出列表长度,则需要先扩容列表再添加。系统会申请一块新的内存,并将原列表的所有元素搬运过去,这时候时间复杂度就会是 $O(n)$ 。 + +**Q**:“列表的出现极大地提高了数组的实用性,但可能导致部分内存空间浪费”,这里的空间浪费是指额外增加的变量如容量、长度、扩容倍数所占的内存吗? + +这里的空间浪费主要有两方面含义:一方面,列表都会设定一个初始长度,我们不一定需要用这么多;另一方面,为了防止频繁扩容,扩容一般会乘以一个系数,比如 $\times 1.5$ 。这样一来,也会出现很多空位,我们通常不能完全填满它们。 + +**Q**:在 Python 中初始化 `n = [1, 2, 3]` 后,这 3 个元素的地址是相连的,但是初始化 `m = [2, 1, 3]` 会发现它们每个元素的 id 并不是连续的,而是分别跟 `n` 中的相同。这些元素的地址不连续,那么 `m` 还是数组吗? + +假如把列表元素换成链表节点 `n = [n1, n2, n3, n4, n5]` ,通常情况下这 5 个节点对象也分散存储在内存各处。然而,给定一个列表索引,我们仍然可以在 $O(1)$ 时间内获取节点内存地址,从而访问到对应的节点。这是因为数组中存储的是节点的引用,而非节点本身。 + +与许多语言不同,Python 中的数字也被包装为对象,列表中存储的不是数字本身,而是对数字的引用。因此,我们会发现两个数组中的相同数字拥有同一个 id ,并且这些数字的内存地址无须连续。 -## 数组 VS 链表 +**Q**:C++ STL 里面的 `std::list` 已经实现了双向链表,但好像一些算法书上不怎么直接使用它,是不是因为有什么局限性呢? -

Table. 数组与链表特点对比

+一方面,我们往往更青睐使用数组实现算法,而只在必要时才使用链表,主要有两个原因。 -
+- 空间开销:由于每个元素需要两个额外的指针(一个用于前一个元素,一个用于后一个元素),所以 `std::list` 通常比 `std::vector` 更占用空间。 +- 缓存不友好:由于数据不是连续存放的,因此 `std::list` 对缓存的利用率较低。一般情况下,`std::vector` 的性能会更好。 -| | 数组 | 链表 | -| ------------ | ------------------------ | ------------ | -| 存储方式 | 连续内存空间 | 离散内存空间 | -| 数据结构长度 | 长度不可变 | 长度可变 | -| 内存使用率 | 占用内存少、缓存局部性好 | 占用内存多 | -| 优势操作 | 随机访问 | 插入、删除 | +另一方面,必要使用链表的情况主要是二叉树和图。栈和队列往往会使用编程语言提供的 `stack` 和 `queue` ,而非链表。 -
+**Q**:操作 `res = [[0]] * n` 生成了一个二维列表,其中每一个 `[0]` 都是独立的吗? -!!! tip +不是独立的。此二维列表中,所有的 `[0]` 实际上是同一个对象的引用。如果我们修改其中一个元素,会发现所有的对应元素都会随之改变。 - 「缓存局部性(Cache locality)」涉及到了计算机操作系统,在本书不做展开介绍,建议有兴趣的同学 Google / Baidu 一下。 +如果希望二维列表中的每个 `[0]` 都是独立的,可以使用 `res = [[0] for _ in range(n)]` 来实现。这种方式的原理是初始化了 $n$ 个独立的 `[0]` 列表对象。 -

Table. 数组与链表操作时间复杂度

+**Q**:操作 `res = [0] * n` 生成了一个列表,其中每一个整数 0 都是独立的吗? -
+在该列表中,所有整数 0 都是同一个对象的引用。这是因为 Python 对小整数(通常是 -5 到 256)采用了缓存池机制,以便最大化对象复用,从而提升性能。 -| 操作 | 数组 | 链表 | -| ------- | ------ | ------ | -| 访问元素 | $O(1)$ | $O(N)$ | -| 添加元素 | $O(N)$ | $O(1)$ | -| 删除元素 | $O(N)$ | $O(1)$ | +虽然它们指向同一个对象,但我们仍然可以独立修改列表中的每个元素,这是因为 Python 的整数是“不可变对象”。当我们修改某个元素时,实际上是切换为另一个对象的引用,而不是改变原有对象本身。 -
+然而,当列表元素是“可变对象”时(例如列表、字典或类实例等),修改某个元素会直接改变该对象本身,所有引用该对象的元素都会产生相同变化。 diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png b/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png new file mode 100644 index 0000000000..e67db95386 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png new file mode 100644 index 0000000000..33f6b5984a Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png new file mode 100644 index 0000000000..5aa53b8980 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png new file mode 100644 index 0000000000..54f8ae71d5 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png new file mode 100644 index 0000000000..bc2c907c3c Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png new file mode 100644 index 0000000000..128a3c17bd Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png new file mode 100644 index 0000000000..5487d8f31d Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png new file mode 100644 index 0000000000..a09c3a22c3 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png new file mode 100644 index 0000000000..1c748d86b9 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png new file mode 100644 index 0000000000..bbc4a66fd5 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png new file mode 100644 index 0000000000..c2df100bd2 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png new file mode 100644 index 0000000000..85ecd6b6c2 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png new file mode 100644 index 0000000000..48e08cd4db Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png new file mode 100644 index 0000000000..792d0120bd Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md new file mode 100644 index 0000000000..dcdebd6770 --- /dev/null +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -0,0 +1,509 @@ +# 回溯算法 + +回溯算法(backtracking algorithm)是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。 + +回溯算法通常采用“深度优先搜索”来遍历解空间。在“二叉树”章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。接下来,我们利用前序遍历构造一个回溯问题,逐步了解回溯算法的工作原理。 + +!!! question "例题一" + + 给定一棵二叉树,搜索并记录所有值为 $7$ 的节点,请返回节点列表。 + +对于此题,我们前序遍历这棵树,并判断当前节点的值是否为 $7$ ,若是,则将该节点的值加入结果列表 `res` 之中。相关过程实现如下图和以下代码所示: + +```src +[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} +``` + +![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png) + +## 尝试与回退 + +**之所以称之为回溯算法,是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略**。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。 + +对于例题一,访问每个节点都代表一次“尝试”,而越过叶节点或返回父节点的 `return` 则表示“回退”。 + +值得说明的是,**回退并不仅仅包括函数返回**。为解释这一点,我们对例题一稍作拓展。 + +!!! question "例题二" + + 在二叉树中搜索所有值为 $7$ 的节点,**请返回根节点到这些节点的路径**。 + +在例题一代码的基础上,我们需要借助一个列表 `path` 记录访问过的节点路径。当访问到值为 $7$ 的节点时,则复制 `path` 并添加进结果列表 `res` 。遍历完成后,`res` 中保存的就是所有的解。代码如下所示: + +```src +[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} +``` + +在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。 + +观察下图所示的过程,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作互为逆向。 + +=== "<1>" + ![尝试与回退](backtracking_algorithm.assets/preorder_find_paths_step1.png) + +=== "<2>" + ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) + +=== "<3>" + ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) + +=== "<4>" + ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) + +=== "<5>" + ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) + +=== "<6>" + ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) + +=== "<7>" + ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) + +=== "<8>" + ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) + +=== "<9>" + ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) + +=== "<10>" + ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) + +=== "<11>" + ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) + +## 剪枝 + +复杂的回溯问题通常包含一个或多个约束条件,**约束条件通常可用于“剪枝”**。 + +!!! question "例题三" + + 在二叉树中搜索所有值为 $7$ 的节点,请返回根节点到这些节点的路径,**并要求路径中不包含值为 $3$ 的节点**。 + +为了满足以上约束条件,**我们需要添加剪枝操作**:在搜索过程中,若遇到值为 $3$ 的节点,则提前返回,不再继续搜索。代码如下所示: + +```src +[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} +``` + +“剪枝”是一个非常形象的名词。如下图所示,在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提高了搜索效率。 + +![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png) + +## 框架代码 + +接下来,我们尝试将回溯的“尝试、回退、剪枝”的主体框架提炼出来,提升代码的通用性。 + +在以下框架代码中,`state` 表示问题的当前状态,`choices` 表示当前状态下可以做出的选择: + +=== "Python" + + ```python title="" + def backtrack(state: State, choices: list[choice], res: list[state]): + """回溯算法框架""" + # 判断是否为解 + if is_solution(state): + # 记录解 + record_solution(state, res) + # 不再继续搜索 + return + # 遍历所有选择 + for choice in choices: + # 剪枝:判断选择是否合法 + if is_valid(state, choice): + # 尝试:做出选择,更新状态 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + ``` + +=== "C++" + + ```cpp title="" + /* 回溯算法框架 */ + void backtrack(State *state, vector &choices, vector &res) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + // 不再继续搜索 + return; + } + // 遍历所有选择 + for (Choice choice : choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "Java" + + ```java title="" + /* 回溯算法框架 */ + void backtrack(State state, List choices, List res) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + // 不再继续搜索 + return; + } + // 遍历所有选择 + for (Choice choice : choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + /* 回溯算法框架 */ + void Backtrack(State state, List choices, List res) { + // 判断是否为解 + if (IsSolution(state)) { + // 记录解 + RecordSolution(state, res); + // 不再继续搜索 + return; + } + // 遍历所有选择 + foreach (Choice choice in choices) { + // 剪枝:判断选择是否合法 + if (IsValid(state, choice)) { + // 尝试:做出选择,更新状态 + MakeChoice(state, choice); + Backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + UndoChoice(state, choice); + } + } + } + ``` + +=== "Go" + + ```go title="" + /* 回溯算法框架 */ + func backtrack(state *State, choices []Choice, res *[]State) { + // 判断是否为解 + if isSolution(state) { + // 记录解 + recordSolution(state, res) + // 不再继续搜索 + return + } + // 遍历所有选择 + for _, choice := range choices { + // 剪枝:判断选择是否合法 + if isValid(state, choice) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice) + backtrack(state, choices, res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 回溯算法框架 */ + func backtrack(state: inout State, choices: [Choice], res: inout [State]) { + // 判断是否为解 + if isSolution(state: state) { + // 记录解 + recordSolution(state: state, res: &res) + // 不再继续搜索 + return + } + // 遍历所有选择 + for choice in choices { + // 剪枝:判断选择是否合法 + if isValid(state: state, choice: choice) { + // 尝试:做出选择,更新状态 + makeChoice(state: &state, choice: choice) + backtrack(state: &state, choices: choices, res: &res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state: &state, choice: choice) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 回溯算法框架 */ + function backtrack(state, choices, res) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + // 不再继续搜索 + return; + } + // 遍历所有选择 + for (let choice of choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 回溯算法框架 */ + function backtrack(state: State, choices: Choice[], res: State[]): void { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + // 不再继续搜索 + return; + } + // 遍历所有选择 + for (let choice of choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 回溯算法框架 */ + void backtrack(State state, List, List res) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + // 不再继续搜索 + return; + } + // 遍历所有选择 + for (Choice choice in choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + /* 回溯算法框架 */ + fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { + // 判断是否为解 + if is_solution(state) { + // 记录解 + record_solution(state, res); + // 不再继续搜索 + return; + } + // 遍历所有选择 + for choice in choices { + // 剪枝:判断选择是否合法 + if is_valid(state, choice) { + // 尝试:做出选择,更新状态 + make_choice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice); + } + } + } + ``` + +=== "C" + + ```c title="" + /* 回溯算法框架 */ + void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res, numRes); + // 不再继续搜索 + return; + } + // 遍历所有选择 + for (int i = 0; i < numChoices; i++) { + // 剪枝:判断选择是否合法 + if (isValid(state, &choices[i])) { + // 尝试:做出选择,更新状态 + makeChoice(state, &choices[i]); + backtrack(state, choices, numChoices, res, numRes); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, &choices[i]); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 回溯算法框架 */ + fun backtrack(state: State?, choices: List, res: List?) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res) + // 不再继续搜索 + return + } + // 遍历所有选择 + for (choice in choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice) + backtrack(state, choices, res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice) + } + } + } + ``` + +=== "Ruby" + + ```ruby title="" + ### 回溯算法框架 ### + def backtrack(state, choices, res) + # 判断是否为解 + if is_solution?(state) + # 记录解 + record_solution(state, res) + return + end + + # 遍历所有选择 + for choice in choices + # 剪枝:判断选择是否合法 + if is_valid?(state, choice) + # 尝试:做出选择,更新状态 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + end + end + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +接下来,我们基于框架代码来解决例题三。状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表: + +```src +[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} +``` + +根据题意,我们在找到值为 $7$ 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。下图对比了保留或删除 `return` 语句的搜索过程。 + +![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) + +相比基于前序遍历的代码实现,基于回溯算法框架的代码实现虽然显得啰唆,但通用性更好。实际上,**许多回溯问题可以在该框架下解决**。我们只需根据具体问题来定义 `state` 和 `choices` ,并实现框架中的各个方法即可。 + +## 常用术语 + +为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例,如下表所示。 + +

  常见的回溯算法术语

+ +| 名词 | 定义 | 例题三 | +| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| 解(solution) | 解是满足问题特定条件的答案,可能有一个或多个 | 根节点到节点 $7$ 的满足约束条件的所有路径 | +| 约束条件(constraint) | 约束条件是问题中限制解的可行性的条件,通常用于剪枝 | 路径中不包含节点 $3$ | +| 状态(state) | 状态表示问题在某一时刻的情况,包括已经做出的选择 | 当前已访问的节点路径,即 `path` 节点列表 | +| 尝试(attempt) | 尝试是根据可用选择来探索解空间的过程,包括做出选择,更新状态,检查是否为解 | 递归访问左(右)子节点,将节点添加进 `path` ,判断节点的值是否为 $7$ | +| 回退(backtracking) | 回退指遇到不满足约束条件的状态时,撤销前面做出的选择,回到上一个状态 | 当越过叶节点、结束节点访问、遇到值为 $3$ 的节点时终止搜索,函数返回 | +| 剪枝(pruning) | 剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率 | 当遇到值为 $3$ 的节点时,则不再继续搜索 | + +!!! tip + + 问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。 + +## 优点与局限性 + +回溯算法本质上是一种深度优先搜索算法,它尝试所有可能的解决方案直到找到满足条件的解。这种方法的优点在于能够找到所有可能的解决方案,而且在合理的剪枝操作下,具有很高的效率。 + +然而,在处理大规模或者复杂问题时,**回溯算法的运行效率可能难以接受**。 + +- **时间**:回溯算法通常需要遍历状态空间的所有可能,时间复杂度可以达到指数阶或阶乘阶。 +- **空间**:在递归调用中需要保存当前的状态(例如路径、用于剪枝的辅助变量等),当深度很大时,空间需求可能会变得很大。 + +即便如此,**回溯算法仍然是某些搜索问题和约束满足问题的最佳解决方案**。对于这些问题,由于无法预测哪些选择可生成有效的解,因此我们必须对所有可能的选择进行遍历。在这种情况下,**关键是如何优化效率**,常见的效率优化方法有两种。 + +- **剪枝**:避免搜索那些肯定不会产生解的路径,从而节省时间和空间。 +- **启发式搜索**:在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。 + +## 回溯典型例题 + +回溯算法可用于解决许多搜索问题、约束满足问题和组合优化问题。 + +**搜索问题**:这类问题的目标是找到满足特定条件的解决方案。 + +- 全排列问题:给定一个集合,求出其所有可能的排列组合。 +- 子集和问题:给定一个集合和一个目标和,找到集合中所有和为目标和的子集。 +- 汉诺塔问题:给定三根柱子和一系列大小不同的圆盘,要求将所有圆盘从一根柱子移动到另一根柱子,每次只能移动一个圆盘,且不能将大圆盘放在小圆盘上。 + +**约束满足问题**:这类问题的目标是找到满足所有约束条件的解。 + +- $n$ 皇后:在 $n \times n$ 的棋盘上放置 $n$ 个皇后,使得它们互不攻击。 +- 数独:在 $9 \times 9$ 的网格中填入数字 $1$ ~ $9$ ,使得每行、每列和每个 $3 \times 3$ 子网格中的数字不重复。 +- 图着色问题:给定一个无向图,用最少的颜色给图的每个顶点着色,使得相邻顶点颜色不同。 + +**组合优化问题**:这类问题的目标是在一个组合空间中找到满足某些条件的最优解。 + +- 0-1 背包问题:给定一组物品和一个背包,每个物品有一定的价值和重量,要求在背包容量限制内,选择物品使得总价值最大。 +- 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。 +- 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。 + +请注意,对于许多组合优化问题,回溯不是最优解决方案。 + +- 0-1 背包问题通常使用动态规划解决,以达到更高的时间效率。 +- 旅行商是一个著名的 NP-Hard 问题,常用解法有遗传算法和蚁群算法等。 +- 最大团问题是图论中的一个经典问题,可用贪心算法等启发式算法来解决。 diff --git a/docs/chapter_backtracking/index.md b/docs/chapter_backtracking/index.md new file mode 100644 index 0000000000..68b6a3ae40 --- /dev/null +++ b/docs/chapter_backtracking/index.md @@ -0,0 +1,9 @@ +# 回溯 + +![回溯](../assets/covers/chapter_backtracking.jpg) + +!!! abstract + + 我们如同迷宫中的探索者,在前进的道路上可能会遇到困难。 + + 回溯的力量让我们能够重新开始,不断尝试,最终找到通往光明的出口。 diff --git a/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png b/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png new file mode 100644 index 0000000000..2e25943258 Binary files /dev/null and b/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png differ diff --git a/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png b/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png new file mode 100644 index 0000000000..58c1529a5a Binary files /dev/null and b/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png differ diff --git a/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png b/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png new file mode 100644 index 0000000000..318ee7fc87 Binary files /dev/null and b/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png differ diff --git a/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png b/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png new file mode 100644 index 0000000000..4fb4ba987b Binary files /dev/null and b/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png differ diff --git a/docs/chapter_backtracking/n_queens_problem.md b/docs/chapter_backtracking/n_queens_problem.md new file mode 100644 index 0000000000..c1ef020610 --- /dev/null +++ b/docs/chapter_backtracking/n_queens_problem.md @@ -0,0 +1,53 @@ +# n 皇后问题 + +!!! question + + 根据国际象棋的规则,皇后可以攻击与同处一行、一列或一条斜线上的棋子。给定 $n$ 个皇后和一个 $n \times n$ 大小的棋盘,寻找使得所有皇后之间无法相互攻击的摆放方案。 + +如下图所示,当 $n = 4$ 时,共可以找到两个解。从回溯算法的角度看,$n \times n$ 大小的棋盘共有 $n^2$ 个格子,给出了所有的选择 `choices` 。在逐个放置皇后的过程中,棋盘状态在不断地变化,每个时刻的棋盘就是状态 `state` 。 + +![4 皇后问题的解](n_queens_problem.assets/solution_4_queens.png) + +下图展示了本题的三个约束条件:**多个皇后不能在同一行、同一列、同一条对角线上**。值得注意的是,对角线分为主对角线 `\` 和次对角线 `/` 两种。 + +![n 皇后问题的约束条件](n_queens_problem.assets/n_queens_constraints.png) + +### 逐行放置策略 + +皇后的数量和棋盘的行数都为 $n$ ,因此我们容易得到一个推论:**棋盘每行都允许且只允许放置一个皇后**。 + +也就是说,我们可以采取逐行放置策略:从第一行开始,在每行放置一个皇后,直至最后一行结束。 + +下图所示为 4 皇后问题的逐行放置过程。受画幅限制,下图仅展开了第一行的其中一个搜索分支,并且将不满足列约束和对角线约束的方案都进行了剪枝。 + +![逐行放置策略](n_queens_problem.assets/n_queens_placing.png) + +从本质上看,**逐行放置策略起到了剪枝的作用**,它避免了同一行出现多个皇后的所有搜索分支。 + +### 列与对角线剪枝 + +为了满足列约束,我们可以利用一个长度为 $n$ 的布尔型数组 `cols` 记录每一列是否有皇后。在每次决定放置前,我们通过 `cols` 将已有皇后的列进行剪枝,并在回溯中动态更新 `cols` 的状态。 + +!!! tip + + 请注意,矩阵的起点位于左上角,其中行索引从上到下增加,列索引从左到右增加。 + +那么,如何处理对角线约束呢?设棋盘中某个格子的行列索引为 $(row, col)$ ,选定矩阵中的某条主对角线,我们发现该对角线上所有格子的行索引减列索引都相等,**即主对角线上所有格子的 $row - col$ 为恒定值**。 + +也就是说,如果两个格子满足 $row_1 - col_1 = row_2 - col_2$ ,则它们一定处在同一条主对角线上。利用该规律,我们可以借助下图所示的数组 `diags1` 记录每条主对角线上是否有皇后。 + +同理,**次对角线上的所有格子的 $row + col$ 是恒定值**。我们同样也可以借助数组 `diags2` 来处理次对角线约束。 + +![处理列约束和对角线约束](n_queens_problem.assets/n_queens_cols_diagonals.png) + +### 代码实现 + +请注意,$n$ 维方阵中 $row - col$ 的范围是 $[-n + 1, n - 1]$ ,$row + col$ 的范围是 $[0, 2n - 2]$ ,所以主对角线和次对角线的数量都为 $2n - 1$ ,即数组 `diags1` 和 `diags2` 的长度都为 $2n - 1$ 。 + +```src +[file]{n_queens}-[class]{}-[func]{n_queens} +``` + +逐行放置 $n$ 次,考虑列约束,则从第一行到最后一行分别有 $n$、$n-1$、$\dots$、$2$、$1$ 个选择,使用 $O(n!)$ 时间。当记录解时,需要复制矩阵 `state` 并添加进 `res` ,复制操作使用 $O(n^2)$ 时间。因此,**总体时间复杂度为 $O(n! \cdot n^2)$** 。实际上,根据对角线约束的剪枝也能够大幅缩小搜索空间,因而搜索效率往往优于以上时间复杂度。 + +数组 `state` 使用 $O(n^2)$ 空间,数组 `cols`、`diags1` 和 `diags2` 皆使用 $O(n)$ 空间。最大递归深度为 $n$ ,使用 $O(n)$ 栈帧空间。因此,**空间复杂度为 $O(n^2)$** 。 diff --git a/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png b/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png new file mode 100644 index 0000000000..20588b2e54 Binary files /dev/null and b/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png differ diff --git a/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png b/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png new file mode 100644 index 0000000000..ae7eefde63 Binary files /dev/null and b/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png differ diff --git a/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png b/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png new file mode 100644 index 0000000000..577760d392 Binary files /dev/null and b/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png differ diff --git a/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png b/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png new file mode 100644 index 0000000000..b46f5335e8 Binary files /dev/null and b/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png differ diff --git a/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png b/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png new file mode 100644 index 0000000000..ed5b53d53b Binary files /dev/null and b/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png differ diff --git a/docs/chapter_backtracking/permutations_problem.md b/docs/chapter_backtracking/permutations_problem.md new file mode 100644 index 0000000000..4c993606a7 --- /dev/null +++ b/docs/chapter_backtracking/permutations_problem.md @@ -0,0 +1,95 @@ +# 全排列问题 + +全排列问题是回溯算法的一个典型应用。它的定义是在给定一个集合(如一个数组或字符串)的情况下,找出其中元素的所有可能的排列。 + +下表列举了几个示例数据,包括输入数组和对应的所有排列。 + +

  全排列示例

+ +| 输入数组 | 所有排列 | +| :---------- | :----------------------------------------------------------------- | +| $[1]$ | $[1]$ | +| $[1, 2]$ | $[1, 2], [2, 1]$ | +| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | + +## 无相等元素的情况 + +!!! question + + 输入一个整数数组,其中不包含重复元素,返回所有可能的排列。 + +从回溯算法的角度看,**我们可以把生成排列的过程想象成一系列选择的结果**。假设输入数组为 $[1, 2, 3]$ ,如果我们先选择 $1$ ,再选择 $3$ ,最后选择 $2$ ,则获得排列 $[1, 3, 2]$ 。回退表示撤销一个选择,之后继续尝试其他选择。 + +从回溯代码的角度看,候选集合 `choices` 是输入数组中的所有元素,状态 `state` 是直至目前已被选择的元素。请注意,每个元素只允许被选择一次,**因此 `state` 中的所有元素都应该是唯一的**。 + +如下图所示,我们可以将搜索过程展开成一棵递归树,树中的每个节点代表当前状态 `state` 。从根节点开始,经过三轮选择后到达叶节点,每个叶节点都对应一个排列。 + +![全排列的递归树](permutations_problem.assets/permutations_i.png) + +### 重复选择剪枝 + +为了实现每个元素只被选择一次,我们考虑引入一个布尔型数组 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被选择,并基于它实现以下剪枝操作。 + +- 在做出选择 `choice[i]` 后,我们就将 `selected[i]` 赋值为 $\text{True}$ ,代表它已被选择。 +- 遍历选择列表 `choices` 时,跳过所有已被选择的节点,即剪枝。 + +如下图所示,假设我们第一轮选择 1 ,第二轮选择 3 ,第三轮选择 2 ,则需要在第二轮剪掉元素 1 的分支,在第三轮剪掉元素 1 和元素 3 的分支。 + +![全排列剪枝示例](permutations_problem.assets/permutations_i_pruning.png) + +观察上图发现,该剪枝操作将搜索空间大小从 $O(n^n)$ 减小至 $O(n!)$ 。 + +### 代码实现 + +想清楚以上信息之后,我们就可以在框架代码中做“完形填空”了。为了缩短整体代码,我们不单独实现框架代码中的各个函数,而是将它们展开在 `backtrack()` 函数中: + +```src +[file]{permutations_i}-[class]{}-[func]{permutations_i} +``` + +## 考虑相等元素的情况 + +!!! question + + 输入一个整数数组,**数组中可能包含重复元素**,返回所有不重复的排列。 + +假设输入数组为 $[1, 1, 2]$ 。为了方便区分两个重复元素 $1$ ,我们将第二个 $1$ 记为 $\hat{1}$ 。 + +如下图所示,上述方法生成的排列有一半是重复的。 + +![重复排列](permutations_problem.assets/permutations_ii.png) + +那么如何去除重复的排列呢?最直接地,考虑借助一个哈希集合,直接对排列结果进行去重。然而这样做不够优雅,**因为生成重复排列的搜索分支没有必要,应当提前识别并剪枝**,这样可以进一步提升算法效率。 + +### 相等元素剪枝 + +观察下图,在第一轮中,选择 $1$ 或选择 $\hat{1}$ 是等价的,在这两个选择之下生成的所有排列都是重复的。因此应该把 $\hat{1}$ 剪枝。 + +同理,在第一轮选择 $2$ 之后,第二轮选择中的 $1$ 和 $\hat{1}$ 也会产生重复分支,因此也应将第二轮的 $\hat{1}$ 剪枝。 + +从本质上看,**我们的目标是在某一轮选择中,保证多个相等的元素仅被选择一次**。 + +![重复排列剪枝](permutations_problem.assets/permutations_ii_pruning.png) + +### 代码实现 + +在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希集合 `duplicated` ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝: + +```src +[file]{permutations_ii}-[class]{}-[func]{permutations_ii} +``` + +假设元素两两之间互不相同,则 $n$ 个元素共有 $n!$ 种排列(阶乘);在记录结果时,需要复制长度为 $n$ 的列表,使用 $O(n)$ 时间。**因此时间复杂度为 $O(n!n)$** 。 + +最大递归深度为 $n$ ,使用 $O(n)$ 栈帧空间。`selected` 使用 $O(n)$ 空间。同一时刻最多共有 $n$ 个 `duplicated` ,使用 $O(n^2)$ 空间。**因此空间复杂度为 $O(n^2)$** 。 + +### 两种剪枝对比 + +请注意,虽然 `selected` 和 `duplicated` 都用于剪枝,但两者的目标不同。 + +- **重复选择剪枝**:整个搜索过程中只有一个 `selected` 。它记录的是当前状态中包含哪些元素,其作用是避免某个元素在 `state` 中重复出现。 +- **相等元素剪枝**:每轮选择(每个调用的 `backtrack` 函数)都包含一个 `duplicated` 。它记录的是在本轮遍历(`for` 循环)中哪些元素已被选择过,其作用是保证相等元素只被选择一次。 + +下图展示了两个剪枝条件的生效范围。注意,树中的每个节点代表一个选择,从根节点到叶节点的路径上的各个节点构成一个排列。 + +![两种剪枝条件的作用范围](permutations_problem.assets/permutations_ii_pruning_summary.png) diff --git a/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png new file mode 100644 index 0000000000..b997c39af6 Binary files /dev/null and b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png differ diff --git a/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png new file mode 100644 index 0000000000..29b4232235 Binary files /dev/null and b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png differ diff --git a/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png new file mode 100644 index 0000000000..65b2c2889b Binary files /dev/null and b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png differ diff --git a/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png new file mode 100644 index 0000000000..2a44448d90 Binary files /dev/null and b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png differ diff --git a/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png new file mode 100644 index 0000000000..7edc200edd Binary files /dev/null and b/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png differ diff --git a/docs/chapter_backtracking/subset_sum_problem.md b/docs/chapter_backtracking/subset_sum_problem.md new file mode 100644 index 0000000000..02a2812253 --- /dev/null +++ b/docs/chapter_backtracking/subset_sum_problem.md @@ -0,0 +1,95 @@ +# 子集和问题 + +## 无重复元素的情况 + +!!! question + + 给定一个正整数数组 `nums` 和一个目标正整数 `target` ,请找出所有可能的组合,使得组合中的元素和等于 `target` 。给定数组无重复元素,每个元素可以被选取多次。请以列表形式返回这些组合,列表中不应包含重复组合。 + +例如,输入集合 $\{3, 4, 5\}$ 和目标整数 $9$ ,解为 $\{3, 3, 3\}, \{4, 5\}$ 。需要注意以下两点。 + +- 输入集合中的元素可以被无限次重复选取。 +- 子集不区分元素顺序,比如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一个子集。 + +### 参考全排列解法 + +类似于全排列问题,我们可以把子集的生成过程想象成一系列选择的结果,并在选择过程中实时更新“元素和”,当元素和等于 `target` 时,就将子集记录至结果列表。 + +而与全排列问题不同的是,**本题集合中的元素可以被无限次选取**,因此无须借助 `selected` 布尔列表来记录元素是否已被选择。我们可以对全排列代码进行小幅修改,初步得到解题代码: + +```src +[file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} +``` + +向以上代码输入数组 $[3, 4, 5]$ 和目标元素 $9$ ,输出结果为 $[3, 3, 3], [4, 5], [5, 4]$ 。**虽然成功找出了所有和为 $9$ 的子集,但其中存在重复的子集 $[4, 5]$ 和 $[5, 4]$** 。 + +这是因为搜索过程是区分选择顺序的,然而子集不区分选择顺序。如下图所示,先选 $4$ 后选 $5$ 与先选 $5$ 后选 $4$ 是不同的分支,但对应同一个子集。 + +![子集搜索与越界剪枝](subset_sum_problem.assets/subset_sum_i_naive.png) + +为了去除重复子集,**一种直接的思路是对结果列表进行去重**。但这个方法效率很低,有两方面原因。 + +- 当数组元素较多,尤其是当 `target` 较大时,搜索过程会产生大量的重复子集。 +- 比较子集(数组)的异同非常耗时,需要先排序数组,再比较数组中每个元素的异同。 + +### 重复子集剪枝 + +**我们考虑在搜索过程中通过剪枝进行去重**。观察下图,重复子集是在以不同顺序选择数组元素时产生的,例如以下情况。 + +1. 当第一轮和第二轮分别选择 $3$ 和 $4$ 时,会生成包含这两个元素的所有子集,记为 $[3, 4, \dots]$ 。 +2. 之后,当第一轮选择 $4$ 时,**则第二轮应该跳过 $3$** ,因为该选择产生的子集 $[4, 3, \dots]$ 和第 `1.` 步中生成的子集完全重复。 + +在搜索过程中,每一层的选择都是从左到右被逐个尝试的,因此越靠右的分支被剪掉的越多。 + +1. 前两轮选择 $3$ 和 $5$ ,生成子集 $[3, 5, \dots]$ 。 +2. 前两轮选择 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。 +3. 若第一轮选择 $5$ ,**则第二轮应该跳过 $3$ 和 $4$** ,因为子集 $[5, 3, \dots]$ 和 $[5, 4, \dots]$ 与第 `1.` 步和第 `2.` 步中描述的子集完全重复。 + +![不同选择顺序导致的重复子集](subset_sum_problem.assets/subset_sum_i_pruning.png) + +总结来看,给定输入数组 $[x_1, x_2, \dots, x_n]$ ,设搜索过程中的选择序列为 $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ ,则该选择序列需要满足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,**不满足该条件的选择序列都会造成重复,应当剪枝**。 + +### 代码实现 + +为实现该剪枝,我们初始化变量 `start` ,用于指示遍历起始点。**当做出选择 $x_{i}$ 后,设定下一轮从索引 $i$ 开始遍历**。这样做就可以让选择序列满足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,从而保证子集唯一。 + +除此之外,我们还对代码进行了以下两项优化。 + +- 在开启搜索前,先将数组 `nums` 排序。在遍历所有选择时,**当子集和超过 `target` 时直接结束循环**,因为后边的元素更大,其子集和一定超过 `target` 。 +- 省去元素和变量 `total` ,**通过在 `target` 上执行减法来统计元素和**,当 `target` 等于 $0$ 时记录解。 + +```src +[file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} +``` + +下图所示为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入以上代码后的整体回溯过程。 + +![子集和 I 回溯过程](subset_sum_problem.assets/subset_sum_i.png) + +## 考虑重复元素的情况 + +!!! question + + 给定一个正整数数组 `nums` 和一个目标正整数 `target` ,请找出所有可能的组合,使得组合中的元素和等于 `target` 。**给定数组可能包含重复元素,每个元素只可被选择一次**。请以列表形式返回这些组合,列表中不应包含重复组合。 + +相比于上题,**本题的输入数组可能包含重复元素**,这引入了新的问题。例如,给定数组 $[4, \hat{4}, 5]$ 和目标元素 $9$ ,则现有代码的输出结果为 $[4, 5], [\hat{4}, 5]$ ,出现了重复子集。 + +**造成这种重复的原因是相等元素在某轮中被多次选择**。在下图中,第一轮共有三个选择,其中两个都为 $4$ ,会产生两个重复的搜索分支,从而输出重复子集;同理,第二轮的两个 $4$ 也会产生重复子集。 + +![相等元素导致的重复子集](subset_sum_problem.assets/subset_sum_ii_repeat.png) + +### 相等元素剪枝 + +为解决此问题,**我们需要限制相等元素在每一轮中只能被选择一次**。实现方式比较巧妙:由于数组是已排序的,因此相等元素都是相邻的。这意味着在某轮选择中,若当前元素与其左边元素相等,则说明它已经被选择过,因此直接跳过当前元素。 + +与此同时,**本题规定每个数组元素只能被选择一次**。幸运的是,我们也可以利用变量 `start` 来满足该约束:当做出选择 $x_{i}$ 后,设定下一轮从索引 $i + 1$ 开始向后遍历。这样既能去除重复子集,也能避免重复选择元素。 + +### 代码实现 + +```src +[file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} +``` + +下图展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。 + +![子集和 II 回溯过程](subset_sum_problem.assets/subset_sum_ii.png) diff --git a/docs/chapter_backtracking/summary.md b/docs/chapter_backtracking/summary.md new file mode 100644 index 0000000000..3cb028af84 --- /dev/null +++ b/docs/chapter_backtracking/summary.md @@ -0,0 +1,23 @@ +# 小结 + +### 重点回顾 + +- 回溯算法本质是穷举法,通过对解空间进行深度优先遍历来寻找符合条件的解。在搜索过程中,遇到满足条件的解则记录,直至找到所有解或遍历完成后结束。 +- 回溯算法的搜索过程包括尝试与回退两个部分。它通过深度优先搜索来尝试各种选择,当遇到不满足约束条件的情况时,则撤销上一步的选择,退回到之前的状态,并继续尝试其他选择。尝试与回退是两个方向相反的操作。 +- 回溯问题通常包含多个约束条件,它们可用于实现剪枝操作。剪枝可以提前结束不必要的搜索分支,大幅提升搜索效率。 +- 回溯算法主要可用于解决搜索问题和约束满足问题。组合优化问题虽然可以用回溯算法解决,但往往存在效率更高或效果更好的解法。 +- 全排列问题旨在搜索给定集合元素的所有可能的排列。我们借助一个数组来记录每个元素是否被选择,剪掉重复选择同一元素的搜索分支,确保每个元素只被选择一次。 +- 在全排列问题中,如果集合中存在重复元素,则最终结果会出现重复排列。我们需要约束相等元素在每轮中只能被选择一次,这通常借助一个哈希集合来实现。 +- 子集和问题的目标是在给定集合中找到和为目标值的所有子集。集合不区分元素顺序,而搜索过程会输出所有顺序的结果,产生重复子集。我们在回溯前将数据进行排序,并设置一个变量来指示每一轮的遍历起始点,从而将生成重复子集的搜索分支进行剪枝。 +- 对于子集和问题,数组中的相等元素会产生重复集合。我们利用数组已排序的前置条件,通过判断相邻元素是否相等实现剪枝,从而确保相等元素在每轮中只能被选中一次。 +- $n$ 皇后问题旨在寻找将 $n$ 个皇后放置到 $n \times n$ 尺寸棋盘上的方案,要求所有皇后两两之间无法攻击对方。该问题的约束条件有行约束、列约束、主对角线和次对角线约束。为满足行约束,我们采用按行放置的策略,保证每一行放置一个皇后。 +- 列约束和对角线约束的处理方式类似。对于列约束,我们利用一个数组来记录每一列是否有皇后,从而指示选中的格子是否合法。对于对角线约束,我们借助两个数组来分别记录该主、次对角线上是否存在皇后;难点在于找出处在同一主(副)对角线上的格子所满足的行列索引规律。 + +### Q & A + +**Q**:怎么理解回溯和递归的关系? + +总的来看,回溯是一种“算法策略”,而递归更像是一个“工具”。 + +- 回溯算法通常基于递归实现。然而,回溯是递归的应用场景之一,是递归在搜索问题中的应用。 +- 递归的结构体现了“子问题分解”的解题范式,常用于解决分治、回溯、动态规划(记忆化递归)等问题。 diff --git a/docs/chapter_computational_complexity/index.md b/docs/chapter_computational_complexity/index.md new file mode 100644 index 0000000000..dcb3b499ef --- /dev/null +++ b/docs/chapter_computational_complexity/index.md @@ -0,0 +1,9 @@ +# 复杂度分析 + +![复杂度分析](../assets/covers/chapter_complexity_analysis.jpg) + +!!! abstract + + 复杂度分析犹如浩瀚的算法宇宙中的时空向导。 + + 它带领我们在时间与空间这两个维度上深入探索,寻找更优雅的解决方案。 diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png b/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png new file mode 100644 index 0000000000..412564348e Binary files /dev/null and b/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png differ diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png b/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png new file mode 100644 index 0000000000..9bb69d05c0 Binary files /dev/null and b/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png differ diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png b/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png new file mode 100644 index 0000000000..0206091b26 Binary files /dev/null and b/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png differ diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png b/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png new file mode 100644 index 0000000000..9b862d3cdb Binary files /dev/null and b/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png differ diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png b/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png new file mode 100644 index 0000000000..4b85811d38 Binary files /dev/null and b/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png differ diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png b/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png new file mode 100644 index 0000000000..04066ae5fb Binary files /dev/null and b/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png differ diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.md b/docs/chapter_computational_complexity/iteration_and_recursion.md new file mode 100644 index 0000000000..b12fbcd02c --- /dev/null +++ b/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -0,0 +1,194 @@ +# 迭代与递归 + +在算法中,重复执行某个任务是很常见的,它与复杂度分析息息相关。因此,在介绍时间复杂度和空间复杂度之前,我们先来了解如何在程序中实现重复执行任务,即两种基本的程序控制结构:迭代、递归。 + +## 迭代 + +迭代(iteration)是一种重复执行某个任务的控制结构。在迭代中,程序会在满足一定的条件下重复执行某段代码,直到这个条件不再满足。 + +### for 循环 + +`for` 循环是最常见的迭代形式之一,**适合在预先知道迭代次数时使用**。 + +以下函数基于 `for` 循环实现了求和 $1 + 2 + \dots + n$ ,求和结果使用变量 `res` 记录。需要注意的是,Python 中 `range(a, b)` 对应的区间是“左闭右开”的,对应的遍历范围为 $a, a + 1, \dots, b-1$ : + +```src +[file]{iteration}-[class]{}-[func]{for_loop} +``` + +下图是该求和函数的流程框图。 + +![求和函数的流程框图](iteration_and_recursion.assets/iteration.png) + +此求和函数的操作数量与输入数据大小 $n$ 成正比,或者说成“线性关系”。实际上,**时间复杂度描述的就是这个“线性关系”**。相关内容将会在下一节中详细介绍。 + +### while 循环 + +与 `for` 循环类似,`while` 循环也是一种实现迭代的方法。在 `while` 循环中,程序每轮都会先检查条件,如果条件为真,则继续执行,否则就结束循环。 + +下面我们用 `while` 循环来实现求和 $1 + 2 + \dots + n$ : + +```src +[file]{iteration}-[class]{}-[func]{while_loop} +``` + +**`while` 循环比 `for` 循环的自由度更高**。在 `while` 循环中,我们可以自由地设计条件变量的初始化和更新步骤。 + +例如在以下代码中,条件变量 $i$ 每轮进行两次更新,这种情况就不太方便用 `for` 循环实现: + +```src +[file]{iteration}-[class]{}-[func]{while_loop_ii} +``` + +总的来说,**`for` 循环的代码更加紧凑,`while` 循环更加灵活**,两者都可以实现迭代结构。选择使用哪一个应该根据特定问题的需求来决定。 + +### 嵌套循环 + +我们可以在一个循环结构内嵌套另一个循环结构,下面以 `for` 循环为例: + +```src +[file]{iteration}-[class]{}-[func]{nested_for_loop} +``` + +下图是该嵌套循环的流程框图。 + +![嵌套循环的流程框图](iteration_and_recursion.assets/nested_iteration.png) + +在这种情况下,函数的操作数量与 $n^2$ 成正比,或者说算法运行时间和输入数据大小 $n$ 成“平方关系”。 + +我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方关系”,以此类推。 + +## 递归 + + 递归(recursion)是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。 + +1. **递**:程序不断深入地调用自身,通常传入更小或更简化的参数,直到达到“终止条件”。 +2. **归**:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。 + +而从实现的角度看,递归代码主要包含三个要素。 + +1. **终止条件**:用于决定什么时候由“递”转“归”。 +2. **递归调用**:对应“递”,函数调用自身,通常输入更小或更简化的参数。 +3. **返回结果**:对应“归”,将当前递归层级的结果返回至上一层。 + +观察以下代码,我们只需调用函数 `recur(n)` ,就可以完成 $1 + 2 + \dots + n$ 的计算: + +```src +[file]{recursion}-[class]{}-[func]{recur} +``` + +下图展示了该函数的递归过程。 + +![求和函数的递归过程](iteration_and_recursion.assets/recursion_sum.png) + +虽然从计算角度看,迭代与递归可以得到相同的结果,**但它们代表了两种完全不同的思考和解决问题的范式**。 + +- **迭代**:“自下而上”地解决问题。从最基础的步骤开始,然后不断重复或累加这些步骤,直到任务完成。 +- **递归**:“自上而下”地解决问题。将原问题分解为更小的子问题,这些子问题和原问题具有相同的形式。接下来将子问题继续分解为更小的子问题,直到基本情况时停止(基本情况的解是已知的)。 + +以上述求和函数为例,设问题 $f(n) = 1 + 2 + \dots + n$ 。 + +- **迭代**:在循环中模拟求和过程,从 $1$ 遍历到 $n$ ,每轮执行求和操作,即可求得 $f(n)$ 。 +- **递归**:将问题分解为子问题 $f(n) = n + f(n-1)$ ,不断(递归地)分解下去,直至基本情况 $f(1) = 1$ 时终止。 + +### 调用栈 + +递归函数每次调用自身时,系统都会为新开启的函数分配内存,以存储局部变量、调用地址和其他信息等。这将导致两方面的结果。 + +- 函数的上下文数据都存储在称为“栈帧空间”的内存区域中,直至函数返回后才会被释放。因此,**递归通常比迭代更加耗费内存空间**。 +- 递归调用函数会产生额外的开销。**因此递归通常比循环的时间效率更低**。 + +如下图所示,在触发终止条件前,同时存在 $n$ 个未返回的递归函数,**递归深度为 $n$** 。 + +![递归调用深度](iteration_and_recursion.assets/recursion_sum_depth.png) + +在实际中,编程语言允许的递归深度通常是有限的,过深的递归可能导致栈溢出错误。 + +### 尾递归 + +有趣的是,**如果函数在返回前的最后一步才进行递归调用**,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为尾递归(tail recursion)。 + +- **普通递归**:当函数返回到上一层级的函数后,需要继续执行代码,因此系统需要保存上一层调用的上下文。 +- **尾递归**:递归调用是函数返回前的最后一个操作,这意味着函数返回到上一层级后,无须继续执行其他操作,因此系统无须保存上一层函数的上下文。 + +以计算 $1 + 2 + \dots + n$ 为例,我们可以将结果变量 `res` 设为函数参数,从而实现尾递归: + +```src +[file]{recursion}-[class]{}-[func]{tail_recur} +``` + +尾递归的执行过程如下图所示。对比普通递归和尾递归,两者的求和操作的执行点是不同的。 + +- **普通递归**:求和操作是在“归”的过程中执行的,每层返回后都要再执行一次求和操作。 +- **尾递归**:求和操作是在“递”的过程中执行的,“归”的过程只需层层返回。 + +![尾递归过程](iteration_and_recursion.assets/tail_recursion_sum.png) + +!!! tip + + 请注意,许多编译器或解释器并不支持尾递归优化。例如,Python 默认不支持尾递归优化,因此即使函数是尾递归形式,仍然可能会遇到栈溢出问题。 + +### 递归树 + +当处理与“分治”相关的算法问题时,递归往往比迭代的思路更加直观、代码更加易读。以“斐波那契数列”为例。 + +!!! question + + 给定一个斐波那契数列 $0, 1, 1, 2, 3, 5, 8, 13, \dots$ ,求该数列的第 $n$ 个数字。 + +设斐波那契数列的第 $n$ 个数字为 $f(n)$ ,易得两个结论。 + +- 数列的前两个数字为 $f(1) = 0$ 和 $f(2) = 1$ 。 +- 数列中的每个数字是前两个数字的和,即 $f(n) = f(n - 1) + f(n - 2)$ 。 + +按照递推关系进行递归调用,将前两个数字作为终止条件,便可写出递归代码。调用 `fib(n)` 即可得到斐波那契数列的第 $n$ 个数字: + +```src +[file]{recursion}-[class]{}-[func]{fib} +``` + +观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如下图所示,这样不断递归调用下去,最终将产生一棵层数为 $n$ 的递归树(recursion tree)。 + +![斐波那契数列的递归树](iteration_and_recursion.assets/recursion_tree.png) + +从本质上看,递归体现了“将问题分解为更小子问题”的思维范式,这种分治策略至关重要。 + +- 从算法角度看,搜索、排序、回溯、分治、动态规划等许多重要算法策略直接或间接地应用了这种思维方式。 +- 从数据结构角度看,递归天然适合处理链表、树和图的相关问题,因为它们非常适合用分治思想进行分析。 + +## 两者对比 + +总结以上内容,如下表所示,迭代和递归在实现、性能和适用性上有所不同。 + +

  迭代与递归特点对比

+ +| | 迭代 | 递归 | +| -------- | -------------------------------------- | ------------------------------------------------------------ | +| 实现方式 | 循环结构 | 函数调用自身 | +| 时间效率 | 效率通常较高,无函数调用开销 | 每次函数调用都会产生开销 | +| 内存使用 | 通常使用固定大小的内存空间 | 累积函数调用可能使用大量的栈帧空间 | +| 适用问题 | 适用于简单循环任务,代码直观、可读性好 | 适用于子问题分解,如树、图、分治、回溯等,代码结构简洁、清晰 | + +!!! tip + + 如果感觉以下内容理解困难,可以在读完“栈”章节后再来复习。 + +那么,迭代和递归具有什么内在联系呢?以上述递归函数为例,求和操作在递归的“归”阶段进行。这意味着最初被调用的函数实际上是最后完成其求和操作的,**这种工作机制与栈的“先入后出”原则异曲同工**。 + +事实上,“调用栈”和“栈帧空间”这类递归术语已经暗示了递归与栈之间的密切关系。 + +1. **递**:当函数被调用时,系统会在“调用栈”上为该函数分配新的栈帧,用于存储函数的局部变量、参数、返回地址等数据。 +2. **归**:当函数完成执行并返回时,对应的栈帧会被从“调用栈”上移除,恢复之前函数的执行环境。 + +因此,**我们可以使用一个显式的栈来模拟调用栈的行为**,从而将递归转化为迭代形式: + +```src +[file]{recursion}-[class]{}-[func]{for_loop_recur} +``` + +观察以上代码,当递归转化为迭代后,代码变得更加复杂了。尽管迭代和递归在很多情况下可以互相转化,但不一定值得这样做,有以下两点原因。 + +- 转化后的代码可能更加难以理解,可读性更差。 +- 对于某些复杂问题,模拟系统调用栈的行为可能非常困难。 + +总之,**选择迭代还是递归取决于特定问题的性质**。在编程实践中,权衡两者的优劣并根据情境选择合适的方法至关重要。 diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index 9d6fd6628b..a6fb227b78 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -1,43 +1,49 @@ ---- -comments: true ---- - # 算法效率评估 -## 算法评价维度 +在算法设计中,我们先后追求以下两个层面的目标。 + +1. **找到问题解法**:算法需要在规定的输入范围内可靠地求得问题的正确解。 +2. **寻求最优解法**:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。 + +也就是说,在能够解决问题的前提下,算法效率已成为衡量算法优劣的主要评价指标,它包括以下两个维度。 + +- **时间效率**:算法运行时间的长短。 +- **空间效率**:算法占用内存空间的大小。 -在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。 +简而言之,**我们的目标是设计“既快又省”的数据结构与算法**。而有效地评估算法效率至关重要,因为只有这样,我们才能将各种算法进行对比,进而指导算法设计与优化过程。 -1. **找到问题解法**。算法需要能够在规定的输入范围下,可靠地求得问题的正确解。 -2. **寻求最优解法**。同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。 +效率评估方法主要分为两种:实际测试、理论估算。 -换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括: +## 实际测试 -- **时间效率**,即算法的运行速度的快慢。 -- **空间效率**,即算法占用的内存空间大小。 +假设我们现在有算法 `A` 和算法 `B` ,它们都能解决同一问题,现在需要对比这两个算法的效率。最直接的方法是找一台计算机,运行这两个算法,并监控记录它们的运行时间和内存占用情况。这种评估方式能够反映真实情况,但也存在较大的局限性。 -数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 +一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能表现。比如一个算法的并行度较高,那么它就更适合在多核 CPU 上运行,一个算法的内存操作密集,那么它在高性能内存上的表现就会更好。也就是说,算法在不同的机器上的测试结果可能是不一致的。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。 -## 效率评估方法 +另一方面,**展开完整测试非常耗费资源**。随着输入数据量的变化,算法会表现出不同的效率。例如,在输入数据量较小时,算法 `A` 的运行时间比算法 `B` 短;而在输入数据量较大时,测试结果可能恰恰相反。因此,为了得到有说服力的结论,我们需要测试各种规模的输入数据,而这需要耗费大量的计算资源。 -### 实际测试 +## 理论估算 -假设我们现在有算法 A 和 算法 B ,都能够解决同一问题,现在需要对比两个算法之间的效率。我们能够想到的最直接的方式,就是找一台计算机,把两个算法都完整跑一遍,并监控记录运行时间和内存占用情况。这种评估方式能够反映真实情况,但是也存在很大的硬伤。 +由于实际测试具有较大的局限性,我们可以考虑仅通过一些计算来评估算法的效率。这种估算方法被称为渐近复杂度分析(asymptotic complexity analysis),简称复杂度分析。 -**难以排除测试环境的干扰因素**。硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。 +复杂度分析能够体现算法运行所需的时间和空间资源与输入数据大小之间的关系。**它描述了随着输入数据大小的增加,算法执行所需时间和空间的增长趋势**。这个定义有些拗口,我们可以将其分为三个重点来理解。 -**展开完整测试非常耗费资源**。随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。 +- “时间和空间资源”分别对应时间复杂度(time complexity)空间复杂度(space complexity)。 +- “随着输入数据大小的增加”意味着复杂度反映了算法运行效率与输入数据体量之间的关系。 +- “时间和空间的增长趋势”表示复杂度分析关注的不是运行时间或占用空间的具体值,而是时间或空间增长的“快慢”。 -### 理论估算 +**复杂度分析克服了实际测试方法的弊端**,体现在以下几个方面。 -既然实际测试具有很大的局限性,那么我们是否可以仅通过一些计算,就获知算法的效率水平呢?答案是肯定的,我们将此估算方法称为「复杂度分析 Complexity Analysis」或「渐近复杂度分析 Asymptotic Complexity Analysis」。 +- 它无需实际运行代码,更加绿色节能。 +- 它独立于测试环境,分析结果适用于所有运行平台。 +- 它可以体现不同数据量下的算法效率,尤其是在大数据量下的算法性能。 -**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势**。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。 +!!! tip -**复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 + 如果你仍对复杂度的概念感到困惑,无须担心,我们会在后续章节中详细介绍。 -## 复杂度分析的重要性 +复杂度分析为我们提供了一把评估算法效率的“标尺”,使我们可以衡量执行某个算法所需的时间和空间资源,对比不同算法之间的效率。 -复杂度分析给出一把评价算法效率的“标尺”,告诉我们执行某个算法需要多少时间和空间资源,也让我们可以开展不同算法之间的效率对比。 +复杂度是个数学概念,对于初学者可能比较抽象,学习难度相对较高。从这个角度看,复杂度分析可能不太适合作为最先介绍的内容。然而,当我们讨论某个数据结构或算法的特点时,难以避免要分析其运行速度和空间使用情况。 -计算复杂度是个数学概念,对于初学者可能比较抽象,学习难度相对较高。从这个角度出发,其并不适合作为第一章内容。但是,当我们讨论某个数据结构或者算法的特点时,难以避免需要分析它的运行速度和空间使用情况。**因此,在展开学习数据结构与算法之前,建议读者先对计算复杂度建立起初步的了解,并且能够完成简单案例的复杂度分析**。 +综上所述,建议你在深入学习数据结构与算法之前,**先对复杂度分析建立初步的了解,以便能够完成简单算法的复杂度分析**。 diff --git a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png index 65c79e98ea..217dc6f03a 100644 Binary files a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png and b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png differ diff --git a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png index c26da84618..98730370db 100644 Binary files a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png and b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png differ diff --git a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png index 951a4f4b5b..0c05aabe79 100644 Binary files a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png and b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png differ diff --git a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png index 457b583a65..da696e9600 100644 Binary files a/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png and b/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png differ diff --git a/docs/chapter_computational_complexity/space_complexity.assets/space_types.png b/docs/chapter_computational_complexity/space_complexity.assets/space_types.png index 713a510a85..8dc9bc2b1c 100644 Binary files a/docs/chapter_computational_complexity/space_complexity.assets/space_types.png and b/docs/chapter_computational_complexity/space_complexity.assets/space_types.png differ diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md old mode 100644 new mode 100755 index e594bec8d2..e6909ae8de --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -1,32 +1,75 @@ ---- -comments: true ---- - # 空间复杂度 -「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势**。这个概念与时间复杂度很类似。 +空间复杂度(space complexity)用于衡量算法占用内存空间随着数据量变大时的增长趋势。这个概念与时间复杂度非常类似,只需将“运行时间”替换为“占用内存空间”。 ## 算法相关空间 -算法运行中,使用的内存空间主要有以下几种: +算法在运行过程中使用的内存空间主要包括以下几种。 + +- **输入空间**:用于存储算法的输入数据。 +- **暂存空间**:用于存储算法在运行过程中的变量、对象、函数上下文等数据。 +- **输出空间**:用于存储算法的输出数据。 + +一般情况下,空间复杂度的统计范围是“暂存空间”加上“输出空间”。 + +暂存空间可以进一步划分为三个部分。 + +- **暂存数据**:用于保存算法运行过程中的各种常量、变量、对象等。 +- **栈帧空间**:用于保存调用函数的上下文数据。系统在每次调用函数时都会在栈顶部创建一个栈帧,函数返回后,栈帧空间会被释放。 +- **指令空间**:用于保存编译后的程序指令,在实际统计中通常忽略不计。 + +在分析一段程序的空间复杂度时,**我们通常统计暂存数据、栈帧空间和输出数据三部分**,如下图所示。 + +![算法使用的相关空间](space_complexity.assets/space_types.png) + +相关代码如下: -- 「输入空间」用于存储算法的输入数据; -- 「暂存空间」用于存储算法运行中的变量、对象、函数上下文等数据; -- 「输出空间」用于存储算法的输出数据; +=== "Python" -!!! tip + ```python title="" + class Node: + """类""" + def __init__(self, x: int): + self.val: int = x # 节点值 + self.next: Node | None = None # 指向下一节点的引用 + + def function() -> int: + """函数""" + # 执行某些操作... + return 0 - 通常情况下,空间复杂度统计范围是「暂存空间」+「输出空间」。 + def algorithm(n) -> int: # 输入数据 + A = 0 # 暂存数据(常量,一般用大写字母表示) + b = 0 # 暂存数据(变量) + node = Node(0) # 暂存数据(对象) + c = function() # 栈帧空间(调用函数) + return A + b + c # 输出数据 + ``` -暂存空间可分为三个部分: +=== "C++" -- 「暂存数据」用于保存算法运行中的各种 **常量、变量、对象** 等。 -- 「栈帧空间」用于保存调用函数的上下文数据。系统每次调用函数都会在栈的顶部创建一个栈帧,函数返回时,栈帧空间会被释放。 -- 「指令空间」用于保存编译后的程序指令,**在实际统计中一般忽略不计**。 + ```cpp title="" + /* 结构体 */ + struct Node { + int val; + Node *next; + Node(int x) : val(x), next(nullptr) {} + }; -![space_types](space_complexity.assets/space_types.png) + /* 函数 */ + int func() { + // 执行某些操作... + return 0; + } -

Fig. 算法使用的相关空间

+ int algorithm(int n) { // 输入数据 + const int a = 0; // 暂存数据(常量) + int b = 0; // 暂存数据(变量) + Node* node = new Node(0); // 暂存数据(对象) + int c = func(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } + ``` === "Java" @@ -40,7 +83,7 @@ comments: true /* 函数 */ int function() { - // do something... + // 执行某些操作... return 0; } @@ -53,52 +96,30 @@ comments: true } ``` -=== "C++" +=== "C#" - ```cpp title="" - /* 结构体 */ - struct Node { - int val; - Node *next; - Node(int x) : val(x), next(nullptr) {} - }; + ```csharp title="" + /* 类 */ + class Node(int x) { + int val = x; + Node next; + } /* 函数 */ - int func() { - // do something... + int Function() { + // 执行某些操作... return 0; } - int algorithm(int n) { // 输入数据 + int Algorithm(int n) { // 输入数据 const int a = 0; // 暂存数据(常量) int b = 0; // 暂存数据(变量) - Node* node = new Node(0); // 暂存数据(对象) - int c = func(); // 栈帧空间(调用函数) + Node node = new(0); // 暂存数据(对象) + int c = Function(); // 栈帧空间(调用函数) return a + b + c; // 输出数据 } ``` -=== "Python" - - ```python title="" - """ 类 """ - class Node: - def __init__(self, x): - self.val = x # 结点值 - self.next = None # 指向下一结点的指针(引用) - - """ 函数 """ - def function(): - # do something... - return 0 - - def algorithm(n): # 输入数据 - b = 0 # 暂存数据(变量) - node = Node(0) # 暂存数据(对象) - c = function() # 栈帧空间(调用函数) - return a + b + c # 输出数据 - ``` - === "Go" ```go title="" @@ -115,7 +136,7 @@ comments: true /* 函数 */ func function() int { - // do something... + // 执行某些操作... return 0 } @@ -128,98 +149,243 @@ comments: true } ``` -=== "JavaScript" - - ```js title="" +=== "Swift" - ``` + ```swift title="" + /* 类 */ + class Node { + var val: Int + var next: Node? -=== "TypeScript" + init(x: Int) { + val = x + } + } - ```typescript title="" + /* 函数 */ + func function() -> Int { + // 执行某些操作... + return 0 + } + func algorithm(n: Int) -> Int { // 输入数据 + let a = 0 // 暂存数据(常量) + var b = 0 // 暂存数据(变量) + let node = Node(x: 0) // 暂存数据(对象) + let c = function() // 栈帧空间(调用函数) + return a + b + c // 输出数据 + } ``` -=== "C" +=== "JS" - ```c title="" + ```javascript title="" + /* 类 */ + class Node { + val; + next; + constructor(val) { + this.val = val === undefined ? 0 : val; // 节点值 + this.next = null; // 指向下一节点的引用 + } + } + + /* 函数 */ + function constFunc() { + // 执行某些操作 + return 0; + } + function algorithm(n) { // 输入数据 + const a = 0; // 暂存数据(常量) + let b = 0; // 暂存数据(变量) + const node = new Node(0); // 暂存数据(对象) + const c = constFunc(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } ``` -=== "C#" +=== "TS" - ```csharp title="" + ```typescript title="" /* 类 */ - class Node - { - int val; - Node next; - Node(int x) { val = x; } + class Node { + val: number; + next: Node | null; + constructor(val?: number) { + this.val = val === undefined ? 0 : val; // 节点值 + this.next = null; // 指向下一节点的引用 + } } /* 函数 */ - int function() - { - // do something... + function constFunc(): number { + // 执行某些操作 return 0; } - int algorithm(int n) // 输入数据 - { - int a = 0; // 暂存数据(常量) - int b = 0; // 暂存数据(变量) - Node node = new Node(0); // 暂存数据(对象) - int c = function(); // 栈帧空间(调用函数) - return a + b + c; // 输出数据 + function algorithm(n: number): number { // 输入数据 + const a = 0; // 暂存数据(常量) + let b = 0; // 暂存数据(变量) + const node = new Node(0); // 暂存数据(对象) + const c = constFunc(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 } ``` -=== "Swift" +=== "Dart" - ```swift title="" + ```dart title="" /* 类 */ class Node { - var val: Int - var next: Node? + int val; + Node next; + Node(this.val, [this.next]); + } - init(x: Int) { - val = x + /* 函数 */ + int function() { + // 执行某些操作... + return 0; + } + + int algorithm(int n) { // 输入数据 + const int a = 0; // 暂存数据(常量) + int b = 0; // 暂存数据(变量) + Node node = Node(0); // 暂存数据(对象) + int c = function(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 结构体 */ + struct Node { + val: i32, + next: Option>>, + } + + /* 创建 Node 结构体 */ + impl Node { + fn new(val: i32) -> Self { + Self { val: val, next: None } } } /* 函数 */ - func function() -> Int { - // do something... + fn function() -> i32 { + // 执行某些操作... + return 0; + } + + fn algorithm(n: i32) -> i32 { // 输入数据 + const a: i32 = 0; // 暂存数据(常量) + let mut b = 0; // 暂存数据(变量) + let node = Node::new(0); // 暂存数据(对象) + let c = function(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } + ``` + +=== "C" + + ```c title="" + /* 函数 */ + int func() { + // 执行某些操作... + return 0; + } + + int algorithm(int n) { // 输入数据 + const int a = 0; // 暂存数据(常量) + int b = 0; // 暂存数据(变量) + int c = func(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 类 */ + class Node(var _val: Int) { + var next: Node? = null + } + + /* 函数 */ + fun function(): Int { + // 执行某些操作... return 0 } - func algorithm(n: Int) -> Int { // 输入数据 - let a = 0 // 暂存数据(常量) - var b = 0 // 暂存数据(变量) - let node = Node(x: 0) // 暂存数据(对象) - let c = function() // 栈帧空间(调用函数) - return a + b + c // 输出数据 + fun algorithm(n: Int): Int { // 输入数据 + val a = 0 // 暂存数据(常量) + var b = 0 // 暂存数据(变量) + val node = Node(0) // 暂存数据(对象) + val c = function() // 栈帧空间(调用函数) + return a + b + c // 输出数据 } ``` +=== "Ruby" + + ```ruby title="" + ### 类 ### + class Node + attr_accessor :val # 节点值 + attr_accessor :next # 指向下一节点的引用 + + def initialize(x) + @val = x + end + end + + ### 函数 ### + def function + # 执行某些操作... + 0 + end + + ### 算法 ### + def algorithm(n) # 输入数据 + a = 0 # 暂存数据(常量) + b = 0 # 暂存数据(变量) + node = Node.new(0) # 暂存数据(对象) + c = function # 栈帧空间(调用函数) + a + b + c # 输出数据 + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + ## 推算方法 -空间复杂度的推算方法和时间复杂度总体类似,只是从统计“计算操作数量”变为统计“使用空间大小”。与时间复杂度不同的是,**我们一般只关注「最差空间复杂度」**。这是因为内存空间是一个硬性要求,我们必须保证在所有输入数据下都有足够的内存空间预留。 +空间复杂度的推算方法与时间复杂度大致相同,只需将统计对象从“操作数量”转为“使用空间大小”。 -**最差空间复杂度中的“最差”有两层含义**,分别为输入数据的最差分布、算法运行中的最差时间点。 +而与时间复杂度不同的是,**我们通常只关注最差空间复杂度**。这是因为内存空间是一项硬性要求,我们必须确保在所有输入数据下都有足够的内存空间预留。 -- **以最差输入数据为准**。当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; -- **以算法运行过程中的峰值内存为准**。程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; +观察以下代码,最差空间复杂度中的“最差”有两层含义。 -=== "Java" +1. **以最差输入数据为准**:当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但当 $n > 10$ 时,初始化的数组 `nums` 占用 $O(n)$ 空间,因此最差空间复杂度为 $O(n)$ 。 +2. **以算法运行中的峰值内存为准**:例如,程序在执行最后一行之前,占用 $O(1)$ 空间;当初始化数组 `nums` 时,程序占用 $O(n)$ 空间,因此最差空间复杂度为 $O(n)$ 。 - ```java title="" - void algorithm(int n) { - int a = 0; // O(1) - int[] b = new int[10000]; // O(1) - if (n > 10) - int[] nums = new int[n]; // O(n) - } +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 0 # O(1) + b = [0] * 10000 # O(1) + if n > 10: + nums = [0] * n # O(n) ``` === "C++" @@ -233,14 +399,27 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="" - def algorithm(n): - a = 0 # O(1) - b = [0] * 10000 # O(1) - if n > 10: - nums = [0] * n # O(n) + ```java title="" + void algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) + int[] nums = new int[n]; // O(n) + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) { + int[] nums = new int[n]; // O(n) + } + } ``` === "Go" @@ -257,126 +436,202 @@ comments: true } ``` -=== "JavaScript" +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + let a = 0 // O(1) + let b = Array(repeating: 0, count: 10000) // O(1) + if n > 10 { + let nums = Array(repeating: 0, count: n) // O(n) + } + } + ``` - ```js title="" +=== "JS" + ```javascript title="" + function algorithm(n) { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } ``` -=== "TypeScript" +=== "TS" ```typescript title="" - + function algorithm(n: number): void { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } ``` -=== "C" +=== "Dart" - ```c title="" + ```dart title="" + void algorithm(int n) { + int a = 0; // O(1) + List b = List.filled(10000, 0); // O(1) + if (n > 10) { + List nums = List.filled(n, 0); // O(n) + } + } + ``` +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let a = 0; // O(1) + let b = [0; 10000]; // O(1) + if n > 10 { + let nums = vec![0; n as usize]; // O(n) + } + } ``` -=== "C#" +=== "C" - ```csharp title="" - void algorithm(int n) - { - int a = 0; // O(1) - int[] b = new int[10000]; // O(1) + ```c title="" + void algorithm(int n) { + int a = 0; // O(1) + int b[10000]; // O(1) if (n > 10) - { - int[] nums = new int[n]; // O(n) - } + int nums[n] = {0}; // O(n) } ``` -=== "Swift" +=== "Kotlin" - ```swift title="" - func algorithm(n: Int) { - let a = 0 // O(1) - let b = Array(repeating: 0, count: 10000) // O(1) - if n > 10 { - let nums = Array(repeating: 0, count: n) // O(n) + ```kotlin title="" + fun algorithm(n: Int) { + val a = 0 // O(1) + val b = IntArray(10000) // O(1) + if (n > 10) { + val nums = IntArray(n) // O(n) } } ``` -**在递归函数中,需要注意统计栈帧空间**。例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 +=== "Ruby" -=== "Java" + ```ruby title="" + def algorithm(n) + a = 0 # O(1) + b = Array.new(10000) # O(1) + nums = Array.new(n) if n > 10 # O(n) + end + ``` + +=== "Zig" + + ```zig title="" - ```java title="" - int function() { - // do something - return 0; - } - /* 循环 O(1) */ - void loop(int n) { - for (int i = 0; i < n; i++) { - function(); - } - } - /* 递归 O(n) */ - void recur(int n) { - if (n == 1) return; - return recur(n - 1); - } ``` -=== "C++" +**在递归函数中,需要注意统计栈帧空间**。观察以下代码: - ```cpp title="" +=== "Python" + + ```python title="" + def function() -> int: + # 执行某些操作 + return 0 + + def loop(n: int): + """循环的空间复杂度为 O(1)""" + for _ in range(n): + function() + + def recur(n: int): + """递归的空间复杂度为 O(n)""" + if n == 1: + return + return recur(n - 1) + ``` + +=== "C++" + + ```cpp title="" int func() { - // do something + // 执行某些操作 return 0; } - /* 循环 O(1) */ + /* 循环的空间复杂度为 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } - /* 递归 O(n) */ + /* 递归的空间复杂度为 O(n) */ void recur(int n) { if (n == 1) return; return recur(n - 1); } ``` -=== "Python" +=== "Java" - ```python title="" - def function(): - # do something - return 0 + ```java title="" + int function() { + // 执行某些操作 + return 0; + } + /* 循环的空间复杂度为 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* 递归的空间复杂度为 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` - """ 循环 O(1) """ - def loop(n): - for _ in range(n): - function() +=== "C#" - """ 递归 O(n) """ - def recur(n): - if n == 1: return - return recur(n - 1) + ```csharp title="" + int Function() { + // 执行某些操作 + return 0; + } + /* 循环的空间复杂度为 O(1) */ + void Loop(int n) { + for (int i = 0; i < n; i++) { + Function(); + } + } + /* 递归的空间复杂度为 O(n) */ + int Recur(int n) { + if (n == 1) return 1; + return Recur(n - 1); + } ``` === "Go" ```go title="" func function() int { - // do something + // 执行某些操作 return 0 } - /* 循环 O(1) */ + /* 循环的空间复杂度为 O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } - /* 递归 O(n) */ + /* 递归的空间复杂度为 O(n) */ func recur(n int) { if n == 1 { return @@ -385,65 +640,23 @@ comments: true } ``` -=== "JavaScript" - - ```js title="" - - ``` - -=== "TypeScript" - - ```typescript title="" - - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - int function() - { - // do something - return 0; - } - /* 循环 O(1) */ - void loop(int n) - { - for (int i = 0; i < n; i++) - { - function(); - } - } - /* 递归 O(n) */ - int recur(int n) - { - if (n == 1) return 1; - return recur(n - 1); - } - ``` - === "Swift" ```swift title="" @discardableResult func function() -> Int { - // do something + // 执行某些操作 return 0 } - /* 循环 O(1) */ + /* 循环的空间复杂度为 O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } - /* 递归 O(n) */ + /* 递归的空间复杂度为 O(n) */ func recur(n: Int) { if n == 1 { return @@ -452,728 +665,234 @@ comments: true } ``` -## 常见类型 - -设输入数据大小为 $n$ ,常见的空间复杂度类型有(从低到高排列) - -$$ -\begin{aligned} -O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline -\text{常数阶} < \text{对数阶} < \text{线性阶} < \text{平方阶} < \text{指数阶} -\end{aligned} -$$ - -![space_complexity_common_types](space_complexity.assets/space_complexity_common_types.png) - -

Fig. 空间复杂度的常见类型

+=== "JS" -!!! tip - - 部分示例代码需要一些前置知识,包括数组、链表、二叉树、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解空间复杂度含义和推算方法上。 - -### 常数阶 $O(1)$ - -常数阶常见于数量与输入数据大小 $n$ 无关的常量、变量、对象。 - -需要注意的是,在循环中初始化变量或调用函数而占用的内存,在进入下一循环后就会被释放,即不会累积占用空间,空间复杂度仍为 $O(1)$ 。 - -=== "Java" - - ```java title="space_complexity.java" - /* 常数阶 */ - void constant(int n) { - // 常量、变量、对象占用 O(1) 空间 - final int a = 0; - int b = 0; - int[] nums = new int[10000]; - ListNode node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) { - function(); - } + ```javascript title="" + function constFunc() { + // 执行某些操作 + return 0; } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 常数阶 */ - void constant(int n) { - // 常量、变量、对象占用 O(1) 空间 - const int a = 0; - int b = 0; - vector nums(10000); - ListNode* node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) { - func(); + /* 循环的空间复杂度为 O(1) */ + function loop(n) { + for (let i = 0; i < n; i++) { + constFunc(); } } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 常数阶 """ - def constant(n): - # 常量、变量、对象占用 O(1) 空间 - a = 0 - nums = [0] * 10000 - node = ListNode(0) - # 循环中的变量占用 O(1) 空间 - for _ in range(n): - c = 0 - # 循环中的函数占用 O(1) 空间 - for _ in range(n): - function() - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 常数阶 */ - func spaceConstant(n int) { - // 常量、变量、对象占用 O(1) 空间 - const a = 0 - b := 0 - nums := make([]int, 10000) - ListNode := newNode(0) - // 循环中的变量占用 O(1) 空间 - var c int - for i := 0; i < n; i++ { - c = 0 - } - // 循环中的函数占用 O(1) 空间 - for i := 0; i < n; i++ { - function() - } - fmt.Println(a, b, nums, c, ListNode) - } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 常数阶 */ - void constant(int n) - { - // 常量、变量、对象占用 O(1) 空间 - int a = 0; - int b = 0; - int[] nums = new int[10000]; - ListNode node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) - { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) - { - function(); - } + /* 递归的空间复杂度为 O(n) */ + function recur(n) { + if (n === 1) return; + return recur(n - 1); } ``` -=== "Swift" +=== "TS" - ```swift title="space_complexity.swift" - /* 常数阶 */ - func constant(n: Int) { - // 常量、变量、对象占用 O(1) 空间 - let a = 0 - var b = 0 - let nums = Array(repeating: 0, count: 10000) - let node = ListNode(x: 0) - // 循环中的变量占用 O(1) 空间 - for _ in 0 ..< n { - let c = 0 - } - // 循环中的函数占用 O(1) 空间 - for _ in 0 ..< n { - function() - } + ```typescript title="" + function constFunc(): number { + // 执行某些操作 + return 0; } - ``` - -### 线性阶 $O(n)$ - -线性阶常见于元素数量与 $n$ 成正比的数组、链表、栈、队列等。 - -=== "Java" - - ```java title="space_complexity.java" - /* 线性阶 */ - void linear(int n) { - // 长度为 n 的数组占用 O(n) 空间 - int[] nums = new int[n]; - // 长度为 n 的列表占用 O(n) 空间 - List nodes = new ArrayList<>(); - for (int i = 0; i < n; i++) { - nodes.add(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - Map map = new HashMap<>(); - for (int i = 0; i < n; i++) { - map.put(i, String.valueOf(i)); + /* 循环的空间复杂度为 O(1) */ + function loop(n: number): void { + for (let i = 0; i < n; i++) { + constFunc(); } } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 线性阶 */ - void linear(int n) { - // 长度为 n 的数组占用 O(n) 空间 - vector nums(n); - // 长度为 n 的列表占用 O(n) 空间 - vector nodes; - for (int i = 0; i < n; i++) { - nodes.push_back(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - unordered_map map; - for (int i = 0; i < n; i++) { - map[i] = to_string(i); - } + /* 递归的空间复杂度为 O(n) */ + function recur(n: number): void { + if (n === 1) return; + return recur(n - 1); } ``` -=== "Python" - - ```python title="space_complexity.py" - """ 线性阶 """ - def linear(n): - # 长度为 n 的列表占用 O(n) 空间 - nums = [0] * n - # 长度为 n 的哈希表占用 O(n) 空间 - mapp = {} - for i in range(n): - mapp[i] = str(i) - ``` +=== "Dart" -=== "Go" - - ```go title="space_complexity.go" - /* 线性阶 */ - func spaceLinear(n int) { - // 长度为 n 的数组占用 O(n) 空间 - _ = make([]int, n) - // 长度为 n 的列表占用 O(n) 空间 - var nodes []*node - for i := 0; i < n; i++ { - nodes = append(nodes, newNode(i)) - } - // 长度为 n 的哈希表占用 O(n) 空间 - m := make(map[int]string, n) - for i := 0; i < n; i++ { - m[i] = strconv.Itoa(i) - } + ```dart title="" + int function() { + // 执行某些操作 + return 0; } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 线性阶 */ - void linear(int n) - { - // 长度为 n 的数组占用 O(n) 空间 - int[] nums = new int[n]; - // 长度为 n 的列表占用 O(n) 空间 - List nodes = new(); - for (int i = 0; i < n; i++) - { - nodes.Add(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - Dictionary map = new(); - for (int i = 0; i < n; i++) - { - map.Add(i, i.ToString()); - } + /* 循环的空间复杂度为 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 线性阶 */ - func linear(n: Int) { - // 长度为 n 的数组占用 O(n) 空间 - let nums = Array(repeating: 0, count: n) - // 长度为 n 的列表占用 O(n) 空间 - let nodes = (0 ..< n).map { ListNode(x: $0) } - // 长度为 n 的哈希表占用 O(n) 空间 - let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) + /* 递归的空间复杂度为 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); } ``` -以下递归函数会同时存在 $n$ 个未返回的 `algorithm()` 函数,使用 $O(n)$ 大小的栈帧空间。 +=== "Rust" -=== "Java" - - ```java title="space_complexity.java" - /* 线性阶(递归实现) */ - void linearRecur(int n) { - System.out.println("递归 n = " + n); - if (n == 1) return; - linearRecur(n - 1); + ```rust title="" + fn function() -> i32 { + // 执行某些操作 + return 0; } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 线性阶(递归实现) */ - void linearRecur(int n) { - cout << "递归 n = " << n << endl; - if (n == 1) return; - linearRecur(n - 1); + /* 循环的空间复杂度为 O(1) */ + fn loop(n: i32) { + for i in 0..n { + function(); + } } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 线性阶(递归实现) """ - def linearRecur(n): - print("递归 n =", n) - if n == 1: return - linearRecur(n - 1) - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 线性阶(递归实现) */ - func spaceLinearRecur(n int) { - fmt.Println("递归 n =", n) + /* 递归的空间复杂度为 O(n) */ + fn recur(n: i32) { if n == 1 { - return + return; } - spaceLinearRecur(n - 1) + recur(n - 1); } ``` -=== "JavaScript" - - ```js title="space_complexity.js" - - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - - ``` - === "C" - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 线性阶(递归实现) */ - void linearRecur(int n) - { - Console.WriteLine("递归 n = " + n); - if (n == 1) return; - linearRecur(n - 1); - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 线性阶(递归实现) */ - func linearRecur(n: Int) { - print("递归 n = \(n)") - if n == 1 { - return - } - linearRecur(n: n - 1) + ```c title="" + int func() { + // 执行某些操作 + return 0; } - ``` - -![space_complexity_recursive_linear](space_complexity.assets/space_complexity_recursive_linear.png) - -

Fig. 递归函数产生的线性阶空间复杂度

- -### 平方阶 $O(n^2)$ - -平方阶常见于元素数量与 $n$ 成平方关系的矩阵、图。 - -=== "Java" - - ```java title="space_complexity.java" - /* 平方阶 */ - void quadratic(int n) { - // 矩阵占用 O(n^2) 空间 - int [][]numMatrix = new int[n][n]; - // 二维列表占用 O(n^2) 空间 - List> numList = new ArrayList<>(); + /* 循环的空间复杂度为 O(1) */ + void loop(int n) { for (int i = 0; i < n; i++) { - List tmp = new ArrayList<>(); - for (int j = 0; j < n; j++) { - tmp.add(0); - } - numList.add(tmp); + func(); } } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 平方阶 */ - void quadratic(int n) { - // 二维列表占用 O(n^2) 空间 - vector> numMatrix; - for (int i = 0; i < n; i++) { - vector tmp; - for (int j = 0; j < n; j++) { - tmp.push_back(0); - } - numMatrix.push_back(tmp); - } + /* 递归的空间复杂度为 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); } ``` -=== "Python" +=== "Kotlin" - ```python title="space_complexity.py" - """ 平方阶 """ - def quadratic(n): - # 二维列表占用 O(n^2) 空间 - num_matrix = [[0] * n for _ in range(n)] - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 平方阶 */ - func spaceQuadratic(n int) { - // 矩阵占用 O(n^2) 空间 - numMatrix := make([][]int, n) - for i := 0; i < n; i++ { - numMatrix[i] = make([]int, n) - } + ```kotlin title="" + fun function(): Int { + // 执行某些操作 + return 0 } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 平方阶 */ - void quadratic(int n) - { - // 矩阵占用 O(n^2) 空间 - int[,] numMatrix = new int[n, n]; - // 二维列表占用 O(n^2) 空间 - List> numList = new(); - for (int i = 0; i < n; i++) - { - List tmp = new(); - for (int j = 0; j < n; j++) - { - tmp.Add(0); - } - numList.Add(tmp); + /* 循环的空间复杂度为 O(1) */ + fun loop(n: Int) { + for (i in 0.. nums(n); - cout << "递归 n = " << n << " 中的 nums 长度 = " << nums.size() << endl; - return quadraticRecur(n - 1); - } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 平方阶(递归实现) """ - def quadratic_recur(n): - if n <= 0: return 0 - # 数组 nums 长度为 n, n-1, ..., 2, 1 - nums = [0] * n - return quadratic_recur(n - 1) - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 平方阶(递归实现) */ - func spaceQuadraticRecur(n int) int { - if n <= 0 { - return 0 - } - // 数组 nums 长度为 n, n-1, ..., 2, 1 - nums := make([]int, n) - return spaceQuadraticRecur(n - 1) + /* 递归的空间复杂度为 O(n) */ + fun recur(n: Int) { + if (n == 1) return + return recur(n - 1) } ``` -=== "JavaScript" - - ```js title="space_complexity.js" - - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - - ``` +=== "Ruby" -=== "C" + ```ruby title="" + def function + # 执行某些操作 + 0 + end - ```c title="space_complexity.c" + ### 循环的空间复杂度为 O(1) ### + def loop(n) + (0...n).each { function } + end + ### 递归的空间复杂度为 O(n) ### + def recur(n) + return if n == 1 + recur(n - 1) + end ``` -=== "C#" +=== "Zig" - ```csharp title="space_complexity.cs" - /* 平方阶(递归实现) */ - int quadraticRecur(int n) - { - if (n <= 0) return 0; - // 数组 nums 长度为 n, n-1, ..., 2, 1 - int[] nums = new int[n]; - return quadraticRecur(n - 1); - } + ```zig title="" ``` -=== "Swift" +函数 `loop()` 和 `recur()` 的时间复杂度都为 $O(n)$ ,但空间复杂度不同。 - ```swift title="space_complexity.swift" - /* 平方阶(递归实现) */ - func quadraticRecur(n: Int) -> Int { - if n <= 0 { - return 0 - } - // 数组 nums 长度为 n, n-1, ..., 2, 1 - let nums = Array(repeating: 0, count: n) - return quadraticRecur(n: n - 1) - } - ``` +- 函数 `loop()` 在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。 +- 递归函数 `recur()` 在运行过程中会同时存在 $n$ 个未返回的 `recur()` ,从而占用 $O(n)$ 的栈帧空间。 -![space_complexity_recursive_quadratic](space_complexity.assets/space_complexity_recursive_quadratic.png) +## 常见类型 -

Fig. 递归函数产生的平方阶空间复杂度

+设输入数据大小为 $n$ ,下图展示了常见的空间复杂度类型(从低到高排列)。 -### 指数阶 $O(2^n)$ +$$ +\begin{aligned} +O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline +\text{常数阶} < \text{对数阶} < \text{线性阶} < \text{平方阶} < \text{指数阶} +\end{aligned} +$$ -指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的结点数量为 $2^n - 1$ ,使用 $O(2^n)$ 空间。 +![常见的空间复杂度类型](space_complexity.assets/space_complexity_common_types.png) -=== "Java" +### 常数阶 $O(1)$ - ```java title="space_complexity.java" - /* 指数阶(建立满二叉树) */ - TreeNode buildTree(int n) { - if (n == 0) return null; - TreeNode root = new TreeNode(0); - root.left = buildTree(n - 1); - root.right = buildTree(n - 1); - return root; - } - ``` +常数阶常见于数量与输入数据大小 $n$ 无关的常量、变量、对象。 -=== "C++" +需要注意的是,在循环中初始化变量或调用函数而占用的内存,在进入下一循环后就会被释放,因此不会累积占用空间,空间复杂度仍为 $O(1)$ : - ```cpp title="space_complexity.cpp" - /* 指数阶(建立满二叉树) */ - TreeNode* buildTree(int n) { - if (n == 0) return nullptr; - TreeNode* root = new TreeNode(0); - root->left = buildTree(n - 1); - root->right = buildTree(n - 1); - return root; - } - ``` +```src +[file]{space_complexity}-[class]{}-[func]{constant} +``` -=== "Python" +### 线性阶 $O(n)$ - ```python title="space_complexity.py" - """ 指数阶(建立满二叉树) """ - def build_tree(n): - if n == 0: return None - root = TreeNode(0) - root.left = build_tree(n - 1) - root.right = build_tree(n - 1) - return root - ``` +线性阶常见于元素数量与 $n$ 成正比的数组、链表、栈、队列等: -=== "Go" +```src +[file]{space_complexity}-[class]{}-[func]{linear} +``` - ```go title="space_complexity.go" - /* 指数阶(建立满二叉树) */ - func buildTree(n int) *treeNode { - if n == 0 { - return nil - } - root := newTreeNode(0) - root.left = buildTree(n - 1) - root.right = buildTree(n - 1) - return root - } - ``` +如下图所示,此函数的递归深度为 $n$ ,即同时存在 $n$ 个未返回的 `linear_recur()` 函数,使用 $O(n)$ 大小的栈帧空间: -=== "JavaScript" +```src +[file]{space_complexity}-[class]{}-[func]{linear_recur} +``` - ```js title="space_complexity.js" +![递归函数产生的线性阶空间复杂度](space_complexity.assets/space_complexity_recursive_linear.png) - ``` +### 平方阶 $O(n^2)$ -=== "TypeScript" +平方阶常见于矩阵和图,元素数量与 $n$ 成平方关系: - ```typescript title="space_complexity.ts" +```src +[file]{space_complexity}-[class]{}-[func]{quadratic} +``` - ``` +如下图所示,该函数的递归深度为 $n$ ,在每个递归函数中都初始化了一个数组,长度分别为 $n$、$n-1$、$\dots$、$2$、$1$ ,平均长度为 $n / 2$ ,因此总体占用 $O(n^2)$ 空间: -=== "C" +```src +[file]{space_complexity}-[class]{}-[func]{quadratic_recur} +``` - ```c title="space_complexity.c" +![递归函数产生的平方阶空间复杂度](space_complexity.assets/space_complexity_recursive_quadratic.png) - ``` +### 指数阶 $O(2^n)$ -=== "C#" +指数阶常见于二叉树。观察下图,层数为 $n$ 的“满二叉树”的节点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间: - ```csharp title="space_complexity.cs" - /* 指数阶(建立满二叉树) */ - TreeNode? buildTree(int n) - { - if (n == 0) return null; - TreeNode root = new TreeNode(0); - root.left = buildTree(n - 1); - root.right = buildTree(n - 1); - return root; - } - ``` +```src +[file]{space_complexity}-[class]{}-[func]{build_tree} +``` -=== "Swift" +![满二叉树产生的指数阶空间复杂度](space_complexity.assets/space_complexity_exponential.png) - ```swift title="space_complexity.swift" - /* 指数阶(建立满二叉树) */ - func buildTree(n: Int) -> TreeNode? { - if n == 0 { - return nil - } - let root = TreeNode(x: 0) - root.left = buildTree(n: n - 1) - root.right = buildTree(n: n - 1) - return root - } - ``` +### 对数阶 $O(\log n)$ -![space_complexity_exponential](space_complexity.assets/space_complexity_exponential.png) +对数阶常见于分治算法。例如归并排序,输入长度为 $n$ 的数组,每轮递归将数组从中点处划分为两半,形成高度为 $\log n$ 的递归树,使用 $O(\log n)$ 栈帧空间。 -

Fig. 满二叉树下的指数阶空间复杂度

+再例如将数字转化为字符串,输入一个正整数 $n$ ,它的位数为 $\lfloor \log_{10} n \rfloor + 1$ ,即对应字符串长度为 $\lfloor \log_{10} n \rfloor + 1$ ,因此空间复杂度为 $O(\log_{10} n + 1) = O(\log n)$ 。 -### 对数阶 $O(\log n)$ +## 权衡时间与空间 -对数阶常见于分治算法、数据类型转换等。 +理想情况下,我们希望算法的时间复杂度和空间复杂度都能达到最优。然而在实际情况中,同时优化时间复杂度和空间复杂度通常非常困难。 -例如「归并排序」,长度为 $n$ 的数组可以形成高度为 $\log n$ 的递归树,因此空间复杂度为 $O(\log n)$ 。 +**降低时间复杂度通常需要以提升空间复杂度为代价,反之亦然**。我们将牺牲内存空间来提升算法运行速度的思路称为“以空间换时间”;反之,则称为“以时间换空间”。 -再例如「数字转化为字符串」,输入任意正整数 $n$ ,它的位数为 $\log_{10} n$ ,即对应字符串长度为 $\log_{10} n$ ,因此空间复杂度为 $O(\log_{10} n) = O(\log n)$ 。 +选择哪种思路取决于我们更看重哪个方面。在大多数情况下,时间比空间更宝贵,因此“以空间换时间”通常是更常用的策略。当然,在数据量很大的情况下,控制空间复杂度也非常重要。 diff --git a/docs/chapter_computational_complexity/space_time_tradeoff.md b/docs/chapter_computational_complexity/space_time_tradeoff.md deleted file mode 100644 index 49cf33778b..0000000000 --- a/docs/chapter_computational_complexity/space_time_tradeoff.md +++ /dev/null @@ -1,329 +0,0 @@ ---- -comments: true ---- - -# 权衡时间与空间 - -理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。 - -**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然**。我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。 - -大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,**因此以空间换时间最为常用**。 - -## 示例题目 * - -以 LeetCode 全站第一题 [两数之和](https://leetcode.cn/problems/two-sum/) 为例,「暴力枚举」和「辅助哈希表」分别为 **空间最优** 和 **时间最优** 的两种解法。本着时间比空间更宝贵的原则,后者是本题的最佳解法。 - -### 方法一:暴力枚举 - -时间复杂度 $O(N^2)$ ,空间复杂度 $O(1)$ ,属于「时间换空间」。 - -虽然仅使用常数大小的额外空间,但运行速度过慢。 - -=== "Java" - - ```java title="leetcode_two_sum.java" - class SolutionBruteForce { - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return new int[] { i, j }; - } - } - return new int[0]; - } - } - ``` - -=== "C++" - - ```cpp title="leetcode_two_sum.cpp" - class SolutionBruteForce { - public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return { i, j }; - } - } - return {}; - } - }; - ``` - -=== "Python" - - ```python title="leetcode_two_sum.py" - class SolutionBruteForce: - def twoSum(self, nums: List[int], target: int) -> List[int]: - # 两层循环,时间复杂度 O(n^2) - for i in range(len(nums) - 1): - for j in range(i + 1, len(nums)): - if nums[i] + nums[j] == target: - return i, j - return [] - ``` - -=== "Go" - - ```go title="leetcode_two_sum.go" - func twoSumBruteForce(nums []int, target int) []int { - size := len(nums) - // 两层循环,时间复杂度 O(n^2) - for i := 0; i < size-1; i++ { - for j := i + 1; i < size; j++ { - if nums[i]+nums[j] == target { - return []int{i, j} - } - } - } - return nil - } - ``` - -=== "JavaScript" - - ```js title="leetcode_two_sum.js" - function twoSumBruteForce(nums, target) { - const n = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (let i = 0; i < n; i++) { - for (let j = i + 1; j < n; j++) { - if (nums[i] + nums[j] === target) { - return [i, j]; - } - } - } - return []; - } - ``` - -=== "TypeScript" - - ```typescript title="leetcode_two_sum.ts" - function twoSumBruteForce(nums: number[], target: number): number[] { - const n = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (let i = 0; i < n; i++) { - for (let j = i + 1; j < n; j++) { - if (nums[i] + nums[j] === target) { - return [i, j]; - } - } - } - return []; - }; - ``` - -=== "C" - - ```c title="leetcode_two_sum.c" - - ``` - -=== "C#" - - ```csharp title="leetcode_two_sum.cs" - class SolutionBruteForce - { - public int[] twoSum(int[] nums, int target) - { - int size = nums.Length; - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) - { - for (int j = i + 1; j < size; j++) - { - if (nums[i] + nums[j] == target) - return new int[] { i, j }; - } - } - return new int[0]; - } - } - ``` - -=== "Swift" - - ```swift title="leetcode_two_sum.swift" - func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { - // 两层循环,时间复杂度 O(n^2) - for i in nums.indices.dropLast() { - for j in nums.indices.dropFirst(i + 1) { - if nums[i] + nums[j] == target { - return [i, j] - } - } - } - return [0] - } - ``` - -### 方法二:辅助哈希表 - -时间复杂度 $O(N)$ ,空间复杂度 $O(N)$ ,属于「空间换时间」。 - -借助辅助哈希表 dic ,通过保存数组元素与索引的映射来提升算法运行速度。 - -=== "Java" - - ```java title="leetcode_two_sum.java" - class SolutionHashMap { - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 辅助哈希表,空间复杂度 O(n) - Map dic = new HashMap<>(); - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.containsKey(target - nums[i])) { - return new int[] { dic.get(target - nums[i]), i }; - } - dic.put(nums[i], i); - } - return new int[0]; - } - } - ``` - -=== "C++" - - ```cpp title="leetcode_two_sum.cpp" - class SolutionHashMap { - public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 辅助哈希表,空间复杂度 O(n) - unordered_map dic; - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.find(target - nums[i]) != dic.end()) { - return { dic[target - nums[i]], i }; - } - dic.emplace(nums[i], i); - } - return {}; - } - }; - ``` - -=== "Python" - - ```python title="leetcode_two_sum.py" - class SolutionHashMap: - def twoSum(self, nums: List[int], target: int) -> List[int]: - # 辅助哈希表,空间复杂度 O(n) - dic = {} - # 单层循环,时间复杂度 O(n) - for i in range(len(nums)): - if target - nums[i] in dic: - return dic[target - nums[i]], i - dic[nums[i]] = i - return [] - ``` - -=== "Go" - - ```go title="leetcode_two_sum.go" - func twoSumHashTable(nums []int, target int) []int { - // 辅助哈希表,空间复杂度 O(n) - hashTable := map[int]int{} - // 单层循环,时间复杂度 O(n) - for idx, val := range nums { - if preIdx, ok := hashTable[target-val]; ok { - return []int{preIdx, idx} - } - hashTable[val] = idx - } - return nil - } - ``` - -=== "JavaScript" - - ```js title="leetcode_two_sum.js" - function twoSumHashTable(nums, target) { - // 辅助哈希表,空间复杂度 O(n) - let m = {}; - // 单层循环,时间复杂度 O(n) - for (let i = 0; i < nums.length; i++) { - if (m[nums[i]] !== undefined) { - return [m[nums[i]], i]; - } else { - m[target - nums[i]] = i; - } - } - return []; - } - ``` - -=== "TypeScript" - - ```typescript title="leetcode_two_sum.ts" - function twoSumHashTable(nums: number[], target: number): number[] { - // 辅助哈希表,空间复杂度 O(n) - let m: Map = new Map(); - // 单层循环,时间复杂度 O(n) - for (let i = 0; i < nums.length; i++) { - let index = m.get(nums[i]); - if (index !== undefined) { - return [index, i]; - } else { - m.set(target - nums[i], i); - } - } - return []; - }; - ``` - -=== "C" - - ```c title="leetcode_two_sum.c" - - ``` - -=== "C#" - - ```csharp title="leetcode_two_sum.cs" - class SolutionHashMap - { - public int[] twoSum(int[] nums, int target) - { - int size = nums.Length; - // 辅助哈希表,空间复杂度 O(n) - Dictionary dic = new(); - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) - { - if (dic.ContainsKey(target - nums[i])) - { - return new int[] { dic[target - nums[i]], i }; - } - dic.Add(nums[i], i); - } - return new int[0]; - } - } - ``` - -=== "Swift" - - ```swift title="leetcode_two_sum.swift" - func twoSumHashTable(nums: [Int], target: Int) -> [Int] { - // 辅助哈希表,空间复杂度 O(n) - var dic: [Int: Int] = [:] - // 单层循环,时间复杂度 O(n) - for i in nums.indices { - if let j = dic[target - nums[i]] { - return [j, i] - } - dic[nums[i]] = i - } - return [0] - } - ``` diff --git a/docs/chapter_computational_complexity/summary.md b/docs/chapter_computational_complexity/summary.md index 0a1ee80beb..4beb14adf6 100644 --- a/docs/chapter_computational_complexity/summary.md +++ b/docs/chapter_computational_complexity/summary.md @@ -1,28 +1,49 @@ ---- -comments: true ---- - # 小结 -### 算法效率评估 +### 重点回顾 + +**算法效率评估** + +- 时间效率和空间效率是衡量算法优劣的两个主要评价指标。 +- 我们可以通过实际测试来评估算法效率,但难以消除测试环境的影响,且会耗费大量计算资源。 +- 复杂度分析可以消除实际测试的弊端,分析结果适用于所有运行平台,并且能够揭示算法在不同数据规模下的效率。 + +**时间复杂度** + +- 时间复杂度用于衡量算法运行时间随数据量增长的趋势,可以有效评估算法效率,但在某些情况下可能失效,如在输入的数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣。 +- 最差时间复杂度使用大 $O$ 符号表示,对应函数渐近上界,反映当 $n$ 趋向正无穷时,操作数量 $T(n)$ 的增长级别。 +- 推算时间复杂度分为两步,首先统计操作数量,然后判断渐近上界。 +- 常见时间复杂度从低到高排列有 $O(1)$、$O(\log n)$、$O(n)$、$O(n \log n)$、$O(n^2)$、$O(2^n)$ 和 $O(n!)$ 等。 +- 某些算法的时间复杂度非固定,而是与输入数据的分布有关。时间复杂度分为最差、最佳、平均时间复杂度,最佳时间复杂度几乎不用,因为输入数据一般需要满足严格条件才能达到最佳情况。 +- 平均时间复杂度反映算法在随机数据输入下的运行效率,最接近实际应用中的算法性能。计算平均时间复杂度需要统计输入数据分布以及综合后的数学期望。 + +**空间复杂度** + +- 空间复杂度的作用类似于时间复杂度,用于衡量算法占用内存空间随数据量增长的趋势。 +- 算法运行过程中的相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不纳入空间复杂度计算。暂存空间可分为暂存数据、栈帧空间和指令空间,其中栈帧空间通常仅在递归函数中影响空间复杂度。 +- 我们通常只关注最差空间复杂度,即统计算法在最差输入数据和最差运行时刻下的空间复杂度。 +- 常见空间复杂度从低到高排列有 $O(1)$、$O(\log n)$、$O(n)$、$O(n^2)$ 和 $O(2^n)$ 等。 + +### Q & A + +**Q**:尾递归的空间复杂度是 $O(1)$ 吗? + +理论上,尾递归函数的空间复杂度可以优化至 $O(1)$ 。不过绝大多数编程语言(例如 Java、Python、C++、Go、C# 等)不支持自动优化尾递归,因此通常认为空间复杂度是 $O(n)$ 。 + +**Q**:函数和方法这两个术语的区别是什么? + +函数(function)可以被独立执行,所有参数都以显式传递。方法(method)与一个对象关联,被隐式传递给调用它的对象,能够对类的实例中包含的数据进行操作。 -- 「时间效率」和「空间效率」是算法性能的两个重要的评价维度。 -- 我们可以通过「实际测试」来评估算法效率,但难以排除测试环境的干扰,并且非常耗费计算资源。 -- 「复杂度分析」克服了实际测试的弊端,分析结果适用于所有运行平台,并且可以体现不同数据大小下的算法效率。 +下面以几种常见的编程语言为例来说明。 -### 时间复杂度 +- C 语言是过程式编程语言,没有面向对象的概念,所以只有函数。但我们可以通过创建结构体(struct)来模拟面向对象编程,与结构体相关联的函数就相当于其他编程语言中的方法。 +- Java 和 C# 是面向对象的编程语言,代码块(方法)通常作为某个类的一部分。静态方法的行为类似于函数,因为它被绑定在类上,不能访问特定的实例变量。 +- C++ 和 Python 既支持过程式编程(函数),也支持面向对象编程(方法)。 -- 「时间复杂度」统计算法运行时间随着数据量变大时的增长趋势,可以有效评估算法效率,但在某些情况下可能失效,比如在输入数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣性。 -- 「最差时间复杂度」使用大 $O$ 符号表示,即函数渐近上界,其反映当 $n$ 趋于正无穷时,$T(n)$ 处于何种增长级别。 -- 推算时间复杂度分为两步,首先统计计算操作数量,再判断渐近上界。 -- 常见时间复杂度从小到大排列有 $O(1)$ , $O(\log n)$ , $O(n)$ , $O(n \log n)$ , $O(n^2)$ , $O(2^n)$ , $O(n!)$ 。 -- 某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。时间复杂度分为「最差时间复杂度」和「最佳时间复杂度」,后者几乎不用,因为输入数据需要满足苛刻的条件才能达到最佳情况。 -- 「平均时间复杂度」可以反映在随机数据输入下的算法效率,最贴合实际使用情况下的算法性能。计算平均时间复杂度需要统计输入数据的分布,以及综合后的数学期望。 +**Q**:图解“常见的空间复杂度类型”反映的是否是占用空间的绝对大小? -### 空间复杂度 +不是,该图展示的是空间复杂度,其反映的是增长趋势,而不是占用空间的绝对大小。 -- 与时间复杂度的定义类似,「空间复杂度」统计算法占用空间随着数据量变大时的增长趋势。 +假设取 $n = 8$ ,你可能会发现每条曲线的值与函数对应不上。这是因为每条曲线都包含一个常数项,用于将取值范围压缩到一个视觉舒适的范围内。 -- 算法运行中相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不计入空间复杂度计算。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间一般在递归函数中才会影响到空间复杂度。 -- 我们一般只关心「最差空间复杂度」,即统计算法在「最差输入数据」和「最差运行时间点」下的空间复杂度。 -- 常见空间复杂度从小到大排列有 $O(1)$ , $O(\log n)$ , $O(n)$ , $O(n^2)$ , $O(2^n)$ 。 +在实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法。但对于 $n = 8^5$ 就很好选了,这时增长趋势已经占主导了。 diff --git a/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png b/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png index 59399bfcc3..d0dddbb05d 100644 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png and b/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png index f0108530f1..c1ab027e0e 100644 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png and b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png index e4e839c901..99758187ff 100644 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png and b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png index 3c9466574c..bf9ef1b372 100644 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png and b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png index 7c86dfd3f4..52bf0c1c2e 100644 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png and b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_first_example.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_first_example.png deleted file mode 100644 index f2358b7ccb..0000000000 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_first_example.png and /dev/null differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png index a4df4527fb..739c386f4d 100644 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png and b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png index 6f336f4b5a..3ae59496cc 100644 Binary files a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png and b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png new file mode 100644 index 0000000000..dd1c525288 Binary files /dev/null and b/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png differ diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md old mode 100644 new mode 100755 index def052c927..4dae586aea --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -1,64 +1,69 @@ ---- -comments: true ---- - # 时间复杂度 -## 统计算法运行时间 +运行时间可以直观且准确地反映算法的效率。如果我们想准确预估一段代码的运行时间,应该如何操作呢? -运行时间能够直观且准确地体现出算法的效率水平。如果我们想要 **准确预估一段代码的运行时间** ,该如何做呢? +1. **确定运行平台**,包括硬件配置、编程语言、系统环境等,这些因素都会影响代码的运行效率。 +2. **评估各种计算操作所需的运行时间**,例如加法操作 `+` 需要 1 ns ,乘法操作 `*` 需要 10 ns ,打印操作 `print()` 需要 5 ns 等。 +3. **统计代码中所有的计算操作**,并将所有操作的执行时间求和,从而得到运行时间。 -1. 首先需要 **确定运行平台** ,包括硬件配置、编程语言、系统环境等,这些都会影响到代码的运行效率。 -2. 评估 **各种计算操作的所需运行时间** ,例如加法操作 `+` 需要 1 ns ,乘法操作 `*` 需要 10 ns ,打印操作需要 5 ns 等。 -3. 根据代码 **统计所有计算操作的数量** ,并将所有操作的执行时间求和,即可得到运行时间。 +例如在以下代码中,输入数据大小为 $n$ : -例如以下代码,输入数据大小为 $n$ ,根据以上方法,可以得到算法运行时间为 $6n + 12$ ns 。 +=== "Python" -$$ -1 + 1 + 10 + (1 + 5) \times n = 6n + 12 -$$ + ```python title="" + # 在某运行平台下 + def algorithm(n: int): + a = 2 # 1 ns + a = a + 1 # 1 ns + a = a * 2 # 10 ns + # 循环 n 次 + for _ in range(n): # 1 ns + print(0) # 5 ns + ``` -=== "Java" +=== "C++" - ```java title="" + ```cpp title="" // 在某运行平台下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 - for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - System.out.println(0); // 5 ns + for (int i = 0; i < n; i++) { // 1 ns + cout << 0 << endl; // 5 ns } } ``` -=== "C++" +=== "Java" - ```cpp title="" + ```java title="" // 在某运行平台下 void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 - for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - cout << 0 << endl; // 5 ns + for (int i = 0; i < n; i++) { // 1 ns + System.out.println(0); // 5 ns } } ``` -=== "Python" +=== "C#" - ```python title="" - # 在某运行平台下 - def algorithm(n): - a = 2 # 1 ns - a = a + 1 # 1 ns - a = a * 2 # 10 ns - # 循环 n 次 - for _ in range(n): # 1 ns - print(0) # 5 ns + ```csharp title="" + // 在某运行平台下 + void Algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 循环 n 次 + for (int i = 0; i < n; i++) { // 1 ns + Console.WriteLine(0); // 5 ns + } + } ``` === "Go" @@ -66,32 +71,47 @@ $$ ```go title="" // 在某运行平台下 func algorithm(n int) { - a := 2 // 1 ns - a = a + 1 // 1 ns - a = a * 2 // 10 ns + a := 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // 循环 n 次 + for i := 0; i < n; i++ { // 1 ns + fmt.Println(a) // 5 ns + } + } + ``` + +=== "Swift" + + ```swift title="" + // 在某运行平台下 + func algorithm(n: Int) { + var a = 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns // 循环 n 次 - for i := 0; i < n; i++ { // 1 ns - fmt.Println(a) // 5 ns + for _ in 0 ..< n { // 1 ns + print(0) // 5 ns } } ``` -=== "JavaScript" +=== "JS" - ```js title="" + ```javascript title="" // 在某运行平台下 function algorithm(n) { var a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 - for(let i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ + for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` -=== "TypeScript" +=== "TS" ```typescript title="" // 在某运行平台下 @@ -100,143 +120,209 @@ $$ a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 - for(let i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ + for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } ``` -=== "C" +=== "Dart" - ```c title="" + ```dart title="" // 在某运行平台下 void algorithm(int n) { - int a = 2; // 1 ns - a = a + 1; // 1 ns - a = a * 2; // 10 ns + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 循环 n 次 + for (int i = 0; i < n; i++) { // 1 ns + print(0); // 5 ns + } + } + ``` + +=== "Rust" + + ```rust title="" + // 在某运行平台下 + fn algorithm(n: i32) { + let mut a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns // 循环 n 次 - for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - printf("%d", 0); // 5 ns + for _ in 0..n { // 1 ns + println!("{}", 0); // 5 ns } } ``` -=== "C#" +=== "C" - ```csharp title="" + ```c title="" // 在某运行平台下 - void algorithm(int n) - { + void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns // 循环 n 次 - for (int i = 0; i < n; i++) - { // 1 ns ,每轮都要执行 i++ - Console.WriteLine(0); // 5 ns + for (int i = 0; i < n; i++) { // 1 ns + printf("%d", 0); // 5 ns } } ``` -=== "Swift" +=== "Kotlin" - ```swift title="" + ```kotlin title="" // 在某运行平台下 - func algorithm(_ n: Int) { + fun algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns // 循环 n 次 - for _ in 0 ..< n { // 1 ns - print(0) // 5 ns + for (i in 0.. Fig. 算法 A, B, C 的时间增长趋势

+下图展示了以上三个算法函数的时间复杂度。 -相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足? +- 算法 `A` 只有 $1$ 个打印操作,算法运行时间不随着 $n$ 增大而增长。我们称此算法的时间复杂度为“常数阶”。 +- 算法 `B` 中的打印操作需要循环 $n$ 次,算法运行时间随着 $n$ 增大呈线性增长。此算法的时间复杂度被称为“线性阶”。 +- 算法 `C` 中的打印操作需要循环 $1000000$ 次,虽然运行时间很长,但它与输入数据大小 $n$ 无关。因此 `C` 的时间复杂度和 `A` 相同,仍为“常数阶”。 -**时间复杂度可以有效评估算法效率**。算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。 +![算法 A、B 和 C 的时间增长趋势](time_complexity.assets/time_complexity_simple_example.png) -**时间复杂度的推算方法更加简便**。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。 +相较于直接统计算法的运行时间,时间复杂度分析有哪些特点呢? -**时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 +- **时间复杂度能够有效评估算法效率**。例如,算法 `B` 的运行时间呈线性增长,在 $n > 1$ 时比算法 `A` 更慢,在 $n > 1000000$ 时比算法 `C` 更慢。事实上,只要输入数据大小 $n$ 足够大,复杂度为“常数阶”的算法一定优于“线性阶”的算法,这正是时间增长趋势的含义。 +- **时间复杂度的推算方法更简便**。显然,运行平台和计算操作类型都与算法运行时间的增长趋势无关。因此在时间复杂度分析中,我们可以简单地将所有计算操作的执行时间视为相同的“单位时间”,从而将“计算操作运行时间统计”简化为“计算操作数量统计”,这样一来估算难度就大大降低了。 +- **时间复杂度也存在一定的局限性**。例如,尽管算法 `A` 和 `C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C` 。对于此类情况,我们时常难以仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。 ## 函数渐近上界 -设算法「计算操作数量」为 $T(n)$ ,其是一个关于输入数据大小 $n$ 的函数。例如,以下算法的操作数量为 +给定一个输入大小为 $n$ 的函数: -$$ -T(n) = 3 + 2n -$$ +=== "Python" -=== "Java" + ```python title="" + def algorithm(n: int): + a = 1 # +1 + a = a + 1 # +1 + a = a * 2 # +1 + # 循环 n 次 + for i in range(n): # +1 + print(0) # +1 + ``` - ```java title="" +=== "C++" + + ```cpp title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - System.out.println(0); // +1 + cout << 0 << endl; // +1 } } ``` -=== "C++" +=== "Java" - ```cpp title="" + ```java title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - cout << 0 << endl; // +1 + System.out.println(0); // +1 } } ``` -=== "Python" +=== "C#" - ```python title="" - def algorithm(n): - a = 1 # +1 - a = a + 1 # +1 - a = a * 2 # +1 - # 循环 n 次 - for i in range(n): # +1 - print(0) # +1 + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 循环 n 次 + for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) + Console.WriteLine(0); // +1 + } + } ``` === "Go" @@ -431,10 +608,24 @@ $$ } ``` -=== "JavaScript" +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // 循环 n 次 + for _ in 0 ..< n { // +1 + print(0) // +1 + } + } + ``` + +=== "JS" - ```js title="" - function algorithm(n){ + ```javascript title="" + function algorithm(n) { var a = 1; // +1 a += 1; // +1 a *= 2; // +1 @@ -442,11 +633,10 @@ $$ for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++) console.log(0); // +1 } - } ``` -=== "TypeScript" +=== "TS" ```typescript title="" function algorithm(n: number): void{ @@ -457,161 +647,207 @@ $$ for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++) console.log(0); // +1 } - } ``` -=== "C" +=== "Dart" - ```c title="" + ```dart title="" void algorithm(int n) { - int a = 1; // +1 - a = a + 1; // +1 - a = a * 2; // +1 + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 循环 n 次 + for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) + print(0); // +1 + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 循环 n 次 - for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - printf("%d", 0); // +1 + for _ in 0..n { // +1(每轮都执行 i ++) + println!("{}", 0); // +1 } - } + } ``` -=== "C#" +=== "C" - ```csharp title="" + ```c title="" void algorithm(int n) { int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 // 循环 n 次 - for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - Console.WriteLine(0); // +1 + for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) + printf("%d", 0); // +1 } } ``` -=== "Swift" +=== "Kotlin" - ```swift title="" - func algorithm(n: Int) { + ```kotlin title="" + fun algorithm(n: Int) { var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 // 循环 n 次 - for _ in 0 ..< n { // +1 - print(0) // +1 + for (i in 0.. n_0$ ,均有 - $$ - T(n) \leq c \cdot f(n) - $$ - 则可认为 $f(n)$ 给出了 $T(n)$ 的一个渐近上界,记为 - $$ - T(n) = O(f(n)) - $$ +设算法的操作数量是一个关于输入数据大小 $n$ 的函数,记为 $T(n)$ ,则以上函数的操作数量为: -![asymptotic_upper_bound](time_complexity.assets/asymptotic_upper_bound.png) +$$ +T(n) = 3 + 2n +$$ + +$T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因此它的时间复杂度是线性阶。 -

Fig. 函数的渐近上界

+我们将线性阶的时间复杂度记为 $O(n)$ ,这个数学符号称为大 $O$ 记号(big-$O$ notation),表示函数 $T(n)$ 的渐近上界(asymptotic upper bound)。 -本质上看,计算渐近上界就是在找一个函数 $f(n)$ ,**使得在 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别(仅相差一个常数项 $c$ 的倍数)**。 +时间复杂度分析本质上是计算“操作数量 $T(n)$”的渐近上界,它具有明确的数学定义。 -!!! tip +!!! note "函数渐近上界" - 渐近上界的数学味儿有点重,如果你感觉没有完全理解,无需担心,因为在实际使用中我们只需要会推算即可,数学意义可以慢慢领悟。 + 若存在正实数 $c$ 和实数 $n_0$ ,使得对于所有的 $n > n_0$ ,均有 $T(n) \leq c \cdot f(n)$ ,则可认为 $f(n)$ 给出了 $T(n)$ 的一个渐近上界,记为 $T(n) = O(f(n))$ 。 + +如下图所示,计算渐近上界就是寻找一个函数 $f(n)$ ,使得当 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别,仅相差一个常数项 $c$ 的倍数。 + +![函数的渐近上界](time_complexity.assets/asymptotic_upper_bound.png) ## 推算方法 -推算出 $f(n)$ 后,我们就得到时间复杂度 $O(f(n))$ 。那么,如何来确定渐近上界 $f(n)$ 呢?总体分为两步,首先「统计操作数量」,然后「判断渐近上界」。 +渐近上界的数学味儿有点重,如果你感觉没有完全理解,也无须担心。我们可以先掌握推算方法,在不断的实践中,就可以逐渐领悟其数学意义。 -### 1. 统计操作数量 +根据定义,确定 $f(n)$ 之后,我们便可得到时间复杂度 $O(f(n))$ 。那么如何确定渐近上界 $f(n)$ 呢?总体分为两步:首先统计操作数量,然后判断渐近上界。 -对着代码,从上到下一行一行地计数即可。然而,**由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,因此操作数量 $T(n)$ 中的各种系数、常数项都可以被忽略**。根据此原则,可以总结出以下计数偷懒技巧: +### 第一步:统计操作数量 -1. **跳过数量与 $n$ 无关的操作**。因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。 -2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。 -3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 +针对代码,逐行从上到下计算即可。然而,由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,**因此操作数量 $T(n)$ 中的各种系数、常数项都可以忽略**。根据此原则,可以总结出以下计数简化技巧。 -根据以下示例,使用上述技巧前、后的统计结果分别为 +1. **忽略 $T(n)$ 中的常数项**。因为它们都与 $n$ 无关,所以对时间复杂度不产生影响。 +2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次等,都可以简化记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度没有影响。 +3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用第 `1.` 点和第 `2.` 点的技巧。 -$$ -\begin{aligned} -T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{完整统计 (-.-|||)} \newline -& = 2n^2 + 7n + 3 \newline -T(n) & = n^2 + n & \text{偷懒统计 (o.O)} -\end{aligned} -$$ +给定一个函数,我们可以用上述技巧来统计操作数量: -最终,两者都能推出相同的时间复杂度结果,即 $O(n^2)$ 。 +=== "Python" -=== "Java" + ```python title="" + def algorithm(n: int): + a = 1 # +0(技巧 1) + a = a + n # +0(技巧 1) + # +n(技巧 2) + for i in range(5 * n + 1): + print(0) + # +n*n(技巧 3) + for i in range(2 * n): + for j in range(n + 1): + print(0) + ``` - ```java title="" +=== "C++" + + ```cpp title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { - System.out.println(0); + cout << 0 << endl; } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { - System.out.println(0); + cout << 0 << endl; } } } ``` -=== "C++" +=== "Java" - ```cpp title="" + ```java title="" void algorithm(int n) { int a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) // +n(技巧 2) for (int i = 0; i < 5 * n + 1; i++) { - cout << 0 << endl; + System.out.println(0); } // +n*n(技巧 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { - cout << 0 << endl; + System.out.println(0); } } } ``` -=== "Python" +=== "C#" - ```python title="" - def algorithm(n): - a = 1 # +0(技巧 1) - a = a + n # +0(技巧 1) - # +n(技巧 2) - for i in range(5 * n + 1): - print(0) - # +n*n(技巧 3) - for i in range(2 * n): - for j in range(n + 1): - print(0) + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } ``` === "Go" ```go title="" func algorithm(n int) { - a := 1 // +0(技巧 1) + a := 1 // +0(技巧 1) a = a + n // +0(技巧 1) // +n(技巧 2) for i := 0; i < 5 * n + 1; i++ { @@ -626,9 +862,28 @@ $$ } ``` -=== "JavaScript" +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +0(技巧 1) + a = a + n // +0(技巧 1) + // +n(技巧 2) + for _ in 0 ..< (5 * n + 1) { + print(0) + } + // +n*n(技巧 3) + for _ in 0 ..< (2 * n) { + for _ in 0 ..< (n + 1) { + print(0) + } + } + } + ``` + +=== "JS" - ```js title="" + ```javascript title="" function algorithm(n) { let a = 1; // +0(技巧 1) a = a + n; // +0(技巧 1) @@ -645,7 +900,7 @@ $$ } ``` -=== "TypeScript" +=== "TS" ```typescript title="" function algorithm(n: number): void { @@ -664,6 +919,46 @@ $$ } ``` +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + print(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + print(0); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + + // +n(技巧 2) + for i in 0..(5 * n + 1) { + println!("{}", 0); + } + + // +n*n(技巧 3) + for i in 0..(2 * n) { + for j in 0..(n + 1) { + println!("{}", 0); + } + } + } + ``` + === "C" ```c title="" @@ -683,57 +978,80 @@ $$ } ``` -=== "C#" +=== "Kotlin" - ```csharp title="" - void algorithm(int n) - { - int a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) + ```kotlin title="" + fun algorithm(n: Int) { + var a = 1 // +0(技巧 1) + a = a + n // +0(技巧 1) // +n(技巧 2) - for (int i = 0; i < 5 * n + 1; i++) - { - Console.WriteLine(0); + for (i in 0..<5 * n + 1) { + println(0) } // +n*n(技巧 3) - for (int i = 0; i < 2 * n; i++) - { - for (int j = 0; j < n + 1; j++) - { - Console.WriteLine(0); + for (i in 0..<2 * n) { + for (j in 0.. +

  不同操作数量对应的时间复杂度

-| 操作数量 $T(n)$ | 时间复杂度 $O(f(n))$ | +| 操作数量 $T(n)$ | 时间复杂度 $O(f(n))$ | | ---------------------- | -------------------- | | $100000$ | $O(1)$ | | $3n + 2$ | $O(n)$ | @@ -741,11 +1059,9 @@ $$ | $n^3 + 10000n^2$ | $O(n^3)$ | | $2^n + 10000n^{10000}$ | $O(2^n)$ | - - ## 常见类型 -设输入数据大小为 $n$ ,常见的时间复杂度类型有(从低到高排列) +设输入数据大小为 $n$ ,常见的时间复杂度类型如下图所示(按照从低到高的顺序排列)。 $$ \begin{aligned} @@ -754,1954 +1070,155 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline \end{aligned} $$ -![time_complexity_common_types](time_complexity.assets/time_complexity_common_types.png) - -

Fig. 时间复杂度的常见类型

- -!!! tip - - 部分示例代码需要一些前置知识,包括数组、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解时间复杂度含义和推算方法上。 +![常见的时间复杂度类型](time_complexity.assets/time_complexity_common_types.png) ### 常数阶 $O(1)$ 常数阶的操作数量与输入数据大小 $n$ 无关,即不随着 $n$ 的变化而变化。 -对于以下算法,无论操作数量 `size` 有多大,只要与数据大小 $n$ 无关,时间复杂度就仍为 $O(1)$ 。 +在以下函数中,尽管操作数量 `size` 可能很大,但由于其与输入数据大小 $n$ 无关,因此时间复杂度仍为 $O(1)$ : -=== "Java" +```src +[file]{time_complexity}-[class]{}-[func]{constant} +``` - ```java title="time_complexity.java" - /* 常数阶 */ - int constant(int n) { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } - ``` +### 线性阶 $O(n)$ -=== "C++" +线性阶的操作数量相对于输入数据大小 $n$ 以线性级别增长。线性阶通常出现在单层循环中: - ```cpp title="time_complexity.cpp" - /* 常数阶 */ - int constant(int n) { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } - ``` +```src +[file]{time_complexity}-[class]{}-[func]{linear} +``` -=== "Python" +遍历数组和遍历链表等操作的时间复杂度均为 $O(n)$ ,其中 $n$ 为数组或链表的长度: - ```python title="time_complexity.py" - """ 常数阶 """ - def constant(n): - count = 0 - size = 100000 - for _ in range(size): - count += 1 - return count - ``` +```src +[file]{time_complexity}-[class]{}-[func]{array_traversal} +``` -=== "Go" +值得注意的是,**输入数据大小 $n$ 需根据输入数据的类型来具体确定**。比如在第一个示例中,变量 $n$ 为输入数据大小;在第二个示例中,数组长度 $n$ 为数据大小。 - ```go title="time_complexity.go" - /* 常数阶 */ - func constant(n int) int { - count := 0 - size := 100000 - for i := 0; i < size; i++ { - count ++ - } - return count - } - ``` +### 平方阶 $O(n^2)$ -=== "JavaScript" +平方阶的操作数量相对于输入数据大小 $n$ 以平方级别增长。平方阶通常出现在嵌套循环中,外层循环和内层循环的时间复杂度都为 $O(n)$ ,因此总体的时间复杂度为 $O(n^2)$ : - ```js title="time_complexity.js" - /* 常数阶 */ - function constant(n) { - let count = 0; - const size = 100000; - for (let i = 0; i < size; i++) count++; - return count; - } - ``` +```src +[file]{time_complexity}-[class]{}-[func]{quadratic} +``` -=== "TypeScript" +下图对比了常数阶、线性阶和平方阶三种时间复杂度。 - ```typescript title="time_complexity.ts" - /* 常数阶 */ - function constant(n: number): number { - let count = 0; - const size = 100000; - for (let i = 0; i < size; i++) count++; - return count; - } - ``` +![常数阶、线性阶和平方阶的时间复杂度](time_complexity.assets/time_complexity_constant_linear_quadratic.png) -=== "C" +以冒泡排序为例,外层循环执行 $n - 1$ 次,内层循环执行 $n-1$、$n-2$、$\dots$、$2$、$1$ 次,平均为 $n / 2$ 次,因此时间复杂度为 $O((n - 1) n / 2) = O(n^2)$ : - ```c title="time_complexity.c" - /* 常数阶 */ - int constant(int n) { - int count = 0; - int size = 100000; - int i = 0; - for (int i = 0; i < size; i++) { - count ++; - } - return count; - } - ``` +```src +[file]{time_complexity}-[class]{}-[func]{bubble_sort} +``` -=== "C#" +### 指数阶 $O(2^n)$ - ```csharp title="time_complexity.cs" - /* 常数阶 */ - int constant(int n) - { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } - ``` +生物学的“细胞分裂”是指数阶增长的典型例子:初始状态为 $1$ 个细胞,分裂一轮后变为 $2$ 个,分裂两轮后变为 $4$ 个,以此类推,分裂 $n$ 轮后有 $2^n$ 个细胞。 -=== "Swift" +下图和以下代码模拟了细胞分裂的过程,时间复杂度为 $O(2^n)$ 。请注意,输入 $n$ 表示分裂轮数,返回值 `count` 表示总分裂次数。 - ```swift title="time_complexity.swift" - /* 常数阶 */ - func constant(n: Int) -> Int { - var count = 0 - let size = 100000 - for _ in 0 ..< size { - count += 1 - } - return count - } - ``` +```src +[file]{time_complexity}-[class]{}-[func]{exponential} +``` -### 线性阶 $O(n)$ +![指数阶的时间复杂度](time_complexity.assets/time_complexity_exponential.png) -线性阶的操作数量相对输入数据大小成线性级别增长。线性阶常出现于单层循环。 +在实际算法中,指数阶常出现于递归函数中。例如在以下代码中,其递归地一分为二,经过 $n$ 次分裂后停止: -=== "Java" +```src +[file]{time_complexity}-[class]{}-[func]{exp_recur} +``` - ```java title="time_complexity.java" - /* 线性阶 */ - int linear(int n) { - int count = 0; - for (int i = 0; i < n; i++) - count++; - return count; - } - ``` +指数阶增长非常迅速,在穷举法(暴力搜索、回溯等)中比较常见。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心算法等来解决。 -=== "C++" +### 对数阶 $O(\log n)$ - ```cpp title="time_complexity.cpp" - /* 线性阶 */ - int linear(int n) { - int count = 0; - for (int i = 0; i < n; i++) - count++; - return count; - } - ``` +与指数阶相反,对数阶反映了“每轮缩减到一半”的情况。设输入数据大小为 $n$ ,由于每轮缩减到一半,因此循环次数是 $\log_2 n$ ,即 $2^n$ 的反函数。 -=== "Python" +下图和以下代码模拟了“每轮缩减到一半”的过程,时间复杂度为 $O(\log_2 n)$ ,简记为 $O(\log n)$ : - ```python title="time_complexity.py" - """ 线性阶 """ - def linear(n): - count = 0 - for _ in range(n): - count += 1 - return count - ``` +```src +[file]{time_complexity}-[class]{}-[func]{logarithmic} +``` -=== "Go" +![对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic.png) - ```go title="time_complexity.go" - /* 线性阶 */ - func linear(n int) int { - count := 0 - for i := 0; i < n; i++ { - count++ - } - return count - } - ``` +与指数阶类似,对数阶也常出现于递归函数中。以下代码形成了一棵高度为 $\log_2 n$ 的递归树: -=== "JavaScript" +```src +[file]{time_complexity}-[class]{}-[func]{log_recur} +``` - ```js title="time_complexity.js" - /* 线性阶 */ - function linear(n) { - let count = 0; - for (let i = 0; i < n; i++) count++; - return count; - } - ``` +对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。 -=== "TypeScript" +!!! tip "$O(\log n)$ 的底数是多少?" - ```typescript title="time_complexity.ts" - /* 线性阶 */ - function linear(n: number): number { - let count = 0; - for (let i = 0; i < n; i++) count++; - return count; - } - ``` + 准确来说,“一分为 $m$”对应的时间复杂度是 $O(\log_m n)$ 。而通过对数换底公式,我们可以得到具有不同底数、相等的时间复杂度: -=== "C" + $$ + O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) + $$ - ```c title="time_complexity.c" - /* 线性阶 */ - int linear(int n) { - int count = 0; - for (int i = 0; i < n; i++) { - count ++; - } - return count; - } - ``` + 也就是说,底数 $m$ 可以在不影响复杂度的前提下转换。因此我们通常会省略底数 $m$ ,将对数阶直接记为 $O(\log n)$ 。 -=== "C#" +### 线性对数阶 $O(n \log n)$ - ```csharp title="time_complexity.cs" - /* 线性阶 */ - int linear(int n) - { - int count = 0; - for (int i = 0; i < n; i++) - count++; - return count; - } - ``` +线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 $O(\log n)$ 和 $O(n)$ 。相关代码如下: -=== "Swift" +```src +[file]{time_complexity}-[class]{}-[func]{linear_log_recur} +``` - ```swift title="time_complexity.swift" - /* 线性阶 */ - func linear(n: Int) -> Int { - var count = 0 - for _ in 0 ..< n { - count += 1 - } - return count - } - ``` +下图展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 $n$ ,树共有 $\log_2 n + 1$ 层,因此时间复杂度为 $O(n \log n)$ 。 -「遍历数组」和「遍历链表」等操作,时间复杂度都为 $O(n)$ ,其中 $n$ 为数组或链表的长度。 +![线性对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic_linear.png) -!!! tip +主流排序算法的时间复杂度通常为 $O(n \log n)$ ,例如快速排序、归并排序、堆排序等。 - **数据大小 $n$ 是根据输入数据的类型来确定的**。比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。 +### 阶乘阶 $O(n!)$ -=== "Java" +阶乘阶对应数学上的“全排列”问题。给定 $n$ 个互不重复的元素,求其所有可能的排列方案,方案数量为: - ```java title="time_complexity.java" - /* 线性阶(遍历数组) */ - int arrayTraversal(int[] nums) { - int count = 0; - // 循环次数与数组长度成正比 - for (int num : nums) { - count++; - } - return count; - } - ``` +$$ +n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 +$$ -=== "C++" +阶乘通常使用递归实现。如下图和以下代码所示,第一层分裂出 $n$ 个,第二层分裂出 $n - 1$ 个,以此类推,直至第 $n$ 层时停止分裂: - ```cpp title="time_complexity.cpp" - /* 线性阶(遍历数组) */ - int arrayTraversal(vector& nums) { - int count = 0; - // 循环次数与数组长度成正比 - for (int num : nums) { - count++; - } - return count; - } - ``` +```src +[file]{time_complexity}-[class]{}-[func]{factorial_recur} +``` -=== "Python" +![阶乘阶的时间复杂度](time_complexity.assets/time_complexity_factorial.png) - ```python title="time_complexity.py" - """ 线性阶(遍历数组)""" - def array_traversal(nums): - count = 0 - # 循环次数与数组长度成正比 - for num in nums: - count += 1 - return count - ``` +请注意,因为当 $n \geq 4$ 时恒有 $n! > 2^n$ ,所以阶乘阶比指数阶增长得更快,在 $n$ 较大时也是不可接受的。 -=== "Go" +## 最差、最佳、平均时间复杂度 - ```go title="time_complexity.go" - /* 线性阶(遍历数组) */ - func arrayTraversal(nums []int) int { - count := 0 - // 循环次数与数组长度成正比 - for range nums { - count++ - } - return count - } - ``` +**算法的时间效率往往不是固定的,而是与输入数据的分布有关**。假设输入一个长度为 $n$ 的数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,每个数字只出现一次;但元素顺序是随机打乱的,任务目标是返回元素 $1$ 的索引。我们可以得出以下结论。 -=== "JavaScript" +- 当 `nums = [?, ?, ..., 1]` ,即当末尾元素是 $1$ 时,需要完整遍历数组,**达到最差时间复杂度 $O(n)$** 。 +- 当 `nums = [1, ?, ?, ...]` ,即当首个元素为 $1$ 时,无论数组多长都不需要继续遍历,**达到最佳时间复杂度 $\Omega(1)$** 。 - ```js title="time_complexity.js" - /* 线性阶(遍历数组) */ - function arrayTraversal(nums) { - let count = 0; - // 循环次数与数组长度成正比 - for (let i = 0; i < nums.length; i++) { - count++; - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 线性阶(遍历数组) */ - function arrayTraversal(nums: number[]): number { - let count = 0; - // 循环次数与数组长度成正比 - for (let i = 0; i < nums.length; i++) { - count++; - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 线性阶(遍历数组) */ - int arrayTraversal(int *nums, int n) { - int count = 0; - // 循环次数与数组长度成正比 - for (int i = 0; i < n; i++) { - count ++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 线性阶(遍历数组) */ - int arrayTraversal(int[] nums) - { - int count = 0; - // 循环次数与数组长度成正比 - foreach(int num in nums) - { - count++; - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 线性阶(遍历数组) */ - func arrayTraversal(nums: [Int]) -> Int { - var count = 0 - // 循环次数与数组长度成正比 - for _ in nums { - count += 1 - } - return count - } - ``` - -### 平方阶 $O(n^2)$ - -平方阶的操作数量相对输入数据大小成平方级别增长。平方阶常出现于嵌套循环,外层循环和内层循环都为 $O(n)$ ,总体为 $O(n^2)$ 。 - -=== "Java" - - ```java title="time_complexity.java" - /* 平方阶 */ - int quadratic(int n) { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 平方阶 */ - int quadratic(int n) { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 平方阶 """ - def quadratic(n): - count = 0 - # 循环次数与数组长度成平方关系 - for i in range(n): - for j in range(n): - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 平方阶 */ - func quadratic(n int) int { - count := 0 - // 循环次数与数组长度成平方关系 - for i := 0; i < n; i++ { - for j := 0; j < n; j++ { - count++ - } - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 平方阶 */ - function quadratic(n) { - let count = 0; - // 循环次数与数组长度成平方关系 - for (let i = 0; i < n; i++) { - for (let j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 平方阶 */ - function quadratic(n: number): number { - let count = 0; - // 循环次数与数组长度成平方关系 - for (let i = 0; i < n; i++) { - for (let j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 平方阶 */ - int quadratic(int n) { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - count ++; - } - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 平方阶 */ - int quadratic(int n) - { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) - { - for (int j = 0; j < n; j++) - { - count++; - } - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 平方阶 */ - func quadratic(n: Int) -> Int { - var count = 0 - // 循环次数与数组长度成平方关系 - for _ in 0 ..< n { - for _ in 0 ..< n { - count += 1 - } - } - return count - } - ``` - -![time_complexity_constant_linear_quadratic](time_complexity.assets/time_complexity_constant_linear_quadratic.png) - -

Fig. 常数阶、线性阶、平方阶的时间复杂度

- -以「冒泡排序」为例,外层循环 $n - 1$ 次,内层循环 $n-1, n-2, \cdots, 2, 1$ 次,平均为 $\frac{n}{2}$ 次,因此时间复杂度为 $O(n^2)$ 。 - -$$ -O((n - 1) \frac{n}{2}) = O(n^2) -$$ - -=== "Java" - - ```java title="time_complexity.java" - /* 平方阶(冒泡排序) */ - int bubbleSort(int[] nums) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.length - 1; 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; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 平方阶(冒泡排序) */ - int bubbleSort(vector& nums) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; 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; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 平方阶(冒泡排序)""" - def bubble_sort(nums): - count = 0 # 计数器 - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(len(nums) - 1, 0, -1): - # 内循环:冒泡操作 - for j in range(i): - if nums[j] > nums[j + 1]: - # 交换 nums[j] 与 nums[j + 1] - tmp = nums[j] - nums[j] = nums[j + 1] - nums[j + 1] = tmp - count += 3 # 元素交换包含 3 个单元操作 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 平方阶(冒泡排序) */ - func bubbleSort(nums []int) int { - count := 0 // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := len(nums) - 1; i > 0; i-- { - // 内循环:冒泡操作 - for j := 0; j < i; j++ { - if nums[j] > nums[j+1] { - // 交换 nums[j] 与 nums[j + 1] - tmp := nums[j] - nums[j] = nums[j+1] - nums[j+1] = tmp - count += 3 // 元素交换包含 3 个单元操作 - } - } - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 平方阶(冒泡排序) */ - function bubbleSort(nums) { - let count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 平方阶(冒泡排序) */ - function bubbleSort(nums: number[]): number { - let count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 平方阶(冒泡排序) */ - int bubbleSort(int *nums, int n) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = n - 1; 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; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 平方阶(冒泡排序) */ - int bubbleSort(int[] nums) - { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; 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; - } - - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 平方阶(冒泡排序) */ - func bubbleSort(nums: inout [Int]) -> Int { - var count = 0 // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) { - // 内循环:冒泡操作 - for j in 0 ..< i { - if nums[j] > nums[j + 1] { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j] - nums[j] = nums[j + 1] - nums[j + 1] = tmp - count += 3 // 元素交换包含 3 个单元操作 - } - } - } - return count - } - ``` - -### 指数阶 $O(2^n)$ - -!!! note - - 生物学科中的“细胞分裂”即是指数阶增长:初始状态为 $1$ 个细胞,分裂一轮后为 $2$ 个,分裂两轮后为 $4$ 个,……,分裂 $n$ 轮后有 $2^n$ 个细胞。 - -指数阶增长得非常快,在实际应用中一般是不能被接受的。若一个问题使用「暴力枚举」求解的时间复杂度是 $O(2^n)$ ,那么一般都需要使用「动态规划」或「贪心算法」等算法来求解。 - -=== "Java" - - ```java title="time_complexity.java" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) { - for (int j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) { - for (int j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 指数阶(循环实现)""" - def exponential(n): - count, base = 0, 1 - # cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for _ in range(n): - for _ in range(base): - count += 1 - base *= 2 - # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 指数阶(循环实现)*/ - func exponential(n int) int { - count, base := 0, 1 - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for i := 0; i < n; i++ { - for j := 0; j < base; j++ { - count++ - } - base *= 2 - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 指数阶(循环实现) */ - function exponential(n) { - let count = 0, - base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (let i = 0; i < n; i++) { - for (let j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 指数阶(循环实现) */ - function exponential(n: number): number { - let count = 0, - base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (let i = 0; i < n; i++) { - for (let j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0; - int bas = 1; - // cell 每轮一分为二,形成数列 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; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 指数阶(循环实现) */ - int exponential(int n) - { - int count = 0, bas = 1; - // cell 每轮一分为二,形成数列 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; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 指数阶(循环实现) */ - func exponential(n: Int) -> Int { - var count = 0 - var base = 1 - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for _ in 0 ..< n { - for _ in 0 ..< base { - count += 1 - } - base *= 2 - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count - } - ``` - -![time_complexity_exponential](time_complexity.assets/time_complexity_exponential.png) - -

Fig. 指数阶的时间复杂度

- -在实际算法中,指数阶常出现于递归函数。例如以下代码,不断地一分为二,分裂 $n$ 次后停止。 - -=== "Java" - - ```java title="time_complexity.java" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 指数阶(递归实现)""" - def exp_recur(n): - if n == 1: return 1 - return exp_recur(n - 1) + exp_recur(n - 1) + 1 - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 指数阶(递归实现)*/ - func expRecur(n int) int { - if n == 1 { - return 1 - } - return expRecur(n-1) + expRecur(n-1) + 1 - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 指数阶(递归实现) */ - function expRecur(n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 指数阶(递归实现) */ - function expRecur(n: number): number { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 指数阶(递归实现) */ - int expRecur(int n) - { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 指数阶(递归实现) */ - func expRecur(n: Int) -> Int { - if n == 1 { - return 1 - } - return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 - } - ``` - -### 对数阶 $O(\log n)$ - -对数阶与指数阶正好相反,后者反映“每轮增加到两倍的情况”,而前者反映“每轮缩减到一半的情况”。对数阶仅次于常数阶,时间增长得很慢,是理想的时间复杂度。 - -对数阶常出现于「二分查找」和「分治算法」中,体现“一分为多”、“化繁为简”的算法思想。 - -设输入数据大小为 $n$ ,由于每轮缩减到一半,因此循环次数是 $\log_2 n$ ,即 $2^n$ 的反函数。 - -=== "Java" - - ```java title="time_complexity.java" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 对数阶(循环实现)""" - def logarithmic(n): - count = 0 - while n > 1: - n = n / 2 - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 对数阶(循环实现)*/ - func logarithmic(n float64) int { - count := 0 - for n > 1 { - n = n / 2 - count++ - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 对数阶(循环实现) */ - function logarithmic(n) { - let count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 对数阶(循环实现) */ - function logarithmic(n: number): number { - let count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 对数阶(循环实现) */ - int logarithmic(float n) - { - int count = 0; - while (n > 1) - { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 对数阶(循环实现) */ - func logarithmic(n: Int) -> Int { - var count = 0 - var n = n - while n > 1 { - n = n / 2 - count += 1 - } - return count - } - ``` - -![time_complexity_logarithmic](time_complexity.assets/time_complexity_logarithmic.png) - -

Fig. 对数阶的时间复杂度

- -与指数阶类似,对数阶也常出现于递归函数。以下代码形成了一个高度为 $\log_2 n$ 的递归树。 - -=== "Java" - - ```java title="time_complexity.java" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 对数阶(递归实现)""" - def log_recur(n): - if n <= 1: return 0 - return log_recur(n / 2) + 1 - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 对数阶(递归实现)*/ - func logRecur(n float64) int { - if n <= 1 { - return 0 - } - return logRecur(n/2) + 1 - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 对数阶(递归实现) */ - function logRecur(n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 对数阶(递归实现) */ - function logRecur(n: number): number { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 对数阶(递归实现) */ - int logRecur(float n) - { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 对数阶(递归实现) */ - func logRecur(n: Int) -> Int { - if n <= 1 { - return 0 - } - return logRecur(n: n / 2) + 1 - } - ``` - -### 线性对数阶 $O(n \log n)$ - -线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 $O(\log n)$ 和 $O(n)$ 。 - -主流排序算法的时间复杂度都是 $O(n \log n )$ ,例如快速排序、归并排序、堆排序等。 - -=== "Java" - - ```java title="time_complexity.java" - /* 线性对数阶 */ - int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 线性对数阶 */ - int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 线性对数阶 """ - def linear_log_recur(n): - if n <= 1: return 1 - count = linear_log_recur(n // 2) + \ - linear_log_recur(n // 2) - for _ in range(n): - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 线性对数阶 */ - func linearLogRecur(n float64) int { - if n <= 1 { - return 1 - } - count := linearLogRecur(n/2) + - linearLogRecur(n/2) - for i := 0.0; i < n; i++ { - count++ - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 线性对数阶 */ - function linearLogRecur(n) { - if (n <= 1) return 1; - let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); - for (let i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 线性对数阶 */ - function linearLogRecur(n: number): number { - if (n <= 1) return 1; - let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); - for (let i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 线性对数阶 */ - int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) { - count ++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 线性对数阶 */ - int linearLogRecur(float n) - { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) - { - count++; - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 线性对数阶 */ - func linearLogRecur(n: Double) -> Int { - if n <= 1 { - return 1 - } - var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) - for _ in 0 ..< Int(n) { - count += 1 - } - return count - } - ``` - -![time_complexity_logarithmic_linear](time_complexity.assets/time_complexity_logarithmic_linear.png) - -

Fig. 线性对数阶的时间复杂度

- -### 阶乘阶 $O(n!)$ - -阶乘阶对应数学上的「全排列」。即给定 $n$ 个互不重复的元素,求其所有可能的排列方案,则方案数量为 - -$$ -n! = n \times (n - 1) \times (n - 2) \times \cdots \times 2 \times 1 -$$ - -阶乘常使用递归实现。例如以下代码,第一层分裂出 $n$ 个,第二层分裂出 $n - 1$ 个,…… ,直至到第 $n$ 层时终止分裂。 - -=== "Java" - - ```java title="time_complexity.java" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 阶乘阶(递归实现)""" - def factorial_recur(n): - if n == 0: return 1 - count = 0 - # 从 1 个分裂出 n 个 - for _ in range(n): - count += factorial_recur(n - 1) - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 阶乘阶(递归实现) */ - func factorialRecur(n int) int { - if n == 0 { - return 1 - } - count := 0 - // 从 1 个分裂出 n 个 - for i := 0; i < n; i++ { - count += factorialRecur(n - 1) - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 阶乘阶(递归实现) */ - function factorialRecur(n) { - if (n == 0) return 1; - let count = 0; - // 从 1 个分裂出 n 个 - for (let i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 阶乘阶(递归实现) */ - function factorialRecur(n: number): number { - if (n == 0) return 1; - let count = 0; - // 从 1 个分裂出 n 个 - for (let i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 阶乘阶(递归实现) */ - 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; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) - { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) - { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 阶乘阶(递归实现) */ - func factorialRecur(n: Int) -> Int { - if n == 0 { - return 1 - } - var count = 0 - // 从 1 个分裂出 n 个 - for _ in 0 ..< n { - count += factorialRecur(n: n - 1) - } - return count - } - ``` - -![time_complexity_factorial](time_complexity.assets/time_complexity_factorial.png) - -

Fig. 阶乘阶的时间复杂度

- -## 最差、最佳、平均时间复杂度 - -**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关**。举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论: - -- 当 `nums = [?, ?, ..., 1]`,即当末尾元素是 $1$ 时,则需完整遍历数组,此时达到 **最差时间复杂度 $O(n)$** ; -- 当 `nums = [1, ?, ?, ...]` ,即当首个数字为 $1$ 时,无论数组多长都不需要继续遍历,此时达到 **最佳时间复杂度 $\Omega(1)$** ; - -「函数渐近上界」使用大 $O$ 记号表示,代表「最差时间复杂度」。与之对应,「函数渐近下界」用 $\Omega$ 记号(Omega Notation)来表示,代表「最佳时间复杂度」。 - -=== "Java" - - ```java title="worst_best_time_complexity.java" - public class worst_best_time_complexity { - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - int[] randomNumbers(int n) { - Integer[] nums = new Integer[n]; - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 随机打乱数组元素 - Collections.shuffle(Arrays.asList(nums)); - // Integer[] -> int[] - int[] res = new int[n]; - for (int i = 0; i < n; i++) { - res[i] = nums[i]; - } - return res; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - int findOne(int[] nums) { - for (int i = 0; i < nums.length; i++) { - if (nums[i] == 1) - return i; - } - return -1; - } - - /* Driver Code */ - public void main(String[] args) { - for (int i = 0; i < 10; i++) { - int n = 100; - int[] nums = randomNumbers(n); - int index = findOne(nums); - System.out.println("打乱后的数组为 " + Arrays.toString(nums)); - System.out.println("数字 1 的索引为 " + index); - } - } - } - ``` - -=== "C++" - - ```cpp title="worst_best_time_complexity.cpp" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - vector randomNumbers(int n) { - vector nums(n); - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 使用系统时间生成随机种子 - unsigned seed = chrono::system_clock::now().time_since_epoch().count(); - // 随机打乱数组元素 - shuffle(nums.begin(), nums.end(), default_random_engine(seed)); - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - int findOne(vector& nums) { - for (int i = 0; i < nums.size(); i++) { - if (nums[i] == 1) - return i; - } - return -1; - } - - - /* Driver Code */ - int main() { - for (int i = 0; i < 1000; i++) { - int n = 100; - vector nums = randomNumbers(n); - int index = findOne(nums); - cout << "\n数组 [ 1, 2, ..., n ] 被打乱后 = "; - PrintUtil::printVector(nums); - cout << "数字 1 的索引为 " << index << endl; - } - return 0; - } - ``` - -=== "Python" - - ```python title="worst_best_time_complexity.py" - """ 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """ - def random_numbers(n): - # 生成数组 nums =: 1, 2, 3, ..., n - nums = [i for i in range(1, n + 1)] - # 随机打乱数组元素 - random.shuffle(nums) - return nums - - """ 查找数组 nums 中数字 1 所在索引 """ - def find_one(nums): - for i in range(len(nums)): - if nums[i] == 1: - return i - return -1 - - """ Driver Code """ - if __name__ == "__main__": - for i in range(10): - n = 100 - nums = random_numbers(n) - index = find_one(nums) - print("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) - print("数字 1 的索引为", index) - ``` - -=== "Go" - - ```go title="worst_best_time_complexity.go" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - func randomNumbers(n int) []int { - nums := make([]int, n) - // 生成数组 nums = { 1, 2, 3, ..., n } - for i := 0; i < n; i++ { - nums[i] = i + 1 - } - // 随机打乱数组元素 - rand.Shuffle(len(nums), func(i, j int) { - nums[i], nums[j] = nums[j], nums[i] - }) - return nums - } - - /* 查找数组 nums 中数字 1 所在索引 */ - func findOne(nums []int) int { - for i := 0; i < len(nums); i++ { - if nums[i] == 1 { - return i - } - } - return -1 - } - - /* Driver Code */ - func main() { - for i := 0; i < 10; i++ { - n := 100 - nums := randomNumbers(n) - index := findOne(nums) - fmt.Println("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) - fmt.Println("数字 1 的索引为", index) - } - } - ``` - -=== "JavaScript" - - ```js title="worst_best_time_complexity.js" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - function randomNumbers(n) { - let nums = Array(n); - // 生成数组 nums = { 1, 2, 3, ..., n } - for (let i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 随机打乱数组元素 - for (let i = 0; i < n; i++) { - let r = Math.floor(Math.random() * (i + 1)); - let temp = nums[i]; - nums[i] = nums[r]; - nums[r] = temp; - } - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - function findOne(nums) { - for (let i = 0; i < nums.length; i++) { - if (nums[i] === 1) { - return i; - } - } - return -1; - } - - /* Driver Code */ - function main() { - for (let i = 0; i < 10; i++) { - let n = 100; - let nums = randomNumbers(n); - let index = findOne(nums); - console.log( - "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" - ); - console.log("数字 1 的索引为 " + index); - } - } - ``` - -=== "TypeScript" - - ```typescript title="worst_best_time_complexity.ts" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - function randomNumbers(n: number): number[] { - let nums = Array(n); - // 生成数组 nums = { 1, 2, 3, ..., n } - for (let i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 随机打乱数组元素 - for (let i = 0; i < n; i++) { - let r = Math.floor(Math.random() * (i + 1)); - let temp = nums[i]; - nums[i] = nums[r]; - nums[r] = temp; - } - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - function findOne(nums: number[]): number { - for (let i = 0; i < nums.length; i++) { - if (nums[i] === 1) { - return i; - } - } - return -1; - } - - /* Driver Code */ - function main(): void { - for (let i = 0; i < 10; i++) { - let n = 100; - let nums = randomNumbers(n); - let index = findOne(nums); - console.log( - "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" - ); - console.log("数字 1 的索引为 " + index); - } - } - ``` - -=== "C" - - ```c title="worst_best_time_complexity.c" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - int *randomNumbers(int n) { - // 分配堆区内存(创建一维可变长数组:数组中元素数量为n,元素类型为int) - 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++) { - if (nums[i] == 1) return i; - } - return -1; - } - - /* Driver Code */ - 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; - } - } - getchar(); - return 0; - } - ``` - -=== "C#" - - ```csharp title="worst_best_time_complexity.cs" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - int[] randomNumbers(int n) - { - int[] nums = new int[n]; - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) - { - nums[i] = i + 1; - } - - // 随机打乱数组元素 - for (int i = 0; i < nums.Length; i++) - { - var index = new Random().Next(i, nums.Length); - var tmp = nums[i]; - var ran = nums[index]; - nums[i] = ran; - nums[index] = tmp; - } - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - int findOne(int[] nums) - { - for (int i = 0; i < nums.Length; i++) - { - if (nums[i] == 1) - return i; - } - return -1; - } - - /* Driver Code */ - public void main(String[] args) - { - for (int i = 0; i < 10; i++) - { - int n = 100; - int[] nums = randomNumbers(n); - int index = findOne(nums); - Console.WriteLine("\n数组 [ 1, 2, ..., n ] 被打乱后 = " + string.Join(",", nums)); - Console.WriteLine("数字 1 的索引为 " + index); - } - } - ``` - -=== "Swift" - - ```swift title="worst_best_time_complexity.swift" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - func randomNumbers(n: Int) -> [Int] { - // 生成数组 nums = { 1, 2, 3, ..., n } - var nums = Array(1 ... n) - // 随机打乱数组元素 - nums.shuffle() - return nums - } - - /* 查找数组 nums 中数字 1 所在索引 */ - func findOne(nums: [Int]) -> Int { - for i in nums.indices { - if nums[i] == 1 { - return i - } - } - return -1 - } - - /* Driver Code */ - func main() { - for _ in 0 ..< 10 { - let n = 100 - let nums = randomNumbers(n: n) - let index = findOne(nums: nums) - print("数组 [ 1, 2, ..., n ] 被打乱后 = \(nums)") - print("数字 1 的索引为 \(index)") - } - } - ``` +“最差时间复杂度”对应函数渐近上界,使用大 $O$ 记号表示。相应地,“最佳时间复杂度”对应函数渐近下界,用 $\Omega$ 记号表示: -!!! tip +```src +[file]{worst_best_time_complexity}-[class]{}-[func]{find_one} +``` - 我们在实际应用中很少使用「最佳时间复杂度」,因为往往只有很小概率下才能达到,会带来一定的误导性。反之,「最差时间复杂度」最为实用,因为它给出了一个“效率安全值”,让我们可以放心地使用算法。 +值得说明的是,我们在实际中很少使用最佳时间复杂度,因为通常只有在很小概率下才能达到,可能会带来一定的误导性。**而最差时间复杂度更为实用,因为它给出了一个效率安全值**,让我们可以放心地使用算法。 -从上述示例可以看出,最差或最佳时间复杂度只出现在“特殊分布的数据”中,这些情况的出现概率往往很小,因此并不能最真实地反映算法运行效率。**相对地,「平均时间复杂度」可以体现算法在随机输入数据下的运行效率,用 $\Theta$ 记号(Theta Notation)来表示**。 +从上述示例可以看出,最差时间复杂度和最佳时间复杂度只出现于“特殊的数据分布”,这些情况的出现概率可能很小,并不能真实地反映算法运行效率。相比之下,**平均时间复杂度可以体现算法在随机输入数据下的运行效率**,用 $\Theta$ 记号来表示。 -对于部分算法,我们可以简单地推算出随机数据分布下的平均情况。比如上述示例,由于输入数组是被打乱的,因此元素 $1$ 出现在任意索引的概率都是相等的,那么算法的平均循环次数则是数组长度的一半 $\frac{n}{2}$ ,平均时间复杂度为 $\Theta(\frac{n}{2}) = \Theta(n)$ 。 +对于部分算法,我们可以简单地推算出随机数据分布下的平均情况。比如上述示例,由于输入数组是被打乱的,因此元素 $1$ 出现在任意索引的概率都是相等的,那么算法的平均循环次数就是数组长度的一半 $n / 2$ ,平均时间复杂度为 $\Theta(n / 2) = \Theta(n)$ 。 -但在实际应用中,尤其是较为复杂的算法,计算平均时间复杂度比较困难,因为很难简便地分析出在数据分布下的整体数学期望。这种情况下,我们一般使用最差时间复杂度来作为算法效率的评判标准。 +但对于较为复杂的算法,计算平均时间复杂度往往比较困难,因为很难分析出在数据分布下的整体数学期望。在这种情况下,我们通常使用最差时间复杂度作为算法效率的评判标准。 !!! question "为什么很少看到 $\Theta$ 符号?" - 实际中我们经常使用「大 $O$ 符号」来表示「平均复杂度」,这样严格意义上来说是不规范的。这可能是因为 $O$ 符号实在是太朗朗上口了。
如果在本书和其他资料中看到类似 **平均时间复杂度 $O(n)$** 的表述,请你直接理解为 $\Theta(n)$ 即可。 + 可能由于 $O$ 符号过于朗朗上口,因此我们常常使用它来表示平均时间复杂度。但从严格意义上讲,这种做法并不规范。在本书和其他资料中,若遇到类似“平均时间复杂度 $O(n)$”的表述,请将其直接理解为 $\Theta(n)$ 。 diff --git a/docs/chapter_data_structure/basic_data_types.md b/docs/chapter_data_structure/basic_data_types.md new file mode 100644 index 0000000000..3f32a49c6e --- /dev/null +++ b/docs/chapter_data_structure/basic_data_types.md @@ -0,0 +1,181 @@ +# 基本数据类型 + +当谈及计算机中的数据时,我们会想到文本、图片、视频、语音、3D 模型等各种形式。尽管这些数据的组织形式各异,但它们都由各种基本数据类型构成。 + +**基本数据类型是 CPU 可以直接进行运算的类型**,在算法中直接被使用,主要包括以下几种。 + +- 整数类型 `byte`、`short`、`int`、`long` 。 +- 浮点数类型 `float`、`double` ,用于表示小数。 +- 字符类型 `char` ,用于表示各种语言的字母、标点符号甚至表情符号等。 +- 布尔类型 `bool` ,用于表示“是”与“否”判断。 + +**基本数据类型以二进制的形式存储在计算机中**。一个二进制位即为 $1$ 比特。在绝大多数现代操作系统中,$1$ 字节(byte)由 $8$ 比特(bit)组成。 + +基本数据类型的取值范围取决于其占用的空间大小。下面以 Java 为例。 + +- 整数类型 `byte` 占用 $1$ 字节 = $8$ 比特 ,可以表示 $2^{8}$ 个数字。 +- 整数类型 `int` 占用 $4$ 字节 = $32$ 比特 ,可以表示 $2^{32}$ 个数字。 + +下表列举了 Java 中各种基本数据类型的占用空间、取值范围和默认值。此表格无须死记硬背,大致理解即可,需要时可以通过查表来回忆。 + +

  基本数据类型的占用空间和取值范围

+ +| 类型 | 符号 | 占用空间 | 最小值 | 最大值 | 默认值 | +| ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- | +| 整数 | `byte` | 1 字节 | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | +| | `short` | 2 字节 | $-2^{15}$ | $2^{15} - 1$ | $0$ | +| | `int` | 4 字节 | $-2^{31}$ | $2^{31} - 1$ | $0$ | +| | `long` | 8 字节 | $-2^{63}$ | $2^{63} - 1$ | $0$ | +| 浮点数 | `float` | 4 字节 | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | +| | `double` | 8 字节 | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | +| 字符 | `char` | 2 字节 | $0$ | $2^{16} - 1$ | $0$ | +| 布尔 | `bool` | 1 字节 | $\text{false}$ | $\text{true}$ | $\text{false}$ | + +请注意,上表针对的是 Java 的基本数据类型的情况。每种编程语言都有各自的数据类型定义,它们的占用空间、取值范围和默认值可能会有所不同。 + +- 在 Python 中,整数类型 `int` 可以是任意大小,只受限于可用内存;浮点数 `float` 是双精度 64 位;没有 `char` 类型,单个字符实际上是长度为 1 的字符串 `str` 。 +- C 和 C++ 未明确规定基本数据类型的大小,而因实现和平台各异。上表遵循 LP64 [数据模型](https://en.cppreference.com/w/cpp/language/types#Properties),其用于包括 Linux 和 macOS 在内的 Unix 64 位操作系统。 +- 字符 `char` 的大小在 C 和 C++ 中为 1 字节,在大多数编程语言中取决于特定的字符编码方法,详见“字符编码”章节。 +- 即使表示布尔量仅需 1 位($0$ 或 $1$),它在内存中通常也存储为 1 字节。这是因为现代计算机 CPU 通常将 1 字节作为最小寻址内存单元。 + +那么,基本数据类型与数据结构之间有什么联系呢?我们知道,数据结构是在计算机中组织与存储数据的方式。这句话的主语是“结构”而非“数据”。 + +如果想表示“一排数字”,我们自然会想到使用数组。这是因为数组的线性结构可以表示数字的相邻关系和顺序关系,但至于存储的内容是整数 `int`、小数 `float` 还是字符 `char` ,则与“数据结构”无关。 + +换句话说,**基本数据类型提供了数据的“内容类型”,而数据结构提供了数据的“组织方式”**。例如以下代码,我们用相同的数据结构(数组)来存储与表示不同的基本数据类型,包括 `int`、`float`、`char`、`bool` 等。 + +=== "Python" + + ```python title="" + # 使用多种基本数据类型来初始化数组 + numbers: list[int] = [0] * 5 + decimals: list[float] = [0.0] * 5 + # Python 的字符实际上是长度为 1 的字符串 + characters: list[str] = ['0'] * 5 + bools: list[bool] = [False] * 5 + # Python 的列表可以自由存储各种基本数据类型和对象引用 + data = [0, 0.0, 'a', False, ListNode(0)] + ``` + +=== "C++" + + ```cpp title="" + // 使用多种基本数据类型来初始化数组 + int numbers[5]; + float decimals[5]; + char characters[5]; + bool bools[5]; + ``` + +=== "Java" + + ```java title="" + // 使用多种基本数据类型来初始化数组 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + boolean[] bools = new boolean[5]; + ``` + +=== "C#" + + ```csharp title="" + // 使用多种基本数据类型来初始化数组 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + bool[] bools = new bool[5]; + ``` + +=== "Go" + + ```go title="" + // 使用多种基本数据类型来初始化数组 + var numbers = [5]int{} + var decimals = [5]float64{} + var characters = [5]byte{} + var bools = [5]bool{} + ``` + +=== "Swift" + + ```swift title="" + // 使用多种基本数据类型来初始化数组 + let numbers = Array(repeating: 0, count: 5) + let decimals = Array(repeating: 0.0, count: 5) + let characters: [Character] = Array(repeating: "a", count: 5) + let bools = Array(repeating: false, count: 5) + ``` + +=== "JS" + + ```javascript title="" + // JavaScript 的数组可以自由存储各种基本数据类型和对象 + const array = [0, 0.0, 'a', false]; + ``` + +=== "TS" + + ```typescript title="" + // 使用多种基本数据类型来初始化数组 + const numbers: number[] = []; + const characters: string[] = []; + const bools: boolean[] = []; + ``` + +=== "Dart" + + ```dart title="" + // 使用多种基本数据类型来初始化数组 + List numbers = List.filled(5, 0); + List decimals = List.filled(5, 0.0); + List characters = List.filled(5, 'a'); + List bools = List.filled(5, false); + ``` + +=== "Rust" + + ```rust title="" + // 使用多种基本数据类型来初始化数组 + let numbers: Vec = vec![0; 5]; + let decimals: Vec = vec![0.0; 5]; + let characters: Vec = vec!['0'; 5]; + let bools: Vec = vec![false; 5]; + ``` + +=== "C" + + ```c title="" + // 使用多种基本数据类型来初始化数组 + int numbers[10]; + float decimals[10]; + char characters[10]; + bool bools[10]; + ``` + +=== "Kotlin" + + ```kotlin title="" + // 使用多种基本数据类型来初始化数组 + val numbers = IntArray(5) + val decinals = FloatArray(5) + val characters = CharArray(5) + val bools = BooleanArray(5) + ``` + +=== "Ruby" + + ```ruby title="" + # Ruby 的列表可以自由存储各种基本数据类型和对象引用 + data = [0, 0.0, 'a', false, ListNode(0)] + ``` + +=== "Zig" + + ```zig title="" + + ``` + +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/docs/chapter_data_structure/character_encoding.assets/ascii_table.png b/docs/chapter_data_structure/character_encoding.assets/ascii_table.png new file mode 100644 index 0000000000..9d80a7c1ea Binary files /dev/null and b/docs/chapter_data_structure/character_encoding.assets/ascii_table.png differ diff --git a/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png b/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png new file mode 100644 index 0000000000..57255a81d8 Binary files /dev/null and b/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png differ diff --git a/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png b/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png new file mode 100644 index 0000000000..d907834443 Binary files /dev/null and b/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png differ diff --git a/docs/chapter_data_structure/character_encoding.md b/docs/chapter_data_structure/character_encoding.md new file mode 100644 index 0000000000..bf6279e678 --- /dev/null +++ b/docs/chapter_data_structure/character_encoding.md @@ -0,0 +1,87 @@ +# 字符编码 * + +在计算机中,所有数据都是以二进制数的形式存储的,字符 `char` 也不例外。为了表示字符,我们需要建立一套“字符集”,规定每个字符和二进制数之间的一一对应关系。有了字符集之后,计算机就可以通过查表完成二进制数到字符的转换。 + +## ASCII 字符集 + +ASCII 码是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美国标准信息交换代码)。它使用 7 位二进制数(一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。如下图所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符(如换行符和制表符)。 + +![ASCII 码](character_encoding.assets/ascii_table.png) + +然而,**ASCII 码仅能够表示英文**。随着计算机的全球化,诞生了一种能够表示更多语言的 EASCII 字符集。它在 ASCII 的 7 位基础上扩展到 8 位,能够表示 256 个不同的字符。 + +在世界范围内,陆续出现了一批适用于不同地区的 EASCII 字符集。这些字符集的前 128 个字符统一为 ASCII 码,后 128 个字符定义不同,以适应不同语言的需求。 + +## GBK 字符集 + +后来人们发现,**EASCII 码仍然无法满足许多语言的字符数量要求**。比如汉字有近十万个,光日常使用的就有几千个。中国国家标准总局于 1980 年发布了 GB2312 字符集,其收录了 6763 个汉字,基本满足了汉字的计算机处理需要。 + +然而,GB2312 无法处理部分罕见字和繁体字。GBK 字符集是在 GB2312 的基础上扩展得到的,它共收录了 21886 个汉字。在 GBK 的编码方案中,ASCII 字符使用一个字节表示,汉字使用两个字节表示。 + +## Unicode 字符集 + +随着计算机技术的蓬勃发展,字符集与编码标准百花齐放,而这带来了许多问题。一方面,这些字符集一般只定义了特定语言的字符,无法在多语言环境下正常工作。另一方面,同一种语言存在多种字符集标准,如果两台计算机使用的是不同的编码标准,则在信息传递时就会出现乱码。 + +那个时代的研究人员就在想:**如果推出一个足够完整的字符集,将世界范围内的所有语言和符号都收录其中,不就可以解决跨语言环境和乱码问题了吗**?在这种想法的驱动下,一个大而全的字符集 Unicode 应运而生。 + +Unicode 的中文名称为“统一码”,理论上能容纳 100 多万个字符。它致力于将全球范围内的字符纳入统一的字符集之中,提供一种通用的字符集来处理和显示各种语言文字,减少因为编码标准不同而产生的乱码问题。 + +自 1991 年发布以来,Unicode 不断扩充新的语言与字符。截至 2022 年 9 月,Unicode 已经包含 149186 个字符,包括各种语言的字符、符号甚至表情符号等。在庞大的 Unicode 字符集中,常用的字符占用 2 字节,有些生僻的字符占用 3 字节甚至 4 字节。 + +Unicode 是一种通用字符集,本质上是给每个字符分配一个编号(称为“码点”),**但它并没有规定在计算机中如何存储这些字符码点**。我们不禁会问:当多种长度的 Unicode 码点同时出现在一个文本中时,系统如何解析字符?例如给定一个长度为 2 字节的编码,系统如何确认它是一个 2 字节的字符还是两个 1 字节的字符? + +对于以上问题,**一种直接的解决方案是将所有字符存储为等长的编码**。如下图所示,“Hello”中的每个字符占用 1 字节,“算法”中的每个字符占用 2 字节。我们可以通过高位填 0 将“Hello 算法”中的所有字符都编码为 2 字节长度。这样系统就可以每隔 2 字节解析一个字符,恢复这个短语的内容了。 + +![Unicode 编码示例](character_encoding.assets/unicode_hello_algo.png) + +然而 ASCII 码已经向我们证明,编码英文只需 1 字节。若采用上述方案,英文文本占用空间的大小将会是 ASCII 编码下的两倍,非常浪费内存空间。因此,我们需要一种更加高效的 Unicode 编码方法。 + +## UTF-8 编码 + +目前,UTF-8 已成为国际上使用最广泛的 Unicode 编码方法。**它是一种可变长度的编码**,使用 1 到 4 字节来表示一个字符,根据字符的复杂性而变。ASCII 字符只需 1 字节,拉丁字母和希腊字母需要 2 字节,常用的中文字符需要 3 字节,其他的一些生僻字符需要 4 字节。 + +UTF-8 的编码规则并不复杂,分为以下两种情况。 + +- 对于长度为 1 字节的字符,将最高位设置为 $0$ ,其余 7 位设置为 Unicode 码点。值得注意的是,ASCII 字符在 Unicode 字符集中占据了前 128 个码点。也就是说,**UTF-8 编码可以向下兼容 ASCII 码**。这意味着我们可以使用 UTF-8 来解析年代久远的 ASCII 码文本。 +- 对于长度为 $n$ 字节的字符(其中 $n > 1$),将首个字节的高 $n$ 位都设置为 $1$ ,第 $n + 1$ 位设置为 $0$ ;从第二个字节开始,将每个字节的高 2 位都设置为 $10$ ;其余所有位用于填充字符的 Unicode 码点。 + +下图展示了“Hello算法”对应的 UTF-8 编码。观察发现,由于最高 $n$ 位都设置为 $1$ ,因此系统可以通过读取最高位 $1$ 的个数来解析出字符的长度为 $n$ 。 + +但为什么要将其余所有字节的高 2 位都设置为 $10$ 呢?实际上,这个 $10$ 能够起到校验符的作用。假设系统从一个错误的字节开始解析文本,字节头部的 $10$ 能够帮助系统快速判断出异常。 + +之所以将 $10$ 当作校验符,是因为在 UTF-8 编码规则下,不可能有字符的最高两位是 $10$ 。这个结论可以用反证法来证明:假设一个字符的最高两位是 $10$ ,说明该字符的长度为 $1$ ,对应 ASCII 码。而 ASCII 码的最高位应该是 $0$ ,与假设矛盾。 + +![UTF-8 编码示例](character_encoding.assets/utf-8_hello_algo.png) + +除了 UTF-8 之外,常见的编码方式还包括以下两种。 + +- **UTF-16 编码**:使用 2 或 4 字节来表示一个字符。所有的 ASCII 字符和常用的非英文字符,都用 2 字节表示;少数字符需要用到 4 字节表示。对于 2 字节的字符,UTF-16 编码与 Unicode 码点相等。 +- **UTF-32 编码**:每个字符都使用 4 字节。这意味着 UTF-32 比 UTF-8 和 UTF-16 更占用空间,特别是对于 ASCII 字符占比较高的文本。 + +从存储空间占用的角度看,使用 UTF-8 表示英文字符非常高效,因为它仅需 1 字节;使用 UTF-16 编码某些非英文字符(例如中文)会更加高效,因为它仅需 2 字节,而 UTF-8 可能需要 3 字节。 + +从兼容性的角度看,UTF-8 的通用性最佳,许多工具和库优先支持 UTF-8 。 + +## 编程语言的字符编码 + +对于以往的大多数编程语言,程序运行中的字符串都采用 UTF-16 或 UTF-32 这类等长编码。在等长编码下,我们可以将字符串看作数组来处理,这种做法具有以下优点。 + +- **随机访问**:UTF-16 编码的字符串可以很容易地进行随机访问。UTF-8 是一种变长编码,要想找到第 $i$ 个字符,我们需要从字符串的开始处遍历到第 $i$ 个字符,这需要 $O(n)$ 的时间。 +- **字符计数**:与随机访问类似,计算 UTF-16 编码的字符串的长度也是 $O(1)$ 的操作。但是,计算 UTF-8 编码的字符串的长度需要遍历整个字符串。 +- **字符串操作**:在 UTF-16 编码的字符串上,很多字符串操作(如分割、连接、插入、删除等)更容易进行。在 UTF-8 编码的字符串上,进行这些操作通常需要额外的计算,以确保不会产生无效的 UTF-8 编码。 + +实际上,编程语言的字符编码方案设计是一个很有趣的话题,涉及许多因素。 + +- Java 的 `String` 类型使用 UTF-16 编码,每个字符占用 2 字节。这是因为 Java 语言设计之初,人们认为 16 位足以表示所有可能的字符。然而,这是一个不正确的判断。后来 Unicode 规范扩展到了超过 16 位,所以 Java 中的字符现在可能由一对 16 位的值(称为“代理对”)表示。 +- JavaScript 和 TypeScript 的字符串使用 UTF-16 编码的原因与 Java 类似。当 1995 年 Netscape 公司首次推出 JavaScript 语言时,Unicode 还处于发展早期,那时候使用 16 位的编码就足以表示所有的 Unicode 字符了。 +- C# 使用 UTF-16 编码,主要是因为 .NET 平台是由 Microsoft 设计的,而 Microsoft 的很多技术(包括 Windows 操作系统)都广泛使用 UTF-16 编码。 + +由于以上编程语言对字符数量的低估,它们不得不采取“代理对”的方式来表示超过 16 位长度的 Unicode 字符。这是一个不得已为之的无奈之举。一方面,包含代理对的字符串中,一个字符可能占用 2 字节或 4 字节,从而丧失了等长编码的优势。另一方面,处理代理对需要额外增加代码,这提高了编程的复杂性和调试难度。 + +出于以上原因,部分编程语言提出了一些不同的编码方案。 + +- Python 中的 `str` 使用 Unicode 编码,并采用一种灵活的字符串表示,存储的字符长度取决于字符串中最大的 Unicode 码点。若字符串中全部是 ASCII 字符,则每个字符占用 1 字节;如果有字符超出了 ASCII 范围,但全部在基本多语言平面(BMP)内,则每个字符占用 2 字节;如果有超出 BMP 的字符,则每个字符占用 4 字节。 +- Go 语言的 `string` 类型在内部使用 UTF-8 编码。Go 语言还提供了 `rune` 类型,它用于表示单个 Unicode 码点。 +- Rust 语言的 `str` 和 `String` 类型在内部使用 UTF-8 编码。Rust 也提供了 `char` 类型,用于表示单个 Unicode 码点。 + +需要注意的是,以上讨论的都是字符串在编程语言中的存储方式,**这和字符串如何在文件中存储或在网络中传输是不同的问题**。在文件存储或网络传输中,我们通常会将字符串编码为 UTF-8 格式,以达到最优的兼容性和空间效率。 diff --git a/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png b/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png index 7375e43c3b..1c3f42c836 100644 Binary files a/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png and b/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png differ diff --git a/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png b/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png index 29ef8bb226..94288e5113 100644 Binary files a/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png and b/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png differ diff --git a/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png b/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png new file mode 100644 index 0000000000..e14100e85d Binary files /dev/null and b/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png differ diff --git a/docs/chapter_data_structure/classification_of_data_structure.md b/docs/chapter_data_structure/classification_of_data_structure.md index 202af50a25..4a74c2de07 100644 --- a/docs/chapter_data_structure/classification_of_data_structure.md +++ b/docs/chapter_data_structure/classification_of_data_structure.md @@ -1,43 +1,48 @@ ---- -comments: true ---- - # 数据结构分类 -数据结构主要可根据「逻辑结构」和「物理结构」两种角度进行分类。 +常见的数据结构包括数组、链表、栈、队列、哈希表、树、堆、图,它们可以从“逻辑结构”和“物理结构”两个维度进行分类。 ## 逻辑结构:线性与非线性 -**「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 +**逻辑结构揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照一定顺序排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出“祖先”与“后代”之间的派生关系;图则由节点和边构成,反映了复杂的网络关系。 + +如下图所示,逻辑结构可分为“线性”和“非线性”两大类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列。 + +- **线性数据结构**:数组、链表、栈、队列、哈希表,元素之间是一对一的顺序关系。 +- **非线性数据结构**:树、堆、图、哈希表。 -我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线形的(例如是网状或树状的),那么就是非线性数据结构。 +非线性数据结构可以进一步划分为树形结构和网状结构。 -- **线性数据结构**:数组、链表、栈、队列、哈希表; -- **非线性数据结构**:树、图、堆、哈希表; +- **树形结构**:树、堆、哈希表,元素之间是一对多的关系。 +- **网状结构**:图,元素之间是多对多的关系。 -![classification_logic_structure](classification_of_data_structure.assets/classification_logic_structure.png) +![线性数据结构与非线性数据结构](classification_of_data_structure.assets/classification_logic_structure.png) -

Fig. 线性与非线性数据结构

+## 物理结构:连续与分散 -## 物理结构:连续与离散 +**当算法程序运行时,正在处理的数据主要存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储一定大小的数据。 -!!! note +**系统通过内存地址来访问目标位置的数据**。如下图所示,计算机根据特定规则为表格中的每个单元格分配编号,确保每个内存空间都有唯一的内存地址。有了这些地址,程序便可以访问内存中的数据。 + +![内存条、内存空间、内存地址](classification_of_data_structure.assets/computer_memory_location.png) + +!!! tip - 若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。 + 值得说明的是,将内存比作 Excel 表格是一个简化的类比,实际内存的工作机制比较复杂,涉及地址空间、内存管理、缓存机制、虚拟内存和物理内存等概念。 -**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 +内存是所有程序的共享资源,当某块内存被某个程序占用时,则通常无法被其他程序同时使用了。**因此在数据结构与算法的设计中,内存资源是一个重要的考虑因素**。比如,算法所占用的内存峰值不应超过系统剩余空闲内存;如果缺少连续大块的内存空间,那么所选用的数据结构必须能够存储在分散的内存空间内。 -![classification_phisical_structure](classification_of_data_structure.assets/classification_phisical_structure.png) +如下图所示,**物理结构反映了数据在计算机内存中的存储方式**,可分为连续空间存储(数组)和分散空间存储(链表)。物理结构从底层决定了数据的访问、更新、增删等操作方法,两种物理结构在时间效率和空间效率方面呈现出互补的特点。 -

Fig. 连续空间存储与离散空间存储

+![连续空间存储与分散空间存储](classification_of_data_structure.assets/classification_phisical_structure.png) -**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 +值得说明的是,**所有数据结构都是基于数组、链表或二者的组合实现的**。例如,栈和队列既可以使用数组实现,也可以使用链表实现;而哈希表的实现可能同时包含数组和链表。 -- **基于数组可实现**:栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等; -- **基于链表可实现**:栈、队列、堆、哈希表、树、图等; +- **基于数组可实现**:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 $\geq 3$ 的数组)等。 +- **基于链表可实现**:栈、队列、哈希表、树、堆、图等。 -基于数组实现的数据结构也被称为「静态数据结构」,这意味着该数据结构在在被初始化后,长度不可变。相反地,基于链表实现的数据结构被称为「动态数据结构」,该数据结构在被初始化后,我们也可以在程序运行中修改其长度。 +链表在初始化后,仍可以在程序运行过程中对其长度进行调整,因此也称“动态数据结构”。数组在初始化后长度不可变,因此也称“静态数据结构”。值得注意的是,数组可通过重新分配内存实现长度变化,从而具备一定的“动态性”。 !!! tip - 数组与链表是其他所有数据结构的“底层积木”,建议读者一定要多花些时间了解。 + 如果你感觉物理结构理解起来有困难,建议先阅读下一章,然后再回顾本节内容。 diff --git a/docs/chapter_data_structure/data_and_memory.assets/computer_memory_location.png b/docs/chapter_data_structure/data_and_memory.assets/computer_memory_location.png deleted file mode 100644 index 9006431919..0000000000 Binary files a/docs/chapter_data_structure/data_and_memory.assets/computer_memory_location.png and /dev/null differ diff --git a/docs/chapter_data_structure/data_and_memory.md b/docs/chapter_data_structure/data_and_memory.md deleted file mode 100644 index 93147fb652..0000000000 --- a/docs/chapter_data_structure/data_and_memory.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -comments: true ---- - -# 数据与内存 - -## 基本数据类型 - -谈到计算机中的数据,我们能够想到文本、图片、视频、语音、3D 模型等等,这些数据虽然组织形式不同,但是有一个共同点,即都是由各种基本数据类型构成的。 - -**「基本数据类型」是 CPU 可以直接进行运算的类型,在算法中直接被使用。** - -- 「整数」根据不同的长度分为 byte, short, int, long ,根据算法需求选用,即在满足取值范围的情况下尽量减小内存空间占用; -- 「浮点数」代表小数,根据长度分为 float, double ,同样根据算法的实际需求选用; -- 「字符」在计算机中是以字符集的形式保存的,char 的值实际上是数字,代表字符集中的编号,计算机通过字符集查表来完成编号到字符的转换。占用空间与具体编程语言有关,通常为 2 bytes 或 1 byte ; -- 「布尔」代表逻辑中的 ”是“ 与 ”否“ ,其占用空间需要具体根据编程语言确定,通常为 1 byte 或 1 bit ; - -!!! note "字节与比特" - - 1 字节 (byte) = 8 比特 (bit) , 1 比特即最基本的 1 个二进制位 - -

Table. Java 的基本数据类型

- -
- -| 类别 | 符号 | 占用空间 | 取值范围 | 默认值 | -| ------ | ----------- | ----------------- | ---------------------------------------------- | -------------- | -| 整数 | byte | 1 byte | $-2^7$ ~ $2^7 - 1$ ( $-128$ ~ $127$ ) | $0$ | -| | short | 2 bytes | $-2^{15}$ ~ $2^{15} - 1$ | $0$ | -| | **int** | 4 bytes | $-2^{31}$ ~ $2^{31} - 1$ | $0$ | -| | long | 8 bytes | $-2^{63}$ ~ $2^{63} - 1$ | $0$ | -| 浮点数 | **float** | 4 bytes | $-3.4 \times 10^{38}$ ~ $3.4 \times 10^{38}$ | $0.0$ f | -| | double | 8 bytes | $-1.7 \times 10^{308}$ ~ $1.7 \times 10^{308}$ | $0.0$ | -| 字符 | **char** | 2 bytes / 1 byte | $0$ ~ $2^{16} - 1$ | $0$ | -| 布尔 | **boolean(bool)** | 1 byte / 1 bit | $\text{true}$ 或 $\text{false}$ | $\text{false}$ | - -
- -!!! tip - - 以上表格中,加粗项在「算法题」中最为常用。此表格无需硬背,大致理解即可,需要时可以通过查表来回忆。 - -**「基本数据类型」与「数据结构」之间的联系与区别** - -我们知道,数据结构是在计算机中 **组织与存储数据的方式**,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**。 - -=== "Java" - - ```java title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int[] numbers = new int[5]; - float[] decimals = new float[5]; - char[] characters = new char[5]; - boolean[] booleans = new boolean[5]; - ``` - -=== "C++" - - ```cpp title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int numbers[5]; - float decimals[5]; - char characters[5]; - bool booleans[5]; - ``` - -=== "Python" - - ```python title="" - """ Python 的 list 可以自由存储各种基本数据类型和对象 """ - list = [0, 0.0, 'a', False] - ``` - -=== "Go" - - ```go title="" - // 使用多种「基本数据类型」来初始化「数组」 - var numbers = [5]int{} - var decimals = [5]float64{} - var characters = [5]byte{} - var booleans = [5]bool{} - ``` - -=== "JavaScript" - - ```js title="" - - ``` - -=== "TypeScript" - - ```typescript title="" - - ``` - -=== "C" - - ```c title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int numbers[10]; - float decimals[10]; - char characters[10]; - bool booleans[10]; - - ``` - -=== "C#" - - ```csharp title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int[] numbers = new int[5]; - float[] decimals = new float[5]; - char[] characters = new char[5]; - bool[] booleans = new bool[5]; - ``` - -=== "Swift" - - ```swift title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - let numbers = Array(repeating: Int(), count: 5) - let decimals = Array(repeating: Double(), count: 5) - let characters = Array(repeating: Character("a"), count: 5) - let booleans = Array(repeating: Bool(), count: 5) - ``` - -## 计算机内存 - -在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度更快,但容量较小(通常为 GB 级别)。 - -**算法运行中,相关数据都被存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 - -**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 - -![computer_memory_location](data_and_memory.assets/computer_memory_location.png) - -

Fig. 内存条、内存空间、内存地址

- -**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。 diff --git a/docs/chapter_data_structure/index.md b/docs/chapter_data_structure/index.md new file mode 100644 index 0000000000..fb1dced52d --- /dev/null +++ b/docs/chapter_data_structure/index.md @@ -0,0 +1,9 @@ +# 数据结构 + +![数据结构](../assets/covers/chapter_data_structure.jpg) + +!!! abstract + + 数据结构如同一副稳固而多样的框架。 + + 它为数据的有序组织提供了蓝图,算法得以在此基础上生动起来。 diff --git a/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png b/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png new file mode 100644 index 0000000000..16b7ba6efb Binary files /dev/null and b/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png differ diff --git a/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png b/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png new file mode 100644 index 0000000000..b0acc827ee Binary files /dev/null and b/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png differ diff --git a/docs/chapter_data_structure/number_encoding.md b/docs/chapter_data_structure/number_encoding.md new file mode 100644 index 0000000000..95cc5ca36f --- /dev/null +++ b/docs/chapter_data_structure/number_encoding.md @@ -0,0 +1,150 @@ +# 数字编码 * + +!!! tip + + 在本书中,标题带有 * 符号的是选读章节。如果你时间有限或感到理解困难,可以先跳过,等学完必读章节后再单独攻克。 + +## 原码、反码和补码 + +在上一节的表格中我们发现,所有整数类型能够表示的负数都比正数多一个,例如 `byte` 的取值范围是 $[-128, 127]$ 。这个现象比较反直觉,它的内在原因涉及原码、反码、补码的相关知识。 + +首先需要指出,**数字是以“补码”的形式存储在计算机中的**。在分析这样做的原因之前,首先给出三者的定义。 + +- **原码**:我们将数字的二进制表示的最高位视为符号位,其中 $0$ 表示正数,$1$ 表示负数,其余位表示数字的值。 +- **反码**:正数的反码与其原码相同,负数的反码是对其原码除符号位外的所有位取反。 +- **补码**:正数的补码与其原码相同,负数的补码是在其反码的基础上加 $1$ 。 + +下图展示了原码、反码和补码之间的转换方法。 + +![原码、反码与补码之间的相互转换](number_encoding.assets/1s_2s_complement.png) + +原码(sign-magnitude)虽然最直观,但存在一些局限性。一方面,**负数的原码不能直接用于运算**。例如在原码下计算 $1 + (-2)$ ,得到的结果是 $-3$ ,这显然是不对的。 + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline +& = 1000 \; 0011 \newline +& \rightarrow -3 +\end{aligned} +$$ + +为了解决此问题,计算机引入了反码(1's complement)。如果我们先将原码转换为反码,并在反码下计算 $1 + (-2)$ ,最后将结果从反码转换回原码,则可得到正确结果 $-1$ 。 + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 \; \text{(原码)} + 1000 \; 0010 \; \text{(原码)} \newline +& = 0000 \; 0001 \; \text{(反码)} + 1111 \; 1101 \; \text{(反码)} \newline +& = 1111 \; 1110 \; \text{(反码)} \newline +& = 1000 \; 0001 \; \text{(原码)} \newline +& \rightarrow -1 +\end{aligned} +$$ + +另一方面,**数字零的原码有 $+0$ 和 $-0$ 两种表示方式**。这意味着数字零对应两个不同的二进制编码,这可能会带来歧义。比如在条件判断中,如果没有区分正零和负零,则可能会导致判断结果出错。而如果我们想处理正零和负零歧义,则需要引入额外的判断操作,这可能会降低计算机的运算效率。 + +$$ +\begin{aligned} ++0 & \rightarrow 0000 \; 0000 \newline +-0 & \rightarrow 1000 \; 0000 +\end{aligned} +$$ + +与原码一样,反码也存在正负零歧义问题,因此计算机进一步引入了补码(2's complement)。我们先来观察一下负零的原码、反码、补码的转换过程: + +$$ +\begin{aligned} +-0 \rightarrow \; & 1000 \; 0000 \; \text{(原码)} \newline += \; & 1111 \; 1111 \; \text{(反码)} \newline += 1 \; & 0000 \; 0000 \; \text{(补码)} \newline +\end{aligned} +$$ + +在负零的反码基础上加 $1$ 会产生进位,但 `byte` 类型的长度只有 8 位,因此溢出到第 9 位的 $1$ 会被舍弃。也就是说,**负零的补码为 $0000 \; 0000$ ,与正零的补码相同**。这意味着在补码表示中只存在一个零,正负零歧义从而得到解决。 + +还剩最后一个疑惑:`byte` 类型的取值范围是 $[-128, 127]$ ,多出来的一个负数 $-128$ 是如何得到的呢?我们注意到,区间 $[-127, +127]$ 内的所有整数都有对应的原码、反码和补码,并且原码和补码之间可以互相转换。 + +然而,**补码 $1000 \; 0000$ 是一个例外,它并没有对应的原码**。根据转换方法,我们得到该补码的原码为 $0000 \; 0000$ 。这显然是矛盾的,因为该原码表示数字 $0$ ,它的补码应该是自身。计算机规定这个特殊的补码 $1000 \; 0000$ 代表 $-128$ 。实际上,$(-1) + (-127)$ 在补码下的计算结果就是 $-128$ 。 + +$$ +\begin{aligned} +& (-127) + (-1) \newline +& \rightarrow 1111 \; 1111 \; \text{(原码)} + 1000 \; 0001 \; \text{(原码)} \newline +& = 1000 \; 0000 \; \text{(反码)} + 1111 \; 1110 \; \text{(反码)} \newline +& = 1000 \; 0001 \; \text{(补码)} + 1111 \; 1111 \; \text{(补码)} \newline +& = 1000 \; 0000 \; \text{(补码)} \newline +& \rightarrow -128 +\end{aligned} +$$ + +你可能已经发现了,上述所有计算都是加法运算。这暗示着一个重要事实:**计算机内部的硬件电路主要是基于加法运算设计的**。这是因为加法运算相对于其他运算(比如乘法、除法和减法)来说,硬件实现起来更简单,更容易进行并行化处理,运算速度更快。 + +请注意,这并不意味着计算机只能做加法。**通过将加法与一些基本逻辑运算结合,计算机能够实现各种其他的数学运算**。例如,计算减法 $a - b$ 可以转换为计算加法 $a + (-b)$ ;计算乘法和除法可以转换为计算多次加法或减法。 + +现在我们可以总结出计算机使用补码的原因:基于补码表示,计算机可以用同样的电路和操作来处理正数和负数的加法,不需要设计特殊的硬件电路来处理减法,并且无须特别处理正负零的歧义问题。这大大简化了硬件设计,提高了运算效率。 + +补码的设计非常精妙,因篇幅关系我们就先介绍到这里,建议有兴趣的读者进一步深入了解。 + +## 浮点数编码 + +细心的你可能会发现:`int` 和 `float` 长度相同,都是 4 字节 ,但为什么 `float` 的取值范围远大于 `int` ?这非常反直觉,因为按理说 `float` 需要表示小数,取值范围应该变小才对。 + +实际上,**这是因为浮点数 `float` 采用了不同的表示方式**。记一个 32 比特长度的二进制数为: + +$$ +b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 +$$ + +根据 IEEE 754 标准,32-bit 长度的 `float` 由以下三个部分构成。 + +- 符号位 $\mathrm{S}$ :占 1 位 ,对应 $b_{31}$ 。 +- 指数位 $\mathrm{E}$ :占 8 位 ,对应 $b_{30} b_{29} \ldots b_{23}$ 。 +- 分数位 $\mathrm{N}$ :占 23 位 ,对应 $b_{22} b_{21} \ldots b_0$ 。 + +二进制数 `float` 对应值的计算方法为: + +$$ +\text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 +$$ + +转化到十进制下的计算公式为: + +$$ +\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) +$$ + +其中各项的取值范围为: + +$$ +\begin{aligned} +\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline +(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] +\end{aligned} +$$ + +![IEEE 754 标准下的 float 的计算示例](number_encoding.assets/ieee_754_float.png) + +观察上图,给定一个示例数据 $\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,则有: + +$$ +\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 +$$ + +现在我们可以回答最初的问题:**`float` 的表示方式包含指数位,导致其取值范围远大于 `int`** 。根据以上计算,`float` 可表示的最大正数为 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切换符号位便可得到最小负数。 + +**尽管浮点数 `float` 扩展了取值范围,但其副作用是牺牲了精度**。整数类型 `int` 将全部 32 比特用于表示数字,数字是均匀分布的;而由于指数位的存在,浮点数 `float` 的数值越大,相邻两个数字之间的差值就会趋向越大。 + +如下表所示,指数位 $\mathrm{E} = 0$ 和 $\mathrm{E} = 255$ 具有特殊含义,**用于表示零、无穷大、$\mathrm{NaN}$ 等**。 + +

  指数位含义

+ +| 指数位 E | 分数位 $\mathrm{N} = 0$ | 分数位 $\mathrm{N} \ne 0$ | 计算公式 | +| ------------------ | ----------------------- | ------------------------- | ---------------------------------------------------------------------- | +| $0$ | $\pm 0$ | 次正规数 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | +| $1, 2, \dots, 254$ | 正规数 | 正规数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | +| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | + +值得说明的是,次正规数显著提升了浮点数的精度。最小正正规数为 $2^{-126}$ ,最小正次正规数为 $2^{-126} \times 2^{-23}$ 。 + +双精度 `double` 也采用类似于 `float` 的表示方法,在此不做赘述。 diff --git a/docs/chapter_data_structure/summary.md b/docs/chapter_data_structure/summary.md index e51e4dc5bc..54af466001 100644 --- a/docs/chapter_data_structure/summary.md +++ b/docs/chapter_data_structure/summary.md @@ -1,11 +1,66 @@ ---- -comments: true ---- - # 小结 -- 整数 byte, short, int, long 、浮点数 float, double 、字符 char 、布尔 boolean 是计算机中的基本数据类型,占用空间的大小决定了它们的取值范围。 -- 在程序运行时,数据存储在计算机的内存中。内存中每块空间都有独立的内存地址,程序是通过内存地址来访问数据的。 -- 数据结构主要可以从逻辑结构和物理结构两个角度进行分类。逻辑结构反映了数据中元素之间的逻辑关系,物理结构反映了数据在计算机内存中的存储形式。 -- 常见的逻辑结构有线性、树状、网状等。我们一般根据逻辑结构将数据结构分为线性(数组、链表、栈、队列)和非线性(树、图、堆)两种。根据实现方式的不同,哈希表可能是线性或非线性。 -- 物理结构主要有两种,分别是连续空间存储(数组)和离散空间存储(链表),所有的数据结构都是由数组、或链表、或两者组合实现的。 +### 重点回顾 + +- 数据结构可以从逻辑结构和物理结构两个角度进行分类。逻辑结构描述了数据元素之间的逻辑关系,而物理结构描述了数据在计算机内存中的存储方式。 +- 常见的逻辑结构包括线性、树状和网状等。通常我们根据逻辑结构将数据结构分为线性(数组、链表、栈、队列)和非线性(树、图、堆)两种。哈希表的实现可能同时包含线性数据结构和非线性数据结构。 +- 当程序运行时,数据被存储在计算机内存中。每个内存空间都拥有对应的内存地址,程序通过这些内存地址访问数据。 +- 物理结构主要分为连续空间存储(数组)和分散空间存储(链表)。所有数据结构都是由数组、链表或两者的组合实现的。 +- 计算机中的基本数据类型包括整数 `byte`、`short`、`int`、`long` ,浮点数 `float`、`double` ,字符 `char` 和布尔 `bool` 。它们的取值范围取决于占用空间大小和表示方式。 +- 原码、反码和补码是在计算机中编码数字的三种方法,它们之间可以相互转换。整数的原码的最高位是符号位,其余位是数字的值。 +- 整数在计算机中是以补码的形式存储的。在补码表示下,计算机可以对正数和负数的加法一视同仁,不需要为减法操作单独设计特殊的硬件电路,并且不存在正负零歧义的问题。 +- 浮点数的编码由 1 位符号位、8 位指数位和 23 位分数位构成。由于存在指数位,因此浮点数的取值范围远大于整数,代价是牺牲了精度。 +- ASCII 码是最早出现的英文字符集,长度为 1 字节,共收录 127 个字符。GBK 字符集是常用的中文字符集,共收录两万多个汉字。Unicode 致力于提供一个完整的字符集标准,收录世界上各种语言的字符,从而解决由于字符编码方法不一致而导致的乱码问题。 +- UTF-8 是最受欢迎的 Unicode 编码方法,通用性非常好。它是一种变长的编码方法,具有很好的扩展性,有效提升了存储空间的使用效率。UTF-16 和 UTF-32 是等长的编码方法。在编码中文时,UTF-16 占用的空间比 UTF-8 更小。Java 和 C# 等编程语言默认使用 UTF-16 编码。 + +### Q & A + +**Q**:为什么哈希表同时包含线性数据结构和非线性数据结构? + +哈希表底层是数组,而为了解决哈希冲突,我们可能会使用“链式地址”(后续“哈希冲突”章节会讲):数组中每个桶指向一个链表,当链表长度超过一定阈值时,又可能被转化为树(通常为红黑树)。 + +从存储的角度来看,哈希表的底层是数组,其中每一个桶槽位可能包含一个值,也可能包含一个链表或一棵树。因此,哈希表可能同时包含线性数据结构(数组、链表)和非线性数据结构(树)。 + +**Q**:`char` 类型的长度是 1 字节吗? + +`char` 类型的长度由编程语言采用的编码方法决定。例如,Java、JavaScript、TypeScript、C# 都采用 UTF-16 编码(保存 Unicode 码点),因此 `char` 类型的长度为 2 字节。 + +**Q**:基于数组实现的数据结构也称“静态数据结构” 是否有歧义?栈也可以进行出栈和入栈等操作,这些操作都是“动态”的。 + +栈确实可以实现动态的数据操作,但数据结构仍然是“静态”(长度不可变)的。尽管基于数组的数据结构可以动态地添加或删除元素,但它们的容量是固定的。如果数据量超出了预分配的大小,就需要创建一个新的更大的数组,并将旧数组的内容复制到新数组中。 + +**Q**:在构建栈(队列)的时候,未指定它的大小,为什么它们是“静态数据结构”呢? + +在高级编程语言中,我们无须人工指定栈(队列)的初始容量,这个工作由类内部自动完成。例如,Java 的 `ArrayList` 的初始容量通常为 10。另外,扩容操作也是自动实现的。详见后续的“列表”章节。 + +**Q**:原码转补码的方法是“先取反后加 1”,那么补码转原码应该是逆运算“先减 1 后取反”,而补码转原码也一样可以通过“先取反后加 1”得到,这是为什么呢? + +这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。 + +给定一个 $n = 4$ 位长度的二进制数 $0010$ ,如果将这个数字看作原码(不考虑符号位),那么它的补码需通过“先取反后加 1”得到: + +$$ +0010 \rightarrow 1101 \rightarrow 1110 +$$ + +我们会发现,原码和补码的和是 $0010 + 1110 = 10000$ ,也就是说,补码 $1110$ 是原码 $0010$ 到 $10000$ 的“补数”。**这意味着上述“先取反后加 1”实际上是计算到 $10000$ 的补数的过程**。 + +那么,补码 $1110$ 到 $10000$ 的“补数”是多少呢?我们依然可以用“先取反后加 1”得到它: + +$$ +1110 \rightarrow 0001 \rightarrow 0010 +$$ + +换句话说,原码和补码互为对方到 $10000$ 的“补数”,因此“原码转补码”和“补码转原码”可以用相同的操作(先取反后加 1 )实现。 + +当然,我们也可以用逆运算来求补码 $1110$ 的原码,即“先减 1 后取反”: + +$$ +1110 \rightarrow 1101 \rightarrow 0010 +$$ + +总结来看,“先取反后加 1”和“先减 1 后取反”这两种运算都是在计算到 $10000$ 的补数,它们是等价的。 + +本质上看,“取反”操作实际上是求到 $1111$ 的补数(因为恒有 `原码 + 反码 = 1111`);而在反码基础上再加 1 得到的补码,就是到 $10000$ 的补数。 + +上述以 $n = 4$ 为例,其可被推广至任意位数的二进制数。 diff --git a/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png b/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png new file mode 100644 index 0000000000..7811a4ce83 Binary files /dev/null and b/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png differ diff --git a/docs/chapter_divide_and_conquer/binary_search_recur.md b/docs/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 0000000000..d15895828a --- /dev/null +++ b/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,45 @@ +# 分治搜索策略 + +我们已经学过,搜索算法分为两大类。 + +- **暴力搜索**:它通过遍历数据结构实现,时间复杂度为 $O(n)$ 。 +- **自适应搜索**:它利用特有的数据组织形式或先验信息,时间复杂度可达到 $O(\log n)$ 甚至 $O(1)$ 。 + +实际上,**时间复杂度为 $O(\log n)$ 的搜索算法通常是基于分治策略实现的**,例如二分查找和树。 + +- 二分查找的每一步都将问题(在数组中搜索目标元素)分解为一个小问题(在数组的一半中搜索目标元素),这个过程一直持续到数组为空或找到目标元素为止。 +- 树是分治思想的代表,在二叉搜索树、AVL 树、堆等数据结构中,各种操作的时间复杂度皆为 $O(\log n)$ 。 + +二分查找的分治策略如下所示。 + +- **问题可以分解**:二分查找递归地将原问题(在数组中进行查找)分解为子问题(在数组的一半中进行查找),这是通过比较中间元素和目标元素来实现的。 +- **子问题是独立的**:在二分查找中,每轮只处理一个子问题,它不受其他子问题的影响。 +- **子问题的解无须合并**:二分查找旨在查找一个特定元素,因此不需要将子问题的解进行合并。当子问题得到解决时,原问题也会同时得到解决。 + +分治能够提升搜索效率,本质上是因为暴力搜索每轮只能排除一个选项,**而分治搜索每轮可以排除一半选项**。 + +### 基于分治实现二分查找 + +在之前的章节中,二分查找是基于递推(迭代)实现的。现在我们基于分治(递归)来实现它。 + +!!! question + + 给定一个长度为 $n$ 的有序数组 `nums` ,其中所有元素都是唯一的,请查找元素 `target` 。 + +从分治角度,我们将搜索区间 $[i, j]$ 对应的子问题记为 $f(i, j)$ 。 + +以原问题 $f(0, n-1)$ 为起始点,通过以下步骤进行二分查找。 + +1. 计算搜索区间 $[i, j]$ 的中点 $m$ ,根据它排除一半搜索区间。 +2. 递归求解规模减小一半的子问题,可能为 $f(i, m-1)$ 或 $f(m+1, j)$ 。 +3. 循环第 `1.` 步和第 `2.` 步,直至找到 `target` 或区间为空时返回。 + +下图展示了在数组中二分查找元素 $6$ 的分治过程。 + +![二分查找的分治过程](binary_search_recur.assets/binary_search_recur.png) + +在实现代码中,我们声明一个递归函数 `dfs()` 来求解问题 $f(i, j)$ : + +```src +[file]{binary_search_recur}-[class]{}-[func]{binary_search} +``` diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png new file mode 100644 index 0000000000..51f02fa610 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png new file mode 100644 index 0000000000..0ac06bb5a1 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png new file mode 100644 index 0000000000..427cca531f Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png new file mode 100644 index 0000000000..d68b38b075 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png new file mode 100644 index 0000000000..61f1ef74c2 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png new file mode 100644 index 0000000000..23dfb8b881 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png new file mode 100644 index 0000000000..73f650aa6c Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png new file mode 100644 index 0000000000..fb5db20506 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png new file mode 100644 index 0000000000..c3f55b54a7 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png new file mode 100644 index 0000000000..f69ede0a6a Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png new file mode 100644 index 0000000000..ee72272ae3 Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png new file mode 100644 index 0000000000..8a180a23dd Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png new file mode 100644 index 0000000000..923dd9036d Binary files /dev/null and b/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png differ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md new file mode 100644 index 0000000000..62e06eca7f --- /dev/null +++ b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -0,0 +1,99 @@ +# 构建二叉树问题 + +!!! question + + 给定一棵二叉树的前序遍历 `preorder` 和中序遍历 `inorder` ,请从中构建二叉树,返回二叉树的根节点。假设二叉树中没有值重复的节点(如下图所示)。 + +![构建二叉树的示例数据](build_binary_tree_problem.assets/build_tree_example.png) + +### 判断是否为分治问题 + +原问题定义为从 `preorder` 和 `inorder` 构建二叉树,是一个典型的分治问题。 + +- **问题可以分解**:从分治的角度切入,我们可以将原问题划分为两个子问题:构建左子树、构建右子树,加上一步操作:初始化根节点。而对于每棵子树(子问题),我们仍然可以复用以上划分方法,将其划分为更小的子树(子问题),直至达到最小子问题(空子树)时终止。 +- **子问题是独立的**:左子树和右子树是相互独立的,它们之间没有交集。在构建左子树时,我们只需关注中序遍历和前序遍历中与左子树对应的部分。右子树同理。 +- **子问题的解可以合并**:一旦得到了左子树和右子树(子问题的解),我们就可以将它们链接到根节点上,得到原问题的解。 + +### 如何划分子树 + +根据以上分析,这道题可以使用分治来求解,**但如何通过前序遍历 `preorder` 和中序遍历 `inorder` 来划分左子树和右子树呢**? + +根据定义,`preorder` 和 `inorder` 都可以划分为三个部分。 + +- 前序遍历:`[ 根节点 | 左子树 | 右子树 ]` ,例如上图的树对应 `[ 3 | 9 | 2 1 7 ]` 。 +- 中序遍历:`[ 左子树 | 根节点 | 右子树 ]` ,例如上图的树对应 `[ 9 | 3 | 1 2 7 ]` 。 + +以上图数据为例,我们可以通过下图所示的步骤得到划分结果。 + +1. 前序遍历的首元素 3 是根节点的值。 +2. 查找根节点 3 在 `inorder` 中的索引,利用该索引可将 `inorder` 划分为 `[ 9 | 3 | 1 2 7 ]` 。 +3. 根据 `inorder` 的划分结果,易得左子树和右子树的节点数量分别为 1 和 3 ,从而可将 `preorder` 划分为 `[ 3 | 9 | 2 1 7 ]` 。 + +![在前序遍历和中序遍历中划分子树](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) + +### 基于变量描述子树区间 + +根据以上划分方法,**我们已经得到根节点、左子树、右子树在 `preorder` 和 `inorder` 中的索引区间**。而为了描述这些索引区间,我们需要借助几个指针变量。 + +- 将当前树的根节点在 `preorder` 中的索引记为 $i$ 。 +- 将当前树的根节点在 `inorder` 中的索引记为 $m$ 。 +- 将当前树在 `inorder` 中的索引区间记为 $[l, r]$ 。 + +如下表所示,通过以上变量即可表示根节点在 `preorder` 中的索引,以及子树在 `inorder` 中的索引区间。 + +

  根节点和子树在前序遍历和中序遍历中的索引

+ +| | 根节点在 `preorder` 中的索引 | 子树在 `inorder` 中的索引区间 | +| ------ | ---------------------------- | ----------------------------- | +| 当前树 | $i$ | $[l, r]$ | +| 左子树 | $i + 1$ | $[l, m-1]$ | +| 右子树 | $i + 1 + (m - l)$ | $[m+1, r]$ | + +请注意,右子树根节点索引中的 $(m-l)$ 的含义是“左子树的节点数量”,建议结合下图理解。 + +![根节点和左右子树的索引区间表示](build_binary_tree_problem.assets/build_tree_division_pointers.png) + +### 代码实现 + +为了提升查询 $m$ 的效率,我们借助一个哈希表 `hmap` 来存储数组 `inorder` 中元素到索引的映射: + +```src +[file]{build_tree}-[class]{}-[func]{build_tree} +``` + +下图展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(引用)是在向上“归”的过程中建立的。 + +=== "<1>" + ![构建二叉树的递归过程](build_binary_tree_problem.assets/built_tree_step1.png) + +=== "<2>" + ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) + +=== "<3>" + ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) + +=== "<4>" + ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) + +=== "<5>" + ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) + +=== "<6>" + ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) + +=== "<7>" + ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) + +=== "<8>" + ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) + +=== "<9>" + ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) + +每个递归函数内的前序遍历 `preorder` 和中序遍历 `inorder` 的划分结果如下图所示。 + +![每个递归函数中的划分结果](build_binary_tree_problem.assets/built_tree_overall.png) + +设树的节点数量为 $n$ ,初始化每一个节点(执行一个递归函数 `dfs()` )使用 $O(1)$ 时间。**因此总体时间复杂度为 $O(n)$** 。 + +哈希表存储 `inorder` 元素到索引的映射,空间复杂度为 $O(n)$ 。在最差情况下,即二叉树退化为链表时,递归深度达到 $n$ ,使用 $O(n)$ 的栈帧空间。**因此总体空间复杂度为 $O(n)$** 。 diff --git a/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png b/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png new file mode 100644 index 0000000000..261ccf21bc Binary files /dev/null and b/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png differ diff --git a/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png b/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png new file mode 100644 index 0000000000..3ea96e934d Binary files /dev/null and b/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png differ diff --git a/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png b/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png new file mode 100644 index 0000000000..4abbb183d4 Binary files /dev/null and b/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png differ diff --git a/docs/chapter_divide_and_conquer/divide_and_conquer.md b/docs/chapter_divide_and_conquer/divide_and_conquer.md new file mode 100644 index 0000000000..ab030c073e --- /dev/null +++ b/docs/chapter_divide_and_conquer/divide_and_conquer.md @@ -0,0 +1,91 @@ +# 分治算法 + +分治(divide and conquer),全称分而治之,是一种非常重要且常见的算法策略。分治通常基于递归实现,包括“分”和“治”两个步骤。 + +1. **分(划分阶段)**:递归地将原问题分解为两个或多个子问题,直至到达最小子问题时终止。 +2. **治(合并阶段)**:从已知解的最小子问题开始,从底至顶地将子问题的解进行合并,从而构建出原问题的解。 + +如下图所示,“归并排序”是分治策略的典型应用之一。 + +1. **分**:递归地将原数组(原问题)划分为两个子数组(子问题),直到子数组只剩一个元素(最小子问题)。 +2. **治**:从底至顶地将有序的子数组(子问题的解)进行合并,从而得到有序的原数组(原问题的解)。 + +![归并排序的分治策略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) + +## 如何判断分治问题 + +一个问题是否适合使用分治解决,通常可以参考以下几个判断依据。 + +1. **问题可以分解**:原问题可以分解成规模更小、类似的子问题,以及能够以相同方式递归地进行划分。 +2. **子问题是独立的**:子问题之间没有重叠,互不依赖,可以独立解决。 +3. **子问题的解可以合并**:原问题的解通过合并子问题的解得来。 + +显然,归并排序满足以上三个判断依据。 + +1. **问题可以分解**:递归地将数组(原问题)划分为两个子数组(子问题)。 +2. **子问题是独立的**:每个子数组都可以独立地进行排序(子问题可以独立进行求解)。 +3. **子问题的解可以合并**:两个有序子数组(子问题的解)可以合并为一个有序数组(原问题的解)。 + +## 通过分治提升效率 + +**分治不仅可以有效地解决算法问题,往往还可以提升算法效率**。在排序算法中,快速排序、归并排序、堆排序相较于选择、冒泡、插入排序更快,就是因为它们应用了分治策略。 + +那么,我们不禁发问:**为什么分治可以提升算法效率,其底层逻辑是什么**?换句话说,将大问题分解为多个子问题、解决子问题、将子问题的解合并为原问题的解,这几步的效率为什么比直接解决原问题的效率更高?这个问题可以从操作数量和并行计算两方面来讨论。 + +### 操作数量优化 + +以“冒泡排序”为例,其处理一个长度为 $n$ 的数组需要 $O(n^2)$ 时间。假设我们按照下图所示的方式,将数组从中点处分为两个子数组,则划分需要 $O(n)$ 时间,排序每个子数组需要 $O((n / 2)^2)$ 时间,合并两个子数组需要 $O(n)$ 时间,总体时间复杂度为: + +$$ +O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) +$$ + +![划分数组前后的冒泡排序](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) + +接下来,我们计算以下不等式,其左边和右边分别为划分前和划分后的操作总数: + +$$ +\begin{aligned} +n^2 & > \frac{n^2}{2} + 2n \newline +n^2 - \frac{n^2}{2} - 2n & > 0 \newline +n(n - 4) & > 0 +\end{aligned} +$$ + +**这意味着当 $n > 4$ 时,划分后的操作数量更少,排序效率应该更高**。请注意,划分后的时间复杂度仍然是平方阶 $O(n^2)$ ,只是复杂度中的常数项变小了。 + +进一步想,**如果我们把子数组不断地再从中点处划分为两个子数组**,直至子数组只剩一个元素时停止划分呢?这种思路实际上就是“归并排序”,时间复杂度为 $O(n \log n)$ 。 + +再思考,**如果我们多设置几个划分点**,将原数组平均划分为 $k$ 个子数组呢?这种情况与“桶排序”非常类似,它非常适合排序海量数据,理论上时间复杂度可以达到 $O(n + k)$ 。 + +### 并行计算优化 + +我们知道,分治生成的子问题是相互独立的,**因此通常可以并行解决**。也就是说,分治不仅可以降低算法的时间复杂度,**还有利于操作系统的并行优化**。 + +并行优化在多核或多处理器的环境中尤其有效,因为系统可以同时处理多个子问题,更加充分地利用计算资源,从而显著减少总体的运行时间。 + +比如在下图所示的“桶排序”中,我们将海量的数据平均分配到各个桶中,则可将所有桶的排序任务分散到各个计算单元,完成后再合并结果。 + +![桶排序的并行计算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) + +## 分治常见应用 + +一方面,分治可以用来解决许多经典算法问题。 + +- **寻找最近点对**:该算法首先将点集分成两部分,然后分别找出两部分中的最近点对,最后找出跨越两部分的最近点对。 +- **大整数乘法**:例如 Karatsuba 算法,它将大整数乘法分解为几个较小的整数的乘法和加法。 +- **矩阵乘法**:例如 Strassen 算法,它将大矩阵乘法分解为多个小矩阵的乘法和加法。 +- **汉诺塔问题**:汉诺塔问题可以通过递归解决,这是典型的分治策略应用。 +- **求解逆序对**:在一个序列中,如果前面的数字大于后面的数字,那么这两个数字构成一个逆序对。求解逆序对问题可以利用分治的思想,借助归并排序进行求解。 + +另一方面,分治在算法和数据结构的设计中应用得非常广泛。 + +- **二分查找**:二分查找是将有序数组从中点索引处分为两部分,然后根据目标值与中间元素值比较结果,决定排除哪一半区间,并在剩余区间执行相同的二分操作。 +- **归并排序**:本节开头已介绍,不再赘述。 +- **快速排序**:快速排序是选取一个基准值,然后把数组分为两个子数组,一个子数组的元素比基准值小,另一子数组的元素比基准值大,再对这两部分进行相同的划分操作,直至子数组只剩下一个元素。 +- **桶排序**:桶排序的基本思想是将数据分散到多个桶,然后对每个桶内的元素进行排序,最后将各个桶的元素依次取出,从而得到一个有序数组。 +- **树**:例如二叉搜索树、AVL 树、红黑树、B 树、B+ 树等,它们的查找、插入和删除等操作都可以视为分治策略的应用。 +- **堆**:堆是一种特殊的完全二叉树,其各种操作,如插入、删除和堆化,实际上都隐含了分治的思想。 +- **哈希表**:虽然哈希表并不直接应用分治,但某些哈希冲突解决方案间接应用了分治策略,例如,链式地址中的长链表会被转化为红黑树,以提升查询效率。 + +可以看出,**分治是一种“润物细无声”的算法思想**,隐含在各种算法与数据结构之中。 diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png new file mode 100644 index 0000000000..aeab11a065 Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png new file mode 100644 index 0000000000..c45d0e5c5c Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png new file mode 100644 index 0000000000..e322e98108 Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png new file mode 100644 index 0000000000..852c897d72 Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png new file mode 100644 index 0000000000..81ef236f1d Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png new file mode 100644 index 0000000000..390eeca8be Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png new file mode 100644 index 0000000000..9012a14d72 Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png new file mode 100644 index 0000000000..ec75fde61a Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png new file mode 100644 index 0000000000..e0fd3f5201 Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png new file mode 100644 index 0000000000..c3e2a41309 Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png new file mode 100644 index 0000000000..d3b2e1dfc0 Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png new file mode 100644 index 0000000000..95af9ff43a Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png new file mode 100644 index 0000000000..cebed6b1ce Binary files /dev/null and b/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png differ diff --git a/docs/chapter_divide_and_conquer/hanota_problem.md b/docs/chapter_divide_and_conquer/hanota_problem.md new file mode 100644 index 0000000000..f892dd770b --- /dev/null +++ b/docs/chapter_divide_and_conquer/hanota_problem.md @@ -0,0 +1,97 @@ +# 汉诺塔问题 + +在归并排序和构建二叉树中,我们都是将原问题分解为两个规模为原问题一半的子问题。然而对于汉诺塔问题,我们采用不同的分解策略。 + +!!! question + + 给定三根柱子,记为 `A`、`B` 和 `C` 。起始状态下,柱子 `A` 上套着 $n$ 个圆盘,它们从上到下按照从小到大的顺序排列。我们的任务是要把这 $n$ 个圆盘移到柱子 `C` 上,并保持它们的原有顺序不变(如下图所示)。在移动圆盘的过程中,需要遵守以下规则。 + + 1. 圆盘只能从一根柱子顶部拿出,从另一根柱子顶部放入。 + 2. 每次只能移动一个圆盘。 + 3. 小圆盘必须时刻位于大圆盘之上。 + +![汉诺塔问题示例](hanota_problem.assets/hanota_example.png) + +**我们将规模为 $i$ 的汉诺塔问题记作 $f(i)$** 。例如 $f(3)$ 代表将 $3$ 个圆盘从 `A` 移动至 `C` 的汉诺塔问题。 + +### 考虑基本情况 + +如下图所示,对于问题 $f(1)$ ,即当只有一个圆盘时,我们将它直接从 `A` 移动至 `C` 即可。 + +=== "<1>" + ![规模为 1 的问题的解](hanota_problem.assets/hanota_f1_step1.png) + +=== "<2>" + ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) + +如下图所示,对于问题 $f(2)$ ,即当有两个圆盘时,**由于要时刻满足小圆盘在大圆盘之上,因此需要借助 `B` 来完成移动**。 + +1. 先将上面的小圆盘从 `A` 移至 `B` 。 +2. 再将大圆盘从 `A` 移至 `C` 。 +3. 最后将小圆盘从 `B` 移至 `C` 。 + +=== "<1>" + ![规模为 2 的问题的解](hanota_problem.assets/hanota_f2_step1.png) + +=== "<2>" + ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) + +=== "<3>" + ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) + +=== "<4>" + ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) + +解决问题 $f(2)$ 的过程可总结为:**将两个圆盘借助 `B` 从 `A` 移至 `C`** 。其中,`C` 称为目标柱、`B` 称为缓冲柱。 + +### 子问题分解 + +对于问题 $f(3)$ ,即当有三个圆盘时,情况变得稍微复杂了一些。 + +因为已知 $f(1)$ 和 $f(2)$ 的解,所以我们可从分治角度思考,**将 `A` 顶部的两个圆盘看作一个整体**,执行下图所示的步骤。这样三个圆盘就被顺利地从 `A` 移至 `C` 了。 + +1. 令 `B` 为目标柱、`C` 为缓冲柱,将两个圆盘从 `A` 移至 `B` 。 +2. 将 `A` 中剩余的一个圆盘从 `A` 直接移动至 `C` 。 +3. 令 `C` 为目标柱、`A` 为缓冲柱,将两个圆盘从 `B` 移至 `C` 。 + +=== "<1>" + ![规模为 3 的问题的解](hanota_problem.assets/hanota_f3_step1.png) + +=== "<2>" + ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) + +=== "<3>" + ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) + +=== "<4>" + ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) + +从本质上看,**我们将问题 $f(3)$ 划分为两个子问题 $f(2)$ 和一个子问题 $f(1)$** 。按顺序解决这三个子问题之后,原问题随之得到解决。这说明子问题是独立的,而且解可以合并。 + +至此,我们可总结出下图所示的解决汉诺塔问题的分治策略:将原问题 $f(n)$ 划分为两个子问题 $f(n-1)$ 和一个子问题 $f(1)$ ,并按照以下顺序解决这三个子问题。 + +1. 将 $n-1$ 个圆盘借助 `C` 从 `A` 移至 `B` 。 +2. 将剩余 $1$ 个圆盘从 `A` 直接移至 `C` 。 +3. 将 $n-1$ 个圆盘借助 `A` 从 `B` 移至 `C` 。 + +对于这两个子问题 $f(n-1)$ ,**可以通过相同的方式进行递归划分**,直至达到最小子问题 $f(1)$ 。而 $f(1)$ 的解是已知的,只需一次移动操作即可。 + +![解决汉诺塔问题的分治策略](hanota_problem.assets/hanota_divide_and_conquer.png) + +### 代码实现 + +在代码中,我们声明一个递归函数 `dfs(i, src, buf, tar)` ,它的作用是将柱 `src` 顶部的 $i$ 个圆盘借助缓冲柱 `buf` 移动至目标柱 `tar` : + +```src +[file]{hanota}-[class]{}-[func]{solve_hanota} +``` + +如下图所示,汉诺塔问题形成一棵高度为 $n$ 的递归树,每个节点代表一个子问题,对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。 + +![汉诺塔问题的递归树](hanota_problem.assets/hanota_recursive_tree.png) + +!!! quote + + 汉诺塔问题源自一个古老的传说。在古印度的一个寺庙里,僧侣们有三根高大的钻石柱子,以及 $64$ 个大小不一的金圆盘。僧侣们不断地移动圆盘,他们相信在最后一个圆盘被正确放置的那一刻,这个世界就会结束。 + + 然而,即使僧侣们每秒钟移动一次,总共需要大约 $2^{64} \approx 1.84×10^{19}$ 秒,合约 $5850$ 亿年,远远超过了现在对宇宙年龄的估计。所以,倘若这个传说是真的,我们应该不需要担心世界末日的到来。 diff --git a/docs/chapter_divide_and_conquer/index.md b/docs/chapter_divide_and_conquer/index.md new file mode 100644 index 0000000000..5528a7592e --- /dev/null +++ b/docs/chapter_divide_and_conquer/index.md @@ -0,0 +1,9 @@ +# 分治 + +![分治](../assets/covers/chapter_divide_and_conquer.jpg) + +!!! abstract + + 难题被逐层拆解,每一次的拆解都使它变得更为简单。 + + 分而治之揭示了一个重要的事实:从简单做起,一切都不再复杂。 diff --git a/docs/chapter_divide_and_conquer/summary.md b/docs/chapter_divide_and_conquer/summary.md new file mode 100644 index 0000000000..c27ecefd5c --- /dev/null +++ b/docs/chapter_divide_and_conquer/summary.md @@ -0,0 +1,11 @@ +# 小结 + +- 分治是一种常见的算法设计策略,包括分(划分)和治(合并)两个阶段,通常基于递归实现。 +- 判断是否是分治算法问题的依据包括:问题能否分解、子问题是否独立、子问题能否合并。 +- 归并排序是分治策略的典型应用,其递归地将数组划分为等长的两个子数组,直到只剩一个元素时开始逐层合并,从而完成排序。 +- 引入分治策略往往可以提升算法效率。一方面,分治策略减少了操作数量;另一方面,分治后有利于系统的并行优化。 +- 分治既可以解决许多算法问题,也广泛应用于数据结构与算法设计中,处处可见其身影。 +- 相较于暴力搜索,自适应搜索效率更高。时间复杂度为 $O(\log n)$ 的搜索算法通常是基于分治策略实现的。 +- 二分查找是分治策略的另一个典型应用,它不包含将子问题的解进行合并的步骤。我们可以通过递归分治实现二分查找。 +- 在构建二叉树的问题中,构建树(原问题)可以划分为构建左子树和右子树(子问题),这可以通过划分前序遍历和中序遍历的索引区间来实现。 +- 在汉诺塔问题中,一个规模为 $n$ 的问题可以划分为两个规模为 $n-1$ 的子问题和一个规模为 $1$ 的子问题。按顺序解决这三个子问题后,原问题随之得到解决。 diff --git a/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png b/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png new file mode 100644 index 0000000000..28e050093e Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png differ diff --git a/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png b/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png new file mode 100644 index 0000000000..ed1bf07380 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png differ diff --git a/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png b/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png new file mode 100644 index 0000000000..3ef66958b1 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png differ diff --git a/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png b/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png new file mode 100644 index 0000000000..033caa8e80 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png differ diff --git a/docs/chapter_dynamic_programming/dp_problem_features.md b/docs/chapter_dynamic_programming/dp_problem_features.md new file mode 100644 index 0000000000..e75f2f7f6a --- /dev/null +++ b/docs/chapter_dynamic_programming/dp_problem_features.md @@ -0,0 +1,101 @@ +# 动态规划问题特性 + +在上一节中,我们学习了动态规划是如何通过子问题分解来求解原问题的。实际上,子问题分解是一种通用的算法思路,在分治、动态规划、回溯中的侧重点不同。 + +- 分治算法递归地将原问题划分为多个相互独立的子问题,直至最小子问题,并在回溯中合并子问题的解,最终得到原问题的解。 +- 动态规划也对问题进行递归分解,但与分治算法的主要区别是,动态规划中的子问题是相互依赖的,在分解过程中会出现许多重叠子问题。 +- 回溯算法在尝试和回退中穷举所有可能的解,并通过剪枝避免不必要的搜索分支。原问题的解由一系列决策步骤构成,我们可以将每个决策步骤之前的子序列看作一个子问题。 + +实际上,动态规划常用来求解最优化问题,它们不仅包含重叠子问题,还具有另外两大特性:最优子结构、无后效性。 + +## 最优子结构 + +我们对爬楼梯问题稍作改动,使之更加适合展示最优子结构概念。 + +!!! question "爬楼梯最小代价" + + 给定一个楼梯,你每步可以上 $1$ 阶或者 $2$ 阶,每一阶楼梯上都贴有一个非负整数,表示你在该台阶所需要付出的代价。给定一个非负整数数组 $cost$ ,其中 $cost[i]$ 表示在第 $i$ 个台阶需要付出的代价,$cost[0]$ 为地面(起始点)。请计算最少需要付出多少代价才能到达顶部? + +如下图所示,若第 $1$、$2$、$3$ 阶的代价分别为 $1$、$10$、$1$ ,则从地面爬到第 $3$ 阶的最小代价为 $2$ 。 + +![爬到第 3 阶的最小代价](dp_problem_features.assets/min_cost_cs_example.png) + +设 $dp[i]$ 为爬到第 $i$ 阶累计付出的代价,由于第 $i$ 阶只可能从 $i - 1$ 阶或 $i - 2$ 阶走来,因此 $dp[i]$ 只可能等于 $dp[i - 1] + cost[i]$ 或 $dp[i - 2] + cost[i]$ 。为了尽可能减少代价,我们应该选择两者中较小的那一个: + +$$ +dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] +$$ + +这便可以引出最优子结构的含义:**原问题的最优解是从子问题的最优解构建得来的**。 + +本题显然具有最优子结构:我们从两个子问题最优解 $dp[i-1]$ 和 $dp[i-2]$ 中挑选出较优的那一个,并用它构建出原问题 $dp[i]$ 的最优解。 + +那么,上一节的爬楼梯题目有没有最优子结构呢?它的目标是求解方案数量,看似是一个计数问题,但如果换一种问法:“求解最大方案数量”。我们意外地发现,**虽然题目修改前后是等价的,但最优子结构浮现出来了**:第 $n$ 阶最大方案数量等于第 $n-1$ 阶和第 $n-2$ 阶最大方案数量之和。所以说,最优子结构的解释方式比较灵活,在不同问题中会有不同的含义。 + +根据状态转移方程,以及初始状态 $dp[1] = cost[1]$ 和 $dp[2] = cost[2]$ ,我们就可以得到动态规划代码: + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} +``` + +下图展示了以上代码的动态规划过程。 + +![爬楼梯最小代价的动态规划过程](dp_problem_features.assets/min_cost_cs_dp.png) + +本题也可以进行空间优化,将一维压缩至零维,使得空间复杂度从 $O(n)$ 降至 $O(1)$ : + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} +``` + +## 无后效性 + +无后效性是动态规划能够有效解决问题的重要特性之一,其定义为:**给定一个确定的状态,它的未来发展只与当前状态有关,而与过去经历的所有状态无关**。 + +以爬楼梯问题为例,给定状态 $i$ ,它会发展出状态 $i+1$ 和状态 $i+2$ ,分别对应跳 $1$ 步和跳 $2$ 步。在做出这两种选择时,我们无须考虑状态 $i$ 之前的状态,它们对状态 $i$ 的未来没有影响。 + +然而,如果我们给爬楼梯问题添加一个约束,情况就不一样了。 + +!!! question "带约束爬楼梯" + + 给定一个共有 $n$ 阶的楼梯,你每步可以上 $1$ 阶或者 $2$ 阶,**但不能连续两轮跳 $1$ 阶**,请问有多少种方案可以爬到楼顶? + +如下图所示,爬上第 $3$ 阶仅剩 $2$ 种可行方案,其中连续三次跳 $1$ 阶的方案不满足约束条件,因此被舍弃。 + +![带约束爬到第 3 阶的方案数量](dp_problem_features.assets/climbing_stairs_constraint_example.png) + +在该问题中,如果上一轮是跳 $1$ 阶上来的,那么下一轮就必须跳 $2$ 阶。这意味着,**下一步选择不能由当前状态(当前所在楼梯阶数)独立决定,还和前一个状态(上一轮所在楼梯阶数)有关**。 + +不难发现,此问题已不满足无后效性,状态转移方程 $dp[i] = dp[i-1] + dp[i-2]$ 也失效了,因为 $dp[i-1]$ 代表本轮跳 $1$ 阶,但其中包含了许多“上一轮是跳 $1$ 阶上来的”方案,而为了满足约束,我们就不能将 $dp[i-1]$ 直接计入 $dp[i]$ 中。 + +为此,我们需要扩展状态定义:**状态 $[i, j]$ 表示处在第 $i$ 阶并且上一轮跳了 $j$ 阶**,其中 $j \in \{1, 2\}$ 。此状态定义有效地区分了上一轮跳了 $1$ 阶还是 $2$ 阶,我们可以据此判断当前状态是从何而来的。 + +- 当上一轮跳了 $1$ 阶时,上上一轮只能选择跳 $2$ 阶,即 $dp[i, 1]$ 只能从 $dp[i-1, 2]$ 转移过来。 +- 当上一轮跳了 $2$ 阶时,上上一轮可选择跳 $1$ 阶或跳 $2$ 阶,即 $dp[i, 2]$ 可以从 $dp[i-2, 1]$ 或 $dp[i-2, 2]$ 转移过来。 + +如下图所示,在该定义下,$dp[i, j]$ 表示状态 $[i, j]$ 对应的方案数。此时状态转移方程为: + +$$ +\begin{cases} +dp[i, 1] = dp[i-1, 2] \\ +dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] +\end{cases} +$$ + +![考虑约束下的递推关系](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) + +最终,返回 $dp[n, 1] + dp[n, 2]$ 即可,两者之和代表爬到第 $n$ 阶的方案总数: + +```src +[file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} +``` + +在上面的案例中,由于仅需多考虑前面一个状态,因此我们仍然可以通过扩展状态定义,使得问题重新满足无后效性。然而,某些问题具有非常严重的“有后效性”。 + +!!! question "爬楼梯与障碍生成" + + 给定一个共有 $n$ 阶的楼梯,你每步可以上 $1$ 阶或者 $2$ 阶。**规定当爬到第 $i$ 阶时,系统自动会在第 $2i$ 阶上放上障碍物,之后所有轮都不允许跳到第 $2i$ 阶上**。例如,前两轮分别跳到了第 $2$、$3$ 阶上,则之后就不能跳到第 $4$、$6$ 阶上。请问有多少种方案可以爬到楼顶? + +在这个问题中,下次跳跃依赖过去所有的状态,因为每一次跳跃都会在更高的阶梯上设置障碍,并影响未来的跳跃。对于这类问题,动态规划往往难以解决。 + +实际上,许多复杂的组合优化问题(例如旅行商问题)不满足无后效性。对于这类问题,我们通常会选择使用其他方法,例如启发式搜索、遗传算法、强化学习等,从而在有限时间内得到可用的局部最优解。 diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png new file mode 100644 index 0000000000..9a9872bdf0 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png new file mode 100644 index 0000000000..b8a816d95a Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png new file mode 100644 index 0000000000..5699358f9e Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png new file mode 100644 index 0000000000..895c38bd44 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png new file mode 100644 index 0000000000..789fd9d54c Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png new file mode 100644 index 0000000000..a2b1b8ca35 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png new file mode 100644 index 0000000000..d4e9d9f2fc Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png new file mode 100644 index 0000000000..49e984e593 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png new file mode 100644 index 0000000000..df0b83e4e6 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png new file mode 100644 index 0000000000..090e91764c Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png new file mode 100644 index 0000000000..5eb6d2db31 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png new file mode 100644 index 0000000000..ffe8d4cf1a Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png new file mode 100644 index 0000000000..13572ba5dd Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png new file mode 100644 index 0000000000..64228be88f Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png new file mode 100644 index 0000000000..7244328c08 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png new file mode 100644 index 0000000000..f7a41765b0 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png new file mode 100644 index 0000000000..758c0b1166 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png new file mode 100644 index 0000000000..2b01417e29 Binary files /dev/null and b/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png differ diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/docs/chapter_dynamic_programming/dp_solution_pipeline.md new file mode 100644 index 0000000000..068b58aba9 --- /dev/null +++ b/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -0,0 +1,183 @@ +# 动态规划解题思路 + +上两节介绍了动态规划问题的主要特征,接下来我们一起探究两个更加实用的问题。 + +1. 如何判断一个问题是不是动态规划问题? +2. 求解动态规划问题该从何处入手,完整步骤是什么? + +## 问题判断 + +总的来说,如果一个问题包含重叠子问题、最优子结构,并满足无后效性,那么它通常适合用动态规划求解。然而,我们很难从问题描述中直接提取出这些特性。因此我们通常会放宽条件,**先观察问题是否适合使用回溯(穷举)解决**。 + +**适合用回溯解决的问题通常满足“决策树模型”**,这种问题可以使用树形结构来描述,其中每一个节点代表一个决策,每一条路径代表一个决策序列。 + +换句话说,如果问题包含明确的决策概念,并且解是通过一系列决策产生的,那么它就满足决策树模型,通常可以使用回溯来解决。 + +在此基础上,动态规划问题还有一些判断的“加分项”。 + +- 问题包含最大(小)或最多(少)等最优化描述。 +- 问题的状态能够使用一个列表、多维矩阵或树来表示,并且一个状态与其周围的状态存在递推关系。 + +相应地,也存在一些“减分项”。 + +- 问题的目标是找出所有可能的解决方案,而不是找出最优解。 +- 问题描述中有明显的排列组合的特征,需要返回具体的多个方案。 + +如果一个问题满足决策树模型,并具有较为明显的“加分项”,我们就可以假设它是一个动态规划问题,并在求解过程中验证它。 + +## 问题求解步骤 + +动态规划的解题流程会因问题的性质和难度而有所不同,但通常遵循以下步骤:描述决策,定义状态,建立 $dp$ 表,推导状态转移方程,确定边界条件等。 + +为了更形象地展示解题步骤,我们使用一个经典问题“最小路径和”来举例。 + +!!! question + + 给定一个 $n \times m$ 的二维网格 `grid` ,网格中的每个单元格包含一个非负整数,表示该单元格的代价。机器人以左上角单元格为起始点,每次只能向下或者向右移动一步,直至到达右下角单元格。请返回从左上角到右下角的最小路径和。 + +下图展示了一个例子,给定网格的最小路径和为 $13$ 。 + +![最小路径和示例数据](dp_solution_pipeline.assets/min_path_sum_example.png) + +**第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** + +本题的每一轮的决策就是从当前格子向下或向右走一步。设当前格子的行列索引为 $[i, j]$ ,则向下或向右走一步后,索引变为 $[i+1, j]$ 或 $[i, j+1]$ 。因此,状态应包含行索引和列索引两个变量,记为 $[i, j]$ 。 + +状态 $[i, j]$ 对应的子问题为:从起始点 $[0, 0]$ 走到 $[i, j]$ 的最小路径和,解记为 $dp[i, j]$ 。 + +至此,我们就得到了下图所示的二维 $dp$ 矩阵,其尺寸与输入网格 $grid$ 相同。 + +![状态定义与 dp 表](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) + +!!! note + + 动态规划和回溯过程可以描述为一个决策序列,而状态由所有决策变量构成。它应当包含描述解题进度的所有变量,其包含了足够的信息,能够用来推导出下一个状态。 + + 每个状态都对应一个子问题,我们会定义一个 $dp$ 表来存储所有子问题的解,状态的每个独立变量都是 $dp$ 表的一个维度。从本质上看,$dp$ 表是状态和子问题的解之间的映射。 + +**第二步:找出最优子结构,进而推导出状态转移方程** + +对于状态 $[i, j]$ ,它只能从上边格子 $[i-1, j]$ 和左边格子 $[i, j-1]$ 转移而来。因此最优子结构为:到达 $[i, j]$ 的最小路径和由 $[i, j-1]$ 的最小路径和与 $[i-1, j]$ 的最小路径和中较小的那一个决定。 + +根据以上分析,可推出下图所示的状态转移方程: + +$$ +dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] +$$ + +![最优子结构与状态转移方程](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) + +!!! note + + 根据定义好的 $dp$ 表,思考原问题和子问题的关系,找出通过子问题的最优解来构造原问题的最优解的方法,即最优子结构。 + + 一旦我们找到了最优子结构,就可以使用它来构建出状态转移方程。 + +**第三步:确定边界条件和状态转移顺序** + +在本题中,处在首行的状态只能从其左边的状态得来,处在首列的状态只能从其上边的状态得来,因此首行 $i = 0$ 和首列 $j = 0$ 是边界条件。 + +如下图所示,由于每个格子是由其左方格子和上方格子转移而来,因此我们使用循环来遍历矩阵,外循环遍历各行,内循环遍历各列。 + +![边界条件与状态转移顺序](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) + +!!! note + + 边界条件在动态规划中用于初始化 $dp$ 表,在搜索中用于剪枝。 + + 状态转移顺序的核心是要保证在计算当前问题的解时,所有它依赖的更小子问题的解都已经被正确地计算出来。 + +根据以上分析,我们已经可以直接写出动态规划代码。然而子问题分解是一种从顶至底的思想,因此按照“暴力搜索 $\rightarrow$ 记忆化搜索 $\rightarrow$ 动态规划”的顺序实现更加符合思维习惯。 + +### 方法一:暴力搜索 + +从状态 $[i, j]$ 开始搜索,不断分解为更小的状态 $[i-1, j]$ 和 $[i, j-1]$ ,递归函数包括以下要素。 + +- **递归参数**:状态 $[i, j]$ 。 +- **返回值**:从 $[0, 0]$ 到 $[i, j]$ 的最小路径和 $dp[i, j]$ 。 +- **终止条件**:当 $i = 0$ 且 $j = 0$ 时,返回代价 $grid[0, 0]$ 。 +- **剪枝**:当 $i < 0$ 时或 $j < 0$ 时索引越界,此时返回代价 $+\infty$ ,代表不可行。 + +实现代码如下: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} +``` + +下图给出了以 $dp[2, 1]$ 为根节点的递归树,其中包含一些重叠子问题,其数量会随着网格 `grid` 的尺寸变大而急剧增多。 + +从本质上看,造成重叠子问题的原因为:**存在多条路径可以从左上角到达某一单元格**。 + +![暴力搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs.png) + +每个状态都有向下和向右两种选择,从左上角走到右下角总共需要 $m + n - 2$ 步,所以最差时间复杂度为 $O(2^{m + n})$ ,其中 $n$ 和 $m$ 分别为网格的行数和列数。请注意,这种计算方式未考虑临近网格边界的情况,当到达网络边界时只剩下一种选择,因此实际的路径数量会少一些。 + +### 方法二:记忆化搜索 + +我们引入一个和网格 `grid` 相同尺寸的记忆列表 `mem` ,用于记录各个子问题的解,并将重叠子问题进行剪枝: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} +``` + +如下图所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。 + +![记忆化搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) + +### 方法三:动态规划 + +基于迭代实现动态规划解法,代码如下所示: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} +``` + +下图展示了最小路径和的状态转移过程,其遍历了整个网格,**因此时间复杂度为 $O(nm)$** 。 + +数组 `dp` 大小为 $n \times m$ ,**因此空间复杂度为 $O(nm)$** 。 + +=== "<1>" + ![最小路径和的动态规划过程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) + +=== "<2>" + ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) + +=== "<3>" + ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) + +=== "<4>" + ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) + +=== "<5>" + ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) + +=== "<6>" + ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) + +=== "<7>" + ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) + +=== "<8>" + ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) + +=== "<9>" + ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) + +=== "<10>" + ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) + +=== "<11>" + ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) + +=== "<12>" + ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) + +### 空间优化 + +由于每个格子只与其左边和上边的格子有关,因此我们可以只用一个单行数组来实现 $dp$ 表。 + +请注意,因为数组 `dp` 只能表示一行的状态,所以我们无法提前初始化首列状态,而是在遍历每行时更新它: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} +``` diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png new file mode 100644 index 0000000000..5b67c8ace3 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png new file mode 100644 index 0000000000..afb660bee4 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png new file mode 100644 index 0000000000..4bb9ce37fc Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png new file mode 100644 index 0000000000..3756ca0198 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png new file mode 100644 index 0000000000..78ceb86fcd Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png new file mode 100644 index 0000000000..e588f024b5 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png new file mode 100644 index 0000000000..f31b828aa3 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png new file mode 100644 index 0000000000..44003d2baa Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png new file mode 100644 index 0000000000..8f069eca36 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png new file mode 100644 index 0000000000..be21b846bd Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png new file mode 100644 index 0000000000..12dbb4886e Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png new file mode 100644 index 0000000000..67ab268eb1 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png new file mode 100644 index 0000000000..e8d11260ae Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png new file mode 100644 index 0000000000..beeb86ee06 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png new file mode 100644 index 0000000000..235b7f7d21 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png new file mode 100644 index 0000000000..381571bb03 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png new file mode 100644 index 0000000000..f299b0e8db Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png new file mode 100644 index 0000000000..1f96972f34 Binary files /dev/null and b/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png differ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.md b/docs/chapter_dynamic_programming/edit_distance_problem.md new file mode 100644 index 0000000000..d3a3184551 --- /dev/null +++ b/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -0,0 +1,129 @@ +# 编辑距离问题 + +编辑距离,也称 Levenshtein 距离,指两个字符串之间互相转换的最少修改次数,通常用于在信息检索和自然语言处理中度量两个序列的相似度。 + +!!! question + + 输入两个字符串 $s$ 和 $t$ ,返回将 $s$ 转换为 $t$ 所需的最少编辑步数。 + + 你可以在一个字符串中进行三种编辑操作:插入一个字符、删除一个字符、将字符替换为任意一个字符。 + +如下图所示,将 `kitten` 转换为 `sitting` 需要编辑 3 步,包括 2 次替换操作与 1 次添加操作;将 `hello` 转换为 `algo` 需要 3 步,包括 2 次替换操作和 1 次删除操作。 + +![编辑距离的示例数据](edit_distance_problem.assets/edit_distance_example.png) + +**编辑距离问题可以很自然地用决策树模型来解释**。字符串对应树节点,一轮决策(一次编辑操作)对应树的一条边。 + +如下图所示,在不限制操作的情况下,每个节点都可以派生出许多条边,每条边对应一种操作,这意味着从 `hello` 转换到 `algo` 有许多种可能的路径。 + +从决策树的角度看,本题的目标是求解节点 `hello` 和节点 `algo` 之间的最短路径。 + +![基于决策树模型表示编辑距离问题](edit_distance_problem.assets/edit_distance_decision_tree.png) + +### 动态规划思路 + +**第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** + +每一轮的决策是对字符串 $s$ 进行一次编辑操作。 + +我们希望在编辑操作的过程中,问题的规模逐渐缩小,这样才能构建子问题。设字符串 $s$ 和 $t$ 的长度分别为 $n$ 和 $m$ ,我们先考虑两字符串尾部的字符 $s[n-1]$ 和 $t[m-1]$ 。 + +- 若 $s[n-1]$ 和 $t[m-1]$ 相同,我们可以跳过它们,直接考虑 $s[n-2]$ 和 $t[m-2]$ 。 +- 若 $s[n-1]$ 和 $t[m-1]$ 不同,我们需要对 $s$ 进行一次编辑(插入、删除、替换),使得两字符串尾部的字符相同,从而可以跳过它们,考虑规模更小的问题。 + +也就是说,我们在字符串 $s$ 中进行的每一轮决策(编辑操作),都会使得 $s$ 和 $t$ 中剩余的待匹配字符发生变化。因此,状态为当前在 $s$ 和 $t$ 中考虑的第 $i$ 和第 $j$ 个字符,记为 $[i, j]$ 。 + +状态 $[i, j]$ 对应的子问题:**将 $s$ 的前 $i$ 个字符更改为 $t$ 的前 $j$ 个字符所需的最少编辑步数**。 + +至此,得到一个尺寸为 $(i+1) \times (j+1)$ 的二维 $dp$ 表。 + +**第二步:找出最优子结构,进而推导出状态转移方程** + +考虑子问题 $dp[i, j]$ ,其对应的两个字符串的尾部字符为 $s[i-1]$ 和 $t[j-1]$ ,可根据不同编辑操作分为下图所示的三种情况。 + +1. 在 $s[i-1]$ 之后添加 $t[j-1]$ ,则剩余子问题 $dp[i, j-1]$ 。 +2. 删除 $s[i-1]$ ,则剩余子问题 $dp[i-1, j]$ 。 +3. 将 $s[i-1]$ 替换为 $t[j-1]$ ,则剩余子问题 $dp[i-1, j-1]$ 。 + +![编辑距离的状态转移](edit_distance_problem.assets/edit_distance_state_transfer.png) + +根据以上分析,可得最优子结构:$dp[i, j]$ 的最少编辑步数等于 $dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ 三者中的最少编辑步数,再加上本次的编辑步数 $1$ 。对应的状态转移方程为: + +$$ +dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 +$$ + +请注意,**当 $s[i-1]$ 和 $t[j-1]$ 相同时,无须编辑当前字符**,这种情况下的状态转移方程为: + +$$ +dp[i, j] = dp[i-1, j-1] +$$ + +**第三步:确定边界条件和状态转移顺序** + +当两字符串都为空时,编辑步数为 $0$ ,即 $dp[0, 0] = 0$ 。当 $s$ 为空但 $t$ 不为空时,最少编辑步数等于 $t$ 的长度,即首行 $dp[0, j] = j$ 。当 $s$ 不为空但 $t$ 为空时,最少编辑步数等于 $s$ 的长度,即首列 $dp[i, 0] = i$ 。 + +观察状态转移方程,解 $dp[i, j]$ 依赖左方、上方、左上方的解,因此通过两层循环正序遍历整个 $dp$ 表即可。 + +### 代码实现 + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp} +``` + +如下图所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作填写一个二维网格的过程。 + +=== "<1>" + ![编辑距离的动态规划过程](edit_distance_problem.assets/edit_distance_dp_step1.png) + +=== "<2>" + ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) + +=== "<3>" + ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) + +=== "<4>" + ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) + +=== "<5>" + ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) + +=== "<6>" + ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) + +=== "<7>" + ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) + +=== "<8>" + ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) + +=== "<9>" + ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) + +=== "<10>" + ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) + +=== "<11>" + ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) + +=== "<12>" + ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) + +=== "<13>" + ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) + +=== "<14>" + ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) + +=== "<15>" + ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) + +### 空间优化 + +由于 $dp[i,j]$ 是由上方 $dp[i-1, j]$、左方 $dp[i, j-1]$、左上方 $dp[i-1, j-1]$ 转移而来的,而正序遍历会丢失左上方 $dp[i-1, j-1]$ ,倒序遍历无法提前构建 $dp[i, j-1]$ ,因此两种遍历顺序都不可取。 + +为此,我们可以使用一个变量 `leftup` 来暂存左上方的解 $dp[i-1, j-1]$ ,从而只需考虑左方和上方的解。此时的情况与完全背包问题相同,可使用正序遍历。代码如下所示: + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} +``` diff --git a/docs/chapter_dynamic_programming/index.md b/docs/chapter_dynamic_programming/index.md new file mode 100644 index 0000000000..a5e0e585fb --- /dev/null +++ b/docs/chapter_dynamic_programming/index.md @@ -0,0 +1,9 @@ +# 动态规划 + +![动态规划](../assets/covers/chapter_dynamic_programming.jpg) + +!!! abstract + + 小溪汇入河流,江河汇入大海。 + + 动态规划将小问题的解汇集成大问题的答案,一步步引领我们走向解决问题的彼岸。 diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png new file mode 100644 index 0000000000..7e6d225c28 Binary files /dev/null and b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png differ diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png new file mode 100644 index 0000000000..1d990be772 Binary files /dev/null and b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png differ diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png new file mode 100644 index 0000000000..f7873a6d3d Binary files /dev/null and b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png differ diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png new file mode 100644 index 0000000000..6d28e67904 Binary files /dev/null and b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png differ diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png new file mode 100644 index 0000000000..6b28c09300 Binary files /dev/null and b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png differ diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md new file mode 100644 index 0000000000..0f9a01325c --- /dev/null +++ b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -0,0 +1,110 @@ +# 初探动态规划 + +动态规划(dynamic programming)是一个重要的算法范式,它将一个问题分解为一系列更小的子问题,并通过存储子问题的解来避免重复计算,从而大幅提升时间效率。 + +在本节中,我们从一个经典例题入手,先给出它的暴力回溯解法,观察其中包含的重叠子问题,再逐步导出更高效的动态规划解法。 + +!!! question "爬楼梯" + + 给定一个共有 $n$ 阶的楼梯,你每步可以上 $1$ 阶或者 $2$ 阶,请问有多少种方案可以爬到楼顶? + +如下图所示,对于一个 $3$ 阶楼梯,共有 $3$ 种方案可以爬到楼顶。 + +![爬到第 3 阶的方案数量](intro_to_dynamic_programming.assets/climbing_stairs_example.png) + +本题的目标是求解方案数量,**我们可以考虑通过回溯来穷举所有可能性**。具体来说,将爬楼梯想象为一个多轮选择的过程:从地面出发,每轮选择上 $1$ 阶或 $2$ 阶,每当到达楼梯顶部时就将方案数量加 $1$ ,当越过楼梯顶部时就将其剪枝。代码如下所示: + +```src +[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} +``` + +## 方法一:暴力搜索 + +回溯算法通常并不显式地对问题进行拆解,而是将求解问题看作一系列决策步骤,通过试探和剪枝,搜索所有可能的解。 + +我们可以尝试从问题分解的角度分析这道题。设爬到第 $i$ 阶共有 $dp[i]$ 种方案,那么 $dp[i]$ 就是原问题,其子问题包括: + +$$ +dp[i-1], dp[i-2], \dots, dp[2], dp[1] +$$ + +由于每轮只能上 $1$ 阶或 $2$ 阶,因此当我们站在第 $i$ 阶楼梯上时,上一轮只可能站在第 $i - 1$ 阶或第 $i - 2$ 阶上。换句话说,我们只能从第 $i -1$ 阶或第 $i - 2$ 阶迈向第 $i$ 阶。 + +由此便可得出一个重要推论:**爬到第 $i - 1$ 阶的方案数加上爬到第 $i - 2$ 阶的方案数就等于爬到第 $i$ 阶的方案数**。公式如下: + +$$ +dp[i] = dp[i-1] + dp[i-2] +$$ + +这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。下图展示了该递推关系。 + +![方案数量递推关系](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) + +我们可以根据递推公式得到暴力搜索解法。以 $dp[n]$ 为起始点,**递归地将一个较大问题拆解为两个较小问题的和**,直至到达最小子问题 $dp[1]$ 和 $dp[2]$ 时返回。其中,最小子问题的解是已知的,即 $dp[1] = 1$、$dp[2] = 2$ ,表示爬到第 $1$、$2$ 阶分别有 $1$、$2$ 种方案。 + +观察以下代码,它和标准回溯代码都属于深度优先搜索,但更加简洁: + +```src +[file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} +``` + +下图展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。 + +![爬楼梯对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) + +观察上图,**指数阶的时间复杂度是“重叠子问题”导致的**。例如 $dp[9]$ 被分解为 $dp[8]$ 和 $dp[7]$ ,$dp[8]$ 被分解为 $dp[7]$ 和 $dp[6]$ ,两者都包含子问题 $dp[7]$ 。 + +以此类推,子问题中包含更小的重叠子问题,子子孙孙无穷尽也。绝大部分计算资源都浪费在这些重叠的子问题上。 + +## 方法二:记忆化搜索 + +为了提升算法效率,**我们希望所有的重叠子问题都只被计算一次**。为此,我们声明一个数组 `mem` 来记录每个子问题的解,并在搜索过程中将重叠子问题剪枝。 + +1. 当首次计算 $dp[i]$ 时,我们将其记录至 `mem[i]` ,以便之后使用。 +2. 当再次需要计算 $dp[i]$ 时,我们便可直接从 `mem[i]` 中获取结果,从而避免重复计算该子问题。 + +代码如下所示: + +```src +[file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} +``` + +观察下图,**经过记忆化处理后,所有重叠子问题都只需计算一次,时间复杂度优化至 $O(n)$** ,这是一个巨大的飞跃。 + +![记忆化搜索对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) + +## 方法三:动态规划 + +**记忆化搜索是一种“从顶至底”的方法**:我们从原问题(根节点)开始,递归地将较大子问题分解为较小子问题,直至解已知的最小子问题(叶节点)。之后,通过回溯逐层收集子问题的解,构建出原问题的解。 + +与之相反,**动态规划是一种“从底至顶”的方法**:从最小子问题的解开始,迭代地构建更大子问题的解,直至得到原问题的解。 + +由于动态规划不包含回溯过程,因此只需使用循环迭代实现,无须使用递归。在以下代码中,我们初始化一个数组 `dp` 来存储子问题的解,它起到了与记忆化搜索中数组 `mem` 相同的记录作用: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} +``` + +下图模拟了以上代码的执行过程。 + +![爬楼梯的动态规划过程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) + +与回溯算法一样,动态规划也使用“状态”概念来表示问题求解的特定阶段,每个状态都对应一个子问题以及相应的局部最优解。例如,爬楼梯问题的状态定义为当前所在楼梯阶数 $i$ 。 + +根据以上内容,我们可以总结出动态规划的常用术语。 + +- 将数组 `dp` 称为 dp 表,$dp[i]$ 表示状态 $i$ 对应子问题的解。 +- 将最小子问题对应的状态(第 $1$ 阶和第 $2$ 阶楼梯)称为初始状态。 +- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为状态转移方程。 + +## 空间优化 + +细心的读者可能发现了,**由于 $dp[i]$ 只与 $dp[i-1]$ 和 $dp[i-2]$ 有关,因此我们无须使用一个数组 `dp` 来存储所有子问题的解**,而只需两个变量滚动前进即可。代码如下所示: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} +``` + +观察以上代码,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降至 $O(1)$ 。 + +在动态规划问题中,当前状态往往仅与前面有限个状态有关,这时我们可以只保留必要的状态,通过“降维”来节省内存空间。**这种空间优化技巧被称为“滚动变量”或“滚动数组”**。 diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png new file mode 100644 index 0000000000..8867771131 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png new file mode 100644 index 0000000000..0441d70751 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png new file mode 100644 index 0000000000..3ed6c93919 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png new file mode 100644 index 0000000000..dfcce11bbd Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png new file mode 100644 index 0000000000..c5892925fb Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png new file mode 100644 index 0000000000..80eebf9b2c Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png new file mode 100644 index 0000000000..cb1e4f98c2 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png new file mode 100644 index 0000000000..43cb3f3a9c Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png new file mode 100644 index 0000000000..40987153bb Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png new file mode 100644 index 0000000000..56eb993517 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png new file mode 100644 index 0000000000..87540a5026 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png new file mode 100644 index 0000000000..02608bc6e3 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png new file mode 100644 index 0000000000..6d6bd15aa6 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png new file mode 100644 index 0000000000..6ecfe0dc9f Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png new file mode 100644 index 0000000000..536370ab88 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png new file mode 100644 index 0000000000..b734ebe39f Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png new file mode 100644 index 0000000000..7a20a60236 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png new file mode 100644 index 0000000000..679aefd503 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png new file mode 100644 index 0000000000..dd110cca2a Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png new file mode 100644 index 0000000000..22b4872924 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png new file mode 100644 index 0000000000..c5877af299 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png new file mode 100644 index 0000000000..3f57114d00 Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png new file mode 100644 index 0000000000..04c59f1e7a Binary files /dev/null and b/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png differ diff --git a/docs/chapter_dynamic_programming/knapsack_problem.md b/docs/chapter_dynamic_programming/knapsack_problem.md new file mode 100644 index 0000000000..f45d13b52e --- /dev/null +++ b/docs/chapter_dynamic_programming/knapsack_problem.md @@ -0,0 +1,168 @@ +# 0-1 背包问题 + +背包问题是一个非常好的动态规划入门题目,是动态规划中最常见的问题形式。其具有很多变种,例如 0-1 背包问题、完全背包问题、多重背包问题等。 + +在本节中,我们先来求解最常见的 0-1 背包问题。 + +!!! question + + 给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,问在限定背包容量下能放入物品的最大价值。 + +观察下图,由于物品编号 $i$ 从 $1$ 开始计数,数组索引从 $0$ 开始计数,因此物品 $i$ 对应重量 $wgt[i-1]$ 和价值 $val[i-1]$ 。 + +![0-1 背包的示例数据](knapsack_problem.assets/knapsack_example.png) + +我们可以将 0-1 背包问题看作一个由 $n$ 轮决策组成的过程,对于每个物体都有不放入和放入两种决策,因此该问题满足决策树模型。 + +该问题的目标是求解“在限定背包容量下能放入物品的最大价值”,因此较大概率是一个动态规划问题。 + +**第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** + +对于每个物品来说,不放入背包,背包容量不变;放入背包,背包容量减小。由此可得状态定义:当前物品编号 $i$ 和背包容量 $c$ ,记为 $[i, c]$ 。 + +状态 $[i, c]$ 对应的子问题为:**前 $i$ 个物品在容量为 $c$ 的背包中的最大价值**,记为 $dp[i, c]$ 。 + +待求解的是 $dp[n, cap]$ ,因此需要一个尺寸为 $(n+1) \times (cap+1)$ 的二维 $dp$ 表。 + +**第二步:找出最优子结构,进而推导出状态转移方程** + +当我们做出物品 $i$ 的决策后,剩余的是前 $i-1$ 个物品决策的子问题,可分为以下两种情况。 + +- **不放入物品 $i$** :背包容量不变,状态变化为 $[i-1, c]$ 。 +- **放入物品 $i$** :背包容量减少 $wgt[i-1]$ ,价值增加 $val[i-1]$ ,状态变化为 $[i-1, c-wgt[i-1]]$ 。 + +上述分析向我们揭示了本题的最优子结构:**最大价值 $dp[i, c]$ 等于不放入物品 $i$ 和放入物品 $i$ 两种方案中价值更大的那一个**。由此可推导出状态转移方程: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) +$$ + +需要注意的是,若当前物品重量 $wgt[i - 1]$ 超出剩余背包容量 $c$ ,则只能选择不放入背包。 + +**第三步:确定边界条件和状态转移顺序** + +当无物品或背包容量为 $0$ 时最大价值为 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等于 $0$ 。 + +当前状态 $[i, c]$ 从上方的状态 $[i-1, c]$ 和左上方的状态 $[i-1, c-wgt[i-1]]$ 转移而来,因此通过两层循环正序遍历整个 $dp$ 表即可。 + +根据以上分析,我们接下来按顺序实现暴力搜索、记忆化搜索、动态规划解法。 + +### 方法一:暴力搜索 + +搜索代码包含以下要素。 + +- **递归参数**:状态 $[i, c]$ 。 +- **返回值**:子问题的解 $dp[i, c]$ 。 +- **终止条件**:当物品编号越界 $i = 0$ 或背包剩余容量为 $0$ 时,终止递归并返回价值 $0$ 。 +- **剪枝**:若当前物品重量超出背包剩余容量,则只能选择不放入背包。 + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs} +``` + +如下图所示,由于每个物品都会产生不选和选两条搜索分支,因此时间复杂度为 $O(2^n)$ 。 + +观察递归树,容易发现其中存在重叠子问题,例如 $dp[1, 10]$ 等。而当物品较多、背包容量较大,尤其是相同重量的物品较多时,重叠子问题的数量将会大幅增多。 + +![0-1 背包问题的暴力搜索递归树](knapsack_problem.assets/knapsack_dfs.png) + +### 方法二:记忆化搜索 + +为了保证重叠子问题只被计算一次,我们借助记忆列表 `mem` 来记录子问题的解,其中 `mem[i][c]` 对应 $dp[i, c]$ 。 + +引入记忆化之后,**时间复杂度取决于子问题数量**,也就是 $O(n \times cap)$ 。实现代码如下: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} +``` + +下图展示了在记忆化搜索中被剪掉的搜索分支。 + +![0-1 背包问题的记忆化搜索递归树](knapsack_problem.assets/knapsack_dfs_mem.png) + +### 方法三:动态规划 + +动态规划实质上就是在状态转移中填充 $dp$ 表的过程,代码如下所示: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp} +``` + +如下图所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。 + +=== "<1>" + ![0-1 背包问题的动态规划过程](knapsack_problem.assets/knapsack_dp_step1.png) + +=== "<2>" + ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) + +=== "<3>" + ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) + +=== "<4>" + ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) + +=== "<5>" + ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) + +=== "<6>" + ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) + +=== "<7>" + ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) + +=== "<8>" + ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) + +=== "<9>" + ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) + +=== "<10>" + ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) + +=== "<11>" + ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) + +=== "<12>" + ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) + +=== "<13>" + ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) + +=== "<14>" + ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) + +### 空间优化 + +由于每个状态都只与其上一行的状态有关,因此我们可以使用两个数组滚动前进,将空间复杂度从 $O(n^2)$ 降至 $O(n)$ 。 + +进一步思考,我们能否仅用一个数组实现空间优化呢?观察可知,每个状态都是由正上方或左上方的格子转移过来的。假设只有一个数组,当开始遍历第 $i$ 行时,该数组存储的仍然是第 $i-1$ 行的状态。 + +- 如果采取正序遍历,那么遍历到 $dp[i, j]$ 时,左上方 $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ 值可能已经被覆盖,此时就无法得到正确的状态转移结果。 +- 如果采取倒序遍历,则不会发生覆盖问题,状态转移可以正确进行。 + +下图展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。 + +=== "<1>" + ![0-1 背包的空间优化后的动态规划过程](knapsack_problem.assets/knapsack_dp_comp_step1.png) + +=== "<2>" + ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) + +=== "<3>" + ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) + +=== "<4>" + ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) + +=== "<5>" + ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) + +=== "<6>" + ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) + +在代码实现中,我们仅需将数组 `dp` 的第一维 $i$ 直接删除,并且把内循环更改为倒序遍历即可: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} +``` diff --git a/docs/chapter_dynamic_programming/summary.md b/docs/chapter_dynamic_programming/summary.md new file mode 100644 index 0000000000..c46f54dfc2 --- /dev/null +++ b/docs/chapter_dynamic_programming/summary.md @@ -0,0 +1,23 @@ +# 小结 + +- 动态规划对问题进行分解,并通过存储子问题的解来规避重复计算,提高计算效率。 +- 不考虑时间的前提下,所有动态规划问题都可以用回溯(暴力搜索)进行求解,但递归树中存在大量的重叠子问题,效率极低。通过引入记忆化列表,可以存储所有计算过的子问题的解,从而保证重叠子问题只被计算一次。 +- 记忆化搜索是一种从顶至底的递归式解法,而与之对应的动态规划是一种从底至顶的递推式解法,其如同“填写表格”一样。由于当前状态仅依赖某些局部状态,因此我们可以消除 $dp$ 表的一个维度,从而降低空间复杂度。 +- 子问题分解是一种通用的算法思路,在分治、动态规划、回溯中具有不同的性质。 +- 动态规划问题有三大特性:重叠子问题、最优子结构、无后效性。 +- 如果原问题的最优解可以从子问题的最优解构建得来,则它就具有最优子结构。 +- 无后效性指对于一个状态,其未来发展只与该状态有关,而与过去经历的所有状态无关。许多组合优化问题不具有无后效性,无法使用动态规划快速求解。 + +**背包问题** + +- 背包问题是最典型的动态规划问题之一,具有 0-1 背包、完全背包、多重背包等变种。 +- 0-1 背包的状态定义为前 $i$ 个物品在容量为 $c$ 的背包中的最大价值。根据不放入背包和放入背包两种决策,可得到最优子结构,并构建出状态转移方程。在空间优化中,由于每个状态依赖正上方和左上方的状态,因此需要倒序遍历列表,避免左上方状态被覆盖。 +- 完全背包问题的每种物品的选取数量无限制,因此选择放入物品的状态转移与 0-1 背包问题不同。由于状态依赖正上方和正左方的状态,因此在空间优化中应当正序遍历。 +- 零钱兑换问题是完全背包问题的一个变种。它从求“最大”价值变为求“最小”硬币数量,因此状态转移方程中的 $\max()$ 应改为 $\min()$ 。从追求“不超过”背包容量到追求“恰好”凑出目标金额,因此使用 $amt + 1$ 来表示“无法凑出目标金额”的无效解。 +- 零钱兑换问题 II 从求“最少硬币数量”改为求“硬币组合数量”,状态转移方程相应地从 $\min()$ 改为求和运算符。 + +**编辑距离问题** + +- 编辑距离(Levenshtein 距离)用于衡量两个字符串之间的相似度,其定义为从一个字符串到另一个字符串的最少编辑步数,编辑操作包括添加、删除、替换。 +- 编辑距离问题的状态定义为将 $s$ 的前 $i$ 个字符更改为 $t$ 的前 $j$ 个字符所需的最少编辑步数。当 $s[i] \ne t[j]$ 时,具有三种决策:添加、删除、替换,它们都有相应的剩余子问题。据此便可以找出最优子结构与构建状态转移方程。而当 $s[i] = t[j]$ 时,无须编辑当前字符。 +- 在编辑距离中,状态依赖其正上方、正左方、左上方的状态,因此空间优化后正序或倒序遍历都无法正确地进行状态转移。为此,我们利用一个变量暂存左上方状态,从而转化到与完全背包问题等价的情况,可以在空间优化后进行正序遍历。 diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png new file mode 100644 index 0000000000..ea7b6a7b3f Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png new file mode 100644 index 0000000000..c15b242d4c Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png new file mode 100644 index 0000000000..9cde92c651 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png new file mode 100644 index 0000000000..fbe96594c8 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png new file mode 100644 index 0000000000..d267d61b4e Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png new file mode 100644 index 0000000000..87ca4b0802 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png new file mode 100644 index 0000000000..a7e223d54b Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png new file mode 100644 index 0000000000..e0f7f8c0bf Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png new file mode 100644 index 0000000000..9eaa0d4ee4 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png new file mode 100644 index 0000000000..baa2159c0b Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png new file mode 100644 index 0000000000..ee582f98a0 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png new file mode 100644 index 0000000000..6116eb082d Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png new file mode 100644 index 0000000000..d9b92f07c4 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png new file mode 100644 index 0000000000..0ae5e8d097 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png new file mode 100644 index 0000000000..654fd6e6f5 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png new file mode 100644 index 0000000000..c95e0594ca Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png new file mode 100644 index 0000000000..2db0071dba Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png new file mode 100644 index 0000000000..dfa4f59fb5 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png new file mode 100644 index 0000000000..259e8d20e1 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png new file mode 100644 index 0000000000..c3f58803e3 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png new file mode 100644 index 0000000000..f25ff27e79 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png new file mode 100644 index 0000000000..27861f808a Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png new file mode 100644 index 0000000000..2974cd22dc Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png new file mode 100644 index 0000000000..829cbd4012 Binary files /dev/null and b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png differ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md new file mode 100644 index 0000000000..c43604ceac --- /dev/null +++ b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -0,0 +1,207 @@ +# 完全背包问题 + +在本节中,我们先求解另一个常见的背包问题:完全背包,再了解它的一种特例:零钱兑换。 + +## 完全背包问题 + +!!! question + + 给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。**每个物品可以重复选取**,问在限定背包容量下能放入物品的最大价值。示例如下图所示。 + +![完全背包问题的示例数据](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) + +### 动态规划思路 + +完全背包问题和 0-1 背包问题非常相似,**区别仅在于不限制物品的选择次数**。 + +- 在 0-1 背包问题中,每种物品只有一个,因此将物品 $i$ 放入背包后,只能从前 $i-1$ 个物品中选择。 +- 在完全背包问题中,每种物品的数量是无限的,因此将物品 $i$ 放入背包后,**仍可以从前 $i$ 个物品中选择**。 + +在完全背包问题的规定下,状态 $[i, c]$ 的变化分为两种情况。 + +- **不放入物品 $i$** :与 0-1 背包问题相同,转移至 $[i-1, c]$ 。 +- **放入物品 $i$** :与 0-1 背包问题不同,转移至 $[i, c-wgt[i-1]]$ 。 + +从而状态转移方程变为: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) +$$ + +### 代码实现 + +对比两道题目的代码,状态转移中有一处从 $i-1$ 变为 $i$ ,其余完全一致: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} +``` + +### 空间优化 + +由于当前状态是从左边和上边的状态转移而来的,**因此空间优化后应该对 $dp$ 表中的每一行进行正序遍历**。 + +这个遍历顺序与 0-1 背包正好相反。请借助下图来理解两者的区别。 + +=== "<1>" + ![完全背包问题在空间优化后的动态规划过程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) + +=== "<2>" + ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) + +=== "<3>" + ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) + +=== "<4>" + ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) + +=== "<5>" + ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) + +=== "<6>" + ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) + +代码实现比较简单,仅需将数组 `dp` 的第一维删除: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} +``` + +## 零钱兑换问题 + +背包问题是一大类动态规划问题的代表,其拥有很多变种,例如零钱兑换问题。 + +!!! question + + 给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,**每种硬币可以重复选取**,问能够凑出目标金额的最少硬币数量。如果无法凑出目标金额,则返回 $-1$ 。示例如下图所示。 + +![零钱兑换问题的示例数据](unbounded_knapsack_problem.assets/coin_change_example.png) + +### 动态规划思路 + +**零钱兑换可以看作完全背包问题的一种特殊情况**,两者具有以下联系与不同点。 + +- 两道题可以相互转换,“物品”对应“硬币”、“物品重量”对应“硬币面值”、“背包容量”对应“目标金额”。 +- 优化目标相反,完全背包问题是要最大化物品价值,零钱兑换问题是要最小化硬币数量。 +- 完全背包问题是求“不超过”背包容量下的解,零钱兑换是求“恰好”凑到目标金额的解。 + +**第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** + +状态 $[i, a]$ 对应的子问题为:**前 $i$ 种硬币能够凑出金额 $a$ 的最少硬币数量**,记为 $dp[i, a]$ 。 + +二维 $dp$ 表的尺寸为 $(n+1) \times (amt+1)$ 。 + +**第二步:找出最优子结构,进而推导出状态转移方程** + +本题与完全背包问题的状态转移方程存在以下两点差异。 + +- 本题要求最小值,因此需将运算符 $\max()$ 更改为 $\min()$ 。 +- 优化主体是硬币数量而非商品价值,因此在选中硬币时执行 $+1$ 即可。 + +$$ +dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) +$$ + +**第三步:确定边界条件和状态转移顺序** + +当目标金额为 $0$ 时,凑出它的最少硬币数量为 $0$ ,即首列所有 $dp[i, 0]$ 都等于 $0$ 。 + +当无硬币时,**无法凑出任意 $> 0$ 的目标金额**,即是无效解。为使状态转移方程中的 $\min()$ 函数能够识别并过滤无效解,我们考虑使用 $+ \infty$ 来表示它们,即令首行所有 $dp[0, a]$ 都等于 $+ \infty$ 。 + +### 代码实现 + +大多数编程语言并未提供 $+ \infty$ 变量,只能使用整型 `int` 的最大值来代替。而这又会导致大数越界:状态转移方程中的 $+ 1$ 操作可能发生溢出。 + +为此,我们采用数字 $amt + 1$ 来表示无效解,因为凑出 $amt$ 的硬币数量最多为 $amt$ 。最后返回前,判断 $dp[n, amt]$ 是否等于 $amt + 1$ ,若是则返回 $-1$ ,代表无法凑出目标金额。代码如下所示: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp} +``` + +下图展示了零钱兑换的动态规划过程,和完全背包问题非常相似。 + +=== "<1>" + ![零钱兑换问题的动态规划过程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) + +=== "<2>" + ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) + +=== "<3>" + ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) + +=== "<4>" + ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) + +=== "<5>" + ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) + +=== "<6>" + ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) + +=== "<7>" + ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) + +=== "<8>" + ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) + +=== "<9>" + ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) + +=== "<10>" + ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) + +=== "<11>" + ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) + +=== "<12>" + ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) + +=== "<13>" + ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) + +=== "<14>" + ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) + +=== "<15>" + ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) + +### 空间优化 + +零钱兑换的空间优化的处理方式和完全背包问题一致: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} +``` + +## 零钱兑换问题 II + +!!! question + + 给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,每种硬币可以重复选取,**问凑出目标金额的硬币组合数量**。示例如下图所示。 + +![零钱兑换问题 II 的示例数据](unbounded_knapsack_problem.assets/coin_change_ii_example.png) + +### 动态规划思路 + +相比于上一题,本题目标是求组合数量,因此子问题变为:**前 $i$ 种硬币能够凑出金额 $a$ 的组合数量**。而 $dp$ 表仍然是尺寸为 $(n+1) \times (amt + 1)$ 的二维矩阵。 + +当前状态的组合数量等于不选当前硬币与选当前硬币这两种决策的组合数量之和。状态转移方程为: + +$$ +dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] +$$ + +当目标金额为 $0$ 时,无须选择任何硬币即可凑出目标金额,因此应将首列所有 $dp[i, 0]$ 都初始化为 $1$ 。当无硬币时,无法凑出任何 $>0$ 的目标金额,因此首行所有 $dp[0, a]$ 都等于 $0$ 。 + +### 代码实现 + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} +``` + +### 空间优化 + +空间优化处理方式相同,删除硬币维度即可: + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} +``` diff --git a/docs/chapter_graph/graph.assets/adjacency_list.png b/docs/chapter_graph/graph.assets/adjacency_list.png new file mode 100644 index 0000000000..6be64bc170 Binary files /dev/null and b/docs/chapter_graph/graph.assets/adjacency_list.png differ diff --git a/docs/chapter_graph/graph.assets/adjacency_matrix.png b/docs/chapter_graph/graph.assets/adjacency_matrix.png new file mode 100644 index 0000000000..a70ee0db46 Binary files /dev/null and b/docs/chapter_graph/graph.assets/adjacency_matrix.png differ diff --git a/docs/chapter_graph/graph.assets/connected_graph.png b/docs/chapter_graph/graph.assets/connected_graph.png new file mode 100644 index 0000000000..3d3dc1b693 Binary files /dev/null and b/docs/chapter_graph/graph.assets/connected_graph.png differ diff --git a/docs/chapter_graph/graph.assets/directed_graph.png b/docs/chapter_graph/graph.assets/directed_graph.png new file mode 100644 index 0000000000..7067e6fdf2 Binary files /dev/null and b/docs/chapter_graph/graph.assets/directed_graph.png differ diff --git a/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png b/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png new file mode 100644 index 0000000000..2e3f9b7c0b Binary files /dev/null and b/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png differ diff --git a/docs/chapter_graph/graph.assets/weighted_graph.png b/docs/chapter_graph/graph.assets/weighted_graph.png new file mode 100644 index 0000000000..63d6934d6b Binary files /dev/null and b/docs/chapter_graph/graph.assets/weighted_graph.png differ diff --git a/docs/chapter_graph/graph.md b/docs/chapter_graph/graph.md new file mode 100644 index 0000000000..3208dfaf77 --- /dev/null +++ b/docs/chapter_graph/graph.md @@ -0,0 +1,83 @@ +# 图 + +图(graph)是一种非线性数据结构,由顶点(vertex)边(edge)组成。我们可以将图 $G$ 抽象地表示为一组顶点 $V$ 和一组边 $E$ 的集合。以下示例展示了一个包含 5 个顶点和 7 条边的图。 + +$$ +\begin{aligned} +V & = \{ 1, 2, 3, 4, 5 \} \newline +E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline +G & = \{ V, E \} \newline +\end{aligned} +$$ + +如果将顶点看作节点,将边看作连接各个节点的引用(指针),我们就可以将图看作一种从链表拓展而来的数据结构。如下图所示,**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高**,因而更为复杂。 + +![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png) + +## 图的常见类型与术语 + +根据边是否具有方向,可分为无向图(undirected graph)有向图(directed graph),如下图所示。 + +- 在无向图中,边表示两顶点之间的“双向”连接关系,例如微信或 QQ 中的“好友关系”。 +- 在有向图中,边具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系。 + +![有向图与无向图](graph.assets/directed_graph.png) + +根据所有顶点是否连通,可分为连通图(connected graph)非连通图(disconnected graph),如下图所示。 + +- 对于连通图,从某个顶点出发,可以到达其余任意顶点。 +- 对于非连通图,从某个顶点出发,至少有一个顶点无法到达。 + +![连通图与非连通图](graph.assets/connected_graph.png) + +我们还可以为边添加“权重”变量,从而得到如下图所示的有权图(weighted graph)。例如在《王者荣耀》等手游中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以用有权图来表示。 + +![有权图与无权图](graph.assets/weighted_graph.png) + +图数据结构包含以下常用术语。 + +- 邻接(adjacency):当两顶点之间存在边相连时,称这两顶点“邻接”。在上图中,顶点 1 的邻接顶点为顶点 2、3、5。 +- 路径(path):从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。在上图中,边序列 1-5-2-4 是顶点 1 到顶点 4 的一条路径。 +- 度(degree):一个顶点拥有的边数。对于有向图,入度(in-degree)表示有多少条边指向该顶点,出度(out-degree)表示有多少条边从该顶点指出。 + +## 图的表示 + +图的常用表示方式包括“邻接矩阵”和“邻接表”。以下使用无向图进行举例。 + +### 邻接矩阵 + +设图的顶点数量为 $n$ ,邻接矩阵(adjacency matrix)使用一个 $n \times n$ 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 $1$ 或 $0$ 表示两个顶点之间是否存在边。 + +如下图所示,设邻接矩阵为 $M$、顶点列表为 $V$ ,那么矩阵元素 $M[i, j] = 1$ 表示顶点 $V[i]$ 到顶点 $V[j]$ 之间存在边,反之 $M[i, j] = 0$ 表示两顶点之间无边。 + +![图的邻接矩阵表示](graph.assets/adjacency_matrix.png) + +邻接矩阵具有以下特性。 + +- 在简单图中,顶点不能与自身相连,此时邻接矩阵主对角线元素没有意义。 +- 对于无向图,两个方向的边等价,此时邻接矩阵关于主对角线对称。 +- 将邻接矩阵的元素从 $1$ 和 $0$ 替换为权重,则可表示有权图。 + +使用邻接矩阵表示图时,我们可以直接访问矩阵元素以获取边,因此增删查改操作的效率很高,时间复杂度均为 $O(1)$ 。然而,矩阵的空间复杂度为 $O(n^2)$ ,内存占用较多。 + +### 邻接表 + +邻接表(adjacency list)使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 个链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)。下图展示了一个使用邻接表存储的图的示例。 + +![图的邻接表表示](graph.assets/adjacency_list.png) + +邻接表仅存储实际存在的边,而边的总数通常远小于 $n^2$ ,因此它更加节省空间。然而,在邻接表中需要通过遍历链表来查找边,因此其时间效率不如邻接矩阵。 + +观察上图,**邻接表结构与哈希表中的“链式地址”非常相似,因此我们也可以采用类似的方法来优化效率**。比如当链表较长时,可以将链表转化为 AVL 树或红黑树,从而将时间效率从 $O(n)$ 优化至 $O(\log n)$ ;还可以把链表转换为哈希表,从而将时间复杂度降至 $O(1)$ 。 + +## 图的常见应用 + +如下表所示,许多现实系统可以用图来建模,相应的问题也可以约化为图计算问题。 + +

  现实生活中常见的图

+ +| | 顶点 | 边 | 图计算问题 | +| -------- | ---- | -------------------- | ------------ | +| 社交网络 | 用户 | 好友关系 | 潜在好友推荐 | +| 地铁线路 | 站点 | 站点间的连通性 | 最短路线推荐 | +| 太阳系 | 星体 | 星体间的万有引力作用 | 行星轨道计算 | diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png b/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png new file mode 100644 index 0000000000..c2b3af5714 Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png b/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png new file mode 100644 index 0000000000..203feae86d Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png b/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png new file mode 100644 index 0000000000..28138b5c0b Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png b/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png new file mode 100644 index 0000000000..7245edb975 Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png b/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png new file mode 100644 index 0000000000..32809ccefb Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png new file mode 100644 index 0000000000..75e48ce3ab Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png new file mode 100644 index 0000000000..57c1f43e56 Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png new file mode 100644 index 0000000000..3649321b62 Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png new file mode 100644 index 0000000000..87d03db828 Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png differ diff --git a/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png new file mode 100644 index 0000000000..a468a92622 Binary files /dev/null and b/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png differ diff --git a/docs/chapter_graph/graph_operations.md b/docs/chapter_graph/graph_operations.md new file mode 100644 index 0000000000..4ca441d37e --- /dev/null +++ b/docs/chapter_graph/graph_operations.md @@ -0,0 +1,86 @@ +# 图的基础操作 + +图的基础操作可分为对“边”的操作和对“顶点”的操作。在“邻接矩阵”和“邻接表”两种表示方法下,实现方式有所不同。 + +## 基于邻接矩阵的实现 + +给定一个顶点数量为 $n$ 的无向图,则各种操作的实现方式如下图所示。 + +- **添加或删除边**:直接在邻接矩阵中修改指定的边即可,使用 $O(1)$ 时间。而由于是无向图,因此需要同时更新两个方向的边。 +- **添加顶点**:在邻接矩阵的尾部添加一行一列,并全部填 $0$ 即可,使用 $O(n)$ 时间。 +- **删除顶点**:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 $(n-1)^2$ 个元素“向左上移动”,从而使用 $O(n^2)$ 时间。 +- **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。 + +=== "初始化邻接矩阵" + ![邻接矩阵的初始化、增删边、增删顶点](graph_operations.assets/adjacency_matrix_step1_initialization.png) + +=== "添加边" + ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) + +=== "删除边" + ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) + +=== "添加顶点" + ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) + +=== "删除顶点" + ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) + +以下是基于邻接矩阵表示图的实现代码: + +```src +[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} +``` + +## 基于邻接表的实现 + +设无向图的顶点总数为 $n$、边总数为 $m$ ,则可根据下图所示的方法实现各种操作。 + +- **添加边**:在顶点对应链表的末尾添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。 +- **删除边**:在顶点对应链表中查找并删除指定边,使用 $O(m)$ 时间。在无向图中,需要同时删除两个方向的边。 +- **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头节点,使用 $O(1)$ 时间。 +- **删除顶点**:需遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(n + m)$ 时间。 +- **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。 + +=== "初始化邻接表" + ![邻接表的初始化、增删边、增删顶点](graph_operations.assets/adjacency_list_step1_initialization.png) + +=== "添加边" + ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) + +=== "删除边" + ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) + +=== "添加顶点" + ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) + +=== "删除顶点" + ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) + +以下是邻接表的代码实现。对比上图,实际代码有以下不同。 + +- 为了方便添加与删除顶点,以及简化代码,我们使用列表(动态数组)来代替链表。 +- 使用哈希表来存储邻接表,`key` 为顶点实例,`value` 为该顶点的邻接顶点列表(链表)。 + +另外,我们在邻接表中使用 `Vertex` 类来表示顶点,这样做的原因是:如果与邻接矩阵一样,用列表索引来区分不同顶点,那么假设要删除索引为 $i$ 的顶点,则需遍历整个邻接表,将所有大于 $i$ 的索引全部减 $1$ ,效率很低。而如果每个顶点都是唯一的 `Vertex` 实例,删除某一顶点之后就无须改动其他顶点了。 + +```src +[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} +``` + +## 效率对比 + +设图中共有 $n$ 个顶点和 $m$ 条边,下表对比了邻接矩阵和邻接表的时间效率和空间效率。请注意,邻接表(链表)对应本文实现,而邻接表(哈希表)专指将所有链表替换为哈希表后的实现。 + +

  邻接矩阵与邻接表对比

+ +| | 邻接矩阵 | 邻接表(链表) | 邻接表(哈希表) | +| ------------ | -------- | -------------- | ---------------- | +| 判断是否邻接 | $O(1)$ | $O(n)$ | $O(1)$ | +| 添加边 | $O(1)$ | $O(1)$ | $O(1)$ | +| 删除边 | $O(1)$ | $O(n)$ | $O(1)$ | +| 添加顶点 | $O(n)$ | $O(1)$ | $O(1)$ | +| 删除顶点 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| 内存空间占用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | + +观察上表,似乎邻接表(哈希表)的时间效率与空间效率最优。但实际上,在邻接矩阵中操作边的效率更高,只需一次数组访问或赋值操作即可。综合来看,邻接矩阵体现了“以空间换时间”的原则,而邻接表体现了“以时间换空间”的原则。 diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs.png new file mode 100644 index 0000000000..96a68291f4 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png new file mode 100644 index 0000000000..f8a1eaa993 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png new file mode 100644 index 0000000000..a4f234f480 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png new file mode 100644 index 0000000000..74c1a9bbd0 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png new file mode 100644 index 0000000000..7082c2b17d Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png new file mode 100644 index 0000000000..cf07cda634 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png new file mode 100644 index 0000000000..c29791cb85 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png new file mode 100644 index 0000000000..0da0025be5 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png new file mode 100644 index 0000000000..8f00612388 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png new file mode 100644 index 0000000000..9714f10d83 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png new file mode 100644 index 0000000000..c913aadc68 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png new file mode 100644 index 0000000000..f0baca336b Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs.png new file mode 100644 index 0000000000..47939ed078 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png new file mode 100644 index 0000000000..1b5c6545b5 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png new file mode 100644 index 0000000000..1546427bdf Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png new file mode 100644 index 0000000000..7a4b0854cb Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png new file mode 100644 index 0000000000..af2ab1451b Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png new file mode 100644 index 0000000000..3be25b6998 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png new file mode 100644 index 0000000000..c5a8c88106 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png new file mode 100644 index 0000000000..1f12ccb5f3 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png new file mode 100644 index 0000000000..08086c855f Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png new file mode 100644 index 0000000000..61965827de Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png new file mode 100644 index 0000000000..d6cf543ab9 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png differ diff --git a/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png new file mode 100644 index 0000000000..78759442f1 Binary files /dev/null and b/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png differ diff --git a/docs/chapter_graph/graph_traversal.md b/docs/chapter_graph/graph_traversal.md new file mode 100644 index 0000000000..afea4b0a02 --- /dev/null +++ b/docs/chapter_graph/graph_traversal.md @@ -0,0 +1,140 @@ +# 图的遍历 + +树代表的是“一对多”的关系,而图则具有更高的自由度,可以表示任意的“多对多”关系。因此,我们可以把树看作图的一种特例。显然,**树的遍历操作也是图的遍历操作的一种特例**。 + +图和树都需要应用搜索算法来实现遍历操作。图的遍历方式也可分为两种:广度优先遍历深度优先遍历。 + +## 广度优先遍历 + +**广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外扩张**。如下图所示,从左上角顶点出发,首先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。 + +![图的广度优先遍历](graph_traversal.assets/graph_bfs.png) + +### 算法实现 + +BFS 通常借助队列来实现,代码如下所示。队列具有“先入先出”的性质,这与 BFS 的“由近及远”的思想异曲同工。 + +1. 将遍历起始顶点 `startVet` 加入队列,并开启循环。 +2. 在循环的每轮迭代中,弹出队首顶点并记录访问,然后将该顶点的所有邻接顶点加入到队列尾部。 +3. 循环步骤 `2.` ,直到所有顶点被访问完毕后结束。 + +为了防止重复遍历顶点,我们需要借助一个哈希集合 `visited` 来记录哪些节点已被访问。 + +!!! tip + + 哈希集合可以看作一个只存储 `key` 而不存储 `value` 的哈希表,它可以在 $O(1)$ 时间复杂度下进行 `key` 的增删查改操作。根据 `key` 的唯一性,哈希集合通常用于数据去重等场景。 + +```src +[file]{graph_bfs}-[class]{}-[func]{graph_bfs} +``` + +代码相对抽象,建议对照下图来加深理解。 + +=== "<1>" + ![图的广度优先遍历步骤](graph_traversal.assets/graph_bfs_step1.png) + +=== "<2>" + ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) + +=== "<3>" + ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) + +=== "<4>" + ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) + +=== "<5>" + ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) + +=== "<6>" + ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) + +=== "<7>" + ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) + +=== "<8>" + ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) + +=== "<9>" + ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) + +=== "<10>" + ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) + +=== "<11>" + ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) + +!!! question "广度优先遍历的序列是否唯一?" + + 不唯一。广度优先遍历只要求按“由近及远”的顺序遍历,**而多个相同距离的顶点的遍历顺序允许被任意打乱**。以上图为例,顶点 $1$、$3$ 的访问顺序可以交换,顶点 $2$、$4$、$6$ 的访问顺序也可以任意交换。 + +### 复杂度分析 + +**时间复杂度**:所有顶点都会入队并出队一次,使用 $O(|V|)$ 时间;在遍历邻接顶点的过程中,由于是无向图,因此所有边都会被访问 $2$ 次,使用 $O(2|E|)$ 时间;总体使用 $O(|V| + |E|)$ 时间。 + +**空间复杂度**:列表 `res` ,哈希集合 `visited` ,队列 `que` 中的顶点数量最多为 $|V|$ ,使用 $O(|V|)$ 空间。 + +## 深度优先遍历 + +**深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。如下图所示,从左上角顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。 + +![图的深度优先遍历](graph_traversal.assets/graph_dfs.png) + +### 算法实现 + +这种“走到尽头再返回”的算法范式通常基于递归来实现。与广度优先遍历类似,在深度优先遍历中,我们也需要借助一个哈希集合 `visited` 来记录已被访问的顶点,以避免重复访问顶点。 + +```src +[file]{graph_dfs}-[class]{}-[func]{graph_dfs} +``` + +深度优先遍历的算法流程如下图所示。 + +- **直虚线代表向下递推**,表示开启了一个新的递归方法来访问新顶点。 +- **曲虚线代表向上回溯**,表示此递归方法已经返回,回溯到了开启此方法的位置。 + +为了加深理解,建议将下图与代码结合起来,在脑中模拟(或者用笔画下来)整个 DFS 过程,包括每个递归方法何时开启、何时返回。 + +=== "<1>" + ![图的深度优先遍历步骤](graph_traversal.assets/graph_dfs_step1.png) + +=== "<2>" + ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) + +=== "<3>" + ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) + +=== "<4>" + ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) + +=== "<5>" + ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) + +=== "<6>" + ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) + +=== "<7>" + ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) + +=== "<8>" + ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) + +=== "<9>" + ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) + +=== "<10>" + ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) + +=== "<11>" + ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) + +!!! question "深度优先遍历的序列是否唯一?" + + 与广度优先遍历类似,深度优先遍历序列的顺序也不是唯一的。给定某顶点,先往哪个方向探索都可以,即邻接顶点的顺序可以任意打乱,都是深度优先遍历。 + + 以树的遍历为例,“根 $\rightarrow$ 左 $\rightarrow$ 右”“左 $\rightarrow$ 根 $\rightarrow$ 右”“左 $\rightarrow$ 右 $\rightarrow$ 根”分别对应前序、中序、后序遍历,它们展示了三种遍历优先级,然而这三者都属于深度优先遍历。 + +### 复杂度分析 + +**时间复杂度**:所有顶点都会被访问 $1$ 次,使用 $O(|V|)$ 时间;所有边都会被访问 $2$ 次,使用 $O(2|E|)$ 时间;总体使用 $O(|V| + |E|)$ 时间。 + +**空间复杂度**:列表 `res` ,哈希集合 `visited` 顶点数量最多为 $|V|$ ,递归深度最大为 $|V|$ ,因此使用 $O(|V|)$ 空间。 diff --git a/docs/chapter_graph/index.md b/docs/chapter_graph/index.md new file mode 100644 index 0000000000..10318079b5 --- /dev/null +++ b/docs/chapter_graph/index.md @@ -0,0 +1,9 @@ +# 图 + +![图](../assets/covers/chapter_graph.jpg) + +!!! abstract + + 在生命旅途中,我们就像是一个个节点,被无数看不见的边相连。 + + 每一次的相识与相离,都在这张巨大的网络图中留下独特的印记。 diff --git a/docs/chapter_graph/summary.md b/docs/chapter_graph/summary.md new file mode 100644 index 0000000000..af211495e3 --- /dev/null +++ b/docs/chapter_graph/summary.md @@ -0,0 +1,31 @@ +# 小结 + +### 重点回顾 + +- 图由顶点和边组成,可以表示为一组顶点和一组边构成的集合。 +- 相较于线性关系(链表)和分治关系(树),网络关系(图)具有更高的自由度,因而更为复杂。 +- 有向图的边具有方向性,连通图中的任意顶点均可达,有权图的每条边都包含权重变量。 +- 邻接矩阵利用矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 $1$ 或 $0$ 表示两个顶点之间有边或无边。邻接矩阵在增删查改操作上效率很高,但空间占用较多。 +- 邻接表使用多个链表来表示图,第 $i$ 个链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点。邻接表相对于邻接矩阵更加节省空间,但由于需要遍历链表来查找边,因此时间效率较低。 +- 当邻接表中的链表过长时,可以将其转换为红黑树或哈希表,从而提升查询效率。 +- 从算法思想的角度分析,邻接矩阵体现了“以空间换时间”,邻接表体现了“以时间换空间”。 +- 图可用于建模各类现实系统,如社交网络、地铁线路等。 +- 树是图的一种特例,树的遍历也是图的遍历的一种特例。 +- 图的广度优先遍历是一种由近及远、层层扩张的搜索方式,通常借助队列实现。 +- 图的深度优先遍历是一种优先走到底、无路可走时再回溯的搜索方式,常基于递归来实现。 + +### Q & A + +**Q**:路径的定义是顶点序列还是边序列? + +维基百科上不同语言版本的定义不一致:英文版是“路径是一个边序列”,而中文版是“路径是一个顶点序列”。以下是英文版原文:In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. + +在本文中,路径被视为一个边序列,而不是一个顶点序列。这是因为两个顶点之间可能存在多条边连接,此时每条边都对应一条路径。 + +**Q**:非连通图中是否会有无法遍历到的点? + +在非连通图中,从某个顶点出发,至少有一个顶点无法到达。遍历非连通图需要设置多个起点,以遍历到图的所有连通分量。 + +**Q**:在邻接表中,“与该顶点相连的所有顶点”的顶点顺序是否有要求? + +可以是任意顺序。但在实际应用中,可能需要按照指定规则来排序,比如按照顶点添加的次序,或者按照顶点值大小的顺序等,这样有助于快速查找“带有某种极值”的顶点。 diff --git a/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png new file mode 100644 index 0000000000..28023758d1 Binary files /dev/null and b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png differ diff --git a/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png new file mode 100644 index 0000000000..dc2285f6b7 Binary files /dev/null and b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png differ diff --git a/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png new file mode 100644 index 0000000000..713f30addb Binary files /dev/null and b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png differ diff --git a/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png new file mode 100644 index 0000000000..7e4199ca0f Binary files /dev/null and b/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png differ diff --git a/docs/chapter_greedy/fractional_knapsack_problem.md b/docs/chapter_greedy/fractional_knapsack_problem.md new file mode 100644 index 0000000000..33e064d853 --- /dev/null +++ b/docs/chapter_greedy/fractional_knapsack_problem.md @@ -0,0 +1,52 @@ +# 分数背包问题 + +!!! question + + 给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,**但可以选择物品的一部分,价值根据选择的重量比例计算**,问在限定背包容量下背包中物品的最大价值。示例如下图所示。 + +![分数背包问题的示例数据](fractional_knapsack_problem.assets/fractional_knapsack_example.png) + +分数背包问题和 0-1 背包问题整体上非常相似,状态包含当前物品 $i$ 和容量 $c$ ,目标是求限定背包容量下的最大价值。 + +不同点在于,本题允许只选择物品的一部分。如下图所示,**我们可以对物品任意地进行切分,并按照重量比例来计算相应价值**。 + +1. 对于物品 $i$ ,它在单位重量下的价值为 $val[i-1] / wgt[i-1]$ ,简称单位价值。 +2. 假设放入一部分物品 $i$ ,重量为 $w$ ,则背包增加的价值为 $w \times val[i-1] / wgt[i-1]$ 。 + +![物品在单位重量下的价值](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) + +### 贪心策略确定 + +最大化背包内物品总价值,**本质上是最大化单位重量下的物品价值**。由此便可推理出下图所示的贪心策略。 + +1. 将物品按照单位价值从高到低进行排序。 +2. 遍历所有物品,**每轮贪心地选择单位价值最高的物品**。 +3. 若剩余背包容量不足,则使用当前物品的一部分填满背包。 + +![分数背包问题的贪心策略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) + +### 代码实现 + +我们建立了一个物品类 `Item` ,以便将物品按照单位价值进行排序。循环进行贪心选择,当背包已满时跳出并返回解: + +```src +[file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} +``` + +内置排序算法的时间复杂度通常为 $O(\log n)$ ,空间复杂度通常为 $O(\log n)$ 或 $O(n)$ ,取决于编程语言的具体实现。 + +除排序之外,在最差情况下,需要遍历整个物品列表,**因此时间复杂度为 $O(n)$** ,其中 $n$ 为物品数量。 + +由于初始化了一个 `Item` 对象列表,**因此空间复杂度为 $O(n)$** 。 + +### 正确性证明 + +采用反证法。假设物品 $x$ 是单位价值最高的物品,使用某算法求得最大价值为 `res` ,但该解中不包含物品 $x$ 。 + +现在从背包中拿出单位重量的任意物品,并替换为单位重量的物品 $x$ 。由于物品 $x$ 的单位价值最高,因此替换后的总价值一定大于 `res` 。**这与 `res` 是最优解矛盾,说明最优解中必须包含物品 $x$** 。 + +对于该解中的其他物品,我们也可以构建出上述矛盾。总而言之,**单位价值更大的物品总是更优选择**,这说明贪心策略是有效的。 + +如下图所示,如果将物品重量和物品单位价值分别看作一张二维图表的横轴和纵轴,则分数背包问题可转化为“求在有限横轴区间下围成的最大面积”。这个类比可以帮助我们从几何角度理解贪心策略的有效性。 + +![分数背包问题的几何表示](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) diff --git a/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png b/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png new file mode 100644 index 0000000000..1f8b4e8d2b Binary files /dev/null and b/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png differ diff --git a/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png b/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png new file mode 100644 index 0000000000..d13ed01a16 Binary files /dev/null and b/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png differ diff --git a/docs/chapter_greedy/greedy_algorithm.md b/docs/chapter_greedy/greedy_algorithm.md new file mode 100644 index 0000000000..1a961fe720 --- /dev/null +++ b/docs/chapter_greedy/greedy_algorithm.md @@ -0,0 +1,94 @@ +# 贪心算法 + +贪心算法(greedy algorithm)是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。贪心算法简洁且高效,在许多实际问题中有着广泛的应用。 + +贪心算法和动态规划都常用于解决优化问题。它们之间存在一些相似之处,比如都依赖最优子结构性质,但工作原理不同。 + +- 动态规划会根据之前阶段的所有决策来考虑当前决策,并使用过去子问题的解来构建当前子问题的解。 +- 贪心算法不会考虑过去的决策,而是一路向前地进行贪心选择,不断缩小问题范围,直至问题被解决。 + +我们先通过例题“零钱兑换”了解贪心算法的工作原理。这道题已经在“完全背包问题”章节中介绍过,相信你对它并不陌生。 + +!!! question + + 给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,每种硬币可以重复选取,问能够凑出目标金额的最少硬币数量。如果无法凑出目标金额,则返回 $-1$ 。 + +本题采取的贪心策略如下图所示。给定目标金额,**我们贪心地选择不大于且最接近它的硬币**,不断循环该步骤,直至凑出目标金额为止。 + +![零钱兑换的贪心策略](greedy_algorithm.assets/coin_change_greedy_strategy.png) + +实现代码如下所示: + +```src +[file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} +``` + +你可能会不由地发出感叹:So clean !贪心算法仅用约十行代码就解决了零钱兑换问题。 + +## 贪心算法的优点与局限性 + +**贪心算法不仅操作直接、实现简单,而且通常效率也很高**。在以上代码中,记硬币最小面值为 $\min(coins)$ ,则贪心选择最多循环 $amt / \min(coins)$ 次,时间复杂度为 $O(amt / \min(coins))$ 。这比动态规划解法的时间复杂度 $O(n \times amt)$ 小了一个数量级。 + +然而,**对于某些硬币面值组合,贪心算法并不能找到最优解**。下图给出了两个示例。 + +- **正例 $coins = [1, 5, 10, 20, 50, 100]$**:在该硬币组合下,给定任意 $amt$ ,贪心算法都可以找到最优解。 +- **反例 $coins = [1, 20, 50]$**:假设 $amt = 60$ ,贪心算法只能找到 $50 + 1 \times 10$ 的兑换组合,共计 $11$ 枚硬币,但动态规划可以找到最优解 $20 + 20 + 20$ ,仅需 $3$ 枚硬币。 +- **反例 $coins = [1, 49, 50]$**:假设 $amt = 98$ ,贪心算法只能找到 $50 + 1 \times 48$ 的兑换组合,共计 $49$ 枚硬币,但动态规划可以找到最优解 $49 + 49$ ,仅需 $2$ 枚硬币。 + +![贪心算法无法找出最优解的示例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) + +也就是说,对于零钱兑换问题,贪心算法无法保证找到全局最优解,并且有可能找到非常差的解。它更适合用动态规划解决。 + +一般情况下,贪心算法的适用情况分以下两种。 + +1. **可以保证找到最优解**:贪心算法在这种情况下往往是最优选择,因为它往往比回溯、动态规划更高效。 +2. **可以找到近似最优解**:贪心算法在这种情况下也是可用的。对于很多复杂问题来说,寻找全局最优解非常困难,能以较高效率找到次优解也是非常不错的。 + +## 贪心算法特性 + +那么问题来了,什么样的问题适合用贪心算法求解呢?或者说,贪心算法在什么情况下可以保证找到最优解? + +相较于动态规划,贪心算法的使用条件更加苛刻,其主要关注问题的两个性质。 + +- **贪心选择性质**:只有当局部最优选择始终可以导致全局最优解时,贪心算法才能保证得到最优解。 +- **最优子结构**:原问题的最优解包含子问题的最优解。 + +最优子结构已经在“动态规划”章节中介绍过,这里不再赘述。值得注意的是,一些问题的最优子结构并不明显,但仍然可使用贪心算法解决。 + +我们主要探究贪心选择性质的判断方法。虽然它的描述看上去比较简单,**但实际上对于许多问题,证明贪心选择性质并非易事**。 + +例如零钱兑换问题,我们虽然能够容易地举出反例,对贪心选择性质进行证伪,但证实的难度较大。如果问:**满足什么条件的硬币组合可以使用贪心算法求解**?我们往往只能凭借直觉或举例子来给出一个模棱两可的答案,而难以给出严谨的数学证明。 + +!!! quote + + 有一篇论文给出了一个 $O(n^3)$ 时间复杂度的算法,用于判断一个硬币组合能否使用贪心算法找出任意金额的最优解。 + + Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. + +## 贪心算法解题步骤 + +贪心问题的解决流程大体可分为以下三步。 + +1. **问题分析**:梳理与理解问题特性,包括状态定义、优化目标和约束条件等。这一步在回溯和动态规划中都有涉及。 +2. **确定贪心策略**:确定如何在每一步中做出贪心选择。这个策略能够在每一步减小问题的规模,并最终解决整个问题。 +3. **正确性证明**:通常需要证明问题具有贪心选择性质和最优子结构。这个步骤可能需要用到数学证明,例如归纳法或反证法等。 + +确定贪心策略是求解问题的核心步骤,但实施起来可能并不容易,主要有以下原因。 + +- **不同问题的贪心策略的差异较大**。对于许多问题来说,贪心策略比较浅显,我们通过一些大概的思考与尝试就能得出。而对于一些复杂问题,贪心策略可能非常隐蔽,这种情况就非常考验个人的解题经验与算法能力了。 +- **某些贪心策略具有较强的迷惑性**。当我们满怀信心设计好贪心策略,写出解题代码并提交运行,很可能发现部分测试样例无法通过。这是因为设计的贪心策略只是“部分正确”的,上文介绍的零钱兑换就是一个典型案例。 + +为了保证正确性,我们应该对贪心策略进行严谨的数学证明,**通常需要用到反证法或数学归纳法**。 + +然而,正确性证明也很可能不是一件易事。如若没有头绪,我们通常会选择面向测试用例进行代码调试,一步步修改与验证贪心策略。 + +## 贪心算法典型例题 + +贪心算法常常应用在满足贪心选择性质和最优子结构的优化问题中,以下列举了一些典型的贪心算法问题。 + +- **硬币找零问题**:在某些硬币组合下,贪心算法总是可以得到最优解。 +- **区间调度问题**:假设你有一些任务,每个任务在一段时间内进行,你的目标是完成尽可能多的任务。如果每次都选择结束时间最早的任务,那么贪心算法就可以得到最优解。 +- **分数背包问题**:给定一组物品和一个载重量,你的目标是选择一组物品,使得总重量不超过载重量,且总价值最大。如果每次都选择性价比最高(价值 / 重量)的物品,那么贪心算法在一些情况下可以得到最优解。 +- **股票买卖问题**:给定一组股票的历史价格,你可以进行多次买卖,但如果你已经持有股票,那么在卖出之前不能再买,目标是获取最大利润。 +- **霍夫曼编码**:霍夫曼编码是一种用于无损数据压缩的贪心算法。通过构建霍夫曼树,每次选择出现频率最低的两个节点合并,最后得到的霍夫曼树的带权路径长度(编码长度)最小。 +- **Dijkstra 算法**:它是一种解决给定源顶点到其余各顶点的最短路径问题的贪心算法。 diff --git a/docs/chapter_greedy/index.md b/docs/chapter_greedy/index.md new file mode 100644 index 0000000000..69d9075b38 --- /dev/null +++ b/docs/chapter_greedy/index.md @@ -0,0 +1,9 @@ +# 贪心 + +![贪心](../assets/covers/chapter_greedy.jpg) + +!!! abstract + + 向日葵朝着太阳转动,时刻追求自身成长的最大可能。 + + 贪心策略在一轮轮的简单选择中,逐步导向最佳答案。 diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png new file mode 100644 index 0000000000..4d4b883dc1 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png new file mode 100644 index 0000000000..9d7f8aa6bc Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png new file mode 100644 index 0000000000..3f2a083942 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png new file mode 100644 index 0000000000..581ee2701c Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png new file mode 100644 index 0000000000..f1e6e18126 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png new file mode 100644 index 0000000000..4326190544 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png new file mode 100644 index 0000000000..674fbc0a0c Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png new file mode 100644 index 0000000000..0a5a1c47d3 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png new file mode 100644 index 0000000000..836ef9436b Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png new file mode 100644 index 0000000000..22519228f9 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png new file mode 100644 index 0000000000..5ffb164065 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png new file mode 100644 index 0000000000..cf00baaa89 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png new file mode 100644 index 0000000000..7f082e87ac Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png new file mode 100644 index 0000000000..45b3b52d08 Binary files /dev/null and b/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png differ diff --git a/docs/chapter_greedy/max_capacity_problem.md b/docs/chapter_greedy/max_capacity_problem.md new file mode 100644 index 0000000000..fd977c5f5d --- /dev/null +++ b/docs/chapter_greedy/max_capacity_problem.md @@ -0,0 +1,99 @@ +# 最大容量问题 + +!!! question + + 输入一个数组 $ht$ ,其中的每个元素代表一个垂直隔板的高度。数组中的任意两个隔板,以及它们之间的空间可以组成一个容器。 + + 容器的容量等于高度和宽度的乘积(面积),其中高度由较短的隔板决定,宽度是两个隔板的数组索引之差。 + + 请在数组中选择两个隔板,使得组成的容器的容量最大,返回最大容量。示例如下图所示。 + +![最大容量问题的示例数据](max_capacity_problem.assets/max_capacity_example.png) + +容器由任意两个隔板围成,**因此本题的状态为两个隔板的索引,记为 $[i, j]$** 。 + +根据题意,容量等于高度乘以宽度,其中高度由短板决定,宽度是两隔板的数组索引之差。设容量为 $cap[i, j]$ ,则可得计算公式: + +$$ +cap[i, j] = \min(ht[i], ht[j]) \times (j - i) +$$ + +设数组长度为 $n$ ,两个隔板的组合数量(状态总数)为 $C_n^2 = \frac{n(n - 1)}{2}$ 个。最直接地,**我们可以穷举所有状态**,从而求得最大容量,时间复杂度为 $O(n^2)$ 。 + +### 贪心策略确定 + +这道题还有更高效率的解法。如下图所示,现选取一个状态 $[i, j]$ ,其满足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 为短板、$j$ 为长板。 + +![初始状态](max_capacity_problem.assets/max_capacity_initial_state.png) + +如下图所示,**若此时将长板 $j$ 向短板 $i$ 靠近,则容量一定变小**。 + +这是因为在移动长板 $j$ 后,宽度 $j-i$ 肯定变小;而高度由短板决定,因此高度只可能不变( $i$ 仍为短板)或变小(移动后的 $j$ 成为短板)。 + +![向内移动长板后的状态](max_capacity_problem.assets/max_capacity_moving_long_board.png) + +反向思考,**我们只有向内收缩短板 $i$ ,才有可能使容量变大**。因为虽然宽度一定变小,**但高度可能会变大**(移动后的短板 $i$ 可能会变长)。例如在下图中,移动短板后面积变大。 + +![向内移动短板后的状态](max_capacity_problem.assets/max_capacity_moving_short_board.png) + +由此便可推出本题的贪心策略:初始化两指针,使其分列容器两端,每轮向内收缩短板对应的指针,直至两指针相遇。 + +下图展示了贪心策略的执行过程。 + +1. 初始状态下,指针 $i$ 和 $j$ 分列数组两端。 +2. 计算当前状态的容量 $cap[i, j]$ ,并更新最大容量。 +3. 比较板 $i$ 和板 $j$ 的高度,并将短板向内移动一格。 +4. 循环执行第 `2.` 步和第 `3.` 步,直至 $i$ 和 $j$ 相遇时结束。 + +=== "<1>" + ![最大容量问题的贪心过程](max_capacity_problem.assets/max_capacity_greedy_step1.png) + +=== "<2>" + ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) + +=== "<3>" + ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) + +=== "<4>" + ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) + +=== "<5>" + ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) + +=== "<6>" + ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) + +=== "<7>" + ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) + +=== "<8>" + ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) + +=== "<9>" + ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) + +### 代码实现 + +代码循环最多 $n$ 轮,**因此时间复杂度为 $O(n)$** 。 + +变量 $i$、$j$、$res$ 使用常数大小的额外空间,**因此空间复杂度为 $O(1)$** 。 + +```src +[file]{max_capacity}-[class]{}-[func]{max_capacity} +``` + +### 正确性证明 + +之所以贪心比穷举更快,是因为每轮的贪心选择都会“跳过”一些状态。 + +比如在状态 $cap[i, j]$ 下,$i$ 为短板、$j$ 为长板。若贪心地将短板 $i$ 向内移动一格,会导致下图所示的状态被“跳过”。**这意味着之后无法验证这些状态的容量大小**。 + +$$ +cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] +$$ + +![移动短板导致被跳过的状态](max_capacity_problem.assets/max_capacity_skipped_states.png) + +观察发现,**这些被跳过的状态实际上就是将长板 $j$ 向内移动的所有状态**。前面我们已经证明内移长板一定会导致容量变小。也就是说,被跳过的状态都不可能是最优解,**跳过它们不会导致错过最优解**。 + +以上分析说明,移动短板的操作是“安全”的,贪心策略是有效的。 diff --git a/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png new file mode 100644 index 0000000000..fae0741147 Binary files /dev/null and b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png differ diff --git a/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png new file mode 100644 index 0000000000..c8fb60a5aa Binary files /dev/null and b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png differ diff --git a/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png new file mode 100644 index 0000000000..b6d6539e3a Binary files /dev/null and b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png differ diff --git a/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png new file mode 100644 index 0000000000..0d2da2fa16 Binary files /dev/null and b/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png differ diff --git a/docs/chapter_greedy/max_product_cutting_problem.md b/docs/chapter_greedy/max_product_cutting_problem.md new file mode 100644 index 0000000000..e282a92939 --- /dev/null +++ b/docs/chapter_greedy/max_product_cutting_problem.md @@ -0,0 +1,85 @@ +# 最大切分乘积问题 + +!!! question + + 给定一个正整数 $n$ ,将其切分为至少两个正整数的和,求切分后所有整数的乘积最大是多少,如下图所示。 + +![最大切分乘积的问题定义](max_product_cutting_problem.assets/max_product_cutting_definition.png) + +假设我们将 $n$ 切分为 $m$ 个整数因子,其中第 $i$ 个因子记为 $n_i$ ,即 + +$$ +n = \sum_{i=1}^{m}n_i +$$ + +本题的目标是求得所有整数因子的最大乘积,即 + +$$ +\max(\prod_{i=1}^{m}n_i) +$$ + +我们需要思考的是:切分数量 $m$ 应该多大,每个 $n_i$ 应该是多少? + +### 贪心策略确定 + +根据经验,两个整数的乘积往往比它们的加和更大。假设从 $n$ 中分出一个因子 $2$ ,则它们的乘积为 $2(n-2)$ 。我们将该乘积与 $n$ 作比较: + +$$ +\begin{aligned} +2(n-2) & \geq n \newline +2n - n - 4 & \geq 0 \newline +n & \geq 4 +\end{aligned} +$$ + +如下图所示,当 $n \geq 4$ 时,切分出一个 $2$ 后乘积会变大,**这说明大于等于 $4$ 的整数都应该被切分**。 + +**贪心策略一**:如果切分方案中包含 $\geq 4$ 的因子,那么它就应该被继续切分。最终的切分方案只应出现 $1$、$2$、$3$ 这三种因子。 + +![切分导致乘积变大](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) + +接下来思考哪个因子是最优的。在 $1$、$2$、$3$ 这三个因子中,显然 $1$ 是最差的,因为 $1 \times (n-1) < n$ 恒成立,即切分出 $1$ 反而会导致乘积减小。 + +如下图所示,当 $n = 6$ 时,有 $3 \times 3 > 2 \times 2 \times 2$ 。**这意味着切分出 $3$ 比切分出 $2$ 更优**。 + +**贪心策略二**:在切分方案中,最多只应存在两个 $2$ 。因为三个 $2$ 总是可以替换为两个 $3$ ,从而获得更大的乘积。 + +![最优切分因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) + +综上所述,可推理出以下贪心策略。 + +1. 输入整数 $n$ ,从其不断地切分出因子 $3$ ,直至余数为 $0$、$1$、$2$ 。 +2. 当余数为 $0$ 时,代表 $n$ 是 $3$ 的倍数,因此不做任何处理。 +3. 当余数为 $2$ 时,不继续划分,保留。 +4. 当余数为 $1$ 时,由于 $2 \times 2 > 1 \times 3$ ,因此应将最后一个 $3$ 替换为 $2$ 。 + +### 代码实现 + +如下图所示,我们无须通过循环来切分整数,而可以利用向下整除运算得到 $3$ 的个数 $a$ ,用取模运算得到余数 $b$ ,此时有: + +$$ +n = 3 a + b +$$ + +请注意,对于 $n \leq 3$ 的边界情况,必须拆分出一个 $1$ ,乘积为 $1 \times (n - 1)$ 。 + +```src +[file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} +``` + +![最大切分乘积的计算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) + +**时间复杂度取决于编程语言的幂运算的实现方法**。以 Python 为例,常用的幂计算函数有三种。 + +- 运算符 `**` 和函数 `pow()` 的时间复杂度均为 $O(\log⁡ a)$ 。 +- 函数 `math.pow()` 内部调用 C 语言库的 `pow()` 函数,其执行浮点取幂,时间复杂度为 $O(1)$ 。 + +变量 $a$ 和 $b$ 使用常数大小的额外空间,**因此空间复杂度为 $O(1)$** 。 + +### 正确性证明 + +使用反证法,只分析 $n \geq 3$ 的情况。 + +1. **所有因子 $\leq 3$** :假设最优切分方案中存在 $\geq 4$ 的因子 $x$ ,那么一定可以将其继续划分为 $2(x-2)$ ,从而获得更大的乘积。这与假设矛盾。 +2. **切分方案不包含 $1$** :假设最优切分方案中存在一个因子 $1$ ,那么它一定可以合并入另外一个因子中,以获得更大的乘积。这与假设矛盾。 +3. **切分方案最多包含两个 $2$** :假设最优切分方案中包含三个 $2$ ,那么一定可以替换为两个 $3$ ,乘积更大。这与假设矛盾。 diff --git a/docs/chapter_greedy/summary.md b/docs/chapter_greedy/summary.md new file mode 100644 index 0000000000..caefed243b --- /dev/null +++ b/docs/chapter_greedy/summary.md @@ -0,0 +1,12 @@ +# 小结 + +- 贪心算法通常用于解决最优化问题,其原理是在每个决策阶段都做出局部最优的决策,以期获得全局最优解。 +- 贪心算法会迭代地做出一个又一个的贪心选择,每轮都将问题转化成一个规模更小的子问题,直到问题被解决。 +- 贪心算法不仅实现简单,还具有很高的解题效率。相比于动态规划,贪心算法的时间复杂度通常更低。 +- 在零钱兑换问题中,对于某些硬币组合,贪心算法可以保证找到最优解;对于另外一些硬币组合则不然,贪心算法可能找到很差的解。 +- 适合用贪心算法求解的问题具有两大性质:贪心选择性质和最优子结构。贪心选择性质代表贪心策略的有效性。 +- 对于某些复杂问题,贪心选择性质的证明并不简单。相对来说,证伪更加容易,例如零钱兑换问题。 +- 求解贪心问题主要分为三步:问题分析、确定贪心策略、正确性证明。其中,确定贪心策略是核心步骤,正确性证明往往是难点。 +- 分数背包问题在 0-1 背包的基础上,允许选择物品的一部分,因此可使用贪心算法求解。贪心策略的正确性可以使用反证法来证明。 +- 最大容量问题可使用穷举法求解,时间复杂度为 $O(n^2)$ 。通过设计贪心策略,每轮向内移动短板,可将时间复杂度优化至 $O(n)$ 。 +- 在最大切分乘积问题中,我们先后推理出两个贪心策略:$\geq 4$ 的整数都应该继续切分,最优切分因子为 $3$ 。代码中包含幂运算,时间复杂度取决于幂运算实现方法,通常为 $O(1)$ 或 $O(\log n)$ 。 diff --git a/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png b/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png new file mode 100644 index 0000000000..91a47d0ae6 Binary files /dev/null and b/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png differ diff --git a/docs/chapter_hashing/hash_algorithm.md b/docs/chapter_hashing/hash_algorithm.md new file mode 100644 index 0000000000..3b911432af --- /dev/null +++ b/docs/chapter_hashing/hash_algorithm.md @@ -0,0 +1,416 @@ +# 哈希算法 + +前两节介绍了哈希表的工作原理和哈希冲突的处理方法。然而无论是开放寻址还是链式地址,**它们只能保证哈希表可以在发生冲突时正常工作,而无法减少哈希冲突的发生**。 + +如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如下图所示,对于链式地址哈希表,理想情况下键值对均匀分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都存储到同一个桶中,时间复杂度退化至 $O(n)$ 。 + +![哈希冲突的最佳情况与最差情况](hash_algorithm.assets/hash_collision_best_worst_condition.png) + +**键值对的分布情况由哈希函数决定**。回忆哈希函数的计算步骤,先计算哈希值,再对数组长度取模: + +```shell +index = hash(key) % capacity +``` + +观察以上公式,当哈希表容量 `capacity` 固定时,**哈希算法 `hash()` 决定了输出值**,进而决定了键值对在哈希表中的分布情况。 + +这意味着,为了降低哈希冲突的发生概率,我们应当将注意力集中在哈希算法 `hash()` 的设计上。 + +## 哈希算法的目标 + +为了实现“既快又稳”的哈希表数据结构,哈希算法应具备以下特点。 + +- **确定性**:对于相同的输入,哈希算法应始终产生相同的输出。这样才能确保哈希表是可靠的。 +- **效率高**:计算哈希值的过程应该足够快。计算开销越小,哈希表的实用性越高。 +- **均匀分布**:哈希算法应使得键值对均匀分布在哈希表中。分布越均匀,哈希冲突的概率就越低。 + +实际上,哈希算法除了可以用于实现哈希表,还广泛应用于其他领域中。 + +- **密码存储**:为了保护用户密码的安全,系统通常不会直接存储用户的明文密码,而是存储密码的哈希值。当用户输入密码时,系统会对输入的密码计算哈希值,然后与存储的哈希值进行比较。如果两者匹配,那么密码就被视为正确。 +- **数据完整性检查**:数据发送方可以计算数据的哈希值并将其一同发送;接收方可以重新计算接收到的数据的哈希值,并与接收到的哈希值进行比较。如果两者匹配,那么数据就被视为完整。 + +对于密码学的相关应用,为了防止从哈希值推导出原始密码等逆向工程,哈希算法需要具备更高等级的安全特性。 + +- **单向性**:无法通过哈希值反推出关于输入数据的任何信息。 +- **抗碰撞性**:应当极难找到两个不同的输入,使得它们的哈希值相同。 +- **雪崩效应**:输入的微小变化应当导致输出的显著且不可预测的变化。 + +请注意,**“均匀分布”与“抗碰撞性”是两个独立的概念**,满足均匀分布不一定满足抗碰撞性。例如,在随机输入 `key` 下,哈希函数 `key % 100` 可以产生均匀分布的输出。然而该哈希算法过于简单,所有后两位相等的 `key` 的输出都相同,因此我们可以很容易地从哈希值反推出可用的 `key` ,从而破解密码。 + +## 哈希算法的设计 + +哈希算法的设计是一个需要考虑许多因素的复杂问题。然而对于某些要求不高的场景,我们也能设计一些简单的哈希算法。 + +- **加法哈希**:对输入的每个字符的 ASCII 码进行相加,将得到的总和作为哈希值。 +- **乘法哈希**:利用乘法的不相关性,每轮乘以一个常数,将各个字符的 ASCII 码累积到哈希值中。 +- **异或哈希**:将输入数据的每个元素通过异或操作累积到一个哈希值中。 +- **旋转哈希**:将每个字符的 ASCII 码累积到一个哈希值中,每次累积之前都会对哈希值进行旋转操作。 + +```src +[file]{simple_hash}-[class]{}-[func]{rot_hash} +``` + +观察发现,每种哈希算法的最后一步都是对大质数 $1000000007$ 取模,以确保哈希值在合适的范围内。值得思考的是,为什么要强调对质数取模,或者说对合数取模的弊端是什么?这是一个有趣的问题。 + +先给出结论:**使用大质数作为模数,可以最大化地保证哈希值的均匀分布**。因为质数不与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。 + +举个例子,假设我们选择合数 $9$ 作为模数,它可以被 $3$ 整除,那么所有可以被 $3$ 整除的 `key` 都会被映射到 $0$、$3$、$6$ 这三个哈希值。 + +$$ +\begin{aligned} +\text{modulus} & = 9 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} +\end{aligned} +$$ + +如果输入 `key` 恰好满足这种等差数列的数据分布,那么哈希值就会出现聚堆,从而加重哈希冲突。现在,假设将 `modulus` 替换为质数 $13$ ,由于 `key` 和 `modulus` 之间不存在公约数,因此输出的哈希值的均匀性会明显提升。 + +$$ +\begin{aligned} +\text{modulus} & = 13 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} +\end{aligned} +$$ + +值得说明的是,如果能够保证 `key` 是随机均匀分布的,那么选择质数或者合数作为模数都可以,它们都能输出均匀分布的哈希值。而当 `key` 的分布存在某种周期性时,对合数取模更容易出现聚集现象。 + +总而言之,我们通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。 + +## 常见哈希算法 + +不难发现,以上介绍的简单哈希算法都比较“脆弱”,远远没有达到哈希算法的设计目标。例如,由于加法和异或满足交换律,因此加法哈希和异或哈希无法区分内容相同但顺序不同的字符串,这可能会加剧哈希冲突,并引起一些安全问题。 + +在实际中,我们通常会用一些标准哈希算法,例如 MD5、SHA-1、SHA-2 和 SHA-3 等。它们可以将任意长度的输入数据映射到恒定长度的哈希值。 + +近一个世纪以来,哈希算法处在不断升级与优化的过程中。一部分研究人员努力提升哈希算法的性能,另一部分研究人员和黑客则致力于寻找哈希算法的安全性问题。下表展示了在实际应用中常见的哈希算法。 + +- MD5 和 SHA-1 已多次被成功攻击,因此它们被各类安全应用弃用。 +- SHA-2 系列中的 SHA-256 是最安全的哈希算法之一,仍未出现成功的攻击案例,因此常用在各类安全应用与协议中。 +- SHA-3 相较 SHA-2 的实现开销更低、计算效率更高,但目前使用覆盖度不如 SHA-2 系列。 + +

  常见的哈希算法

+ +| | MD5 | SHA-1 | SHA-2 | SHA-3 | +| -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | +| 推出时间 | 1992 | 1995 | 2002 | 2008 | +| 输出长度 | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | +| 哈希冲突 | 较多 | 较多 | 很少 | 很少 | +| 安全等级 | 低,已被成功攻击 | 低,已被成功攻击 | 高 | 高 | +| 应用 | 已被弃用,仍用于数据完整性检查 | 已被弃用 | 加密货币交易验证、数字签名等 | 可用于替代 SHA-2 | + +## 数据结构的哈希值 + +我们知道,哈希表的 `key` 可以是整数、小数或字符串等数据类型。编程语言通常会为这些数据类型提供内置的哈希算法,用于计算哈希表中的桶索引。以 Python 为例,我们可以调用 `hash()` 函数来计算各种数据类型的哈希值。 + +- 整数和布尔量的哈希值就是其本身。 +- 浮点数和字符串的哈希值计算较为复杂,有兴趣的读者请自行学习。 +- 元组的哈希值是对其中每一个元素进行哈希,然后将这些哈希值组合起来,得到单一的哈希值。 +- 对象的哈希值基于其内存地址生成。通过重写对象的哈希方法,可实现基于内容生成哈希值。 + +!!! tip + + 请注意,不同编程语言的内置哈希值计算函数的定义和方法不同。 + +=== "Python" + + ```python title="built_in_hash.py" + num = 3 + hash_num = hash(num) + # 整数 3 的哈希值为 3 + + bol = True + hash_bol = hash(bol) + # 布尔量 True 的哈希值为 1 + + dec = 3.14159 + hash_dec = hash(dec) + # 小数 3.14159 的哈希值为 326484311674566659 + + str = "Hello 算法" + hash_str = hash(str) + # 字符串“Hello 算法”的哈希值为 4617003410720528961 + + tup = (12836, "小哈") + hash_tup = hash(tup) + # 元组 (12836, '小哈') 的哈希值为 1029005403108185979 + + obj = ListNode(0) + hash_obj = hash(obj) + # 节点对象 的哈希值为 274267521 + ``` + +=== "C++" + + ```cpp title="built_in_hash.cpp" + int num = 3; + size_t hashNum = hash()(num); + // 整数 3 的哈希值为 3 + + bool bol = true; + size_t hashBol = hash()(bol); + // 布尔量 1 的哈希值为 1 + + double dec = 3.14159; + size_t hashDec = hash()(dec); + // 小数 3.14159 的哈希值为 4614256650576692846 + + string str = "Hello 算法"; + size_t hashStr = hash()(str); + // 字符串“Hello 算法”的哈希值为 15466937326284535026 + + // 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算 + // 数组、对象的哈希值计算需要自行实现 + ``` + +=== "Java" + + ```java title="built_in_hash.java" + int num = 3; + int hashNum = Integer.hashCode(num); + // 整数 3 的哈希值为 3 + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + // 布尔量 true 的哈希值为 1231 + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + // 小数 3.14159 的哈希值为 -1340954729 + + String str = "Hello 算法"; + int hashStr = str.hashCode(); + // 字符串“Hello 算法”的哈希值为 -727081396 + + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + // 数组 [12836, 小哈] 的哈希值为 1151158 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + // 节点对象 utils.ListNode@7dc5e7b4 的哈希值为 2110121908 + ``` + +=== "C#" + + ```csharp title="built_in_hash.cs" + int num = 3; + int hashNum = num.GetHashCode(); + // 整数 3 的哈希值为 3; + + bool bol = true; + int hashBol = bol.GetHashCode(); + // 布尔量 true 的哈希值为 1; + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + // 小数 3.14159 的哈希值为 -1340954729; + + string str = "Hello 算法"; + int hashStr = str.GetHashCode(); + // 字符串“Hello 算法”的哈希值为 -586107568; + + object[] arr = [12836, "小哈"]; + int hashTup = arr.GetHashCode(); + // 数组 [12836, 小哈] 的哈希值为 42931033; + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + // 节点对象 0 的哈希值为 39053774; + ``` + +=== "Go" + + ```go title="built_in_hash.go" + // Go 未提供内置 hash code 函数 + ``` + +=== "Swift" + + ```swift title="built_in_hash.swift" + let num = 3 + let hashNum = num.hashValue + // 整数 3 的哈希值为 9047044699613009734 + + let bol = true + let hashBol = bol.hashValue + // 布尔量 true 的哈希值为 -4431640247352757451 + + let dec = 3.14159 + let hashDec = dec.hashValue + // 小数 3.14159 的哈希值为 -2465384235396674631 + + let str = "Hello 算法" + let hashStr = str.hashValue + // 字符串“Hello 算法”的哈希值为 -7850626797806988787 + + let arr = [AnyHashable(12836), AnyHashable("小哈")] + let hashTup = arr.hashValue + // 数组 [AnyHashable(12836), AnyHashable("小哈")] 的哈希值为 -2308633508154532996 + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + // 节点对象 utils.ListNode 的哈希值为 -2434780518035996159 + ``` + +=== "JS" + + ```javascript title="built_in_hash.js" + // JavaScript 未提供内置 hash code 函数 + ``` + +=== "TS" + + ```typescript title="built_in_hash.ts" + // TypeScript 未提供内置 hash code 函数 + ``` + +=== "Dart" + + ```dart title="built_in_hash.dart" + int num = 3; + int hashNum = num.hashCode; + // 整数 3 的哈希值为 34803 + + bool bol = true; + int hashBol = bol.hashCode; + // 布尔值 true 的哈希值为 1231 + + double dec = 3.14159; + int hashDec = dec.hashCode; + // 小数 3.14159 的哈希值为 2570631074981783 + + String str = "Hello 算法"; + int hashStr = str.hashCode; + // 字符串“Hello 算法”的哈希值为 468167534 + + List arr = [12836, "小哈"]; + int hashArr = arr.hashCode; + // 数组 [12836, 小哈] 的哈希值为 976512528 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + // 节点对象 Instance of 'ListNode' 的哈希值为 1033450432 + ``` + +=== "Rust" + + ```rust title="built_in_hash.rs" + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + // 整数 3 的哈希值为 568126464209439262 + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + // 布尔量 true 的哈希值为 4952851536318644461 + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + // 小数 3.14159 的哈希值为 2566941990314602357 + + let str = "Hello 算法"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + // 字符串“Hello 算法”的哈希值为 16092673739211250988 + + let arr = (&12836, &"小哈"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + // 元组 (12836, "小哈") 的哈希值为 1885128010422702749 + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + // 节点对象 RefCell { value: ListNode { val: 42, next: None } } 的哈希值为15387811073369036852 + ``` + +=== "C" + + ```c title="built_in_hash.c" + // C 未提供内置 hash code 函数 + ``` + +=== "Kotlin" + + ```kotlin title="built_in_hash.kt" + val num = 3 + val hashNum = num.hashCode() + // 整数 3 的哈希值为 3 + + val bol = true + val hashBol = bol.hashCode() + // 布尔量 true 的哈希值为 1231 + + val dec = 3.14159 + val hashDec = dec.hashCode() + // 小数 3.14159 的哈希值为 -1340954729 + + val str = "Hello 算法" + val hashStr = str.hashCode() + // 字符串“Hello 算法”的哈希值为 -727081396 + + val arr = arrayOf(12836, "小哈") + val hashTup = arr.hashCode() + // 数组 [12836, 小哈] 的哈希值为 189568618 + + val obj = ListNode(0) + val hashObj = obj.hashCode() + // 节点对象 utils.ListNode@1d81eb93 的哈希值为 495053715 + ``` + +=== "Ruby" + + ```ruby title="built_in_hash.rb" + num = 3 + hash_num = num.hash + # 整数 3 的哈希值为 -4385856518450339636 + + bol = true + hash_bol = bol.hash + # 布尔量 true 的哈希值为 -1617938112149317027 + + dec = 3.14159 + hash_dec = dec.hash + # 小数 3.14159 的哈希值为 -1479186995943067893 + + str = "Hello 算法" + hash_str = str.hash + # 字符串“Hello 算法”的哈希值为 -4075943250025831763 + + tup = [12836, '小哈'] + hash_tup = tup.hash + # 元组 (12836, '小哈') 的哈希值为 1999544809202288822 + + obj = ListNode.new(0) + hash_obj = obj.hash + # 节点对象 # 的哈希值为 4302940560806366381 + ``` + +=== "Zig" + + ```zig title="built_in_hash.zig" + + ``` + +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +在许多编程语言中,**只有不可变对象才可作为哈希表的 `key`** 。假如我们将列表(动态数组)作为 `key` ,当列表的内容发生变化时,它的哈希值也随之改变,我们就无法在哈希表中查询到原先的 `value` 了。 + +虽然自定义对象(比如链表节点)的成员变量是可变的,但它是可哈希的。**这是因为对象的哈希值通常是基于内存地址生成的**,即使对象的内容发生了变化,但它的内存地址不变,哈希值仍然是不变的。 + +细心的你可能发现在不同控制台中运行程序时,输出的哈希值是不同的。**这是因为 Python 解释器在每次启动时,都会为字符串哈希函数加入一个随机的盐(salt)值**。这种做法可以有效防止 HashDoS 攻击,提升哈希算法的安全性。 diff --git a/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png b/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png new file mode 100644 index 0000000000..1c67feeffa Binary files /dev/null and b/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png differ diff --git a/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png b/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png new file mode 100644 index 0000000000..5e19434789 Binary files /dev/null and b/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png differ diff --git a/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png b/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png new file mode 100644 index 0000000000..729df3118a Binary files /dev/null and b/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png differ diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index 45a5299705..a1a1fd576c 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -1,74 +1,108 @@ ---- -comments: true ---- +# 哈希冲突 -# 哈希冲突处理 +上一节提到,**通常情况下哈希函数的输入空间远大于输出空间**,因此理论上哈希冲突是不可避免的。比如,输入空间为全体整数,输出空间为数组容量大小,则必然有多个整数映射至同一桶索引。 -理想情况下,哈希函数应该为每个输入产生唯一的输出,使得 key 和 value 一一对应。而实际上,往往存在向哈希函数输入不同的 key 而产生相同输出的情况,这种情况被称为「哈希冲突 Hash Collision」。 +哈希冲突会导致查询结果错误,严重影响哈希表的可用性。为了解决该问题,每当遇到哈希冲突时,我们就进行哈希表扩容,直至冲突消失为止。此方法简单粗暴且有效,但效率太低,因为哈希表扩容需要进行大量的数据搬运与哈希值计算。为了提升效率,我们可以采用以下策略。 -**哈希冲突会严重影响哈希表的实用性**。试想一下,如果在哈希表中总是查找到错误的结果,那么我们肯定不会继续使用这样的数据结构了。 +1. 改良哈希表数据结构,**使得哈希表可以在出现哈希冲突时正常工作**。 +2. 仅在必要时,即当哈希冲突比较严重时,才执行扩容操作。 -!!! question "为什么会出现哈希冲突?" - - 因为 **哈希函数的输入空间往往远大于输出空间**,所以不可避免地会出现多个输入产生相同输出的情况。比如,输入空间是全体整数,输出空间是一个固定大小的桶(数组)的索引范围,那么必定会有多个整数同时映射到一个桶索引。 - -虽然理论上哈希冲突难以避免,**但我们仍然可以在数据结构层面上缓解哈希冲突所带来的负面影响**,尽量保证哈希表的增删查改操作效率。 - -常见的哈希冲突的解决方案有「链式地址」和「开放寻址」。 +哈希表的结构改良方法主要包括“链式地址”和“开放寻址”。 ## 链式地址 -「链式地址」通过引入链表来解决哈希冲突问题,代价是占用空间变大,因为链表或二叉树包含结点指针,相比于数组更加耗费内存空间。 +在原始哈希表中,每个桶仅能存储一个键值对。链式地址(separate chaining)将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。下图展示了一个链式地址哈希表的例子。 -### 链表引入 +![链式地址哈希表](hash_collision.assets/hash_table_chaining.png) -在原始哈希表中,一个桶地址只能存储一个元素(即键值对)。**考虑将桶地址内的单个元素转变成一个链表,将所有冲突元素都存储在一个链表中**,此时哈希表操作方法为: +基于链式地址实现的哈希表的操作方法发生了以下变化。 -- **查询元素**:先将 key 输入到哈希函数得到桶地址(即访问链表头部),再遍历链表来确定对应的 value 。 -- **添加元素**:先通过哈希函数访问链表头部,再将元素直接添加到链表头部即可。 -- **删除元素**:同样先访问链表头部,再遍历链表查找对应元素,删除之即可。 +- **查询元素**:输入 `key` ,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比 `key` 以查找目标键值对。 +- **添加元素**:首先通过哈希函数访问链表头节点,然后将节点(键值对)添加到链表中。 +- **删除元素**:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点并将其删除。 -(图) +链式地址存在以下局限性。 -### 二叉树引入 +- **占用空间增大**:链表包含节点指针,它相比数组更加耗费内存空间。 +- **查询效率降低**:因为需要线性遍历链表来查找对应元素。 -引入链表虽然解决了哈希冲突,但查询效率也随之降低了,因为需要线性查找(即遍历链表)来确认对应元素。为了缓解此问题,**当某个桶地址内的链表太长时,可以把链表转化为「平衡二叉搜索树」**,将时间复杂度降低至 $O(\log n)$ 。 +以下代码给出了链式地址哈希表的简单实现,需要注意两点。 -!!! note "工业界方案" +- 使用列表(动态数组)代替链表,从而简化代码。在这种设定下,哈希表(数组)包含多个桶,每个桶都是一个列表。 +- 以下实现包含哈希表扩容方法。当负载因子超过 $\frac{2}{3}$ 时,我们将哈希表扩容至原先的 $2$ 倍。 - Java 使用了链式地址来解决哈希冲突。在 JDK 1.8 之后, HashMap 内长度大于 8 的链表会被转化为「红黑树」,以提升查找性能。 +```src +[file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} +``` + +值得注意的是,当链表很长时,查询效率 $O(n)$ 很差。**此时可以将链表转换为“AVL 树”或“红黑树”**,从而将查询操作的时间复杂度优化至 $O(\log n)$ 。 ## 开放寻址 -「开放寻址」不引入额外数据结构,而是通过“向后探测”来解决哈希冲突。根据探测方法的不同,主要分为 **线性探测、平方探测、多次哈希**。 +开放寻址(open addressing)不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测和多次哈希等。 + +下面以线性探测为例,介绍开放寻址哈希表的工作机制。 ### 线性探测 -「线性探测」使用固定步长的线性查找来解决哈希冲突。 +线性探测采用固定步长的线性搜索来进行探测,其操作方法与普通哈希表有所不同。 + +- **插入元素**:通过哈希函数计算桶索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 $1$ ),直至找到空桶,将元素插入其中。 +- **查找元素**:若发现哈希冲突,则使用相同步长向后进行线性遍历,直到找到对应元素,返回 `value` 即可;如果遇到空桶,说明目标元素不在哈希表中,返回 `None` 。 + +下图展示了开放寻址(线性探测)哈希表的键值对分布。根据此哈希函数,最后两位相同的 `key` 都会被映射到相同的桶。而通过线性探测,它们被依次存储在该桶以及之下的桶中。 + +![开放寻址(线性探测)哈希表的键值对分布](hash_collision.assets/hash_table_linear_probing.png) + +然而,**线性探测容易产生“聚集现象”**。具体来说,数组中连续被占用的位置越长,这些连续位置发生哈希冲突的可能性越大,从而进一步促使该位置的聚堆生长,形成恶性循环,最终导致增删查改操作效率劣化。 + +值得注意的是,**我们不能在开放寻址哈希表中直接删除元素**。这是因为删除元素会在数组内产生一个空桶 `None` ,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在,如下图所示。 -**插入元素**:如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。 +![在开放寻址中删除元素导致的查询问题](hash_collision.assets/hash_table_open_addressing_deletion.png) -**查找元素**:若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况: +为了解决该问题,我们可以采用懒删除(lazy deletion)机制:它不直接从哈希表中移除元素,**而是利用一个常量 `TOMBSTONE` 来标记这个桶**。在该机制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置键值对。但不同的是,线性探测到 `TOMBSTONE` 时应该继续遍历,因为其之下可能还存在键值对。 -1. 找到对应元素,返回 value 即可; -2. 若遇到空位,则说明查找键值对不在哈希表中; +然而,**懒删除可能会加速哈希表的性能退化**。这是因为每次删除操作都会产生一个删除标记,随着 `TOMBSTONE` 的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 `TOMBSTONE` 才能找到目标元素。 -(图) +为此,考虑在线性探测中记录遇到的首个 `TOMBSTONE` 的索引,并将搜索到的目标元素与该 `TOMBSTONE` 交换位置。这样做的好处是当每次查询或添加元素时,元素会被移动至距离理想位置(探测起始点)更近的桶,从而优化查询效率。 -线性探测有以下缺陷: +以下代码实现了一个包含懒删除的开放寻址(线性探测)哈希表。为了更加充分地使用哈希表的空间,我们将哈希表看作一个“环形数组”,当越过数组尾部时,回到头部继续遍历。 -- **不能直接删除元素**。删除元素会导致桶内出现一个空位,在查找其他元素时,该空位有可能导致程序认为元素不存在(即上述第 `2.` 种情况)。因此需要借助一个标志位来标记删除元素。 -- **容易产生聚集**。桶内被占用的连续位置越长,这些连续位置发生哈希冲突的可能性越大,从而进一步促进这一位置的“聚堆生长”,最终导致增删查改操作效率的劣化。 +```src +[file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} +``` + +### 平方探测 + +平方探测与线性探测类似,都是开放寻址的常见策略之一。当发生冲突时,平方探测不是简单地跳过一个固定的步数,而是跳过“探测次数的平方”的步数,即 $1, 4, 9, \dots$ 步。 + +平方探测主要具有以下优势。 + +- 平方探测通过跳过探测次数平方的距离,试图缓解线性探测的聚集效应。 +- 平方探测会跳过更大的距离来寻找空位置,有助于数据分布得更加均匀。 + +然而,平方探测并不是完美的。 + +- 仍然存在聚集现象,即某些位置比其他位置更容易被占用。 +- 由于平方的增长,平方探测可能不会探测整个哈希表,这意味着即使哈希表中有空桶,平方探测也可能无法访问到它。 ### 多次哈希 -顾名思义,「多次哈希」的思路是基于多个哈希函数 $f_1(x)$ , $f_2(x)$ , $f_3(x)$ , $\cdots$ 进行探测。 +顾名思义,多次哈希方法使用多个哈希函数 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 进行探测。 + +- **插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推,直到找到空位后插入元素。 +- **查找元素**:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;若遇到空位或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 `None` 。 + +与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会带来额外的计算量。 + +!!! tip -**插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。 + 请注意,开放寻址(线性探测、平方探测和多次哈希)哈希表都存在“不能直接删除元素”的问题。 -**查找元素**:以相同的哈希函数顺序查找,存在两种情况: +## 编程语言的选择 -1. 找到目标元素,则返回之; -2. 到空位或已尝试所有哈希函数,说明哈希表中无此元素; +各种编程语言采取了不同的哈希表实现策略,下面举几个例子。 -相比于「线性探测」,「多次哈希」方法更不容易产生聚集,代价是多个哈希函数增加了额外计算量。 +- Python 采用开放寻址。字典 `dict` 使用伪随机数进行探测。 +- Java 采用链式地址。自 JDK 1.8 以来,当 `HashMap` 内数组长度达到 64 且链表长度达到 8 时,链表会转换为红黑树以提升查找性能。 +- Go 采用链式地址。Go 规定每个桶最多存储 8 个键值对,超出容量则连接一个溢出桶;当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。 diff --git a/docs/chapter_hashing/hash_map.assets/hash_collision.png b/docs/chapter_hashing/hash_map.assets/hash_collision.png index e94330064b..45f42fc2a4 100644 Binary files a/docs/chapter_hashing/hash_map.assets/hash_collision.png and b/docs/chapter_hashing/hash_map.assets/hash_collision.png differ diff --git a/docs/chapter_hashing/hash_map.assets/hash_function.png b/docs/chapter_hashing/hash_map.assets/hash_function.png index b15d225156..d89389c213 100644 Binary files a/docs/chapter_hashing/hash_map.assets/hash_function.png and b/docs/chapter_hashing/hash_map.assets/hash_function.png differ diff --git a/docs/chapter_hashing/hash_map.assets/hash_map.png b/docs/chapter_hashing/hash_map.assets/hash_map.png deleted file mode 100644 index 5c36a2a181..0000000000 Binary files a/docs/chapter_hashing/hash_map.assets/hash_map.png and /dev/null differ diff --git a/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png b/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png new file mode 100644 index 0000000000..3df38c8b1a Binary files /dev/null and b/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png differ diff --git a/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png b/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png new file mode 100644 index 0000000000..c00f1c20ec Binary files /dev/null and b/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png differ diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md old mode 100644 new mode 100755 index cbb4ba049e..74d39f1fcb --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -1,41 +1,76 @@ ---- -comments: true ---- - # 哈希表 -哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 $O(1)$ 。 +哈希表(hash table),又称散列表,它通过建立键 `key` 与值 `value` 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键 `key` ,则可以在 $O(1)$ 时间内获取对应的值 `value` 。 -例如,给定一个包含 $n$ 个学生的数据库,每个学生有“姓名 `name` ”和“学号 `id` ”两项数据,希望实现一个查询功能:**输入一个学号,返回对应的姓名**,则可以使用哈希表实现。 +如下图所示,给定 $n$ 个学生,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用下图所示的哈希表来实现。 -![hash_map](hash_map.assets/hash_map.png) +![哈希表的抽象表示](hash_map.assets/hash_table_lookup.png) -

Fig. 哈希表抽象表示

+除哈希表外,数组和链表也可以实现查询功能,它们的效率对比如下表所示。 -## 哈希表优势 +- **添加元素**:仅需将元素添加至数组(链表)的尾部即可,使用 $O(1)$ 时间。 +- **查询元素**:由于数组(链表)是乱序的,因此需要遍历其中的所有元素,使用 $O(n)$ 时间。 +- **删除元素**:需要先查询到元素,再从数组(链表)中删除,使用 $O(n)$ 时间。 -除了哈希表之外,还可以使用以下数据结构来实现上述查询功能: +

  元素查询效率对比

-1. **无序数组**:每个元素为 `[学号, 姓名]` ; -2. **有序数组**:将 `1.` 中的数组按照学号从小到大排序; -3. **链表**:每个结点的值为 `[学号, 姓名]` ; -4. **二叉搜索树**:每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树; +| | 数组 | 链表 | 哈希表 | +| -------- | ------ | ------ | ------ | +| 查找元素 | $O(n)$ | $O(n)$ | $O(1)$ | +| 添加元素 | $O(1)$ | $O(1)$ | $O(1)$ | +| 删除元素 | $O(n)$ | $O(n)$ | $O(1)$ | -使用上述方法,各项操作的时间复杂度如下表所示(在此不做赘述,详解可见 [二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/#_6))。无论是查找元素、还是增删元素,哈希表的时间复杂度都是 $O(1)$ ,全面胜出! +观察发现,**在哈希表中进行增删查改的时间复杂度都是 $O(1)$** ,非常高效。 -
+## 哈希表常用操作 -| | 无序数组 | 有序数组 | 链表 | 二叉搜索树 | 哈希表 | -| -------- | -------- | ----------- | ------ | ----------- | ------ | -| 查找元素 | $O(n)$ | $O(\log n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | -| 插入元素 | $O(1)$ | $O(n)$ | $O(1)$ | $O(\log n)$ | $O(1)$ | -| 删除元素 | $O(n)$ | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等,示例代码如下: -
+=== "Python" -## 哈希表常用操作 + ```python title="hash_map.py" + # 初始化哈希表 + hmap: dict = {} -哈希表的基本操作包括 **初始化、查询操作、添加与删除键值对**。 + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" + + # 查询操作 + # 向哈希表中输入键 key ,得到值 value + name: str = hmap[15937] + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hmap.pop(10583) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* 初始化哈希表 */ + unordered_map map; + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map[12836] = "小哈"; + map[15937] = "小啰"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鸭"; + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string name = map[15937]; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.erase(10583); + ``` === "Java" @@ -45,14 +80,14 @@ comments: true /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); map.put(13276, "小法"); map.put(10583, "小鸭"); /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value + // 向哈希表中输入键 key ,得到值 value String name = map.get(15937); /* 删除操作 */ @@ -60,80 +95,80 @@ comments: true map.remove(10583); ``` -=== "C++" +=== "C#" - ```cpp title="hash_map.cpp" + ```csharp title="hash_map.cs" /* 初始化哈希表 */ - unordered_map map; - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map[12836] = "小哈"; - map[15937] = "小啰"; - map[16750] = "小算"; - map[13276] = "小法"; - map[10583] = "小鸭"; + Dictionary map = new() { + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + { 12836, "小哈" }, + { 15937, "小啰" }, + { 16750, "小算" }, + { 13276, "小法" }, + { 10583, "小鸭" } + }; /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value + // 向哈希表中输入键 key ,得到值 value string name = map[15937]; /* 删除操作 */ // 在哈希表中删除键值对 (key, value) - map.erase(10583); + map.Remove(10583); ``` -=== "Python" +=== "Go" - ```python title="hash_map.py" - """ 初始化哈希表 """ - mapp = {} + ```go title="hash_map_test.go" + /* 初始化哈希表 */ + hmap := make(map[int]string) - """ 添加操作 """ - # 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" - """ 查询操作 """ - # 向哈希表输入键 key ,得到值 value - name = mapp[15937] + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + name := hmap[15937] - """ 删除操作 """ - # 在哈希表中删除键值对 (key, value) - mapp.pop(10583) + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + delete(hmap, 10583) ``` -=== "Go" +=== "Swift" - ```go title="hash_map.go" + ```swift title="hash_map.swift" /* 初始化哈希表 */ - mapp := make(map[int]string) + var map: [Int: String] = [:] /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" + map[12836] = "小哈" + map[15937] = "小啰" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鸭" /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - name := mapp[15937] + // 向哈希表中输入键 key ,得到值 value + let name = map[15937]! /* 删除操作 */ // 在哈希表中删除键值对 (key, value) - delete(mapp, 10583) + map.removeValue(forKey: 10583) ``` -=== "JavaScript" +=== "JS" - ```js title="hash_map.js" + ```javascript title="hash_map.js" /* 初始化哈希表 */ - const map = new ArrayHashMap(); + const map = new Map(); /* 添加操作 */ // 在哈希表中添加键值对 (key, value) map.set(12836, '小哈'); @@ -143,7 +178,7 @@ comments: true map.set(10583, '小鸭'); /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value + // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); /* 删除操作 */ @@ -151,7 +186,7 @@ comments: true map.delete(10583); ``` -=== "TypeScript" +=== "TS" ```typescript title="hash_map.ts" /* 初始化哈希表 */ @@ -167,7 +202,7 @@ comments: true console.info(map); /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value + // 向哈希表中输入键 key ,得到值 value let name = map.get(15937); console.info('\n输入学号 15937 ,查询到姓名 ' + name); @@ -178,42 +213,146 @@ comments: true console.info(map); ``` +=== "Dart" + + ```dart title="hash_map.dart" + /* 初始化哈希表 */ + Map map = {}; + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map[12836] = "小哈"; + map[15937] = "小啰"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鸭"; + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + String name = map[15937]; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + use std::collections::HashMap; + + /* 初始化哈希表 */ + let mut map: HashMap = HashMap::new(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.insert(12836, "小哈".to_string()); + map.insert(15937, "小啰".to_string()); + map.insert(16750, "小算".to_string()); + map.insert(13279, "小法".to_string()); + map.insert(10583, "小鸭".to_string()); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + let _name: Option<&String> = map.get(&15937); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + let _removed_value: Option = map.remove(&10583); + ``` + === "C" ```c title="hash_map.c" - + // C 未提供内置哈希表 ``` -=== "C#" +=== "Kotlin" - ```csharp title="hash_map.cs" + ```kotlin title="hash_map.kt" /* 初始化哈希表 */ - Dictionary map = new (); + val map = HashMap() /* 添加操作 */ // 在哈希表中添加键值对 (key, value) - map.Add(12836, "小哈"); - map.Add(15937, "小啰"); - map.Add(16750, "小算"); - map.Add(13276, "小法"); - map.Add(10583, "小鸭"); + map[12836] = "小哈" + map[15937] = "小啰" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鸭" /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - String name = map[15937]; + // 向哈希表中输入键 key ,得到值 value + val name = map[15937] /* 删除操作 */ // 在哈希表中删除键值对 (key, value) - map.Remove(10583); + map.remove(10583) ``` -=== "Swift" +=== "Ruby" - ```swift title="hash_map.swift" + ```ruby title="hash_map.rb" + # 初始化哈希表 + hmap = {} + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" + + # 查询操作 + # 向哈希表中输入键 key ,得到值 value + name = hmap[15937] + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hmap.delete(10583) ``` -遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**。 +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +哈希表有三种常用的遍历方式:遍历键值对、遍历键和遍历值。示例代码如下: + +=== "Python" + + ```python title="hash_map.py" + # 遍历哈希表 + # 遍历键值对 key->value + for key, value in hmap.items(): + print(key, "->", value) + # 单独遍历键 key + for key in hmap.keys(): + print(key) + # 单独遍历值 value + for value in hmap.values(): + print(value) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* 遍历哈希表 */ + // 遍历键值对 key->value + for (auto kv: map) { + cout << kv.first << " -> " << kv.second << endl; + } + // 使用迭代器遍历 key->value + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + ``` === "Java" @@ -233,79 +372,63 @@ comments: true } ``` -=== "C++" +=== "C#" - ```cpp title="hash_map.cpp" + ```csharp title="hash_map.cs" /* 遍历哈希表 */ - // 遍历键值对 key->value - for (auto kv: map) { - cout << kv.first << " -> " << kv.second << endl; + // 遍历键值对 Key->Value + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); } // 单独遍历键 key - for (auto key: map) { - cout << key.first << endl; + foreach (int key in map.Keys) { + Console.WriteLine(key); } // 单独遍历值 value - for (auto val: map) { - cout << val.second << endl; + foreach (string val in map.Values) { + Console.WriteLine(val); } ``` -=== "Python" - - ```python title="hash_map.py" - """ 遍历哈希表 """ - # 遍历键值对 key->value - for key, value in mapp.items(): - print(key, "->", value) - # 单独遍历键 key - for key in mapp.keys(): - print(key) - # 单独遍历值 value - for value in mapp.values(): - print(value) - ``` - === "Go" ```go title="hash_map_test.go" /* 遍历哈希表 */ // 遍历键值对 key->value - for key, value := range mapp { + for key, value := range hmap { fmt.Println(key, "->", value) } // 单独遍历键 key - for key := range mapp { + for key := range hmap { fmt.Println(key) } // 单独遍历值 value - for _, value := range mapp { + for _, value := range hmap { fmt.Println(value) } ``` -=== "JavaScript" +=== "Swift" - ```js title="hash_map.js" + ```swift title="hash_map.swift" /* 遍历哈希表 */ - // 遍历键值对 key->value - for (const entry of map.entries()) { - if (!entry) continue; - console.info(entry.key + ' -> ' + entry.val); + // 遍历键值对 Key->Value + for (key, value) in map { + print("\(key) -> \(value)") } - // 单独遍历键 key - for (const key of map.keys()) { - console.info(key); + // 单独遍历键 Key + for key in map.keys { + print(key) } - // 单独遍历值 value - for (const val of map.values()) { - console.info(val); + // 单独遍历值 Value + for value in map.values { + print(value) } ``` -=== "TypeScript" +=== "JS" - ```typescript title="hash_map.ts" + ```javascript title="hash_map.js" /* 遍历哈希表 */ console.info('\n遍历键值对 Key->Value'); for (const [k, v] of map.entries()) { @@ -321,475 +444,160 @@ comments: true } ``` -=== "C" +=== "TS" - ```c title="hash_map.c" - - ``` - -=== "C#" - - ```csharp title="hash_map.cs" + ```typescript title="hash_map.ts" /* 遍历哈希表 */ - // 遍历键值对 Key->Value - foreach (var kv in map) { - Console.WriteLine(kv.Key + " -> " + kv.Value); + console.info('\n遍历键值对 Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); } - // 单独遍历键 key - foreach (int key in map.Keys) { - Console.WriteLine(key); + console.info('\n单独遍历键 Key'); + for (const k of map.keys()) { + console.info(k); } - // 单独遍历值 value - foreach (String val in map.Values) { - Console.WriteLine(val); + console.info('\n单独遍历值 Value'); + for (const v of map.values()) { + console.info(v); } ``` -=== "Swift" - - ```swift title="hash_map.swift" +=== "Dart" + ```dart title="hash_map.dart" + /* 遍历哈希表 */ + // 遍历键值对 Key->Value + map.forEach((key, value) { + print('$key -> $value'); + }); + + // 单独遍历键 Key + map.keys.forEach((key) { + print(key); + }); + + // 单独遍历值 Value + map.values.forEach((value) { + print(value); + }); ``` -## 哈希函数 - -哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。 - -最简单地,**我们可以仅用一个「数组」来实现哈希表**。首先,将所有 value 放入数组中,那么每个 value 在数组中都有唯一的「索引」。显然,访问 value 需要给定索引,而为了 **建立 key 和索引之间的映射关系**,我们需要使用「哈希函数 Hash Function」。 - -设数组为 `bucket` ,哈希函数为 `f(x)` ,输入键为 `key` 。那么获取 value 的步骤为: - -1. 通过哈希函数计算出索引,即 `index = f(key)` ; -2. 通过索引在数组中获取值,即 `value = bucket[index]` ; - -以上述学生数据 `key 学号 -> value 姓名` 为例,我们可以将「哈希函数」设计为 - -$$ -f(x) = x \% 100 -$$ - -![hash_function](hash_map.assets/hash_function.png) - -

Fig. 哈希函数

+=== "Rust" -=== "Java" - - ```java title="array_hash_map.java" - /* 键值对 int->String */ - class Entry { - public int key; // 键 - public String val; // 值 - public Entry(int key, String val) { - this.key = key; - this.val = val; - } + ```rust title="hash_map.rs" + /* 遍历哈希表 */ + // 遍历键值对 Key->Value + for (key, value) in &map { + println!("{key} -> {value}"); } - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private List bucket; - public ArrayHashMap() { - // 初始化一个长度为 100 的桶(数组) - bucket = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - bucket.add(null); - } - } - - /* 哈希函数 */ - private int hashFunc(int key) { - int index = key % 100; - return index; - } - - /* 查询操作 */ - public String get(int key) { - int index = hashFunc(key); - Entry pair = bucket.get(index); - if (pair == null) return null; - return pair.val; - } + // 单独遍历键 Key + for key in map.keys() { + println!("{key}"); + } - /* 添加操作 */ - public void put(int key, String val) { - Entry pair = new Entry(key, val); - int index = hashFunc(key); - bucket.set(index, pair); - } - - /* 删除操作 */ - public void remove(int key) { - int index = hashFunc(key); - // 置为 null,代表删除 - bucket.set(index, null); - } + // 单独遍历值 Value + for value in map.values() { + println!("{value}"); } ``` -=== "C++" - - ```cpp title="array_hash_map.cpp" - /* 键值对 int->String */ - struct Entry { - public: - int key; - string val; - Entry(int key, string val) { - this->key = key; - this->val = val; - } - }; - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private: - vector bucket; - public: - ArrayHashMap() { - // 初始化一个长度为 100 的桶(数组) - bucket= vector(100); - } - - /* 哈希函数 */ - int hashFunc(int key) { - int index = key % 100; - return index; - } - - /* 查询操作 */ - string get(int key) { - int index = hashFunc(key); - Entry* pair = bucket[index]; - return pair->val; - } +=== "C" - /* 添加操作 */ - void put(int key, string val) { - Entry* pair = new Entry(key, val); - int index = hashFunc(key); - bucket[index] = pair; - } - - /* 删除操作 */ - void remove(int key) { - int index = hashFunc(key); - // 置为空字符,代表删除 - bucket[index] = nullptr; - } - }; + ```c title="hash_map.c" + // C 未提供内置哈希表 ``` -=== "Python" +=== "Kotlin" - ```python title="array_hash_map.py" - """ 键值对 int->String """ - class Entry: - def __init__(self, key, val): - self.key = key - self.val = val - - """ 基于数组简易实现的哈希表 """ - class ArrayHashMap: - def __init__(self): - # 初始化一个长度为 100 的桶(数组) - self.bucket = [None] * 100 - - """ 哈希函数 """ - def hashFunc(self, key): - index = key % 100 - return index - - """ 查询操作 """ - def get(self, key): - index = self.hashFunc(key) - pair = self.bucket[index] - if pair is None: - return None - return pair.val - - """ 添加操作 """ - def put(self, key, val): - pair = Entry(key, val) - index = self.hashFunc(key) - self.bucket[index] = pair - - """ 删除操作 """ - def remove(self, key): - index = self.hashFunc(key) - # 置为空字符,代表删除 - self.bucket[index] = None + ```kotlin title="hash_map.kt" + /* 遍历哈希表 */ + // 遍历键值对 key->value + for ((key, value) in map) { + println("$key -> $value") + } + // 单独遍历键 key + for (key in map.keys) { + println(key) + } + // 单独遍历值 value + for (_val in map.values) { + println(_val) + } ``` -=== "Go" - - ```go title="array_hash_map.go" - /* 键值对 int->String */ - type entry struct { - key int - val string - } +=== "Ruby" - /* 基于数组简易实现的哈希表 */ - type arrayHashMap struct { - bucket []*entry - } + ```ruby title="hash_map.rb" + # 遍历哈希表 + # 遍历键值对 key->value + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } - func newArrayHashMap() *arrayHashMap { - // 初始化一个长度为 100 的桶(数组) - bucket := make([]*entry, 100) - return &arrayHashMap{bucket: bucket} - } + # 单独遍历键 key + hmap.keys.each { |key| puts key } - /* 哈希函数 */ - func (a *arrayHashMap) hashFunc(key int) int { - index := key % 100 - return index - } + # 单独遍历值 value + hmap.values.each { |val| puts val } + ``` - /* 查询操作 */ - func (a *arrayHashMap) get(key int) string { - index := a.hashFunc(key) - pair := a.bucket[index] - if pair == nil { - return "Not Found" - } - return pair.val - } +=== "Zig" - /* 添加操作 */ - func (a *arrayHashMap) put(key int, val string) { - pair := &entry{key: key, val: val} - index := a.hashFunc(key) - a.bucket[index] = pair - } + ```zig title="hash_map.zig" - /* 删除操作 */ - func (a *arrayHashMap) remove(key int) { - index := a.hashFunc(key) - // 置为 nil ,代表删除 - a.bucket[index] = nil - } ``` -=== "JavaScript" +??? pythontutor "可视化运行" - ```js title="array_hash_map.js" - /* 键值对 Number -> String */ - class Entry { - constructor(key, val) { - this.key = key; - this.val = val; - } - } + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - #bucket; - constructor() { - // 初始化一个长度为 100 的桶(数组) - this.#bucket = new Array(100).fill(null); - } - - /* 哈希函数 */ - #hashFunc(key) { - return key % 100; - } - - /* 查询操作 */ - get(key) { - let index = this.#hashFunc(key); - let entry = this.#bucket[index]; - if (entry === null) return null; - return entry.val; - } +## 哈希表简单实现 - /* 添加操作 */ - set(key, val) { - let index = this.#hashFunc(key); - this.#bucket[index] = new Entry(key, val); - } - - /* 删除操作 */ - delete(key) { - let index = this.#hashFunc(key); - // 置为 null ,代表删除 - this.#bucket[index] = null; - } - } - ``` +我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为桶(bucket),每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。 -=== "TypeScript" - - ```typescript title="array_hash_map.ts" - /* 键值对 Number -> String */ - class Entry { - public key: number; - public val: string; - - constructor(key: number, val: string) { - this.key = key; - this.val = val; - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - - private readonly bucket: (Entry | null)[]; - - constructor() { - // 初始化一个长度为 100 的桶(数组) - this.bucket = (new Array(100)).fill(null); - } - - /* 哈希函数 */ - private hashFunc(key: number): number { - return key % 100; - } - - /* 查询操作 */ - public get(key: number): string | null { - let index = this.hashFunc(key); - let entry = this.bucket[index]; - if (entry === null) return null; - return entry.val; - } - - /* 添加操作 */ - public set(key: number, val: string) { - let index = this.hashFunc(key); - this.bucket[index] = new Entry(key, val); - } - - /* 删除操作 */ - public delete(key: number) { - let index = this.hashFunc(key); - // 置为 null ,代表删除 - this.bucket[index] = null; - } - - /* 获取所有键值对 */ - public entries(): (Entry | null)[] { - let arr: (Entry | null)[] = []; - for (let i = 0; i < this.bucket.length; i++) { - if (this.bucket[i]) { - arr.push(this.bucket[i]); - } - } - return arr; - } - - /* 获取所有键 */ - public keys(): (number | undefined)[] { - let arr: (number | undefined)[] = []; - for (let i = 0; i < this.bucket.length; i++) { - if (this.bucket[i]) { - arr.push(this.bucket[i]?.key); - } - } - return arr; - } - - /* 获取所有值 */ - public values(): (string | undefined)[] { - let arr: (string | undefined)[] = []; - for (let i = 0; i < this.bucket.length; i++) { - if (this.bucket[i]) { - arr.push(this.bucket[i]?.val); - } - } - return arr; - } - } - ``` +那么,如何基于 `key` 定位对应的桶呢?这是通过哈希函数(hash function)实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 `key` ,输出空间是所有桶(数组索引)。换句话说,输入一个 `key` ,**我们可以通过哈希函数得到该 `key` 对应的键值对在数组中的存储位置**。 -=== "C" +输入一个 `key` ,哈希函数的计算过程分为以下两步。 - ```c title="array_hash_map.c" +1. 通过某种哈希算法 `hash()` 计算得到哈希值。 +2. 将哈希值对桶数量(数组长度)`capacity` 取模,从而获取该 `key` 对应的数组索引 `index` 。 - ``` +```shell +index = hash(key) % capacity +``` -=== "C#" +随后,我们就可以利用 `index` 在哈希表中访问对应的桶,从而获取 `value` 。 - ```csharp title="array_hash_map.cs" - /* 键值对 int->String */ - class Entry - { - public int key; - public String val; - public Entry(int key, String val) - { - this.key = key; - this.val = val; - } - } +设数组长度 `capacity = 100`、哈希算法 `hash(key) = key` ,易得哈希函数为 `key % 100` 。下图以 `key` 学号和 `value` 姓名为例,展示了哈希函数的工作原理。 - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap - { - private List bucket; - public ArrayHashMap() - { - // 初始化一个长度为 100 的桶(数组) - bucket = new (); - for (int i = 0; i < 100; i++) - { - bucket.Add(null); - } - } - /* 哈希函数 */ - private int hashFunc(int key) - { - int index = key % 100; - return index; - } - /* 查询操作 */ - public String? get(int key) - { - int index = hashFunc(key); - Entry? pair = bucket[index]; - if (pair == null) return null; - return pair.val; - } - /* 添加操作 */ - public void put(int key, String val) - { - Entry pair = new Entry(key, val); - int index = hashFunc(key); - bucket[index]=pair; - } - /* 删除操作 */ - public void remove(int key) - { - int index = hashFunc(key); - // 置为 null ,代表删除 - bucket[index]=null; - } - } - ``` +![哈希函数工作原理](hash_map.assets/hash_function.png) -=== "Swift" +以下代码实现了一个简单哈希表。其中,我们将 `key` 和 `value` 封装成一个类 `Pair` ,以表示键值对。 - ```swift title="array_hash_map.swift" +```src +[file]{array_hash_map}-[class]{array_hash_map}-[func]{} +``` - ``` +## 哈希冲突与扩容 + +从本质上看,哈希函数的作用是将所有 `key` 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。 + +对于上述示例中的哈希函数,当输入的 `key` 后两位相同时,哈希函数的输出结果也相同。例如,查询学号为 12836 和 20336 的两个学生时,我们得到: -## 哈希冲突 +```shell +12836 % 100 = 36 +20336 % 100 = 36 +``` -细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 12836 和 20336 ,则有 +如下图所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为哈希冲突(hash collision)。 -$$ -f(12836) = f(20336) = 36 -$$ +![哈希冲突示例](hash_map.assets/hash_collision.png) -两个学号指向了同一个姓名,这明显是不对的,我们将这种现象称为「哈希冲突 Hash Collision」,其会严重影响查询的正确性。如何避免哈希冲突的问题将被留在下章讨论。 +容易想到,哈希表容量 $n$ 越大,多个 `key` 被分配到同一个桶中的概率就越低,冲突就越少。因此,**我们可以通过扩容哈希表来减少哈希冲突**。 -![hash_collision](hash_map.assets/hash_collision.png) +如下图所示,扩容前键值对 `(136, A)` 和 `(236, D)` 发生冲突,扩容后冲突消失。 -

Fig. 哈希冲突

+![哈希表扩容](hash_map.assets/hash_table_reshash.png) -综上所述,一个优秀的「哈希函数」应该具备以下特性: +类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量 `capacity` 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。 -- 尽量少地发生哈希冲突; -- 时间复杂度 $O(1)$ ,计算尽可能高效; -- 空间使用率高,即“键值对占用空间 / 哈希表总占用空间”尽可能大; +负载因子(load factor)是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,**也常作为哈希表扩容的触发条件**。例如在 Java 中,当负载因子超过 $0.75$ 时,系统会将哈希表扩容至原先的 $2$ 倍。 diff --git a/docs/chapter_hashing/index.md b/docs/chapter_hashing/index.md new file mode 100644 index 0000000000..6d5b2528ab --- /dev/null +++ b/docs/chapter_hashing/index.md @@ -0,0 +1,9 @@ +# 哈希表 + +![哈希表](../assets/covers/chapter_hashing.jpg) + +!!! abstract + + 在计算机世界中,哈希表如同一位聪慧的图书管理员。 + + 他知道如何计算索书号,从而可以快速找到目标图书。 diff --git a/docs/chapter_hashing/summary.md b/docs/chapter_hashing/summary.md index 5f58b75943..2e8d6ca7f7 100644 --- a/docs/chapter_hashing/summary.md +++ b/docs/chapter_hashing/summary.md @@ -1,5 +1,47 @@ ---- -comments: true ---- - # 小结 + +### 重点回顾 + +- 输入 `key` ,哈希表能够在 $O(1)$ 时间内查询到 `value` ,效率非常高。 +- 常见的哈希表操作包括查询、添加键值对、删除键值对和遍历哈希表等。 +- 哈希函数将 `key` 映射为数组索引,从而访问对应桶并获取 `value` 。 +- 两个不同的 `key` 可能在经过哈希函数后得到相同的数组索引,导致查询结果出错,这种现象被称为哈希冲突。 +- 哈希表容量越大,哈希冲突的概率就越低。因此可以通过扩容哈希表来缓解哈希冲突。与数组扩容类似,哈希表扩容操作的开销很大。 +- 负载因子定义为哈希表中元素数量除以桶数量,反映了哈希冲突的严重程度,常用作触发哈希表扩容的条件。 +- 链式地址通过将单个元素转化为链表,将所有冲突元素存储在同一个链表中。然而,链表过长会降低查询效率,可以通过进一步将链表转换为红黑树来提高效率。 +- 开放寻址通过多次探测来处理哈希冲突。线性探测使用固定步长,缺点是不能删除元素,且容易产生聚集。多次哈希使用多个哈希函数进行探测,相较线性探测更不易产生聚集,但多个哈希函数增加了计算量。 +- 不同编程语言采取了不同的哈希表实现。例如,Java 的 `HashMap` 使用链式地址,而 Python 的 `Dict` 采用开放寻址。 +- 在哈希表中,我们希望哈希算法具有确定性、高效率和均匀分布的特点。在密码学中,哈希算法还应该具备抗碰撞性和雪崩效应。 +- 哈希算法通常采用大质数作为模数,以最大化地保证哈希值均匀分布,减少哈希冲突。 +- 常见的哈希算法包括 MD5、SHA-1、SHA-2 和 SHA-3 等。MD5 常用于校验文件完整性,SHA-2 常用于安全应用与协议。 +- 编程语言通常会为数据类型提供内置哈希算法,用于计算哈希表中的桶索引。通常情况下,只有不可变对象是可哈希的。 + +### Q & A + +**Q**:哈希表的时间复杂度在什么情况下是 $O(n)$ ? + +当哈希冲突比较严重时,哈希表的时间复杂度会退化至 $O(n)$ 。当哈希函数设计得比较好、容量设置比较合理、冲突比较平均时,时间复杂度是 $O(1)$ 。我们使用编程语言内置的哈希表时,通常认为时间复杂度是 $O(1)$ 。 + +**Q**:为什么不使用哈希函数 $f(x) = x$ 呢?这样就不会有冲突了。 + +在 $f(x) = x$ 哈希函数下,每个元素对应唯一的桶索引,这与数组等价。然而,输入空间通常远大于输出空间(数组长度),因此哈希函数的最后一步往往是对数组长度取模。换句话说,哈希表的目标是将一个较大的状态空间映射到一个较小的空间,并提供 $O(1)$ 的查询效率。 + +**Q**:哈希表底层实现是数组、链表、二叉树,但为什么效率可以比它们更高呢? + +首先,哈希表的时间效率变高,但空间效率变低了。哈希表有相当一部分内存未使用。 + +其次,只是在特定使用场景下时间效率变高了。如果一个功能能够在相同的时间复杂度下使用数组或链表实现,那么通常比哈希表更快。这是因为哈希函数计算需要开销,时间复杂度的常数项更大。 + +最后,哈希表的时间复杂度可能发生劣化。例如在链式地址中,我们采取在链表或红黑树中执行查找操作,仍然有退化至 $O(n)$ 时间的风险。 + +**Q**:多次哈希有不能直接删除元素的缺陷吗?标记为已删除的空间还能再次使用吗? + +多次哈希是开放寻址的一种,开放寻址法都有不能直接删除元素的缺陷,需要通过标记删除。标记为已删除的空间可以再次使用。当将新元素插入哈希表,并且通过哈希函数找到标记为已删除的位置时,该位置可以被新元素使用。这样做既能保持哈希表的探测序列不变,又能保证哈希表的空间使用率。 + +**Q**:为什么在线性探测中,查找元素的时候会出现哈希冲突呢? + +查找的时候通过哈希函数找到对应的桶和键值对,发现 `key` 不匹配,这就代表有哈希冲突。因此,线性探测法会根据预先设定的步长依次向下查找,直至找到正确的键值对或无法找到跳出为止。 + +**Q**:为什么哈希表扩容能够缓解哈希冲突? + +哈希函数的最后一步往往是对数组长度 $n$ 取模(取余),让输出值落在数组索引范围内;在扩容后,数组长度 $n$ 发生变化,而 `key` 对应的索引也可能发生变化。原先落在同一个桶的多个 `key` ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。 diff --git a/docs/chapter_heap/build_heap.assets/heapify_operations_count.png b/docs/chapter_heap/build_heap.assets/heapify_operations_count.png new file mode 100644 index 0000000000..644da4b930 Binary files /dev/null and b/docs/chapter_heap/build_heap.assets/heapify_operations_count.png differ diff --git a/docs/chapter_heap/build_heap.md b/docs/chapter_heap/build_heap.md new file mode 100644 index 0000000000..a2c3c505aa --- /dev/null +++ b/docs/chapter_heap/build_heap.md @@ -0,0 +1,74 @@ +# 建堆操作 + +在某些情况下,我们希望使用一个列表的所有元素来构建一个堆,这个过程被称为“建堆操作”。 + +## 借助入堆操作实现 + +我们首先创建一个空堆,然后遍历列表,依次对每个元素执行“入堆操作”,即先将元素添加至堆的尾部,再对该元素执行“从底至顶”堆化。 + +每当一个元素入堆,堆的长度就加一。由于节点是从顶到底依次被添加进二叉树的,因此堆是“自上而下”构建的。 + +设元素数量为 $n$ ,每个元素的入堆操作使用 $O(\log{n})$ 时间,因此该建堆方法的时间复杂度为 $O(n \log n)$ 。 + +## 通过遍历堆化实现 + +实际上,我们可以实现一种更为高效的建堆方法,共分为两步。 + +1. 将列表所有元素原封不动地添加到堆中,此时堆的性质尚未得到满足。 +2. 倒序遍历堆(层序遍历的倒序),依次对每个非叶节点执行“从顶至底堆化”。 + +**每当堆化一个节点后,以该节点为根节点的子树就形成一个合法的子堆**。而由于是倒序遍历,因此堆是“自下而上”构建的。 + +之所以选择倒序遍历,是因为这样能够保证当前节点之下的子树已经是合法的子堆,这样堆化当前节点才是有效的。 + +值得说明的是,**由于叶节点没有子节点,因此它们天然就是合法的子堆,无须堆化**。如以下代码所示,最后一个非叶节点是最后一个节点的父节点,我们从它开始倒序遍历并执行堆化: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{__init__} +``` + +## 复杂度分析 + +下面,我们来尝试推算第二种建堆方法的时间复杂度。 + +- 假设完全二叉树的节点数量为 $n$ ,则叶节点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此需要堆化的节点数量为 $(n - 1) / 2$ 。 +- 在从顶至底堆化的过程中,每个节点最多堆化到叶节点,因此最大迭代次数为二叉树高度 $\log n$ 。 + +将上述两者相乘,可得到建堆过程的时间复杂度为 $O(n \log n)$ 。**但这个估算结果并不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的性质**。 + +接下来我们来进行更为准确的计算。为了降低计算难度,假设给定一个节点数量为 $n$ 、高度为 $h$ 的“完美二叉树”,该假设不会影响计算结果的正确性。 + +![完美二叉树的各层节点数量](build_heap.assets/heapify_operations_count.png) + +如上图所示,节点“从顶至底堆化”的最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”。因此,我们可以对各层的“节点数量 $\times$ 节点高度”求和,**得到所有节点的堆化迭代次数的总和**。 + +$$ +T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 +$$ + +化简上式需要借助中学的数列知识,先将 $T(h)$ 乘以 $2$ ,得到: + +$$ +\begin{aligned} +T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline +2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline +\end{aligned} +$$ + +使用错位相减法,用下式 $2 T(h)$ 减去上式 $T(h)$ ,可得: + +$$ +2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h +$$ + +观察上式,发现 $T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为: + +$$ +\begin{aligned} +T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline +& = 2^{h+1} - h - 2 \newline +& = O(2^h) +\end{aligned} +$$ + +进一步,高度为 $h$ 的完美二叉树的节点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 diff --git a/docs/chapter_heap/heap.assets/heap_poll_step1.png b/docs/chapter_heap/heap.assets/heap_poll_step1.png deleted file mode 100644 index f1a8d9050f..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step1.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step10.png b/docs/chapter_heap/heap.assets/heap_poll_step10.png deleted file mode 100644 index 027c7e1877..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step10.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step2.png b/docs/chapter_heap/heap.assets/heap_poll_step2.png deleted file mode 100644 index abef7ca8c6..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step2.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step3.png b/docs/chapter_heap/heap.assets/heap_poll_step3.png deleted file mode 100644 index 4a2db29a59..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step3.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step4.png b/docs/chapter_heap/heap.assets/heap_poll_step4.png deleted file mode 100644 index 64c4fd4253..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step4.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step5.png b/docs/chapter_heap/heap.assets/heap_poll_step5.png deleted file mode 100644 index 513d6fe8bf..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step5.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step6.png b/docs/chapter_heap/heap.assets/heap_poll_step6.png deleted file mode 100644 index 72f3ffac3c..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step6.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step7.png b/docs/chapter_heap/heap.assets/heap_poll_step7.png deleted file mode 100644 index 85263d5a40..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step7.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step8.png b/docs/chapter_heap/heap.assets/heap_poll_step8.png deleted file mode 100644 index 9231963db1..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step8.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_poll_step9.png b/docs/chapter_heap/heap.assets/heap_poll_step9.png deleted file mode 100644 index 34b578f22c..0000000000 Binary files a/docs/chapter_heap/heap.assets/heap_poll_step9.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step1.png b/docs/chapter_heap/heap.assets/heap_pop_step1.png new file mode 100644 index 0000000000..1f26ebab17 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step1.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step10.png b/docs/chapter_heap/heap.assets/heap_pop_step10.png new file mode 100644 index 0000000000..450ad9b8f2 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step10.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step2.png b/docs/chapter_heap/heap.assets/heap_pop_step2.png new file mode 100644 index 0000000000..a9508de717 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step2.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step3.png b/docs/chapter_heap/heap.assets/heap_pop_step3.png new file mode 100644 index 0000000000..094ee661cc Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step3.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step4.png b/docs/chapter_heap/heap.assets/heap_pop_step4.png new file mode 100644 index 0000000000..3b3a7d25df Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step4.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step5.png b/docs/chapter_heap/heap.assets/heap_pop_step5.png new file mode 100644 index 0000000000..fa5d99b154 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step5.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step6.png b/docs/chapter_heap/heap.assets/heap_pop_step6.png new file mode 100644 index 0000000000..37b35e1422 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step6.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step7.png b/docs/chapter_heap/heap.assets/heap_pop_step7.png new file mode 100644 index 0000000000..0db8578692 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step7.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step8.png b/docs/chapter_heap/heap.assets/heap_pop_step8.png new file mode 100644 index 0000000000..8898f68294 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step8.png differ diff --git a/docs/chapter_heap/heap.assets/heap_pop_step9.png b/docs/chapter_heap/heap.assets/heap_pop_step9.png new file mode 100644 index 0000000000..c06d773757 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_pop_step9.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step1.png b/docs/chapter_heap/heap.assets/heap_push_step1.png index 00489b07c1..26c5fdaacc 100644 Binary files a/docs/chapter_heap/heap.assets/heap_push_step1.png and b/docs/chapter_heap/heap.assets/heap_push_step1.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step2.png b/docs/chapter_heap/heap.assets/heap_push_step2.png index a38eef32b2..e0fbc7e834 100644 Binary files a/docs/chapter_heap/heap.assets/heap_push_step2.png and b/docs/chapter_heap/heap.assets/heap_push_step2.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step3.png b/docs/chapter_heap/heap.assets/heap_push_step3.png index 0149120709..6cc315552d 100644 Binary files a/docs/chapter_heap/heap.assets/heap_push_step3.png and b/docs/chapter_heap/heap.assets/heap_push_step3.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step4.png b/docs/chapter_heap/heap.assets/heap_push_step4.png index 4379a31c3d..91aaaf7ccc 100644 Binary files a/docs/chapter_heap/heap.assets/heap_push_step4.png and b/docs/chapter_heap/heap.assets/heap_push_step4.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step5.png b/docs/chapter_heap/heap.assets/heap_push_step5.png index e810842ec0..33bd2749c0 100644 Binary files a/docs/chapter_heap/heap.assets/heap_push_step5.png and b/docs/chapter_heap/heap.assets/heap_push_step5.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step6.png b/docs/chapter_heap/heap.assets/heap_push_step6.png index 66ea466a5a..4430ae49ed 100644 Binary files a/docs/chapter_heap/heap.assets/heap_push_step6.png and b/docs/chapter_heap/heap.assets/heap_push_step6.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step7.png b/docs/chapter_heap/heap.assets/heap_push_step7.png new file mode 100644 index 0000000000..b7becec5f5 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_push_step7.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step8.png b/docs/chapter_heap/heap.assets/heap_push_step8.png new file mode 100644 index 0000000000..414aeaef13 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_push_step8.png differ diff --git a/docs/chapter_heap/heap.assets/heap_push_step9.png b/docs/chapter_heap/heap.assets/heap_push_step9.png new file mode 100644 index 0000000000..14d20ceaf7 Binary files /dev/null and b/docs/chapter_heap/heap.assets/heap_push_step9.png differ diff --git a/docs/chapter_heap/heap.assets/heapify_count.png b/docs/chapter_heap/heap.assets/heapify_count.png deleted file mode 100644 index 944aab5432..0000000000 Binary files a/docs/chapter_heap/heap.assets/heapify_count.png and /dev/null differ diff --git a/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png b/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png index fe1720d945..799f85c75f 100644 Binary files a/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png and b/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png differ diff --git a/docs/chapter_heap/heap.assets/representation_of_heap.png b/docs/chapter_heap/heap.assets/representation_of_heap.png index 5a9cac2005..366ff26951 100644 Binary files a/docs/chapter_heap/heap.assets/representation_of_heap.png and b/docs/chapter_heap/heap.assets/representation_of_heap.png differ diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index 73f425ff75..f4dd1d3485 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -1,627 +1,538 @@ ---- -comments: true ---- - # 堆 -「堆 Heap」是一颗限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型: - -- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值; -- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值; - -![min_heap_and_max_heap](heap.assets/min_heap_and_max_heap.png) - -## 堆术语与性质 - -- 由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。 -- 二叉树中的根结点对应「堆顶」,底层最靠右结点对应「堆底」。 -- 对于大顶堆 / 小顶堆,其堆顶元素(即根结点)的值最大 / 最小。 +堆(heap)是一种满足特定条件的完全二叉树,主要可分为两种类型,如下图所示。 -## 堆常用操作 +- 小顶堆(min heap):任意节点的值 $\leq$ 其子节点的值。 +- 大顶堆(max heap):任意节点的值 $\geq$ 其子节点的值。 -值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,**定义为具有出队优先级的队列**。 +![小顶堆与大顶堆](heap.assets/min_heap_and_max_heap.png) -而恰好,**堆的定义与优先队列的操作逻辑完全吻合**,大顶堆就是一个元素从大到小出队的优先队列。从使用角度看,我们可以将「优先队列」和「堆」理解为等价的数据结构。因此,本文与代码对两者不做特别区分,统一使用「堆」来命名。 +堆作为完全二叉树的一个特例,具有以下特性。 -堆的常用操作见下表(方法命名以 Java 为例)。 +- 最底层节点靠左填充,其他层的节点都被填满。 +- 我们将二叉树的根节点称为“堆顶”,将底层最靠右的节点称为“堆底”。 +- 对于大顶堆(小顶堆),堆顶元素(根节点)的值是最大(最小)的。 -

Table. 堆的常用操作

+## 堆的常用操作 -
+需要指出的是,许多编程语言提供的是优先队列(priority queue),这是一种抽象的数据结构,定义为具有优先级排序的队列。 -| 方法 | 描述 | 时间复杂度 | -| --------- | -------------------------------------------- | ----------- | -| add() | 元素入堆 | $O(\log n)$ | -| poll() | 堆顶元素出堆 | $O(\log n)$ | -| peek() | 访问堆顶元素(大 / 小顶堆分别为最大 / 小值) | $O(1)$ | -| size() | 获取堆的元素数量 | $O(1)$ | -| isEmpty() | 判断堆是否为空 | $O(1)$ | +实际上,**堆通常用于实现优先队列,大顶堆相当于元素按从大到小的顺序出队的优先队列**。从使用角度来看,我们可以将“优先队列”和“堆”看作等价的数据结构。因此,本书对两者不做特别区分,统一称作“堆”。 -
+堆的常用操作见下表,方法名需要根据编程语言来确定。 -我们可以直接使用编程语言提供的堆类(或优先队列类)。 +

  堆的操作效率

-!!! tip +| 方法名 | 描述 | 时间复杂度 | +| ----------- | ------------------------------------------------ | ----------- | +| `push()` | 元素入堆 | $O(\log n)$ | +| `pop()` | 堆顶元素出堆 | $O(\log n)$ | +| `peek()` | 访问堆顶元素(对于大 / 小顶堆分别为最大 / 小值) | $O(1)$ | +| `size()` | 获取堆的元素数量 | $O(1)$ | +| `isEmpty()` | 判断堆是否为空 | $O(1)$ | - 类似于排序中“从小到大排列”和“从大到小排列”,“大顶堆”和“小顶堆”可仅通过修改 Comparator 来互相转换。 +在实际应用中,我们可以直接使用编程语言提供的堆类(或优先队列类)。 -=== "Java" - - ```java title="heap.java" - /* 初始化堆 */ - // 初始化小顶堆 - Queue minHeap = new PriorityQueue<>(); - // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) - Queue maxHeap = new PriorityQueue<>((a, b) -> { return b - a; }); - - /* 元素入堆 */ - maxHeap.add(1); - maxHeap.add(3); - maxHeap.add(2); - maxHeap.add(5); - maxHeap.add(4); - - /* 获取堆顶元素 */ - int peek = maxHeap.peek(); // 5 - - /* 堆顶元素出堆 */ - // 出堆元素会形成一个从大到小的序列 - peek = heap.poll(); // 5 - peek = heap.poll(); // 4 - peek = heap.poll(); // 3 - peek = heap.poll(); // 2 - peek = heap.poll(); // 1 - - /* 获取堆大小 */ - int size = maxHeap.size(); - - /* 判断堆是否为空 */ - boolean isEmpty = maxHeap.isEmpty(); - - /* 输入列表并建堆 */ - minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); - ``` - -=== "C++" - - ```cpp title="heap.cpp" - - ``` +类似于排序算法中的“从小到大排列”和“从大到小排列”,我们可以通过设置一个 `flag` 或修改 `Comparator` 实现“小顶堆”与“大顶堆”之间的转换。代码如下所示: === "Python" ```python title="heap.py" + # 初始化小顶堆 + min_heap, flag = [], 1 + # 初始化大顶堆 + max_heap, flag = [], -1 - ``` + # Python 的 heapq 模块默认实现小顶堆 + # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 -=== "Go" + # 元素入堆 + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) - ```go title="heap.go" + # 获取堆顶元素 + peek: int = flag * max_heap[0] # 5 - ``` - -=== "JavaScript" + # 堆顶元素出堆 + # 出堆元素会形成一个从大到小的序列 + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 - ```js title="heap.js" - - ``` + # 获取堆大小 + size: int = len(max_heap) -=== "TypeScript" + # 判断堆是否为空 + is_empty: bool = not max_heap - ```typescript title="heap.ts" - - ``` - -=== "C" - - ```c title="heap.c" - - ``` - -=== "C#" - - ```csharp title="heap.cs" - - ``` - -=== "Swift" - - ```swift title="heap.swift" - - ``` - -## 堆的实现 - -下文实现的是「大顶堆」,若想转换为「小顶堆」,将所有大小逻辑判断取逆(例如将 $\geq$ 替换为 $\leq$ )即可,有兴趣的同学可自行实现。 - -### 堆的存储与表示 - -在二叉树章节我们学过,「完全二叉树」非常适合使用「数组」来表示,而堆恰好是一颗完全二叉树,**因而我们采用「数组」来存储「堆」**。 - -**二叉树指针**。使用数组表示二叉树时,元素代表结点值,索引代表结点在二叉树中的位置,**而结点指针通过索引映射公式来实现**。 - -具体地,给定索引 $i$ ,那么其左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$ 、父结点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空结点或结点不存在。 - -![representation_of_heap](heap.assets/representation_of_heap.png) - -我们将索引映射公式封装成函数,以便后续使用。 - -=== "Java" - - ```java title="my_heap.java" - // 使用列表而非数组,这样无需考虑扩容问题 - List maxHeap; - - /* 构造函数,建立空堆 */ - public MaxHeap() { - maxHeap = new ArrayList<>(); - } - - /* 获取左子结点索引 */ - int left(int i) { - return 2 * i + 1; - } - - /* 获取右子结点索引 */ - int right(int i) { - return 2 * i + 2; - } - - /* 获取父结点索引 */ - int parent(int i) { - return (i - 1) / 2; // 向下整除 - } + # 输入列表并建堆 + min_heap: list[int] = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) ``` === "C++" - ```cpp title="my_heap.cpp" - - ``` + ```cpp title="heap.cpp" + /* 初始化堆 */ + // 初始化小顶堆 + priority_queue, greater> minHeap; + // 初始化大顶堆 + priority_queue, less> maxHeap; -=== "Python" + /* 元素入堆 */ + maxHeap.push(1); + maxHeap.push(3); + maxHeap.push(2); + maxHeap.push(5); + maxHeap.push(4); - ```python title="my_heap.py" + /* 获取堆顶元素 */ + int peek = maxHeap.top(); // 5 - ``` + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + maxHeap.pop(); // 5 + maxHeap.pop(); // 4 + maxHeap.pop(); // 3 + maxHeap.pop(); // 2 + maxHeap.pop(); // 1 -=== "Go" + /* 获取堆大小 */ + int size = maxHeap.size(); - ```go title="my_heap.go" + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.empty(); + /* 输入列表并建堆 */ + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); ``` -=== "JavaScript" - - ```js title="my_heap.js" +=== "Java" - ``` + ```java title="heap.java" + /* 初始化堆 */ + // 初始化小顶堆 + Queue minHeap = new PriorityQueue<>(); + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); -=== "TypeScript" + /* 元素入堆 */ + maxHeap.offer(1); + maxHeap.offer(3); + maxHeap.offer(2); + maxHeap.offer(5); + maxHeap.offer(4); - ```typescript title="my_heap.ts" + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); // 5 - ``` + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.poll(); // 5 + peek = maxHeap.poll(); // 4 + peek = maxHeap.poll(); // 3 + peek = maxHeap.poll(); // 2 + peek = maxHeap.poll(); // 1 -=== "C" + /* 获取堆大小 */ + int size = maxHeap.size(); - ```c title="my_heap.c" + /* 判断堆是否为空 */ + boolean isEmpty = maxHeap.isEmpty(); + /* 输入列表并建堆 */ + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` === "C#" - ```csharp title="my_heap.cs" - - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - - ``` - -### 访问堆顶元素 - -堆顶元素是二叉树的根结点,即列表首元素。 - -=== "Java" - - ```java title="my_heap.java" - /* 访问堆顶元素 */ - public int peek() { - return maxHeap.get(0); - } - ``` + ```csharp title="heap.cs" + /* 初始化堆 */ + // 初始化小顶堆 + PriorityQueue minHeap = new(); + // 初始化大顶堆(使用 lambda 表达式修改 Comparer 即可) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); -=== "C++" + /* 元素入堆 */ + maxHeap.Enqueue(1, 1); + maxHeap.Enqueue(3, 3); + maxHeap.Enqueue(2, 2); + maxHeap.Enqueue(5, 5); + maxHeap.Enqueue(4, 4); - ```cpp title="my_heap.cpp" + /* 获取堆顶元素 */ + int peek = maxHeap.Peek();//5 - ``` + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.Dequeue(); // 5 + peek = maxHeap.Dequeue(); // 4 + peek = maxHeap.Dequeue(); // 3 + peek = maxHeap.Dequeue(); // 2 + peek = maxHeap.Dequeue(); // 1 -=== "Python" + /* 获取堆大小 */ + int size = maxHeap.Count; - ```python title="my_heap.py" + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.Count == 0; + /* 输入列表并建堆 */ + minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); ``` === "Go" - ```go title="my_heap.go" - - ``` - -=== "JavaScript" - - ```js title="my_heap.js" - - ``` - -=== "TypeScript" - - ```typescript title="my_heap.ts" - - ``` + ```go title="heap.go" + // Go 语言中可以通过实现 heap.Interface 来构建整数大顶堆 + // 实现 heap.Interface 需要同时实现 sort.Interface + type intHeap []any + + // Push heap.Interface 的方法,实现推入元素到堆 + func (h *intHeap) Push(x any) { + // Push 和 Pop 使用 pointer receiver 作为参数 + // 因为它们不仅会对切片的内容进行调整,还会修改切片的长度。 + *h = append(*h, x.(int)) + } -=== "C" + // Pop heap.Interface 的方法,实现弹出堆顶元素 + func (h *intHeap) Pop() any { + // 待出堆元素存放在最后 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last + } - ```c title="my_heap.c" + // Len sort.Interface 的方法 + func (h *intHeap) Len() int { + return len(*h) + } - ``` + // Less sort.Interface 的方法 + func (h *intHeap) Less(i, j int) bool { + // 如果实现小顶堆,则需要调整为小于号 + return (*h)[i].(int) > (*h)[j].(int) + } -=== "C#" + // Swap sort.Interface 的方法 + func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + } - ```csharp title="my_heap.cs" + // Top 获取堆顶元素 + func (h *intHeap) Top() any { + return (*h)[0] + } + /* Driver Code */ + func TestHeap(t *testing.T) { + /* 初始化堆 */ + // 初始化大顶堆 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 元素入堆 */ + // 调用 heap.Interface 的方法,来添加元素 + heap.Push(maxHeap, 1) + heap.Push(maxHeap, 3) + heap.Push(maxHeap, 2) + heap.Push(maxHeap, 4) + heap.Push(maxHeap, 5) + + /* 获取堆顶元素 */ + top := maxHeap.Top() + fmt.Printf("堆顶元素为 %d\n", top) + + /* 堆顶元素出堆 */ + // 调用 heap.Interface 的方法,来移除元素 + heap.Pop(maxHeap) // 5 + heap.Pop(maxHeap) // 4 + heap.Pop(maxHeap) // 3 + heap.Pop(maxHeap) // 2 + heap.Pop(maxHeap) // 1 + + /* 获取堆大小 */ + size := len(*maxHeap) + fmt.Printf("堆元素数量为 %d\n", size) + + /* 判断堆是否为空 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("堆是否为空 %t\n", isEmpty) + } ``` === "Swift" - ```swift title="my_heap.swift" - - ``` - -### 元素入堆 - -给定元素 `val` ,我们先将其添加到堆底。添加后,由于 `val` 可能大于堆中其它元素,此时堆的成立条件可能已经被破坏,**因此需要修复从插入结点到根结点这条路径上的各个结点**,该操作被称为「堆化 Heapify」。 - -考虑从入堆结点开始,**从底至顶执行堆化**。具体地,比较插入结点与其父结点的值,若插入结点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个结点;直至越过根结点时结束,或当遇到无需交换的结点时提前结束。 - -=== "Step 1" - ![heap_push_step1](heap.assets/heap_push_step1.png) - -=== "Step 2" - ![heap_push_step2](heap.assets/heap_push_step2.png) - -=== "Step 3" - ![heap_push_step3](heap.assets/heap_push_step3.png) - -=== "Step 4" - ![heap_push_step4](heap.assets/heap_push_step4.png) - -=== "Step 5" - ![heap_push_step5](heap.assets/heap_push_step5.png) - -=== "Step 6" - ![heap_push_step6](heap.assets/heap_push_step6.png) - -设结点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ ,**因而元素入堆操作的时间复杂度为 $O(\log n)$** 。 - -=== "Java" + ```swift title="heap.swift" + /* 初始化堆 */ + // Swift 的 Heap 类型同时支持最大堆和最小堆,且需要引入 swift-collections + var heap = Heap() - ```java title="my_heap.java" /* 元素入堆 */ - void push(int val) { - // 添加结点 - maxHeap.add(val); - // 从底至顶堆化 - siftUp(size() - 1); - } - - /* 从结点 i 开始,从底至顶堆化 */ - void siftUp(int i) { - while (true) { - // 获取结点 i 的父结点 - int p = parent(i); - // 若“越过根结点”或“结点无需修复”,则结束堆化 - if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) - break; - // 交换两结点 - swap(i, p); - // 循环向上堆化 - i = p; - } - } - ``` - -=== "C++" - - ```cpp title="my_heap.cpp" - - ``` - -=== "Python" - - ```python title="my_heap.py" - - ``` - -=== "Go" - - ```go title="my_heap.go" - - ``` + heap.insert(1) + heap.insert(3) + heap.insert(2) + heap.insert(5) + heap.insert(4) -=== "JavaScript" - - ```js title="my_heap.js" + /* 获取堆顶元素 */ + var peek = heap.max()! - ``` + /* 堆顶元素出堆 */ + peek = heap.removeMax() // 5 + peek = heap.removeMax() // 4 + peek = heap.removeMax() // 3 + peek = heap.removeMax() // 2 + peek = heap.removeMax() // 1 -=== "TypeScript" + /* 获取堆大小 */ + let size = heap.count - ```typescript title="my_heap.ts" + /* 判断堆是否为空 */ + let isEmpty = heap.isEmpty + /* 输入列表并建堆 */ + let heap2 = Heap([1, 3, 2, 5, 4]) ``` -=== "C" - - ```c title="my_heap.c" +=== "JS" + ```javascript title="heap.js" + // JavaScript 未提供内置 Heap 类 ``` -=== "C#" - - ```csharp title="my_heap.cs" +=== "TS" + ```typescript title="heap.ts" + // TypeScript 未提供内置 Heap 类 ``` -=== "Swift" - - ```swift title="my_heap.swift" +=== "Dart" + ```dart title="heap.dart" + // Dart 未提供内置 Heap 类 ``` -### 堆顶元素出堆 - -堆顶元素是二叉树根结点,即列表首元素,如果我们直接将首元素从列表中删除,则二叉树中所有结点都会随之发生移位(索引发生变化),这样后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤: - -1. 交换堆顶元素与堆底元素(即交换根结点与最右叶结点); -2. 交换完成后,将堆底从列表中删除(注意,因为已经交换,实际上删除的是原来的堆顶元素); -3. 从根结点开始,**从顶至底执行堆化**; - -顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根结点的值与其两个子结点的值,将最大的子结点与根结点执行交换,并循环以上操作,直到越过叶结点时结束,或当遇到无需交换的结点时提前结束。 +=== "Rust" -=== "Step 1" - ![heap_poll_step1](heap.assets/heap_poll_step1.png) + ```rust title="heap.rs" + use std::collections::BinaryHeap; + use std::cmp::Reverse; -=== "Step 2" - ![heap_poll_step2](heap.assets/heap_poll_step2.png) - -=== "Step 3" - ![heap_poll_step3](heap.assets/heap_poll_step3.png) - -=== "Step 4" - ![heap_poll_step4](heap.assets/heap_poll_step4.png) - -=== "Step 5" - ![heap_poll_step5](heap.assets/heap_poll_step5.png) - -=== "Step 6" - ![heap_poll_step6](heap.assets/heap_poll_step6.png) - -=== "Step 7" - ![heap_poll_step7](heap.assets/heap_poll_step7.png) - -=== "Step 8" - ![heap_poll_step8](heap.assets/heap_poll_step8.png) - -=== "Step 9" - ![heap_poll_step9](heap.assets/heap_poll_step9.png) - -=== "Step 10" - ![heap_poll_step10](heap.assets/heap_poll_step10.png) + /* 初始化堆 */ + // 初始化小顶堆 + let mut min_heap = BinaryHeap::>::new(); + // 初始化大顶堆 + let mut max_heap = BinaryHeap::new(); -与元素入堆操作类似,**堆顶元素出堆操作的时间复杂度为 $O(\log n)$** 。 + /* 元素入堆 */ + max_heap.push(1); + max_heap.push(3); + max_heap.push(2); + max_heap.push(5); + max_heap.push(4); -=== "Java" + /* 获取堆顶元素 */ + let peek = max_heap.peek().unwrap(); // 5 - ```java title="my_heap.java" - /* 元素出堆 */ - int poll() { - // 判空处理 - if (isEmpty()) - throw new EmptyStackException(); - // 交换根结点与最右叶结点(即交换首元素与尾元素) - swap(0, size() - 1); - // 删除结点 - int val = maxHeap.remove(size() - 1); - // 从顶至底堆化 - siftDown(0); - // 返回堆顶元素 - return val; - } - - /* 从结点 i 开始,从顶至底堆化 */ - void siftDown(int i) { - while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma - int l = left(i), r = right(i), ma = i; - if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) - ma = l; - if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) - ma = r; - // 若“结点 i 最大”或“越过叶结点”,则结束堆化 - if (ma == i) break; - // 交换两结点 - swap(i, ma); - // 循环向下堆化 - i = ma; - } - } - ``` + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + let peek = max_heap.pop().unwrap(); // 5 + let peek = max_heap.pop().unwrap(); // 4 + let peek = max_heap.pop().unwrap(); // 3 + let peek = max_heap.pop().unwrap(); // 2 + let peek = max_heap.pop().unwrap(); // 1 -=== "C++" + /* 获取堆大小 */ + let size = max_heap.len(); - ```cpp title="my_heap.cpp" + /* 判断堆是否为空 */ + let is_empty = max_heap.is_empty(); + /* 输入列表并建堆 */ + let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); ``` -=== "Python" - - ```python title="my_heap.py" +=== "C" + ```c title="heap.c" + // C 未提供内置 Heap 类 ``` -=== "Go" - - ```go title="my_heap.go" +=== "Kotlin" - ``` + ```kotlin title="heap.kt" + /* 初始化堆 */ + // 初始化小顶堆 + var minHeap = PriorityQueue() + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } -=== "JavaScript" + /* 元素入堆 */ + maxHeap.offer(1) + maxHeap.offer(3) + maxHeap.offer(2) + maxHeap.offer(5) + maxHeap.offer(4) - ```js title="my_heap.js" + /* 获取堆顶元素 */ + var peek = maxHeap.peek() // 5 - ``` + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.poll() // 5 + peek = maxHeap.poll() // 4 + peek = maxHeap.poll() // 3 + peek = maxHeap.poll() // 2 + peek = maxHeap.poll() // 1 -=== "TypeScript" + /* 获取堆大小 */ + val size = maxHeap.size - ```typescript title="my_heap.ts" + /* 判断堆是否为空 */ + val isEmpty = maxHeap.isEmpty() + /* 输入列表并建堆 */ + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` -=== "C" - - ```c title="my_heap.c" +=== "Ruby" + ```ruby title="heap.rb" + # Ruby 未提供内置 Heap 类 ``` -=== "C#" +=== "Zig" - ```csharp title="my_heap.cs" + ```zig title="heap.zig" ``` -=== "Swift" - - ```swift title="my_heap.swift" +??? pythontutor "可视化运行" - ``` + https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false -### 输入数据并建堆 * +## 堆的实现 -如果我们想要直接输入一个列表并将其建堆,那么该怎么做呢?最直接地,考虑使用「元素入堆」方法,将列表元素依次入堆。元素入堆的时间复杂度为 $O(n)$ ,而平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。 +下文实现的是大顶堆。若要将其转换为小顶堆,只需将所有大小逻辑判断进行逆转(例如,将 $\geq$ 替换为 $\leq$ )。感兴趣的读者可以自行实现。 -然而,存在一种更加优雅的建堆方法。设结点数量为 $n$ ,我们先将列表所有元素原封不动添加进堆,**然后迭代地对各个结点执行「从顶至底堆化」**。当然,**无需对叶结点执行堆化**,因为其没有子结点。 +### 堆的存储与表示 -=== "Java" +“二叉树”章节讲过,完全二叉树非常适合用数组来表示。由于堆正是一种完全二叉树,**因此我们将采用数组来存储堆**。 - ```java title="my_heap.java" - /* 构造函数,根据输入列表建堆 */ - public MaxHeap(List nums) { - // 将列表元素原封不动添加进堆 - maxHeap = new ArrayList<>(nums); - // 堆化除叶结点以外的其他所有结点 - for (int i = parent(size() - 1); i >= 0; i--) { - siftDown(i); - } - } - ``` +当使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置。**节点指针通过索引映射公式来实现**。 -=== "C++" +如下图所示,给定索引 $i$ ,其左子节点的索引为 $2i + 1$ ,右子节点的索引为 $2i + 2$ ,父节点的索引为 $(i - 1) / 2$(向下整除)。当索引越界时,表示空节点或节点不存在。 - ```cpp title="my_heap.cpp" +![堆的表示与存储](heap.assets/representation_of_heap.png) - ``` +我们可以将索引映射公式封装成函数,方便后续使用: -=== "Python" +```src +[file]{my_heap}-[class]{max_heap}-[func]{parent} +``` - ```python title="my_heap.py" +### 访问堆顶元素 - ``` +堆顶元素即为二叉树的根节点,也就是列表的首个元素: -=== "Go" +```src +[file]{my_heap}-[class]{max_heap}-[func]{peek} +``` - ```go title="my_heap.go" +### 元素入堆 - ``` +给定元素 `val` ,我们首先将其添加到堆底。添加之后,由于 `val` 可能大于堆中其他元素,堆的成立条件可能已被破坏,**因此需要修复从插入节点到根节点的路径上的各个节点**,这个操作被称为堆化(heapify)。 -=== "JavaScript" +考虑从入堆节点开始,**从底至顶执行堆化**。如下图所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。 - ```js title="my_heap.js" +=== "<1>" + ![元素入堆步骤](heap.assets/heap_push_step1.png) - ``` +=== "<2>" + ![heap_push_step2](heap.assets/heap_push_step2.png) -=== "TypeScript" +=== "<3>" + ![heap_push_step3](heap.assets/heap_push_step3.png) - ```typescript title="my_heap.ts" +=== "<4>" + ![heap_push_step4](heap.assets/heap_push_step4.png) - ``` +=== "<5>" + ![heap_push_step5](heap.assets/heap_push_step5.png) -=== "C" +=== "<6>" + ![heap_push_step6](heap.assets/heap_push_step6.png) - ```c title="my_heap.c" +=== "<7>" + ![heap_push_step7](heap.assets/heap_push_step7.png) - ``` +=== "<8>" + ![heap_push_step8](heap.assets/heap_push_step8.png) -=== "C#" +=== "<9>" + ![heap_push_step9](heap.assets/heap_push_step9.png) - ```csharp title="my_heap.cs" +设节点总数为 $n$ ,则树的高度为 $O(\log n)$ 。由此可知,堆化操作的循环轮数最多为 $O(\log n)$ ,**元素入堆操作的时间复杂度为 $O(\log n)$** 。代码如下所示: - ``` +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_up} +``` -=== "Swift" +### 堆顶元素出堆 - ```swift title="my_heap.swift" +堆顶元素是二叉树的根节点,即列表首元素。如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化进行修复变得困难。为了尽量减少元素索引的变动,我们采用以下操作步骤。 - ``` +1. 交换堆顶元素与堆底元素(交换根节点与最右叶节点)。 +2. 交换完成后,将堆底从列表中删除(注意,由于已经交换,因此实际上删除的是原来的堆顶元素)。 +3. 从根节点开始,**从顶至底执行堆化**。 -那么,第二种建堆方法的时间复杂度时多少呢?我们来做一下简单推算。 +如下图所示,**“从顶至底堆化”的操作方向与“从底至顶堆化”相反**,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。 -- 完全二叉树中,设结点总数为 $n$ ,则叶结点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶结点后,需要堆化结点数量为 $(n - 1)/2$ ,即为 $O(n)$ ; -- 从顶至底堆化中,每个结点最多堆化至叶结点,因此最大迭代次数为二叉树高度 $O(\log n)$ ; +=== "<1>" + ![堆顶元素出堆步骤](heap.assets/heap_pop_step1.png) -将上述两者相乘,可得时间复杂度为 $O(n \log n)$ 。然而,该估算结果仍不够准确,因为我们没有考虑到 **二叉树底层结点远多于顶层结点** 的性质。 +=== "<2>" + ![heap_pop_step2](heap.assets/heap_pop_step2.png) -下面我们来尝试展开计算。为了减小计算难度,我们假设树是一个「完美二叉树」,该假设不会影响计算结果的正确性。设二叉树(即堆)结点数量为 $n$ ,树高度为 $h$ 。上文提到,**结点堆化最大迭代次数等于该结点到叶结点的距离,而这正是“结点高度”**。因此,我们将各层的“结点数量 $\times$ 结点高度”求和,即可得到所有结点的堆化的迭代次数总和。 +=== "<3>" + ![heap_pop_step3](heap.assets/heap_pop_step3.png) -$$ -T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1 -$$ +=== "<4>" + ![heap_pop_step4](heap.assets/heap_pop_step4.png) -![heapify_count](heap.assets/heapify_count.png) +=== "<5>" + ![heap_pop_step5](heap.assets/heap_pop_step5.png) -化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得 +=== "<6>" + ![heap_pop_step6](heap.assets/heap_pop_step6.png) -$$ -\begin{aligned} -T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{h-1}\times1 \newline -2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \cdots + 2^{h}\times1 \newline -\end{aligned} -$$ +=== "<7>" + ![heap_pop_step7](heap.assets/heap_pop_step7.png) -**使用错位相减法**,令下式 $2 T(h)$ 减去上式 $T(h)$ ,可得 +=== "<8>" + ![heap_pop_step8](heap.assets/heap_pop_step8.png) -$$ -2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \cdots + 2^{h-1} + 2^h -$$ +=== "<9>" + ![heap_pop_step9](heap.assets/heap_pop_step9.png) -观察上式,$T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为 +=== "<10>" + ![heap_pop_step10](heap.assets/heap_pop_step10.png) -$$ -\begin{aligned} -T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline -& = 2^{h+1} - h \newline -& = O(2^h) -\end{aligned} -$$ +与元素入堆操作相似,堆顶元素出堆操作的时间复杂度也为 $O(\log n)$ 。代码如下所示: -进一步地,高度为 $h$ 的完美二叉树的结点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_down} +``` -## 堆常见应用 +## 堆的常见应用 -- **优先队列**。堆常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度为 $O(\log n)$ ,建队操作为 $O(n)$ ,皆非常高效。 -- **堆排序**。给定一组数据,我们使用其建堆,并依次全部弹出,则可以得到有序的序列。当然,堆排序一般无需弹出元素,仅需每轮将堆顶元素交换至数组尾部并减小堆的长度即可。 -- **获取最大的 $k$ 个元素**。这既是一道经典算法题目,也是一种常见应用,例如选取热度前 10 的新闻作为微博热搜,选取前 10 销量的商品等。 +- **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 $O(\log n)$ ,而建堆操作为 $O(n)$ ,这些操作都非常高效。 +- **堆排序**:给定一组数据,我们可以用它们建立一个堆,然后不断地执行元素出堆操作,从而得到有序数据。然而,我们通常会使用一种更优雅的方式实现堆排序,详见“堆排序”章节。 +- **获取最大的 $k$ 个元素**:这是一个经典的算法问题,同时也是一种典型应用,例如选择热度前 10 的新闻作为微博热搜,选取销量前 10 的商品等。 diff --git a/docs/chapter_heap/index.md b/docs/chapter_heap/index.md new file mode 100644 index 0000000000..3aeb408e16 --- /dev/null +++ b/docs/chapter_heap/index.md @@ -0,0 +1,9 @@ +# 堆 + +![堆](../assets/covers/chapter_heap.jpg) + +!!! abstract + + 堆就像是山岳峰峦,层叠起伏、形态各异。 + + 座座山峰高低错落,而最高的山峰总是最先映入眼帘。 diff --git a/docs/chapter_heap/summary.md b/docs/chapter_heap/summary.md new file mode 100644 index 0000000000..9c833aab46 --- /dev/null +++ b/docs/chapter_heap/summary.md @@ -0,0 +1,17 @@ +# 小结 + +### 重点回顾 + +- 堆是一棵完全二叉树,根据成立条件可分为大顶堆和小顶堆。大(小)顶堆的堆顶元素是最大(小)的。 +- 优先队列的定义是具有出队优先级的队列,通常使用堆来实现。 +- 堆的常用操作及其对应的时间复杂度包括:元素入堆 $O(\log n)$、堆顶元素出堆 $O(\log n)$ 和访问堆顶元素 $O(1)$ 等。 +- 完全二叉树非常适合用数组表示,因此我们通常使用数组来存储堆。 +- 堆化操作用于维护堆的性质,在入堆和出堆操作中都会用到。 +- 输入 $n$ 个元素并建堆的时间复杂度可以优化至 $O(n)$ ,非常高效。 +- Top-k 是一个经典算法问题,可以使用堆数据结构高效解决,时间复杂度为 $O(n \log k)$ 。 + +### Q & A + +**Q**:数据结构的“堆”与内存管理的“堆”是同一个概念吗? + +两者不是同一个概念,只是碰巧都叫“堆”。计算机系统内存中的堆是动态内存分配的一部分,程序在运行时可以使用它来存储数据。程序可以请求一定量的堆内存,用于存储如对象和数组等复杂结构。当这些数据不再需要时,程序需要释放这些内存,以防止内存泄漏。相较于栈内存,堆内存的管理和使用需要更谨慎,使用不当可能会导致内存泄漏和野指针等问题。 diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step1.png b/docs/chapter_heap/top_k.assets/top_k_heap_step1.png new file mode 100644 index 0000000000..72ba984c78 Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step1.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step2.png b/docs/chapter_heap/top_k.assets/top_k_heap_step2.png new file mode 100644 index 0000000000..4a518c0652 Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step2.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step3.png b/docs/chapter_heap/top_k.assets/top_k_heap_step3.png new file mode 100644 index 0000000000..99ee5ec5cb Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step3.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step4.png b/docs/chapter_heap/top_k.assets/top_k_heap_step4.png new file mode 100644 index 0000000000..b70e2177bd Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step4.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step5.png b/docs/chapter_heap/top_k.assets/top_k_heap_step5.png new file mode 100644 index 0000000000..09620a8d56 Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step5.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step6.png b/docs/chapter_heap/top_k.assets/top_k_heap_step6.png new file mode 100644 index 0000000000..44180cfd26 Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step6.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step7.png b/docs/chapter_heap/top_k.assets/top_k_heap_step7.png new file mode 100644 index 0000000000..32c07b636a Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step7.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step8.png b/docs/chapter_heap/top_k.assets/top_k_heap_step8.png new file mode 100644 index 0000000000..4510d58129 Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step8.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_heap_step9.png b/docs/chapter_heap/top_k.assets/top_k_heap_step9.png new file mode 100644 index 0000000000..804af12d2f Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_heap_step9.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_sorting.png b/docs/chapter_heap/top_k.assets/top_k_sorting.png new file mode 100644 index 0000000000..3f6514c57b Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_sorting.png differ diff --git a/docs/chapter_heap/top_k.assets/top_k_traversal.png b/docs/chapter_heap/top_k.assets/top_k_traversal.png new file mode 100644 index 0000000000..988e26121f Binary files /dev/null and b/docs/chapter_heap/top_k.assets/top_k_traversal.png differ diff --git a/docs/chapter_heap/top_k.md b/docs/chapter_heap/top_k.md new file mode 100644 index 0000000000..710ef423cd --- /dev/null +++ b/docs/chapter_heap/top_k.md @@ -0,0 +1,73 @@ +# Top-k 问题 + +!!! question + + 给定一个长度为 $n$ 的无序数组 `nums` ,请返回数组中最大的 $k$ 个元素。 + +对于该问题,我们先介绍两种思路比较直接的解法,再介绍效率更高的堆解法。 + +## 方法一:遍历选择 + +我们可以进行下图所示的 $k$ 轮遍历,分别在每轮中提取第 $1$、$2$、$\dots$、$k$ 大的元素,时间复杂度为 $O(nk)$ 。 + +此方法只适用于 $k \ll n$ 的情况,因为当 $k$ 与 $n$ 比较接近时,其时间复杂度趋向于 $O(n^2)$ ,非常耗时。 + +![遍历寻找最大的 k 个元素](top_k.assets/top_k_traversal.png) + +!!! tip + + 当 $k = n$ 时,我们可以得到完整的有序序列,此时等价于“选择排序”算法。 + +## 方法二:排序 + +如下图所示,我们可以先对数组 `nums` 进行排序,再返回最右边的 $k$ 个元素,时间复杂度为 $O(n \log n)$ 。 + +显然,该方法“超额”完成任务了,因为我们只需找出最大的 $k$ 个元素即可,而不需要排序其他元素。 + +![排序寻找最大的 k 个元素](top_k.assets/top_k_sorting.png) + +## 方法三:堆 + +我们可以基于堆更加高效地解决 Top-k 问题,流程如下图所示。 + +1. 初始化一个小顶堆,其堆顶元素最小。 +2. 先将数组的前 $k$ 个元素依次入堆。 +3. 从第 $k + 1$ 个元素开始,若当前元素大于堆顶元素,则将堆顶元素出堆,并将当前元素入堆。 +4. 遍历完成后,堆中保存的就是最大的 $k$ 个元素。 + +=== "<1>" + ![基于堆寻找最大的 k 个元素](top_k.assets/top_k_heap_step1.png) + +=== "<2>" + ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) + +=== "<3>" + ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) + +=== "<4>" + ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) + +=== "<5>" + ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) + +=== "<6>" + ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) + +=== "<7>" + ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) + +=== "<8>" + ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) + +=== "<9>" + ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) + +示例代码如下: + +```src +[file]{top_k}-[class]{}-[func]{top_k_heap} +``` + +总共执行了 $n$ 轮入堆和出堆,堆的最大长度为 $k$ ,因此时间复杂度为 $O(n \log k)$ 。该方法的效率很高,当 $k$ 较小时,时间复杂度趋向 $O(n)$ ;当 $k$ 较大时,时间复杂度不会超过 $O(n \log n)$ 。 + +另外,该方法适用于动态数据流的使用场景。在不断加入数据时,我们可以持续维护堆内的元素,从而实现最大的 $k$ 个元素的动态更新。 diff --git a/docs/chapter_hello_algo/index.md b/docs/chapter_hello_algo/index.md new file mode 100644 index 0000000000..c89461d59d --- /dev/null +++ b/docs/chapter_hello_algo/index.md @@ -0,0 +1,30 @@ +--- +comments: true +icon: material/rocket-launch-outline +--- + +# 序 + +几年前,我在力扣上分享了“剑指 Offer”系列题解,受到了许多读者的鼓励和支持。在与读者交流期间,我最常被问的一个问题是“如何入门算法”。逐渐地,我对这个问题产生了浓厚的兴趣。 + +两眼一抹黑地刷题似乎是最受欢迎的方法,简单、直接且有效。然而刷题就如同玩“扫雷”游戏,自学能力强的人能够顺利将地雷逐个排掉,而基础不足的人很可能被炸得满头是包,并在挫折中步步退缩。通读教材也是一种常见做法,但对于面向求职的人来说,毕业论文、投递简历、准备笔试和面试已经消耗了大部分精力,啃厚重的书往往变成了一项艰巨的挑战。 + +如果你也面临类似的困扰,那么很幸运这本书“找”到了你。本书是我对这个问题给出的答案,即使不是最优解,也至少是一次积极的尝试。本书虽然不足以让你直接拿到 Offer,但会引导你探索数据结构与算法的“知识地图”,带你了解不同“地雷”的形状、大小和分布位置,让你掌握各种“排雷方法”。有了这些本领,相信你可以更加自如地刷题和阅读文献,逐步构建起完整的知识体系。 + +我深深赞同费曼教授所言:“Knowledge isn't free. You have to pay attention.”从这个意义上看,这本书并非完全“免费”。为了不辜负你为本书所付出的宝贵“注意力”,我会竭尽所能,投入最大的“注意力”来完成本书的创作。 + +本人自知学疏才浅,书中内容虽然已经过一段时间的打磨,但一定仍有许多错误,恳请各位老师和同学批评指正。 + +![Hello 算法](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" } + +
+

Hello,算法!

+
+ +计算机的出现给世界带来了巨大变革,它凭借高速的计算能力和出色的可编程性,成为了执行算法与处理数据的理想媒介。无论是电子游戏的逼真画面、自动驾驶的智能决策,还是 AlphaGo 的精彩棋局、ChatGPT 的自然交互,这些应用都是算法在计算机上的精妙演绎。 + +事实上,在计算机问世之前,算法和数据结构就已经存在于世界的各个角落。早期的算法相对简单,例如古代的计数方法和工具制作步骤等。随着文明的进步,算法逐渐变得更加精细和复杂。从巧夺天工的匠人技艺、到解放生产力的工业产品、再到宇宙运行的科学规律,几乎每一件平凡或令人惊叹的事物背后,都隐藏着精妙的算法思想。 + +同样,数据结构无处不在:大到社会网络,小到地铁线路,许多系统都可以建模为“图”;大到一个国家,小到一个家庭,社会的主要组织形式呈现出“树”的特征;冬天的衣服就像“栈”,最先穿上的最后才能脱下;羽毛球筒则如同“队列”,一端放入、另一端取出;字典就像一个“哈希表”,能够快速查找目标词条。 + +本书旨在通过清晰易懂的动画图解和可运行的代码示例,使读者理解算法和数据结构的核心概念,并能够通过编程来实现它们。在此基础上,本书致力于揭示算法在复杂世界中的生动体现,展现算法之美。希望本书能够帮助到你! diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png new file mode 100644 index 0000000000..83b560f9a5 Binary files /dev/null and b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png new file mode 100644 index 0000000000..d1a95d4e44 Binary files /dev/null and b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png new file mode 100644 index 0000000000..237b8dcf58 Binary files /dev/null and b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png new file mode 100644 index 0000000000..74a3d0fff7 Binary files /dev/null and b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png new file mode 100644 index 0000000000..7c43143011 Binary files /dev/null and b/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png new file mode 100644 index 0000000000..fa970c15cb Binary files /dev/null and b/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_1.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_1.png deleted file mode 100644 index f05b05178e..0000000000 Binary files a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_1.png and /dev/null differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_2.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_2.png deleted file mode 100644 index c4cc5d9d4e..0000000000 Binary files a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_2.png and /dev/null differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_3.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_3.png deleted file mode 100644 index 828c626068..0000000000 Binary files a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_3.png and /dev/null differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_4.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_4.png deleted file mode 100644 index c13db8dace..0000000000 Binary files a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_4.png and /dev/null differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_5.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_5.png deleted file mode 100644 index ccd4f7ffa8..0000000000 Binary files a/docs/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_5.png and /dev/null differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png b/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png new file mode 100644 index 0000000000..b22d452f29 Binary files /dev/null and b/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png differ diff --git a/docs/chapter_introduction/algorithms_are_everywhere.md b/docs/chapter_introduction/algorithms_are_everywhere.md index 5d80f9c92e..0b2e51280f 100644 --- a/docs/chapter_introduction/algorithms_are_everywhere.md +++ b/docs/chapter_introduction/algorithms_are_everywhere.md @@ -1,38 +1,56 @@ ---- -comments: true ---- - # 算法无处不在 -听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。 +当我们听到“算法”这个词时,很自然地会想到数学。然而实际上,许多算法并不涉及复杂数学,而是更多地依赖基本逻辑,这些逻辑在我们的日常生活中处处可见。 + +在正式探讨算法之前,有一个有趣的事实值得分享:**你已经在不知不觉中学会了许多算法,并习惯将它们应用到日常生活中了**。下面我将举几个具体的例子来证实这一点。 + +**例一:查字典**。在字典里,每个汉字都对应一个拼音,而字典是按照拼音字母顺序排列的。假设我们需要查找一个拼音首字母为 $r$ 的字,通常会按照下图所示的方式实现。 + +1. 翻开字典约一半的页数,查看该页的首字母是什么,假设首字母为 $m$ 。 +2. 由于在拼音字母表中 $r$ 位于 $m$ 之后,所以排除字典前半部分,查找范围缩小到后半部分。 +3. 不断重复步骤 `1.` 和步骤 `2.` ,直至找到拼音首字母为 $r$ 的页码为止。 + +=== "<1>" + ![查字典步骤](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) + +=== "<2>" + ![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) + +=== "<3>" + ![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) + +=== "<4>" + ![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) + +=== "<5>" + ![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) + +查字典这个小学生必备技能,实际上就是著名的“二分查找”算法。从数据结构的角度,我们可以把字典视为一个已排序的“数组”;从算法的角度,我们可以将上述查字典的一系列操作看作“二分查找”。 + +**例二:整理扑克**。我们在打牌时,每局都需要整理手中的扑克牌,使其从小到大排列,实现流程如下图所示。 -在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中**。接下来,我将介绍两个具体例子来佐证。 +1. 将扑克牌划分为“有序”和“无序”两部分,并假设初始状态下最左 1 张扑克牌已经有序。 +2. 在无序部分抽出一张扑克牌,插入至有序部分的正确位置;完成后最左 2 张扑克已经有序。 +3. 不断循环步骤 `2.` ,每一轮将一张扑克牌从无序部分插入至有序部分,直至所有扑克牌都有序。 -**例一:拼积木**。一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。 +![扑克排序步骤](algorithms_are_everywhere.assets/playing_cards_sorting.png) -如果从数据结构与算法的角度看,大大小小的「积木」就是数据结构,而「拼装说明书」上的一系列步骤就是算法。 +上述整理扑克牌的方法本质上是“插入排序”算法,它在处理小型数据集时非常高效。许多编程语言的排序库函数中都有插入排序的身影。 -**例二:查字典**。在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做: +**例三:货币找零**。假设我们在超市购买了 $69$ 元的商品,给了收银员 $100$ 元,则收银员需要找我们 $31$ 元。他会很自然地完成如下图所示的思考。 -1. 打开字典大致一半页数的位置,查看此页的首字母是什么(假设为 $m$ ); -2. 由于在英文字母表中 $r$ 在 $m$ 的后面,因此应排除字典前半部分,查找范围仅剩后半部分; -3. 循环执行步骤 1-2 ,直到找到拼音首字母为 $r$ 的页码时终止。 +1. 可选项是比 $31$ 元面值更小的货币,包括 $1$ 元、$5$ 元、$10$ 元、$20$ 元。 +2. 从可选项中拿出最大的 $20$ 元,剩余 $31 - 20 = 11$ 元。 +3. 从剩余可选项中拿出最大的 $10$ 元,剩余 $11 - 10 = 1$ 元。 +4. 从剩余可选项中拿出最大的 $1$ 元,剩余 $1 - 1 = 0$ 元。 +5. 完成找零,方案为 $20 + 10 + 1 = 31$ 元。 -=== "Step 1" - ![look_up_dictionary_step_1](algorithms_are_everywhere.assets/look_up_dictionary_step_1.png) -=== "Step 2" - ![look_up_dictionary_step_2](algorithms_are_everywhere.assets/look_up_dictionary_step_2.png) -=== "Step 3" - ![look_up_dictionary_step_3](algorithms_are_everywhere.assets/look_up_dictionary_step_3.png) -=== "Step 4" - ![look_up_dictionary_step_4](algorithms_are_everywhere.assets/look_up_dictionary_step_4.png) -=== "Step 5" - ![look_up_dictionary_step_5](algorithms_are_everywhere.assets/look_up_dictionary_step_5.png) +![货币找零过程](algorithms_are_everywhere.assets/greedy_change.png) -查字典这个小学生的标配技能,实际上就是大名鼎鼎的「二分查找」。从数据结构角度,我们可以将字典看作是一个已排序的「数组」;而从算法角度,我们可将上述查字典的一系列指令看作是「二分查找」算法。 +在以上步骤中,我们每一步都采取当前看来最好的选择(尽可能用大面额的货币),最终得到了可行的找零方案。从数据结构与算法的角度看,这种方法本质上是“贪心”算法。 -小到烹饪一道菜、大到星际航行,几乎所有问题的解决都离不开算法。计算机的出现,使我们可以通过编程将数据结构存储在内存中,也可以编写代码来调用 CPU, GPU 执行算法,从而将生活中的问题搬运到计算机中,更加高效地解决各式各样的复杂问题。 +小到烹饪一道菜,大到星际航行,几乎所有问题的解决都离不开算法。计算机的出现使得我们能够通过编程将数据结构存储在内存中,同时编写代码调用 CPU 和 GPU 执行算法。这样一来,我们就能把生活中的问题转移到计算机上,以更高效的方式解决各种复杂问题。 !!! tip - 读到这里,如果你感到对数据结构、算法、数组、二分查找等此类概念一知半解,那么就太好了!因为这正是本书存在的价值,接下来,本书将会一步步地引导你进入数据结构与算法的知识殿堂。 + 如果你对数据结构、算法、数组和二分查找等概念仍感到一知半解,请继续往下阅读,本书将引导你迈入数据结构与算法的知识殿堂。 diff --git a/docs/chapter_introduction/index.md b/docs/chapter_introduction/index.md new file mode 100644 index 0000000000..81959f7946 --- /dev/null +++ b/docs/chapter_introduction/index.md @@ -0,0 +1,9 @@ +# 初识算法 + +![初识算法](../assets/covers/chapter_introduction.jpg) + +!!! abstract + + 一位少女翩翩起舞,与数据交织在一起,裙摆上飘扬着算法的旋律。 + + 她邀请你共舞,请紧跟她的步伐,踏入充满逻辑与美感的算法世界。 diff --git a/docs/chapter_introduction/summary.md b/docs/chapter_introduction/summary.md new file mode 100644 index 0000000000..ba538bcaa6 --- /dev/null +++ b/docs/chapter_introduction/summary.md @@ -0,0 +1,22 @@ +# 小结 + +- 算法在日常生活中无处不在,并不是遥不可及的高深知识。实际上,我们已经在不知不觉中学会了许多算法,用以解决生活中的大小问题。 +- 查字典的原理与二分查找算法相一致。二分查找算法体现了分而治之的重要算法思想。 +- 整理扑克的过程与插入排序算法非常类似。插入排序算法适合排序小型数据集。 +- 货币找零的步骤本质上是贪心算法,每一步都采取当前看来最好的选择。 +- 算法是在有限时间内解决特定问题的一组指令或操作步骤,而数据结构是计算机中组织和存储数据的方式。 +- 数据结构与算法紧密相连。数据结构是算法的基石,而算法为数据结构注入生命力。 +- 我们可以将数据结构与算法类比为拼装积木,积木代表数据,积木的形状和连接方式等代表数据结构,拼装积木的步骤则对应算法。 + +### Q & A + +**Q**:作为一名程序员,我在日常工作中从未用算法解决过问题,常用算法都被编程语言封装好了,直接用就可以了;这是否意味着我们工作中的问题还没有到达需要算法的程度? + +如果把具体的工作技能比作是武功的“招式”的话,那么基础科目应该更像是“内功”。 + +我认为学算法(以及其他基础科目)的意义不是在于在工作中从零实现它,而是基于学到的知识,在解决问题时能够作出专业的反应和判断,从而提升工作的整体质量。举一个简单例子,每种编程语言都内置了排序函数: + +- 如果我们没有学过数据结构与算法,那么给定任何数据,我们可能都塞给这个排序函数去做了。运行顺畅、性能不错,看上去并没有什么问题。 +- 但如果学过算法,我们就会知道内置排序函数的时间复杂度是 $O(n \log n)$ ;而如果给定的数据是固定位数的整数(例如学号),那么我们就可以用效率更高的“基数排序”来做,将时间复杂度降为 $O(nk)$ ,其中 $k$ 为位数。当数据体量很大时,节省出来的运行时间就能创造较大价值(成本降低、体验变好等)。 + +在工程领域中,大量问题是难以达到最优解的,许多问题只是被“差不多”地解决了。问题的难易程度一方面取决于问题本身的性质,另一方面也取决于观测问题的人的知识储备。人的知识越完备、经验越多,分析问题就会越深入,问题就能被解决得更优雅。 diff --git a/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png b/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png new file mode 100644 index 0000000000..d9e2065fa5 Binary files /dev/null and b/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png differ diff --git a/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png b/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png index 1ab8721df4..e088e5974d 100644 Binary files a/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png and b/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png differ diff --git a/docs/chapter_introduction/what_is_dsa.md b/docs/chapter_introduction/what_is_dsa.md index 67b3c37e1a..05485f85fd 100644 --- a/docs/chapter_introduction/what_is_dsa.md +++ b/docs/chapter_introduction/what_is_dsa.md @@ -1,53 +1,53 @@ ---- -comments: true ---- - # 算法是什么 ## 算法定义 -「算法 Algorithm」是在有限时间内解决问题的一组指令或操作步骤。算法具有以下特性: +算法(algorithm)是在有限时间内解决特定问题的一组指令或操作步骤,它具有以下特性。 -- 问题是明确的,需要拥有明确的输入和输出定义。 -- 解具有确定性,即给定相同输入时,输出一定相同。 -- 具有可行性,可在有限步骤、有限时间、有限内存空间下完成。 -- 独立于编程语言,即可用多种语言实现。 +- 问题是明确的,包含清晰的输入和输出定义。 +- 具有可行性,能够在有限步骤、时间和内存空间下完成。 +- 各步骤都有确定的含义,在相同的输入和运行条件下,输出始终相同。 ## 数据结构定义 -「数据结构 Data Structure」是在计算机中组织与存储数据的方式。为了提高数据存储和操作性能,数据结构的设计原则有: +数据结构(data structure)是组织和存储数据的方式,涵盖数据内容、数据之间关系和数据操作方法,它具有以下设计目标。 -- 空间占用尽可能小,节省计算机内存。 -- 数据操作尽量快,包括数据访问、添加、删除、更新等。 +- 空间占用尽量少,以节省计算机内存。 +- 数据操作尽可能快速,涵盖数据访问、添加、删除、更新等。 - 提供简洁的数据表示和逻辑信息,以便算法高效运行。 -数据结构的设计是一个充满权衡的过程,这意味着如果获得某方面的优势,则往往需要在另一方面做出妥协。例如,链表相对于数组,数据添加删除操作更加方便,但牺牲了数据的访问速度;图相对于链表,提供了更多的逻辑信息,但需要占用更多的内存空间。 +**数据结构设计是一个充满权衡的过程**。如果想在某方面取得提升,往往需要在另一方面作出妥协。下面举两个例子。 + +- 链表相较于数组,在数据添加和删除操作上更加便捷,但牺牲了数据访问速度。 +- 图相较于链表,提供了更丰富的逻辑信息,但需要占用更大的内存空间。 ## 数据结构与算法的关系 -「数据结构」与「算法」是高度相关、紧密嵌合的,体现在: +如下图所示,数据结构与算法高度相关、紧密结合,具体表现在以下三个方面。 + +- 数据结构是算法的基石。数据结构为算法提供了结构化存储的数据,以及操作数据的方法。 +- 算法为数据结构注入生命力。数据结构本身仅存储数据信息,结合算法才能解决特定问题。 +- 算法通常可以基于不同的数据结构实现,但执行效率可能相差很大,选择合适的数据结构是关键。 -- 数据结构是算法的底座。数据结构为算法提供结构化存储的数据,以及操作数据的对应方法。 -- 算法是发挥数据结构优势的舞台。数据结构仅存储数据信息,结合算法才可解决特定问题。 -- 算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,而最终执行效率往往相差很大。 +![数据结构与算法的关系](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) -![relationship_between_data_structure_and_algorithm](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) +数据结构与算法犹如下图所示的拼装积木。一套积木,除了包含许多零件之外,还附有详细的组装说明书。我们按照说明书一步步操作,就能组装出精美的积木模型。 -

Fig. 数据结构与算法的关系

+![拼装积木](what_is_dsa.assets/assembling_blocks.png) -如果将「LEGO 乐高」类比到「数据结构与算法」,那么可以得到下表所示的对应关系。 +两者的详细对应关系如下表所示。 -
+

  将数据结构与算法类比为拼装积木

-| 数据结构与算法 | LEGO 乐高 | +| 数据结构与算法 | 拼装积木 | | -------------- | ---------------------------------------- | | 输入数据 | 未拼装的积木 | | 数据结构 | 积木组织形式,包括形状、大小、连接方式等 | | 算法 | 把积木拼成目标形态的一系列操作步骤 | | 输出数据 | 积木模型 | -
+值得说明的是,数据结构与算法是独立于编程语言的。正因如此,本书得以提供基于多种编程语言的实现。 !!! tip "约定俗成的简称" - 在实际讨论中,我们通常会将「数据结构与算法」直接简称为「算法」。例如,我们熟称的 LeetCode 算法题目,实际上同时考察了数据结构和算法两部分知识。 + 在实际讨论时,我们通常会将“数据结构与算法”简称为“算法”。比如众所周知的 LeetCode 算法题目,实际上同时考查数据结构和算法两方面的知识。 diff --git a/docs/chapter_paperbook/index.assets/book_jd_link.jpg b/docs/chapter_paperbook/index.assets/book_jd_link.jpg new file mode 100644 index 0000000000..3b8d8a58d1 Binary files /dev/null and b/docs/chapter_paperbook/index.assets/book_jd_link.jpg differ diff --git a/docs/chapter_paperbook/index.assets/paper_book_avl_tree.jpg b/docs/chapter_paperbook/index.assets/paper_book_avl_tree.jpg new file mode 100644 index 0000000000..c559a76bec Binary files /dev/null and b/docs/chapter_paperbook/index.assets/paper_book_avl_tree.jpg differ diff --git a/docs/chapter_paperbook/index.assets/paper_book_chapter_heap.jpg b/docs/chapter_paperbook/index.assets/paper_book_chapter_heap.jpg new file mode 100644 index 0000000000..9789e45b1d Binary files /dev/null and b/docs/chapter_paperbook/index.assets/paper_book_chapter_heap.jpg differ diff --git a/docs/chapter_paperbook/index.assets/paper_book_overview.jpg b/docs/chapter_paperbook/index.assets/paper_book_overview.jpg new file mode 100644 index 0000000000..400e37f55d Binary files /dev/null and b/docs/chapter_paperbook/index.assets/paper_book_overview.jpg differ diff --git a/docs/chapter_paperbook/index.md b/docs/chapter_paperbook/index.md new file mode 100644 index 0000000000..190c38e625 --- /dev/null +++ b/docs/chapter_paperbook/index.md @@ -0,0 +1,68 @@ +--- +comments: true +icon: material/book-open-page-variant +status: new +--- + +# 纸质书 + +经过长时间的打磨,《Hello 算法》纸质书终于发布了!此时的心情可以用一句诗来形容: + +

追风赶月莫停留,平芜尽处是春山。

+ +![](index.assets/paper_book_overview.jpg){ class="animation-figure" } + +以下视频展示了纸质书,并且包含我的一些思考: + +- 学习数据结构与算法的重要性。 +- 为什么在纸质书中选择 Python。 +- 对知识分享的理解。 + +> 新人 UP 主,请多多关照、一键三连~谢谢! + +
+ +
+ +附纸质书快照: + +![](index.assets/paper_book_chapter_heap.jpg){ class="animation-figure" } + +![](index.assets/paper_book_avl_tree.jpg){ class="animation-figure" } + +## 优势与不足 + +总结一下纸质书可能会给大家带来惊喜的地方: + +- 采用全彩印刷,能够原汁原味地发挥出本书“动画图解”的优势。 +- 考究纸张材质,既保证色彩高度还原,也保留纸质书特有的质感。 +- 纸质版比网页版的格式更加规范,例如图中的公式使用斜体。 +- 在不提升定价的前提下,附赠思维导图折页、书签。 +- 纸质书、网页版、PDF 版内容同步,随意切换阅读。 + +!!! tip + + 由于纸质书和网页版的同步难度较大,因此可能会有一些细节上的不同,请您见谅! + +当然,纸质书也有一些值得大家入手前考虑的地方: + +- 使用 Python 语言,可能不匹配你的主语言(可以把 Python 看作伪代码,重在理解思路)。 +- 全彩印刷虽然大幅提升了图解和代码的阅读体验,但价格会比黑白印刷高一些。 + +!!! tip + + “印刷质量”和“价格”就像算法中的“时间效率”和“空间效率”,难以两全。而我认为,“印刷质量”对应的是“时间效率”,更应该被注重。 + +## 购买链接 + +如果你对纸质书感兴趣,可以考虑入手一本。我们为大家争取到了新书 5 折优惠,请见[此链接](https://3.cn/1X-qmTD3)或扫描以下二维码: + +![](index.assets/book_jd_link.jpg){ class="animation-figure" } + +## 尾记 + +起初,我低估了纸质书出版的工作量,以为只要维护好了开源项目,纸质版就可以通过某些自动化手段生成出来。实践证明,纸质书的生产流程与开源项目的更新机制存在很大的不同,两者之间的转化需要做许多额外工作。 + +一本书的初稿与达到出版标准的定稿之间仍有较长距离,需要出版社(策划、编辑、设计、市场等)与作者的通力合作、长期雕琢。在此感谢图灵策划编辑王军花、以及人民邮电出版社和图灵社区每位参与本书出版流程的工作人员! + +希望这本书能够帮助到你! diff --git a/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png b/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png new file mode 100644 index 0000000000..98b412c699 Binary files /dev/null and b/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png differ diff --git a/docs/chapter_preface/about_the_book.assets/profile.png b/docs/chapter_preface/about_the_book.assets/profile.png deleted file mode 100644 index aeabdeb913..0000000000 Binary files a/docs/chapter_preface/about_the_book.assets/profile.png and /dev/null differ diff --git a/docs/chapter_preface/about_the_book.md b/docs/chapter_preface/about_the_book.md index 01f513f00d..c05626c262 100644 --- a/docs/chapter_preface/about_the_book.md +++ b/docs/chapter_preface/about_the_book.md @@ -1,254 +1,52 @@ ---- -comments: true ---- - # 关于本书 -五年前发生的一件事,成为了我职业生涯的重要转折点。当时的我在交大读研,对互联网求职一无所知,但仍然硬着头皮申请了 Microsoft 软件工程师实习。面试官让我在白板上写出“快速排序”代码,我畏畏缩缩地写了一个“冒泡排序”,并且还写错了` (ToT) ` 。从面试官的表情上,我看到了一个大大的 "GG" 。 - -此次失利倒逼我开始刷算法题。我采用“扫雷游戏”式的学习方法,两眼一抹黑刷题,扫到不会的“雷”就通过查资料把它“排掉”,配合周期性总结,逐渐形成了数据结构与算法的知识图景。幸运地,我在秋招斩获了多家大厂的 Offer 。 - -回想自己当初在“扫雷式”刷题中被炸的满头包的痛苦,思考良久,我意识到一本“前期刷题必看”的读物可以使算法小白少走许多弯路。写作意愿滚滚袭来,那就动笔吧: +本项目旨在创建一本开源、免费、对新手友好的数据结构与算法入门教程。 -

Hello,算法!

+- 全书采用动画图解,内容清晰易懂、学习曲线平滑,引导初学者探索数据结构与算法的知识地图。 +- 源代码可一键运行,帮助读者在练习中提升编程技能,了解算法工作原理和数据结构底层实现。 +- 提倡读者互助学习,欢迎大家在评论区提出问题与分享见解,在交流讨论中共同进步。 ## 读者对象 -!!! success "前置条件" - - 您需要至少具备任一语言的编程基础,能够阅读和编写简单代码。 +若你是算法初学者,从未接触过算法,或者已经有一些刷题经验,对数据结构与算法有模糊的认识,在会与不会之间反复横跳,那么本书正是为你量身定制的! -如果您是 **算法初学者**,完全没有接触过算法,或者已经有少量刷题,对数据结构与算法有朦胧的理解,在会与不会之间反复横跳,那么这本书就是为您而写!本书能够带来: +如果你已经积累一定的刷题量,熟悉大部分题型,那么本书可助你回顾与梳理算法知识体系,仓库源代码可以当作“刷题工具库”或“算法字典”来使用。 -- 了解刷题所需的 **数据结构**,包括常用操作、优势和劣势、典型应用、实现方法等。 -- 学习各类 **算法**,介绍算法的设计思想、运行效率、优势劣势、实现方法等。 -- 可一键运行的 **配套代码**,包含详细注释,帮助你通过实践加深理解。 +若你是算法“大神”,我们期待收到你的宝贵建议,或者[一起参与创作](https://www.hello-algo.com/chapter_appendix/contribution/)。 -如果您是 **算法熟练工**,已经积累一定刷题量,接触过大多数题型,那么本书内容对你来说可能稍显基础,但仍能够带来以下价值: - -- 本书篇幅不长,可以帮助你提纲挈领地回顾算法知识。 -- 书中包含许多对比性、总结性的算法内容,可以帮助你梳理算法知识体系。 -- 源代码实现了各种经典数据结构和算法,可以作为“刷题工具库”来使用。 +!!! success "前置条件" -如果您是 **算法大佬**,请受我膜拜!希望您可以抽时间提出意见建议,或者[一起参与创作](https://www.hello-algo.com/chapter_preface/contribution/),帮助各位同学获取更好的学习内容,感谢! + 你需要至少具备任一语言的编程基础,能够阅读和编写简单代码。 ## 内容结构 -本书主要内容分为复杂度分析、数据结构、算法三个部分。 - -![mindmap](index.assets/mindmap.png) - -

Fig. 知识点思维导图

- -### 复杂度分析 - -首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。 - -接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度** 和 **空间复杂度**,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。 - -### 数据结构 - -首先介绍了常用的 **基本数据类型** 、以及它们是如何在内存中存储的。 - -接下来,介绍了两种 **数据结构分类方法**,包括逻辑结构与物理结构。 - -后续展开介绍了 **数组、链表、栈、队列、散列表、树、堆、图** 等数据结构,关心以下内容: - -- 基本定义:数据结构的设计来源、存在意义; -- 主要特点:在各项数据操作中的优势、劣势; -- 常用操作:例如访问、更新、插入、删除、遍历、搜索等; -- 常见类型:在算法题或工程实际中,经常碰到的数据结构类型; -- 典型应用:此数据结构经常搭配哪些算法使用; -- 实现方法:对于重要的数据结构,将给出完整的实现示例; - -### 算法 - -包括 **查找算法、排序算法、搜索与回溯、动态规划、分治算法**,内容包括: - -- 基本定义:算法的设计思想; -- 主要特点:使用前置条件、优势和劣势; -- 算法效率:最差和平均时间复杂度、空间复杂度; -- 实现方法:完整的算法实现,以及优化措施; -- 示例题目:结合例题加深理解; +本书的主要内容如下图所示。 -## 配套代码 +- **复杂度分析**:数据结构和算法的评价维度与方法。时间复杂度和空间复杂度的推算方法、常见类型、示例等。 +- **数据结构**:基本数据类型和数据结构的分类方法。数组、链表、栈、队列、哈希表、树、堆、图等数据结构的定义、优缺点、常用操作、常见类型、典型应用、实现方法等。 +- **算法**:搜索、排序、分治、回溯、动态规划、贪心等算法的定义、优缺点、效率、应用场景、解题步骤和示例问题等。 -完整代码托管在 [GitHub 仓库](https://github.com/krahets/hello-algo) ,皆可一键运行。 - -!!! tip "前置工作" - - 1. [编程环境安装](https://www.hello-algo.com/chapter_preface/installation/) ,若有请跳过 - 2. 代码下载与使用方法请见 [如何使用本书](https://www.hello-algo.com/chapter_preface/suggestions/#_4) - -## 风格约定 - -- 标题后标注 * 符号的是选读章节,如果你的时间有限,可以先跳过这些章节。 -- 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。 -- 重点内容、总起句、总结句会被 **加粗**,此类文字值得特别关注。 -- 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。 -- 在工程应用中,每种语言都有注释规范;而本书放弃了一部分的注释规范性,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。 - -=== "Java" - - ```java title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "C++" - - ```cpp title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "Python" - - ```python title="" - """ 标题注释,用于标注函数、类、测试样例等 """ - - # 内容注释,用于详解代码 - - """ - 多行 - 注释 - """ - ``` - -=== "Go" - - ```go title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "JavaScript" - - ```js title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "TypeScript" - - ```typescript title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "C" - - ```c title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "C#" - - ```csharp title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "Swift" - - ```swift title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -## 本书特点 * - -??? abstract "默认折叠,可以跳过" - - **以实践为主**。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。 - - 本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。 - - 实践部分主要由示例和代码组成。代码配有简要注释,复杂示例会尽可能地使用视觉化的形式呈现。我强烈建议读者对照着代码自己敲一遍,如果时间有限,也至少逐行读、复制并运行一遍,配合着讲解将代码吃透。 - - **视觉化学习**。信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。 - - 近两年,短视频成为最受欢迎的信息媒介,可以在短时间内将高密度的信息“灌”给我们,有着极其舒适的观看体验。阅读则不然,读者与书本之间天然存在一种“疏离感”,我们看书会累、会走神、会停下来想其他事、会划下喜欢的句子、会思考某一片段的含义,这种疏离感给了读者与书本之间对话的可能,拓宽了想象空间。 - - 本书作为一本入门教材,希望可以保有书本的“慢节奏”,但也会避免与读者产生过多“疏离感”,而是努力将知识完整清晰地推送到你聪明的小脑袋瓜中。我将采用视觉化的方式(例如配图、动画),尽我可能清晰易懂地讲解复杂概念和抽象示例。 - - **内容精简化**。大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。 - - 引入一些生活案例或趣味内容,非常适合作为知识点的引子或者解释的补充,但当融入过多额外元素时,内容会稍显冗长,也许反而使读者容易迷失、抓不住重点,这也是本书需要避免的。 - - 敲代码如同写字,“美”是统一的追求。本书力求美观的代码,保证规范的变量命名、统一的空格与换行、对齐的缩进、整齐的注释等。 +![本书主要内容](about_the_book.assets/hello_algo_mindmap.png) ## 致谢 -本书的成书过程中,我获得了许多人的帮助,包括但不限于: - -- 感谢我的女朋友泡泡担任本书的首位读者,从算法小白的视角为本书的写作提出了许多建议,使这本书更加适合算法初学者来阅读。 -- 感谢腾宝、琦宝、飞宝为本书起了个响当当的名字,好听又有梗,直接唤起我最初敲下第一行代码 "Hello, World!" 的回忆。 -- 感谢我的导师李博,在小酌畅谈时您告诉我“觉得适合、想做就去做”,坚定了我写这本书的决心。 -- 感谢苏潼为本书设计了封面和 LOGO ,我有些强迫症,前后多次修改,谢谢你的耐心。 -- 感谢 @squidfunk ,包括 [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) 顶级开源项目以及给出的写作排版建议。 +本书在开源社区众多贡献者的共同努力下不断完善。感谢每一位投入时间与精力的撰稿人,他们是(按照 GitHub 自动生成的顺序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、msk397、gvenusleo、khoaxuantu、RiverTwilight、rongyi、gyt95、zhuoqinyue、K3v123、Zuoxun、mingXta、hello-ikun、FangYuan33、GN-Yu、yuelinxin、longsizhuo、Cathay-Chen、guowei-gong、xBLACKICEx、IsChristina、JoseHung、qualifier1024、QiLOL、pengchzn、Guanngxu、L-Super、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、theNefelibatas、longranger2、cy-by-side、xiongsp、JeffersonHuang、Transmigration-zhou、magentaqin、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、Shyam-Chen、nanlei、hongyun-robot、Phoenix0415、MolDuM、Nigh、he-weilai、junminhong、mgisr、iron-irax、yd-j、XiaChuerwu、XC-Zero、seven1240、SamJin98、wodray、reeswell、NI-SW、Horbin-Magician、Enlightenus、xjr7670、YangXuanyi、DullSword、boloboloda、iStig、qq909244296、jiaxianhua、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、liuxjerry、lucaswangdev、lyl625760、hts0000、gledfish、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、luluxia、xb534、bitsmi、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、steventimes、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、ZhongGuanbin、shanghai-Jerry、JackYang-hellobobo、Javesun99、lipusheng、BlindTerran、ShiMaRing、FreddieLi、FloranceYeh、iFleey、fanchenggang、gltianwen、goerll、Dr-XYZ、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、fanenr、eagleanurag、LifeGoesOnionOnionOnion、52coder、foursevenlove、KorsChen、hezhizhen、linzeyan、ZJKung、GaochaoZhu、hopkings2008、yang-le、Evilrabbit520、Turing-1024-Lee、thomasq0、Suremotoo、Allen-Scai、Risuntsy、Richard-Zhang1019、qingpeng9802、primexiao、nidhoggfgg、1ch0、MwumLi、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai 和 0130w。 -在写作过程中,我阅读了许多与数据结构与算法的书籍材料,学习到了许多知识,感谢前辈们的精彩创作。 +本书的代码审阅工作由 coderonion、curtishd、Gonglja、gvenusleo、hpstory、justin-tse、khoaxuantu、krahets、night-cruise、nuomi1、Reanon 和 rongyi 完成(按照首字母顺序排列)。感谢他们付出的时间与精力,正是他们确保了各语言代码的规范与统一。 -感谢父母,你们一贯的支持与鼓励给了我自由度来做这些有趣的事。 +本书的繁体中文版由 Shyam-Chen 和 Dr-XYZ 审阅,英文版由 yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0 和 magentaqin 审阅。正是因为他们的持续贡献,这本书才能够服务于更广泛的读者群体,感谢他们。 -## 作者简介 +在本书的创作过程中,我得到了许多人的帮助。 -![profile](about_the_book.assets/profile.png){: .center} +- 感谢我在公司的导师李汐博士,在一次畅谈中你鼓励我“快行动起来”,坚定了我写这本书的决心; +- 感谢我的女朋友泡泡作为本书的首位读者,从算法小白的角度提出许多宝贵建议,使得本书更适合新手阅读; +- 感谢腾宝、琦宝、飞宝为本书起了一个富有创意的名字,唤起大家写下第一行代码“Hello World!”的美好回忆; +- 感谢校铨在知识产权方面提供的专业帮助,这对本开源书的完善起到了重要作用; +- 感谢苏潼为本书设计了精美的封面和 logo ,并在我的强迫症的驱使下多次耐心修改; +- 感谢 @squidfunk 提供的排版建议,以及他开发的开源文档主题 [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) 。 -

Krahets

+在写作过程中,我阅读了许多关于数据结构与算法的教材和文章。这些作品为本书提供了优秀的范本,确保了本书内容的准确性与品质。在此感谢所有老师和前辈的杰出贡献! -
大厂高级算法工程师、算法爱好者
+本书倡导手脑并用的学习方式,在这一点上我深受[《动手学深度学习》](https://github.com/d2l-ai/d2l-zh)的启发。在此向各位读者强烈推荐这本优秀的著作。 -

力扣(LeetCode)全网阅读量最高博主

-

分享近百道算法题解,累积回复数千读者的评论问题

-

创作 LeetBook《图解算法数据结构》,已免费售出 22 万本

+**衷心感谢我的父母,正是你们一直以来的支持与鼓励,让我有机会做这件富有趣味的事**。 diff --git a/docs/chapter_preface/contribution.assets/edit_markdown.png b/docs/chapter_preface/contribution.assets/edit_markdown.png deleted file mode 100644 index 25037fce64..0000000000 Binary files a/docs/chapter_preface/contribution.assets/edit_markdown.png and /dev/null differ diff --git a/docs/chapter_preface/contribution.md b/docs/chapter_preface/contribution.md deleted file mode 100644 index 498002c724..0000000000 --- a/docs/chapter_preface/contribution.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -comments: true ---- - -# 一起参与创作 - -!!! success "开源的魅力" - - 纸质书籍的两次印刷的间隔时间往往需要数年,内容更新非常不方便。
但在本开源 HTML 书中,内容更迭的时间被缩短至数日甚至几个小时。 - -由于作者水平有限,书中内容难免疏漏谬误,请您谅解。此外,期待您可以一同参与本书的创作。如果发现笔误、无效链接、内容缺失、文字歧义、解释不清晰、行文结构不合理等问题,烦请您修正内容,以帮助其他读者获取更优质的学习内容。所有 [撰稿人](https://github.com/krahets/hello-algo/graphs/contributors) 将被展示在仓库主页,以感谢您对开源社区的无私奉献。 - -## 修改文字与代码 - -每个页面的右上角都有一个「编辑」按钮,你可以按照以下步骤修改文章: - -1. 点击编辑按钮,如果遇到提示“需要 Fork 此仓库”,请通过; -2. 修改 Markdown 源文件内容; -3. 在页面底部填写更改说明,然后单击“Propose file change”按钮; -4. 页面跳转后,点击“Create pull request”按钮发起拉取请求即可,我会第一时间查看处理并及时更新内容。 - -![edit_markdown](contribution.assets/edit_markdown.png) - -## 修改图片与动画 - -书中的配图无法直接修改,需要通过以下途径提出修改意见: - -1. 新建一个 Issue ,将需要修改的图片复制或截图,粘贴在面板中; -2. 描述图片问题,应如何修改; -3. 提交 Issue 即可,我会第一时间重新画图并替换图片。 - -## 创作新内容 - -如果您想要创作新内容,例如 **重写章节、新增章节、修改代码、翻译代码至其他编程语言** 等,那么需要实施 Pull Request 工作流程: - -1. 登录 GitHub ,并 Fork [本仓库](https://github.com/krahets/hello-algo) 至个人账号; -2. 进入 Fork 仓库网页,使用 `git clone` 克隆该仓库至本地; -3. 在本地进行内容创作(建议通过运行测试来验证代码正确性); -4. 将本地更改 Commit ,并 Push 至远程仓库; -5. 刷新仓库网页,点击“Create pull request”按钮发起拉取请求(Pull Request)即可; - -非常欢迎您和我一同来创作本书! - -## 本地部署 hello-algo - -### Docker - -请确保 Docker 已经安装并启动,并根据如下命令离线部署。 - -稍等片刻,即可使用浏览器打开 `http://localhost:8000` 访问本项目。 - -```bash -git clone https://github.com/krahets/hello-algo.git -cd hello-algo - -docker-compose up -d -``` - -使用如下命令即可删除部署。 - -```bash -docker-compose down -``` - -(TODO:教学视频) diff --git a/docs/chapter_preface/index.assets/learning_route.png b/docs/chapter_preface/index.assets/learning_route.png deleted file mode 100644 index 7423808d62..0000000000 Binary files a/docs/chapter_preface/index.assets/learning_route.png and /dev/null differ diff --git a/docs/chapter_preface/index.assets/mindmap.png b/docs/chapter_preface/index.assets/mindmap.png deleted file mode 100644 index a1c83838f3..0000000000 Binary files a/docs/chapter_preface/index.assets/mindmap.png and /dev/null differ diff --git a/docs/chapter_preface/index.md b/docs/chapter_preface/index.md new file mode 100644 index 0000000000..0b4d0a4fee --- /dev/null +++ b/docs/chapter_preface/index.md @@ -0,0 +1,9 @@ +# 前言 + +![前言](../assets/covers/chapter_preface.jpg) + +!!! abstract + + 算法犹如美妙的交响乐,每一行代码都像韵律般流淌。 + + 愿这本书在你的脑海中轻轻响起,留下独特而深刻的旋律。 diff --git a/docs/chapter_preface/installation.assets/image-20221117201957848.png b/docs/chapter_preface/installation.assets/image-20221117201957848.png deleted file mode 100644 index fdb2c2f62f..0000000000 Binary files a/docs/chapter_preface/installation.assets/image-20221117201957848.png and /dev/null differ diff --git a/docs/chapter_preface/installation.assets/image-20221118013006841.png b/docs/chapter_preface/installation.assets/image-20221118013006841.png deleted file mode 100644 index ae6f9aa299..0000000000 Binary files a/docs/chapter_preface/installation.assets/image-20221118013006841.png and /dev/null differ diff --git a/docs/chapter_preface/installation.assets/image-20221118013751773.png b/docs/chapter_preface/installation.assets/image-20221118013751773.png deleted file mode 100644 index 3bd4cf97a1..0000000000 Binary files a/docs/chapter_preface/installation.assets/image-20221118013751773.png and /dev/null differ diff --git a/docs/chapter_preface/installation.assets/vscode_installation.png b/docs/chapter_preface/installation.assets/vscode_installation.png deleted file mode 100644 index 77bd06d7f7..0000000000 Binary files a/docs/chapter_preface/installation.assets/vscode_installation.png and /dev/null differ diff --git a/docs/chapter_preface/installation.md b/docs/chapter_preface/installation.md deleted file mode 100644 index 828365edfa..0000000000 --- a/docs/chapter_preface/installation.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -comments: true ---- - -# 编程环境安装 - -(TODO 视频教程) - -## 安装 VSCode - -本书推荐使用开源轻量的 VSCode 作为本地 IDE ,下载并安装 [VSCode](https://code.visualstudio.com/) 。 - -## Java 环境 - -1. 下载并安装 [OpenJDK](https://jdk.java.net/18/)(版本需满足 > JDK 9)。 -2. 在 VSCode 的插件市场中搜索 `java` ,安装 Java Extension Pack 。 - - -## C++ 环境 - -1. Windows 系统需要安装 [MinGW](https://www.mingw-w64.org/downloads/) ,MacOS 自带 Clang 无需安装。 -2. 在 VSCode 的插件市场中搜索 `c++` ,安装 C/C++ Extension Pack 。 - -## Python 环境 - -1. 下载并安装 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) 。 -2. 在 VSCode 的插件市场中搜索 `python` ,安装 Python Extension Pack 。 - -## Go 环境 - -1. 下载并安装 [go](https://go.dev/dl/) 。 -2. 在 VSCode 的插件市场中搜索 `go` ,安装 Go 。 -3. 快捷键 `Ctrl + Shift + P` 呼出命令栏,输入 go ,选择 `Go: Install/Update Tools` ,全部勾选并安装即可。 - -## JavaScript 环境 - -1. 下载并安装 [node.js](https://nodejs.org/en/) 。 -2. 在 VSCode 的插件市场中搜索 `javascript` ,安装 JavaScript (ES6) code snippets 。 - -## C# 环境 - -1. 下载并安装 [.Net 6.0](https://dotnet.microsoft.com/en-us/download) ; -2. 在 VSCode 的插件市场中搜索 `c#` ,安装 c# 。 - -## Swift 环境 - -1. 下载并安装 [Swift](https://www.swift.org/download/); -2. 在 VSCode 的插件市场中搜索 `swift`,安装 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang)。 - -## Rust 环境 - -1. 下载并安装 [Rust](https://www.rust-lang.org/tools/install); -2. 在 VSCode 的插件市场中搜索 `rust`,安装 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)。 diff --git a/docs/chapter_preface/suggestions.assets/algorithm_animation.gif b/docs/chapter_preface/suggestions.assets/algorithm_animation.gif deleted file mode 100644 index 5db93eeee1..0000000000 Binary files a/docs/chapter_preface/suggestions.assets/algorithm_animation.gif and /dev/null differ diff --git a/docs/chapter_preface/suggestions.assets/code_md_to_repo.png b/docs/chapter_preface/suggestions.assets/code_md_to_repo.png index 365ac4fe11..360f2eea9e 100644 Binary files a/docs/chapter_preface/suggestions.assets/code_md_to_repo.png and b/docs/chapter_preface/suggestions.assets/code_md_to_repo.png differ diff --git a/docs/chapter_preface/suggestions.assets/comment.gif b/docs/chapter_preface/suggestions.assets/comment.gif deleted file mode 100644 index d64787ac3e..0000000000 Binary files a/docs/chapter_preface/suggestions.assets/comment.gif and /dev/null differ diff --git a/docs/chapter_preface/suggestions.assets/download_code.png b/docs/chapter_preface/suggestions.assets/download_code.png index ae6f9aa299..bf563db2b6 100644 Binary files a/docs/chapter_preface/suggestions.assets/download_code.png and b/docs/chapter_preface/suggestions.assets/download_code.png differ diff --git a/docs/chapter_preface/suggestions.assets/learning_route.png b/docs/chapter_preface/suggestions.assets/learning_route.png index 1f9e39844f..d7986b4336 100644 Binary files a/docs/chapter_preface/suggestions.assets/learning_route.png and b/docs/chapter_preface/suggestions.assets/learning_route.png differ diff --git a/docs/chapter_preface/suggestions.assets/pythontutor_example.png b/docs/chapter_preface/suggestions.assets/pythontutor_example.png new file mode 100644 index 0000000000..a560f5cccb Binary files /dev/null and b/docs/chapter_preface/suggestions.assets/pythontutor_example.png differ diff --git a/docs/chapter_preface/suggestions.assets/running_code.gif b/docs/chapter_preface/suggestions.assets/running_code.gif deleted file mode 100644 index 7377773c1e..0000000000 Binary files a/docs/chapter_preface/suggestions.assets/running_code.gif and /dev/null differ diff --git a/docs/chapter_preface/suggestions.md b/docs/chapter_preface/suggestions.md index b2e18905fa..292038deeb 100644 --- a/docs/chapter_preface/suggestions.md +++ b/docs/chapter_preface/suggestions.md @@ -1,63 +1,252 @@ ---- -comments: true ---- - # 如何使用本书 -## 图文搭配学 +!!! tip + + 为了获得最佳的阅读体验,建议你通读本节内容。 + +## 行文风格约定 + +- 标题后标注 `*` 的是选读章节,内容相对困难。如果你的时间有限,可以先跳过。 +- 专业术语会使用黑体(纸质版和 PDF 版)或添加下划线(网页版),例如数组(array)。建议记住它们,以便阅读文献。 +- 重点内容和总结性语句会 **加粗**,这类文字值得特别关注。 +- 有特指含义的词句会使用“引号”标注,以避免歧义。 +- 当涉及编程语言之间不一致的名词时,本书均以 Python 为准,例如使用 `None` 来表示“空”。 +- 本书部分放弃了编程语言的注释规范,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。 + +=== "Python" + + ```python title="" + """标题注释,用于标注函数、类、测试样例等""" + + # 内容注释,用于详解代码 + + """ + 多行 + 注释 + """ + ``` + +=== "C++" + + ```cpp title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Java" + + ```java title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "C#" + + ```csharp title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Go" + + ```go title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Swift" + + ```swift title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "JS" + + ```javascript title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "TS" + + ```typescript title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Dart" + + ```dart title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Rust" + + ```rust title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "C" + + ```c title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Ruby" + + ```ruby title="" + ### 标题注释,用于标注函数、类、测试样例等 ### + + # 内容注释,用于详解代码 + + # 多行 + # 注释 + ``` + +=== "Zig" + + ```zig title="" + // 标题注释,用于标注函数、类、测试样例等 + + // 内容注释,用于详解代码 + + // 多行 + // 注释 + ``` -视频和图片相比于文字的信息密度和结构化程度更高,更容易让人理解。在本书中,重点和难点知识会主要以动画、图解的形式呈现,而文字的作用则是作为动画和图的解释与补充。 +## 在动画图解中高效学习 -在阅读本书的过程中,若发现某段内容提供了动画或图解,**建议你以图为主线**,将文字内容(一般在图的上方)对齐到图中内容,综合来理解。 +相较于文字,视频和图片具有更高的信息密度和结构化程度,更易于理解。在本书中,**重点和难点知识将主要通过动画以图解形式展示**,而文字则作为解释与补充。 + +如果你在阅读本书时,发现某段内容提供了如下图所示的动画图解,**请以图为主、以文字为辅**,综合两者来理解内容。 -![algorithm_animation](suggestions.assets/algorithm_animation.gif) +![动画图解示例](../index.assets/animation.gif) -## 代码实践学 +## 在代码实践中加深理解 -!!! tip "前置工作" +本书的配套代码托管在 [GitHub 仓库](https://github.com/krahets/hello-algo)。如下图所示,**源代码附有测试样例,可一键运行**。 - 如果没有本地编程环境,可以参照下节 [编程环境安装](https://www.hello-algo.com/chapter_preface/installation/) 。 +如果时间允许,**建议你参照代码自行敲一遍**。如果学习时间有限,请至少通读并运行所有代码。 -### 下载代码仓 +与阅读代码相比,编写代码的过程往往能带来更多收获。**动手学,才是真的学**。 -如果已经安装 [Git](https://git-scm.com/downloads) ,可以通过命令行来克隆代码仓。 +![运行代码示例](../index.assets/running_code.gif) -```shell -git clone https://github.com/krahets/hello-algo.git -``` +运行代码的前置工作主要分为三步。 -当然,你也可以点击“Download ZIP”直接下载代码压缩包,解压即可。 +**第一步:安装本地编程环境**。请参照附录所示的[教程](https://www.hello-algo.com/chapter_appendix/installation/)进行安装,如果已安装,则可跳过此步骤。 -![download_code](suggestions.assets/download_code.png) +**第二步:克隆或下载代码仓库**。前往 [GitHub 仓库](https://github.com/krahets/hello-algo)。如果已经安装 [Git](https://git-scm.com/downloads) ,可以通过以下命令克隆本仓库: -### 运行源代码 +```shell +git clone https://github.com/krahets/hello-algo.git +``` -本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件**。 +当然,你也可以在下图所示的位置,点击“Download ZIP”按钮直接下载代码压缩包,然后在本地解压即可。 -![code_md_to_repo](suggestions.assets/code_md_to_repo.png) +![克隆仓库与下载代码](suggestions.assets/download_code.png) -这些源文件中包含详细注释,配有测试样例,可以直接运行,帮助你省去不必要的调试时间,可以将精力集中在学习内容上。 +**第三步:运行源代码**。如下图所示,对于顶部标有文件名称的代码块,我们可以在仓库的 `codes` 文件夹内找到对应的源代码文件。源代码文件可一键运行,将帮助你节省不必要的调试时间,让你能够专注于学习内容。 -![running_code](suggestions.assets/running_code.gif) +![代码块与对应的源代码文件](suggestions.assets/code_md_to_repo.png) -!!! tip "代码学习建议" +除了本地运行代码,**网页版还支持 Python 代码的可视化运行**(基于 [pythontutor](https://pythontutor.com/) 实现)。如下图所示,你可以点击代码块下方的“可视化运行”来展开视图,观察算法代码的执行过程;也可以点击“全屏观看”,以获得更好的阅览体验。 - 若学习时间紧张,**请至少将所有代码通读并运行一遍**。若时间允许,**强烈建议对照着代码自己敲一遍**,逐渐锻炼肌肉记忆。相比于读代码,写代码的过程往往能带来新的收获。 +![Python 代码的可视化运行](suggestions.assets/pythontutor_example.png) -## 提问讨论学 +## 在提问讨论中共同成长 -阅读本书时,请不要“惯着”那些弄不明白的知识点。如果有任何疑惑,**可以在评论区留下你的问题**,小伙伴们和我都会给予解答(您一般 3 天内会得到回复)。 +在阅读本书时,请不要轻易跳过那些没学明白的知识点。**欢迎在评论区提出你的问题**,我和小伙伴们将竭诚为你解答,一般情况下可在两天内回复。 -同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,也希望你可以慷慨地解答小伙伴们的问题、分享自己的见解,大家一起加油与进步! +如下图所示,网页版每个章节的底部都配有评论区。希望你能多关注评论区的内容。一方面,你可以了解大家遇到的问题,从而查漏补缺,激发更深入的思考。另一方面,期待你能慷慨地回答其他小伙伴的问题,分享你的见解,帮助他人进步。 -![comment](suggestions.assets/comment.gif) +![评论区示例](../index.assets/comment.gif) -## 算法学习“三步走” +## 算法学习路线 -**第一阶段,算法入门,也正是本书的定位**。熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。 +从总体上看,我们可以将学习数据结构与算法的过程划分为三个阶段。 -**第二阶段,刷算法题**。可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。 +1. **阶段一:算法入门**。我们需要熟悉各种数据结构的特点和用法,学习不同算法的原理、流程、用途和效率等方面的内容。 +2. **阶段二:刷算法题**。建议从热门题目开刷,先积累至少 100 道题目,熟悉主流的算法问题。初次刷题时,“知识遗忘”可能是一个挑战,但请放心,这是很正常的。我们可以按照“艾宾浩斯遗忘曲线”来复习题目,通常在进行 3~5 轮的重复后,就能将其牢记在心。推荐的题单和刷题计划请见此 [GitHub 仓库](https://github.com/krahets/LeetCode-Book)。 +3. **阶段三:搭建知识体系**。在学习方面,我们可以阅读算法专栏文章、解题框架和算法教材,以不断丰富知识体系。在刷题方面,可以尝试采用进阶刷题策略,如按专题分类、一题多解、一解多题等,相关的刷题心得可以在各个社区找到。 -**第三阶段,搭建知识体系**。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。 +如下图所示,本书内容主要涵盖“阶段一”,旨在帮助你更高效地展开阶段二和阶段三的学习。 -![learning_route](suggestions.assets/learning_route.png) +![算法学习路线](suggestions.assets/learning_route.png) diff --git a/docs/chapter_preface/summary.md b/docs/chapter_preface/summary.md new file mode 100644 index 0000000000..0ee5a588d5 --- /dev/null +++ b/docs/chapter_preface/summary.md @@ -0,0 +1,8 @@ +# 小结 + +- 本书的主要受众是算法初学者。如果你已有一定基础,本书能帮助你系统回顾算法知识,书中源代码也可作为“刷题工具库”使用。 +- 书中内容主要包括复杂度分析、数据结构和算法三部分,涵盖了该领域的大部分主题。 +- 对于算法新手,在初学阶段阅读一本入门书至关重要,可以少走许多弯路。 +- 书中的动画图解通常用于介绍重点和难点知识。阅读本书时,应给予这些内容更多关注。 +- 实践乃学习编程之最佳途径。强烈建议运行源代码并亲自敲代码。 +- 本书网页版的每个章节都设有评论区,欢迎随时分享你的疑惑与见解。 diff --git a/docs/chapter_reference/index.md b/docs/chapter_reference/index.md index fcc5da56f1..80e29189b9 100644 --- a/docs/chapter_reference/index.md +++ b/docs/chapter_reference/index.md @@ -1,17 +1,25 @@ +--- +icon: material/bookshelf +--- + # 参考文献 [1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). -[3] 程杰. 大话数据结构. +[3] Robert Sedgewick, et al. Algorithms (4th Edition). + +[4] 严蔚敏. 数据结构(C 语言版). + +[5] 邓俊辉. 数据结构(C++ 语言版,第三版). -[4] 王争. 数据结构与算法之美. +[6] 马克 艾伦 维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版). -[5] 严蔚敏. 数据结构( C 语言版). +[7] 程杰. 大话数据结构. -[6] 邓俊辉. 数据结构( C++ 语言版,第三版). +[8] 王争. 数据结构与算法之美. -[7] 马克·艾伦·维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版). +[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). -[8] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). +[10] Aston Zhang, et al. Dive into Deep Learning. diff --git a/docs/chapter_searching/binary_search.assets/binary_search_example.png b/docs/chapter_searching/binary_search.assets/binary_search_example.png new file mode 100644 index 0000000000..9051addbd4 Binary files /dev/null and b/docs/chapter_searching/binary_search.assets/binary_search_example.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_ranges.png b/docs/chapter_searching/binary_search.assets/binary_search_ranges.png new file mode 100644 index 0000000000..93bc6a3249 Binary files /dev/null and b/docs/chapter_searching/binary_search.assets/binary_search_ranges.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_step1.png b/docs/chapter_searching/binary_search.assets/binary_search_step1.png index 7748b34377..2b3d7af3c2 100644 Binary files a/docs/chapter_searching/binary_search.assets/binary_search_step1.png and b/docs/chapter_searching/binary_search.assets/binary_search_step1.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_step2.png b/docs/chapter_searching/binary_search.assets/binary_search_step2.png index fba2f959a8..6063730061 100644 Binary files a/docs/chapter_searching/binary_search.assets/binary_search_step2.png and b/docs/chapter_searching/binary_search.assets/binary_search_step2.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_step3.png b/docs/chapter_searching/binary_search.assets/binary_search_step3.png index 663ffcd26e..0184c3a23d 100644 Binary files a/docs/chapter_searching/binary_search.assets/binary_search_step3.png and b/docs/chapter_searching/binary_search.assets/binary_search_step3.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_step4.png b/docs/chapter_searching/binary_search.assets/binary_search_step4.png index 728b4cb1aa..54e8e12c38 100644 Binary files a/docs/chapter_searching/binary_search.assets/binary_search_step4.png and b/docs/chapter_searching/binary_search.assets/binary_search_step4.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_step5.png b/docs/chapter_searching/binary_search.assets/binary_search_step5.png index 1710f2433c..9f57fb75b6 100644 Binary files a/docs/chapter_searching/binary_search.assets/binary_search_step5.png and b/docs/chapter_searching/binary_search.assets/binary_search_step5.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_step6.png b/docs/chapter_searching/binary_search.assets/binary_search_step6.png index b0f6ab1b86..8a9e4e436d 100644 Binary files a/docs/chapter_searching/binary_search.assets/binary_search_step6.png and b/docs/chapter_searching/binary_search.assets/binary_search_step6.png differ diff --git a/docs/chapter_searching/binary_search.assets/binary_search_step7.png b/docs/chapter_searching/binary_search.assets/binary_search_step7.png index 20bbcec0f8..293a878372 100644 Binary files a/docs/chapter_searching/binary_search.assets/binary_search_step7.png and b/docs/chapter_searching/binary_search.assets/binary_search_step7.png differ diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md old mode 100644 new mode 100755 index da60b3b434..c220b0ec43 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -1,498 +1,83 @@ ---- -comments: true ---- - # 二分查找 -「二分查找 Binary Search」利用数据的有序性,通过每轮缩小一半搜索区间来查找目标元素。 - -使用二分查找有两个前置条件: +二分查找(binary search)是一种基于分治策略的高效搜索算法。它利用数据的有序性,每轮缩小一半搜索范围,直至找到目标元素或搜索区间为空为止。 -- **要求输入数据是有序的**,这样才能通过判断大小关系来排除一半的搜索区间; -- **二分查找仅适用于数组**,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。 +!!! question -## 算法实现 + 给定一个长度为 $n$ 的数组 `nums` ,元素按从小到大的顺序排列且不重复。请查找并返回元素 `target` 在该数组中的索引。若数组不包含该元素,则返回 $-1$ 。示例如下图所示。 -给定一个长度为 $n$ 的排序数组 `nums` ,元素从小到大排列。数组的索引取值范围为 +![二分查找示例数据](binary_search.assets/binary_search_example.png) -$$ -0, 1, 2, \cdots, n-1 -$$ +如下图所示,我们先初始化指针 $i = 0$ 和 $j = n - 1$ ,分别指向数组首元素和尾元素,代表搜索区间 $[0, n - 1]$ 。请注意,中括号表示闭区间,其包含边界值本身。 -使用「区间」来表示这个取值范围的方法主要有两种: +接下来,循环执行以下两步。 -1. **双闭区间 $[0, n-1]$** ,即两个边界都包含自身;此方法下,区间 $[0, 0]$ 仍包含一个元素; -2. **左闭右开 $[0, n)$** ,即左边界包含自身、右边界不包含自身;此方法下,区间 $[0, 0)$ 为空; +1. 计算中点索引 $m = \lfloor {(i + j) / 2} \rfloor$ ,其中 $\lfloor \: \rfloor$ 表示向下取整操作。 +2. 判断 `nums[m]` 和 `target` 的大小关系,分为以下三种情况。 + 1. 当 `nums[m] < target` 时,说明 `target` 在区间 $[m + 1, j]$ 中,因此执行 $i = m + 1$ 。 + 2. 当 `nums[m] > target` 时,说明 `target` 在区间 $[i, m - 1]$ 中,因此执行 $j = m - 1$ 。 + 3. 当 `nums[m] = target` 时,说明找到 `target` ,因此返回索引 $m$ 。 -### “双闭区间”实现 +若数组不包含目标元素,搜索区间最终会缩小为空。此时返回 $-1$ 。 -首先,我们先采用“双闭区间”的表示,在数组 `nums` 中查找目标元素 `target` 的对应索引。 +=== "<1>" + ![二分查找流程](binary_search.assets/binary_search_step1.png) -=== "Step 1" - ![binary_search_step1](binary_search.assets/binary_search_step1.png) - -=== "Step 2" +=== "<2>" ![binary_search_step2](binary_search.assets/binary_search_step2.png) -=== "Step 3" +=== "<3>" ![binary_search_step3](binary_search.assets/binary_search_step3.png) -=== "Step 4" +=== "<4>" ![binary_search_step4](binary_search.assets/binary_search_step4.png) -=== "Step 5" +=== "<5>" ![binary_search_step5](binary_search.assets/binary_search_step5.png) -=== "Step 6" +=== "<6>" ![binary_search_step6](binary_search.assets/binary_search_step6.png) -=== "Step 7" +=== "<7>" ![binary_search_step7](binary_search.assets/binary_search_step7.png) -二分查找“双闭区间”表示下的代码如下所示。 - -=== "Java" - - ```java title="binary_search.java" - /* 二分查找(双闭区间) */ - int binarySearch(int[] nums, int target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - int m = (i + j) / 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; - } - ``` - -=== "C++" - - ```cpp title="binary_search.cpp" - /* 二分查找(双闭区间) */ - int binarySearch(vector& nums, int target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.size() - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - int m = (i + j) / 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; - } - ``` - -=== "Python" - - ```python title="binary_search.py" - """ 二分查找(双闭区间) """ - def binary_search(nums, target): - # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - i, j = 0, len(nums) - 1 - while i <= j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: # 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1 - elif nums[m] > target: # 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1 - else: - return m # 找到目标元素,返回其索引 - return -1 # 未找到目标元素,返回 -1 - ``` - -=== "Go" - - ```go title="binary_search.go" - /* 二分查找(双闭区间) */ - func binarySearch(nums []int, target int) int { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - i, j := 0, len(nums)-1 - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - for i <= j { - m := (i + j) / 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 - } - ``` - -=== "JavaScript" - - ```js title="binary_search.js" - /* 二分查找(双闭区间) */ - function binarySearch(nums, target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - let i = 0, j = nums.length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整 - 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; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search.ts" - /* 二分查找(双闭区间) */ - const binarySearch = function (nums: number[], target: number): number { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - let i = 0, j = nums.length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - const m = Math.floor(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; - } - } - return -1; // 未找到目标元素,返回 -1 - } - ``` - -=== "C" - - ```c title="binary_search.c" - - ``` - -=== "C#" - - ```csharp title="binary_search.cs" - /* 二分查找(双闭区间) */ - int binarySearch(int[] nums, int target) - { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.Length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) - { - int m = (i + j) / 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; - } - ``` - -=== "Swift" - - ```swift title="binary_search.swift" - - ``` - -### “左闭右开”实现 - -当然,我们也可以使用“左闭右开”的表示方法,写出相同功能的二分查找代码。 - -=== "Java" - - ```java title="binary_search.java" - /* 二分查找(左闭右开) */ - int binarySearch1(int[] nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = (i + j) / 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; - } - ``` - -=== "C++" - - ```cpp title="binary_search.cpp" - /* 二分查找(左闭右开) */ - int binarySearch1(vector& nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.size(); - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = (i + j) / 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; - } - ``` - -=== "Python" - - ```python title="binary_search.py" - """ 二分查找(左闭右开) """ - def binary_search1(nums, target): - # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - i, j = 0, len(nums) - # 循环,当搜索区间为空时跳出(当 i = j 时为空) - while i < j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: # 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1 - elif nums[m] > target: # 此情况说明 target 在区间 [i, m] 中 - j = m - else: # 找到目标元素,返回其索引 - return m - return -1 # 未找到目标元素,返回 -1 - ``` - -=== "Go" - - ```go title="binary_search.go" - /* 二分查找(左闭右开) */ - func binarySearch1(nums []int, target int) int { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - i, j := 0, len(nums) - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - for i < j { - m := (i + j) / 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 - } - ``` - -=== "JavaScript" - - ```js title="binary_search.js" - /* 二分查找(左闭右开) */ - function binarySearch1(nums, target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - let i = 0, j = nums.length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整 - 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; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search.ts" - /* 二分查找(左闭右开) */ - const binarySearch1 = function (nums: number[], target: number): number { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - let i = 0, j = nums.length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - const m = Math.floor(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; - } - } - return -1; // 未找到目标元素,返回 -1 - } - ``` - -=== "C" - - ```c title="binary_search.c" - - ``` - -=== "C#" - - ```csharp title="binary_search.cs" - /* 二分查找(左闭右开) */ - int binarySearch1(int[] nums, int target) - { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.Length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) - { - int m = (i + j) / 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; - } - ``` - -=== "Swift" - - ```swift title="binary_search.swift" - - ``` - -### 两种表示对比 - -对比下来,两种表示的代码写法有以下不同点: - -
- -| 表示方法 | 初始化指针 | 缩小区间 | 循环终止条件 | -| ------------------- | ------------------- | ------------------------- | ------------ | -| 双闭区间 $[0, n-1]$ | $i = 0$ , $j = n-1$ | $i = m + 1$ , $j = m - 1$ | $i > j$ | -| 左闭右开 $[0, n)$ | $i = 0$ , $j = n$ | $i = m + 1$ , $j = m$ | $i = j$ | - -
- -观察发现,在“双闭区间”表示中,由于对左右两边界的定义是相同的,因此缩小区间的 $i$ , $j$ 处理方法也是对称的,这样更不容易出错。综上所述,**建议你采用“双闭区间”的写法。** - -### 大数越界处理 - -当数组长度很大时,加法 $i + j$ 的结果有可能会超出 `int` 类型的取值范围。在此情况下,我们需要换一种计算中点的写法。 - -=== "Java" - - ```java title="" - // (i + j) 有可能超出 int 的取值范围 - int m = (i + j) / 2; - // 更换为此写法则不会越界 - int m = i + (j - i) / 2; - ``` - -=== "C++" - - ```cpp title="" - // (i + j) 有可能超出 int 的取值范围 - int m = (i + j) / 2; - // 更换为此写法则不会越界 - int m = i + (j - i) / 2; - ``` - -=== "Python" - - ```py title="" - # Python 中的数字理论上可以无限大(取决于内存大小) - # 因此无需考虑大数越界问题 - ``` - -=== "Go" - - ```go title="" - // (i + j) 有可能超出 int 的取值范围 - m := (i + j) / 2 - // 更换为此写法则不会越界 - m := i + (j - i) / 2 - ``` - -=== "JavaScript" - - ```js title="" - // (i + j) 有可能超出 int 的取值范围 - let m = parseInt((i + j) / 2); - // 更换为此写法则不会越界 - let m = parseInt(i + (j - i) / 2); - ``` - -=== "TypeScript" - - ```typescript title="" - // (i + j) 有可能超出 Number 的取值范围 - let m = Math.floor((i + j) / 2); - // 更换为此写法则不会越界 - let m = Math.floor(i + (j - i) / 2); - ``` +值得注意的是,由于 $i$ 和 $j$ 都是 `int` 类型,**因此 $i + j$ 可能会超出 `int` 类型的取值范围**。为了避免大数越界,我们通常采用公式 $m = \lfloor {i + (j - i) / 2} \rfloor$ 来计算中点。 -=== "C" +代码如下所示: - ```c title="" +```src +[file]{binary_search}-[class]{}-[func]{binary_search} +``` - ``` +**时间复杂度为 $O(\log n)$** :在二分循环中,区间每轮缩小一半,因此循环次数为 $\log_2 n$ 。 -=== "C#" +**空间复杂度为 $O(1)$** :指针 $i$ 和 $j$ 使用常数大小空间。 - ```csharp title="" - // (i + j) 有可能超出 int 的取值范围 - int m = (i + j) / 2; - // 更换为此写法则不会越界 - int m = i + (j - i) / 2; - ``` +## 区间表示方法 -=== "Swift" +除了上述双闭区间外,常见的区间表示还有“左闭右开”区间,定义为 $[0, n)$ ,即左边界包含自身,右边界不包含自身。在该表示下,区间 $[i, j)$ 在 $i = j$ 时为空。 - ```swift title="" +我们可以基于该表示实现具有相同功能的二分查找算法: - ``` +```src +[file]{binary_search}-[class]{}-[func]{binary_search_lcro} +``` -## 复杂度分析 +如下图所示,在两种区间表示下,二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。 -**时间复杂度 $O(\log n)$** :其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 +由于“双闭区间”表示中的左右边界都被定义为闭区间,因此通过指针 $i$ 和指针 $j$ 缩小区间的操作也是对称的。这样更不容易出错,**因此一般建议采用“双闭区间”的写法**。 -**空间复杂度 $O(1)$** :指针 `i` , `j` 使用常数大小空间。 +![两种区间定义](binary_search.assets/binary_search_ranges.png) -## 优点与缺点 +## 优点与局限性 -二分查找效率很高,体现在: +二分查找在时间和空间方面都有较好的性能。 -- **二分查找时间复杂度低**。对数阶在数据量很大时具有巨大优势,例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需要 $\log_2 2^{20} = 20$ 轮循环。 -- **二分查找不需要额外空间**。相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。 +- 二分查找的时间效率高。在大数据量下,对数阶的时间复杂度具有显著优势。例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需 $\log_2 2^{20} = 20$ 轮循环。 +- 二分查找无须额外空间。相较于需要借助额外空间的搜索算法(例如哈希查找),二分查找更加节省空间。 -但并不意味着所有情况下都应使用二分查找,这是因为: +然而,二分查找并非适用于所有情况,主要有以下原因。 -- **二分查找仅适用于有序数据**。如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 $O(n \log n)$ ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。 -- **二分查找仅适用于数组**。由于在二分查找中,访问索引是 ”非连续“ 的,因此链表或者基于链表实现的数据结构都无法使用。 -- **在小数据量下,线性查找的性能更好**。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 $n$ 较小时,线性查找反而比二分查找更快。 +- 二分查找仅适用于有序数据。若输入数据无序,为了使用二分查找而专门进行排序,得不偿失。因为排序算法的时间复杂度通常为 $O(n \log n)$ ,比线性查找和二分查找都更高。对于频繁插入元素的场景,为保持数组有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。 +- 二分查找仅适用于数组。二分查找需要跳跃式(非连续地)访问元素,而在链表中执行跳跃式访问的效率较低,因此不适合应用在链表或基于链表实现的数据结构。 +- 小数据量下,线性查找性能更佳。在线性查找中,每轮只需 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,当数据量 $n$ 较小时,线性查找反而比二分查找更快。 diff --git a/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png b/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png new file mode 100644 index 0000000000..b2799062a7 Binary files /dev/null and b/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png differ diff --git a/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png b/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png new file mode 100644 index 0000000000..6f10b838e6 Binary files /dev/null and b/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png differ diff --git a/docs/chapter_searching/binary_search_edge.md b/docs/chapter_searching/binary_search_edge.md new file mode 100644 index 0000000000..bccf0f50e3 --- /dev/null +++ b/docs/chapter_searching/binary_search_edge.md @@ -0,0 +1,56 @@ +# 二分查找边界 + +## 查找左边界 + +!!! question + + 给定一个长度为 $n$ 的有序数组 `nums` ,其中可能包含重复元素。请返回数组中最左一个元素 `target` 的索引。若数组中不包含该元素,则返回 $-1$ 。 + +回忆二分查找插入点的方法,搜索完成后 $i$ 指向最左一个 `target` ,**因此查找插入点本质上是在查找最左一个 `target` 的索引**。 + +考虑通过查找插入点的函数实现查找左边界。请注意,数组中可能不包含 `target` ,这种情况可能导致以下两种结果。 + +- 插入点的索引 $i$ 越界。 +- 元素 `nums[i]` 与 `target` 不相等。 + +当遇到以上两种情况时,直接返回 $-1$ 即可。代码如下所示: + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} +``` + +## 查找右边界 + +那么如何查找最右一个 `target` 呢?最直接的方式是修改代码,替换在 `nums[m] == target` 情况下的指针收缩操作。代码在此省略,有兴趣的读者可以自行实现。 + +下面我们介绍两种更加取巧的方法。 + +### 复用查找左边界 + +实际上,我们可以利用查找最左元素的函数来查找最右元素,具体方法为:**将查找最右一个 `target` 转化为查找最左一个 `target + 1`**。 + +如下图所示,查找完成后,指针 $i$ 指向最左一个 `target + 1`(如果存在),而 $j$ 指向最右一个 `target` ,**因此返回 $j$ 即可**。 + +![将查找右边界转化为查找左边界](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) + +请注意,返回的插入点是 $i$ ,因此需要将其减 $1$ ,从而获得 $j$ : + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} +``` + +### 转化为查找元素 + +我们知道,当数组不包含 `target` 时,最终 $i$ 和 $j$ 会分别指向首个大于、小于 `target` 的元素。 + +因此,如下图所示,我们可以构造一个数组中不存在的元素,用于查找左右边界。 + +- 查找最左一个 `target` :可以转化为查找 `target - 0.5` ,并返回指针 $i$ 。 +- 查找最右一个 `target` :可以转化为查找 `target + 0.5` ,并返回指针 $j$ 。 + +![将查找边界转化为查找元素](binary_search_edge.assets/binary_search_edge_by_element.png) + +代码在此省略,以下两点值得注意。 + +- 给定数组不包含小数,这意味着我们无须关心如何处理相等的情况。 +- 因为该方法引入了小数,所以需要将函数中的变量 `target` 改为浮点数类型(Python 无须改动)。 diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png new file mode 100644 index 0000000000..3803db33d5 Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png new file mode 100644 index 0000000000..b47ffd79ab Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png new file mode 100644 index 0000000000..7717114a25 Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png new file mode 100644 index 0000000000..13b9fec08a Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png new file mode 100644 index 0000000000..4f26ada249 Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png new file mode 100644 index 0000000000..262aebc6d4 Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png new file mode 100644 index 0000000000..2c739e9f23 Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png new file mode 100644 index 0000000000..56433a0134 Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png new file mode 100644 index 0000000000..127eb70fcd Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png differ diff --git a/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png new file mode 100644 index 0000000000..d322ffc3ca Binary files /dev/null and b/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png differ diff --git a/docs/chapter_searching/binary_search_insertion.md b/docs/chapter_searching/binary_search_insertion.md new file mode 100644 index 0000000000..1f2040e677 --- /dev/null +++ b/docs/chapter_searching/binary_search_insertion.md @@ -0,0 +1,91 @@ +# 二分查找插入点 + +二分查找不仅可用于搜索目标元素,还可用于解决许多变种问题,比如搜索目标元素的插入位置。 + +## 无重复元素的情况 + +!!! question + + 给定一个长度为 $n$ 的有序数组 `nums` 和一个元素 `target` ,数组不存在重复元素。现将 `target` 插入数组 `nums` 中,并保持其有序性。若数组中已存在元素 `target` ,则插入到其左方。请返回插入后 `target` 在数组中的索引。示例如下图所示。 + +![二分查找插入点示例数据](binary_search_insertion.assets/binary_search_insertion_example.png) + +如果想复用上一节的二分查找代码,则需要回答以下两个问题。 + +**问题一**:当数组中包含 `target` 时,插入点的索引是否是该元素的索引? + +题目要求将 `target` 插入到相等元素的左边,这意味着新插入的 `target` 替换了原来 `target` 的位置。也就是说,**当数组包含 `target` 时,插入点的索引就是该 `target` 的索引**。 + +**问题二**:当数组中不存在 `target` 时,插入点是哪个元素的索引? + +进一步思考二分查找过程:当 `nums[m] < target` 时 $i$ 移动,这意味着指针 $i$ 在向大于等于 `target` 的元素靠近。同理,指针 $j$ 始终在向小于等于 `target` 的元素靠近。 + +因此二分结束时一定有:$i$ 指向首个大于 `target` 的元素,$j$ 指向首个小于 `target` 的元素。**易得当数组不包含 `target` 时,插入索引为 $i$** 。代码如下所示: + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} +``` + +## 存在重复元素的情况 + +!!! question + + 在上一题的基础上,规定数组可能包含重复元素,其余不变。 + +假设数组中存在多个 `target` ,则普通二分查找只能返回其中一个 `target` 的索引,**而无法确定该元素的左边和右边还有多少 `target`**。 + +题目要求将目标元素插入到最左边,**所以我们需要查找数组中最左一个 `target` 的索引**。初步考虑通过下图所示的步骤实现。 + +1. 执行二分查找,得到任意一个 `target` 的索引,记为 $k$ 。 +2. 从索引 $k$ 开始,向左进行线性遍历,当找到最左边的 `target` 时返回。 + +![线性查找重复元素的插入点](binary_search_insertion.assets/binary_search_insertion_naive.png) + +此方法虽然可用,但其包含线性查找,因此时间复杂度为 $O(n)$ 。当数组中存在很多重复的 `target` 时,该方法效率很低。 + +现考虑拓展二分查找代码。如下图所示,整体流程保持不变,每轮先计算中点索引 $m$ ,再判断 `target` 和 `nums[m]` 的大小关系,分为以下几种情况。 + +- 当 `nums[m] < target` 或 `nums[m] > target` 时,说明还没有找到 `target` ,因此采用普通二分查找的缩小区间操作,**从而使指针 $i$ 和 $j$ 向 `target` 靠近**。 +- 当 `nums[m] == target` 时,说明小于 `target` 的元素在区间 $[i, m - 1]$ 中,因此采用 $j = m - 1$ 来缩小区间,**从而使指针 $j$ 向小于 `target` 的元素靠近**。 + +循环完成后,$i$ 指向最左边的 `target` ,$j$ 指向首个小于 `target` 的元素,**因此索引 $i$ 就是插入点**。 + +=== "<1>" + ![二分查找重复元素的插入点的步骤](binary_search_insertion.assets/binary_search_insertion_step1.png) + +=== "<2>" + ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) + +=== "<3>" + ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) + +=== "<4>" + ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) + +=== "<5>" + ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) + +=== "<6>" + ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) + +=== "<7>" + ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) + +=== "<8>" + ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) + +观察以下代码,判断分支 `nums[m] > target` 和 `nums[m] == target` 的操作相同,因此两者可以合并。 + +即便如此,我们仍然可以将判断条件保持展开,因为其逻辑更加清晰、可读性更好。 + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} +``` + +!!! tip + + 本节的代码都是“双闭区间”写法。有兴趣的读者可以自行实现“左闭右开”写法。 + +总的来看,二分查找无非就是给指针 $i$ 和 $j$ 分别设定搜索目标,目标可能是一个具体的元素(例如 `target` ),也可能是一个元素范围(例如小于 `target` 的元素)。 + +在不断的循环二分中,指针 $i$ 和 $j$ 都逐渐逼近预先设定的目标。最终,它们或是成功找到答案,或是越过边界后停止。 diff --git a/docs/chapter_searching/hashing_search.assets/hash_search_index.png b/docs/chapter_searching/hashing_search.assets/hash_search_index.png deleted file mode 100644 index ab9f0b6b42..0000000000 Binary files a/docs/chapter_searching/hashing_search.assets/hash_search_index.png and /dev/null differ diff --git a/docs/chapter_searching/hashing_search.assets/hash_search_listnode.png b/docs/chapter_searching/hashing_search.assets/hash_search_listnode.png deleted file mode 100644 index dd1ed8fab8..0000000000 Binary files a/docs/chapter_searching/hashing_search.assets/hash_search_listnode.png and /dev/null differ diff --git a/docs/chapter_searching/hashing_search.md b/docs/chapter_searching/hashing_search.md deleted file mode 100644 index 9f3d74c0cf..0000000000 --- a/docs/chapter_searching/hashing_search.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -comments: true ---- - -# 哈希查找 - -!!! question - - 在数据量很大时,「线性查找」太慢;而「二分查找」要求数据必须是有序的,并且只能在数组中应用。那么是否有方法可以同时避免上述缺点呢?答案是肯定的,此方法被称为「哈希查找」。 - -「哈希查找 Hash Searching」借助一个哈希表来存储需要的「键值对 Key Value Pair」,我们可以在 $O(1)$ 时间下实现“键 $\rightarrow$ 值”映射查找,体现着“以空间换时间”的算法思想。 - -## 算法实现 - -如果我们想要给定数组中的一个目标元素 `target` ,获取该元素的索引,那么可以借助一个哈希表实现查找。 - -![hash_search_index](hashing_search.assets/hash_search_index.png) - -=== "Java" - - ```java title="hashing_search.java" - /* 哈希查找(数组) */ - int hashingSearch(Map map, int target) { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.getOrDefault(target, -1); - } - ``` - -=== "C++" - - ```cpp title="hashing_search.cpp" - /* 哈希查找(数组) */ - int hashingSearch(unordered_map map, int target) { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - if (map.find(target) == map.end()) - return -1; - return map[target]; - } - ``` - -=== "Python" - - ```python title="hashing_search.py" - """ 哈希查找(数组) """ - def hashing_search(mapp, target): - # 哈希表的 key: 目标元素,value: 索引 - # 若哈希表中无此 key ,返回 -1 - return mapp.get(target, -1) - ``` - -=== "Go" - - ```go title="hashing_search.go" - /* 哈希查找(数组) */ - func hashingSearch(m map[int]int, target int) int { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - if index, ok := m[target]; ok { - return index - } else { - return -1 - } - } - ``` - -=== "JavaScript" - - ```js title="hashing_search.js" - /* 哈希查找(数组) */ - function hashingSearch(map, target) { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.has(target) ? map.get(target) : -1; - } - ``` - -=== "TypeScript" - - ```typescript title="hashing_search.ts" - /* 哈希查找(数组) */ - function hashingSearch(map: Map, target: number): number { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.has(target) ? map.get(target) as number : -1; - } - ``` - -=== "C" - - ```c title="hashing_search.c" - - ``` - -=== "C#" - - ```csharp title="hashing_search.cs" - /* 哈希查找(数组) */ - int hashingSearch(Dictionary map, int target) - { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.GetValueOrDefault(target, -1); - } - ``` - -=== "Swift" - - ```swift title="hashing_search.swift" - - ``` - -再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。 - -![hash_search_listnode](hashing_search.assets/hash_search_listnode.png) - -=== "Java" - - ```java title="hashing_search.java" - /* 哈希查找(链表) */ - ListNode hashingSearch1(Map map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.getOrDefault(target, null); - } - ``` - -=== "C++" - - ```cpp title="hashing_search.cpp" - /* 哈希查找(链表) */ - ListNode* hashingSearch1(unordered_map map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 nullptr - if (map.find(target) == map.end()) - return nullptr; - return map[target]; - } - ``` - -=== "Python" - - ```python title="hashing_search.py" - """ 哈希查找(链表) """ - def hashing_search1(mapp, target): - # 哈希表的 key: 目标元素,value: 结点对象 - # 若哈希表中无此 key ,返回 -1 - return mapp.get(target, -1) - ``` - -=== "Go" - - ```go title="hashing_search.go" - /* 哈希查找(链表) */ - func hashingSearch1(m map[int]*ListNode, target int) *ListNode { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 nil - if node, ok := m[target]; ok { - return node - } else { - return nil - } - } - ``` - -=== "JavaScript" - - ```js title="hashing_search.js" - /* 哈希查找(链表) */ - function hashingSearch1(map, target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.has(target) ? map.get(target) : null; - } - ``` - -=== "TypeScript" - - ```typescript title="hashing_search.ts" - /* 哈希查找(链表) */ - function hashingSearch1(map: Map, target: number): ListNode | null { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.has(target) ? map.get(target) as ListNode : null; - } - ``` - -=== "C" - - ```c title="hashing_search.c" - - ``` - -=== "C#" - - ```csharp title="hashing_search.cs" - /* 哈希查找(链表) */ - ListNode? hashingSearch1(Dictionary map, int target) - { - - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.GetValueOrDefault(target); - } - ``` - -=== "Swift" - - ```swift title="hashing_search.swift" - - ``` - -## 复杂度分析 - -**时间复杂度 $O(1)$** :哈希表的查找操作使用 $O(1)$ 时间。 - -**空间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 - -## 优点与缺点 - -在哈希表中,**查找、插入、删除操作的平均时间复杂度都为 $O(1)$** ,这意味着无论是高频增删还是高频查找场景,哈希查找的性能表现都非常好。当然,一切的前提是保证哈希表未退化。 - -即使如此,哈希查找仍存在一些问题,在实际应用中,需要根据情况灵活选择方法。 - -- 辅助哈希表 **需要使用 $O(n)$ 的额外空间**,意味着需要预留更多的计算机内存; -- 建立和维护哈希表需要时间,因此哈希查找 **不适合高频增删、低频查找的使用场景**; -- 当哈希冲突严重时,哈希表会退化为链表,**时间复杂度劣化至 $O(n)$** ; -- **当数据量很小时,线性查找比哈希查找更快**。这是因为计算哈希映射函数可能比遍历一个小型数组更慢; diff --git a/docs/chapter_searching/index.md b/docs/chapter_searching/index.md new file mode 100644 index 0000000000..f473aaf8f6 --- /dev/null +++ b/docs/chapter_searching/index.md @@ -0,0 +1,9 @@ +# 搜索 + +![搜索](../assets/covers/chapter_searching.jpg) + +!!! abstract + + 搜索是一场未知的冒险,我们或许需要走遍神秘空间的每个角落,又或许可以快速锁定目标。 + + 在这场寻觅之旅中,每一次探索都可能得到一个未曾料想的答案。 diff --git a/docs/chapter_searching/linear_search.assets/linear_search.png b/docs/chapter_searching/linear_search.assets/linear_search.png deleted file mode 100644 index 01c26cbeac..0000000000 Binary files a/docs/chapter_searching/linear_search.assets/linear_search.png and /dev/null differ diff --git a/docs/chapter_searching/linear_search.md b/docs/chapter_searching/linear_search.md deleted file mode 100644 index a42ccc365e..0000000000 --- a/docs/chapter_searching/linear_search.md +++ /dev/null @@ -1,286 +0,0 @@ ---- -comments: true ---- - -# 线性查找 - -「线性查找 Linear Search」是一种最基础的查找方法,其从数据结构的一端开始,依次访问每个元素,直到另一端后停止。 - -## 算法实现 - -线性查找实质上就是遍历数据结构 + 判断条件。比如,我们想要在数组 `nums` 中查找目标元素 `target` 的对应索引,那么可以在数组中进行线性查找。 - -![linear_search](linear_search.assets/linear_search.png) - -=== "Java" - - ```java title="linear_search.java" - /* 线性查找(数组) */ - int linearSearch(int[] nums, int target) { - // 遍历数组 - for (int i = 0; i < nums.length; i++) { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "C++" - - ```cpp title="linear_search.cpp" - /* 线性查找(数组) */ - int linearSearch(vector& nums, int target) { - // 遍历数组 - for (int i = 0; i < nums.size(); i++) { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "Python" - - ```python title="linear_search.py" - """ 线性查找(数组) """ - def linear_search(nums, target): - # 遍历数组 - for i in range(len(nums)): - if nums[i] == target: # 找到目标元素,返回其索引 - return i - return -1 # 未找到目标元素,返回 -1 - ``` - -=== "Go" - - ```go title="linear_search.go" - /* 线性查找(数组) */ - func linerSearchArray(nums []int, target int) int { - // 遍历数组 - for i := 0; i < len(nums); i++ { - // 找到目标元素,返回其索引 - if nums[i] == target { - return i - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "JavaScript" - - ```js title="linear_search.js" - /* 线性查找(数组) */ - function linearSearchArray(nums, target) { - // 遍历数组 - for (let i = 0; i < nums.length; i++) { - // 找到目标元素,返回其索引 - if (nums[i] === target) { - return i; - } - } - // 未找到目标元素,返回 -1 - return -1; - } - - ``` - -=== "TypeScript" - - ```typescript title="linear_search.ts" - /* 线性查找(数组)*/ - function linearSearchArray(nums: number[], target: number): number { - // 遍历数组 - for (let i = 0; i < nums.length; i++) { - // 找到目标元素,返回其索引 - if (nums[i] === target) { - return i; - } - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "C" - - ```c title="linear_search.c" - - ``` - -=== "C#" - - ```csharp title="linear_search.cs" - /* 线性查找(数组) */ - int linearSearch(int[] nums, int target) - { - // 遍历数组 - for (int i = 0; i < nums.Length; i++) - { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; - } - - ``` - -=== "Swift" - - ```swift title="linear_search.swift" - - ``` - -再比如,我们想要在给定一个目标结点值 `target` ,返回此结点对象,也可以在链表中进行线性查找。 - -=== "Java" - - ```java title="linear_search.java" - /* 线性查找(链表) */ - ListNode linearSearch(ListNode head, int target) { - // 遍历链表 - while (head != null) { - // 找到目标结点,返回之 - if (head.val == target) - return head; - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "C++" - - ```cpp title="linear_search.cpp" - /* 线性查找(链表) */ - ListNode* linearSearch(ListNode* head, int target) { - // 遍历链表 - while (head != nullptr) { - // 找到目标结点,返回之 - if (head->val == target) - return head; - head = head->next; - } - // 未找到目标结点,返回 nullptr - return nullptr; - } - ``` - -=== "Python" - - ```python title="linear_search.py" - """ 线性查找(链表) """ - def linear_search1(head, target): - # 遍历链表 - while head: - if head.val == target: # 找到目标结点,返回之 - return head - head = head.next - return None # 未找到目标结点,返回 None - ``` - -=== "Go" - - ```go title="linear_search.go" - /* 线性查找(链表)*/ - func linerSearchLinkedList(node *ListNode, target int) *ListNode { - // 遍历链表 - for node != nil { - // 找到目标结点,返回之 - if node.Val == target { - return node - } - node = node.Next - } - // 未找到目标元素,返回 nil - return nil - } - ``` - -=== "JavaScript" - - ```js title="linear_search.js" - /* 线性查找(链表)*/ - function linearSearchLinkedList(head, target) { - // 遍历链表 - while(head) { - // 找到目标结点,返回之 - if(head.val === target) { - return head; - } - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "TypeScript" - - ```typescript title="linear_search.ts" - /* 线性查找(链表)*/ - function linearSearchLinkedList(head: ListNode | null, target: number): ListNode | null { - // 遍历链表 - while (head) { - // 找到目标结点,返回之 - if (head.val === target) { - return head; - } - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "C" - - ```c title="linear_search.c" - - ``` - -=== "C#" - - ```csharp title="linear_search.cs" - /* 线性查找(链表) */ - ListNode? linearSearch(ListNode head, int target) - { - // 遍历链表 - while (head != null) - { - // 找到目标结点,返回之 - if (head.val == target) - return head; - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "Swift" - - ```swift title="linear_search.swift" - - ``` - -## 复杂度分析 - -**时间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 - -**空间复杂度 $O(1)$** :无需使用额外空间。 - -## 优点与缺点 - -**线性查找的通用性极佳**。由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。 - -**线性查找的时间复杂度太高**。在数据量 $n$ 很大时,查找效率很低。 diff --git a/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png new file mode 100644 index 0000000000..a9db1579dd Binary files /dev/null and b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png differ diff --git a/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png new file mode 100644 index 0000000000..df323dd416 Binary files /dev/null and b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png differ diff --git a/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png new file mode 100644 index 0000000000..bed0ac02f1 Binary files /dev/null and b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png differ diff --git a/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png new file mode 100644 index 0000000000..accf61def0 Binary files /dev/null and b/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png differ diff --git a/docs/chapter_searching/replace_linear_by_hashing.md b/docs/chapter_searching/replace_linear_by_hashing.md new file mode 100755 index 0000000000..46e8e53d22 --- /dev/null +++ b/docs/chapter_searching/replace_linear_by_hashing.md @@ -0,0 +1,47 @@ +# 哈希优化策略 + +在算法题中,**我们常通过将线性查找替换为哈希查找来降低算法的时间复杂度**。我们借助一个算法题来加深理解。 + +!!! question + + 给定一个整数数组 `nums` 和一个目标元素 `target` ,请在数组中搜索“和”为 `target` 的两个元素,并返回它们的数组索引。返回任意一个解即可。 + +## 线性查找:以时间换空间 + +考虑直接遍历所有可能的组合。如下图所示,我们开启一个两层循环,在每轮中判断两个整数的和是否为 `target` ,若是,则返回它们的索引。 + +![线性查找求解两数之和](replace_linear_by_hashing.assets/two_sum_brute_force.png) + +代码如下所示: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_brute_force} +``` + +此方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,在大数据量下非常耗时。 + +## 哈希查找:以空间换时间 + +考虑借助一个哈希表,键值对分别为数组元素和元素索引。循环遍历数组,每轮执行下图所示的步骤。 + +1. 判断数字 `target - nums[i]` 是否在哈希表中,若是,则直接返回这两个元素的索引。 +2. 将键值对 `nums[i]` 和索引 `i` 添加进哈希表。 + +=== "<1>" + ![辅助哈希表求解两数之和](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + +=== "<2>" + ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) + +=== "<3>" + ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) + +实现代码如下所示,仅需单层循环即可: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_hash_table} +``` + +此方法通过哈希查找将时间复杂度从 $O(n^2)$ 降至 $O(n)$ ,大幅提升运行效率。 + +由于需要维护一个额外的哈希表,因此空间复杂度为 $O(n)$ 。**尽管如此,该方法的整体时空效率更为均衡,因此它是本题的最优解法**。 diff --git a/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png b/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png new file mode 100644 index 0000000000..fff1066ffc Binary files /dev/null and b/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png differ diff --git a/docs/chapter_searching/searching_algorithm_revisited.md b/docs/chapter_searching/searching_algorithm_revisited.md new file mode 100644 index 0000000000..fc6d5e880a --- /dev/null +++ b/docs/chapter_searching/searching_algorithm_revisited.md @@ -0,0 +1,84 @@ +# 重识搜索算法 + +搜索算法(searching algorithm)用于在数据结构(例如数组、链表、树或图)中搜索一个或一组满足特定条件的元素。 + +搜索算法可根据实现思路分为以下两类。 + +- **通过遍历数据结构来定位目标元素**,例如数组、链表、树和图的遍历等。 +- **利用数据组织结构或数据包含的先验信息,实现高效元素查找**,例如二分查找、哈希查找和二叉搜索树查找等。 + +不难发现,这些知识点都已在前面的章节中介绍过,因此搜索算法对于我们来说并不陌生。在本节中,我们将从更加系统的视角切入,重新审视搜索算法。 + +## 暴力搜索 + +暴力搜索通过遍历数据结构的每个元素来定位目标元素。 + +- “线性搜索”适用于数组和链表等线性数据结构。它从数据结构的一端开始,逐个访问元素,直到找到目标元素或到达另一端仍没有找到目标元素为止。 +- “广度优先搜索”和“深度优先搜索”是图和树的两种遍历策略。广度优先搜索从初始节点开始逐层搜索,由近及远地访问各个节点。深度优先搜索从初始节点开始,沿着一条路径走到头,再回溯并尝试其他路径,直到遍历完整个数据结构。 + +暴力搜索的优点是简单且通用性好,**无须对数据做预处理和借助额外的数据结构**。 + +然而,**此类算法的时间复杂度为 $O(n)$** ,其中 $n$ 为元素数量,因此在数据量较大的情况下性能较差。 + +## 自适应搜索 + +自适应搜索利用数据的特有属性(例如有序性)来优化搜索过程,从而更高效地定位目标元素。 + +- “二分查找”利用数据的有序性实现高效查找,仅适用于数组。 +- “哈希查找”利用哈希表将搜索数据和目标数据建立为键值对映射,从而实现查询操作。 +- “树查找”在特定的树结构(例如二叉搜索树)中,基于比较节点值来快速排除节点,从而定位目标元素。 + +此类算法的优点是效率高,**时间复杂度可达到 $O(\log n)$ 甚至 $O(1)$** 。 + +然而,**使用这些算法往往需要对数据进行预处理**。例如,二分查找需要预先对数组进行排序,哈希查找和树查找都需要借助额外的数据结构,维护这些数据结构也需要额外的时间和空间开销。 + +!!! tip + + 自适应搜索算法常被称为查找算法,**主要用于在特定数据结构中快速检索目标元素**。 + +## 搜索方法选取 + +给定大小为 $n$ 的一组数据,我们可以使用线性搜索、二分查找、树查找、哈希查找等多种方法从中搜索目标元素。各个方法的工作原理如下图所示。 + +![多种搜索策略](searching_algorithm_revisited.assets/searching_algorithms.png) + +上述几种方法的操作效率与特性如下表所示。 + +

  查找算法效率对比

+ +| | 线性搜索 | 二分查找 | 树查找 | 哈希查找 | +| ------------ | -------- | ------------------ | ------------------ | --------------- | +| 查找元素 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | +| 插入元素 | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 删除元素 | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 额外空间 | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | +| 数据预处理 | / | 排序 $O(n \log n)$ | 建树 $O(n \log n)$ | 建哈希表 $O(n)$ | +| 数据是否有序 | 无序 | 有序 | 有序 | 无序 | + +搜索算法的选择还取决于数据体量、搜索性能要求、数据查询与更新频率等。 + +**线性搜索** + +- 通用性较好,无须任何数据预处理操作。假如我们仅需查询一次数据,那么其他三种方法的数据预处理的时间比线性搜索的时间还要更长。 +- 适用于体量较小的数据,此情况下时间复杂度对效率影响较小。 +- 适用于数据更新频率较高的场景,因为该方法不需要对数据进行任何额外维护。 + +**二分查找** + +- 适用于大数据量的情况,效率表现稳定,最差时间复杂度为 $O(\log n)$ 。 +- 数据量不能过大,因为存储数组需要连续的内存空间。 +- 不适用于高频增删数据的场景,因为维护有序数组的开销较大。 + +**哈希查找** + +- 适合对查询性能要求很高的场景,平均时间复杂度为 $O(1)$ 。 +- 不适合需要有序数据或范围查找的场景,因为哈希表无法维护数据的有序性。 +- 对哈希函数和哈希冲突处理策略的依赖性较高,具有较大的性能劣化风险。 +- 不适合数据量过大的情况,因为哈希表需要额外空间来最大程度地减少冲突,从而提供良好的查询性能。 + +**树查找** + +- 适用于海量数据,因为树节点在内存中是分散存储的。 +- 适合需要维护有序数据或范围查找的场景。 +- 在持续增删节点的过程中,二叉搜索树可能产生倾斜,时间复杂度劣化至 $O(n)$ 。 +- 若使用 AVL 树或红黑树,则各项操作可在 $O(\log n)$ 效率下稳定运行,但维护树平衡的操作会增加额外的开销。 diff --git a/docs/chapter_searching/summary.md b/docs/chapter_searching/summary.md index 066d09af5e..4e8bf66f12 100644 --- a/docs/chapter_searching/summary.md +++ b/docs/chapter_searching/summary.md @@ -1,23 +1,8 @@ ---- -comments: true ---- - # 小结 -- 线性查找是一种最基础的查找方法,通过遍历数据结构 + 判断条件实现查找。 -- 二分查找利用数据的有序性,通过循环不断缩小一半搜索区间来实现查找,其要求输入数据是有序的,并且仅适用于数组或基于数组实现的数据结构。 -- 哈希查找借助哈希表来实现常数阶时间复杂度的查找操作,体现以空间换时间的算法思想。 - -

Table. 三种查找方法对比

- -
- -| | 线性查找 | 二分查找 | 哈希查找 | -| ------------------------------------- | ------------------------ | ----------------------------- | ------------------------ | -| 适用数据结构 | 数组、链表 | 数组 | 数组、链表 | -| 输入数据要求 | 无 | 有序 | 无 | -| 平均时间复杂度
查找 / 插入 / 删除 | $O(n)$ / $O(1)$ / $O(n)$ | $O(\log n)$ / $O(n)$ / $O(n)$ | $O(1)$ / $O(1)$ / $O(1)$ | -| 最差时间复杂度
查找 / 插入 / 删除 | $O(n)$ / $O(1)$ / $O(n)$ | $O(\log n)$ / $O(n)$ / $O(n)$ | $O(n)$ / $O(n)$ / $O(n)$ | -| 空间复杂度 | $O(1)$ | $O(1)$ | $O(n)$ | - -
+- 二分查找依赖数据的有序性,通过循环逐步缩减一半搜索区间来进行查找。它要求输入数据有序,且仅适用于数组或基于数组实现的数据结构。 +- 暴力搜索通过遍历数据结构来定位数据。线性搜索适用于数组和链表,广度优先搜索和深度优先搜索适用于图和树。此类算法通用性好,无须对数据进行预处理,但时间复杂度 $O(n)$ 较高。 +- 哈希查找、树查找和二分查找属于高效搜索方法,可在特定数据结构中快速定位目标元素。此类算法效率高,时间复杂度可达 $O(\log n)$ 甚至 $O(1)$ ,但通常需要借助额外数据结构。 +- 实际中,我们需要对数据体量、搜索性能要求、数据查询和更新频率等因素进行具体分析,从而选择合适的搜索方法。 +- 线性搜索适用于小型或频繁更新的数据;二分查找适用于大型、排序的数据;哈希查找适用于对查询效率要求较高且无须范围查询的数据;树查找适用于需要维护顺序和支持范围查询的大型动态数据。 +- 用哈希查找替换线性查找是一种常用的优化运行时间的策略,可将时间复杂度从 $O(n)$ 降至 $O(1)$ 。 diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png index 9b72415edd..7c53d16c71 100644 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png and b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png index b4d9588d27..1387819b03 100644 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png and b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png index bc44269ce6..dd5953407b 100644 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png and b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png index 126f24db75..cc63734ffd 100644 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png and b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png index 0d43357675..96623a708e 100644 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png and b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png index 265b063f5c..56ec7c2d32 100644 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png and b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png index 3899ca716d..49e1ee3b00 100644 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png and b/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_sort.png b/docs/chapter_sorting/bubble_sort.assets/bubble_sort.png deleted file mode 100644 index 693d915a20..0000000000 Binary files a/docs/chapter_sorting/bubble_sort.assets/bubble_sort.png and /dev/null differ diff --git a/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png b/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png new file mode 100644 index 0000000000..c88959ef1f Binary files /dev/null and b/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png differ diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md old mode 100644 new mode 100755 index 3bb0bc9943..fb26171bef --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -1,428 +1,59 @@ ---- -comments: true ---- - # 冒泡排序 -「冒泡排序 Bubble Sort」是一种最基础的排序算法,非常适合作为第一个学习的排序算法。顾名思义,「冒泡」是该算法的核心操作。 - -!!! question "为什么叫“冒泡”" - - 在水中,越大的泡泡浮力越大,所以最大的泡泡会最先浮到水面。 - -「冒泡」操作则是在模拟上述过程,具体做法为:从数组最左端开始向右遍历,依次对比相邻元素大小,若 **左元素 > 右元素** 则将它俩交换,最终可将最大元素移动至数组最右端。 +冒泡排序(bubble sort)通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样,因此得名冒泡排序。 -完成此次冒泡操作后,**数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素**。 +如下图所示,冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换二者。遍历完成后,最大的元素会被移动到数组的最右端。 -=== "Step 1" - - ![bubble_operation_step1](bubble_sort.assets/bubble_operation_step1.png) - -=== "Step 2" +=== "<1>" + ![利用元素交换操作模拟冒泡](bubble_sort.assets/bubble_operation_step1.png) +=== "<2>" ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) -=== "Step 3" - +=== "<3>" ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) -=== "Step 4" - +=== "<4>" ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) -=== "Step 5" - +=== "<5>" ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) -=== "Step 6" - +=== "<6>" ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) -=== "Step 7" - +=== "<7>" ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) -

Fig. 冒泡操作

- ## 算法流程 -1. 设数组长度为 $n$ ,完成第一轮「冒泡」后,数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素。 -2. 同理,对剩余 $n - 1$ 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 $n - 2$ 个。 -3. 以此类推…… **循环 $n - 1$ 轮「冒泡」,即可完成整个数组的排序**。 - -![bubble_sort](bubble_sort.assets/bubble_sort.png) - -

Fig. 冒泡排序流程

- -=== "Java" - - ```java title="bubble_sort.java" - /* 冒泡排序 */ - void bubbleSort(int[] nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.length - 1; 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; - } - } - } - } - ``` - -=== "C++" - - ```cpp title="bubble_sort.cpp" - /* 冒泡排序 */ - void bubbleSort(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - // 这里使用了 std::swap() 函数 - swap(nums[j], nums[j + 1]); - } - } - } - } - ``` - -=== "Python" - - ```python title="bubble_sort.py" - """ 冒泡排序 """ - def bubble_sort(nums): - n = len(nums) - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(n - 1, -1, -1): - # 内循环:冒泡操作 - for j in range(i): - if nums[j] > nums[j + 1]: - # 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j + 1] = nums[j + 1], nums[j] - ``` - -=== "Go" - - ```go title="bubble_sort.go" - /* 冒泡排序 */ - func bubbleSort(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := len(nums) - 1; i > 0; i-- { - // 内循环:冒泡操作 - for j := 0; j < i; j++ { - if nums[j] > nums[j+1] { - // 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j+1] = nums[j+1], nums[j] - } - } - } - } - ``` - -=== "JavaScript" - - ```js title="bubble_sort.js" - /* 冒泡排序 */ - function bubbleSort(nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - } - } - } - } - ``` - -=== "TypeScript" - - ```typescript title="bubble_sort.ts" - /* 冒泡排序 */ - function bubbleSort(nums: number[]): void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - } - } - } - } - ``` - -=== "C" +设数组的长度为 $n$ ,冒泡排序的步骤如下图所示。 - ```c title="bubble_sort.c" - /* 冒泡排序 */ - void bubbleSort(int nums[], int size) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = 0; i < size - 1; i++) - { - // 内循环:冒泡操作 - for (int j = 0; j < size - 1 - i; j++) - { - if (nums[j] > nums[j + 1]) - { - int temp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = temp; - } - } - } - } - ``` +1. 首先,对 $n$ 个元素执行“冒泡”,**将数组的最大元素交换至正确位置**。 +2. 接下来,对剩余 $n - 1$ 个元素执行“冒泡”,**将第二大元素交换至正确位置**。 +3. 以此类推,经过 $n - 1$ 轮“冒泡”后,**前 $n - 1$ 大的元素都被交换至正确位置**。 +4. 仅剩的一个元素必定是最小元素,无须排序,因此数组排序完成。 -=== "C#" +![冒泡排序流程](bubble_sort.assets/bubble_sort_overview.png) - ```csharp title="bubble_sort.cs" - /* 冒泡排序 */ - void bubbleSort(int[] nums) - { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; 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; - } - } - } - } - ``` +示例代码如下: -=== "Swift" - - ```swift title="bubble_sort.swift" - - ``` - -## 算法特性 - -**时间复杂度 $O(n^2)$** :各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 - -**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 - -**原地排序**:指针变量仅使用常数大小额外空间。 - -**稳定排序**:不交换相等元素。 - -**自适排序**:引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort} +``` ## 效率优化 -我们发现,若在某轮「冒泡」中未执行任何交换操作,则说明数组已经完成排序,可直接返回结果。考虑可以增加一个标志位 `flag` 来监听该情况,若出现则直接返回。 - -优化后,冒泡排序的最差和平均时间复杂度仍为 $O(n^2)$ ;而在输入数组 **已排序** 时,达到 **最佳时间复杂度** $O(n)$ 。 - -=== "Java" - - ```java title="bubble_sort.java" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(int[] nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.length - 1; i > 0; i--) { - boolean flag = false; // 初始化标志位 - // 内循环:冒泡操作 - 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; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "C++" +我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 `flag` 来监测这种情况,一旦出现就立即返回。 - ```cpp title="bubble_sort.cpp" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - bool flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - // 这里使用了 std::swap() 函数 - swap(nums[j], nums[j + 1]); - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` +经过优化,冒泡排序的最差时间复杂度和平均时间复杂度仍为 $O(n^2)$ ;但当输入数组完全有序时,可达到最佳时间复杂度 $O(n)$ 。 -=== "Python" +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} +``` - ```python title="bubble_sort.py" - """ 冒泡排序(标志优化) """ - def bubble_sort_with_flag(nums): - n = len(nums) - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(n - 1, -1, -1): - flag = False # 初始化标志位 - # 内循环:冒泡操作 - for j in range(i): - if nums[j] > nums[j + 1]: - # 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j + 1] = nums[j + 1], nums[j] - flag = True # 记录交换元素 - if not flag: - break # 此轮冒泡未交换任何元素,直接跳出 - ``` - -=== "Go" - - ```go title="bubble_sort.go" - /* 冒泡排序(标志优化)*/ - func bubbleSortWithFlag(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := len(nums) - 1; i > 0; i-- { - flag := false // 初始化标志位 - // 内循环:冒泡操作 - for j := 0; j < i; j++ { - if nums[j] > nums[j+1] { - // 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j+1] = nums[j+1], nums[j] - flag = true // 记录交换元素 - } - } - if flag == false { // 此轮冒泡未交换任何元素,直接跳出 - break - } - } - } - ``` - -=== "JavaScript" - - ```js title="bubble_sort.js" - /* 冒泡排序(标志优化)*/ - function bubbleSortWithFlag(nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - let flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "TypeScript" - - ```typescript title="bubble_sort.ts" - /* 冒泡排序(标志优化)*/ - function bubbleSortWithFlag(nums: number[]): void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - let flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "C" - - ```c title="bubble_sort.c" - /* 冒泡排序 */ - void bubbleSortWithFlag(int nums[], int size) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = 0; i < size - 1; i++) - { - bool flag = false; - // 内循环:冒泡操作 - for (int j = 0; j < size - 1 - 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; - } - } - ``` - -=== "C#" - - ```csharp title="bubble_sort.cs" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(int[] nums) - { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; i > 0; i--) - { - bool flag = false; // 初始化标志位 - // 内循环:冒泡操作 - 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; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "Swift" - - ```swift title="bubble_sort.swift" +## 算法特性 - ``` +- **时间复杂度为 $O(n^2)$、自适应排序**:各轮“冒泡”遍历的数组长度依次为 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,总和为 $(n - 1) n / 2$ 。在引入 `flag` 优化后,最佳时间复杂度可达到 $O(n)$ 。 +- **空间复杂度为 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。 +- **稳定排序**:由于在“冒泡”中遇到相等元素不交换。 diff --git a/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png b/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png new file mode 100644 index 0000000000..4a3886e8dc Binary files /dev/null and b/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png differ diff --git a/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png b/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png new file mode 100644 index 0000000000..2b52627a93 Binary files /dev/null and b/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png differ diff --git a/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png b/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png new file mode 100644 index 0000000000..9c4d02353d Binary files /dev/null and b/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png differ diff --git a/docs/chapter_sorting/bucket_sort.md b/docs/chapter_sorting/bucket_sort.md new file mode 100644 index 0000000000..84b7f08d09 --- /dev/null +++ b/docs/chapter_sorting/bucket_sort.md @@ -0,0 +1,45 @@ +# 桶排序 + +前述几种排序算法都属于“基于比较的排序算法”,它们通过比较元素间的大小来实现排序。此类排序算法的时间复杂度无法超越 $O(n \log n)$ 。接下来,我们将探讨几种“非比较排序算法”,它们的时间复杂度可以达到线性阶。 + +桶排序(bucket sort)是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序;最终按照桶的顺序将所有数据合并。 + +## 算法流程 + +考虑一个长度为 $n$ 的数组,其元素是范围 $[0, 1)$ 内的浮点数。桶排序的流程如下图所示。 + +1. 初始化 $k$ 个桶,将 $n$ 个元素分配到 $k$ 个桶中。 +2. 对每个桶分别执行排序(这里采用编程语言的内置排序函数)。 +3. 按照桶从小到大的顺序合并结果。 + +![桶排序算法流程](bucket_sort.assets/bucket_sort_overview.png) + +代码如下所示: + +```src +[file]{bucket_sort}-[class]{}-[func]{bucket_sort} +``` + +## 算法特性 + +桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。 + +- **时间复杂度为 $O(n + k)$** :假设元素在各个桶内平均分布,那么每个桶内的元素数量为 $\frac{n}{k}$ 。假设排序单个桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 时间,则排序所有桶使用 $O(n \log\frac{n}{k})$ 时间。**当桶数量 $k$ 比较大时,时间复杂度则趋向于 $O(n)$** 。合并结果时需要遍历所有桶和元素,花费 $O(n + k)$ 时间。在最差情况下,所有数据被分配到一个桶中,且排序该桶使用 $O(n^2)$ 时间。 +- **空间复杂度为 $O(n + k)$、非原地排序**:需要借助 $k$ 个桶和总共 $n$ 个元素的额外空间。 +- 桶排序是否稳定取决于排序桶内元素的算法是否稳定。 + +## 如何实现平均分配 + +桶排序的时间复杂度理论上可以达到 $O(n)$ ,**关键在于将元素均匀分配到各个桶中**,因为实际数据往往不是均匀分布的。例如,我们想要将淘宝上的所有商品按价格范围平均分配到 10 个桶中,但商品价格分布不均,低于 100 元的非常多,高于 1000 元的非常少。若将价格区间平均划分为 10 个,各个桶中的商品数量差距会非常大。 + +为实现平均分配,我们可以先设定一条大致的分界线,将数据粗略地分到 3 个桶中。**分配完毕后,再将商品较多的桶继续划分为 3 个桶,直至所有桶中的元素数量大致相等**。 + +如下图所示,这种方法本质上是创建一棵递归树,目标是让叶节点的值尽可能平均。当然,不一定要每轮将数据划分为 3 个桶,具体划分方式可根据数据特点灵活选择。 + +![递归划分桶](bucket_sort.assets/scatter_in_buckets_recursively.png) + +如果我们提前知道商品价格的概率分布,**则可以根据数据概率分布设置每个桶的价格分界线**。值得注意的是,数据分布并不一定需要特意统计,也可以根据数据特点采用某种概率模型进行近似。 + +如下图所示,我们假设商品价格服从正态分布,这样就可以合理地设定价格区间,从而将商品平均分配到各个桶中。 + +![根据概率分布划分桶](bucket_sort.assets/scatter_in_buckets_distribution.png) diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png new file mode 100644 index 0000000000..fff7c78141 Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png new file mode 100644 index 0000000000..b7f5e68338 Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png new file mode 100644 index 0000000000..b054b7bdbd Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png new file mode 100644 index 0000000000..63987089b9 Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png new file mode 100644 index 0000000000..afa7993d11 Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png new file mode 100644 index 0000000000..cc92259e5a Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png new file mode 100644 index 0000000000..f77fc7c996 Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png new file mode 100644 index 0000000000..5ab818a65c Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png differ diff --git a/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png b/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png new file mode 100644 index 0000000000..33ed7550b8 Binary files /dev/null and b/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png differ diff --git a/docs/chapter_sorting/counting_sort.md b/docs/chapter_sorting/counting_sort.md new file mode 100644 index 0000000000..fbb119a721 --- /dev/null +++ b/docs/chapter_sorting/counting_sort.md @@ -0,0 +1,84 @@ +# 计数排序 + +计数排序(counting sort)通过统计元素数量来实现排序,通常应用于整数数组。 + +## 简单实现 + +先来看一个简单的例子。给定一个长度为 $n$ 的数组 `nums` ,其中的元素都是“非负整数”,计数排序的整体流程如下图所示。 + +1. 遍历数组,找出其中的最大数字,记为 $m$ ,然后创建一个长度为 $m + 1$ 的辅助数组 `counter` 。 +2. **借助 `counter` 统计 `nums` 中各数字的出现次数**,其中 `counter[num]` 对应数字 `num` 的出现次数。统计方法很简单,只需遍历 `nums`(设当前数字为 `num`),每轮将 `counter[num]` 增加 $1$ 即可。 +3. **由于 `counter` 的各个索引天然有序,因此相当于所有数字已经排序好了**。接下来,我们遍历 `counter` ,根据各数字出现次数从小到大的顺序填入 `nums` 即可。 + +![计数排序流程](counting_sort.assets/counting_sort_overview.png) + +代码如下所示: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort_naive} +``` + +!!! note "计数排序与桶排序的联系" + + 从桶排序的角度看,我们可以将计数排序中的计数数组 `counter` 的每个索引视为一个桶,将统计数量的过程看作将各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。 + +## 完整实现 + +细心的读者可能发现了,**如果输入数据是对象,上述步骤 `3.` 就失效了**。假设输入数据是商品对象,我们想按照商品价格(类的成员变量)对商品进行排序,而上述算法只能给出价格的排序结果。 + +那么如何才能得到原数据的排序结果呢?我们首先计算 `counter` 的“前缀和”。顾名思义,索引 `i` 处的前缀和 `prefix[i]` 等于数组前 `i` 个元素之和: + +$$ +\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} +$$ + +**前缀和具有明确的意义,`prefix[num] - 1` 代表元素 `num` 在结果数组 `res` 中最后一次出现的索引**。这个信息非常关键,因为它告诉我们各个元素应该出现在结果数组的哪个位置。接下来,我们倒序遍历原数组 `nums` 的每个元素 `num` ,在每轮迭代中执行以下两步。 + +1. 将 `num` 填入数组 `res` 的索引 `prefix[num] - 1` 处。 +2. 令前缀和 `prefix[num]` 减小 $1$ ,从而得到下次放置 `num` 的索引。 + +遍历完成后,数组 `res` 中就是排序好的结果,最后使用 `res` 覆盖原数组 `nums` 即可。下图展示了完整的计数排序流程。 + +=== "<1>" + ![计数排序步骤](counting_sort.assets/counting_sort_step1.png) + +=== "<2>" + ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) + +=== "<3>" + ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) + +=== "<4>" + ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) + +=== "<5>" + ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) + +=== "<6>" + ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) + +=== "<7>" + ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) + +=== "<8>" + ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) + +计数排序的实现代码如下所示: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort} +``` + +## 算法特性 + +- **时间复杂度为 $O(n + m)$、非自适应排序** :涉及遍历 `nums` 和遍历 `counter` ,都使用线性时间。一般情况下 $n \gg m$ ,时间复杂度趋于 $O(n)$ 。 +- **空间复杂度为 $O(n + m)$、非原地排序**:借助了长度分别为 $n$ 和 $m$ 的数组 `res` 和 `counter` 。 +- **稳定排序**:由于向 `res` 中填充元素的顺序是“从右向左”的,因此倒序遍历 `nums` 可以避免改变相等元素之间的相对位置,从而实现稳定排序。实际上,正序遍历 `nums` 也可以得到正确的排序结果,但结果是非稳定的。 + +## 局限性 + +看到这里,你也许会觉得计数排序非常巧妙,仅通过统计数量就可以实现高效的排序。然而,使用计数排序的前置条件相对较为严格。 + +**计数排序只适用于非负整数**。若想将其用于其他类型的数据,需要确保这些数据可以转换为非负整数,并且在转换过程中不能改变各个元素之间的相对大小关系。例如,对于包含负数的整数数组,可以先给所有数字加上一个常数,将全部数字转化为正数,排序完成后再转换回去。 + +**计数排序适用于数据量大但数据范围较小的情况**。比如,在上述示例中 $m$ 不能太大,否则会占用过多空间。而当 $n \ll m$ 时,计数排序使用 $O(m)$ 时间,可能比 $O(n \log n)$ 的排序算法还要慢。 diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png new file mode 100644 index 0000000000..f634af76c4 Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png new file mode 100644 index 0000000000..e3997706aa Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png new file mode 100644 index 0000000000..a857ddca4f Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png new file mode 100644 index 0000000000..ee9c1a062e Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png new file mode 100644 index 0000000000..bd5c2d9cc2 Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png new file mode 100644 index 0000000000..ec761049e9 Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png new file mode 100644 index 0000000000..fea98499d5 Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png new file mode 100644 index 0000000000..6a8de4c5ea Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png new file mode 100644 index 0000000000..61656286b2 Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png new file mode 100644 index 0000000000..9b938dd6e6 Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png new file mode 100644 index 0000000000..2c61e8bb81 Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png differ diff --git a/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png b/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png new file mode 100644 index 0000000000..1cb96c815b Binary files /dev/null and b/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png differ diff --git a/docs/chapter_sorting/heap_sort.md b/docs/chapter_sorting/heap_sort.md new file mode 100644 index 0000000000..9e31db886a --- /dev/null +++ b/docs/chapter_sorting/heap_sort.md @@ -0,0 +1,73 @@ +# 堆排序 + +!!! tip + + 阅读本节前,请确保已学完“堆”章节。 + +堆排序(heap sort)是一种基于堆数据结构实现的高效排序算法。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。 + +1. 输入数组并建立小顶堆,此时最小元素位于堆顶。 +2. 不断执行出堆操作,依次记录出堆元素,即可得到从小到大排序的序列。 + +以上方法虽然可行,但需要借助一个额外数组来保存弹出的元素,比较浪费空间。在实际中,我们通常使用一种更加优雅的实现方式。 + +## 算法流程 + +设数组的长度为 $n$ ,堆排序的流程如下图所示。 + +1. 输入数组并建立大顶堆。完成后,最大元素位于堆顶。 +2. 将堆顶元素(第一个元素)与堆底元素(最后一个元素)交换。完成交换后,堆的长度减 $1$ ,已排序元素数量加 $1$ 。 +3. 从堆顶元素开始,从顶到底执行堆化操作(sift down)。完成堆化后,堆的性质得到修复。 +4. 循环执行第 `2.` 步和第 `3.` 步。循环 $n - 1$ 轮后,即可完成数组排序。 + +!!! tip + + 实际上,元素出堆操作中也包含第 `2.` 步和第 `3.` 步,只是多了一个弹出元素的步骤。 + +=== "<1>" + ![堆排序步骤](heap_sort.assets/heap_sort_step1.png) + +=== "<2>" + ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) + +=== "<3>" + ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) + +=== "<4>" + ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) + +=== "<5>" + ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) + +=== "<6>" + ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) + +=== "<7>" + ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) + +=== "<8>" + ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) + +=== "<9>" + ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) + +=== "<10>" + ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) + +=== "<11>" + ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) + +=== "<12>" + ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) + +在代码实现中,我们使用了与“堆”章节相同的从顶至底堆化 `sift_down()` 函数。值得注意的是,由于堆的长度会随着提取最大元素而减小,因此我们需要给 `sift_down()` 函数添加一个长度参数 $n$ ,用于指定堆的当前有效长度。代码如下所示: + +```src +[file]{heap_sort}-[class]{}-[func]{heap_sort} +``` + +## 算法特性 + +- **时间复杂度为 $O(n \log n)$、非自适应排序**:建堆操作使用 $O(n)$ 时间。从堆中提取最大元素的时间复杂度为 $O(\log n)$ ,共循环 $n - 1$ 轮。 +- **空间复杂度为 $O(1)$、原地排序**:几个指针变量使用 $O(1)$ 空间。元素交换和堆化操作都是在原数组上进行的。 +- **非稳定排序**:在交换堆顶元素和堆底元素时,相等元素的相对位置可能发生变化。 diff --git a/docs/chapter_sorting/index.md b/docs/chapter_sorting/index.md new file mode 100644 index 0000000000..41c7dece9c --- /dev/null +++ b/docs/chapter_sorting/index.md @@ -0,0 +1,9 @@ +# 排序 + +![排序](../assets/covers/chapter_sorting.jpg) + +!!! abstract + + 排序犹如一把将混乱变为秩序的魔法钥匙,使我们能以更高效的方式理解与处理数据。 + + 无论是简单的升序,还是复杂的分类排列,排序都向我们展示了数据的和谐美感。 diff --git a/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png b/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png index cdaaa4e1ae..c6806c0ea7 100644 Binary files a/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png and b/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png differ diff --git a/docs/chapter_sorting/insertion_sort.assets/insertion_sort.png b/docs/chapter_sorting/insertion_sort.assets/insertion_sort.png deleted file mode 100644 index 61ee43dfeb..0000000000 Binary files a/docs/chapter_sorting/insertion_sort.assets/insertion_sort.png and /dev/null differ diff --git a/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png b/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png new file mode 100644 index 0000000000..f3fd0486ea Binary files /dev/null and b/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png differ diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md old mode 100644 new mode 100755 index 05ee7293ec..2531452c75 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -1,209 +1,46 @@ ---- -comments: true ---- - # 插入排序 -「插入排序 Insertion Sort」是一种基于 **数组插入操作** 的排序算法。 - -「插入操作」的思想:选定数组的某个元素为基准数 `base` ,将 `base` 与其左边的元素依次对比大小,并“插入”到正确位置。 +插入排序(insertion sort)是一种简单的排序算法,它的工作原理与手动整理一副牌的过程非常相似。 -然而,由于数组在内存中的存储方式是连续的,我们无法直接把 `base` 插入到目标位置,而是需要将从目标位置到 `base` 之间的所有元素向右移动一位(本质上是一次数组插入操作)。 +具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置。 -![insertion_operation](insertion_sort.assets/insertion_operation.png) +下图展示了数组插入元素的操作流程。设基准元素为 `base` ,我们需要将从目标索引到 `base` 之间的所有元素向右移动一位,然后将 `base` 赋值给目标索引。 -

Fig. 插入操作

+![单次插入操作](insertion_sort.assets/insertion_operation.png) ## 算法流程 -1. 第 1 轮先选取数组的 **第 2 个元素** 为 `base` ,执行「插入操作」后, **数组前 2 个元素已完成排序**。 -2. 第 2 轮选取 **第 3 个元素** 为 `base` ,执行「插入操作」后, **数组前 3 个元素已完成排序**。 -3. 以此类推……最后一轮选取 **数组尾元素** 为 `base` ,执行「插入操作」后 **所有元素已完成排序**。 - -![insertion_sort](insertion_sort.assets/insertion_sort.png) - -

Fig. 插入排序流程

- -=== "Java" - - ```java title="insertion_sort.java" - /* 插入排序 */ - void insertionSort(int[] nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.length; i++) { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "C++" - - ```cpp title="insertion_sort.cpp" - /* 插入排序 */ - void insertionSort(vector& nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.size(); i++) { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "Python" - - ```python title="insertion_sort.py" - """ 插入排序 """ - def insertion_sort(nums): - # 外循环:base = nums[1], nums[2], ..., nums[n-1] - for i in range(1, len(nums)): - base = nums[i] - j = i - 1 - # 内循环:将 base 插入到左边的正确位置 - while j >= 0 and nums[j] > base: - nums[j + 1] = nums[j] # 1. 将 nums[j] 向右移动一位 - j -= 1 - nums[j + 1] = base # 2. 将 base 赋值到正确位置 - ``` - -=== "Go" - - ```go title="insertion_sort.go" - /* 插入排序 */ - func insertionSort(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := 1; i < len(nums); i++ { - base := nums[i] - j := i - 1 - // 内循环:将 base 插入到左边的正确位置 - for j >= 0 && nums[j] > base { - nums[j+1] = nums[j] // 1. 将 nums[j] 向右移动一位 - j-- - } - nums[j+1] = base // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "JavaScript" - - ```js title="insertion_sort.js" - /* 插入排序 */ - function insertionSort(nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (let i = 1; i < nums.length; i++) { - let base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "TypeScript" - - ```typescript title="insertion_sort.ts" - /* 插入排序 */ - function insertionSort(nums: number[]): void { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (let i = 1; i < nums.length; i++) { - const base = nums[i]; - let j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "C" - - ```c title="insertion_sort.c" - /* 插入排序 */ - void insertionSort(int nums[], int size) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < size; i++) - { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) - { - // 1. 将 nums[j] 向右移动一位 - nums[j + 1] = nums[j]; - j--; - } - // 2. 将 base 赋值到正确位置 - nums[j + 1] = base; - } - } - ``` - -=== "C#" - - ```csharp title="insertion_sort.cs" - /* 插入排序 */ - void insertionSort(int[] nums) - { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.Length; i++) - { - int bas = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > bas) - { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = bas; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "Swift" - - ```swift title="insertion_sort.swift" - - ``` +插入排序的整体流程如下图所示。 -## 算法特性 +1. 初始状态下,数组的第 1 个元素已完成排序。 +2. 选取数组的第 2 个元素作为 `base` ,将其插入到正确位置后,**数组的前 2 个元素已排序**。 +3. 选取第 3 个元素作为 `base` ,将其插入到正确位置后,**数组的前 3 个元素已排序**。 +4. 以此类推,在最后一轮中,选取最后一个元素作为 `base` ,将其插入到正确位置后,**所有元素均已排序**。 -**时间复杂度 $O(n^2)$** :最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 +![插入排序流程](insertion_sort.assets/insertion_sort_overview.png) -**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 +示例代码如下: -**原地排序**:指针变量仅使用常数大小额外空间。 +```src +[file]{insertion_sort}-[class]{}-[func]{insertion_sort} +``` -**稳定排序**:不交换相等元素。 - -**自适应排序**:最佳情况下,时间复杂度为 $O(n)$ 。 +## 算法特性 -## 插入排序 vs 冒泡排序 +- **时间复杂度为 $O(n^2)$、自适应排序**:在最差情况下,每次插入操作分别需要循环 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此时间复杂度为 $O(n^2)$ 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 $O(n)$ 。 +- **空间复杂度为 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。 +- **稳定排序**:在插入操作过程中,我们会将元素插入到相等元素的右侧,不会改变它们的顺序。 -!!! question +## 插入排序的优势 - 虽然「插入排序」和「冒泡排序」的时间复杂度皆为 $O(n^2)$ ,但实际运行速度却有很大差别,这是为什么呢? +插入排序的时间复杂度为 $O(n^2)$ ,而我们即将学习的快速排序的时间复杂度为 $O(n \log n)$ 。尽管插入排序的时间复杂度更高,**但在数据量较小的情况下,插入排序通常更快**。 -回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换**,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值**,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。 +这个结论与线性查找和二分查找的适用情况的结论类似。快速排序这类 $O(n \log n)$ 的算法属于基于分治策略的排序算法,往往包含更多单元计算操作。而在数据量较小时,$n^2$ 和 $n \log n$ 的数值比较接近,复杂度不占主导地位,每轮中的单元操作数量起到决定性作用。 -插入排序运行速度快,并且具有原地、稳定、自适应的优点,因此很受欢迎。实际上,包括 Java 在内的许多编程语言的排序库函数的实现都用到了插入排序。库函数的大致思路: +实际上,许多编程语言(例如 Java)的内置排序函数采用了插入排序,大致思路为:对于长数组,采用基于分治策略的排序算法,例如快速排序;对于短数组,直接使用插入排序。 -- 对于 **长数组**,采用基于分治的排序算法,例如「快速排序」,时间复杂度为 $O(n \log n)$ ; -- 对于 **短数组**,直接使用「插入排序」,时间复杂度为 $O(n^2)$ ; +虽然冒泡排序、选择排序和插入排序的时间复杂度都为 $O(n^2)$ ,但在实际情况中,**插入排序的使用频率显著高于冒泡排序和选择排序**,主要有以下原因。 -在数组较短时,复杂度中的常数项(即每轮中的单元操作数量)占主导作用,此时插入排序运行地更快。这个现象与「线性查找」和「二分查找」的情况类似。 +- 冒泡排序基于元素交换实现,需要借助一个临时变量,共涉及 3 个单元操作;插入排序基于元素赋值实现,仅需 1 个单元操作。因此,**冒泡排序的计算开销通常比插入排序更高**。 +- 选择排序在任何情况下的时间复杂度都为 $O(n^2)$ 。**如果给定一组部分有序的数据,插入排序通常比选择排序效率更高**。 +- 选择排序不稳定,无法应用于多级排序。 diff --git a/docs/chapter_sorting/intro_to_sort.assets/sorting_examples.png b/docs/chapter_sorting/intro_to_sort.assets/sorting_examples.png deleted file mode 100644 index 3423f9224d..0000000000 Binary files a/docs/chapter_sorting/intro_to_sort.assets/sorting_examples.png and /dev/null differ diff --git a/docs/chapter_sorting/intro_to_sort.md b/docs/chapter_sorting/intro_to_sort.md deleted file mode 100644 index c4d50c5355..0000000000 --- a/docs/chapter_sorting/intro_to_sort.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -comments: true ---- - -# 排序简介 - -「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。 - -- 待排序的列表的 **元素类型** 可以是整数、浮点数、字符、或字符串; -- 排序算法可以根据需要设定 **判断规则**,例如数字大小、字符 ASCII 码顺序、自定义规则; - -![sorting_examples](intro_to_sort.assets/sorting_examples.png) - -

Fig. 排序中的不同元素类型和判断规则

- -## 评价维度 - -排序算法主要可根据 **稳定性 、就地性 、自适应性 、比较类** 来分类。 - -### 稳定性 - -- 「稳定排序」在完成排序后,**不改变** 相等元素在数组中的相对顺序。 -- 「非稳定排序」在完成排序后,相等元素在数组中的相对位置 **可能被改变**。 - -假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。因此「稳定排序」是很好的特性,**在多级排序中是必须的**。 - -```shell -# 输入数据是按照姓名排序好的 -# (name, age) - ('A', 19) - ('B', 18) - ('C', 21) - ('D', 19) - ('E', 23) - -# 假设使用非稳定排序算法按年龄排序列表, -# 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变, -# 输入数据按姓名排序的性质丢失 - ('B', 18) - ('D', 19) - ('A', 19) - ('C', 21) - ('E', 23) -``` - -### 就地性 - -- 「原地排序」无需辅助数据,不使用额外空间; -- 「非原地排序」需要借助辅助数据,使用额外空间; - -「原地排序」不使用额外空间,可以节约内存;并且一般情况下,由于数据操作减少,原地排序的运行效率也更高。 - -### 自适应性 - -- 「自适应排序」的时间复杂度受输入数据影响,即最佳 / 最差 / 平均时间复杂度不相等。 -- 「非自适应排序」的时间复杂度恒定,与输入数据无关。 - -我们希望 **最差 = 平均**,即不希望排序算法的运行效率在某些输入数据下发生劣化。 - -### 比较类 - -- 「比较类排序」基于元素之间的比较算子(小于、相等、大于)来决定元素的相对顺序。 -- 「非比较类排序」不基于元素之间的比较算子来决定元素的相对顺序。 - -「比较类排序」的时间复杂度最优为 $O(n \log n)$ ;而「非比较类排序」可以达到 $O(n)$ 的时间复杂度,但通用性较差。 - -## 理想排序算法 - -- **运行地快**,即时间复杂度低; -- **稳定排序**,即排序后相等元素的相对位置不变化; -- **原地排序**,即运行中不使用额外的辅助空间; -- **正向自适应性**,即算法的运行效率不会在某些输入数据下发生劣化; - -然而,**没有排序算法同时具备以上所有特性**。排序算法的选型使用取决于具体的列表类型、列表长度、元素分布等因素。 diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png new file mode 100644 index 0000000000..7e71adce4e Binary files /dev/null and b/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_preview.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_preview.png deleted file mode 100644 index fa1844b71f..0000000000 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_preview.png and /dev/null differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png index 2250b178e8..34d567f0cb 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png index a2d944a0b9..c870f0466d 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png index 74d58b5049..4e8d1e8095 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png index 4fc818342f..c25844dca9 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png index f7b501d084..890a0e4977 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png index ee688f3a78..67a3d88080 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png index 18b54c1756..1aa7dcfca8 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png index 90d2a05e6b..c8d94c13bf 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png index 4db8c0c663..a25be5c344 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png differ diff --git a/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png b/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png index 49e845708b..129d4506fb 100644 Binary files a/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png and b/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png differ diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md old mode 100644 new mode 100755 index 5895fcb8ea..7e90e84d1e --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -1,422 +1,73 @@ ---- -comments: true ---- - # 归并排序 -「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段: - -1. **划分阶段**:通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题; -2. **合并阶段**:划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序; +归并排序(merge sort)是一种基于分治策略的排序算法,包含下图所示的“划分”和“合并”阶段。 -![merge_sort_preview](merge_sort.assets/merge_sort_preview.png) +1. **划分阶段**:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。 +2. **合并阶段**:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束。 -

Fig. 归并排序两阶段:划分与合并

+![归并排序的划分与合并阶段](merge_sort.assets/merge_sort_overview.png) ## 算法流程 -**「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组**,直至长度为 1 ; +如下图所示,“划分阶段”从顶至底递归地将数组从中点切分为两个子数组。 -1. 计算数组中点 `mid` ,递归划分左子数组(区间 `[left, mid]` )和右子数组(区间 `[mid + 1, right]` ); -2. 递归执行 `1.` 步骤,直至子数组区间长度为 1 时,终止递归划分; +1. 计算数组中点 `mid` ,递归划分左子数组(区间 `[left, mid]` )和右子数组(区间 `[mid + 1, right]` )。 +2. 递归执行步骤 `1.` ,直至子数组区间长度为 1 时终止。 -**「回溯合并」** 从底至顶地将左子数组和右子数组合并为一个 **有序数组** ; +“合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。 -需要注意,由于从长度为 1 的子数组开始合并,所以 **每个子数组都是有序的**。因此,合并任务本质是要 **将两个有序子数组合并为一个有序数组**。 +=== "<1>" + ![归并排序步骤](merge_sort.assets/merge_sort_step1.png) -=== "Step1" - ![merge_sort_step1](merge_sort.assets/merge_sort_step1.png) - -=== "Step2" +=== "<2>" ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) -=== "Step3" +=== "<3>" ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) -=== "Step4" +=== "<4>" ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) -=== "Step5" +=== "<5>" ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) -=== "Step6" +=== "<6>" ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) -=== "Step7" +=== "<7>" ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) -=== "Step8" +=== "<8>" ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) -=== "Step9" +=== "<9>" ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) -=== "Step10" +=== "<10>" ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) -观察发现,归并排序的递归顺序就是二叉树的「后序遍历」。 - -- **后序遍历**:先递归左子树、再递归右子树、最后处理根结点。 -- **归并排序**:先递归左子树、再递归右子树、最后处理合并。 - -=== "Java" - - ```java title="merge_sort.java" - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ - void merge(int[] nums, int left, int mid, int right) { - // 初始化辅助数组 - int[] tmp = Arrays.copyOfRange(nums, left, right + 1); - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else - nums[k] = tmp[j++]; - } - } - - /* 归并排序 */ - void mergeSort(int[] nums, int left, int right) { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 递归划分 - int mid = (left + right) / 2; // 计算数组中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 回溯合并 - merge(nums, left, mid, right); - } - ``` - -=== "C++" - - ```cpp title="merge_sort.cpp" - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ - void merge(vector& nums, int left, int mid, int right) { - // 初始化辅助数组 - vector tmp(nums.begin() + left, nums.begin() + right + 1); - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else - nums[k] = tmp[j++]; - } - } - - /* 归并排序 */ - void mergeSort(vector& nums, int left, int right) { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - int mid = (left + right) / 2; // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "Python" - - ```python title="merge_sort.py" - """ - 合并左子数组和右子数组 - 左子数组区间 [left, mid] - 右子数组区间 [mid + 1, right] - """ - def merge(nums, left, mid, right): - # 初始化辅助数组 借助 copy模块 - tmp = nums[left:right + 1] - # 左子数组的起始索引和结束索引 - left_start, left_end = left - left, mid - left - # 右子数组的起始索引和结束索引 - right_start, right_end = mid + 1 - left, right - left - # i, j 分别指向左子数组、右子数组的首元素 - i, j = left_start, right_start - # 通过覆盖原数组 nums 来合并左子数组和右子数组 - for k in range(left, right + 1): - # 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > left_end: - nums[k] = tmp[j] - j += 1 - # 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - elif j > right_end or tmp[i] <= tmp[j]: - nums[k] = tmp[i] - i += 1 - # 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else: - nums[k] = tmp[j] - j += 1 - - """ 归并排序 """ - def merge_sort(nums, left, right): - # 终止条件 - if left >= right: - return # 当子数组长度为 1 时终止递归 - # 划分阶段 - mid = (left + right) // 2 # 计算中点 - merge_sort(nums, left, mid) # 递归左子数组 - merge_sort(nums, mid + 1, right) # 递归右子数组 - # 合并阶段 - merge(nums, left, mid, right) - ``` - -=== "Go" - - ```go title="merge_sort.go" - /* - 合并左子数组和右子数组 - 左子数组区间 [left, mid] - 右子数组区间 [mid + 1, right] - */ - func merge(nums []int, left, mid, right int) { - // 初始化辅助数组 借助 copy 模块 - tmp := make([]int, right-left+1) - for i := left; i <= right; i++ { - tmp[i-left] = nums[i] - } - // 左子数组的起始索引和结束索引 - leftStart, leftEnd := left-left, mid-left - // 右子数组的起始索引和结束索引 - rightStart, rightEnd := mid+1-left, right-left - // i, j 分别指向左子数组、右子数组的首元素 - i, j := leftStart, rightStart - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for k := left; k <= right; k++ { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > leftEnd { - nums[k] = tmp[j] - j++ - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if j > rightEnd || tmp[i] <= tmp[j] { - nums[k] = tmp[i] - i++ - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - } else { - nums[k] = tmp[j] - j++ - } - } - } - - func mergeSort(nums []int, left, right int) { - // 终止条件 - if left >= right { - return - } - // 划分阶段 - mid := (left + right) / 2 - mergeSort(nums, left, mid) - mergeSort(nums, mid+1, right) - // 合并阶段 - merge(nums, left, mid, right) - } - ``` - -=== "JavaScript" - - ```js title="merge_sort.js" - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ - function merge(nums, left, mid, right) { - // 初始化辅助数组 - let tmp = nums.slice(left, right + 1); - // 左子数组的起始索引和结束索引 - let leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - let rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - let i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (let k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) { - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if (j > rightEnd || tmp[i] <= tmp[j]) { - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - } else { - nums[k] = tmp[j++]; - } - } - } - - /* 归并排序 */ - function mergeSort(nums, left, right) { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - let mid = Math.floor((left + right) / 2); // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "TypeScript" - - ```typescript title="merge_sort.ts" - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ - function merge(nums: number[], left: number, mid: number, right: number): void { - // 初始化辅助数组 - let tmp = nums.slice(left, right + 1); - // 左子数组的起始索引和结束索引 - let leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - let rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - let i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (let k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) { - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if (j > rightEnd || tmp[i] <= tmp[j]) { - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - } else { - nums[k] = tmp[j++]; - } - } - } - - /* 归并排序 */ - function mergeSort(nums: number[], left: number, right: number): void { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - let mid = Math.floor((left + right) / 2); // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "C" - - ```c title="merge_sort.c" - - ``` - -=== "C#" - - ```csharp title="merge_sort.cs" - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ - void merge(int[] nums, int left, int mid, int right) - { - // 初始化辅助数组 - int[] tmp = nums[left..(right + 1)]; - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) - { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else - nums[k] = tmp[j++]; - } - } - - /* 归并排序 */ - void mergeSort(int[] nums, int left, int right) - { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - int mid = (left + right) / 2; // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "Swift" - - ```swift title="merge_sort.swift" - - ``` - -下面重点解释一下合并方法 `merge()` 的流程: +观察发现,归并排序与二叉树后序遍历的递归顺序是一致的。 -1. 初始化一个辅助数组 `tmp` 暂存待合并区间 `[left, right]` 内的元素,后续通过覆盖原数组 `nums` 的元素来实现合并; -2. 初始化指针 `i` , `j` , `k` 分别指向左子数组、右子数组、原数组的首元素; -3. 循环判断 `tmp[i]` 和 `tmp[j]` 的大小,将较小的先覆盖至 `nums[k]` ,指针 `i` , `j` 根据判断结果交替前进(指针 `k` 也前进),直至两个子数组都遍历完,即可完成合并。 +- **后序遍历**:先递归左子树,再递归右子树,最后处理根节点。 +- **归并排序**:先递归左子数组,再递归右子数组,最后处理合并。 -合并方法 `merge()` 代码中的主要难点: +归并排序的实现如以下代码所示。请注意,`nums` 的待合并区间为 `[left, right]` ,而 `tmp` 的对应区间为 `[0, right - left]` 。 -- `nums` 的待合并区间为 `[left, right]` ,而因为 `tmp` 只复制了 `nums` 该区间元素,所以 `tmp` 对应区间为 `[0, right - left]` ,**需要特别注意代码中各个变量的含义**。 -- 判断 `tmp[i]` 和 `tmp[j]` 的大小的操作中,还 **需考虑当子数组遍历完成后的索引越界问题**,即 `i > leftEnd` 和 `j > rightEnd` 的情况,索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。 +```src +[file]{merge_sort}-[class]{}-[func]{merge_sort} +``` ## 算法特性 -- **时间复杂度 $O(n \log n)$** :划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 -- **空间复杂度 $O(n)$** :需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 -- **非原地排序**:辅助数组需要使用 $O(n)$ 额外空间。 -- **稳定排序**:在合并时可保证相等元素的相对位置不变。 -- **非自适应排序**:对于任意输入数据,归并排序的时间复杂度皆相同。 +- **时间复杂度为 $O(n \log n)$、非自适应排序**:划分产生高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,因此总体时间复杂度为 $O(n \log n)$ 。 +- **空间复杂度为 $O(n)$、非原地排序**:递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。合并操作需要借助辅助数组实现,使用 $O(n)$ 大小的额外空间。 +- **稳定排序**:在合并过程中,相等元素的次序保持不变。 -## 链表排序 * +## 链表排序 -归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,**空间复杂度可被优化至 $O(1)$** ,这是因为: +对于链表,归并排序相较于其他排序算法具有显著优势,**可以将链表排序任务的空间复杂度优化至 $O(1)$** 。 -- 由于链表可仅通过改变指针来实现结点增删,因此“将两个短有序链表合并为一个长有序链表”无需使用额外空间,即回溯合并阶段不用像排序数组一样建立辅助数组 `tmp` ; -- 通过使用「迭代」代替「递归划分」,可省去递归使用的栈帧空间; +- **划分阶段**:可以使用“迭代”替代“递归”来实现链表划分工作,从而省去递归使用的栈帧空间。 +- **合并阶段**:在链表中,节点增删操作仅需改变引用(指针)即可实现,因此合并阶段(将两个短有序链表合并为一个长有序链表)无须创建额外链表。 -> 详情参考:[148. 排序链表](https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/) +具体实现细节比较复杂,有兴趣的读者可以查阅相关资料进行学习。 diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png index 41f143c4e3..242ff0d0aa 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png index ebb3d43f83..27a829600d 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png index 8eb4f872c3..44a80612d1 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png index b3e1fd129c..41af011229 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png index 0fc46a4b96..721cd8db7d 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png index 808db7e2bf..546d67a58c 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png index cf96530219..44975cc827 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png index acb723b770..ad2014f8d2 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png b/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png index c07b457497..e226b88bed 100644 Binary files a/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png and b/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png differ diff --git a/docs/chapter_sorting/quick_sort.assets/quick_sort.png b/docs/chapter_sorting/quick_sort.assets/quick_sort.png deleted file mode 100644 index 7b71ecd36c..0000000000 Binary files a/docs/chapter_sorting/quick_sort.assets/quick_sort.png and /dev/null differ diff --git a/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png b/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png new file mode 100644 index 0000000000..2e82a4b8ec Binary files /dev/null and b/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png differ diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md old mode 100644 new mode 100755 index a712a1bffe..96be54bacd --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -1,760 +1,100 @@ ---- -comments: true ---- - # 快速排序 -「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。 +快速排序(quick sort)是一种基于分治策略的排序算法,运行高效,应用广泛。 -快速排序的核心操作为「哨兵划分」,其目标为:选取数组某个元素为 **基准数**,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为: +快速排序的核心操作是“哨兵划分”,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程如下图所示。 -1. 以数组最左端元素作为基准数,初始化两个指针 `i` , `j` 指向数组两端; -2. 设置一个循环,每轮中使用 `i` / `j` 分别寻找首个比基准数大 / 小的元素,并交换此两元素; -3. 不断循环步骤 `2.` ,直至 `i` , `j` 相遇时跳出,最终把基准数交换至两个子数组的分界线; +1. 选取数组最左端元素作为基准数,初始化两个指针 `i` 和 `j` 分别指向数组的两端。 +2. 设置一个循环,在每轮中使用 `i`(`j`)分别寻找第一个比基准数大(小)的元素,然后交换这两个元素。 +3. 循环执行步骤 `2.` ,直到 `i` 和 `j` 相遇时停止,最后将基准数交换至两个子数组的分界线。 -「哨兵划分」执行完毕后,原数组被划分成两个部分,即 **左子数组** 和 **右子数组**,且满足 **左子数组任意元素 < 基准数 < 右子数组任意元素**。因此,接下来我们只需要排序两个子数组即可。 +=== "<1>" + ![哨兵划分步骤](quick_sort.assets/pivot_division_step1.png) -=== "Step 1" - ![pivot_division_step1](quick_sort.assets/pivot_division_step1.png) -=== "Step 2" +=== "<2>" ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) -=== "Step 3" + +=== "<3>" ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) -=== "Step 4" + +=== "<4>" ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) -=== "Step 5" + +=== "<5>" ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) -=== "Step 6" + +=== "<6>" ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) -=== "Step 7" + +=== "<7>" ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) -=== "Step 8" + +=== "<8>" ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) -=== "Step 9" + +=== "<9>" ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) -

Fig. 哨兵划分

- -=== "Java" - - ``` java title="quick_sort.java" - /* 元素交换 */ - 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; // 返回基准数的索引 - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 元素交换 */ - void swap(vector& nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 哨兵划分 */ - int partition(vector& 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; // 返回基准数的索引 - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 哨兵划分 """ - def partition(self, nums, left, right): - # 以 nums[left] 作为基准数 - i, j = left, right - while i < j: - while i < j and nums[j] >= nums[left]: - j -= 1 # 从右向左找首个小于基准数的元素 - while i < j and nums[i] <= nums[left]: - i += 1 # 从左向右找首个大于基准数的元素 - # 元素交换 - nums[i], nums[j] = nums[j], nums[i] - # 将基准数交换至两子数组的分界线 - nums[i], nums[left] = nums[left], nums[i] - return i # 返回基准数的索引 - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 哨兵划分 */ - func partition(nums []int, left, right int) int { - // 以 nums[left] 作为基准数 - i, j := left, right - for i < j { - for i < j && nums[j] >= nums[left] { - j-- // 从右向左找首个小于基准数的元素 - } - for i < j && nums[i] <= nums[left] { - i++ // 从左向右找首个大于基准数的元素 - } - //元素交换 - nums[i], nums[j] = nums[j], nums[i] - } - // 将基准数交换至两子数组的分界线 - nums[i], nums[left] = nums[left], nums[i] - return i // 返回基准数的索引 - } - ``` - -=== "JavaScript" - - ``` js title="quick_sort.js" - /* 元素交换 */ - function swap(nums, i, j) { - let tmp = nums[i] - nums[i] = nums[j] - nums[j] = tmp - } - - /* 哨兵划分 */ - function partition(nums, left, right){ - // 以 nums[left] 作为基准数 - let i = left, j = right - while(i < j){ - while(i < j && nums[j] >= nums[left]){ - j -= 1 // 从右向左找首个小于基准数的元素 - } - while(i < j && nums[i] <= nums[left]){ - i += 1 // 从左向右找首个大于基准数的元素 - } - // 元素交换 - swap(nums, i, j) // 交换这两个元素 - } - swap(nums, i, left) // 将基准数交换至两子数组的分界线 - return i // 返回基准数的索引 - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 元素交换 */ - function swap(nums: number[], i: number, j: number): void { - let tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 哨兵划分 */ - function partition(nums: number[], left: number, right: number): number { - // 以 nums[left] 作为基准数 - let i = left, j = right; - while (i < j) { - while (i < j && nums[j] >= nums[left]) { - j -= 1; // 从右向左找首个小于基准数的元素 - } - while (i < j && nums[i] <= nums[left]) { - i += 1; // 从左向右找首个大于基准数的元素 - } - // 元素交换 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 元素交换 */ - 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; // 返回基准数的索引 - } - - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - - ``` - -!!! note "快速排序的分治思想" - - 哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。 +哨兵划分完成后,原数组被划分成三部分:左子数组、基准数、右子数组,且满足“左子数组任意元素 $\leq$ 基准数 $\leq$ 右子数组任意元素”。因此,我们接下来只需对这两个子数组进行排序。 + +!!! note "快速排序的分治策略" + + 哨兵划分的实质是将一个较长数组的排序问题简化为两个较短数组的排序问题。 + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{partition} +``` ## 算法流程 -1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组**; -2. 接下来,对 **左子数组** 和 **右子数组** 分别 **递归执行**「哨兵划分」…… -3. 直至子数组长度为 1 时 **终止递归**,即可完成对整个数组的排序; - -观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。 - -![quick_sort](quick_sort.assets/quick_sort.png) - -

Fig. 快速排序流程

- -=== "Java" - - ```java title="quick_sort.java" - /* 快速排序 */ - 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); - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 快速排序 */ - void quickSort(vector& 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); - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 快速排序 """ - def quick_sort(self, nums, left, right): - # 子数组长度为 1 时终止递归 - if left >= right: - return - # 哨兵划分 - pivot = self.partition(nums, left, right) - # 递归左子数组、右子数组 - self.quick_sort(nums, left, pivot - 1) - self.quick_sort(nums, pivot + 1, right) - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 快速排序 */ - func quickSort(nums []int, left, right int) { - // 子数组长度为 1 时终止递归 - if left >= right { - return - } - // 哨兵划分 - pivot := partition(nums, left, right) - // 递归左子数组、右子数组 - quickSort(nums, left, pivot-1) - quickSort(nums, pivot+1, right) - } - ``` - -=== "JavaScript" - - ```js title="quick_sort.js" - /* 快速排序 */ - function quickSort(nums, left, right){ - // 子数组长度为 1 时终止递归 - if(left >= right) return - // 哨兵划分 - const pivot = partition(nums, left, right) - // 递归左子数组、右子数组 - quick_sort(nums, left, pivot - 1) - quick_sort(nums, pivot + 1, right) - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 快速排序 */ - function quickSort(nums: number[], left: number, right: number): void { - // 子数组长度为 1 时终止递归 - if (left >= right) { - return; - } - // 哨兵划分 - const pivot = partition(nums, left, right); - // 递归左子数组、右子数组 - quickSort(nums, left, pivot - 1); - quickSort(nums, pivot + 1, right); - } - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 快速排序 */ - 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); - } - - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - - ``` +快速排序的整体流程如下图所示。 + +1. 首先,对原数组执行一次“哨兵划分”,得到未排序的左子数组和右子数组。 +2. 然后,对左子数组和右子数组分别递归执行“哨兵划分”。 +3. 持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序。 + +![快速排序流程](quick_sort.assets/quick_sort_overview.png) + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} +``` ## 算法特性 -**平均时间复杂度 $O(n \log n)$** :平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 +- **时间复杂度为 $O(n \log n)$、非自适应排序**:在平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。在最差情况下,每轮哨兵划分操作都将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ ,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 +- **空间复杂度为 $O(n)$、原地排序**:在输入数组完全倒序的情况下,达到最差递归深度 $n$ ,使用 $O(n)$ 栈帧空间。排序操作是在原数组上进行的,未借助额外数组。 +- **非稳定排序**:在哨兵划分的最后一步,基准数可能会被交换至相等元素的右侧。 -**最差时间复杂度 $O(n^2)$** :最差情况下,哨兵划分操作将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 +## 快速排序为什么快 -**空间复杂度 $O(n)$** :输入数组完全倒序下,达到最差递归深度 $n$ 。 +从名称上就能看出,快速排序在效率方面应该具有一定的优势。尽管快速排序的平均时间复杂度与“归并排序”和“堆排序”相同,但通常快速排序的效率更高,主要有以下原因。 -**原地排序**:只在递归中使用 $O(\log n)$ 大小的栈帧空间。 +- **出现最差情况的概率很低**:虽然快速排序的最差时间复杂度为 $O(n^2)$ ,没有归并排序稳定,但在绝大多数情况下,快速排序能在 $O(n \log n)$ 的时间复杂度下运行。 +- **缓存使用效率高**:在执行哨兵划分操作时,系统可将整个子数组加载到缓存,因此访问元素的效率较高。而像“堆排序”这类算法需要跳跃式访问元素,从而缺乏这一特性。 +- **复杂度的常数系数小**:在上述三种算法中,快速排序的比较、赋值、交换等操作的总数量最少。这与“插入排序”比“冒泡排序”更快的原因类似。 -**非稳定排序**:哨兵划分操作可能改变相等元素的相对位置。 +## 基准数优化 -**自适应排序**:最差情况下,时间复杂度劣化至 $O(n^2)$ 。 +**快速排序在某些输入下的时间效率可能降低**。举一个极端例子,假设输入数组是完全倒序的,由于我们选择最左端元素作为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,导致左子数组长度为 $n - 1$、右子数组长度为 $0$ 。如此递归下去,每轮哨兵划分后都有一个子数组的长度为 $0$ ,分治策略失效,快速排序退化为“冒泡排序”的近似形式。 -## 快排为什么快? +为了尽量避免这种情况发生,**我们可以优化哨兵划分中的基准数的选取策略**。例如,我们可以随机选取一个元素作为基准数。然而,如果运气不佳,每次都选到不理想的基准数,效率仍然不尽如人意。 -从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高**,这是因为: +需要注意的是,编程语言通常生成的是“伪随机数”。如果我们针对伪随机数序列构建一个特定的测试样例,那么快速排序的效率仍然可能劣化。 -- **出现最差情况的概率很低**:虽然快速排序的最差时间复杂度为 $O(n^2)$ ,不如归并排序,但绝大部分情况下,快速排序可以达到 $O(n \log n)$ 的复杂度。 -- **缓存使用效率高**:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 -- **复杂度的常数系数低**:在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 +为了进一步改进,我们可以在数组中选取三个候选元素(通常为数组的首、尾、中点元素),**并将这三个候选元素的中位数作为基准数**。这样一来,基准数“既不太小也不太大”的概率将大幅提升。当然,我们还可以选取更多候选元素,以进一步提高算法的稳健性。采用这种方法后,时间复杂度劣化至 $O(n^2)$ 的概率大大降低。 -## 基准数优化 +示例代码如下: -**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 - -为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数**。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。 - -进一步地,我们可以在数组中选取 3 个候选元素(一般为数组的首、尾、中点元素),**并将三个候选元素的中位数作为基准数**,这样基准数“既不大也不小”的概率就大大提升了。当然,如果数组很长的话,我们也可以选取更多候选元素,来进一步提升算法的稳健性。采取该方法后,时间复杂度劣化至 $O(n^2)$ 的概率极低。 - -=== "Java" - - ```java title="quick_sort.java" - /* 选取三个元素的中位数 */ - int medianThree(int[] nums, int left, int mid, int right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - int partition(int[] nums, int left, int right) { - // 选取三个候选元素的中位数 - int med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 选取三个元素的中位数 */ - int medianThree(vector& nums, int left, int mid, int right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - int partition(vector& nums, int left, int right) { - // 选取三个候选元素的中位数 - int med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 选取三个元素的中位数 """ - def median_three(self, nums, left, mid, right): - # 使用了异或操作来简化代码 - # 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if (nums[left] > nums[mid]) ^ (nums[left] > nums[right]): - return left - elif (nums[mid] < nums[left]) ^ (nums[mid] > nums[right]): - return mid - return right - - """ 哨兵划分(三数取中值) """ - def partition(self, nums, left, right): - # 以 nums[left] 作为基准数 - med = self.median_three(nums, left, (left + right) // 2, right) - # 将中位数交换至数组最左端 - nums[left], nums[med] = nums[med], nums[left] - # 以 nums[left] 作为基准数 - # 下同省略... - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 选取三个元素的中位数 */ - func medianThree(nums []int, left, mid, right int) int { - if (nums[left] > nums[mid]) != (nums[left] > nums[right]) { - return left - } else if (nums[mid] < nums[left]) != (nums[mid] > nums[right]) { - return mid - } - return right - } - - /* 哨兵划分(三数取中值)*/ - func partition(nums []int, left, right int) int { - // 以 nums[left] 作为基准数 - med := medianThree(nums, left, (left+right)/2, right) - // 将中位数交换至数组最左端 - nums[left], nums[med] = nums[med], nums[left] - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "JavaScript" - - ```js title="quick_sort.js" - /* 选取三个元素的中位数 */ - function medianThree(nums, left, mid, right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - function partition(nums, left, right) { - // 选取三个候选元素的中位数 - let med = medianThree(nums, left, Math.floor((left + right) / 2), right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 选取三个元素的中位数 */ - function medianThree(nums: number[], left: number, mid: number, right: number): number { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if (Number(nums[left] > nums[mid]) ^ Number(nums[left] > nums[right])) { - return left; - } else if (Number(nums[mid] < nums[left]) ^ Number(nums[mid] < nums[right])) { - return mid; - } else { - return right; - } - } - - /* 哨兵划分(三数取中值) */ - function partition(nums: number[], left: number, right: number): number { - // 选取三个候选元素的中位数 - let med = medianThree(nums, left, Math.floor((left + right) / 2), right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 选取三个元素的中位数 */ - int medianThree(int[] nums, int left, int mid, int right) - { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - int partition(int[] nums, int left, int right) - { - // 选取三个候选元素的中位数 - int med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - - ``` +```src +[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} +``` ## 尾递归优化 -**普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 - -为了避免栈帧空间的累积,我们可以在每轮哨兵排序完成后,判断两个子数组的长度大小,仅递归排序较短的子数组。由于较短的子数组长度不会超过 $\frac{n}{2}$ ,因此这样做能保证递归深度不超过 $\log n$ ,即最差空间复杂度被优化至 $O(\log n)$ 。 - -=== "Java" - - ```java title="quick_sort.java" - /* 快速排序(尾递归优化) */ - void quickSort(int[] nums, int left, int right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 快速排序(尾递归优化) */ - void quickSort(vector& nums, int left, int right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 快速排序(尾递归优化) """ - def quick_sort(self, nums, left, right): - # 子数组长度为 1 时终止 - while left < right: - # 哨兵划分操作 - pivot = self.partition(nums, left, right) - # 对两个子数组中较短的那个执行快排 - if pivot - left < right - pivot: - self.quick_sort(nums, left, pivot - 1) # 递归排序左子数组 - left = pivot + 1 # 剩余待排序区间为 [pivot + 1, right] - else: - self.quick_sort(nums, pivot + 1, right) # 递归排序右子数组 - right = pivot - 1 # 剩余待排序区间为 [left, pivot - 1] - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 快速排序(尾递归优化)*/ - func quickSort(nums []int, left, right int) { - // 子数组长度为 1 时终止 - for left < right { - // 哨兵划分操作 - pivot := partition(nums, left, right) - // 对两个子数组中较短的那个执行快排 - if pivot-left < right-pivot { - quickSort(nums, left, pivot-1) // 递归排序左子数组 - left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot+1, right) // 递归排序右子数组 - right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "JavaScript" - - ```js title="quick_sort.js" - /* 快速排序(尾递归优化) */ - function quickSort(nums, left, right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - let pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 快速排序(尾递归优化) */ - function quickSort(nums: number[], left: number, right: number): void { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - let pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 快速排序(尾递归优化) */ - void quickSort(int[] nums, int left, int right) - { - // 子数组长度为 1 时终止 - while (left < right) - { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) - { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } - else - { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - - ``` +**在某些输入下,快速排序可能占用空间较多**。以完全有序的输入数组为例,设递归中的子数组长度为 $m$ ,每轮哨兵划分操作都将产生长度为 $0$ 的左子数组和长度为 $m - 1$ 的右子数组,这意味着每一层递归调用减少的问题规模非常小(只减少一个元素),递归树的高度会达到 $n - 1$ ,此时需要占用 $O(n)$ 大小的栈帧空间。 + +为了防止栈帧空间的累积,我们可以在每轮哨兵排序完成后,比较两个子数组的长度,**仅对较短的子数组进行递归**。由于较短子数组的长度不会超过 $n / 2$ ,因此这种方法能确保递归深度不超过 $\log n$ ,从而将最差空间复杂度优化至 $O(\log n)$ 。代码如下所示: + +```src +[file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} +``` diff --git a/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png b/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png new file mode 100644 index 0000000000..e123cb73b3 Binary files /dev/null and b/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png differ diff --git a/docs/chapter_sorting/radix_sort.md b/docs/chapter_sorting/radix_sort.md new file mode 100644 index 0000000000..15af50b9b7 --- /dev/null +++ b/docs/chapter_sorting/radix_sort.md @@ -0,0 +1,41 @@ +# 基数排序 + +上一节介绍了计数排序,它适用于数据量 $n$ 较大但数据范围 $m$ 较小的情况。假设我们需要对 $n = 10^6$ 个学号进行排序,而学号是一个 $8$ 位数字,这意味着数据范围 $m = 10^8$ 非常大,使用计数排序需要分配大量内存空间,而基数排序可以避免这种情况。 + +基数排序(radix sort)的核心思想与计数排序一致,也通过统计个数来实现排序。在此基础上,基数排序利用数字各位之间的递进关系,依次对每一位进行排序,从而得到最终的排序结果。 + +## 算法流程 + +以学号数据为例,假设数字的最低位是第 $1$ 位,最高位是第 $8$ 位,基数排序的流程如下图所示。 + +1. 初始化位数 $k = 1$ 。 +2. 对学号的第 $k$ 位执行“计数排序”。完成后,数据会根据第 $k$ 位从小到大排序。 +3. 将 $k$ 增加 $1$ ,然后返回步骤 `2.` 继续迭代,直到所有位都排序完成后结束。 + +![基数排序算法流程](radix_sort.assets/radix_sort_overview.png) + +下面剖析代码实现。对于一个 $d$ 进制的数字 $x$ ,要获取其第 $k$ 位 $x_k$ ,可以使用以下计算公式: + +$$ +x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d +$$ + +其中 $\lfloor a \rfloor$ 表示对浮点数 $a$ 向下取整,而 $\bmod \: d$ 表示对 $d$ 取模(取余)。对于学号数据,$d = 10$ 且 $k \in [1, 8]$ 。 + +此外,我们需要小幅改动计数排序代码,使之可以根据数字的第 $k$ 位进行排序: + +```src +[file]{radix_sort}-[class]{}-[func]{radix_sort} +``` + +!!! question "为什么从最低位开始排序?" + + 在连续的排序轮次中,后一轮排序会覆盖前一轮排序的结果。举例来说,如果第一轮排序结果 $a < b$ ,而第二轮排序结果 $a > b$ ,那么第二轮的结果将取代第一轮的结果。由于数字的高位优先级高于低位,因此应该先排序低位再排序高位。 + +## 算法特性 + +相较于计数排序,基数排序适用于数值范围较大的情况,**但前提是数据必须可以表示为固定位数的格式,且位数不能过大**。例如,浮点数不适合使用基数排序,因为其位数 $k$ 过大,可能导致时间复杂度 $O(nk) \gg O(n^2)$ 。 + +- **时间复杂度为 $O(nk)$、非自适应排序**:设数据量为 $n$、数据为 $d$ 进制、最大位数为 $k$ ,则对某一位执行计数排序使用 $O(n + d)$ 时间,排序所有 $k$ 位使用 $O((n + d)k)$ 时间。通常情况下,$d$ 和 $k$ 都相对较小,时间复杂度趋向 $O(n)$ 。 +- **空间复杂度为 $O(n + d)$、非原地排序**:与计数排序相同,基数排序需要借助长度为 $n$ 和 $d$ 的数组 `res` 和 `counter` 。 +- **稳定排序**:当计数排序稳定时,基数排序也稳定;当计数排序不稳定时,基数排序无法保证得到正确的排序结果。 diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png new file mode 100644 index 0000000000..e3c1de0db1 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png new file mode 100644 index 0000000000..f540c6d545 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png new file mode 100644 index 0000000000..d99b293700 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png new file mode 100644 index 0000000000..9fd1494a42 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png new file mode 100644 index 0000000000..e895849253 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png new file mode 100644 index 0000000000..31a5db2daf Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png new file mode 100644 index 0000000000..ff5e1d719e Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png new file mode 100644 index 0000000000..e482839f71 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png new file mode 100644 index 0000000000..1f33c582b6 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png new file mode 100644 index 0000000000..647b0b7557 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png new file mode 100644 index 0000000000..0cf6653e79 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png differ diff --git a/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png b/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png new file mode 100644 index 0000000000..db74ca40b9 Binary files /dev/null and b/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png differ diff --git a/docs/chapter_sorting/selection_sort.md b/docs/chapter_sorting/selection_sort.md new file mode 100644 index 0000000000..80d3effc3a --- /dev/null +++ b/docs/chapter_sorting/selection_sort.md @@ -0,0 +1,58 @@ +# 选择排序 + +选择排序(selection sort)的工作原理非常简单:开启一个循环,每轮从未排序区间选择最小的元素,将其放到已排序区间的末尾。 + +设数组的长度为 $n$ ,选择排序的算法流程如下图所示。 + +1. 初始状态下,所有元素未排序,即未排序(索引)区间为 $[0, n-1]$ 。 +2. 选取区间 $[0, n-1]$ 中的最小元素,将其与索引 $0$ 处的元素交换。完成后,数组前 1 个元素已排序。 +3. 选取区间 $[1, n-1]$ 中的最小元素,将其与索引 $1$ 处的元素交换。完成后,数组前 2 个元素已排序。 +4. 以此类推。经过 $n - 1$ 轮选择与交换后,数组前 $n - 1$ 个元素已排序。 +5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。 + +=== "<1>" + ![选择排序步骤](selection_sort.assets/selection_sort_step1.png) + +=== "<2>" + ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) + +=== "<3>" + ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) + +=== "<4>" + ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) + +=== "<5>" + ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) + +=== "<6>" + ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) + +=== "<7>" + ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) + +=== "<8>" + ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) + +=== "<9>" + ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) + +=== "<10>" + ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) + +=== "<11>" + ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) + +在代码中,我们用 $k$ 来记录未排序区间内的最小元素: + +```src +[file]{selection_sort}-[class]{}-[func]{selection_sort} +``` + +## 算法特性 + +- **时间复杂度为 $O(n^2)$、非自适应排序**:外循环共 $n - 1$ 轮,第一轮的未排序区间长度为 $n$ ,最后一轮的未排序区间长度为 $2$ ,即各轮外循环分别包含 $n$、$n - 1$、$\dots$、$3$、$2$ 轮内循环,求和为 $\frac{(n - 1)(n + 2)}{2}$ 。 +- **空间复杂度为 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。 +- **非稳定排序**:如下图所示,元素 `nums[i]` 有可能被交换至与其相等的元素的右边,导致两者的相对顺序发生改变。 + +![选择排序非稳定示例](selection_sort.assets/selection_sort_instability.png) diff --git a/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png b/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png new file mode 100644 index 0000000000..aec33cb1f9 Binary files /dev/null and b/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png differ diff --git a/docs/chapter_sorting/sorting_algorithm.md b/docs/chapter_sorting/sorting_algorithm.md new file mode 100644 index 0000000000..ded41fd5ac --- /dev/null +++ b/docs/chapter_sorting/sorting_algorithm.md @@ -0,0 +1,46 @@ +# 排序算法 + +排序算法(sorting algorithm)用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用,因为有序数据通常能够被更高效地查找、分析和处理。 + +如下图所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求设定,如数字大小、字符 ASCII 码顺序或自定义规则。 + +![数据类型和判断规则示例](sorting_algorithm.assets/sorting_examples.png) + +## 评价维度 + +**运行效率**:我们期望排序算法的时间复杂度尽量低,且总体操作数量较少(时间复杂度中的常数项变小)。对于大数据量的情况,运行效率显得尤为重要。 + +**就地性**:顾名思义,原地排序通过在原数组上直接操作实现排序,无须借助额外的辅助数组,从而节省内存。通常情况下,原地排序的数据搬运操作较少,运行速度也更快。 + +**稳定性**:稳定排序在完成排序后,相等元素在数组中的相对顺序不发生改变。 + +稳定排序是多级排序场景的必要条件。假设我们有一个存储学生信息的表格,第 1 列和第 2 列分别是姓名和年龄。在这种情况下,非稳定排序可能导致输入数据的有序性丧失: + +```shell +# 输入数据是按照姓名排序好的 +# (name, age) + ('A', 19) + ('B', 18) + ('C', 21) + ('D', 19) + ('E', 23) + +# 假设使用非稳定排序算法按年龄排序列表, +# 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变, +# 输入数据按姓名排序的性质丢失 + ('B', 18) + ('D', 19) + ('A', 19) + ('C', 21) + ('E', 23) +``` + +**自适应性**:自适应排序能够利用输入数据已有的顺序信息来减少计算量,达到更优的时间效率。自适应排序算法的最佳时间复杂度通常优于平均时间复杂度。 + +**是否基于比较**:基于比较的排序依赖比较运算符($<$、$=$、$>$)来判断元素的相对顺序,从而排序整个数组,理论最优时间复杂度为 $O(n \log n)$ 。而非比较排序不使用比较运算符,时间复杂度可达 $O(n)$ ,但其通用性相对较差。 + +## 理想排序算法 + +**运行快、原地、稳定、自适应、通用性好**。显然,迄今为止尚未发现兼具以上所有特性的排序算法。因此,在选择排序算法时,需要根据具体的数据特点和问题需求来决定。 + +接下来,我们将共同学习各种排序算法,并基于上述评价维度对各个排序算法的优缺点进行分析。 diff --git a/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png b/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png new file mode 100644 index 0000000000..832dfb4dd0 Binary files /dev/null and b/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png differ diff --git a/docs/chapter_sorting/summary.md b/docs/chapter_sorting/summary.md index df0b7a7006..31edcb8b82 100644 --- a/docs/chapter_sorting/summary.md +++ b/docs/chapter_sorting/summary.md @@ -1,6 +1,47 @@ ---- -comments: true ---- - # 小结 +### 重点回顾 + +- 冒泡排序通过交换相邻元素来实现排序。通过添加一个标志位来实现提前返回,我们可以将冒泡排序的最佳时间复杂度优化到 $O(n)$ 。 +- 插入排序每轮将未排序区间内的元素插入到已排序区间的正确位置,从而完成排序。虽然插入排序的时间复杂度为 $O(n^2)$ ,但由于单元操作相对较少,因此在小数据量的排序任务中非常受欢迎。 +- 快速排序基于哨兵划分操作实现排序。在哨兵划分中,有可能每次都选取到最差的基准数,导致时间复杂度劣化至 $O(n^2)$ 。引入中位数基准数或随机基准数可以降低这种劣化的概率。尾递归方法可以有效地减少递归深度,将空间复杂度优化到 $O(\log n)$ 。 +- 归并排序包括划分和合并两个阶段,典型地体现了分治策略。在归并排序中,排序数组需要创建辅助数组,空间复杂度为 $O(n)$ ;然而排序链表的空间复杂度可以优化至 $O(1)$ 。 +- 桶排序包含三个步骤:数据分桶、桶内排序和合并结果。它同样体现了分治策略,适用于数据体量很大的情况。桶排序的关键在于对数据进行平均分配。 +- 计数排序是桶排序的一个特例,它通过统计数据出现的次数来实现排序。计数排序适用于数据量大但数据范围有限的情况,并且要求数据能够转换为正整数。 +- 基数排序通过逐位排序来实现数据排序,要求数据能够表示为固定位数的数字。 +- 总的来说,我们希望找到一种排序算法,具有高效率、稳定、原地以及自适应性等优点。然而,正如其他数据结构和算法一样,没有一种排序算法能够同时满足所有这些条件。在实际应用中,我们需要根据数据的特性来选择合适的排序算法。 +- 下图对比了主流排序算法的效率、稳定性、就地性和自适应性等。 + +![排序算法对比](summary.assets/sorting_algorithms_comparison.png) + +### Q & A + +**Q**:排序算法稳定性在什么情况下是必需的? + +在现实中,我们有可能基于对象的某个属性进行排序。例如,学生有姓名和身高两个属性,我们希望实现一个多级排序:先按照姓名进行排序,得到 `(A, 180) (B, 185) (C, 170) (D, 170)` ;再对身高进行排序。由于排序算法不稳定,因此可能得到 `(D, 170) (C, 170) (A, 180) (B, 185)` 。 + +可以发现,学生 D 和 C 的位置发生了交换,姓名的有序性被破坏了,而这是我们不希望看到的。 + +**Q**:哨兵划分中“从右往左查找”与“从左往右查找”的顺序可以交换吗? + +不行,当我们以最左端元素为基准数时,必须先“从右往左查找”再“从左往右查找”。这个结论有些反直觉,我们来剖析一下原因。 + +哨兵划分 `partition()` 的最后一步是交换 `nums[left]` 和 `nums[i]` 。完成交换后,基准数左边的元素都 `<=` 基准数,**这就要求最后一步交换前 `nums[left] >= nums[i]` 必须成立**。假设我们先“从左往右查找”,那么如果找不到比基准数更大的元素,**则会在 `i == j` 时跳出循环,此时可能 `nums[j] == nums[i] > nums[left]`**。也就是说,此时最后一步交换操作会把一个比基准数更大的元素交换至数组最左端,导致哨兵划分失败。 + +举个例子,给定数组 `[0, 0, 0, 0, 1]` ,如果先“从左向右查找”,哨兵划分后数组为 `[1, 0, 0, 0, 0]` ,这个结果是不正确的。 + +再深入思考一下,如果我们选择 `nums[right]` 为基准数,那么正好反过来,必须先“从左往右查找”。 + +**Q**:关于尾递归优化,为什么选短的数组能保证递归深度不超过 $\log n$ ? + +递归深度就是当前未返回的递归方法的数量。每轮哨兵划分我们将原数组划分为两个子数组。在尾递归优化后,向下递归的子数组长度最大为原数组长度的一半。假设最差情况,一直为一半长度,那么最终的递归深度就是 $\log n$ 。 + +回顾原始的快速排序,我们有可能会连续地递归长度较大的数组,最差情况下为 $n$、$n - 1$、$\dots$、$2$、$1$ ,递归深度为 $n$ 。尾递归优化可以避免这种情况出现。 + +**Q**:当数组中所有元素都相等时,快速排序的时间复杂度是 $O(n^2)$ 吗?该如何处理这种退化情况? + +是的。对于这种情况,可以考虑通过哨兵划分将数组划分为三个部分:小于、等于、大于基准数。仅向下递归小于和大于的两部分。在该方法下,输入元素全部相等的数组,仅一轮哨兵划分即可完成排序。 + +**Q**:桶排序的最差时间复杂度为什么是 $O(n^2)$ ? + +最差情况下,所有元素被分至同一个桶中。如果我们采用一个 $O(n^2)$ 算法来排序这些元素,则时间复杂度为 $O(n^2)$ 。 diff --git a/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png b/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png new file mode 100644 index 0000000000..ffbf7a0a85 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png b/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png new file mode 100644 index 0000000000..da42823573 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png b/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png new file mode 100644 index 0000000000..8a32f23d04 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png b/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png new file mode 100644 index 0000000000..21eb9c4536 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png b/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png new file mode 100644 index 0000000000..2d53a52b98 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/deque_operations.png b/docs/chapter_stack_and_queue/deque.assets/deque_operations.png index 242ea98c58..6fa08af9d9 100644 Binary files a/docs/chapter_stack_and_queue/deque.assets/deque_operations.png and b/docs/chapter_stack_and_queue/deque.assets/deque_operations.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png new file mode 100644 index 0000000000..d615020104 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png new file mode 100644 index 0000000000..779f3eb07f Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png new file mode 100644 index 0000000000..204e1808cb Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png new file mode 100644 index 0000000000..67c0a20e82 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png differ diff --git a/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png new file mode 100644 index 0000000000..7a4d416987 Binary files /dev/null and b/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png differ diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index 4671d5c9e8..a8c5da91a9 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -1,120 +1,139 @@ ---- -comments: true ---- - # 双向队列 -对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。 - -![deque_operations](deque.assets/deque_operations.png) +在队列中,我们仅能删除头部元素或在尾部添加元素。如下图所示,双向队列(double-ended queue)提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。 -

Fig. 双向队列的操作

+![双向队列的操作](deque.assets/deque_operations.png) ## 双向队列常用操作 -双向队列的常用操作见下表(方法命名以 Java 为例)。 +双向队列的常用操作如下表所示,具体的方法名称需要根据所使用的编程语言来确定。 + +

  双向队列操作效率

+ +| 方法名 | 描述 | 时间复杂度 | +| -------------- | ---------------- | ---------- | +| `push_first()` | 将元素添加至队首 | $O(1)$ | +| `push_last()` | 将元素添加至队尾 | $O(1)$ | +| `pop_first()` | 删除队首元素 | $O(1)$ | +| `pop_last()` | 删除队尾元素 | $O(1)$ | +| `peek_first()` | 访问队首元素 | $O(1)$ | +| `peek_last()` | 访问队尾元素 | $O(1)$ | + +同样地,我们可以直接使用编程语言中已实现的双向队列类: + +=== "Python" + + ```python title="deque.py" + from collections import deque + + # 初始化双向队列 + deq: deque[int] = deque() + + # 元素入队 + deq.append(2) # 添加至队尾 + deq.append(5) + deq.append(4) + deq.appendleft(3) # 添加至队首 + deq.appendleft(1) + + # 访问元素 + front: int = deq[0] # 队首元素 + rear: int = deq[-1] # 队尾元素 + + # 元素出队 + pop_front: int = deq.popleft() # 队首元素出队 + pop_rear: int = deq.pop() # 队尾元素出队 + + # 获取双向队列的长度 + size: int = len(deq) -

Table. 双向队列的常用操作

+ # 判断双向队列是否为空 + is_empty: bool = len(deq) == 0 + ``` + +=== "C++" + + ```cpp title="deque.cpp" + /* 初始化双向队列 */ + deque deque; + + /* 元素入队 */ + deque.push_back(2); // 添加至队尾 + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // 添加至队首 + deque.push_front(1); -
+ /* 访问元素 */ + int front = deque.front(); // 队首元素 + int back = deque.back(); // 队尾元素 -| 方法 | 描述 | 时间复杂度 | -| ------------ | ---------------- | ---------- | -| offerFirst() | 将元素添加至队首 | $O(1)$ | -| offerLast() | 将元素添加至队尾 | $O(1)$ | -| pollFirst() | 删除队首元素 | $O(1)$ | -| pollLast() | 删除队尾元素 | $O(1)$ | -| peekFirst() | 访问队首元素 | $O(1)$ | -| peekLast() | 访问队尾元素 | $O(1)$ | -| size() | 获取队列的长度 | $O(1)$ | -| isEmpty() | 判断队列是否为空 | $O(1)$ | + /* 元素出队 */ + deque.pop_front(); // 队首元素出队 + deque.pop_back(); // 队尾元素出队 -
+ /* 获取双向队列的长度 */ + int size = deque.size(); -相同地,我们可以直接使用编程语言实现好的双向队列类。 + /* 判断双向队列是否为空 */ + bool empty = deque.empty(); + ``` === "Java" ```java title="deque.java" /* 初始化双向队列 */ Deque deque = new LinkedList<>(); - + /* 元素入队 */ deque.offerLast(2); // 添加至队尾 deque.offerLast(5); deque.offerLast(4); deque.offerFirst(3); // 添加至队首 deque.offerFirst(1); - + /* 访问元素 */ int peekFirst = deque.peekFirst(); // 队首元素 int peekLast = deque.peekLast(); // 队尾元素 - + /* 元素出队 */ - int pollFirst = deque.pollFirst(); // 队首元素出队 - int pollLast = deque.pollLast(); // 队尾元素出队 - + int popFirst = deque.pollFirst(); // 队首元素出队 + int popLast = deque.pollLast(); // 队尾元素出队 + /* 获取双向队列的长度 */ int size = deque.size(); - + /* 判断双向队列是否为空 */ boolean isEmpty = deque.isEmpty(); ``` -=== "C++" +=== "C#" - ```cpp title="deque.cpp" + ```csharp title="deque.cs" /* 初始化双向队列 */ - deque deque; - + // 在 C# 中,将链表 LinkedList 看作双向队列来使用 + LinkedList deque = new(); + /* 元素入队 */ - deque.push_back(2); // 添加至队尾 - deque.push_back(5); - deque.push_back(4); - deque.push_front(3); // 添加至队首 - deque.push_front(1); - + deque.AddLast(2); // 添加至队尾 + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 添加至队首 + deque.AddFirst(1); + /* 访问元素 */ - int front = deque.front(); // 队首元素 - int back = deque.back(); // 队尾元素 - + int peekFirst = deque.First.Value; // 队首元素 + int peekLast = deque.Last.Value; // 队尾元素 + /* 元素出队 */ - deque.pop_front(); // 队首元素出队 - deque.pop_back(); // 队尾元素出队 - - /* 获取双向队列的长度 */ - int size = deque.size(); - - /* 判断双向队列是否为空 */ - bool empty = deque.empty(); - ``` + deque.RemoveFirst(); // 队首元素出队 + deque.RemoveLast(); // 队尾元素出队 -=== "Python" + /* 获取双向队列的长度 */ + int size = deque.Count; - ```python title="deque.py" - """ 初始化双向队列 """ - duque = deque() - - """ 元素入队 """ - duque.append(2) # 添加至队尾 - duque.append(5) - duque.append(4) - duque.appendleft(3) # 添加至队首 - duque.appendleft(1) - - """ 访问元素 """ - front = duque[0] # 队首元素 - rear = duque[-1] # 队尾元素 - - """ 元素出队 """ - pop_front = duque.popleft() # 队首元素出队 - pop_rear = duque.pop() # 队尾元素出队 - - """ 获取双向队列的长度 """ - size = len(duque) - - """ 判断双向队列是否为空 """ - is_empty = len(duque) == 0 + /* 判断双向队列是否为空 */ + bool isEmpty = deque.Count == 0; ``` === "Go" @@ -123,78 +142,317 @@ comments: true /* 初始化双向队列 */ // 在 Go 中,将 list 作为双向队列使用 deque := list.New() - + /* 元素入队 */ deque.PushBack(2) // 添加至队尾 deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) // 添加至队首 deque.PushFront(1) - + /* 访问元素 */ front := deque.Front() // 队首元素 rear := deque.Back() // 队尾元素 - + /* 元素出队 */ deque.Remove(front) // 队首元素出队 deque.Remove(rear) // 队尾元素出队 - + /* 获取双向队列的长度 */ size := deque.Len() - + /* 判断双向队列是否为空 */ isEmpty := deque.Len() == 0 ``` -=== "JavaScript" +=== "Swift" + + ```swift title="deque.swift" + /* 初始化双向队列 */ + // Swift 没有内置的双向队列类,可以把 Array 当作双向队列来使用 + var deque: [Int] = [] + + /* 元素入队 */ + deque.append(2) // 添加至队尾 + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) // 添加至队首 + deque.insert(1, at: 0) + + /* 访问元素 */ + let peekFirst = deque.first! // 队首元素 + let peekLast = deque.last! // 队尾元素 + + /* 元素出队 */ + // 使用 Array 模拟时 popFirst 的复杂度为 O(n) + let popFirst = deque.removeFirst() // 队首元素出队 + let popLast = deque.removeLast() // 队尾元素出队 - ```js title="deque.js" - + /* 获取双向队列的长度 */ + let size = deque.count + + /* 判断双向队列是否为空 */ + let isEmpty = deque.isEmpty ``` -=== "TypeScript" +=== "JS" + + ```javascript title="deque.js" + /* 初始化双向队列 */ + // JavaScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 + const deque = []; + + /* 元素入队 */ + deque.push(2); + deque.push(5); + deque.push(4); + // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) + deque.unshift(3); + deque.unshift(1); + + /* 访问元素 */ + const peekFirst = deque[0]; + const peekLast = deque[deque.length - 1]; + + /* 元素出队 */ + // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) + const popFront = deque.shift(); + const popBack = deque.pop(); + + /* 获取双向队列的长度 */ + const size = deque.length; + + /* 判断双向队列是否为空 */ + const isEmpty = size === 0; + ``` + +=== "TS" ```typescript title="deque.ts" - + /* 初始化双向队列 */ + // TypeScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 + const deque: number[] = []; + + /* 元素入队 */ + deque.push(2); + deque.push(5); + deque.push(4); + // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) + deque.unshift(3); + deque.unshift(1); + + /* 访问元素 */ + const peekFirst: number = deque[0]; + const peekLast: number = deque[deque.length - 1]; + + /* 元素出队 */ + // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) + const popFront: number = deque.shift() as number; + const popBack: number = deque.pop() as number; + + /* 获取双向队列的长度 */ + const size: number = deque.length; + + /* 判断双向队列是否为空 */ + const isEmpty: boolean = size === 0; + ``` + +=== "Dart" + + ```dart title="deque.dart" + /* 初始化双向队列 */ + // 在 Dart 中,Queue 被定义为双向队列 + Queue deque = Queue(); + + /* 元素入队 */ + deque.addLast(2); // 添加至队尾 + deque.addLast(5); + deque.addLast(4); + deque.addFirst(3); // 添加至队首 + deque.addFirst(1); + + /* 访问元素 */ + int peekFirst = deque.first; // 队首元素 + int peekLast = deque.last; // 队尾元素 + + /* 元素出队 */ + int popFirst = deque.removeFirst(); // 队首元素出队 + int popLast = deque.removeLast(); // 队尾元素出队 + + /* 获取双向队列的长度 */ + int size = deque.length; + + /* 判断双向队列是否为空 */ + bool isEmpty = deque.isEmpty; + ``` + +=== "Rust" + + ```rust title="deque.rs" + /* 初始化双向队列 */ + let mut deque: VecDeque = VecDeque::new(); + + /* 元素入队 */ + deque.push_back(2); // 添加至队尾 + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // 添加至队首 + deque.push_front(1); + + /* 访问元素 */ + if let Some(front) = deque.front() { // 队首元素 + } + if let Some(rear) = deque.back() { // 队尾元素 + } + + /* 元素出队 */ + if let Some(pop_front) = deque.pop_front() { // 队首元素出队 + } + if let Some(pop_rear) = deque.pop_back() { // 队尾元素出队 + } + + /* 获取双向队列的长度 */ + let size = deque.len(); + + /* 判断双向队列是否为空 */ + let is_empty = deque.is_empty(); ``` === "C" ```c title="deque.c" - + // C 未提供内置双向队列 ``` -=== "C#" +=== "Kotlin" - ```csharp title="deque.cs" + ```kotlin title="deque.kt" /* 初始化双向队列 */ - // 在 C# 中,将链表 LinkedList 看作双向队列来使用 - LinkedList deque = new LinkedList(); - + val deque = LinkedList() + /* 元素入队 */ - deque.AddLast(2); // 添加至队尾 - deque.AddLast(5); - deque.AddLast(4); - deque.AddFirst(3); // 添加至队首 - deque.AddFirst(1); - + deque.offerLast(2) // 添加至队尾 + deque.offerLast(5) + deque.offerLast(4) + deque.offerFirst(3) // 添加至队首 + deque.offerFirst(1) + /* 访问元素 */ - int peekFirst = deque.First.Value; // 队首元素 - int peekLast = deque.Last.Value; // 队尾元素 - + val peekFirst = deque.peekFirst() // 队首元素 + val peekLast = deque.peekLast() // 队尾元素 + /* 元素出队 */ - deque.RemoveFirst(); // 队首元素出队 - deque.RemoveLast(); // 队尾元素出队 - + val popFirst = deque.pollFirst() // 队首元素出队 + val popLast = deque.pollLast() // 队尾元素出队 + /* 获取双向队列的长度 */ - int size = deque.Count; - + val size = deque.size + /* 判断双向队列是否为空 */ - bool isEmpty = deque.Count == 0; + val isEmpty = deque.isEmpty() ``` -=== "Swift" +=== "Ruby" + + ```ruby title="deque.rb" + # 初始化双向队列 + # Ruby 没有内直的双端队列,只能把 Array 当作双端队列来使用 + deque = [] + + # 元素如队 + deque << 2 + deque << 5 + deque << 4 + # 请注意,由于是数组,Array#unshift 方法的时间复杂度为 O(n) + deque.unshift(3) + deque.unshift(1) + + # 访问元素 + peek_first = deque.first + peek_last = deque.last + + # 元素出队 + # 请注意,由于是数组, Array#shift 方法的时间复杂度为 O(n) + pop_front = deque.shift + pop_back = deque.pop + + # 获取双向队列的长度 + size = deque.length + + # 判断双向队列是否为空 + is_empty = size.zero? + ``` + +=== "Zig" + + ```zig title="deque.zig" - ```swift title="deque.swift" - ``` + +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 双向队列实现 * + +双向队列的实现与队列类似,可以选择链表或数组作为底层数据结构。 + +### 基于双向链表的实现 + +回顾上一节内容,我们使用普通单向链表来实现队列,因为它可以方便地删除头节点(对应出队操作)和在尾节点后添加新节点(对应入队操作)。 + +对于双向队列而言,头部和尾部都可以执行入队和出队操作。换句话说,双向队列需要实现另一个对称方向的操作。为此,我们采用“双向链表”作为双向队列的底层数据结构。 + +如下图所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。 + +=== "LinkedListDeque" + ![基于链表实现双向队列的入队出队操作](deque.assets/linkedlist_deque_step1.png) + +=== "push_last()" + ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) + +=== "push_first()" + ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) + +=== "pop_last()" + ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) + +=== "pop_first()" + ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) + +实现代码如下所示: + +```src +[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} +``` + +### 基于数组的实现 + +如下图所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。 + +=== "ArrayDeque" + ![基于数组实现双向队列的入队出队操作](deque.assets/array_deque_step1.png) + +=== "push_last()" + ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) + +=== "push_first()" + ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) + +=== "pop_last()" + ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) + +=== "pop_first()" + ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) + +在队列的实现基础上,仅需增加“队首入队”和“队尾出队”的方法: + +```src +[file]{array_deque}-[class]{array_deque}-[func]{} +``` + +## 双向队列应用 + +双向队列兼具栈与队列的逻辑,**因此它可以实现这两者的所有应用场景,同时提供更高的自由度**。 + +我们知道,软件的“撤销”功能通常使用栈来实现:系统将每次更改操作 `push` 到栈中,然后通过 `pop` 实现撤销。然而,考虑到系统资源的限制,软件通常会限制撤销的步数(例如仅允许保存 $50$ 步)。当栈的长度超过 $50$ 时,软件需要在栈底(队首)执行删除操作。**但栈无法实现该功能,此时就需要使用双向队列来替代栈**。请注意,“撤销”的核心逻辑仍然遵循栈的先入后出原则,只是双向队列能够更加灵活地实现一些额外逻辑。 diff --git a/docs/chapter_stack_and_queue/index.md b/docs/chapter_stack_and_queue/index.md new file mode 100644 index 0000000000..ec053f82c2 --- /dev/null +++ b/docs/chapter_stack_and_queue/index.md @@ -0,0 +1,9 @@ +# 栈与队列 + +![栈与队列](../assets/covers/chapter_stack_and_queue.jpg) + +!!! abstract + + 栈如同叠猫猫,而队列就像猫猫排队。 + + 两者分别代表先入后出和先入先出的逻辑关系。 diff --git a/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png b/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png new file mode 100644 index 0000000000..2c6b92a65f Binary files /dev/null and b/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png differ diff --git a/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png b/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png new file mode 100644 index 0000000000..453238f2c3 Binary files /dev/null and b/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png differ diff --git a/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png b/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png new file mode 100644 index 0000000000..7e97aca29d Binary files /dev/null and b/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png differ diff --git a/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png b/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png new file mode 100644 index 0000000000..7cff9b1b9a Binary files /dev/null and b/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png differ diff --git a/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png b/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png new file mode 100644 index 0000000000..a53b9f44dc Binary files /dev/null and b/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png differ diff --git a/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png b/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png new file mode 100644 index 0000000000..6a43121f67 Binary files /dev/null and b/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png differ diff --git a/docs/chapter_stack_and_queue/queue.assets/queue_operations.png b/docs/chapter_stack_and_queue/queue.assets/queue_operations.png index b165cd10d0..87dc2b7ede 100644 Binary files a/docs/chapter_stack_and_queue/queue.assets/queue_operations.png and b/docs/chapter_stack_and_queue/queue.assets/queue_operations.png differ diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md old mode 100644 new mode 100755 index d70b3e528a..5c695b8ddc --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -1,115 +1,131 @@ ---- -comments: true ---- - # 队列 -「队列 Queue」是一种遵循「先入先出 first in, first out」数据操作规则的线性数据结构。顾名思义,队列模拟的是排队现象,即外面的人不断加入队列尾部,而处于队列头部的人不断地离开。 - -我们将队列头部称为「队首」,队列尾部称为「队尾」,将把元素加入队尾的操作称为「入队」,删除队首元素的操作称为「出队」。 +队列(queue)是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开。 -![queue_operations](queue.assets/queue_operations.png) +如下图所示,我们将队列头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。 -

Fig. 队列的先入先出特性

+![队列的先入先出规则](queue.assets/queue_operations.png) ## 队列常用操作 -队列的常用操作见下表(方法命名以 Java 为例)。 +队列的常见操作如下表所示。需要注意的是,不同编程语言的方法名称可能会有所不同。我们在此采用与栈相同的方法命名。 + +

  队列操作效率

+ +| 方法名 | 描述 | 时间复杂度 | +| -------- | ---------------------------- | ---------- | +| `push()` | 元素入队,即将元素添加至队尾 | $O(1)$ | +| `pop()` | 队首元素出队 | $O(1)$ | +| `peek()` | 访问队首元素 | $O(1)$ | + +我们可以直接使用编程语言中现成的队列类: + +=== "Python" + + ```python title="queue.py" + from collections import deque + + # 初始化队列 + # 在 Python 中,我们一般将双向队列类 deque 当作队列使用 + # 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不推荐 + que: deque[int] = deque() + + # 元素入队 + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + + # 访问队首元素 + front: int = que[0] + + # 元素出队 + pop: int = que.popleft() + + # 获取队列的长度 + size: int = len(que) + + # 判断队列是否为空 + is_empty: bool = len(que) == 0 + ``` + +=== "C++" -

Table. 队列的常用操作

+ ```cpp title="queue.cpp" + /* 初始化队列 */ + queue queue; + + /* 元素入队 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); -
+ /* 访问队首元素 */ + int front = queue.front(); -| 方法 | 描述 | 时间复杂度 | -| --------- | ---------------------------- | ---------- | -| offer() | 元素入队,即将元素添加至队尾 | $O(1)$ | -| poll() | 队首元素出队 | $O(1)$ | -| front() | 访问队首元素 | $O(1)$ | -| size() | 获取队列的长度 | $O(1)$ | -| isEmpty() | 判断队列是否为空 | $O(1)$ | + /* 元素出队 */ + queue.pop(); -
+ /* 获取队列的长度 */ + int size = queue.size(); -我们可以直接使用编程语言实现好的队列类。 + /* 判断队列是否为空 */ + bool empty = queue.empty(); + ``` === "Java" ```java title="queue.java" /* 初始化队列 */ Queue queue = new LinkedList<>(); - + /* 元素入队 */ queue.offer(1); queue.offer(3); queue.offer(2); queue.offer(5); queue.offer(4); - + /* 访问队首元素 */ int peek = queue.peek(); - + /* 元素出队 */ - int poll = queue.poll(); - + int pop = queue.poll(); + /* 获取队列的长度 */ int size = queue.size(); - + /* 判断队列是否为空 */ boolean isEmpty = queue.isEmpty(); ``` -=== "C++" +=== "C#" - ```cpp title="queue.cpp" + ```csharp title="queue.cs" /* 初始化队列 */ - queue queue; - + Queue queue = new(); + /* 元素入队 */ - queue.push(1); - queue.push(3); - queue.push(2); - queue.push(5); - queue.push(4); - + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + /* 访问队首元素 */ - int front = queue.front(); - + int peek = queue.Peek(); + /* 元素出队 */ - queue.pop(); - - /* 获取队列的长度 */ - int size = queue.size(); - - /* 判断队列是否为空 */ - bool empty = queue.empty(); - ``` + int pop = queue.Dequeue(); -=== "Python" + /* 获取队列的长度 */ + int size = queue.Count; - ```python title="queue.py" - """ 初始化队列 """ - # 在 Python 中,我们一般将双向队列类 deque 看作队列使用 - # 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议 - que = collections.deque() - - """ 元素入队 """ - que.append(1) - que.append(3) - que.append(2) - que.append(5) - que.append(4) - - """ 访问队首元素 """ - front = que[0]; - - """ 元素出队 """ - pop = que.popleft() - - """ 获取队列的长度 """ - size = len(que) - - """ 判断队列是否为空 """ - is_empty = len(que) == 0 + /* 判断队列是否为空 */ + bool isEmpty = queue.Count == 0; ``` === "Go" @@ -118,1063 +134,296 @@ comments: true /* 初始化队列 */ // 在 Go 中,将 list 作为队列来使用 queue := list.New() - + /* 元素入队 */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) - + /* 访问队首元素 */ peek := queue.Front() - + /* 元素出队 */ - poll := queue.Front() - queue.Remove(poll) - + pop := queue.Front() + queue.Remove(pop) + /* 获取队列的长度 */ size := queue.Len() - + /* 判断队列是否为空 */ isEmpty := queue.Len() == 0 ``` -=== "JavaScript" +=== "Swift" - ```js title="queue.js" + ```swift title="queue.swift" + /* 初始化队列 */ + // Swift 没有内置的队列类,可以把 Array 当作队列来使用 + var queue: [Int] = [] + + /* 元素入队 */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + + /* 访问队首元素 */ + let peek = queue.first! + + /* 元素出队 */ + // 由于是数组,因此 removeFirst 的复杂度为 O(n) + let pool = queue.removeFirst() + + /* 获取队列的长度 */ + let size = queue.count + + /* 判断队列是否为空 */ + let isEmpty = queue.isEmpty + ``` + +=== "JS" + + ```javascript title="queue.js" /* 初始化队列 */ // JavaScript 没有内置的队列,可以把 Array 当作队列来使用 const queue = []; - + /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); - + /* 访问队首元素 */ const peek = queue[0]; - + /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) - const poll = queue.shift(); - + const pop = queue.shift(); + /* 获取队列的长度 */ const size = queue.length; - + /* 判断队列是否为空 */ const empty = queue.length === 0; ``` -=== "TypeScript" +=== "TS" ```typescript title="queue.ts" /* 初始化队列 */ - // TypeScript 没有内置的队列,可以把 Array 当作队列来使用 + // TypeScript 没有内置的队列,可以把 Array 当作队列来使用 const queue: number[] = []; - + /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); - + /* 访问队首元素 */ const peek = queue[0]; - + /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) - const poll = queue.shift(); - + const pop = queue.shift(); + /* 获取队列的长度 */ const size = queue.length; - + /* 判断队列是否为空 */ const empty = queue.length === 0; ``` -=== "C" +=== "Dart" - ```c title="queue.c" - - ``` - -=== "C#" - - ```csharp title="queue.cs" + ```dart title="queue.dart" /* 初始化队列 */ - Queue queue = new(); - - /* 元素入队 */ - queue.Enqueue(1); - queue.Enqueue(3); - queue.Enqueue(2); - queue.Enqueue(5); - queue.Enqueue(4); - - /* 访问队首元素 */ - int peek = queue.Peek(); - - /* 元素出队 */ - int poll = queue.Dequeue(); - - /* 获取队列的长度 */ - int size = queue.Count(); - - /* 判断队列是否为空 */ - bool isEmpty = queue.Count() == 0; - ``` - -=== "Swift" - - ```swift title="queue.swift" - /* 初始化队列 */ - // Swift 没有内置的队列类,可以把 Array 当作队列来使用 - var queue: [Int] = [] + // 在 Dart 中,队列类 Qeque 是双向队列,也可作为队列使用 + Queue queue = Queue(); /* 元素入队 */ - queue.append(1) - queue.append(3) - queue.append(2) - queue.append(5) - queue.append(4) + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); /* 访问队首元素 */ - let peek = queue.first! + int peek = queue.first; /* 元素出队 */ - let pool = queue.removeFirst() + int pop = queue.removeFirst(); /* 获取队列的长度 */ - let size = queue.count + int size = queue.length; /* 判断队列是否为空 */ - let isEmpty = queue.isEmpty + bool isEmpty = queue.isEmpty; ``` -## 队列实现 - -队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。 +=== "Rust" -### 基于链表的实现 + ```rust title="queue.rs" + /* 初始化双向队列 */ + // 在 Rust 中使用双向队列作为普通队列来使用 + let mut deque: VecDeque = VecDeque::new(); -我们将链表的「头结点」和「尾结点」分别看作是队首和队尾,并规定队尾只可添加结点,队首只可删除结点。 + /* 元素入队 */ + deque.push_back(1); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); -=== "Java" + /* 访问队首元素 */ + if let Some(front) = deque.front() { + } - ```java title="linkedlist_queue.java" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private ListNode front, rear; // 头结点 front ,尾结点 rear - private int queSize = 0; - - public LinkedListQueue() { - front = null; - rear = null; - } - /* 获取队列的长度 */ - public int size() { - return queSize; - } - /* 判断队列是否为空 */ - public boolean isEmpty() { - return size() == 0; - } - /* 入队 */ - public void offer(int num) { - // 尾结点后添加 num - ListNode node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == null) { - front = node; - rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } else { - rear.next = node; - rear = node; - } - queSize++; - } - /* 出队 */ - public int poll() { - int num = peek(); - // 删除头结点 - front = front.next; - queSize--; - return num; - } - /* 访问队首元素 */ - public int peek() { - if (size() == 0) - throw new EmptyStackException(); - return front.val; - } + /* 元素出队 */ + if let Some(pop) = deque.pop_front() { } - ``` -=== "C++" + /* 获取队列的长度 */ + let size = deque.len(); - ```cpp title="linkedlist_queue.cpp" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private: - ListNode *front, *rear; // 头结点 front ,尾结点 rear - int queSize; - - public: - LinkedListQueue() { - front = nullptr; - rear = nullptr; - queSize = 0; - } - /* 获取队列的长度 */ - int size() { - return queSize; - } - /* 判断队列是否为空 */ - bool empty() { - return queSize == 0; - } - /* 入队 */ - void offer(int num) { - // 尾结点后添加 num - ListNode* node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == nullptr) { - front = node; - rear = node; - } - // 如果队列不为空,则将该结点添加到尾结点后 - else { - rear->next = node; - rear = node; - } - queSize++; - } - /* 出队 */ - void poll() { - int num = peek(); - // 删除头结点 - ListNode *tmp = front; - front = front->next; - // 释放内存 - delete tmp; - queSize--; - } - /* 访问队首元素 */ - int peek() { - if (size() == 0) - throw out_of_range("队列为空"); - return front->val; - } - }; + /* 判断队列是否为空 */ + let is_empty = deque.is_empty(); ``` -=== "Python" +=== "C" - ```python title="linkedlist_queue.py" - """ 基于链表实现的队列 """ - class LinkedListQueue: - def __init__(self): - self.__front = None # 头结点 front - self.__rear = None # 尾结点 rear - self.__size = 0 - - """ 获取队列的长度 """ - def size(self): - return self.__size - - """ 判断队列是否为空 """ - def is_empty(self): - return not self.__front - - """ 入队 """ - def push(self, num): - # 尾结点后添加 num - node = ListNode(num) - # 如果队列为空,则令头、尾结点都指向该结点 - if self.__front == 0: - self.__front = node - self.__rear = node - # 如果队列不为空,则将该结点添加到尾结点后 - else: - self.__rear.next = node - self.__rear = node - self.__size += 1 - - """ 出队 """ - def poll(self): - num = self.peek() - # 删除头结点 - self.__front = self.__front.next - self.__size -= 1 - return num - - """ 访问队首元素 """ - def peek(self): - if self.size() == 0: - print("队列为空") - return False - return self.__front.val + ```c title="queue.c" + // C 未提供内置队列 ``` -=== "Go" - - ```go title="linkedlist_queue.go" - /* 基于链表实现的队列 */ - type linkedListQueue struct { - // 使用内置包 list 来实现队列 - data *list.List - } - - // newLinkedListQueue 初始化链表 - func newLinkedListQueue() *linkedListQueue { - return &linkedListQueue{ - data: list.New(), - } - } +=== "Kotlin" - // offer 入队 - func (s *linkedListQueue) offer(value any) { - s.data.PushBack(value) - } + ```kotlin title="queue.kt" + /* 初始化队列 */ + val queue = LinkedList() - // poll 出队 - func (s *linkedListQueue) poll() any { - if s.isEmpty() { - return nil - } - e := s.data.Front() - s.data.Remove(e) - return e.Value - } + /* 元素入队 */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) - // peek 访问队首元素 - func (s *linkedListQueue) peek() any { - if s.isEmpty() { - return nil - } - e := s.data.Front() - return e.Value - } + /* 访问队首元素 */ + val peek = queue.peek() - // size 获取队列的长度 - func (s *linkedListQueue) size() int { - return s.data.Len() - } + /* 元素出队 */ + val pop = queue.poll() - // isEmpty 判断队列是否为空 - func (s *linkedListQueue) isEmpty() bool { - return s.data.Len() == 0 - } - ``` + /* 获取队列的长度 */ + val size = queue.size -=== "JavaScript" - - ```js title="linkedlist_queue.js" - /* 基于链表实现的队列 */ - class LinkedListQueue { - #front; // 头结点 #front - #rear; // 尾结点 #rear - #queSize = 0; - constructor() { - this.#front = null; - this.#rear = null; - } - /* 获取队列的长度 */ - get size() { - return this.#queSize; - } - /* 判断队列是否为空 */ - isEmpty() { - return this.size === 0; - } - /* 入队 */ - offer(num) { - // 尾结点后添加 num - const node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (!this.#front) { - this.#front = node; - this.#rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } else { - this.#rear.next = node; - this.#rear = node; - } - this.#queSize++; - } - /* 出队 */ - poll() { - const num = this.peek(); - // 删除头结点 - this.#front = this.#front.next; - this.#queSize--; - return num; - } - /* 访问队首元素 */ - peek() { - if (this.size === 0) - throw new Error("队列为空"); - return this.#front.val; - } - } + /* 判断队列是否为空 */ + val isEmpty = queue.isEmpty() ``` -=== "TypeScript" - - ```typescript title="linkedlist_queue.ts" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private front: ListNode | null; // 头结点 front - private rear: ListNode | null; // 尾结点 rear - private queSize: number = 0; - constructor() { - this.front = null; - this.rear = null; - } - /* 获取队列的长度 */ - get size(): number { - return this.queSize; - } - /* 判断队列是否为空 */ - isEmpty(): boolean { - return this.size === 0; - } - /* 入队 */ - offer(num: number): void { - // 尾结点后添加 num - const node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (!this.front) { - this.front = node; - this.rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } else { - this.rear!.next = node; - this.rear = node; - } - this.queSize++; - } - /* 出队 */ - poll(): number { - const num = this.peek(); - if (!this.front) - throw new Error("队列为空") - // 删除头结点 - this.front = this.front.next; - this.queSize--; - return num; - } - /* 访问队首元素 */ - peek(): number { - if (this.size === 0) - throw new Error("队列为空"); - return this.front!.val; - } - } - ``` +=== "Ruby" -=== "C" + ```ruby title="queue.rb" + # 初始化队列 + # Ruby 内置的队列(Thread::Queue) 没有 peek 和遍历方法,可以把 Array 当作队列来使用 + queue = [] - ```c title="linkedlist_queue.c" - - ``` + # 元素入队 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) -=== "C#" + # 访问队列元素 + peek = queue.first - ```csharp title="linkedlist_queue.cs" - /* 基于链表实现的队列 */ - class LinkedListQueue - { - private ListNode? front, rear; // 头结点 front ,尾结点 rear - private int queSize = 0; - public LinkedListQueue() - { - front = null; - rear = null; - } - /* 获取队列的长度 */ - public int size() - { - return queSize; - } - /* 判断队列是否为空 */ - public bool isEmpty() - { - return size() == 0; - } - /* 入队 */ - public void offer(int num) - { - // 尾结点后添加 num - ListNode node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == null) - { - front = node; - rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } - else if (rear != null) - { - rear.next = node; - rear = node; - } - queSize++; - } - /* 出队 */ - public int poll() - { - int num = peek(); - // 删除头结点 - front = front?.next; - queSize--; - return num; - } - /* 访问队首元素 */ - public int peek() - { - if (size() == 0 || front == null) - throw new Exception(); - return front.val; - } - } - ``` + # 元素出队 + # 清注意,由于是数组,Array#shift 方法时间复杂度为 O(n) + pop = queue.shift -=== "Swift" + # 获取队列的长度 + size = queue.length - ```swift title="linkedlist_queue.swift" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private var front: ListNode? // 头结点 - private var rear: ListNode? // 尾结点 - private var _size = 0 - - init() {} - - /* 获取队列的长度 */ - func size() -> Int { - _size - } - - /* 判断队列是否为空 */ - func isEmpty() -> Bool { - size() == 0 - } - - /* 入队 */ - func offer(num: Int) { - // 尾结点后添加 num - let node = ListNode(x: num) - // 如果队列为空,则令头、尾结点都指向该结点 - if front == nil { - front = node - rear = node - } - // 如果队列不为空,则将该结点添加到尾结点后 - else { - rear?.next = node - rear = node - } - _size += 1 - } - - /* 出队 */ - @discardableResult - func poll() -> Int { - let num = peek() - // 删除头结点 - front = front?.next - _size -= 1 - return num - } - - /* 访问队首元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("队列为空") - } - return front!.val - } - } + # 判断队列是否为空 + is_empty = queue.empty? ``` -### 基于数组的实现 +=== "Zig" -数组的删除首元素的时间复杂度为 $O(n)$ ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 `front` , `rear` 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 `front` / `rear` 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 $O(1)$ 。 + ```zig title="queue.zig" -还有一个问题,在入队与出队的过程中,两个指针都在向后移动,而到达尾部后则无法继续移动了。为了解决此问题,我们可以采取一个取巧方案,即将数组看作是“环形”的。具体做法是规定指针越过数组尾部后,再次回到头部接续遍历,这样相当于使数组“首尾相连”了。 + ``` -为了适应环形数组的设定,获取长度 `size()` 、入队 `offer()` 、出队 `poll()` 方法都需要做相应的取余操作处理,使得当尾指针绕回数组头部时,仍然可以正确处理操作。 +??? pythontutor "可视化运行" -基于数组实现的队列有一个缺点,即长度不可变。但这点我们可以通过动态数组来解决,有兴趣的同学可以自行实现。 + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false -=== "Java" +## 队列实现 - ```java title="array_queue.java" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private int[] nums; // 用于存储队列元素的数组 - private int front = 0; // 头指针,指向队首 - private int rear = 0; // 尾指针,指向队尾 + 1 - - public ArrayQueue(int capacity) { - // 初始化数组 - nums = new int[capacity]; - } - /* 获取队列的容量 */ - public int capacity() { - return nums.length; - } - /* 获取队列的长度 */ - public int size() { - int capacity = capacity(); - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity + rear - front) % capacity; - } - /* 判断队列是否为空 */ - public boolean isEmpty() { - return rear - front == 0; - } - /* 入队 */ - public void offer(int num) { - if (size() == capacity()) { - System.out.println("队列已满"); - return; - } - // 尾结点后添加 num - nums[rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity(); - } - /* 出队 */ - public int poll() { - int num = peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - return num; - } - /* 访问队首元素 */ - public int peek() { - if (isEmpty()) - throw new EmptyStackException(); - return nums[front]; - } - } - ``` +为了实现队列,我们需要一种数据结构,可以在一端添加元素,并在另一端删除元素,链表和数组都符合要求。 -=== "C++" +### 基于链表的实现 - ```cpp title="array_queue.cpp" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private: - int *nums; // 用于存储队列元素的数组 - int cap; // 队列容量 - int front = 0; // 头指针,指向队首 - int rear = 0; // 尾指针,指向队尾 + 1 - - public: - ArrayQueue(int capacity) { - // 初始化数组 - cap = capacity; - nums = new int[capacity]; - } - /* 获取队列的容量 */ - int capacity() { - return cap; - } - /* 获取队列的长度 */ - int size() { - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity() + rear - front) % capacity(); - } - /* 判断队列是否为空 */ - bool empty() { - return rear - front == 0; - } - /* 入队 */ - void offer(int num) { - if (size() == capacity()) { - cout << "队列已满" << endl; - return; - } - // 尾结点后添加 num - nums[rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity(); - } - /* 出队 */ - void poll() { - int num = peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - } - /* 访问队首元素 */ - int peek() { - if (empty()) - throw out_of_range("队列为空"); - return nums[front]; - } - }; - ``` +如下图所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。 -=== "Python" +=== "LinkedListQueue" + ![基于链表实现队列的入队出队操作](queue.assets/linkedlist_queue_step1.png) - ```python title="array_queue.py" - """ 基于环形数组实现的队列 """ - class ArrayQueue: - def __init__(self, size): - self.__nums = [0] * size # 用于存储队列元素的数组 - self.__front = 0 # 头指针,指向队首 - self.__rear = 0 # 尾指针,指向队尾 + 1 - - """ 获取队列的容量 """ - def capacity(self): - return len(self.__nums) - - """ 获取队列的长度 """ - def size(self): - # 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (self.capacity() + self.__rear - self.__front) % self.capacity() - - """ 判断队列是否为空 """ - def is_empty(self): - return (self.__rear - self.__front) == 0 - - """ 入队 """ - def push(self, val): - if self.size() == self.capacity(): - print("队列已满") - return False - # 尾结点后添加 num - self.__nums[self.__rear] = val - # 尾指针向后移动一位,越过尾部后返回到数组头部 - self.__rear = (self.__rear + 1) % self.capacity() - - """ 出队 """ - def poll(self): - num = self.peek() - # 队头指针向后移动一位,若越过尾部则返回到数组头部 - self.__front = (self.__front + 1) % self.capacity() - return num - - """ 访问队首元素 """ - def peek(self): - if self.is_empty(): - print("队列为空") - return False - return self.__nums[self.__front] - - """ 返回列表用于打印 """ - def to_list(self): - res = [0] * self.size() - j = self.__front - for i in range(self.size()): - res[i] = self.__nums[(j % self.capacity())] - j += 1 - return res - ``` +=== "push()" + ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) -=== "Go" +=== "pop()" + ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) - ```go title="array_queue.go" - /* 基于环形数组实现的队列 */ - type arrayQueue struct { - data []int // 用于存储队列元素的数组 - capacity int // 队列容量(即最多容量的元素个数) - front int // 头指针,指向队首 - rear int // 尾指针,指向队尾 + 1 - } +以下是用链表实现队列的代码: - // newArrayQueue 基于环形数组实现的队列 - func newArrayQueue(capacity int) *arrayQueue { - return &arrayQueue{ - data: make([]int, capacity), - capacity: capacity, - front: 0, - rear: 0, - } - } +```src +[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} +``` - // size 获取队列的长度 - func (q *arrayQueue) size() int { - size := (q.capacity + q.rear - q.front) % q.capacity - return size - } +### 基于数组的实现 - // isEmpty 判断队列是否为空 - func (q *arrayQueue) isEmpty() bool { - return q.rear-q.front == 0 - } +在数组中删除首元素的时间复杂度为 $O(n)$ ,这会导致出队操作效率较低。然而,我们可以采用以下巧妙方法来避免这个问题。 - // offer 入队 - func (q *arrayQueue) offer(v int) { - // 当 rear == capacity 表示队列已满 - if q.size() == q.capacity { - return - } - // 尾结点后添加 - q.data[q.rear] = v - // 尾指针向后移动一位,越过尾部后返回到数组头部 - q.rear = (q.rear + 1) % q.capacity - } +我们可以使用一个变量 `front` 指向队首元素的索引,并维护一个变量 `size` 用于记录队列长度。定义 `rear = front + size` ,这个公式计算出的 `rear` 指向队尾元素之后的下一个位置。 - // poll 出队 - func (q *arrayQueue) poll() any { - if q.isEmpty() { - return nil - } - v := q.data[q.front] - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - q.front = (q.front + 1) % q.capacity - return v - } +基于此设计,**数组中包含元素的有效区间为 `[front, rear - 1]`**,各种操作的实现方法如下图所示。 - // peek 访问队首元素 - func (q *arrayQueue) peek() any { - if q.isEmpty() { - return nil - } - v := q.data[q.front] - return v - } - ``` +- 入队操作:将输入元素赋值给 `rear` 索引处,并将 `size` 增加 1 。 +- 出队操作:只需将 `front` 增加 1 ,并将 `size` 减少 1 。 -=== "JavaScript" - - ```js title="array_queue.js" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - #queue; // 用于存储队列元素的数组 - #front = 0; // 头指针,指向队首 - #rear = 0; // 尾指针,指向队尾 + 1 - constructor(capacity) { - this.#queue = new Array(capacity); - } - /* 获取队列的容量 */ - get capacity() { - return this.#queue.length; - } - /* 获取队列的长度 */ - get size() { - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (this.capacity + this.#rear - this.#front) % this.capacity; - } - /* 判断队列是否为空 */ - empty() { - return this.#rear - this.#front == 0; - } - /* 入队 */ - offer(num) { - if (this.size == this.capacity) - throw new Error("队列已满"); - // 尾结点后添加 num - this.#queue[this.#rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - this.#rear = (this.#rear + 1) % this.capacity; - } - /* 出队 */ - poll() { - const num = this.peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - this.#front = (this.#front + 1) % this.capacity; - return num; - } - /* 访问队首元素 */ - peek() { - if (this.empty()) - throw new Error("队列为空"); - return this.#queue[this.#front]; - } - } - ``` +可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 $O(1)$ 。 -=== "TypeScript" - - ```typescript title="array_queue.ts" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private queue: number[]; // 用于存储队列元素的数组 - private front: number = 0; // 头指针,指向队首 - private rear: number = 0; // 尾指针,指向队尾 + 1 - private CAPACITY: number = 1e5; - constructor(capacity?: number) { - this.queue = new Array(capacity ?? this.CAPACITY); - } - /* 获取队列的容量 */ - get capacity(): number { - return this.queue.length; - } - /* 获取队列的长度 */ - get size(): number { - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (this.capacity + this.rear - this.front) % this.capacity; - } - /* 判断队列是否为空 */ - empty(): boolean { - return this.rear - this.front == 0; - } - /* 入队 */ - offer(num: number): void { - if (this.size == this.capacity) - throw new Error("队列已满"); - // 尾结点后添加 num - this.queue[this.rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - this.rear = (this.rear + 1) % this.capacity; - } - /* 出队 */ - poll(): number { - const num = this.peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - this.front = (this.front + 1) % this.capacity; - return num; - } - /* 访问队首元素 */ - peek(): number { - if (this.empty()) - throw new Error("队列为空"); - return this.queue[this.front]; - } - } - ``` +=== "ArrayQueue" + ![基于数组实现队列的入队出队操作](queue.assets/array_queue_step1.png) -=== "C" +=== "push()" + ![array_queue_push](queue.assets/array_queue_step2_push.png) - ```c title="array_queue.c" - - ``` +=== "pop()" + ![array_queue_pop](queue.assets/array_queue_step3_pop.png) -=== "C#" +你可能会发现一个问题:在不断进行入队和出队的过程中,`front` 和 `rear` 都在向右移动,**当它们到达数组尾部时就无法继续移动了**。为了解决此问题,我们可以将数组视为首尾相接的“环形数组”。 - ```csharp title="array_queue.cs" - /* 基于环形数组实现的队列 */ - class ArrayQueue - { - private int[] nums; // 用于存储队列元素的数组 - private int front = 0; // 头指针,指向队首 - private int rear = 0; // 尾指针,指向队尾 + 1 - public ArrayQueue(int capacity) - { - // 初始化数组 - nums = new int[capacity]; - } - /* 获取队列的容量 */ - public int capacity() - { - return nums.Length; - } - /* 获取队列的长度 */ - public int size() - { - int capacity = this.capacity(); - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity + rear - front) % capacity; - } - /* 判断队列是否为空 */ - public bool isEmpty() - { - return rear - front == 0; - } - /* 入队 */ - public void offer(int num) - { - if (size() == capacity()) - { - Console.WriteLine("队列已满"); - return; - } - // 尾结点后添加 num - nums[rear] = num; - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity(); - } - /* 出队 */ - public int poll() - { - int num = peek(); - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - return num; - } - /* 访问队首元素 */ - public int peek() - { - if (isEmpty()) - throw new Exception(); - return nums[front]; - } - } - ``` +对于环形数组,我们需要让 `front` 或 `rear` 在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示: -=== "Swift" +```src +[file]{array_queue}-[class]{array_queue}-[func]{} +``` - ```swift title="array_queue.swift" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private var nums: [Int] // 用于存储队列元素的数组 - private var front = 0 // 头指针,指向队首 - private var rear = 0 // 尾指针,指向队尾 + 1 - - init(capacity: Int) { - // 初始化数组 - nums = Array(repeating: 0, count: capacity) - } - - /* 获取队列的容量 */ - func capacity() -> Int { - nums.count - } - - /* 获取队列的长度 */ - func size() -> Int { - let capacity = capacity() - // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 - return (capacity + rear - front) % capacity - } - - /* 判断队列是否为空 */ - func isEmpty() -> Bool { - rear - front == 0 - } - - /* 入队 */ - func offer(num: Int) { - if size() == capacity() { - print("队列已满") - return - } - // 尾结点后添加 num - nums[rear] = num - // 尾指针向后移动一位,越过尾部后返回到数组头部 - rear = (rear + 1) % capacity() - } - - /* 出队 */ - @discardableResult - func poll() -> Int { - let num = peek() - // 队头指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity() - return num - } - - /* 访问队首元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("队列为空") - } - return nums[front] - } - } - ``` +以上实现的队列仍然具有局限性:其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。有兴趣的读者可以尝试自行实现。 + +两种实现的对比结论与栈一致,在此不再赘述。 ## 队列典型应用 -- **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 -- **各种待办事项**。例如打印机的任务队列、餐厅的出餐队列等等。 +- **淘宝订单**。购物者下单后,订单将加入队列中,系统随后会根据顺序处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。 +- **各类待办事项**。任何需要实现“先来后到”功能的场景,例如打印机的任务队列、餐厅的出餐队列等,队列在这些场景中可以有效地维护处理顺序。 diff --git a/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png b/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png new file mode 100644 index 0000000000..e8e3445b7e Binary files /dev/null and b/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png differ diff --git a/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png b/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png new file mode 100644 index 0000000000..6c4cae3fd7 Binary files /dev/null and b/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png differ diff --git a/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png b/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png new file mode 100644 index 0000000000..be56b586d3 Binary files /dev/null and b/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png differ diff --git a/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png b/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png new file mode 100644 index 0000000000..5821ca82ad Binary files /dev/null and b/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png differ diff --git a/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png b/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png new file mode 100644 index 0000000000..b46053185a Binary files /dev/null and b/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png differ diff --git a/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png b/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png new file mode 100644 index 0000000000..09a57488b0 Binary files /dev/null and b/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png differ diff --git a/docs/chapter_stack_and_queue/stack.assets/stack_operations.png b/docs/chapter_stack_and_queue/stack.assets/stack_operations.png index 59acdacb9f..afeb9679b2 100644 Binary files a/docs/chapter_stack_and_queue/stack.assets/stack_operations.png and b/docs/chapter_stack_and_queue/stack.assets/stack_operations.png differ diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md old mode 100644 new mode 100755 index c47f7cc82b..e6d651a06f --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -1,117 +1,130 @@ ---- -comments: true ---- - # 栈 -「栈 Stack」是一种遵循「先入后出 first in, last out」数据操作规则的线性数据结构。我们可以将栈类比为放在桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。 +栈(stack)是一种遵循先入后出逻辑的线性数据结构。 -“盘子”是一种形象比喻,我们将盘子替换为任意一种元素(例如整数、字符、对象等),就得到了栈数据结构。 +我们可以将栈类比为桌面上的一摞盘子,如果想取出底部的盘子,则需要先将上面的盘子依次移走。我们将盘子替换为各种类型的元素(如整数、字符、对象等),就得到了栈这种数据结构。 -我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。 +如下图所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫作“入栈”,删除栈顶元素的操作叫作“出栈”。 -![stack_operations](stack.assets/stack_operations.png) +![栈的先入后出规则](stack.assets/stack_operations.png) -

Fig. 栈的先入后出特性

+## 栈的常用操作 -## 栈常用操作 +栈的常用操作如下表所示,具体的方法名需要根据所使用的编程语言来确定。在此,我们以常见的 `push()`、`pop()`、`peek()` 命名为例。 -栈的常用操作见下表(方法命名以 Java 为例)。 +

  栈的操作效率

-

Table. 栈的常用操作

+| 方法 | 描述 | 时间复杂度 | +| -------- | ---------------------- | ---------- | +| `push()` | 元素入栈(添加至栈顶) | $O(1)$ | +| `pop()` | 栈顶元素出栈 | $O(1)$ | +| `peek()` | 访问栈顶元素 | $O(1)$ | -
+通常情况下,我们可以直接使用编程语言内置的栈类。然而,某些语言可能没有专门提供栈类,这时我们可以将该语言的“数组”或“链表”当作栈来使用,并在程序逻辑上忽略与栈无关的操作。 -| 方法 | 描述 | 时间复杂度 | -| --------- | ---------------------- | ---------- | -| push() | 元素入栈(添加至栈顶) | $O(1)$ | -| pop() | 栈顶元素出栈 | $O(1)$ | -| peek() | 访问栈顶元素 | $O(1)$ | -| size() | 获取栈的长度 | $O(1)$ | -| isEmpty() | 判断栈是否为空 | $O(1)$ | +=== "Python" -
+ ```python title="stack.py" + # 初始化栈 + # Python 没有内置的栈类,可以把 list 当作栈来使用 + stack: list[int] = [] -我们可以直接使用编程语言实现好的栈类。 + # 元素入栈 + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) -=== "Java" + # 访问栈顶元素 + peek: int = stack[-1] - ```java title="stack.java" + # 元素出栈 + pop: int = stack.pop() + + # 获取栈的长度 + size: int = len(stack) + + # 判断是否为空 + is_empty: bool = len(stack) == 0 + ``` + +=== "C++" + + ```cpp title="stack.cpp" /* 初始化栈 */ - // 在 Java 中,推荐将 LinkedList 当作栈来使用 - LinkedList stack = new LinkedList<>(); - + stack stack; + /* 元素入栈 */ - stack.addLast(1); - stack.addLast(3); - stack.addLast(2); - stack.addLast(5); - stack.addLast(4); - + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + /* 访问栈顶元素 */ - int peek = stack.peekLast(); - + int top = stack.top(); + /* 元素出栈 */ - int pop = stack.removeLast(); - + stack.pop(); // 无返回值 + /* 获取栈的长度 */ int size = stack.size(); - + /* 判断是否为空 */ - boolean isEmpty = stack.isEmpty(); + bool empty = stack.empty(); ``` -=== "C++" +=== "Java" - ```cpp title="stack.cpp" + ```java title="stack.java" /* 初始化栈 */ - stack stack; - + Stack stack = new Stack<>(); + /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - + /* 访问栈顶元素 */ - int top = stack.top(); - + int peek = stack.peek(); + /* 元素出栈 */ - stack.pop(); - + int pop = stack.pop(); + /* 获取栈的长度 */ int size = stack.size(); - + /* 判断是否为空 */ - bool empty = stack.empty(); + boolean isEmpty = stack.isEmpty(); ``` -=== "Python" +=== "C#" - ```python title="stack.py" - """ 初始化栈 """ - # Python 没有内置的栈类,可以把 List 当作栈来使用 - stack = [] - - """ 元素入栈 """ - stack.append(1) - stack.append(3) - stack.append(2) - stack.append(5) - stack.append(4) - - """ 访问栈顶元素 """ - peek = stack[-1] - - """ 元素出栈 """ - pop = stack.pop() - - """ 获取栈的长度 """ - size = len(stack) - - """ 判断是否为空 """ - is_empty = len(stack) == 0 + ```csharp title="stack.cs" + /* 初始化栈 */ + Stack stack = new(); + + /* 元素入栈 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + + /* 访问栈顶元素 */ + int peek = stack.Peek(); + + /* 元素出栈 */ + int pop = stack.Pop(); + + /* 获取栈的长度 */ + int size = stack.Count; + + /* 判断是否为空 */ + bool isEmpty = stack.Count == 0; ``` === "Go" @@ -120,893 +133,304 @@ comments: true /* 初始化栈 */ // 在 Go 中,推荐将 Slice 当作栈来使用 var stack []int - + /* 元素入栈 */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) - + /* 访问栈顶元素 */ peek := stack[len(stack)-1] - + /* 元素出栈 */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] - + /* 获取栈的长度 */ size := len(stack) - + /* 判断是否为空 */ isEmpty := len(stack) == 0 ``` -=== "JavaScript" +=== "Swift" - ```js title="stack.js" + ```swift title="stack.swift" /* 初始化栈 */ - // Javascript 没有内置的栈类,可以把 Array 当作栈来使用 + // Swift 没有内置的栈类,可以把 Array 当作栈来使用 + var stack: [Int] = [] + + /* 元素入栈 */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + /* 访问栈顶元素 */ + let peek = stack.last! + + /* 元素出栈 */ + let pop = stack.removeLast() + + /* 获取栈的长度 */ + let size = stack.count + + /* 判断是否为空 */ + let isEmpty = stack.isEmpty + ``` + +=== "JS" + + ```javascript title="stack.js" + /* 初始化栈 */ + // JavaScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack = []; - + /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - + /* 访问栈顶元素 */ const peek = stack[stack.length-1]; - + /* 元素出栈 */ const pop = stack.pop(); - + /* 获取栈的长度 */ const size = stack.length; - + /* 判断是否为空 */ const is_empty = stack.length === 0; ``` -=== "TypeScript" +=== "TS" ```typescript title="stack.ts" /* 初始化栈 */ - // Typescript 没有内置的栈类,可以把 Array 当作栈来使用 + // TypeScript 没有内置的栈类,可以把 Array 当作栈来使用 const stack: number[] = []; - + /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - + /* 访问栈顶元素 */ const peek = stack[stack.length - 1]; - + /* 元素出栈 */ const pop = stack.pop(); - + /* 获取栈的长度 */ const size = stack.length; - + /* 判断是否为空 */ const is_empty = stack.length === 0; ``` -=== "C" - - ```c title="stack.c" - - ``` - -=== "C#" +=== "Dart" - ```csharp title="stack.cs" + ```dart title="stack.dart" /* 初始化栈 */ - Stack stack = new (); - + // Dart 没有内置的栈类,可以把 List 当作栈来使用 + List stack = []; + /* 元素入栈 */ - stack.Push(1); - stack.Push(3); - stack.Push(2); - stack.Push(5); - stack.Push(4); - + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + /* 访问栈顶元素 */ - int peek = stack.Peek(); - + int peek = stack.last; + /* 元素出栈 */ - int pop = stack.Pop(); - + int pop = stack.removeLast(); + /* 获取栈的长度 */ - int size = stack.Count(); - + int size = stack.length; + /* 判断是否为空 */ - bool isEmpty = stack.Count()==0; + bool isEmpty = stack.isEmpty; ``` -=== "Swift" +=== "Rust" - ```swift title="stack.swift" + ```rust title="stack.rs" /* 初始化栈 */ - // Swift 没有内置的栈类,可以把 Array 当作栈来使用 - var stack: [Int] = [] + // 把 Vec 当作栈来使用 + let mut stack: Vec = Vec::new(); /* 元素入栈 */ - stack.append(1) - stack.append(3) - stack.append(2) - stack.append(5) - stack.append(4) + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); /* 访问栈顶元素 */ - let peek = stack.last! + let top = stack.last().unwrap(); /* 元素出栈 */ - let pop = stack.removeLast() + let pop = stack.pop().unwrap(); /* 获取栈的长度 */ - let size = stack.count + let size = stack.len(); /* 判断是否为空 */ - let isEmpty = stack.isEmpty + let is_empty = stack.is_empty(); ``` -## 栈的实现 +=== "C" -为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。 + ```c title="stack.c" + // C 未提供内置栈 + ``` -栈规定元素是先入后出的,因此我们只能在栈顶添加或删除元素。然而,数组或链表都可以在任意位置添加删除元素,因此 **栈可被看作是一种受约束的数组或链表**。换言之,我们可以“屏蔽”数组或链表的部分无关操作,使之对外的表现逻辑符合栈的规定即可。 +=== "Kotlin" -### 基于链表的实现 + ```kotlin title="stack.kt" + /* 初始化栈 */ + val stack = Stack() -使用「链表」实现栈时,将链表的头结点看作栈顶,尾结点看作栈底。 + /* 元素入栈 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) -对于入栈操作,将元素插入到链表头部即可,这种结点添加方式被称为“头插法”。而对于出栈操作,则将头结点从链表中删除即可。 + /* 访问栈顶元素 */ + val peek = stack.peek() -受益于链表的离散存储方式,栈的扩容更加灵活,删除元素的内存也会被系统自动回收;缺点是无法像数组一样高效地随机访问,并且由于链表结点需存储指针,导致单个元素占用空间更大。 + /* 元素出栈 */ + val pop = stack.pop() -=== "Java" + /* 获取栈的长度 */ + val size = stack.size - ```java title="linkedlist_stack.java" - /* 基于链表实现的栈 */ - class LinkedListStack { - private ListNode stackPeek; // 将头结点作为栈顶 - private int stkSize = 0; // 栈的长度 - public LinkedListStack() { - stackPeek = null; - } - /* 获取栈的长度 */ - public int size() { - return stkSize; - } - /* 判断栈是否为空 */ - public boolean isEmpty() { - return size() == 0; - } - /* 入栈 */ - public void push(int num) { - ListNode node = new ListNode(num); - node.next = stackPeek; - stackPeek = node; - stkSize++; - } - /* 出栈 */ - public int pop() { - int num = peek(); - stackPeek = stackPeek.next; - stkSize--; - return num; - } - /* 访问栈顶元素 */ - public int peek() { - if (size() == 0) - throw new EmptyStackException(); - return stackPeek.val; - } - } + /* 判断是否为空 */ + val isEmpty = stack.isEmpty() ``` -=== "C++" +=== "Ruby" - ```cpp title="linkedlist_stack.cpp" - /* 基于链表实现的栈 */ - class LinkedListStack { - private: - ListNode* stackTop; // 将头结点作为栈顶 - int stkSize; // 栈的长度 - - public: - LinkedListStack() { - stackTop = nullptr; - stkSize = 0; - } - /* 获取栈的长度 */ - int size() { - return stkSize; - } - /* 判断栈是否为空 */ - bool empty() { - return size() == 0; - } - /* 入栈 */ - void push(int num) { - ListNode* node = new ListNode(num); - node->next = stackTop; - stackTop = node; - stkSize++; - } - /* 出栈 */ - void pop() { - int num = top(); - ListNode *tmp = stackTop; - stackTop = stackTop->next; - // 释放内存 - delete tmp; - stkSize--; - } - /* 访问栈顶元素 */ - int top() { - if (size() == 0) - throw out_of_range("栈为空"); - return stackTop->val; - } - }; - ``` + ```ruby title="stack.rb" + # 初始化栈 + # Ruby 没有内置的栈类,可以把 Array 当作栈来使用 + stack = [] -=== "Python" + # 元素入栈 + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 - ```python title="linkedlist_stack.py" - """ 基于链表实现的栈 """ - class LinkedListStack: - def __init__(self): - self.__peek = None - self.__size = 0 - - """ 获取栈的长度 """ - def size(self): - return self.__size - - """ 判断栈是否为空 """ - def is_empty(self): - return not self.__peek - - """ 入栈 """ - def push(self, val): - node = ListNode(val) - node.next = self.__peek - self.__peek = node - self.__size += 1 - - """ 出栈 """ - def pop(self): - num = self.peek() - self.__peek = self.__peek.next - self.__size -= 1 - return num - - """ 访问栈顶元素 """ - def peek(self): - # 判空处理 - if not self.__peek: return None - return self.__peek.val - ``` + # 访问栈顶元素 + peek = stack.last -=== "Go" + # 元素出栈 + pop = stack.pop - ```go title="linkedlist_stack.go" - /* 基于链表实现的栈 */ - type linkedListStack struct { - // 使用内置包 list 来实现栈 - data *list.List - } - - // newLinkedListStack 初始化链表 - func newLinkedListStack() *linkedListStack { - return &linkedListStack{ - data: list.New(), - } - } - - // push 入栈 - func (s *linkedListStack) push(value int) { - s.data.PushBack(value) - } - - // pop 出栈 - func (s *linkedListStack) pop() any { - if s.isEmpty() { - return nil - } - e := s.data.Back() - s.data.Remove(e) - return e.Value - } - - // peek 访问栈顶元素 - func (s *linkedListStack) peek() any { - if s.isEmpty() { - return nil - } - e := s.data.Back() - return e.Value - } - - // size 获取栈的长度 - func (s *linkedListStack) size() int { - return s.data.Len() - } - - // isEmpty 判断栈是否为空 - func (s *linkedListStack) isEmpty() bool { - return s.data.Len() == 0 - } - ``` + # 获取栈的长度 + size = stack.length -=== "JavaScript" - - ```js title="linkedlist_stack.js" - /* 基于链表实现的栈 */ - class LinkedListStack { - #stackPeek; // 将头结点作为栈顶 - #stkSize = 0; // 栈的长度 - - constructor() { - this.#stackPeek = null; - } - - /* 获取栈的长度 */ - get size() { - return this.#stkSize; - } - - /* 判断栈是否为空 */ - isEmpty() { - return this.size == 0; - } - - /* 入栈 */ - push(num) { - const node = new ListNode(num); - node.next = this.#stackPeek; - this.#stackPeek = node; - this.#stkSize++; - } - - /* 出栈 */ - pop() { - const num = this.peek(); - if (!this.#stackPeek) { - throw new Error("栈为空!"); - } - this.#stackPeek = this.#stackPeek.next; - this.#stkSize--; - return num; - } - - /* 访问栈顶元素 */ - peek() { - if (!this.#stackPeek) { - throw new Error("栈为空!"); - } - return this.#stackPeek.val; - } - - /* 将链表转化为 Array 并返回 */ - toArray() { - let node = this.#stackPeek; - const res = new Array(this.size); - for (let i = res.length - 1; i >= 0; i--) { - res[i] = node.val; - node = node.next; - } - return res; - } - } + # 判断是否为空 + is_empty = stack.empty? ``` -=== "TypeScript" - - ```typescript title="linkedlist_stack.ts" - /* 基于链表实现的栈 */ - class LinkedListStack { - private stackPeek: ListNode | null; // 将头结点作为栈顶 - private stkSize: number = 0; // 栈的长度 - - constructor() { - this.stackPeek = null; - } - - /* 获取栈的长度 */ - get size(): number { - return this.stkSize; - } - - /* 判断栈是否为空 */ - isEmpty(): boolean { - return this.size == 0; - } - - /* 入栈 */ - push(num: number): void { - const node = new ListNode(num); - node.next = this.stackPeek; - this.stackPeek = node; - this.stkSize++; - } - - /* 出栈 */ - pop(): number { - const num = this.peek(); - if (!this.stackPeek) { - throw new Error("栈为空!"); - } - this.stackPeek = this.stackPeek.next; - this.stkSize--; - return num; - } - - /* 访问栈顶元素 */ - peek(): number { - if (!this.stackPeek) { - throw new Error("栈为空!"); - } - return this.stackPeek.val; - } - - /* 将链表转化为 Array 并返回 */ - toArray(): number[] { - let node = this.stackPeek; - const res = new Array(this.size); - for (let i = res.length - 1; i >= 0; i--) { - res[i] = node!.val; - node = node!.next; - } - return res; - } - } - ``` +=== "Zig" -=== "C" + ```zig title="stack.zig" - ```c title="linkedlist_stack.c" - ``` -=== "C#" +??? pythontutor "可视化运行" - ```csharp title="linkedlist_stack.cs" - /* 基于链表实现的栈 */ - class LinkedListStack - { - private ListNode stackPeek; // 将头结点作为栈顶 - private int stkSize = 0; // 栈的长度 - public LinkedListStack() - { - stackPeek = null; - } - /* 获取栈的长度 */ - public int size() - { - return stkSize; - } - /* 判断栈是否为空 */ - public bool isEmpty() - { - return size() == 0; - } - /* 入栈 */ - public void push(int num) - { - ListNode node = new ListNode(num); - node.next = stackPeek; - stackPeek = node; - stkSize++; - } - /* 出栈 */ - public int pop() - { - int num = peek(); - stackPeek = stackPeek?.next; - stkSize--; - return num; - } - /* 访问栈顶元素 */ - public int peek() - { - if (size() == 0) - throw new Exception(); - return stackPeek.val; - } - } - ``` + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false -=== "Swift" +## 栈的实现 - ```swift title="linkedlist_stack.swift" - /* 基于链表实现的栈 */ - class LinkedListStack { - private var _peek: ListNode? // 将头结点作为栈顶 - private var _size = 0 // 栈的长度 - - init() {} - - /* 获取栈的长度 */ - func size() -> Int { - _size - } - - /* 判断栈是否为空 */ - func isEmpty() -> Bool { - size() == 0 - } - - /* 入栈 */ - func push(num: Int) { - let node = ListNode(x: num) - node.next = _peek - _peek = node - _size += 1 - } - - /* 出栈 */ - @discardableResult - func pop() -> Int { - let num = peek() - _peek = _peek?.next - _size -= 1 - return num - } - - /* 访问栈顶元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("栈为空") - } - return _peek!.val - } - } - ``` +为了深入了解栈的运行机制,我们来尝试自己实现一个栈类。 -### 基于数组的实现 +栈遵循先入后出的原则,因此我们只能在栈顶添加或删除元素。然而,数组和链表都可以在任意位置添加和删除元素,**因此栈可以视为一种受限制的数组或链表**。换句话说,我们可以“屏蔽”数组或链表的部分无关操作,使其对外表现的逻辑符合栈的特性。 -使用「数组」实现栈时,将数组的尾部当作栈顶,这样可以保证入栈与出栈操作的时间复杂度都为 $O(1)$ 。准确地说,由于入栈的元素可能是源源不断的,我们需要使用可以动态扩容的「列表」。 +### 基于链表的实现 -基于数组实现的栈,优点是支持随机访问,缺点是会造成一定的空间浪费,因为列表的容量始终 $\geq$ 元素数量。 +使用链表实现栈时,我们可以将链表的头节点视为栈顶,尾节点视为栈底。 -=== "Java" +如下图所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。 - ```java title="array_stack.java" - /* 基于数组实现的栈 */ - class ArrayStack { - private ArrayList stack; - public ArrayStack() { - // 初始化列表(动态数组) - stack = new ArrayList<>(); - } - /* 获取栈的长度 */ - public int size() { - return stack.size(); - } - /* 判断栈是否为空 */ - public boolean isEmpty() { - return size() == 0; - } - /* 入栈 */ - public void push(int num) { - stack.add(num); - } - /* 出栈 */ - public int pop() { - if (isEmpty()) - throw new EmptyStackException(); - return stack.remove(size() - 1); - } - /* 访问栈顶元素 */ - public int peek() { - if (isEmpty()) - throw new EmptyStackException(); - return stack.get(size() - 1); - } - } - ``` +=== "LinkedListStack" + ![基于链表实现栈的入栈出栈操作](stack.assets/linkedlist_stack_step1.png) -=== "C++" +=== "push()" + ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) - ```cpp title="array_stack.cpp" - /* 基于数组实现的栈 */ - class ArrayStack { - private: - vector stack; - - public: - /* 获取栈的长度 */ - int size() { - return stack.size(); - } - /* 判断栈是否为空 */ - bool empty() { - return stack.empty(); - } - /* 入栈 */ - void push(int num) { - stack.push_back(num); - } - /* 出栈 */ - void pop() { - int oldTop = top(); - stack.pop_back(); - } - /* 访问栈顶元素 */ - int top() { - if(empty()) - throw out_of_range("栈为空"); - return stack.back(); - } - }; - ``` +=== "pop()" + ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) -=== "Python" +以下是基于链表实现栈的示例代码: - ```python title="array_stack.py" - """ 基于数组实现的栈 """ - class ArrayStack: - def __init__(self): - self.__stack = [] - - """ 获取栈的长度 """ - def size(self): - return len(self.__stack) - - """ 判断栈是否为空 """ - def is_empty(self): - return self.__stack == [] - - """ 入栈 """ - def push(self, item): - self.__stack.append(item) - - """ 出栈 """ - def pop(self): - assert not self.is_empty(), "栈为空" - return self.__stack.pop() - - """ 访问栈顶元素 """ - def peek(self): - assert not self.is_empty(), "栈为空" - return self.__stack[-1] - ``` +```src +[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} +``` -=== "Go" +### 基于数组的实现 - ```go title="array_stack.go" - /* 基于数组实现的栈 */ - type arrayStack struct { - data []int // 数据 - } - - func newArrayStack() *arrayStack { - return &arrayStack{ - // 设置栈的长度为 0,容量为 16 - data: make([]int, 0, 16), - } - } - - // size 栈的长度 - func (s *arrayStack) size() int { - return len(s.data) - } - - // isEmpty 栈是否为空 - func (s *arrayStack) isEmpty() bool { - return s.size() == 0 - } - - // push 入栈 - func (s *arrayStack) push(v int) { - // 切片会自动扩容 - s.data = append(s.data, v) - } - - // pop 出栈 - func (s *arrayStack) pop() any { - // 弹出栈前,先判断是否为空 - if s.isEmpty() { - return nil - } - val := s.peek() - s.data = s.data[:len(s.data)-1] - return val - } - - // peek 获取栈顶元素 - func (s *arrayStack) peek() any { - if s.isEmpty() { - return nil - } - val := s.data[len(s.data)-1] - return val - } - ``` +使用数组实现栈时,我们可以将数组的尾部作为栈顶。如下图所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。 -=== "JavaScript" - - ```js title="array_stack.js" - /* 基于数组实现的栈 */ - class ArrayStack { - stack; - constructor() { - this.stack = []; - } - /* 获取栈的长度 */ - get size() { - return this.stack.length; - } - /* 判断栈是否为空 */ - empty() { - return this.stack.length === 0; - } - /* 入栈 */ - push(num) { - this.stack.push(num); - } - /* 出栈 */ - pop() { - if (this.empty()) - throw new Error("栈为空"); - return this.stack.pop(); - } - /* 访问栈顶元素 */ - top() { - if (this.empty()) - throw new Error("栈为空"); - return this.stack[this.stack.length - 1]; - } - }; - ``` +=== "ArrayStack" + ![基于数组实现栈的入栈出栈操作](stack.assets/array_stack_step1.png) -=== "TypeScript" - - ```typescript title="array_stack.ts" - /* 基于数组实现的栈 */ - class ArrayStack { - private stack: number[]; - constructor() { - this.stack = []; - } - /* 获取栈的长度 */ - get size(): number { - return this.stack.length; - } - /* 判断栈是否为空 */ - empty(): boolean { - return this.stack.length === 0; - } - /* 入栈 */ - push(num: number): void { - this.stack.push(num); - } - /* 出栈 */ - pop(): number | undefined { - if (this.empty()) - throw new Error('栈为空'); - return this.stack.pop(); - } - /* 访问栈顶元素 */ - top(): number | undefined { - if (this.empty()) - throw new Error('栈为空'); - return this.stack[this.stack.length - 1]; - } - }; - ``` +=== "push()" + ![array_stack_push](stack.assets/array_stack_step2_push.png) -=== "C" +=== "pop()" + ![array_stack_pop](stack.assets/array_stack_step3_pop.png) - ```c title="array_stack.c" - - ``` +由于入栈的元素可能会源源不断地增加,因此我们可以使用动态数组,这样就无须自行处理数组扩容问题。以下为示例代码: -=== "C#" +```src +[file]{array_stack}-[class]{array_stack}-[func]{} +``` - ```csharp title="array_stack.cs" - /* 基于数组实现的栈 */ - class ArrayStack - { - private List stack; - public ArrayStack() - { - // 初始化列表(动态数组) - stack = new(); - } - /* 获取栈的长度 */ - public int size() - { - return stack.Count(); - } - /* 判断栈是否为空 */ - public bool isEmpty() - { - return size() == 0; - } - /* 入栈 */ - public void push(int num) - { - stack.Add(num); - } - /* 出栈 */ - public int pop() - { - if (isEmpty()) - throw new Exception(); - var val = peek(); - stack.RemoveAt(size() - 1); - return val; - } - /* 访问栈顶元素 */ - public int peek() - { - if (isEmpty()) - throw new Exception(); - return stack[size() - 1]; - } - } - ``` +## 两种实现对比 -=== "Swift" +**支持操作** - ```swift title="array_stack.swift" - /* 基于数组实现的栈 */ - class ArrayStack { - private var stack: [Int] - - init() { - // 初始化列表(动态数组) - stack = [] - } - - /* 获取栈的长度 */ - func size() -> Int { - stack.count - } - - /* 判断栈是否为空 */ - func isEmpty() -> Bool { - stack.isEmpty - } - - /* 入栈 */ - func push(num: Int) { - stack.append(num) - } - - /* 出栈 */ - @discardableResult - func pop() -> Int { - if isEmpty() { - fatalError("栈为空") - } - return stack.removeLast() - } - - /* 访问栈顶元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("栈为空") - } - return stack.last! - } - } - ``` +两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。 + +**时间效率** + +在基于数组的实现中,入栈和出栈操作都在预先分配好的连续内存中进行,具有很好的缓存本地性,因此效率较高。然而,如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为 $O(n)$ 。 + +在基于链表的实现中,链表的扩容非常灵活,不存在上述数组扩容时效率降低的问题。但是,入栈操作需要初始化节点对象并修改指针,因此效率相对较低。不过,如果入栈元素本身就是节点对象,那么可以省去初始化步骤,从而提高效率。 + +综上所述,当入栈与出栈操作的元素是基本数据类型时,例如 `int` 或 `double` ,我们可以得出以下结论。 + +- 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高。 +- 基于链表实现的栈可以提供更加稳定的效率表现。 + +**空间效率** + +在初始化列表时,系统会为列表分配“初始容量”,该容量可能超出实际需求;并且,扩容机制通常是按照特定倍率(例如 2 倍)进行扩容的,扩容后的容量也可能超出实际需求。因此,**基于数组实现的栈可能造成一定的空间浪费**。 -!!! tip +然而,由于链表节点需要额外存储指针,**因此链表节点占用的空间相对较大**。 - 某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作,而无需像上述代码去特意包装一层。 +综上,我们不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析。 -## 栈典型应用 +## 栈的典型应用 -- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就将上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 -- **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 +- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就会对上一个网页执行入栈,这样我们就可以通过后退操作回到上一个网页。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。 +- **程序内存管理**。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会不断执行出栈操作。 diff --git a/docs/chapter_stack_and_queue/summary.md b/docs/chapter_stack_and_queue/summary.md index 964f1f73ca..a104e04ac9 100644 --- a/docs/chapter_stack_and_queue/summary.md +++ b/docs/chapter_stack_and_queue/summary.md @@ -1,9 +1,31 @@ ---- -comments: true ---- - # 小结 -- 栈是一种遵循先入后出的数据结构,可以使用数组或链表实现。 -- 队列是一种遵循先入先出的数据结构,可以使用数组或链表实现。 -- 双向队列的两端都可以添加与删除元素。 +### 重点回顾 + +- 栈是一种遵循先入后出原则的数据结构,可通过数组或链表来实现。 +- 在时间效率方面,栈的数组实现具有较高的平均效率,但在扩容过程中,单次入栈操作的时间复杂度会劣化至 $O(n)$ 。相比之下,栈的链表实现具有更为稳定的效率表现。 +- 在空间效率方面,栈的数组实现可能导致一定程度的空间浪费。但需要注意的是,链表节点所占用的内存空间比数组元素更大。 +- 队列是一种遵循先入先出原则的数据结构,同样可以通过数组或链表来实现。在时间效率和空间效率的对比上,队列的结论与前述栈的结论相似。 +- 双向队列是一种具有更高自由度的队列,它允许在两端进行元素的添加和删除操作。 + +### Q & A + +**Q**:浏览器的前进后退是否是双向链表实现? + +浏览器的前进后退功能本质上是“栈”的体现。当用户访问一个新页面时,该页面会被添加到栈顶;当用户点击后退按钮时,该页面会从栈顶弹出。使用双向队列可以方便地实现一些额外操作,这个在“双向队列”章节有提到。 + +**Q**:在出栈后,是否需要释放出栈节点的内存? + +如果后续仍需要使用弹出节点,则不需要释放内存。若之后不需要用到,`Java` 和 `Python` 等语言拥有自动垃圾回收机制,因此不需要手动释放内存;在 `C` 和 `C++` 中需要手动释放内存。 + +**Q**:双向队列像是两个栈拼接在了一起,它的用途是什么? + +双向队列就像是栈和队列的组合或两个栈拼在了一起。它表现的是栈 + 队列的逻辑,因此可以实现栈与队列的所有应用,并且更加灵活。 + +**Q**:撤销(undo)和反撤销(redo)具体是如何实现的? + +使用两个栈,栈 `A` 用于撤销,栈 `B` 用于反撤销。 + +1. 每当用户执行一个操作,将这个操作压入栈 `A` ,并清空栈 `B` 。 +2. 当用户执行“撤销”时,从栈 `A` 中弹出最近的操作,并将其压入栈 `B` 。 +3. 当用户执行“反撤销”时,从栈 `B` 中弹出最近的操作,并将其压入栈 `A` 。 diff --git a/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png new file mode 100644 index 0000000000..3480c57181 Binary files /dev/null and b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png differ diff --git a/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png new file mode 100644 index 0000000000..753ddcc270 Binary files /dev/null and b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png differ diff --git a/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png new file mode 100644 index 0000000000..3c0184f3f3 Binary files /dev/null and b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png differ diff --git a/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png new file mode 100644 index 0000000000..39a6169c44 Binary files /dev/null and b/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png differ diff --git a/docs/chapter_tree/array_representation_of_tree.md b/docs/chapter_tree/array_representation_of_tree.md new file mode 100644 index 0000000000..1b31a13769 --- /dev/null +++ b/docs/chapter_tree/array_representation_of_tree.md @@ -0,0 +1,166 @@ +# 二叉树数组表示 + +在链表表示下,二叉树的存储单元为节点 `TreeNode` ,节点之间通过指针相连接。上一节介绍了链表表示下的二叉树的各项基本操作。 + +那么,我们能否用数组来表示二叉树呢?答案是肯定的。 + +## 表示完美二叉树 + +先分析一个简单案例。给定一棵完美二叉树,我们将所有节点按照层序遍历的顺序存储在一个数组中,则每个节点都对应唯一的数组索引。 + +根据层序遍历的特性,我们可以推导出父节点索引与子节点索引之间的“映射公式”:**若某节点的索引为 $i$ ,则该节点的左子节点索引为 $2i + 1$ ,右子节点索引为 $2i + 2$** 。下图展示了各个节点索引之间的映射关系。 + +![完美二叉树的数组表示](array_representation_of_tree.assets/array_representation_binary_tree.png) + +**映射公式的角色相当于链表中的节点引用(指针)**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。 + +## 表示任意二叉树 + +完美二叉树是一个特例,在二叉树的中间层通常存在许多 `None` 。由于层序遍历序列并不包含这些 `None` ,因此我们无法仅凭该序列来推测 `None` 的数量和分布位置。**这意味着存在多种二叉树结构都符合该层序遍历序列**。 + +如下图所示,给定一棵非完美二叉树,上述数组表示方法已经失效。 + +![层序遍历序列对应多种二叉树可能性](array_representation_of_tree.assets/array_representation_without_empty.png) + +为了解决此问题,**我们可以考虑在层序遍历序列中显式地写出所有 `None`** 。如下图所示,这样处理后,层序遍历序列就可以唯一表示二叉树了。示例代码如下: + +=== "Python" + + ```python title="" + # 二叉树的数组表示 + # 使用 None 来表示空位 + tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + ``` + +=== "C++" + + ```cpp title="" + /* 二叉树的数组表示 */ + // 使用 int 最大值 INT_MAX 标记空位 + vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Java" + + ```java title="" + /* 二叉树的数组表示 */ + // 使用 int 的包装类 Integer ,就可以使用 null 来标记空位 + Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ``` + +=== "C#" + + ```csharp title="" + /* 二叉树的数组表示 */ + // 使用 int? 可空类型 ,就可以使用 null 来标记空位 + int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Go" + + ```go title="" + /* 二叉树的数组表示 */ + // 使用 any 类型的切片, 就可以使用 nil 来标记空位 + tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + ``` + +=== "Swift" + + ```swift title="" + /* 二叉树的数组表示 */ + // 使用 Int? 可空类型 ,就可以使用 nil 来标记空位 + let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "JS" + + ```javascript title="" + /* 二叉树的数组表示 */ + // 使用 null 来表示空位 + let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "TS" + + ```typescript title="" + /* 二叉树的数组表示 */ + // 使用 null 来表示空位 + let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Dart" + + ```dart title="" + /* 二叉树的数组表示 */ + // 使用 int? 可空类型 ,就可以使用 null 来标记空位 + List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Rust" + + ```rust title="" + /* 二叉树的数组表示 */ + // 使用 None 来标记空位 + let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; + ``` + +=== "C" + + ```c title="" + /* 二叉树的数组表示 */ + // 使用 int 最大值标记空位,因此要求节点值不能为 INT_MAX + int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 二叉树的数组表示 */ + // 使用 null 来表示空位 + val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) + ``` + +=== "Ruby" + + ```ruby title="" + ### 二叉树的数组表示 ### + # 使用 nil 来表示空位 + tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "Zig" + + ```zig title="" + + ``` + +![任意类型二叉树的数组表示](array_representation_of_tree.assets/array_representation_with_empty.png) + +值得说明的是,**完全二叉树非常适合使用数组来表示**。回顾完全二叉树的定义,`None` 只出现在最底层且靠右的位置,**因此所有 `None` 一定出现在层序遍历序列的末尾**。 + +这意味着使用数组表示完全二叉树时,可以省略存储所有 `None` ,非常方便。下图给出了一个例子。 + +![完全二叉树的数组表示](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) + +以下代码实现了一棵基于数组表示的二叉树,包括以下几种操作。 + +- 给定某节点,获取它的值、左(右)子节点、父节点。 +- 获取前序遍历、中序遍历、后序遍历、层序遍历序列。 + +```src +[file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} +``` + +## 优点与局限性 + +二叉树的数组表示主要有以下优点。 + +- 数组存储在连续的内存空间中,对缓存友好,访问与遍历速度较快。 +- 不需要存储指针,比较节省空间。 +- 允许随机访问节点。 + +然而,数组表示也存在一些局限性。 + +- 数组存储需要连续内存空间,因此不适合存储数据量过大的树。 +- 增删节点需要通过数组插入与删除操作实现,效率较低。 +- 当二叉树中存在大量 `None` 时,数组中包含的节点数据比重较低,空间利用率较低。 diff --git a/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png b/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png new file mode 100644 index 0000000000..00586167c5 Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png b/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png new file mode 100644 index 0000000000..dc773daa1a Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png b/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png new file mode 100644 index 0000000000..3685ee4753 Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png b/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png new file mode 100644 index 0000000000..3ef850f4a6 Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png b/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png new file mode 100644 index 0000000000..8cdb224489 Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png b/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png new file mode 100644 index 0000000000..d22d81129f Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png new file mode 100644 index 0000000000..c0947cb5a6 Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png new file mode 100644 index 0000000000..e87d5f295c Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png new file mode 100644 index 0000000000..bb366c15d9 Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png new file mode 100644 index 0000000000..a39b1294be Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png new file mode 100644 index 0000000000..086be7880a Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png differ diff --git a/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png b/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png new file mode 100644 index 0000000000..643d0ee9ca Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png differ diff --git a/docs/chapter_tree/avl_tree.assets/degradation_from_inserting_node.png b/docs/chapter_tree/avl_tree.assets/degradation_from_inserting_node.png deleted file mode 100644 index 60faa77fac..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/degradation_from_inserting_node.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/degradation_from_removing_node.png b/docs/chapter_tree/avl_tree.assets/degradation_from_removing_node.png deleted file mode 100644 index 90f1d155ce..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/degradation_from_removing_node.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/left_right_rotate.png b/docs/chapter_tree/avl_tree.assets/left_right_rotate.png deleted file mode 100644 index a5e73acb6d..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/left_right_rotate.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/left_rotate.png b/docs/chapter_tree/avl_tree.assets/left_rotate.png deleted file mode 100644 index 4c07196dce..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/left_rotate.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png b/docs/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png deleted file mode 100644 index 949c5c20fa..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/right_left_rotate.png b/docs/chapter_tree/avl_tree.assets/right_left_rotate.png deleted file mode 100644 index 3e178b8941..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/right_left_rotate.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_step1.png b/docs/chapter_tree/avl_tree.assets/right_rotate_step1.png deleted file mode 100644 index c798126322..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_step1.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_step2.png b/docs/chapter_tree/avl_tree.assets/right_rotate_step2.png deleted file mode 100644 index 8cabc71212..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_step2.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_step3.png b/docs/chapter_tree/avl_tree.assets/right_rotate_step3.png deleted file mode 100644 index 8a13863553..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_step3.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_step4.png b/docs/chapter_tree/avl_tree.assets/right_rotate_step4.png deleted file mode 100644 index 5ede1ed204..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_step4.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png b/docs/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png deleted file mode 100644 index ea07a8a2e5..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.assets/rotation_cases.png b/docs/chapter_tree/avl_tree.assets/rotation_cases.png deleted file mode 100644 index 6cd6128d17..0000000000 Binary files a/docs/chapter_tree/avl_tree.assets/rotation_cases.png and /dev/null differ diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 6f6272645f..3d90186778 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -1,1116 +1,364 @@ ---- -comments: true ---- - # AVL 树 * -在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 $O(\log n)$ 劣化至 $O(n)$ 。 - -如下图所示,执行两步删除结点后,该二叉搜索树就会退化为链表。 +在“二叉搜索树”章节中我们提到,在多次插入和删除操作后,二叉搜索树可能退化为链表。在这种情况下,所有操作的时间复杂度将从 $O(\log n)$ 劣化为 $O(n)$ 。 -![degradation_from_removing_node](avl_tree.assets/degradation_from_removing_node.png) +如下图所示,经过两次删除节点操作,这棵二叉搜索树便会退化为链表。 -再比如,在以下完美二叉树中插入两个结点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。 +![AVL 树在删除节点后发生退化](avl_tree.assets/avltree_degradation_from_removing_node.png) -![degradation_from_inserting_node](avl_tree.assets/degradation_from_inserting_node.png) +再例如,在下图所示的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之劣化。 -G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。**论文中描述了一系列操作,使得在不断添加与删除结点后,AVL 树仍然不会发生退化**,进而使得各种操作的时间复杂度均能保持在 $O(\log n)$ 级别。 +![AVL 树在插入节点后发生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png) -换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。 +1962 年 G. M. Adelson-Velsky 和 E. M. Landis 在论文“An algorithm for the organization of information”中提出了 AVL 树。论文中详细描述了一系列操作,确保在持续添加和删除节点后,AVL 树不会退化,从而使得各种操作的时间复杂度保持在 $O(\log n)$ 级别。换句话说,在需要频繁进行增删查改操作的场景中,AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。 ## AVL 树常见术语 -「AVL 树」既是「二叉搜索树」又是「平衡二叉树」,同时满足这两种二叉树的所有性质,因此又被称为「平衡二叉搜索树」。 - -### 结点高度 +AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二叉树的所有性质,因此是一种平衡二叉搜索树(balanced binary search tree)。 -在 AVL 树的操作中,需要获取结点「高度 Height」,所以给 AVL 树的结点类添加 `height` 变量。 - -=== "Java" +### 节点高度 - ```java title="avl_tree.java" - /* AVL 树结点类 */ - class TreeNode { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode left; // 左子结点 - public TreeNode right; // 右子结点 - public TreeNode(int x) { val = x; } - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - - ``` +由于 AVL 树的相关操作需要获取节点高度,因此我们需要为节点类添加 `height` 变量: === "Python" - ```python title="avl_tree.py" - """ AVL 树结点类 """ + ```python title="" class TreeNode: - def __init__(self, val=None, left=None, right=None): - self.val = val # 结点值 - self.height = 0 # 结点高度 - self.left = left # 左子结点引用 - self.right = right # 右子结点引用 - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* AVL 树结点类 */ - type TreeNode struct { - Val int // 结点值 - Height int // 结点高度 - Left *TreeNode // 左子结点引用 - Right *TreeNode // 右子结点引用 - } + """AVL 树节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.height: int = 0 # 节点高度 + self.left: TreeNode | None = None # 左子节点引用 + self.right: TreeNode | None = None # 右子节点引用 ``` -=== "JavaScript" - - ```js title="avl_tree.js" - - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - - ``` - -=== "C" +=== "C++" - ```c title="avl_tree.c" - + ```cpp title="" + /* AVL 树节点类 */ + struct TreeNode { + int val{}; // 节点值 + int height = 0; // 节点高度 + TreeNode *left{}; // 左子节点 + TreeNode *right{}; // 右子节点 + TreeNode() = default; + explicit TreeNode(int x) : val(x){} + }; ``` -=== "C#" +=== "Java" - ```csharp title="avl_tree.cs" - /* AVL 树结点类 */ + ```java title="" + /* AVL 树节点类 */ class TreeNode { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode? left; // 左子结点 - public TreeNode? right; // 右子结点 + public int val; // 节点值 + public int height; // 节点高度 + public TreeNode left; // 左子节点 + public TreeNode right; // 右子节点 public TreeNode(int x) { val = x; } } ``` -=== "Swift" - - ```swift title="avl_tree.swift" - - ``` - -「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1**。我们封装两个工具函数,分别用于获取与更新结点的高度。 - -=== "Java" +=== "C#" - ```java title="avl_tree.java" - /* 获取结点高度 */ - int height(TreeNode node) { - // 空结点高度为 -1 ,叶结点高度为 0 - return node == null ? -1 : node.height; + ```csharp title="" + /* AVL 树节点类 */ + class TreeNode(int? x) { + public int? val = x; // 节点值 + public int height; // 节点高度 + public TreeNode? left; // 左子节点引用 + public TreeNode? right; // 右子节点引用 } - - /* 更新结点高度 */ - void updateHeight(TreeNode node) { - // 结点高度等于最高子树高度 + 1 - node.height = Math.max(height(node.left), height(node.right)) + 1; - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 获取结点高度 """ - def height(self, node: Optional[TreeNode]) -> int: - # 空结点高度为 -1 ,叶结点高度为 0 - if node is not None: - return node.height - return -1 - - """ 更新结点高度 """ - def __update_height(self, node: Optional[TreeNode]): - # 结点高度等于最高子树高度 + 1 - node.height = max([self.height(node.left), self.height(node.right)]) + 1 ``` === "Go" - ```go title="avl_tree.go" - /* 获取结点高度 */ - func height(node *TreeNode) int { - // 空结点高度为 -1 ,叶结点高度为 0 - if node != nil { - return node.Height - } - return -1 - } - - /* 更新结点高度 */ - func updateHeight(node *TreeNode) { - lh := height(node.Left) - rh := height(node.Right) - // 结点高度等于最高子树高度 + 1 - if lh > rh { - node.Height = lh + 1 - } else { - node.Height = rh + 1 - } - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - - ``` - -=== "C" - - ```c title="avl_tree.c" - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 获取结点高度 */ - public int height(TreeNode? node) - { - // 空结点高度为 -1 ,叶结点高度为 0 - return node == null ? -1 : node.height; - } - - /* 更新结点高度 */ - private void updateHeight(TreeNode node) - { - // 结点高度等于最高子树高度 + 1 - node.height = Math.Max(height(node.left), height(node.right)) + 1; + ```go title="" + /* AVL 树节点结构体 */ + type TreeNode struct { + Val int // 节点值 + Height int // 节点高度 + Left *TreeNode // 左子节点引用 + Right *TreeNode // 右子节点引用 } ``` === "Swift" - ```swift title="avl_tree.swift" - - ``` - -### 结点平衡因子 - -结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。 - -=== "Java" - - ```java title="avl_tree.java" - /* 获取结点平衡因子 */ - public int balanceFactor(TreeNode node) { - // 空结点平衡因子为 0 - if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.left) - height(node.right); - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 获取平衡因子 """ - def balance_factor(self, node: Optional[TreeNode]) -> int: - # 空结点平衡因子为 0 - if node is None: - return 0 - # 结点平衡因子 = 左子树高度 - 右子树高度 - return self.height(node.left) - self.height(node.right) - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 获取平衡因子 */ - func balanceFactor(node *TreeNode) int { - // 空结点平衡因子为 0 - if node == nil { - return 0 + ```swift title="" + /* AVL 树节点类 */ + class TreeNode { + var val: Int // 节点值 + var height: Int // 节点高度 + var left: TreeNode? // 左子节点 + var right: TreeNode? // 右子节点 + + init(x: Int) { + val = x + height = 0 } - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.Left) - height(node.Right) - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - - ``` - -=== "C" - - ```c title="avl_tree.c" - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 获取平衡因子 */ - public int balanceFactor(TreeNode? node) - { - // 空结点平衡因子为 0 - if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.left) - height(node.right); } ``` -=== "Swift" - - ```swift title="avl_tree.swift" - - ``` - -!!! note - - 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 +=== "JS" -## AVL 树旋转 - -AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 - -我们将平衡因子的绝对值 $> 1$ 的结点称为「失衡结点」。根据结点的失衡情况,旋转操作分为 **右旋、左旋、先右旋后左旋、先左旋后右旋**,接下来我们来一起来看看它们是如何操作的。 - -### Case 1 - 右旋 - -如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3**。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 - -=== "Step 1" - ![right_rotate_step1](avl_tree.assets/right_rotate_step1.png) -=== "Step 2" - ![right_rotate_step2](avl_tree.assets/right_rotate_step2.png) -=== "Step 3" - ![right_rotate_step3](avl_tree.assets/right_rotate_step3.png) -=== "Step 4" - ![right_rotate_step4](avl_tree.assets/right_rotate_step4.png) - -进而,如果结点 `child` 本身有右子结点(记为 `grandChild`),则需要在「右旋」中添加一步:将 `grandChild` 作为 `node` 的左子结点。 - -![right_rotate_with_grandchild](avl_tree.assets/right_rotate_with_grandchild.png) - -“向右旋转”是一种形象化的说法,实际需要通过修改结点指针实现,代码如下所示。 - -=== "Java" - - ```java title="avl_tree.java" - /* 右旋操作 */ - TreeNode rightRotate(TreeNode node) { - TreeNode child = node.left; - TreeNode grandChild = child.right; - // 以 child 为原点,将 node 向右旋转 - child.right = node; - node.left = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; + ```javascript title="" + /* AVL 树节点类 */ + class TreeNode { + val; // 节点值 + height; //节点高度 + left; // 左子节点指针 + right; // 右子节点指针 + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } } ``` -=== "C++" - - ```cpp title="avl_tree.cpp" - - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 右旋操作 """ - def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode: - child = node.left - grand_child = child.right - # 以 child 为原点,将 node 向右旋转 - child.right = node - node.left = grand_child - # 更新结点高度 - self.__update_height(node) - self.__update_height(child) - # 返回旋转后子树的根节点 - return child - ``` - -=== "Go" +=== "TS" - ```go title="avl_tree.go" - /* 右旋操作 */ - func rightRotate(node *TreeNode) *TreeNode { - child := node.Left - grandChild := child.Right - // 以 child 为原点,将 node 向右旋转 - child.Right = node - node.Left = grandChild - // 更新结点高度 - updateHeight(node) - updateHeight(child) - // 返回旋转后子树的根节点 - return child + ```typescript title="" + /* AVL 树节点类 */ + class TreeNode { + val: number; // 节点值 + height: number; // 节点高度 + left: TreeNode | null; // 左子节点指针 + right: TreeNode | null; // 右子节点指针 + constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } } ``` -=== "JavaScript" - - ```js title="avl_tree.js" - - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - - ``` - -=== "C" - - ```c title="avl_tree.c" - - ``` - -=== "C#" +=== "Dart" - ```csharp title="avl_tree.cs" - /* 右旋操作 */ - TreeNode? rightRotate(TreeNode? node) - { - TreeNode? child = node.left; - TreeNode? grandChild = child?.right; - // 以 child 为原点,将 node 向右旋转 - child.right = node; - node.left = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; + ```dart title="" + /* AVL 树节点类 */ + class TreeNode { + int val; // 节点值 + int height; // 节点高度 + TreeNode? left; // 左子节点 + TreeNode? right; // 右子节点 + TreeNode(this.val, [this.height = 0, this.left, this.right]); } - - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - ``` -### Case 2 - 左旋 - -类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。 - -![left_rotate](avl_tree.assets/left_rotate.png) - -同理,若结点 `child` 本身有左子结点(记为 `grandChild`),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子结点。 - -![left_rotate_with_grandchild](avl_tree.assets/left_rotate_with_grandchild.png) +=== "Rust" -观察发现,**「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的**。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 `left` 替换为 `right` 、所有的 `right` 替换为 `left` ,即可得到「左旋」代码。 + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; -=== "Java" - - ```java title="avl_tree.java" - /* 左旋操作 */ - private TreeNode leftRotate(TreeNode node) { - TreeNode child = node.right; - TreeNode grandChild = child.left; - // 以 child 为原点,将 node 向左旋转 - child.left = node; - node.right = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; + /* AVL 树节点结构体 */ + struct TreeNode { + val: i32, // 节点值 + height: i32, // 节点高度 + left: Option>>, // 左子节点 + right: Option>>, // 右子节点 } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - - ``` - -=== "Python" - ```python title="avl_tree.py" - """ 左旋操作 """ - def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode: - child = node.right - grand_child = child.left - # 以 child 为原点,将 node 向左旋转 - child.left = node - node.right = grand_child - # 更新结点高度 - self.__update_height(node) - self.__update_height(child) - # 返回旋转后子树的根节点 - return child - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 左旋操作 */ - func leftRotate(node *TreeNode) *TreeNode { - child := node.Right - grandChild := child.Left - // 以 child 为原点,将 node 向左旋转 - child.Left = node - node.Right = grandChild - // 更新结点高度 - updateHeight(node) - updateHeight(child) - // 返回旋转后子树的根节点 - return child + impl TreeNode { + /* 构造方法 */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + left: None, + right: None + })) + } } ``` -=== "JavaScript" - - ```js title="avl_tree.js" - - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - - ``` - === "C" - ```c title="avl_tree.c" - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 左旋操作 */ - TreeNode? leftRotate(TreeNode? node) - { - TreeNode? child = node.right; - TreeNode? grandChild = child?.left; - // 以 child 为原点,将 node 向左旋转 - child.left = node; - node.right = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - - ``` - -### Case 3 - 先左后右 - -对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。 - -![left_right_rotate](avl_tree.assets/left_right_rotate.png) - -### Case 4 - 先右后左 - -同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 `child` 执行「右旋」,然后对 `node` 执行「左旋」。 - -![right_left_rotate](avl_tree.assets/right_left_rotate.png) - -### 旋转的选择 - -下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 **右旋、左旋、先右后左、先左后右** 的旋转操作。 - -![rotation_cases](avl_tree.assets/rotation_cases.png) - -具体地,在代码中使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。 - -
- -| 失衡结点的平衡因子 | 子结点的平衡因子 | 应采用的旋转方法 | -| ------------------ | ---------------- | ---------------- | -| $>0$ (即左偏树) | $\geq 0$ | 右旋 | -| $>0$ (即左偏树) | $<0$ | 先左旋后右旋 | -| $<0$ (即右偏树) | $\leq 0$ | 左旋 | -| $<0$ (即右偏树) | $>0$ | 先右旋后左旋 | - -
- -为方便使用,我们将旋转操作封装成一个函数。至此,**我们可以使用此函数来旋转各种失衡情况,使失衡结点重新恢复平衡**。 - -=== "Java" - - ```java title="avl_tree.java" - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode rotate(TreeNode node) { - // 获取结点 node 的平衡因子 - int balanceFactor = balanceFactor(node); - // 左偏树 - if (balanceFactor > 1) { - if (balanceFactor(node.left) >= 0) { - // 右旋 - return rightRotate(node); - } else { - // 先左旋后右旋 - node.left = leftRotate(node.left); - return rightRotate(node); - } - } - // 右偏树 - if (balanceFactor < -1) { - if (balanceFactor(node.right) <= 0) { - // 左旋 - return leftRotate(node); - } else { - // 先右旋后左旋 - node.right = rightRotate(node.right); - return leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 + ```c title="" + /* AVL 树节点结构体 */ + typedef struct TreeNode { + int val; + int height; + struct TreeNode *left; + struct TreeNode *right; + } TreeNode; + + /* 构造函数 */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; return node; } ``` -=== "C++" - - ```cpp title="avl_tree.cpp" - - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 执行旋转操作,使该子树重新恢复平衡 """ - def __rotate(self, node: Optional[TreeNode]) -> TreeNode: - # 获取结点 node 的平衡因子 - balance_factor = self.balance_factor(node) - # 左偏树 - if balance_factor > 1: - if self.balance_factor(node.left) >= 0: - # 右旋 - return self.__right_rotate(node) - else: - # 先左旋后右旋 - node.left = self.__left_rotate(node.left) - return self.__right_rotate(node) - # 右偏树 - elif balance_factor < -1: - if self.balance_factor(node.right) <= 0: - # 左旋 - return self.__left_rotate(node) - else: - # 先右旋后左旋 - node.right = self.__right_rotate(node.right) - return self.__left_rotate(node) - # 平衡树,无需旋转,直接返回 - return node - ``` +=== "Kotlin" -=== "Go" - - ```go title="avl_tree.go" - /* 执行旋转操作,使该子树重新恢复平衡 */ - func rotate(node *TreeNode) *TreeNode { - // 获取结点 node 的平衡因子 - // Go 推荐短变量,这里 bf 指代 balanceFactor - bf := balanceFactor(node) - // 左偏树 - if bf > 1 { - if balanceFactor(node.Left) >= 0 { - // 右旋 - return rightRotate(node) - } else { - // 先左旋后右旋 - node.Left = leftRotate(node.Left) - return rightRotate(node) - } - } - // 右偏树 - if bf < -1 { - if balanceFactor(node.Right) <= 0 { - // 左旋 - return leftRotate(node) - } else { - // 先右旋后左旋 - node.Right = rightRotate(node.Right) - return leftRotate(node) - } - } - // 平衡树,无需旋转,直接返回 - return node + ```kotlin title="" + /* AVL 树节点类 */ + class TreeNode(val _val: Int) { // 节点值 + val height: Int = 0 // 节点高度 + val left: TreeNode? = null // 左子节点 + val right: TreeNode? = null // 右子节点 } ``` -=== "JavaScript" +=== "Ruby" - ```js title="avl_tree.js" - - ``` - -=== "TypeScript" + ```ruby title="" + ### AVL 树节点类 ### + class TreeNode + attr_accessor :val # 节点值 + attr_accessor :height # 节点高度 + attr_accessor :left # 左子节点引用 + attr_accessor :right # 右子节点引用 - ```typescript title="avl_tree.ts" - + def initialize(val) + @val = val + @height = 0 + end + end ``` -=== "C" +=== "Zig" + + ```zig title="" - ```c title="avl_tree.c" - ``` -=== "C#" +“节点高度”是指从该节点到它的最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 $0$ ,而空节点的高度为 $-1$ 。我们将创建两个工具函数,分别用于获取和更新节点的高度: - ```csharp title="avl_tree.cs" - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode? rotate(TreeNode? node) - { - // 获取结点 node 的平衡因子 - int balanceFactorInt = balanceFactor(node); - // 左偏树 - if (balanceFactorInt > 1) - { - if (balanceFactor(node.left) >= 0) - { - // 右旋 - return rightRotate(node); - } - else - { - // 先左旋后右旋 - node.left = leftRotate(node?.left); - return rightRotate(node); - } - } - // 右偏树 - if (balanceFactorInt < -1) - { - if (balanceFactor(node.right) <= 0) - { - // 左旋 - return leftRotate(node); - } - else - { - // 先右旋后左旋 - node.right = rightRotate(node?.right); - return leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 - return node; - } - ``` +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{update_height} +``` -=== "Swift" +### 节点平衡因子 - ```swift title="avl_tree.swift" +节点的平衡因子(balance factor)定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 $0$ 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用: - ``` +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} +``` -## AVL 树常用操作 +!!! tip -### 插入结点 + 设平衡因子为 $f$ ,则一棵 AVL 树的任意节点的平衡因子皆满足 $-1 \le f \le 1$ 。 -「AVL 树」的结点插入操作与「二叉搜索树」主体类似。不同的是,在插入结点后,从该结点到根结点的路径上会出现一系列「失衡结点」。所以,**我们需要从该结点开始,从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。 +## AVL 树旋转 -=== "Java" +AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中序遍历序列的前提下,使失衡节点重新恢复平衡。换句话说,**旋转操作既能保持“二叉搜索树”的性质,也能使树重新变为“平衡二叉树”**。 - ```java title="avl_tree.java" - /* 插入结点 */ - TreeNode insert(int val) { - root = insertHelper(root, val); - return root; - } - - /* 递归插入结点(辅助函数) */ - TreeNode insertHelper(TreeNode node, int val) { - if (node == null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node.val) - node.left = insertHelper(node.left, val); - else if (val > node.val) - node.right = insertHelper(node.right, val); - else - return node; // 重复结点不插入,直接返回 - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根节点 - return node; - } - ``` +我们将平衡因子绝对值 $> 1$ 的节点称为“失衡节点”。根据节点失衡情况的不同,旋转操作分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。下面详细介绍这些旋转操作。 -=== "C++" +### 右旋 - ```cpp title="avl_tree.cpp" - - ``` +如下图所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 3”。我们关注以该失衡节点为根节点的子树,将该节点记为 `node` ,其左子节点记为 `child` ,执行“右旋”操作。完成右旋后,子树恢复平衡,并且仍然保持二叉搜索树的性质。 -=== "Python" +=== "<1>" + ![右旋操作步骤](avl_tree.assets/avltree_right_rotate_step1.png) - ```python title="avl_tree.py" - """ 插入结点 """ - def insert(self, val) -> TreeNode: - self.root = self.__insert_helper(self.root, val) - return self.root - - """ 递归插入结点(辅助函数)""" - def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode: - if node is None: - return TreeNode(val) - # 1. 查找插入位置,并插入结点 - if val < node.val: - node.left = self.__insert_helper(node.left, val) - elif val > node.val: - node.right = self.__insert_helper(node.right, val) - else: - # 重复结点不插入,直接返回 - return node - # 更新结点高度 - self.__update_height(node) - # 2. 执行旋转操作,使该子树重新恢复平衡 - return self.__rotate(node) - ``` +=== "<2>" + ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) -=== "Go" +=== "<3>" + ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) - ```go title="avl_tree.go" - /* 插入结点 */ - func (t *avlTree) insert(val int) *TreeNode { - t.root = insertHelper(t.root, val) - return t.root - } - /* 递归插入结点(辅助函数) */ - func insertHelper(node *TreeNode, val int) *TreeNode { - if node == nil { - return NewTreeNode(val) - } - /* 1. 查找插入位置,并插入结点 */ - if val < node.Val { - node.Left = insertHelper(node.Left, val) - } else if val > node.Val { - node.Right = insertHelper(node.Right, val) - } else { - // 重复结点不插入,直接返回 - return node - } - // 更新结点高度 - updateHeight(node) - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node) - // 返回子树的根节点 - return node - } - ``` +=== "<4>" + ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) -=== "JavaScript" +如下图所示,当节点 `child` 有右子节点(记为 `grand_child` )时,需要在右旋中添加一步:将 `grand_child` 作为 `node` 的左子节点。 - ```js title="avl_tree.js" - - ``` +![有 grand_child 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png) -=== "TypeScript" +“向右旋转”是一种形象化的说法,实际上需要通过修改节点指针来实现,代码如下所示: - ```typescript title="avl_tree.ts" - - ``` +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} +``` -=== "C" +### 左旋 - ```c title="avl_tree.c" - - ``` +相应地,如果考虑上述失衡二叉树的“镜像”,则需要执行下图所示的“左旋”操作。 -=== "C#" +![左旋操作](avl_tree.assets/avltree_left_rotate.png) - ```csharp title="avl_tree.cs" - /* 插入结点 */ - public TreeNode? insert(int val) - { - root = insertHelper(root, val); - return root; - } - - /* 递归插入结点(辅助函数) */ - private TreeNode? insertHelper(TreeNode? node, int val) - { - if (node == null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node.val) - node.left = insertHelper(node.left, val); - else if (val > node.val) - node.right = insertHelper(node.right, val); - else - return node; // 重复结点不插入,直接返回 - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根节点 - return node; - } - ``` +同理,如下图所示,当节点 `child` 有左子节点(记为 `grand_child` )时,需要在左旋中添加一步:将 `grand_child` 作为 `node` 的右子节点。 -=== "Swift" +![有 grand_child 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png) - ```swift title="avl_tree.swift" +可以观察到,**右旋和左旋操作在逻辑上是镜像对称的,它们分别解决的两种失衡情况也是对称的**。基于对称性,我们只需将右旋的实现代码中的所有的 `left` 替换为 `right` ,将所有的 `right` 替换为 `left` ,即可得到左旋的实现代码: - ``` +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} +``` -### 删除结点 +### 先左旋后右旋 -「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,**在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。 +对于下图中的失衡节点 3 ,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 `child` 执行“左旋”,再对 `node` 执行“右旋”。 -=== "Java" +![先左旋后右旋](avl_tree.assets/avltree_left_right_rotate.png) - ```java title="avl_tree.java" - /* 删除结点 */ - TreeNode remove(int val) { - root = removeHelper(root, val); - return root; - } - - /* 递归删除结点(辅助函数) */ - TreeNode removeHelper(TreeNode node, int val) { - if (node == null) return null; - /* 1. 查找结点,并删除之 */ - if (val < node.val) - node.left = removeHelper(node.left, val); - else if (val > node.val) - node.right = removeHelper(node.right, val); - else { - if (node.left == null || node.right == null) { - TreeNode child = node.left != null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child == null) - return null; - // 子结点数量 = 1 ,直接删除 node - else - node = child; - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode temp = getInOrderNext(node.right); - node.right = removeHelper(node.right, temp.val); - node.val = temp.val; - } - } - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根节点 - return node; - } - ``` +### 先右旋后左旋 -=== "C++" +如下图所示,对于上述失衡二叉树的镜像情况,需要先对 `child` 执行“右旋”,再对 `node` 执行“左旋”。 - ```cpp title="avl_tree.cpp" - - ``` +![先右旋后左旋](avl_tree.assets/avltree_right_left_rotate.png) -=== "Python" +### 旋转的选择 - ```python title="avl_tree.py" - """ 删除结点 """ - def remove(self, val: int): - root = self.__remove_helper(self.root, val) - return root - - """ 递归删除结点(辅助函数) """ - def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]: - if node is None: - return None - # 1. 查找结点,并删除之 - if val < node.val: - node.left = self.__remove_helper(node.left, val) - elif val > node.val: - node.right = self.__remove_helper(node.right, val) - else: - if node.left is None or node.right is None: - child = node.left or node.right - # 子结点数量 = 0 ,直接删除 node 并返回 - if child is None: - return None - # 子结点数量 = 1 ,直接删除 node - else: - node = child - else: # 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp = self.__get_inorder_next(node.right) - node.right = self.__remove_helper(node.right, temp.val) - node.val = temp.val - # 更新结点高度 - self.__update_height(node) - # 2. 执行旋转操作,使该子树重新恢复平衡 - return self.__rotate(node) - ``` +下图展示的四种失衡情况与上述案例逐个对应,分别需要采用右旋、先左旋后右旋、先右旋后左旋、左旋的操作。 -=== "Go" +![AVL 树的四种旋转情况](avl_tree.assets/avltree_rotation_cases.png) - ```go title="avl_tree.go" - /* 删除结点 */ - func (t *avlTree) remove(val int) *TreeNode { - root := removeHelper(t.root, val) - return root - } - - /* 递归删除结点(辅助函数) */ - func removeHelper(node *TreeNode, val int) *TreeNode { - if node == nil { - return nil - } - /* 1. 查找结点,并删除之 */ - if val < node.Val { - node.Left = removeHelper(node.Left, val) - } else if val > node.Val { - node.Right = removeHelper(node.Right, val) - } else { - if node.Left == nil || node.Right == nil { - child := node.Left - if node.Right != nil { - child = node.Right - } - // 子结点数量 = 0 ,直接删除 node 并返回 - if child == nil { - return nil - } else { - // 子结点数量 = 1 ,直接删除 node - node = child - } - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp := getInOrderNext(node.Right) - node.Right = removeHelper(node.Right, temp.Val) - node.Val = temp.Val - } - } - // 更新结点高度 - updateHeight(node) - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node) - // 返回子树的根节点 - return node - } - ``` +如下表所示,我们通过判断失衡节点的平衡因子以及较高一侧子节点的平衡因子的正负号,来确定失衡节点属于上图中的哪种情况。 -=== "JavaScript" +

  四种旋转情况的选择条件

- ```js title="avl_tree.js" - - ``` +| 失衡节点的平衡因子 | 子节点的平衡因子 | 应采用的旋转方法 | +| ------------------ | ---------------- | ---------------- | +| $> 1$ (左偏树) | $\geq 0$ | 右旋 | +| $> 1$ (左偏树) | $<0$ | 先左旋后右旋 | +| $< -1$ (右偏树) | $\leq 0$ | 左旋 | +| $< -1$ (右偏树) | $>0$ | 先右旋后左旋 | -=== "TypeScript" +为了便于使用,我们将旋转操作封装成一个函数。**有了这个函数,我们就能对各种失衡情况进行旋转,使失衡节点重新恢复平衡**。代码如下所示: - ```typescript title="avl_tree.ts" - - ``` +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{rotate} +``` -=== "C" +## AVL 树常用操作 - ```c title="avl_tree.c" - - ``` +### 插入节点 -=== "C#" +AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区别在于,在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。因此,**我们需要从这个节点开始,自底向上执行旋转操作,使所有失衡节点恢复平衡**。代码如下所示: - ```csharp title="avl_tree.cs" - /* 删除结点 */ - public TreeNode? remove(int val) - { - root = removeHelper(root, val); - return root; - } - - /* 递归删除结点(辅助函数) */ - private TreeNode? removeHelper(TreeNode? node, int val) - { - if (node == null) return null; - /* 1. 查找结点,并删除之 */ - if (val < node.val) - node.left = removeHelper(node.left, val); - else if (val > node.val) - node.right = removeHelper(node.right, val); - else - { - if (node.left == null || node.right == null) - { - TreeNode? child = node.left != null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child == null) - return null; - // 子结点数量 = 1 ,直接删除 node - else - node = child; - } - else - { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode? temp = getInOrderNext(node.right); - node.right = removeHelper(node.right, temp.val); - node.val = temp.val; - } - } - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根节点 - return node; - } - ``` +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} +``` -=== "Swift" +### 删除节点 - ```swift title="avl_tree.swift" +类似地,在二叉搜索树的删除节点方法的基础上,需要从底至顶执行旋转操作,使所有失衡节点恢复平衡。代码如下所示: - ``` +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} +``` -### 查找结点 +### 查找节点 -「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。 +AVL 树的节点查找操作与二叉搜索树一致,在此不再赘述。 ## AVL 树典型应用 -- 组织存储大型数据,适用于高频查找、低频增删场景; -- 用于建立数据库中的索引系统; - -!!! question "为什么红黑树比 AVL 树更受欢迎?" - 红黑树的平衡条件相对宽松,因此在红黑树中插入与删除结点所需的旋转操作相对更少,结点增删操作相比 AVL 树的效率更高。 +- 组织和存储大型数据,适用于高频查找、低频增删的场景。 +- 用于构建数据库中的索引系统。 +- 红黑树也是一种常见的平衡二叉搜索树。相较于 AVL 树,红黑树的平衡条件更宽松,插入与删除节点所需的旋转操作更少,节点增删操作的平均效率更高。 diff --git a/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png b/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png index b1c71f14e8..316b330892 100644 Binary files a/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png and b/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png b/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png index fcacaa0289..8d1b53fdfa 100644 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png and b/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png b/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png new file mode 100644 index 0000000000..9f787470f6 Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_insert.png b/docs/chapter_tree/binary_search_tree.assets/bst_insert.png index 8e64eac323..7b999e2750 100644 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_insert.png and b/docs/chapter_tree/binary_search_tree.assets/bst_insert.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png index fd9f5cde8c..c0e3f9e396 100644 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png and b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png index daf11618e8..ab2ce7db5f 100644 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png and b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_1.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_1.png deleted file mode 100644 index ce70ba3303..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_1.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_2.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_2.png deleted file mode 100644 index 1f1a2b99b6..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_2.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_3.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_3.png deleted file mode 100644 index d9ef7f7496..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_3.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_4.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_4.png deleted file mode 100644 index 43da8c11bb..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_4.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png new file mode 100644 index 0000000000..344f94c633 Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png new file mode 100644 index 0000000000..45b8bf15ad Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png new file mode 100644 index 0000000000..282043197f Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png new file mode 100644 index 0000000000..16ff39ed89 Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_1.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_1.png deleted file mode 100644 index 8257ef0782..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_search_1.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_2.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_2.png deleted file mode 100644 index 3cab42e564..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_search_2.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_3.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_3.png deleted file mode 100644 index 48d019dcce..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_search_3.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_4.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_4.png deleted file mode 100644 index a745e0a54a..0000000000 Binary files a/docs/chapter_tree/binary_search_tree.assets/bst_search_4.png and /dev/null differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png new file mode 100644 index 0000000000..ef9257130b Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png new file mode 100644 index 0000000000..73cc0a82ef Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png new file mode 100644 index 0000000000..2822887ac2 Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png differ diff --git a/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png b/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png new file mode 100644 index 0000000000..88706353d9 Binary files /dev/null and b/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png differ diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md old mode 100644 new mode 100755 index a0322154e7..4d6baf59c7 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -1,874 +1,129 @@ ---- -comments: true ---- - # 二叉搜索树 -「二叉搜索树 Binary Search Tree」满足以下条件: +如下图所示,二叉搜索树(binary search tree)满足以下条件。 -1. 对于根结点,左子树中所有结点的值 $<$ 根结点的值 $<$ 右子树中所有结点的值; -2. 任意结点的左子树和右子树也是二叉搜索树,即也满足条件 `1.` ; +1. 对于根节点,左子树中所有节点的值 $<$ 根节点的值 $<$ 右子树中所有节点的值。 +2. 任意节点的左、右子树也是二叉搜索树,即同样满足条件 `1.` 。 -![binary_search_tree](binary_search_tree.assets/binary_search_tree.png) +![二叉搜索树](binary_search_tree.assets/binary_search_tree.png) ## 二叉搜索树的操作 -### 查找结点 - -给定目标结点值 `num` ,可以根据二叉搜索树的性质来查找。我们声明一个结点 `cur` ,从二叉树的根结点 `root` 出发,循环比较结点值 `cur.val` 和 `num` 之间的大小关系 - -- 若 `cur.val < num` ,说明目标结点在 `cur` 的右子树中,因此执行 `cur = cur.right` ; -- 若 `cur.val > num` ,说明目标结点在 `cur` 的左子树中,因此执行 `cur = cur.left` ; -- 若 `cur.val = num` ,说明找到目标结点,跳出循环并返回该结点即可; - -=== "Step 1" - - ![bst_search_1](binary_search_tree.assets/bst_search_1.png) - -=== "Step 2" - - ![bst_search_2](binary_search_tree.assets/bst_search_2.png) - -=== "Step 3" - - ![bst_search_3](binary_search_tree.assets/bst_search_3.png) - -=== "Step 4" - - ![bst_search_4](binary_search_tree.assets/bst_search_4.png) - -二叉搜索树的查找操作和二分查找算法如出一辙,也是在每轮排除一半情况。循环次数最多为二叉树的高度,当二叉树平衡时,使用 $O(\log n)$ 时间。 - -=== "Java" - - ```java title="binary_search_tree.java" - /* 查找结点 */ - TreeNode search(int num) { - TreeNode cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != null) { - // 目标结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 root 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "C++" - - ```cpp title="binary_search_tree.cpp" - /* 查找结点 */ - TreeNode* search(int num) { - TreeNode* cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 目标结点在 root 的右子树中 - if (cur->val < num) cur = cur->right; - // 目标结点在 root 的左子树中 - else if (cur->val > num) cur = cur->left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "Python" - - ```python title="binary_search_tree.py" - """ 查找结点 """ - def search(self, num: int) -> Optional[TreeNode]: - cur = self.root - # 循环查找,越过叶结点后跳出 - while cur is not None: - # 目标结点在 root 的右子树中 - if cur.val < num: - cur = cur.right - # 目标结点在 root 的左子树中 - elif cur.val > num: - cur = cur.left - # 找到目标结点,跳出循环 - else: - break - return cur - ``` - -=== "Go" - - ```go title="binary_search_tree.go" - /* 查找结点 */ - func (bst *binarySearchTree) search(num int) *TreeNode { - node := bst.root - // 循环查找,越过叶结点后跳出 - for node != nil { - if node.Val < num { - // 目标结点在 root 的右子树中 - node = node.Right - } else if node.Val > num { - // 目标结点在 root 的左子树中 - node = node.Left - } else { - // 找到目标结点,跳出循环 - break - } - } - // 返回目标结点 - return node - } - ``` - -=== "JavaScript" - - ```js title="binary_search_tree.js" - /* 查找结点 */ - function search(num) { - let cur = root; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 目标结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 root 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search_tree.ts" - /* 查找结点 */ - function search(num: number): TreeNode | null { - let cur = root; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - if (cur.val < num) { - cur = cur.right; // 目标结点在 root 的右子树中 - } else if (cur.val > num) { - cur = cur.left; // 目标结点在 root 的左子树中 - } else { - break; // 找到目标结点,跳出循环 - } - } - // 返回目标结点 - return cur; - } - ``` - -=== "C" - - ```c title="binary_search_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 查找结点 */ - TreeNode? search(int num) - { - TreeNode? cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 目标结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 root 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - - ``` - -### 插入结点 - -给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步: - -1. **查找插入位置**:与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环; -2. **在该位置插入结点**:初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置 ; - -二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。 - -![bst_insert](binary_search_tree.assets/bst_insert.png) - -=== "Java" - - ```java title="binary_search_tree.java" - /* 插入结点 */ - TreeNode insert(int num) { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) { - // 找到重复结点,直接返回 - if (cur.val == num) return null; - pre = cur; - // 插入位置在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 root 的左子树中 - else cur = cur.left; - } - // 插入结点 val - TreeNode node = new TreeNode(num); - if (pre.val < num) pre.right = node; - else pre.left = node; - return node; - } - ``` - -=== "C++" - - ```cpp title="binary_search_tree.cpp" - /* 插入结点 */ - TreeNode* insert(int num) { - // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; - TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 找到重复结点,直接返回 - if (cur->val == num) return nullptr; - pre = cur; - // 插入位置在 root 的右子树中 - if (cur->val < num) cur = cur->right; - // 插入位置在 root 的左子树中 - else cur = cur->left; - } - // 插入结点 val - TreeNode* node = new TreeNode(num); - if (pre->val < num) pre->right = node; - else pre->left = node; - return node; - } - ``` - -=== "Python" - - ```python title="binary_search_tree.py" - """ 插入结点 """ - def insert(self, num: int) -> Optional[TreeNode]: - root = self.root - # 若树为空,直接提前返回 - if root is None: - return None - - cur = root - pre = None - - # 循环查找,越过叶结点后跳出 - while cur is not None: - # 找到重复结点,直接返回 - if cur.val == num: - return None - pre = cur - - if cur.val < num: # 插入位置在 root 的右子树中 - cur = cur.right - else: # 插入位置在 root 的左子树中 - cur = cur.left - - # 插入结点 val - node = TreeNode(num) - if pre.val < num: - pre.right = node - else: - pre.left = node - return node - ``` - -=== "Go" - - ```go title="binary_search_tree.go" - /* 插入结点 */ - func (bst *binarySearchTree) insert(num int) *TreeNode { - cur := bst.root - // 若树为空,直接提前返回 - if cur == nil { - return nil - } - // 待插入结点之前的结点位置 - var prev *TreeNode = nil - // 循环查找,越过叶结点后跳出 - for cur != nil { - if cur.Val == num { - return nil - } - prev = cur - if cur.Val < num { - cur = cur.Right - } else { - cur = cur.Left - } - } - // 插入结点 - node := NewTreeNode(num) - if prev.Val < num { - prev.Right = node - } else { - prev.Left = node - } - return cur - } - ``` - -=== "JavaScript" - - ```js title="binary_search_tree.js" - /* 插入结点 */ - function insert(num) { - // 若树为空,直接提前返回 - if (root === null) return null; - let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到重复结点,直接返回 - if (cur.val === num) return null; - pre = cur; - // 插入位置在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 root 的左子树中 - else cur = cur.left; - } - // 插入结点 val - let node = new Tree.TreeNode(num); - if (pre.val < num) pre.right = node; - else pre.left = node; - return node; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search_tree.ts" - /* 插入结点 */ - function insert(num: number): TreeNode | null { - // 若树为空,直接提前返回 - if (root === null) { - return null; - } - let cur = root, - pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - if (cur.val === num) { - return null; // 找到重复结点,直接返回 - } - pre = cur; - if (cur.val < num) { - cur = cur.right as TreeNode; // 插入位置在 root 的右子树中 - } else { - cur = cur.left as TreeNode; // 插入位置在 root 的左子树中 - } - } - // 插入结点 val - let node = new TreeNode(num); - if (pre!.val < num) { - pre!.right = node; - } else { - pre!.left = node; - } - return node; - } - ``` - -=== "C" - - ```c title="binary_search_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 插入结点 */ - TreeNode? insert(int num) - { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 找到重复结点,直接返回 - if (cur.val == num) return null; - pre = cur; - // 插入位置在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 root 的左子树中 - else cur = cur.left; - } - - // 插入结点 val - TreeNode node = new TreeNode(num); - if (pre != null) - { - if (pre.val < num) pre.right = node; - else pre.left = node; - } - return node; - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - - ``` - -为了插入结点,需要借助 **辅助结点 `prev`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。 - -与查找结点相同,插入结点使用 $O(\log n)$ 时间。 - -### 删除结点 - -与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况: - -**当待删除结点的子结点数量 $= 0$ 时**,表明待删除结点是叶结点,直接删除即可。 - -![bst_remove_case1](binary_search_tree.assets/bst_remove_case1.png) - -**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。 - -![bst_remove_case2](binary_search_tree.assets/bst_remove_case2.png) - -**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步: - -1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex` ; -2. 在树中递归删除结点 `nex` ; -3. 使用 `nex` 替换待删除结点; - -=== "Step 1" - ![bst_remove_case3_1](binary_search_tree.assets/bst_remove_case3_1.png) - -=== "Step 2" - ![bst_remove_case3_2](binary_search_tree.assets/bst_remove_case3_2.png) - -=== "Step 3" - ![bst_remove_case3_3](binary_search_tree.assets/bst_remove_case3_3.png) - -=== "Step 4" - ![bst_remove_case3_4](binary_search_tree.assets/bst_remove_case3_4.png) - -删除结点操作也使用 $O(\log n)$ 时间,其中查找待删除结点 $O(\log n)$ ,获取中序遍历后继结点 $O(\log n)$ 。 - -=== "Java" - - ```java title="binary_search_tree.java" - /* 删除结点 */ - TreeNode remove(int num) { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) { - // 找到待删除结点,跳出循环 - if (cur.val == num) break; - pre = cur; - // 待删除结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 root 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur == null) return null; - // 子结点数量 = 0 or 1 - if (cur.left == null || cur.right == null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - TreeNode child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left == cur) pre.left = child; - else pre.right = child; - // 释放内存 - delete cur; - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - TreeNode nex = getInOrderNext(cur.right); - int tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - return cur; - } - ``` - -=== "C++" - - ```cpp title="binary_search_tree.cpp" - /* 删除结点 */ - TreeNode* remove(int num) { - // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; - TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 找到待删除结点,跳出循环 - if (cur->val == num) break; - pre = cur; - // 待删除结点在 root 的右子树中 - if (cur->val < num) cur = cur->right; - // 待删除结点在 root 的左子树中 - else cur = cur->left; - } - // 若无待删除结点,则直接返回 - if (cur == nullptr) return nullptr; - // 子结点数量 = 0 or 1 - if (cur->left == nullptr || cur->right == nullptr) { - // 当子结点数量 = 0 / 1 时, child = nullptr / 该子结点 - TreeNode* child = cur->left != nullptr ? cur->left : cur->right; - // 删除结点 cur - if (pre->left == cur) pre->left = child; - else pre->right = child; - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - TreeNode* nex = getInOrderNext(cur->right); - int tmp = nex->val; - // 递归删除结点 nex - remove(nex->val); - // 将 nex 的值复制给 cur - cur->val = tmp; - } - return cur; - } - ``` - -=== "Python" - - ```python title="binary_search_tree.py" - """ 删除结点 """ - def remove(self, num: int) -> Optional[TreeNode]: - root = self.root - # 若树为空,直接提前返回 - if root is None: - return None - - cur = root - pre = None - - # 循环查找,越过叶结点后跳出 - while cur is not None: - # 找到待删除结点,跳出循环 - if cur.val == num: - break - pre = cur - if cur.val < num: # 待删除结点在 root 的右子树中 - cur = cur.right - else: # 待删除结点在 root 的左子树中 - cur = cur.left - - # 若无待删除结点,则直接返回 - if cur is None: - return None - - # 子结点数量 = 0 or 1 - if cur.left is None or cur.right is None: - # 当子结点数量 = 0 / 1 时, child = null / 该子结点 - child = cur.left or cur.right - # 删除结点 cur - if pre.left == cur: - pre.left = child - else: - pre.right = child - # 子结点数量 = 2 - else: - # 获取中序遍历中 cur 的下一个结点 - nex = self.get_inorder_next(cur.right) - tmp = nex.val - # 递归删除结点 nex - self.remove(nex.val) - # 将 nex 的值复制给 cur - cur.val = tmp - return cur - ``` - -=== "Go" - - ```go title="binary_search_tree.go" - /* 删除结点 */ - func (bst *binarySearchTree) remove(num int) *TreeNode { - cur := bst.root - // 若树为空,直接提前返回 - if cur == nil { - return nil - } - // 待删除结点之前的结点位置 - var prev *TreeNode = nil - // 循环查找,越过叶结点后跳出 - for cur != nil { - if cur.Val == num { - break - } - prev = cur - if cur.Val < num { - // 待删除结点在右子树中 - cur = cur.Right - } else { - // 待删除结点在左子树中 - cur = cur.Left - } - } - // 若无待删除结点,则直接返回 - if cur == nil { - return nil - } - // 子结点数为 0 或 1 - if cur.Left == nil || cur.Right == nil { - var child *TreeNode = nil - // 取出待删除结点的子结点 - if cur.Left != nil { - child = cur.Left - } else { - child = cur.Right - } - // 将子结点替换为待删除结点 - if prev.Left == cur { - prev.Left = child - } else { - prev.Right = child - } - // 子结点数为 2 - } else { - // 获取中序遍历中待删除结点 cur 的下一个结点 - next := bst.getInOrderNext(cur) - temp := next.Val - // 递归删除结点 next - bst.remove(next.Val) - // 将 next 的值复制给 cur - cur.Val = temp - } - return cur - } - ``` - -=== "JavaScript" - - ```js title="binary_search_tree.js" - /* 删除结点 */ - function remove(num) { - // 若树为空,直接提前返回 - if (root === null) return null; - let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到待删除结点,跳出循环 - if (cur.val === num) break; - pre = cur; - // 待删除结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 root 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur === null) return null; - // 子结点数量 = 0 or 1 - if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left === cur) pre.left = child; - else pre.right = child; - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - let nex = getInOrderNext(cur.right); - let tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - return cur; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search_tree.ts" - /* 删除结点 */ - function remove(num: number): TreeNode | null { - // 若树为空,直接提前返回 - if (root === null) { - return null; - } - let cur = root, - pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到待删除结点,跳出循环 - if (cur.val === num) { - break; - } - pre = cur; - if (cur.val < num) { - cur = cur.right as TreeNode; // 待删除结点在 root 的右子树中 - } else { - cur = cur.left as TreeNode; // 待删除结点在 root 的左子树中 - } - } - // 若无待删除结点,则直接返回 - if (cur === null) { - return null; - } - // 子结点数量 = 0 or 1 - if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur - if (pre!.left === cur) { - pre!.left = child; - } else { - pre!.right = child; - } - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - let next = getInOrderNext(cur.right); - let tmp = next!.val; - // 递归删除结点 nex - remove(next!.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - return cur; - } - ``` - -=== "C" - - ```c title="binary_search_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 删除结点 */ - TreeNode? remove(int num) - { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 找到待删除结点,跳出循环 - if (cur.val == num) break; - pre = cur; - // 待删除结点在 root 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 root 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur == null || pre == null) return null; - // 子结点数量 = 0 or 1 - if (cur.left == null || cur.right == null) - { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - TreeNode? child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left == cur) - { - pre.left = child; - } - else - { - pre.right = child; - } - } - // 子结点数量 = 2 - else - { - // 获取中序遍历中 cur 的下一个结点 - TreeNode? nex = getInOrderNext(cur.right); - if (nex != null) - { - int tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - } - return cur; - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - - ``` - -## 二叉搜索树的优势 - -假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: - -- **查找元素**:由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间; -- **插入元素**:只需将元素添加至数组尾部即可,使用 $O(1)$ 时间; -- **删除元素**:先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素**:需要遍历数组来确定,使用 $O(n)$ 时间; - -为了得到先验信息,我们也可以预先将数组元素进行排序,得到一个「排序数组」,此时操作效率为: - -- **查找元素**:由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间; -- **插入元素**:先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间; -- **删除元素**:先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素**:数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间; - -观察发现,无序数组和有序数组中的各项操作的时间复杂度是“偏科”的,即有的快有的慢;**而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 很大时有巨大优势**。 - -
- -| | 无序数组 | 有序数组 | 二叉搜索树 | -| ------------------- | -------- | ----------- | ----------- | -| 查找指定元素 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | -| 插入元素 | $O(1)$ | $O(n)$ | $O(\log n)$ | -| 删除元素 | $O(n)$ | $O(n)$ | $O(\log n)$ | -| 获取最小 / 最大元素 | $O(n)$ | $O(1)$ | $O(\log n)$ | - -
- -## 二叉搜索树的退化 - -理想情况下,我们希望二叉搜索树的是“左右平衡”的(详见「平衡二叉树」章节),此时可以在 $\log n$ 轮循环内查找任意结点。 - -如果我们动态地在二叉搜索树中插入与删除结点,**则可能导致二叉树退化为链表**,此时各种操作的时间复杂度也退化之 $O(n)$ 。 - -!!! note - - 在实际应用中,如何保持二叉搜索树的平衡,也是一个需要重要考虑的问题。 - -![bst_degradation](binary_search_tree.assets/bst_degradation.png) +我们将二叉搜索树封装为一个类 `BinarySearchTree` ,并声明一个成员变量 `root` ,指向树的根节点。 + +### 查找节点 + +给定目标节点值 `num` ,可以根据二叉搜索树的性质来查找。如下图所示,我们声明一个节点 `cur` ,从二叉树的根节点 `root` 出发,循环比较节点值 `cur.val` 和 `num` 之间的大小关系。 + +- 若 `cur.val < num` ,说明目标节点在 `cur` 的右子树中,因此执行 `cur = cur.right` 。 +- 若 `cur.val > num` ,说明目标节点在 `cur` 的左子树中,因此执行 `cur = cur.left` 。 +- 若 `cur.val = num` ,说明找到目标节点,跳出循环并返回该节点。 + +=== "<1>" + ![二叉搜索树查找节点示例](binary_search_tree.assets/bst_search_step1.png) + +=== "<2>" + ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) + +=== "<3>" + ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) + +=== "<4>" + ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) + +二叉搜索树的查找操作与二分查找算法的工作原理一致,都是每轮排除一半情况。循环次数最多为二叉树的高度,当二叉树平衡时,使用 $O(\log n)$ 时间。示例代码如下: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} +``` + +### 插入节点 + +给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根节点 < 右子树”的性质,插入操作流程如下图所示。 + +1. **查找插入位置**:与查找操作相似,从根节点出发,根据当前节点值和 `num` 的大小关系循环向下搜索,直到越过叶节点(遍历至 `None` )时跳出循环。 +2. **在该位置插入节点**:初始化节点 `num` ,将该节点置于 `None` 的位置。 + +![在二叉搜索树中插入节点](binary_search_tree.assets/bst_insert.png) + +在代码实现中,需要注意以下两点。 + +- 二叉搜索树不允许存在重复节点,否则将违反其定义。因此,若待插入节点在树中已存在,则不执行插入,直接返回。 +- 为了实现插入节点,我们需要借助节点 `pre` 保存上一轮循环的节点。这样在遍历至 `None` 时,我们可以获取到其父节点,从而完成节点插入操作。 + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} +``` + +与查找节点相同,插入节点使用 $O(\log n)$ 时间。 + +### 删除节点 + +先在二叉树中查找到目标节点,再将其删除。与插入节点类似,我们需要保证在删除操作完成后,二叉搜索树的“左子树 < 根节点 < 右子树”的性质仍然满足。因此,我们根据目标节点的子节点数量,分 0、1 和 2 三种情况,执行对应的删除节点操作。 + +如下图所示,当待删除节点的度为 $0$ 时,表示该节点是叶节点,可以直接删除。 + +![在二叉搜索树中删除节点(度为 0 )](binary_search_tree.assets/bst_remove_case1.png) + +如下图所示,当待删除节点的度为 $1$ 时,将待删除节点替换为其子节点即可。 + +![在二叉搜索树中删除节点(度为 1 )](binary_search_tree.assets/bst_remove_case2.png) + +当待删除节点的度为 $2$ 时,我们无法直接删除它,而需要使用一个节点替换该节点。由于要保持二叉搜索树“左子树 $<$ 根节点 $<$ 右子树”的性质,**因此这个节点可以是右子树的最小节点或左子树的最大节点**。 + +假设我们选择右子树的最小节点(中序遍历的下一个节点),则删除操作流程如下图所示。 + +1. 找到待删除节点在“中序遍历序列”中的下一个节点,记为 `tmp` 。 +2. 用 `tmp` 的值覆盖待删除节点的值,并在树中递归删除节点 `tmp` 。 + +=== "<1>" + ![在二叉搜索树中删除节点(度为 2 )](binary_search_tree.assets/bst_remove_case3_step1.png) + +=== "<2>" + ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) + +=== "<3>" + ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) + +=== "<4>" + ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) + +删除节点操作同样使用 $O(\log n)$ 时间,其中查找待删除节点需要 $O(\log n)$ 时间,获取中序遍历后继节点需要 $O(\log n)$ 时间。示例代码如下: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} +``` + +### 中序遍历有序 + +如下图所示,二叉树的中序遍历遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的遍历顺序,而二叉搜索树满足“左子节点 $<$ 根节点 $<$ 右子节点”的大小关系。 + +这意味着在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小节点,从而得出一个重要性质:**二叉搜索树的中序遍历序列是升序的**。 + +利用中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,无须进行额外的排序操作,非常高效。 + +![二叉搜索树的中序遍历序列](binary_search_tree.assets/bst_inorder_traversal.png) + +## 二叉搜索树的效率 + +给定一组数据,我们考虑使用数组或二叉搜索树存储。观察下表,二叉搜索树的各项操作的时间复杂度都是对数阶,具有稳定且高效的性能。只有在高频添加、低频查找删除数据的场景下,数组比二叉搜索树的效率更高。 + +

  数组与搜索树的效率对比

+ +| | 无序数组 | 二叉搜索树 | +| -------- | -------- | ----------- | +| 查找元素 | $O(n)$ | $O(\log n)$ | +| 插入元素 | $O(1)$ | $O(\log n)$ | +| 删除元素 | $O(n)$ | $O(\log n)$ | + +在理想情况下,二叉搜索树是“平衡”的,这样就可以在 $\log n$ 轮循环内查找任意节点。 + +然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为下图所示的链表,这时各种操作的时间复杂度也会退化为 $O(n)$ 。 + +![二叉搜索树退化](binary_search_tree.assets/bst_degradation.png) ## 二叉搜索树常见应用 -- 系统中的多级索引,高效查找、插入、删除操作。 -- 各种搜索算法的底层数据结构。 -- 存储数据流,保持其已排序。 +- 用作系统中的多级索引,实现高效的查找、插入、删除操作。 +- 作为某些搜索算法的底层数据结构。 +- 用于存储数据流,以保持其有序状态。 diff --git a/docs/chapter_tree/binary_tree.assets/array_representation_complete_binary_tree.png b/docs/chapter_tree/binary_tree.assets/array_representation_complete_binary_tree.png deleted file mode 100644 index f369d5d580..0000000000 Binary files a/docs/chapter_tree/binary_tree.assets/array_representation_complete_binary_tree.png and /dev/null differ diff --git a/docs/chapter_tree/binary_tree.assets/array_representation_mapping.png b/docs/chapter_tree/binary_tree.assets/array_representation_mapping.png deleted file mode 100644 index 5c51843bc6..0000000000 Binary files a/docs/chapter_tree/binary_tree.assets/array_representation_mapping.png and /dev/null differ diff --git a/docs/chapter_tree/binary_tree.assets/array_representation_with_empty.png b/docs/chapter_tree/binary_tree.assets/array_representation_with_empty.png deleted file mode 100644 index 7d1be8dd1f..0000000000 Binary files a/docs/chapter_tree/binary_tree.assets/array_representation_with_empty.png and /dev/null differ diff --git a/docs/chapter_tree/binary_tree.assets/array_representation_without_empty.png b/docs/chapter_tree/binary_tree.assets/array_representation_without_empty.png deleted file mode 100644 index d0f5d9bc1c..0000000000 Binary files a/docs/chapter_tree/binary_tree.assets/array_representation_without_empty.png and /dev/null differ diff --git a/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png b/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png index afc08723f0..64120ef5bb 100644 Binary files a/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png and b/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png differ diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png index d95c48f078..a0a9452723 100644 Binary files a/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png and b/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png differ diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png b/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png new file mode 100644 index 0000000000..acb42ce2f4 Binary files /dev/null and b/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png differ diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_corner_cases.png b/docs/chapter_tree/binary_tree.assets/binary_tree_corner_cases.png deleted file mode 100644 index 6832f68c93..0000000000 Binary files a/docs/chapter_tree/binary_tree.assets/binary_tree_corner_cases.png and /dev/null differ diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png b/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png index 94252aff88..51eed42776 100644 Binary files a/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png and b/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png differ diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png b/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png index 08c0ccf20d..082bab825d 100644 Binary files a/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png and b/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png differ diff --git a/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png b/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png index 8d26549a17..74c033f28f 100644 Binary files a/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png and b/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png differ diff --git a/docs/chapter_tree/binary_tree.assets/full_binary_tree.png b/docs/chapter_tree/binary_tree.assets/full_binary_tree.png index 37043df953..e0420545a0 100644 Binary files a/docs/chapter_tree/binary_tree.assets/full_binary_tree.png and b/docs/chapter_tree/binary_tree.assets/full_binary_tree.png differ diff --git a/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png b/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png index 95f7194beb..fea729b177 100644 Binary files a/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png and b/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png differ diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 0201f6a2ac..6635e0d135 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -1,89 +1,153 @@ ---- -comments: true ---- - # 二叉树 -「二叉树 Binary Tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。类似于链表,二叉树也是以结点为单位存储的,结点包含「值」和两个「指针」。 +二叉树(binary tree)是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。 -=== "Java" +=== "Python" - ```java title="" - /* 链表结点类 */ - class TreeNode { - int val; // 结点值 - TreeNode left; // 左子结点指针 - TreeNode right; // 右子结点指针 - TreeNode(int x) { val = x; } - } + ```python title="" + class TreeNode: + """二叉树节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.left: TreeNode | None = None # 左子节点引用 + self.right: TreeNode | None = None # 右子节点引用 ``` === "C++" ```cpp title="" - /* 链表结点结构体 */ + /* 二叉树节点结构体 */ struct TreeNode { - int val; // 结点值 - TreeNode *left; // 左子结点指针 - TreeNode *right; // 右子结点指针 + int val; // 节点值 + TreeNode *left; // 左子节点指针 + TreeNode *right; // 右子节点指针 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; ``` -=== "Python" +=== "Java" - ```python title="" - """ 链表结点类 """ - class TreeNode: - def __init__(self, val=None, left=None, right=None): - self.val = val # 结点值 - self.left = left # 左子结点指针 - self.right = right # 右子结点指针 + ```java title="" + /* 二叉树节点类 */ + class TreeNode { + int val; // 节点值 + TreeNode left; // 左子节点引用 + TreeNode right; // 右子节点引用 + TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* 二叉树节点类 */ + class TreeNode(int? x) { + public int? val = x; // 节点值 + public TreeNode? left; // 左子节点引用 + public TreeNode? right; // 右子节点引用 + } ``` === "Go" ```go title="" - /* 链表结点类 */ + /* 二叉树节点结构体 */ type TreeNode struct { Val int Left *TreeNode Right *TreeNode } - /* 结点初始化方法 */ + /* 构造方法 */ func NewTreeNode(v int) *TreeNode { return &TreeNode{ - Left: nil, - Right: nil, - Val: v, + Left: nil, // 左子节点指针 + Right: nil, // 右子节点指针 + Val: v, // 节点值 + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 二叉树节点类 */ + class TreeNode { + var val: Int // 节点值 + var left: TreeNode? // 左子节点引用 + var right: TreeNode? // 右子节点引用 + + init(x: Int) { + val = x } } ``` -=== "JavaScript" +=== "JS" - ```js title="" - /* 链表结点类 */ - function TreeNode(val, left, right) { - this.val = (val === undefined ? 0 : val); // 结点值 - this.left = (left === undefined ? null : left); // 左子结点指针 - this.right = (right === undefined ? null : right); // 右子结点指针 + ```javascript title="" + /* 二叉树节点类 */ + class TreeNode { + val; // 节点值 + left; // 左子节点指针 + right; // 右子节点指针 + constructor(val, left, right) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } } ``` -=== "TypeScript" +=== "TS" ```typescript title="" - /* 链表结点类 */ + /* 二叉树节点类 */ class TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; - + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.left = left === undefined ? null : left; // 左子结点指针 - this.right = right === undefined ? null : right; // 右子结点指针 + this.val = val === undefined ? 0 : val; // 节点值 + this.left = left === undefined ? null : left; // 左子节点引用 + this.right = right === undefined ? null : right; // 右子节点引用 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 二叉树节点类 */ + class TreeNode { + int val; // 节点值 + TreeNode? left; // 左子节点引用 + TreeNode? right; // 右子节点引用 + TreeNode(this.val, [this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 二叉树节点结构体 */ + struct TreeNode { + val: i32, // 节点值 + left: Option>>, // 左子节点引用 + right: Option>>, // 右子节点引用 + } + + impl TreeNode { + /* 构造方法 */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + left: None, + right: None + })) } } ``` @@ -91,438 +155,538 @@ comments: true === "C" ```c title="" - + /* 二叉树节点结构体 */ + typedef struct TreeNode { + int val; // 节点值 + int height; // 节点高度 + struct TreeNode *left; // 左子节点指针 + struct TreeNode *right; // 右子节点指针 + } TreeNode; + + /* 构造函数 */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } ``` -=== "C#" +=== "Kotlin" - ```csharp title="" - /* 链表结点类 */ - class TreeNode { - int val; // 结点值 - TreeNode? left; // 左子结点指针 - TreeNode? right; // 右子结点指针 - TreeNode(int x) { val = x; } + ```kotlin title="" + /* 二叉树节点类 */ + class TreeNode(val _val: Int) { // 节点值 + val left: TreeNode? = null // 左子节点引用 + val right: TreeNode? = null // 右子节点引用 } ``` -=== "Swift" +=== "Ruby" - ```swift title="" + ```ruby title="" + ### 二叉树节点类 ### + class TreeNode + attr_accessor :val # 节点值 + attr_accessor :left # 左子节点引用 + attr_accessor :right # 右子节点引用 + def initialize(val) + @val = val + end + end ``` -结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。 +=== "Zig" -除了叶结点外,每个结点都有子结点和子树。例如,若将下图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。 + ```zig title="" -![binary_tree_definition](binary_tree.assets/binary_tree_definition.png) + ``` -

Fig. 子结点与子树

+每个节点都有两个引用(指针),分别指向左子节点(left-child node)右子节点(right-child node),该节点被称为这两个子节点的父节点(parent node)。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树(left subtree),同理可得右子树(right subtree)。 -## 二叉树常见术语 +**在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如下图所示,如果将“节点 2”视为父节点,则其左子节点和右子节点分别是“节点 4”和“节点 5”,左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。 -二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。 +![父节点、子节点、子树](binary_tree.assets/binary_tree_definition.png) -- 「根结点 Root Node」:二叉树最顶层的结点,其没有父结点; -- 「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 $\text{null}$ ; -- 结点所处「层 Level」:从顶至底依次增加,根结点所处层为 1 ; -- 结点「度 Degree」:结点的子结点数量。二叉树中,度的范围是 0, 1, 2 ; -- 「边 Edge」:连接两个结点的边,即结点指针; -- 二叉树「高度」:二叉树中根结点到最远叶结点走过边的数量; -- 结点「深度 Depth」 :根结点到该结点走过边的数量; -- 结点「高度 Height」:最远叶结点到该结点走过边的数量; +## 二叉树常见术语 -![binary_tree_terminology](binary_tree.assets/binary_tree_terminology.png) +二叉树的常用术语如下图所示。 -

Fig. 二叉树的常见术语

+- 根节点(root node):位于二叉树顶层的节点,没有父节点。 +- 叶节点(leaf node):没有子节点的节点,其两个指针均指向 `None` 。 +- 边(edge):连接两个节点的线段,即节点引用(指针)。 +- 节点所在的层(level):从顶至底递增,根节点所在层为 1 。 +- 节点的度(degree):节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。 +- 二叉树的高度(height):从根节点到最远叶节点所经过的边的数量。 +- 节点的深度(depth):从根节点到该节点所经过的边的数量。 +- 节点的高度(height):从距离该节点最远的叶节点到该节点所经过的边的数量。 -!!! tip "高度与深度的定义" +![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png) - 值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。 +!!! tip + + 请注意,我们通常将“高度”和“深度”定义为“经过的边的数量”,但有些题目或教材可能会将其定义为“经过的节点的数量”。在这种情况下,高度和深度都需要加 1 。 ## 二叉树基本操作 -**初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。 +### 初始化二叉树 -=== "Java" +与链表类似,首先初始化节点,然后构建引用(指针)。 - ```java title="binary_tree.java" - // 初始化结点 - TreeNode n1 = new TreeNode(1); - TreeNode n2 = new TreeNode(2); - TreeNode n3 = new TreeNode(3); - TreeNode n4 = new TreeNode(4); - TreeNode n5 = new TreeNode(5); - // 构建引用指向(即指针) - n1.left = n2; - n1.right = n3; - n2.left = n4; - n2.right = n5; +=== "Python" + + ```python title="binary_tree.py" + # 初始化二叉树 + # 初始化节点 + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 ``` === "C++" ```cpp title="binary_tree.cpp" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 TreeNode* n1 = new TreeNode(1); TreeNode* n2 = new TreeNode(2); TreeNode* n3 = new TreeNode(3); TreeNode* n4 = new TreeNode(4); TreeNode* n5 = new TreeNode(5); - // 构建引用指向(即指针) + // 构建节点之间的引用(指针) n1->left = n2; n1->right = n3; n2->left = n4; n2->right = n5; ``` -=== "Python" +=== "Java" - ```python title="binary_tree.py" - """ 初始化二叉树 """ - # 初始化节点 - n1 = TreeNode(val=1) - n2 = TreeNode(val=2) - n3 = TreeNode(val=3) - n4 = TreeNode(val=4) - n5 = TreeNode(val=5) - # 构建引用指向(即指针) - n1.left = n2 - n1.right = n3 - n2.left = n4 - n2.right = n5 + ```java title="binary_tree.java" + // 初始化节点 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 构建节点之间的引用(指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* 初始化二叉树 */ + // 初始化节点 + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // 构建节点之间的引用(指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; ``` === "Go" ```go title="binary_tree.go" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) - // 构建引用指向(即指针) + // 构建节点之间的引用(指针) n1.Left = n2 n1.Right = n3 n2.Left = n4 n2.Right = n5 ``` -=== "JavaScript" +=== "Swift" + + ```swift title="binary_tree.swift" + // 初始化节点 + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "JS" - ```js title="binary_tree.js" + ```javascript title="binary_tree.js" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); - // 构建引用指向(即指针) + // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` -=== "TypeScript" +=== "TS" ```typescript title="binary_tree.ts" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); - // 构建引用指向(即指针) + // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` -=== "C" +=== "Dart" - ```c title="binary_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree.cs" + ```dart title="binary_tree.dart" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); - // 构建引用指向(即指针) + // 构建节点之间的引用(指针) n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5; ``` -=== "Swift" - - ```swift title="binary_tree.swift" +=== "Rust" + ```rust title="binary_tree.rs" + // 初始化节点 + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // 构建节点之间的引用(指针) + n1.borrow_mut().left = Some(n2.clone()); + n1.borrow_mut().right = Some(n3); + n2.borrow_mut().left = Some(n4); + n2.borrow_mut().right = Some(n5); ``` -**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。 +=== "C" -![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png) + ```c title="binary_tree.c" + /* 初始化二叉树 */ + // 初始化节点 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // 构建节点之间的引用(指针) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` -

Fig. 在二叉树中插入与删除结点

+=== "Kotlin" -=== "Java" + ```kotlin title="binary_tree.kt" + // 初始化节点 + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` - ```java title="binary_tree.java" - TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1.left = P; - P.left = n2; - // 删除结点 P - n1.left = n2; +=== "Ruby" + + ```ruby title="binary_tree.rb" + # 初始化二叉树 + # 初始化节点 + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # 构建节点之间的引用(指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 ``` -=== "C++" +=== "Zig" + + ```zig title="binary_tree.zig" - ```cpp title="binary_tree.cpp" - /* 插入与删除结点 */ - TreeNode* P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1->left = P; - P->left = n2; - // 删除结点 P - n1->left = n2; ``` +??? pythontutor "可视化运行" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 插入与删除节点 + +与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。下图给出了一个示例。 + +![在二叉树中插入与删除节点](binary_tree.assets/binary_tree_add_remove.png) + === "Python" ```python title="binary_tree.py" - """ 插入与删除结点 """ + # 插入与删除节点 p = TreeNode(0) - # 在 n1 -> n2 中间插入结点 P + # 在 n1 -> n2 中间插入节点 P n1.left = p p.left = n2 # 删除节点 P n1.left = n2 ``` +=== "C++" + + ```cpp title="binary_tree.cpp" + /* 插入与删除节点 */ + TreeNode* P = new TreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1->left = P; + P->left = n2; + // 删除节点 P + n1->left = n2; + // 释放内存 + delete P; + ``` + +=== "Java" + + ```java title="binary_tree.java" + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1.left = P; + P.left = n2; + // 删除节点 P + n1.left = n2; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* 插入与删除节点 */ + TreeNode P = new(0); + // 在 n1 -> n2 中间插入节点 P + n1.left = P; + P.left = n2; + // 删除节点 P + n1.left = n2; + ``` + === "Go" ```go title="binary_tree.go" - /* 插入与删除结点 */ - // 在 n1 -> n2 中间插入结点 P + /* 插入与删除节点 */ + // 在 n1 -> n2 中间插入节点 P p := NewTreeNode(0) n1.Left = p p.Left = n2 - // 删除结点 P + // 删除节点 P n1.Left = n2 ``` -=== "JavaScript" +=== "Swift" - ```js title="binary_tree.js" - /* 插入与删除结点 */ + ```swift title="binary_tree.swift" + let P = TreeNode(x: 0) + // 在 n1 -> n2 中间插入节点 P + n1.left = P + P.left = n2 + // 删除节点 P + n1.left = n2 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* 插入与删除节点 */ let P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - // 删除结点 P + // 删除节点 P n1.left = n2; ``` -=== "TypeScript" +=== "TS" ```typescript title="binary_tree.ts" - /* 插入与删除结点 */ + /* 插入与删除节点 */ const P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - // 删除结点 P + // 删除节点 P n1.left = n2; ``` -=== "C" - - ```c title="binary_tree.c" - - ``` - -=== "C#" +=== "Dart" - ```csharp title="binary_tree.cs" - /* 插入与删除结点 */ + ```dart title="binary_tree.dart" + /* 插入与删除节点 */ TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - // 删除结点 P + // 删除节点 P n1.left = n2; ``` -=== "Swift" - - ```swift title="binary_tree.swift" +=== "Rust" + ```rust title="binary_tree.rs" + let p = TreeNode::new(0); + // 在 n1 -> n2 中间插入节点 P + n1.borrow_mut().left = Some(p.clone()); + p.borrow_mut().left = Some(n2.clone()); + // 删除节点 p + n1.borrow_mut().left = Some(n2); ``` -!!! note - - 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 - -## 常见二叉树类型 - -### 完美二叉树 - -「完美二叉树 Perfect Binary Tree」的所有层的结点都被完全填满。在完美二叉树中,所有结点的度 = 2 ;若树高度 $= h$ ,则结点总数 $= 2^{h+1} - 1$ ,呈标准的指数级关系,反映着自然界中常见的细胞分裂。 - -!!! tip - - 在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。 - -![perfect_binary_tree](binary_tree.assets/perfect_binary_tree.png) - -### 完全二叉树 - -「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。 - -**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。 - -![complete_binary_tree](binary_tree.assets/complete_binary_tree.png) - -### 完满二叉树 - -「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。 - -![full_binary_tree](binary_tree.assets/full_binary_tree.png) - -### 平衡二叉树 - -「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。 - -![balanced_binary_tree](binary_tree.assets/balanced_binary_tree.png) - -## 二叉树的退化 - -当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。 - -- 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势; -- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ; - -![binary_tree_corner_cases](binary_tree.assets/binary_tree_corner_cases.png) - -

Fig. 二叉树的最佳和最差结构

- -如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。 +=== "C" -
+ ```c title="binary_tree.c" + /* 插入与删除节点 */ + TreeNode *P = newTreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1->left = P; + P->left = n2; + // 删除节点 P + n1->left = n2; + // 释放内存 + free(P); + ``` -| | 完美二叉树 | 链表 | -| ----------------------------- | ---------- | ---------- | -| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ | -| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ | -| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ | -| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | +=== "Kotlin" -
+ ```kotlin title="binary_tree.kt" + val P = TreeNode(0) + // 在 n1 -> n2 中间插入节点 P + n1.left = P + P.left = n2 + // 删除节点 P + n1.left = n2 + ``` -## 二叉树表示方式 * +=== "Ruby" -我们一般使用二叉树的「链表表示」,即存储单位为结点 `TreeNode` ,结点之间通过指针(引用)相连接。本文前述示例代码展示了二叉树在链表表示下的各项基本操作。 + ```ruby title="binary_tree.rb" + # 插入与删除节点 + _p = TreeNode.new(0) + # 在 n1 -> n2 中间插入节点 _p + n1.left = _p + _p.left = n2 + # 删除节点 + n1.left = n2 + ``` -那能否可以用「数组表示」二叉树呢?答案是肯定的。先来分析一个简单案例,给定一个「完美二叉树」,将结点按照层序遍历的顺序编号(从 0 开始),那么可以推导得出父结点索引与子结点索引之间的「映射公式」:**设结点的索引为 $i$ ,则该结点的左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$** 。 +=== "Zig" -**本质上,映射公式的作用就是链表中的指针**。对于层序遍历序列中的任意结点,我们都可以使用映射公式来访问子结点。因此,可以直接使用层序遍历序列(即数组)来表示完美二叉树。 + ```zig title="binary_tree.zig" -![array_representation_mapping](binary_tree.assets/array_representation_mapping.png) + ``` -然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 `null` ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,**即理论上存在许多种二叉树都符合该层序遍历序列**。显然,这种情况无法使用数组来存储二叉树。 +??? pythontutor "可视化运行" -![array_representation_without_empty](binary_tree.assets/array_representation_without_empty.png) + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false -为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,**即在序列中使用特殊符号来显式地表示“空位”**。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。 +!!! tip -=== "Java" + 需要注意的是,插入节点可能会改变二叉树的原有逻辑结构,而删除节点通常意味着删除该节点及其所有子树。因此,在二叉树中,插入与删除通常是由一套操作配合完成的,以实现有实际意义的操作。 - ```java title="" - /* 二叉树的数组表示 */ - // 使用 int 的包装类 Integer ,就可以使用 null 来标记空位 - Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; - ``` +## 常见二叉树类型 -=== "C++" +### 完美二叉树 - ```cpp title="" - /* 二叉树的数组表示 */ - // 为了符合数据类型为 int ,使用 int 最大值标记空位 - // 该方法的使用前提是没有结点的值 = INT_MAX - vector tree = { 1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15 }; - ``` +如下图所示,完美二叉树(perfect binary tree)所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 $0$ ,其余所有节点的度都为 $2$ ;若树的高度为 $h$ ,则节点总数为 $2^{h+1} - 1$ ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。 -=== "Python" +!!! tip - ```python title="" - """ 二叉树的数组表示 """ - # 直接使用 None 来表示空位 - tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] - ``` + 请注意,在中文社区中,完美二叉树常被称为满二叉树。 -=== "Go" +![完美二叉树](binary_tree.assets/perfect_binary_tree.png) - ```go title="" - - ``` +### 完全二叉树 -=== "JavaScript" +如下图所示,完全二叉树(complete binary tree)仅允许最底层的节点不完全填满,且最底层的节点必须从左至右依次连续填充。请注意,完美二叉树也是一棵完全二叉树。 - ```js title="" - /* 二叉树的数组表示 */ - // 直接使用 null 来表示空位 - let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; - ``` +![完全二叉树](binary_tree.assets/complete_binary_tree.png) -=== "TypeScript" +### 完满二叉树 - ```typescript title="" - /* 二叉树的数组表示 */ - // 直接使用 null 来表示空位 - let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; - ``` +如下图所示,完满二叉树(full binary tree)除了叶节点之外,其余所有节点都有两个子节点。 -=== "C" +![完满二叉树](binary_tree.assets/full_binary_tree.png) - ```c title="" - - ``` +### 平衡二叉树 -=== "C#" +如下图所示,平衡二叉树(balanced binary tree)中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。 - ```csharp title="" - /* 二叉树的数组表示 */ - // 使用 int? 可空类型 ,就可以使用 null 来标记空位 - int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; - ``` +![平衡二叉树](binary_tree.assets/balanced_binary_tree.png) -=== "Swift" +## 二叉树的退化 - ```swift title="" +下图展示了二叉树的理想结构与退化结构。当二叉树的每层节点都被填满时,达到“完美二叉树”;而当所有节点都偏向一侧时,二叉树退化为“链表”。 - ``` +- 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。 +- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ 。 -![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png) +![二叉树的最佳结构与最差结构](binary_tree.assets/binary_tree_best_worst_cases.png) -回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 +如下表所示,在最佳结构和最差结构下,二叉树的叶节点数量、节点总数、高度等达到极大值或极小值。 -![array_representation_complete_binary_tree](binary_tree.assets/array_representation_complete_binary_tree.png) +

  二叉树的最佳结构与最差结构

-数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。 +| | 完美二叉树 | 链表 | +| --------------------------- | ------------------ | ------- | +| 第 $i$ 层的节点数量 | $2^{i-1}$ | $1$ | +| 高度为 $h$ 的树的叶节点数量 | $2^h$ | $1$ | +| 高度为 $h$ 的树的节点总数 | $2^{h+1} - 1$ | $h + 1$ | +| 节点总数为 $n$ 的树的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | diff --git a/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png b/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png index 9d2a4d039f..2c53267727 100644 Binary files a/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png and b/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png b/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png index 3104a0ba0e..321e76c8ee 100644 Binary files a/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png and b/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png new file mode 100644 index 0000000000..7c5193de21 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png new file mode 100644 index 0000000000..6f864c7880 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png new file mode 100644 index 0000000000..d3026cfe4b Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png new file mode 100644 index 0000000000..6418f839fa Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png new file mode 100644 index 0000000000..4a5603c135 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png new file mode 100644 index 0000000000..2977b827dd Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png new file mode 100644 index 0000000000..dc68920e10 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png new file mode 100644 index 0000000000..807c36a063 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png new file mode 100644 index 0000000000..4c4dcf69c5 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png new file mode 100644 index 0000000000..d5d694c745 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png new file mode 100644 index 0000000000..afb7495c74 Binary files /dev/null and b/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png differ diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md old mode 100644 new mode 100755 index 650a15297e..12025885d3 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -1,460 +1,89 @@ ---- -comments: true ---- - # 二叉树遍历 -非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。 +从物理结构的角度来看,树是一种基于链表的数据结构,因此其遍历方式是通过指针逐个访问节点。然而,树是一种非线性数据结构,这使得遍历树比遍历链表更加复杂,需要借助搜索算法来实现。 + +二叉树常见的遍历方式包括层序遍历、前序遍历、中序遍历和后序遍历等。 ## 层序遍历 -「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。 - -层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。 - -![binary_tree_bfs](binary_tree_traversal.assets/binary_tree_bfs.png) - -

Fig. 二叉树的层序遍历

- -广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。 - -=== "Java" - - ```java title="binary_tree_bfs.java" - /* 层序遍历 */ - List hierOrder(TreeNode root) { - // 初始化队列,加入根结点 - Queue queue = new LinkedList<>() {{ add(root); }}; - // 初始化一个列表,用于保存遍历序列 - List list = new ArrayList<>(); - while (!queue.isEmpty()) { - TreeNode node = queue.poll(); // 队列出队 - list.add(node.val); // 保存结点值 - if (node.left != null) - queue.offer(node.left); // 左子结点入队 - if (node.right != null) - queue.offer(node.right); // 右子结点入队 - } - return list; - } - ``` - -=== "C++" - - ```cpp title="binary_tree_bfs.cpp" - /* 层序遍历 */ - vector hierOrder(TreeNode* root) { - // 初始化队列,加入根结点 - queue queue; - queue.push(root); - // 初始化一个列表,用于保存遍历序列 - vector vec; - while (!queue.empty()) { - TreeNode* node = queue.front(); - queue.pop(); // 队列出队 - vec.push_back(node->val); // 保存结点 - if (node->left != nullptr) - queue.push(node->left); // 左子结点入队 - if (node->right != nullptr) - queue.push(node->right); // 右子结点入队 - } - return vec; - } - ``` - -=== "Python" - - ```python title="binary_tree_bfs.py" - """ 层序遍历 """ - def hier_order(root: Optional[TreeNode]): - # 初始化队列,加入根结点 - queue = collections.deque() - queue.append(root) - # 初始化一个列表,用于保存遍历序列 - res = [] - while queue: - node = queue.popleft() # 队列出队 - res.append(node.val) # 保存节点值 - if node.left is not None: - queue.append(node.left) # 左子结点入队 - if node.right is not None: - queue.append(node.right) # 右子结点入队 - return res - ``` - -=== "Go" - - ```go title="binary_tree_bfs.go" - /* 层序遍历 */ - func levelOrder(root *TreeNode) []int { - // 初始化队列,加入根结点 - queue := list.New() - queue.PushBack(root) - // 初始化一个切片,用于保存遍历序列 - nums := make([]int, 0) - for queue.Len() > 0 { - // poll - node := queue.Remove(queue.Front()).(*TreeNode) - // 保存结点 - nums = append(nums, node.Val) - if node.Left != nil { - // 左子结点入队 - queue.PushBack(node.Left) - } - if node.Right != nil { - // 右子结点入队 - queue.PushBack(node.Right) - } - } - return nums - } - ``` - -=== "JavaScript" - - ```js title="binary_tree_bfs.js" - /* 层序遍历 */ - function hierOrder(root) { - // 初始化队列,加入根结点 - let queue = [root]; - // 初始化一个列表,用于保存遍历序列 - let list = []; - while (queue.length) { - let node = queue.shift(); // 队列出队 - list.push(node.val); // 保存结点 - if (node.left) - queue.push(node.left); // 左子结点入队 - if (node.right) - queue.push(node.right); // 右子结点入队 - } - return list; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_tree_bfs.ts" - /* 层序遍历 */ - function hierOrder(root: TreeNode | null): number[] { - // 初始化队列,加入根结点 - const queue = [root]; - // 初始化一个列表,用于保存遍历序列 - const list: number[] = []; - while (queue.length) { - let node = queue.shift() as TreeNode; // 队列出队 - list.push(node.val); // 保存结点 - if (node.left) { - queue.push(node.left); // 左子结点入队 - } - if (node.right) { - queue.push(node.right); // 右子结点入队 - } - } - return list; - } - ``` - -=== "C" - - ```c title="binary_tree_bfs.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree_bfs.cs" - /* 层序遍历 */ - public List hierOrder(TreeNode root) - { - // 初始化队列,加入根结点 - Queue queue = new(); - queue.Enqueue(root); - // 初始化一个列表,用于保存遍历序列 - List list = new(); - while (queue.Count != 0) - { - TreeNode node = queue.Dequeue(); // 队列出队 - list.Add(node.val); // 保存结点值 - if (node.left != null) - queue.Enqueue(node.left); // 左子结点入队 - if (node.right != null) - queue.Enqueue(node.right); // 右子结点入队 - } - return list; - } - - ``` - -=== "Swift" - - ```swift title="binary_tree_bfs.swift" - - ``` +如下图所示,层序遍历(level-order traversal)从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。 + +层序遍历本质上属于广度优先遍历(breadth-first traversal),也称广度优先搜索(breadth-first search, BFS),它体现了一种“一圈一圈向外扩展”的逐层遍历方式。 + +![二叉树的层序遍历](binary_tree_traversal.assets/binary_tree_bfs.png) + +### 代码实现 + +广度优先遍历通常借助“队列”来实现。队列遵循“先进先出”的规则,而广度优先遍历则遵循“逐层推进”的规则,两者背后的思想是一致的。实现代码如下: + +```src +[file]{binary_tree_bfs}-[class]{}-[func]{level_order} +``` + +### 复杂度分析 + +- **时间复杂度为 $O(n)$** :所有节点被访问一次,使用 $O(n)$ 时间,其中 $n$ 为节点数量。 +- **空间复杂度为 $O(n)$** :在最差情况下,即满二叉树时,遍历到最底层之前,队列中最多同时存在 $(n + 1) / 2$ 个节点,占用 $O(n)$ 空间。 ## 前序、中序、后序遍历 -相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 - -如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。 - -![binary_tree_dfs](binary_tree_traversal.assets/binary_tree_dfs.png) - -

Fig. 二叉树的前 / 中 / 后序遍历

- -
- -| 位置 | 含义 | 此处访问结点时对应 | -| ---------- | ------------------------------------ | ----------------------------- | -| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal | -| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal | -| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal | - -
- -=== "Java" - - ```java title="binary_tree_dfs.java" - /* 前序遍历 */ - void preOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.add(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - void inOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.add(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - void postOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.add(root.val); - } - ``` - -=== "C++" - - ```cpp title="binary_tree_dfs.cpp" - /* 前序遍历 */ - void preOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - vec.push_back(root->val); - preOrder(root->left); - preOrder(root->right); - } - - /* 中序遍历 */ - void inOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root->left); - vec.push_back(root->val); - inOrder(root->right); - } - - /* 后序遍历 */ - void postOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root->left); - postOrder(root->right); - vec.push_back(root->val); - } - ``` - -=== "Python" - - ```python title="binary_tree_dfs.py" - """ 前序遍历 """ - def pre_order(root: Optional[TreeNode]): - if root is None: - return - # 访问优先级:根结点 -> 左子树 -> 右子树 - res.append(root.val) - pre_order(root=root.left) - pre_order(root=root.right) - - """ 中序遍历 """ - def in_order(root: Optional[TreeNode]): - if root is None: - return - # 访问优先级:左子树 -> 根结点 -> 右子树 - in_order(root=root.left) - res.append(root.val) - in_order(root=root.right) - - """ 后序遍历 """ - def post_order(root: Optional[TreeNode]): - if root is None: - return - # 访问优先级:左子树 -> 右子树 -> 根结点 - post_order(root=root.left) - post_order(root=root.right) - res.append(root.val) - ``` - -=== "Go" - - ```go title="binary_tree_dfs.go" - /* 前序遍历 */ - func preOrder(node *TreeNode) { - if node == nil { - return - } - // 访问优先级:根结点 -> 左子树 -> 右子树 - nums = append(nums, node.Val) - preOrder(node.Left) - preOrder(node.Right) - } - - /* 中序遍历 */ - func inOrder(node *TreeNode) { - if node == nil { - return - } - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(node.Left) - nums = append(nums, node.Val) - inOrder(node.Right) - } - - /* 后序遍历 */ - func postOrder(node *TreeNode) { - if node == nil { - return - } - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(node.Left) - postOrder(node.Right) - nums = append(nums, node.Val) - } - ``` - -=== "JavaScript" - - ```js title="binary_tree_dfs.js" - /* 前序遍历 */ - function preOrder(root){ - if (root === null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.push(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - function inOrder(root) { - if (root === null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.push(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - function postOrder(root) { - if (root === null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.push(root.val); - } - ``` - -=== "TypeScript" - - ```typescript title="binary_tree_dfs.ts" - /* 前序遍历 */ - function preOrder(root: TreeNode | null): void { - if (root === null) { - return; - } - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.push(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - function inOrder(root: TreeNode | null): void { - if (root === null) { - return; - } - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.push(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - function postOrder(root: TreeNode | null): void { - if (root === null) { - return; - } - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.push(root.val); - } - ``` - -=== "C" - - ```c title="binary_tree_dfs.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree_dfs.cs" - /* 前序遍历 */ - void preOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.Add(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - void inOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.Add(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - void postOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.Add(root.val); - } - ``` - -=== "Swift" - - ```swift title="binary_tree_dfs.swift" - - ``` - -!!! note - - 使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。 +相应地,前序、中序和后序遍历都属于深度优先遍历(depth-first traversal),也称深度优先搜索(depth-first search, DFS),它体现了一种“先走到尽头,再回溯继续”的遍历方式。 + +下图展示了对二叉树进行深度优先遍历的工作原理。**深度优先遍历就像是绕着整棵二叉树的外围“走”一圈**,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。 + +![二叉搜索树的前序、中序、后序遍历](binary_tree_traversal.assets/binary_tree_dfs.png) + +### 代码实现 + +深度优先搜索通常基于递归实现: + +```src +[file]{binary_tree_dfs}-[class]{}-[func]{post_order} +``` + +!!! tip + + 深度优先搜索也可以基于迭代实现,有兴趣的读者可以自行研究。 + +下图展示了前序遍历二叉树的递归过程,其可分为“递”和“归”两个逆向的部分。 + +1. “递”表示开启新方法,程序在此过程中访问下一个节点。 +2. “归”表示函数返回,代表当前节点已经访问完毕。 + +=== "<1>" + ![前序遍历的递归过程](binary_tree_traversal.assets/preorder_step1.png) + +=== "<2>" + ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) + +=== "<3>" + ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) + +=== "<4>" + ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) + +=== "<5>" + ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) + +=== "<6>" + ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) + +=== "<7>" + ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) + +=== "<8>" + ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) + +=== "<9>" + ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) + +=== "<10>" + ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) + +=== "<11>" + ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) + +### 复杂度分析 + +- **时间复杂度为 $O(n)$** :所有节点被访问一次,使用 $O(n)$ 时间。 +- **空间复杂度为 $O(n)$** :在最差情况下,即树退化为链表时,递归深度达到 $n$ ,系统占用 $O(n)$ 栈帧空间。 diff --git a/docs/chapter_tree/index.md b/docs/chapter_tree/index.md new file mode 100644 index 0000000000..c12f050f15 --- /dev/null +++ b/docs/chapter_tree/index.md @@ -0,0 +1,9 @@ +# 树 + +![树](../assets/covers/chapter_tree.jpg) + +!!! abstract + + 参天大树充满生命力,根深叶茂,分枝扶疏。 + + 它为我们展现了数据分治的生动形态。 diff --git a/docs/chapter_tree/summary.md b/docs/chapter_tree/summary.md index 15dc2f6d37..edf276b6be 100644 --- a/docs/chapter_tree/summary.md +++ b/docs/chapter_tree/summary.md @@ -1,18 +1,54 @@ ---- -comments: true ---- - # 小结 -- 二叉树是一种非线性数据结构,代表着“一分为二”的分治逻辑。二叉树的结点包含「值」和两个「指针」,分别指向左子结点和右子结点。 -- 选定二叉树中某结点,将其左(右)子结点以下形成的树称为左(右)子树。 -- 二叉树的术语较多,包括根结点、叶结点、层、度、边、高度、深度等。 -- 二叉树的初始化、结点插入、结点删除操作与链表的操作方法类似。 -- 常见的二叉树类型包括完美二叉树、完全二叉树、完满二叉树、平衡二叉树。完美二叉树是理想状态,链表则是退化后的最差状态。 -- 二叉树可以使用数组表示,具体做法是将结点值和空位按照层序遍历的顺序排列,并基于父结点和子结点之间的索引映射公式实现指针。 - -- 二叉树层序遍历是一种广度优先搜索,体现着“一圈一圈向外”的层进式遍历方式,通常借助队列来实现。 -- 前序、中序、后序遍历是深度优先搜索,体现着“走到头、再回头继续”的回溯遍历方式,通常使用递归实现。 -- 二叉搜索树是一种高效的元素查找数据结构,查找、插入、删除操作的时间复杂度皆为 $O(\log n)$ 。二叉搜索树退化为链表后,各项时间复杂度劣化至 $O(n)$ ,因此如何避免退化是非常重要的课题。 -- AVL 树又称平衡二叉搜索树,其通过旋转操作,使得在不断插入与删除结点后,仍然可以保持二叉树的平衡(不退化)。 -- AVL 树的旋转操作分为右旋、左旋、先右旋后左旋、先左旋后右旋。在插入或删除结点后,AVL 树会从底至顶地执行旋转操作,使树恢复平衡。 +### 重点回顾 + +- 二叉树是一种非线性数据结构,体现“一分为二”的分治逻辑。每个二叉树节点包含一个值以及两个指针,分别指向其左子节点和右子节点。 +- 对于二叉树中的某个节点,其左(右)子节点及其以下形成的树被称为该节点的左(右)子树。 +- 二叉树的相关术语包括根节点、叶节点、层、度、边、高度和深度等。 +- 二叉树的初始化、节点插入和节点删除操作与链表操作方法类似。 +- 常见的二叉树类型有完美二叉树、完全二叉树、完满二叉树和平衡二叉树。完美二叉树是最理想的状态,而链表是退化后的最差状态。 +- 二叉树可以用数组表示,方法是将节点值和空位按层序遍历顺序排列,并根据父节点与子节点之间的索引映射关系来实现指针。 +- 二叉树的层序遍历是一种广度优先搜索方法,它体现了“一圈一圈向外扩展”的逐层遍历方式,通常通过队列来实现。 +- 前序、中序、后序遍历皆属于深度优先搜索,它们体现了“先走到尽头,再回溯继续”的遍历方式,通常使用递归来实现。 +- 二叉搜索树是一种高效的元素查找数据结构,其查找、插入和删除操作的时间复杂度均为 $O(\log n)$ 。当二叉搜索树退化为链表时,各项时间复杂度会劣化至 $O(n)$ 。 +- AVL 树,也称平衡二叉搜索树,它通过旋转操作确保在不断插入和删除节点后树仍然保持平衡。 +- AVL 树的旋转操作包括右旋、左旋、先右旋再左旋、先左旋再右旋。在插入或删除节点后,AVL 树会从底向顶执行旋转操作,使树重新恢复平衡。 + +### Q & A + +**Q**:对于只有一个节点的二叉树,树的高度和根节点的深度都是 $0$ 吗? + +是的,因为高度和深度通常定义为“经过的边的数量”。 + +**Q**:二叉树中的插入与删除一般由一套操作配合完成,这里的“一套操作”指什么呢?可以理解为资源的子节点的资源释放吗? + +拿二叉搜索树来举例,删除节点操作要分三种情况处理,其中每种情况都需要进行多个步骤的节点操作。 + +**Q**:为什么 DFS 遍历二叉树有前、中、后三种顺序,分别有什么用呢? + +与顺序和逆序遍历数组类似,前序、中序、后序遍历是三种二叉树遍历方法,我们可以使用它们得到一个特定顺序的遍历结果。例如在二叉搜索树中,由于节点大小满足 `左子节点值 < 根节点值 < 右子节点值` ,因此我们只要按照“左 $\rightarrow$ 根 $\rightarrow$ 右”的优先级遍历树,就可以获得有序的节点序列。 + +**Q**:右旋操作是处理失衡节点 `node`、`child`、`grand_child` 之间的关系,那 `node` 的父节点和 `node` 原来的连接不需要维护吗?右旋操作后岂不是断掉了? + +我们需要从递归的视角来看这个问题。右旋操作 `right_rotate(root)` 传入的是子树的根节点,最终 `return child` 返回旋转之后的子树的根节点。子树的根节点和其父节点的连接是在该函数返回后完成的,不属于右旋操作的维护范围。 + +**Q**:在 C++ 中,函数被划分到 `private` 和 `public` 中,这方面有什么考量吗?为什么要将 `height()` 函数和 `updateHeight()` 函数分别放在 `public` 和 `private` 中呢? + +主要看方法的使用范围,如果方法只在类内部使用,那么就设计为 `private` 。例如,用户单独调用 `updateHeight()` 是没有意义的,它只是插入、删除操作中的一步。而 `height()` 是访问节点高度,类似于 `vector.size()` ,因此设置成 `public` 以便使用。 + +**Q**:如何从一组输入数据构建一棵二叉搜索树?根节点的选择是不是很重要? + +是的,构建树的方法已在二叉搜索树代码中的 `build_tree()` 方法中给出。至于根节点的选择,我们通常会将输入数据排序,然后将中点元素作为根节点,再递归地构建左右子树。这样做可以最大程度保证树的平衡性。 + +**Q**:在 Java 中,字符串对比是否一定要用 `equals()` 方法? + +在 Java 中,对于基本数据类型,`==` 用于对比两个变量的值是否相等。对于引用类型,两种符号的工作原理是不同的。 + +- `==` :用来比较两个变量是否指向同一个对象,即它们在内存中的位置是否相同。 +- `equals()`:用来对比两个对象的值是否相等。 + +因此,如果要对比值,我们应该使用 `equals()` 。然而,通过 `String a = "hi"; String b = "hi";` 初始化的字符串都存储在字符串常量池中,它们指向同一个对象,因此也可以用 `a == b` 来比较两个字符串的内容。 + +**Q**:广度优先遍历到最底层之前,队列中的节点数量是 $2^h$ 吗? + +是的,例如高度 $h = 2$ 的满二叉树,其节点总数 $n = 7$ ,则底层节点数量 $4 = 2^h = (n + 1) / 2$ 。 diff --git a/docs/index.assets/animation.gif b/docs/index.assets/animation.gif index 6522ef9fff..641e677efb 100644 Binary files a/docs/index.assets/animation.gif and b/docs/index.assets/animation.gif differ diff --git a/docs/index.assets/animation_dark.gif b/docs/index.assets/animation_dark.gif new file mode 100644 index 0000000000..a05aedd70e Binary files /dev/null and b/docs/index.assets/animation_dark.gif differ diff --git a/docs/index.assets/btn_download_pdf.svg b/docs/index.assets/btn_download_pdf.svg new file mode 100644 index 0000000000..e77fe03121 --- /dev/null +++ b/docs/index.assets/btn_download_pdf.svg @@ -0,0 +1 @@ +下载PDF \ No newline at end of file diff --git a/docs/index.assets/btn_download_pdf_dark.svg b/docs/index.assets/btn_download_pdf_dark.svg new file mode 100644 index 0000000000..039e36688b --- /dev/null +++ b/docs/index.assets/btn_download_pdf_dark.svg @@ -0,0 +1 @@ +下载PDF \ No newline at end of file diff --git a/docs/index.assets/btn_english_edition.svg b/docs/index.assets/btn_english_edition.svg new file mode 100644 index 0000000000..9ca2a67e89 --- /dev/null +++ b/docs/index.assets/btn_english_edition.svg @@ -0,0 +1 @@ +English Ed. \ No newline at end of file diff --git a/docs/index.assets/btn_english_edition_dark.svg b/docs/index.assets/btn_english_edition_dark.svg new file mode 100644 index 0000000000..cff7a80ca2 --- /dev/null +++ b/docs/index.assets/btn_english_edition_dark.svg @@ -0,0 +1 @@ +English Ed. \ No newline at end of file diff --git a/docs/index.assets/btn_read_online.svg b/docs/index.assets/btn_read_online.svg new file mode 100644 index 0000000000..aacbc6577d --- /dev/null +++ b/docs/index.assets/btn_read_online.svg @@ -0,0 +1 @@ +在线阅读 \ No newline at end of file diff --git a/docs/index.assets/btn_read_online_dark.svg b/docs/index.assets/btn_read_online_dark.svg new file mode 100644 index 0000000000..96a6c91331 --- /dev/null +++ b/docs/index.assets/btn_read_online_dark.svg @@ -0,0 +1 @@ +在线阅读 \ No newline at end of file diff --git a/docs/index.assets/comment.gif b/docs/index.assets/comment.gif index d64787ac3e..a6c5b84446 100644 Binary files a/docs/index.assets/comment.gif and b/docs/index.assets/comment.gif differ diff --git a/docs/index.assets/conceptual_rendering.png b/docs/index.assets/conceptual_rendering.png deleted file mode 100644 index 591d633be2..0000000000 Binary files a/docs/index.assets/conceptual_rendering.png and /dev/null differ diff --git a/docs/index.assets/demo.png b/docs/index.assets/demo.png deleted file mode 100644 index af3e00593f..0000000000 Binary files a/docs/index.assets/demo.png and /dev/null differ diff --git a/docs/index.assets/hello_algo_header.png b/docs/index.assets/hello_algo_header.png new file mode 100644 index 0000000000..51e21c54e4 Binary files /dev/null and b/docs/index.assets/hello_algo_header.png differ diff --git a/docs/index.assets/hello_algo_mindmap_tp.png b/docs/index.assets/hello_algo_mindmap_tp.png new file mode 100644 index 0000000000..9ba5217173 Binary files /dev/null and b/docs/index.assets/hello_algo_mindmap_tp.png differ diff --git a/docs/index.assets/learning_route.png b/docs/index.assets/learning_route.png deleted file mode 100644 index 7423808d62..0000000000 Binary files a/docs/index.assets/learning_route.png and /dev/null differ diff --git a/docs/index.assets/running_code.gif b/docs/index.assets/running_code.gif index 7377773c1e..5c77187f7b 100644 Binary files a/docs/index.assets/running_code.gif and b/docs/index.assets/running_code.gif differ diff --git a/docs/index.assets/running_code_dark.gif b/docs/index.assets/running_code_dark.gif new file mode 100644 index 0000000000..5d56a0184c Binary files /dev/null and b/docs/index.assets/running_code_dark.gif differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000000..5e8b0888a8 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,356 @@ + +
+ + + + + +
+ +
+

+ 动画图解、一键运行的数据结构与算法教程 +

+ + + + + 开始阅读 + + + + + + + 代码仓库 + +
+ +
+ + + +
+
+
+ + +
+
+ Preview +
+ + + + + + + + + + + + + + +
+

500 幅动画图解、14 种编程语言代码、3000 条社区问答,助你快速入门数据结构与算法

+
+
+ + +
+ +
+ + +
+
+

推荐语

+
+
+

“一本通俗易懂的数据结构与算法入门书,引导读者手脑并用地学习,强烈推荐算法初学者阅读。”

+

—— 邓俊辉,清华大学计算机系教授

+
+
+

“如果我当年学数据结构与算法的时候有《Hello 算法》,学起来应该会简单 10 倍!”

+

—— 李沐,亚马逊资深首席科学家

+
+
+
+
+ + +
+
+
+
+
+
+ + + +

动画图解

+
+

内容清晰易懂,学习曲线平滑

+

"A picture is worth a thousand words."
“一图胜千言”

+
+
+ Animation example +
+ +
+ Running code example +
+
+
+ + + +

一键运行

+
+

十余种编程语言,代码可视化运行

+

"Talk is cheap. Show me the code."
“少吹牛,看代码”

+
+
+
+ +
+
+
+
+ + + +

互助学习

+
+

欢迎讨论与提问,读者间携手共进

+

"Learning by teaching."
“教学相长”

+
+
+ Comments example +
+ +
+
+ + +
+
+ +
+

作者

+ +
+ + + + + +
+

贡献者

+

本书在开源社区一百多位贡献者的共同努力下不断完善,感谢他们付出的时间与精力!

+ + Contributors + +
+
+
\ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 3b64052022..fc67400fb2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,75 +1,5 @@ ---- -comments: true -hide: - - footer ---- +# Hello 算法 -=== " " +动画图解、一键运行的数据结构与算法教程。 -
- ![conceptual_rendering](index.assets/conceptual_rendering.png){ align=left width=350 } -




-

《 Hello,算法 》

-

动画图解、能运行、可提问的
数据结构与算法快速入门教程

-

[![github-stars](https://img.shields.io/github/stars/krahets/hello-algo?style=social)](https://github.com/krahets/hello-algo)

-
[@Krahets](https://leetcode.cn/u/jyd/)
-
- ---- - -

「清晰动画讲解」

- -

动画诠释重点,平滑学习曲线
电脑、平板、手机全终端阅读

- -![algorithm_animation](index.assets/animation.gif) - -!!! quote "" - -

"A picture is worth a thousand words."

-

“一图胜千言”

- ---- - -

「代码实践导向」

- -

提供经典算法的清晰实现与测试代码
多种语言,详细注释,皆可一键运行

- -![running_code](index.assets/running_code.gif) - -!!! quote "" - -

"Talk is cheap. Show me the code."

-

“少吹牛,看代码”

- ---- - -

「可讨论与提问」

- -

作者一般 72h 内回复评论问题
与小伙伴们一起讨论学习进步

- -![comment](index.assets/comment.gif) - -!!! quote "" - -

“追风赶月莫停留,平芜尽处是春山”

-

一起加油!

- ---- - -

推荐语

- -!!! quote - - “一本通俗易懂的数据结构与算法入门书,引导读者手脑并用地学习,强烈推荐算法初学者阅读。” - - **—— 邓俊辉,清华大学计算机系教授** - -

致谢

- -感谢本开源书的每一位撰稿人,是他们的无私奉献让这本书变得更好,他们是: - - - - - ---- +[开始阅读](chapter_hello_algo/) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css deleted file mode 100644 index bbbfe4417a..0000000000 --- a/docs/stylesheets/extra.css +++ /dev/null @@ -1,65 +0,0 @@ - -/* Color Settings */ -/* https://github.com/squidfunk/mkdocs-material/blob/6b5035f5580f97532d664e3d1babf5f320e88ee9/src/assets/stylesheets/main/_colors.scss */ -/* https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#custom-colors */ -:root > * { - --md-primary-fg-color: #FFFFFF; - --md-primary-bg-color: #1D1D20; - - --md-accent-fg-color: #999; - - --md-typeset-color: #1D1D20; - --md-typeset-a-color: #2AA996; -} - -[data-md-color-scheme="slate"] { - --md-primary-fg-color: #2E303E; - --md-primary-bg-color: #FEFEFE; - - --md-accent-fg-color: #999; - - --md-typeset-color: #FEFEFE; - --md-typeset-a-color: #21C8B8; -} - -/* Center Markdown Tables (requires md_in_html extension) */ -.center-table { - text-align: center; -} - -.md-typeset .center-table :is(td,th):not([align]) { - /* Reset alignment for table cells */ - text-align: initial; -} - - -/* Markdown Header */ -/* https://github.com/squidfunk/mkdocs-material/blob/dcab57dd1cced4b77875c1aa1b53467c62709d31/src/assets/stylesheets/main/_typeset.scss */ -.md-typeset h1 { - font-weight: 400; - color: var(--md-default-fg-color); -} - -.md-typeset h2 { - font-weight: 400; -} - -.md-typeset h3 { - font-weight: 500; -} - -.md-typeset a { - text-decoration: underline; -} - -/* Image align center */ -.center { - display: block; - margin: 0 auto; -} - -/* font-family setting for Win10 */ -body { - --md-text-font-family: -apple-system,BlinkMacSystemFont,var(--md-text-font,_),Helvetica,Arial,sans-serif; - --md-code-font-family: var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,-apple-system,BlinkMacSystemFont,var(--md-text-font,_),monospace; -} \ No newline at end of file diff --git a/en/CONTRIBUTING.md b/en/CONTRIBUTING.md new file mode 100644 index 0000000000..5e226da57b --- /dev/null +++ b/en/CONTRIBUTING.md @@ -0,0 +1,134 @@ +# Contributing guidelines for Chinese-to-English + +We are working on translating "Hello Algo" from Chinese to English with the following approach: + +1. **AI translation**: Carry out an initial pass of translations using large language models. +2. **Human optimization**: Manually refine the machine-generated outputs to ensure authenticity and accuracy. +3. **Pull request review**: The optimized translation will be double checked by the reviewers through GitHub pull request workflow. +4. Repeat steps `2.` and `3.` for further improvements. + +translation_pipeline + +## Join us + +We're seeking contributors who meet the following criteria. + +- **Technical background**: Strong foundation in computer science, particularly in data structures and algorithms. +- **Language skills**: Native proficiency in Chinese with professional-level English, or native English. +- **Available time**: Dedicated to contributing to open-source projects with a willingness to engage in long-term translation efforts. + +That is, our contributors are computer scientists, engineers, and students from different linguistic backgrounds, and their objectives have different focal points: + +- **Native Chinese with professional working English**: Ensuring translation accuracy and consistency between CN and EN versions. +- **Native English**: Enhance the authenticity and fluency of the English content to flow naturally and to be engaging. + +> [!note] +> If you are interested in joining us, don't hesitate to contact me via krahetx@gmail.com or WeChat `krahets-jyd`. +> +> We use this [Notion page](https://hello-algo.notion.site/chinese-to-english) to track progress and assign tasks. Please visit it for more details. + +## Translation process + +> [!important] +> Before diving in, ensure you're comfortable with the GitHub pull request workflow and have read the "Translation standards" and "Pseudo-code for translation" below. + +1. **Task assignment**: Self-assign a task in the Notion workspace. +2. **Translation**: Optimize the translation on your local PC, referring to the “Translation Pseudo-Code” section below for more details. +3. **Peer review**: Carefully review your changes before submitting a Pull Request (PR). The PR will be merged into the main branch after approval from two reviewers. + +## Translation standards + +> [!tip] +> **The "Accuracy" and "Authenticity" are primarily handled by native Chinese speakers and native English speakers, respectively.** +> +> In some instances, "Accuracy (consistency)" and "Authenticity" represent a trade-off, where optimizing one aspect could significantly affect the other. In such cases, please leave a comment in the pull request for discussion. + +**Accuracy**: + +- Maintain consistency in terminology across translations by referring to the [Terminology](https://www.hello-algo.com/chapter_appendix/terminology/) section. +- Prioritize technical accuracy and maintain the tone and style of the Chinese version. +- Always take into account the content and context of the Chinese version to ensure modifications are accurate and comprehensive. + +**Authenticity**: + +- Translations should flow naturally and fluently, adhering to English expression conventions. +- Always consider the context of the content to harmonize the article. +- Be aware of cultural differences between Chinese and English. For instance, Chinese "pinyin" does not exist in English. +- If the optimized sentence could alter the original meaning, please add a comment for discussion. + +**Formatting**: + +- Figures and tables will be automatically numbered during deployment, so DO NOT manually number them. +- Each PR should cover at least one complete document to ensure manageable review sizes, except for bug fixes. + +**Review**: + +- During the review, prioritize evaluating the changes, consulting the surrounding context as needed. +- Learning from each other's perspectives can lead to better translations and more cohesive results. + +## Translation pseudo-code + +The following pseudo-code models the steps in a typical translation process. + +```python +def optimize_translation(markdown_texts, lang_skill): + """Optimize the translation""" + for sentence in markdown_texts: + """Accuracy is handled primarily by native Chinese speakers""" + if lang_skill is "Native Chinese + Professional working English": + if is_accurate_Chinese_to_English(sentence): + continue + # Optimize the accuracy + result = refine_accuracy(sentence) + + """ + Authenticity is handled primarily by native English speakers + and secondarily by native Chinese speakers + """ + if is_authentic_English(sentence): + continue + # Optimize the authenticity + result = refine_authenticity(sentence) + # Add comments in the PR if it may break consistency + if break_consistency(result): + add_comment(description) + + pull_request = submit_pull_request(markdown_texts) + # The PR will be merged after approved by >= 2 reviewers + while count_approvals(pull_request) < 2: + continue + merge(pull_request) +``` + +The following pseudo-code is for the reviewers: + +```python +def review_pull_requests(pull_request, lang_skill): + """Review the PR""" + # Loop through all the changes in the PR + while is_anything_left_to_review(pull_request): + change = get_next_change(pull_request) + + """Accuracy is handled primarily by native Chinese speakers""" + if lang_skill is "Native Chinese + Professional working English": + # Check the accuracy(consistency) between CN and EN versions + if is_accurate_Chinese_to_English(change): + continue + # Optimize the accuracy(consistency) + result = refine_accuracy(change) + # Add comments in the PR + add_comment(result) + + """ + Authenticity is handled primarily by native English speakers + and secondarily by native Chinese speakers + """ + if is_authentic_English(change): + continue + # Optimize the authenticity if it is not authentic English + result = refine_authenticity(change) + # Add comments in the PR + add_comment(result) + + approve(pull_request) +``` diff --git a/en/README.md b/en/README.md new file mode 100644 index 0000000000..339e789774 --- /dev/null +++ b/en/README.md @@ -0,0 +1,89 @@ +

+ + +

+ +

+ hello-algo-typing-svg +
+ Data structures and algorithms crash course with animated illustrations and off-the-shelf code +

+ +

+ + +

+ +

+ + +

+ +

+ + + + + + + + + + + + + +

+ +

+ 简体中文 + | + 繁體中文 + | + English +

+ +## The book + +This open-source project aims to create a free and beginner-friendly crash course for data structures and algorithms. + +- Animated illustrations, easy-to-understand content, and a smooth learning curve help beginners explore the "knowledge map" of data structures and algorithms. +- Run code with just one click, helping readers improve their programming skills and understand the working principle of algorithms and the underlying implementation of data structures. +- Promoting learning by teaching, feel free to ask questions and share insights. Let's grow together through discussion. + +If you find this book helpful, please give it a Star :star: to support us, thank you! + +## Endorsements + +> "An easy-to-understand book on data structures and algorithms, which guides readers to learn by minds-on and hands-on. Strongly recommended for algorithm beginners!" +> +> **—— Junhui Deng, Professor, Department of computer science and technology, Tsinghua University** + +> "If I had 'Hello Algo' when I was learning data structures and algorithms, it would have been 10 times easier!" +> +> **—— Mu Li, Senior Principal Scientist, Amazon** + +## Contributing + +> [!Important] +> +> Welcome to contribute to Chinese-to-English translation! For more information please see [CONTRIBUTING.md](CONTRIBUTING.md). + +This open-source book is continuously being updated, and we welcome your participation in this project to provide better learning content for readers. + +- [Content Correction](https://www.hello-algo.com/en/chapter_appendix/contribution/): Please help us correct or point out mistakes in the comments section such as grammatical errors, missing content, ambiguities, invalid links, or code bugs. +- [Code Transpilation](https://github.com/krahets/hello-algo/issues/15): We look forward to your contributions in various programming languages. We currently support 12 languages including Python, Java, C++, Go, and JavaScript. + +We welcome your valuable suggestions and feedback. If you have any questions, please submit Issues or reach out via WeChat: `krahets-jyd`. + +We would like to dedicate our thanks to all the contributors of this book. It is their selfless dedication that has made this book better. They are: + +

+ + + +

+ +## License + +The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/en/codes/cpp/.gitignore b/en/codes/cpp/.gitignore new file mode 100644 index 0000000000..dc1ffacf49 --- /dev/null +++ b/en/codes/cpp/.gitignore @@ -0,0 +1,10 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ + +*.dSYM/ + +build/ diff --git a/en/codes/cpp/CMakeLists.txt b/en/codes/cpp/CMakeLists.txt new file mode 100644 index 0000000000..1e80bc4d78 --- /dev/null +++ b/en/codes/cpp/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/en/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt b/en/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 0000000000..2e933e0163 --- /dev/null +++ b/en/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array array.cpp) +add_executable(linked_list linked_list.cpp) +add_executable(list list.cpp) +add_executable(my_list my_list.cpp) diff --git a/en/codes/cpp/chapter_array_and_linkedlist/array.cpp b/en/codes/cpp/chapter_array_and_linkedlist/array.cpp new file mode 100644 index 0000000000..b555d9db3c --- /dev/null +++ b/en/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -0,0 +1,113 @@ +/** + * File: array.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Random access to elements */ +int randomAccess(int *nums, int size) { + // Randomly select a number in the range [0, size) + int randomIndex = rand() % size; + // Retrieve and return a random element + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* Extend array length */ +int *extend(int *nums, int size, int enlarge) { + // Initialize an extended length array + int *res = new int[size + enlarge]; + // Copy all elements from the original array to the new array + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // Free memory + delete[] nums; + // Return the new array after expansion + return res; +} + +/* Insert element num at `index` */ +void insert(int *nums, int size, int num, int index) { + // Move all elements after `index` one position backward + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Assign num to the element at index + nums[index] = num; +} + +/* Remove the element at `index` */ +void remove(int *nums, int size, int index) { + // Move all elements after `index` one position forward + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* Traverse array */ +void traverse(int *nums, int size) { + int count = 0; + // Traverse array by index + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* Search for a specified element in the array */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* Initialize an array */ + int size = 5; + int *arr = new int[size]; + cout << "Array arr = "; + printArray(arr, size); + + int *nums = new int[size]{1, 3, 2, 5, 4}; + cout << "Array nums = "; + printArray(nums, size); + + /* Random access */ + int randomNum = randomAccess(nums, size); + cout << "Get a random element from nums = " << randomNum << endl; + + /* Length extension */ + int enlarge = 3; + nums = extend(nums, size, enlarge); + size += enlarge; + cout << "Extend the array length to 8, resulting in nums = "; + printArray(nums, size); + + /* Insert element */ + insert(nums, size, 6, 3); + cout << "Insert the number 6 at index 3, resulting in nums = "; + printArray(nums, size); + + /* Remove element */ + remove(nums, size, 2); + cout << "Remove the element at index 2, resulting in nums = "; + printArray(nums, size); + + /* Traverse array */ + traverse(nums, size); + + /* Search for elements */ + int index = find(nums, size, 3); + cout << "Find element 3 in nums, index = " << index << endl; + + // Free memory + delete[] arr; + delete[] nums; + + return 0; +} diff --git a/en/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/en/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp new file mode 100644 index 0000000000..2767807f83 --- /dev/null +++ b/en/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -0,0 +1,89 @@ +/** + * File: linked_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Insert node P after node n0 in the linked list */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* Remove the first node after node n0 in the linked list */ +void remove(ListNode *n0) { + if (n0->next == nullptr) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // Free memory + delete P; +} + +/* Access the node at `index` in the linked list */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == nullptr) + return nullptr; + head = head->next; + } + return head; +} + +/* Search for the first node with value target in the linked list */ +int find(ListNode *head, int target) { + int index = 0; + while (head != nullptr) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* Initialize linked list */ + // Initialize each node + ListNode *n0 = new ListNode(1); + ListNode *n1 = new ListNode(3); + ListNode *n2 = new ListNode(2); + ListNode *n3 = new ListNode(5); + ListNode *n4 = new ListNode(4); + // Build references between nodes + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + cout << "The initialized linked list is" << endl; + printLinkedList(n0); + + /* Insert node */ + insert(n0, new ListNode(0)); + cout << "Linked list after inserting the node is" << endl; + printLinkedList(n0); + + /* Remove node */ + remove(n0); + cout << "Linked list after removing the node is" << endl; + printLinkedList(n0); + + /* Access node */ + ListNode *node = access(n0, 3); + cout << "The value of the node at index 3 in the linked list = " << node->val << endl; + + /* Search node */ + int index = find(n0, 2); + cout << "The index of the node with value 2 in the linked list = " << index << endl; + + // Free memory + freeMemoryLinkedList(n0); + + return 0; +} diff --git a/en/codes/cpp/chapter_array_and_linkedlist/list.cpp b/en/codes/cpp/chapter_array_and_linkedlist/list.cpp new file mode 100644 index 0000000000..7c5d6e287b --- /dev/null +++ b/en/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -0,0 +1,72 @@ +/** + * File: list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Initialize list */ + vector nums = {1, 3, 2, 5, 4}; + cout << "List nums = "; + printVector(nums); + + /* Access element */ + int num = nums[1]; + cout << "Access the element at index 1, obtained num = " << num << endl; + + /* Update element */ + nums[1] = 0; + cout << "Update the element at index 1 to 0, resulting in nums = "; + printVector(nums); + + /* Clear list */ + nums.clear(); + cout << "After clearing the list, nums = "; + printVector(nums); + + /* Add element at the end */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + cout << "After adding elements, nums = "; + printVector(nums); + + /* Insert element in the middle */ + nums.insert(nums.begin() + 3, 6); + cout << "Insert the number 6 at index 3, resulting in nums = "; + printVector(nums); + + /* Remove element */ + nums.erase(nums.begin() + 3); + cout << "Remove the element at index 3, resulting in nums = "; + printVector(nums); + + /* Traverse the list by index */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + /* Traverse the list elements */ + count = 0; + for (int x : nums) { + count += x; + } + + /* Concatenate two lists */ + vector nums1 = {6, 8, 7, 10, 9}; + nums.insert(nums.end(), nums1.begin(), nums1.end()); + cout << "Concatenate list nums1 to nums, resulting in nums = "; + printVector(nums); + + /* Sort list */ + sort(nums.begin(), nums.end()); + cout << "After sorting the list, nums = "; + printVector(nums); + + return 0; +} diff --git a/en/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/en/codes/cpp/chapter_array_and_linkedlist/my_list.cpp new file mode 100644 index 0000000000..8af4c6f1da --- /dev/null +++ b/en/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -0,0 +1,171 @@ +/** + * File: my_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* List class */ +class MyList { + private: + int *arr; // Array (stores list elements) + int arrCapacity = 10; // List capacity + int arrSize = 0; // List length (current number of elements) + int extendRatio = 2; // Multiple for each list expansion + + public: + /* Constructor */ + MyList() { + arr = new int[arrCapacity]; + } + + /* Destructor */ + ~MyList() { + delete[] arr; + } + + /* Get list length (current number of elements)*/ + int size() { + return arrSize; + } + + /* Get list capacity */ + int capacity() { + return arrCapacity; + } + + /* Access element */ + int get(int index) { + // If the index is out of bounds, throw an exception, as below + if (index < 0 || index >= size()) + throw out_of_range("Index out of bounds"); + return arr[index]; + } + + /* Update element */ + void set(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("Index out of bounds"); + arr[index] = num; + } + + /* Add element at the end */ + void add(int num) { + // When the number of elements exceeds capacity, trigger the expansion mechanism + if (size() == capacity()) + extendCapacity(); + arr[size()] = num; + // Update the number of elements + arrSize++; + } + + /* Insert element in the middle */ + void insert(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("Index out of bounds"); + // When the number of elements exceeds capacity, trigger the expansion mechanism + if (size() == capacity()) + extendCapacity(); + // Move all elements after `index` one position backward + for (int j = size() - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // Update the number of elements + arrSize++; + } + + /* Remove element */ + int remove(int index) { + if (index < 0 || index >= size()) + throw out_of_range("Index out of bounds"); + int num = arr[index]; + // Move all elements after `index` one position forward + for (int j = index; j < size() - 1; j++) { + arr[j] = arr[j + 1]; + } + // Update the number of elements + arrSize--; + // Return the removed element + return num; + } + + /* Extend list */ + void extendCapacity() { + // Create a new array with a length multiple of the original array by extendRatio + int newCapacity = capacity() * extendRatio; + int *tmp = arr; + arr = new int[newCapacity]; + // Copy all elements from the original array to the new array + for (int i = 0; i < size(); i++) { + arr[i] = tmp[i]; + } + // Free memory + delete[] tmp; + arrCapacity = newCapacity; + } + + /* Convert the list to a Vector for printing */ + vector toVector() { + // Only convert elements within valid length range + vector vec(size()); + for (int i = 0; i < size(); i++) { + vec[i] = arr[i]; + } + return vec; + } +}; + +/* Driver Code */ +int main() { + /* Initialize list */ + MyList *nums = new MyList(); + /* Add element at the end */ + nums->add(1); + nums->add(3); + nums->add(2); + nums->add(5); + nums->add(4); + cout << "List nums = "; + vector vec = nums->toVector(); + printVector(vec); + cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl; + + /* Insert element in the middle */ + nums->insert(3, 6); + cout << "Insert the number 6 at index 3, resulting in nums = "; + vec = nums->toVector(); + printVector(vec); + + /* Remove element */ + nums->remove(3); + cout << "Remove the element at index 3, resulting in nums = "; + vec = nums->toVector(); + printVector(vec); + + /* Access element */ + int num = nums->get(1); + cout << "Access the element at index 1, obtained num = " << num << endl; + + /* Update element */ + nums->set(1, 0); + cout << "Update the element at index 1 to 0, resulting in nums = "; + vec = nums->toVector(); + printVector(vec); + + /* Test expansion mechanism */ + for (int i = 0; i < 10; i++) { + // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism at this time + nums->add(i); + } + cout << "After extending, list nums = "; + vec = nums->toVector(); + printVector(vec); + cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl; + + // Free memory + delete nums; + + return 0; +} diff --git a/en/codes/cpp/chapter_backtracking/CMakeLists.txt b/en/codes/cpp/chapter_backtracking/CMakeLists.txt new file mode 100644 index 0000000000..6c271e330b --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) +add_executable(permutations_i permutations_i.cpp) +add_executable(permutations_ii permutations_ii.cpp) +add_executable(n_queens n_queens.cpp) +add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) +add_executable(subset_sum_i subset_sum_i.cpp) +add_executable(subset_sum_ii subset_sum_ii.cpp) diff --git a/en/codes/cpp/chapter_backtracking/n_queens.cpp b/en/codes/cpp/chapter_backtracking/n_queens.cpp new file mode 100644 index 0000000000..6fbbacd8e5 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/n_queens.cpp @@ -0,0 +1,65 @@ +/** + * File: n_queens.cpp + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Backtracking algorithm: n queens */ +void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, + vector &diags1, vector &diags2) { + // When all rows are placed, record the solution + if (row == n) { + res.push_back(state); + return; + } + // Traverse all columns + for (int col = 0; col < n; col++) { + // Calculate the main and minor diagonals corresponding to the cell + int diag1 = row - col + n - 1; + int diag2 = row + col; + // Pruning: do not allow queens on the column, main diagonal, or minor diagonal of the cell + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Attempt: place the queen in the cell + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Place the next row + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Retract: restore the cell to an empty spot + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* Solve n queens */ +vector>> nQueens(int n) { + // Initialize an n*n size chessboard, where 'Q' represents the queen and '#' represents an empty spot + vector> state(n, vector(n, "#")); + vector cols(n, false); // Record columns with queens + vector diags1(2 * n - 1, false); // Record main diagonals with queens + vector diags2(2 * n - 1, false); // Record minor diagonals with queens + vector>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + vector>> res = nQueens(n); + + cout << "Input the dimensions of the chessboard as " << n << endl; + cout << "Total number of queen placement solutions = " << res.size() << endl; + for (const vector> &state : res) { + cout << "--------------------" << endl; + for (const vector &row : state) { + printVector(row); + } + } + + return 0; +} diff --git a/en/codes/cpp/chapter_backtracking/permutations_i.cpp b/en/codes/cpp/chapter_backtracking/permutations_i.cpp new file mode 100644 index 0000000000..be9907ab64 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/permutations_i.cpp @@ -0,0 +1,54 @@ +/** + * File: permutations_i.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Backtracking algorithm: Permutation I */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // When the state length equals the number of elements, record the solution + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // Traverse all choices + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // Pruning: do not allow repeated selection of elements + if (!selected[i]) { + // Attempt: make a choice, update the state + selected[i] = true; + state.push_back(choice); + // Proceed to the next round of selection + backtrack(state, choices, selected, res); + // Retract: undo the choice, restore to the previous state + selected[i] = false; + state.pop_back(); + } + } +} + +/* Permutation I */ +vector> permutationsI(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 2, 3}; + + vector> res = permutationsI(nums); + + cout << "Input array nums = "; + printVector(nums); + cout << "All permutations res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_backtracking/permutations_ii.cpp b/en/codes/cpp/chapter_backtracking/permutations_ii.cpp new file mode 100644 index 0000000000..4cdbe8faaf --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/permutations_ii.cpp @@ -0,0 +1,56 @@ +/** + * File: permutations_ii.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Backtracking algorithm: Permutation II */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // When the state length equals the number of elements, record the solution + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // Traverse all choices + unordered_set duplicated; + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements + if (!selected[i] && duplicated.find(choice) == duplicated.end()) { + // Attempt: make a choice, update the state + duplicated.emplace(choice); // Record selected element values + selected[i] = true; + state.push_back(choice); + // Proceed to the next round of selection + backtrack(state, choices, selected, res); + // Retract: undo the choice, restore to the previous state + selected[i] = false; + state.pop_back(); + } + } +} + +/* Permutation II */ +vector> permutationsII(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 1, 2}; + + vector> res = permutationsII(nums); + + cout << "Input array nums = "; + printVector(nums); + cout << "All permutations res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp b/en/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp new file mode 100644 index 0000000000..b92cf43d7a --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -0,0 +1,39 @@ +/** + * File: preorder_traversal_i_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector res; + +/* Pre-order traversal: Example one */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // Record solution + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nInitialize binary tree" << endl; + printTree(root); + + // Pre-order traversal + preOrder(root); + + cout << "\nOutput all nodes with value 7" << endl; + vector vals; + for (TreeNode *node : res) { + vals.push_back(node->val); + } + printVector(vals); +} diff --git a/en/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp b/en/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp new file mode 100644 index 0000000000..08905abc22 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -0,0 +1,46 @@ +/** + * File: preorder_traversal_ii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* Pre-order traversal: Example two */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // Attempt + path.push_back(root); + if (root->val == 7) { + // Record solution + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // Retract + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nInitialize binary tree" << endl; + printTree(root); + + // Pre-order traversal + preOrder(root); + + cout << "\nOutput all root-to-node 7 paths" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/en/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp b/en/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp new file mode 100644 index 0000000000..2e9f0a7c97 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* Pre-order traversal: Example three */ +void preOrder(TreeNode *root) { + // Pruning + if (root == nullptr || root->val == 3) { + return; + } + // Attempt + path.push_back(root); + if (root->val == 7) { + // Record solution + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // Retract + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nInitialize binary tree" << endl; + printTree(root); + + // Pre-order traversal + preOrder(root); + + cout << "\nOutput all root-to-node 7 paths, requiring paths not to include nodes with value 3" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/en/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/en/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp new file mode 100644 index 0000000000..a1f06fa736 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Determine if the current state is a solution */ +bool isSolution(vector &state) { + return !state.empty() && state.back()->val == 7; +} + +/* Record solution */ +void recordSolution(vector &state, vector> &res) { + res.push_back(state); +} + +/* Determine if the choice is legal under the current state */ +bool isValid(vector &state, TreeNode *choice) { + return choice != nullptr && choice->val != 3; +} + +/* Update state */ +void makeChoice(vector &state, TreeNode *choice) { + state.push_back(choice); +} + +/* Restore state */ +void undoChoice(vector &state, TreeNode *choice) { + state.pop_back(); +} + +/* Backtracking algorithm: Example three */ +void backtrack(vector &state, vector &choices, vector> &res) { + // Check if it's a solution + if (isSolution(state)) { + // Record solution + recordSolution(state, res); + } + // Traverse all choices + for (TreeNode *choice : choices) { + // Pruning: check if the choice is legal + if (isValid(state, choice)) { + // Attempt: make a choice, update the state + makeChoice(state, choice); + // Proceed to the next round of selection + vector nextChoices{choice->left, choice->right}; + backtrack(state, nextChoices, res); + // Retract: undo the choice, restore to the previous state + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nInitialize binary tree" << endl; + printTree(root); + + // Backtracking algorithm + vector state; + vector choices = {root}; + vector> res; + backtrack(state, choices, res); + + cout << "\nOutput all root-to-node 7 paths, requiring paths not to include nodes with value 3" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/en/codes/cpp/chapter_backtracking/subset_sum_i.cpp b/en/codes/cpp/chapter_backtracking/subset_sum_i.cpp new file mode 100644 index 0000000000..d5971e9031 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/subset_sum_i.cpp @@ -0,0 +1,57 @@ +/** + * File: subset_sum_i.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Backtracking algorithm: Subset Sum I */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // When the subset sum equals target, record the solution + if (target == 0) { + res.push_back(state); + return; + } + // Traverse all choices + // Pruning two: start traversing from start to avoid generating duplicate subsets + for (int i = start; i < choices.size(); i++) { + // Pruning one: if the subset sum exceeds target, end the loop immediately + // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target + if (target - choices[i] < 0) { + break; + } + // Attempt: make a choice, update target, start + state.push_back(choices[i]); + // Proceed to the next round of selection + backtrack(state, target - choices[i], choices, i, res); + // Retract: undo the choice, restore to the previous state + state.pop_back(); + } +} + +/* Solve Subset Sum I */ +vector> subsetSumI(vector &nums, int target) { + vector state; // State (subset) + sort(nums.begin(), nums.end()); // Sort nums + int start = 0; // Start point for traversal + vector> res; // Result list (subset list) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumI(nums, target); + + cout << "Input array nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "All subsets summing to " << target << "is" << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp b/en/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp new file mode 100644 index 0000000000..e06e6d0a48 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Backtracking algorithm: Subset Sum I */ +void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // When the subset sum equals target, record the solution + if (total == target) { + res.push_back(state); + return; + } + // Traverse all choices + for (size_t i = 0; i < choices.size(); i++) { + // Pruning: if the subset sum exceeds target, skip that choice + if (total + choices[i] > target) { + continue; + } + // Attempt: make a choice, update elements and total + state.push_back(choices[i]); + // Proceed to the next round of selection + backtrack(state, target, total + choices[i], choices, res); + // Retract: undo the choice, restore to the previous state + state.pop_back(); + } +} + +/* Solve Subset Sum I (including duplicate subsets) */ +vector> subsetSumINaive(vector &nums, int target) { + vector state; // State (subset) + int total = 0; // Subset sum + vector> res; // Result list (subset list) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumINaive(nums, target); + + cout << "Input array nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "All subsets summing to " << target << "is" << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_backtracking/subset_sum_ii.cpp b/en/codes/cpp/chapter_backtracking/subset_sum_ii.cpp new file mode 100644 index 0000000000..49589cad05 --- /dev/null +++ b/en/codes/cpp/chapter_backtracking/subset_sum_ii.cpp @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Backtracking algorithm: Subset Sum II */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // When the subset sum equals target, record the solution + if (target == 0) { + res.push_back(state); + return; + } + // Traverse all choices + // Pruning two: start traversing from start to avoid generating duplicate subsets + // Pruning three: start traversing from start to avoid repeatedly selecting the same element + for (int i = start; i < choices.size(); i++) { + // Pruning one: if the subset sum exceeds target, end the loop immediately + // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target + if (target - choices[i] < 0) { + break; + } + // Pruning four: if the element equals the left element, it indicates that the search branch is repeated, skip it + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // Attempt: make a choice, update target, start + state.push_back(choices[i]); + // Proceed to the next round of selection + backtrack(state, target - choices[i], choices, i + 1, res); + // Retract: undo the choice, restore to the previous state + state.pop_back(); + } +} + +/* Solve Subset Sum II */ +vector> subsetSumII(vector &nums, int target) { + vector state; // State (subset) + sort(nums.begin(), nums.end()); // Sort nums + int start = 0; // Start point for traversal + vector> res; // Result list (subset list) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {4, 4, 5}; + int target = 9; + + vector> res = subsetSumII(nums, target); + + cout << "Input array nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "All subsets summing to " << target << "is" << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_computational_complexity/CMakeLists.txt b/en/codes/cpp/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 0000000000..ea2845b751 --- /dev/null +++ b/en/codes/cpp/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.cpp) +add_executable(recursion recursion.cpp) +add_executable(space_complexity space_complexity.cpp) +add_executable(time_complexity time_complexity.cpp) +add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) \ No newline at end of file diff --git a/en/codes/cpp/chapter_computational_complexity/iteration.cpp b/en/codes/cpp/chapter_computational_complexity/iteration.cpp new file mode 100644 index 0000000000..d8858ebbdc --- /dev/null +++ b/en/codes/cpp/chapter_computational_complexity/iteration.cpp @@ -0,0 +1,76 @@ +/** + * File: iteration.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* for loop */ +int forLoop(int n) { + int res = 0; + // Loop sum 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + res += i; + } + return res; +} + +/* while loop */ +int whileLoop(int n) { + int res = 0; + int i = 1; // Initialize condition variable + // Loop sum 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Update condition variable + } + return res; +} + +/* while loop (two updates) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // Initialize condition variable + // Loop sum 1, 4, 10, ... + while (i <= n) { + res += i; + // Update condition variable + i++; + i *= 2; + } + return res; +} + +/* Double for loop */ +string nestedForLoop(int n) { + ostringstream res; + // Loop i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + // Loop j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; ++j) { + res << "(" << i << ", " << j << "), "; + } + } + return res.str(); +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + cout << "\nSum result of the for loop res = " << res << endl; + + res = whileLoop(n); + cout << "\nSum result of the while loop res = " << res << endl; + + res = whileLoopII(n); + cout << "\nSum result of the while loop (with two updates) res = " << res << endl; + + string resStr = nestedForLoop(n); + cout << "\nResult of the double for loop traversal = " << resStr << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_computational_complexity/recursion.cpp b/en/codes/cpp/chapter_computational_complexity/recursion.cpp new file mode 100644 index 0000000000..686691b5c4 --- /dev/null +++ b/en/codes/cpp/chapter_computational_complexity/recursion.cpp @@ -0,0 +1,78 @@ +/** + * File: recursion.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Recursion */ +int recur(int n) { + // Termination condition + if (n == 1) + return 1; + // Recursive: recursive call + int res = recur(n - 1); + // Return: return result + return n + res; +} + +/* Simulate recursion with iteration */ +int forLoopRecur(int n) { + // Use an explicit stack to simulate the system call stack + stack stack; + int res = 0; + // Recursive: recursive call + for (int i = n; i > 0; i--) { + // Simulate "recursive" by "pushing onto the stack" + stack.push(i); + } + // Return: return result + while (!stack.empty()) { + // Simulate "return" by "popping from the stack" + res += stack.top(); + stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* Tail recursion */ +int tailRecur(int n, int res) { + // Termination condition + if (n == 0) + return res; + // Tail recursive call + return tailRecur(n - 1, res + n); +} + +/* Fibonacci sequence: Recursion */ +int fib(int n) { + // Termination condition f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // Recursive call f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // Return result f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + cout << "\nSum result of the recursive function res = " << res << endl; + + res = forLoopRecur(n); + cout << "\nSum result using iteration to simulate recursion res = " << res << endl; + + res = tailRecur(n, 0); + cout << "\nSum result of the tail-recursive function res = " << res << endl; + + res = fib(n); + cout << "The " << n << "th number in the Fibonacci sequence is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/en/codes/cpp/chapter_computational_complexity/space_complexity.cpp new file mode 100644 index 0000000000..bcc0c1c551 --- /dev/null +++ b/en/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -0,0 +1,107 @@ +/** + * File: space_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Function */ +int func() { + // Perform some operations + return 0; +} + +/* Constant complexity */ +void constant(int n) { + // Constants, variables, objects occupy O(1) space + const int a = 0; + int b = 0; + vector nums(10000); + ListNode node(0); + // Variables in a loop occupy O(1) space + for (int i = 0; i < n; i++) { + int c = 0; + } + // Functions in a loop occupy O(1) space + for (int i = 0; i < n; i++) { + func(); + } +} + +/* Linear complexity */ +void linear(int n) { + // Array of length n occupies O(n) space + vector nums(n); + // A list of length n occupies O(n) space + vector nodes; + for (int i = 0; i < n; i++) { + nodes.push_back(ListNode(i)); + } + // A hash table of length n occupies O(n) space + unordered_map map; + for (int i = 0; i < n; i++) { + map[i] = to_string(i); + } +} + +/* Linear complexity (recursive implementation) */ +void linearRecur(int n) { + cout << "Recursion n = " << n << endl; + if (n == 1) + return; + linearRecur(n - 1); +} + +/* Quadratic complexity */ +void quadratic(int n) { + // A two-dimensional list occupies O(n^2) space + vector> numMatrix; + for (int i = 0; i < n; i++) { + vector tmp; + for (int j = 0; j < n; j++) { + tmp.push_back(0); + } + numMatrix.push_back(tmp); + } +} + +/* Quadratic complexity (recursive implementation) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + vector nums(n); + cout << "Recursive n = " << n << ", length of nums = " << nums.size() << endl; + return quadraticRecur(n - 1); +} + +/* Exponential complexity (building a full binary tree) */ +TreeNode *buildTree(int n) { + if (n == 0) + return nullptr; + TreeNode *root = new TreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // Constant complexity + constant(n); + // Linear complexity + linear(n); + linearRecur(n); + // Quadratic complexity + quadratic(n); + quadraticRecur(n); + // Exponential complexity + TreeNode *root = buildTree(n); + printTree(root); + + // Free memory + freeMemoryTree(root); + + return 0; +} diff --git a/en/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/en/codes/cpp/chapter_computational_complexity/time_complexity.cpp new file mode 100644 index 0000000000..3035e75d5f --- /dev/null +++ b/en/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -0,0 +1,168 @@ +/** + * File: time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Constant complexity */ +int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; +} + +/* Linear complexity */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; +} + +/* Linear complexity (traversing an array) */ +int arrayTraversal(vector &nums) { + int count = 0; + // Loop count is proportional to the length of the array + for (int num : nums) { + count++; + } + return count; +} + +/* Quadratic complexity */ +int quadratic(int n) { + int count = 0; + // Loop count is squared in relation to the data size n + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* Quadratic complexity (bubble sort) */ +int bubbleSort(vector &nums) { + int count = 0; // Counter + // Outer loop: unsorted range is [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Swap nums[j] and nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Element swap includes 3 individual operations + } + } + } + return count; +} + +/* Exponential complexity (loop implementation) */ +int exponential(int n) { + int count = 0, base = 1; + // Cells split into two every round, forming the sequence 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* Exponential complexity (recursive implementation) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* Logarithmic complexity (loop implementation) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* Logarithmic complexity (recursive implementation) */ +int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; +} + +/* Linear logarithmic complexity */ +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; +} + +/* Factorial complexity (recursive implementation) */ +int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // From 1 split into n + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main() { + // Can modify n to experience the trend of operation count changes under various complexities + int n = 8; + cout << "Input data size n = " << n << endl; + + int count = constant(n); + cout << "Number of constant complexity operations = " << count << endl; + + count = linear(n); + cout << "Number of linear complexity operations = " << count << endl; + vector arr(n); + count = arrayTraversal(arr); + cout << "Number of linear complexity operations (traversing the array) = " << count << endl; + + count = quadratic(n); + cout << "Number of quadratic order operations = " << count << endl; + vector nums(n); + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + cout << "Number of quadratic order operations (bubble sort) = " << count << endl; + + count = exponential(n); + cout << "Number of exponential complexity operations (implemented by loop) = " << count << endl; + count = expRecur(n); + cout << "Number of exponential complexity operations (implemented by recursion) = " << count << endl; + + count = logarithmic(n); + cout << "Number of logarithmic complexity operations (implemented by loop) = " << count << endl; + count = logRecur(n); + cout << "Number of logarithmic complexity operations (implemented by recursion) = " << count << endl; + + count = linearLogRecur(n); + cout << "Number of linear logarithmic complexity operations (implemented by recursion) = " << count << endl; + + count = factorialRecur(n); + cout << "Number of factorial complexity operations (implemented by recursion) = " << count << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/en/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp new file mode 100644 index 0000000000..10bb2a8f2a --- /dev/null +++ b/en/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Generate an array with elements {1, 2, ..., n} in a randomly shuffled order */ +vector randomNumbers(int n) { + vector nums(n); + // Generate array nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Generate a random seed using system time + unsigned seed = chrono::system_clock::now().time_since_epoch().count(); + // Randomly shuffle array elements + shuffle(nums.begin(), nums.end(), default_random_engine(seed)); + return nums; +} + +/* Find the index of number 1 in array nums */ +int findOne(vector &nums) { + for (int i = 0; i < nums.size(); i++) { + // When element 1 is at the start of the array, achieve best time complexity O(1) + // When element 1 is at the end of the array, achieve worst time complexity O(n) + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + for (int i = 0; i < 1000; i++) { + int n = 100; + vector nums = randomNumbers(n); + int index = findOne(nums); + cout << "\nThe array [ 1, 2, ..., n ] after being shuffled = "; + printVector(nums); + cout << "The index of number 1 is " << index << endl; + } + return 0; +} diff --git a/en/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt b/en/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 0000000000..38dfff7107 --- /dev/null +++ b/en/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.cpp) +add_executable(build_tree build_tree.cpp) +add_executable(hanota hanota.cpp) \ No newline at end of file diff --git a/en/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp b/en/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp new file mode 100644 index 0000000000..78f9e8333e --- /dev/null +++ b/en/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp @@ -0,0 +1,46 @@ +/** + * File: binary_search_recur.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Binary search: problem f(i, j) */ +int dfs(vector &nums, int target, int i, int j) { + // If the interval is empty, indicating no target element, return -1 + if (i > j) { + return -1; + } + // Calculate midpoint index m + int m = i + (j - i) / 2; + if (nums[m] < target) { + // Recursive subproblem f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Recursive subproblem f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Found the target element, thus return its index + return m; + } +} + +/* Binary search */ +int binarySearch(vector &nums, int target) { + int n = nums.size(); + // Solve problem f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + // Binary search (double closed interval) + int index = binarySearch(nums, target); + cout << "Index of target element 6 =" << index << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_divide_and_conquer/build_tree.cpp b/en/codes/cpp/chapter_divide_and_conquer/build_tree.cpp new file mode 100644 index 0000000000..636d0db7f0 --- /dev/null +++ b/en/codes/cpp/chapter_divide_and_conquer/build_tree.cpp @@ -0,0 +1,51 @@ +/** + * File: build_tree.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Build binary tree: Divide and conquer */ +TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { + // Terminate when subtree interval is empty + if (r - l < 0) + return NULL; + // Initialize root node + TreeNode *root = new TreeNode(preorder[i]); + // Query m to divide left and right subtrees + int m = inorderMap[preorder[i]]; + // Subproblem: build left subtree + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // Subproblem: build right subtree + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Return root node + return root; +} + +/* Build binary tree */ +TreeNode *buildTree(vector &preorder, vector &inorder) { + // Initialize hash table, storing in-order elements to indices mapping + unordered_map inorderMap; + for (int i = 0; i < inorder.size(); i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1); + return root; +} + +/* Driver Code */ +int main() { + vector preorder = {3, 9, 2, 1, 7}; + vector inorder = {9, 3, 1, 2, 7}; + cout << "Pre-order traversal = "; + printVector(preorder); + cout << "In-order traversal = "; + printVector(inorder); + + TreeNode *root = buildTree(preorder, inorder); + cout << "The constructed binary tree is:\n"; + printTree(root); + + return 0; +} diff --git a/en/codes/cpp/chapter_divide_and_conquer/hanota.cpp b/en/codes/cpp/chapter_divide_and_conquer/hanota.cpp new file mode 100644 index 0000000000..c98f6fd866 --- /dev/null +++ b/en/codes/cpp/chapter_divide_and_conquer/hanota.cpp @@ -0,0 +1,66 @@ +/** + * File: hanota.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Move a disc */ +void move(vector &src, vector &tar) { + // Take out a disc from the top of src + int pan = src.back(); + src.pop_back(); + // Place the disc on top of tar + tar.push_back(pan); +} + +/* Solve the Tower of Hanoi problem f(i) */ +void dfs(int i, vector &src, vector &buf, vector &tar) { + // If only one disc remains on src, move it to tar + if (i == 1) { + move(src, tar); + return; + } + // Subproblem f(i-1): move the top i-1 discs from src with the help of tar to buf + dfs(i - 1, src, tar, buf); + // Subproblem f(1): move the remaining one disc from src to tar + move(src, tar); + // Subproblem f(i-1): move the top i-1 discs from buf with the help of src to tar + dfs(i - 1, buf, src, tar); +} + +/* Solve the Tower of Hanoi problem */ +void solveHanota(vector &A, vector &B, vector &C) { + int n = A.size(); + // Move the top n discs from A with the help of B to C + dfs(n, A, B, C); +} + +/* Driver Code */ +int main() { + // The tail of the list is the top of the pillar + vector A = {5, 4, 3, 2, 1}; + vector B = {}; + vector C = {}; + + cout << "Initial state:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + solveHanota(A, B, C); + + cout << "After disk movement:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/CMakeLists.txt b/en/codes/cpp/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 0000000000..ed185458a4 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) +add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) +add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) +add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) +add_executable(min_path_sum min_path_sum.cpp) +add_executable(unbounded_knapsack unbounded_knapsack.cpp) +add_executable(coin_change coin_change.cpp) +add_executable(coin_change_ii coin_change_ii.cpp) +add_executable(edit_distance edit_distance.cpp) \ No newline at end of file diff --git a/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp new file mode 100644 index 0000000000..5477b9aea2 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -0,0 +1,43 @@ + +/** + * File: climbing_stairs_backtrack.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Backtracking */ +void backtrack(vector &choices, int state, int n, vector &res) { + // When climbing to the nth step, add 1 to the number of solutions + if (state == n) + res[0]++; + // Traverse all choices + for (auto &choice : choices) { + // Pruning: do not allow climbing beyond the nth step + if (state + choice > n) + continue; + // Attempt: make a choice, update the state + backtrack(choices, state + choice, n, res); + // Retract + } +} + +/* Climbing stairs: Backtracking */ +int climbingStairsBacktrack(int n) { + vector choices = {1, 2}; // Can choose to climb up 1 step or 2 steps + int state = 0; // Start climbing from the 0th step + vector res = {0}; // Use res[0] to record the number of solutions + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + cout << "There are " << res << " solutions to climb " << n << " stairs" << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp new file mode 100644 index 0000000000..454d533388 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -0,0 +1,37 @@ +/** + * File: climbing_stairs_constraint_dp.cpp + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Constrained climbing stairs: Dynamic programming */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // Initialize dp table, used to store subproblem solutions + vector> dp(n + 1, vector(3, 0)); + // Initial state: preset the smallest subproblem solution + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // State transition: gradually solve larger subproblems from smaller ones + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + cout << "There are " << res << " solutions to climb " << n << " stairs" << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp new file mode 100644 index 0000000000..3072f956e1 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Search */ +int dfs(int i) { + // Known dp[1] and dp[2], return them + 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; +} + +/* Climbing stairs: Search */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + cout << "There are " << res << " solutions to climb " << n << " stairs" << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp new file mode 100644 index 0000000000..a1b2ef24f1 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_dfs_mem.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Memoized search */ +int dfs(int i, vector &mem) { + // Known dp[1] and dp[2], return them + if (i == 1 || i == 2) + return i; + // If there is a record for dp[i], return it + 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); + // Record dp[i] + mem[i] = count; + return count; +} + +/* Climbing stairs: Memoized search */ +int climbingStairsDFSMem(int n) { + // mem[i] records the total number of solutions for climbing to the ith step, -1 means no record + vector mem(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + cout << "There are " << res << " solutions to climb " << n << " stairs" << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp new file mode 100644 index 0000000000..f8974fb4cf --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Climbing stairs: Dynamic programming */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // Initialize dp table, used to store subproblem solutions + vector dp(n + 1); + // Initial state: preset the smallest subproblem solution + dp[1] = 1; + dp[2] = 2; + // State transition: gradually solve larger subproblems from smaller ones + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* Climbing stairs: Space-optimized dynamic programming */ +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; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + cout << "There are " << res << " solutions to climb " << n << " stairs" << endl; + + res = climbingStairsDPComp(n); + cout << "There are " << res << " solutions to climb " << n << " stairs" << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/coin_change.cpp b/en/codes/cpp/chapter_dynamic_programming/coin_change.cpp new file mode 100644 index 0000000000..80c974f9b7 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/coin_change.cpp @@ -0,0 +1,70 @@ +/** + * File: coin_change.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Coin change: Dynamic programming */ +int coinChangeDP(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // Initialize dp table + vector> dp(n + 1, vector(amt + 1, 0)); + // State transition: first row and first column + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // State transition: the rest of the rows and columns + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[i][a] = dp[i - 1][a]; + } else { + // The smaller value between not choosing and choosing coin i + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* Coin change: Space-optimized dynamic programming */ +int coinChangeDPComp(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // Initialize dp table + vector dp(amt + 1, MAX); + dp[0] = 0; + // State transition + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[a] = dp[a]; + } else { + // The smaller value between not choosing and choosing coin i + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 4; + + // Dynamic programming + int res = coinChangeDP(coins, amt); + cout << "The minimum number of coins required to make up the target amount is " << res << endl; + + // Space-optimized dynamic programming + res = coinChangeDPComp(coins, amt); + cout << "The minimum number of coins required to make up the target amount is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp b/en/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp new file mode 100644 index 0000000000..4a99a5e5ac --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp @@ -0,0 +1,68 @@ +/** + * File: coin_change_ii.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Coin change II: Dynamic programming */ +int coinChangeIIDP(vector &coins, int amt) { + int n = coins.size(); + // Initialize dp table + vector> dp(n + 1, vector(amt + 1, 0)); + // Initialize first column + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // State transition + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[i][a] = dp[i - 1][a]; + } else { + // The sum of the two options of not choosing and choosing coin i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* Coin change II: Space-optimized dynamic programming */ +int coinChangeIIDPComp(vector &coins, int amt) { + int n = coins.size(); + // Initialize dp table + vector dp(amt + 1, 0); + dp[0] = 1; + // State transition + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[a] = dp[a]; + } else { + // The sum of the two options of not choosing and choosing coin i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 5; + + // Dynamic programming + int res = coinChangeIIDP(coins, amt); + cout << "The number of coin combinations to make up the target amount is " << res << endl; + + // Space-optimized dynamic programming + res = coinChangeIIDPComp(coins, amt); + cout << "The number of coin combinations to make up the target amount is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/edit_distance.cpp b/en/codes/cpp/chapter_dynamic_programming/edit_distance.cpp new file mode 100644 index 0000000000..08062503cb --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/edit_distance.cpp @@ -0,0 +1,136 @@ +/** + * File: edit_distance.cpp + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Edit distance: Brute force search */ +int editDistanceDFS(string s, string t, int i, int j) { + // If both s and t are empty, return 0 + if (i == 0 && j == 0) + return 0; + // If s is empty, return the length of t + if (i == 0) + return j; + // If t is empty, return the length of s + if (j == 0) + return i; + // If the two characters are equal, skip these two characters + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 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 the minimum number of edits + return min(min(insert, del), replace) + 1; +} + +/* Edit distance: Memoized search */ +int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { + // If both s and t are empty, return 0 + if (i == 0 && j == 0) + return 0; + // If s is empty, return the length of t + if (i == 0) + return j; + // If t is empty, return the length of s + if (j == 0) + return i; + // If there is a record, return it + if (mem[i][j] != -1) + return mem[i][j]; + // If the two characters are equal, skip these two characters + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Record and return the minimum number of edits + mem[i][j] = min(min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* Edit distance: Dynamic programming */ +int editDistanceDP(string s, string t) { + int n = s.length(), m = t.length(); + vector> dp(n + 1, vector(m + 1, 0)); + // State transition: first row and first column + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // State transition: the rest of the rows and columns + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // If the two characters are equal, skip these two characters + dp[i][j] = dp[i - 1][j - 1]; + } else { + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* Edit distance: Space-optimized dynamic programming */ +int editDistanceDPComp(string s, string t) { + int n = s.length(), m = t.length(); + vector dp(m + 1, 0); + // State transition: first row + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // State transition: the rest of the rows + for (int i = 1; i <= n; i++) { + // State transition: first column + int leftup = dp[0]; // Temporarily store dp[i-1, j-1] + dp[0] = i; + // State transition: the rest of the columns + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // If the two characters are equal, skip these two characters + dp[j] = leftup; + } else { + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Update for the next round of dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +int main() { + string s = "bag"; + string t = "pack"; + int n = s.length(), m = t.length(); + + // Brute force search + int res = editDistanceDFS(s, t, n, m); + cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits.\n"; + + // Memoized search + vector> mem(n + 1, vector(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits.\n"; + + // Dynamic programming + res = editDistanceDP(s, t); + cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits.\n"; + + // Space-optimized dynamic programming + res = editDistanceDPComp(s, t); + cout << "Changing " << s << " to " << t << " requires a minimum of " << res << " edits.\n"; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/knapsack.cpp b/en/codes/cpp/chapter_dynamic_programming/knapsack.cpp new file mode 100644 index 0000000000..ec03fc6415 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/knapsack.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +using namespace std; + +/* 0-1 Knapsack: Brute force search */ +int knapsackDFS(vector &wgt, vector &val, int i, int c) { + // If all items have been chosen or the knapsack has no remaining capacity, return value 0 + if (i == 0 || c == 0) { + return 0; + } + // If exceeding the knapsack capacity, can only choose not to put it in the knapsack + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Calculate the maximum value of not putting in and putting in item i + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Return the greater value of the two options + return max(no, yes); +} + +/* 0-1 Knapsack: Memoized search */ +int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { + // If all items have been chosen or the knapsack has no remaining capacity, return value 0 + if (i == 0 || c == 0) { + return 0; + } + // If there is a record, return it + if (mem[i][c] != -1) { + return mem[i][c]; + } + // If exceeding the knapsack capacity, can only choose not to put it in the knapsack + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Calculate the maximum value of not putting in and putting in item i + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Record and return the greater value of the two options + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 Knapsack: Dynamic programming */ +int knapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Initialize dp table + vector> dp(n + 1, vector(cap + 1, 0)); + // State transition + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // If exceeding the knapsack capacity, do not choose item i + dp[i][c] = dp[i - 1][c]; + } else { + // The greater value between not choosing and choosing item i + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 0-1 Knapsack: Space-optimized dynamic programming */ +int knapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Initialize dp table + vector dp(cap + 1, 0); + // State transition + for (int i = 1; i <= n; i++) { + // Traverse in reverse order + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // The greater value between not choosing and choosing item i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + int n = wgt.size(); + + // Brute force search + int res = knapsackDFS(wgt, val, n, cap); + cout << "The maximum value within the bag capacity is " << res << endl; + + // Memoized search + vector> mem(n + 1, vector(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + cout << "The maximum value within the bag capacity is " << res << endl; + + // Dynamic programming + res = knapsackDP(wgt, val, cap); + cout << "The maximum value within the bag capacity is " << res << endl; + + // Space-optimized dynamic programming + res = knapsackDPComp(wgt, val, cap); + cout << "The maximum value within the bag capacity is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/en/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp new file mode 100644 index 0000000000..86f0a6efe4 --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Climbing stairs with minimum cost: Dynamic programming */ +int minCostClimbingStairsDP(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + // Initialize dp table, used to store subproblem solutions + vector dp(n + 1); + // Initial state: preset the smallest subproblem solution + dp[1] = cost[1]; + dp[2] = cost[2]; + // State transition: gradually solve larger subproblems from smaller ones + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* Climbing stairs with minimum cost: Space-optimized dynamic programming */ +int minCostClimbingStairsDPComp(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + cout << "Input the cost list for stairs"; + printVector(cost); + + int res = minCostClimbingStairsDP(cost); + cout << "Minimum cost to climb the stairs " << res << endl; + + res = minCostClimbingStairsDPComp(cost); + cout << "Minimum cost to climb the stairs " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp b/en/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp new file mode 100644 index 0000000000..c81c3a54ed --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp @@ -0,0 +1,116 @@ +/** + * File: min_path_sum.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Minimum path sum: Brute force search */ +int minPathSumDFS(vector> &grid, int i, int j) { + // If it's the top-left cell, terminate the search + if (i == 0 && j == 0) { + return grid[0][0]; + } + // If the row or column index is out of bounds, return a +∞ cost + if (i < 0 || j < 0) { + return INT_MAX; + } + // Calculate the minimum path cost from the top-left to (i-1, j) and (i, j-1) + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // Return the minimum path cost from the top-left to (i, j) + return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; +} + +/* Minimum path sum: Memoized search */ +int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { + // If it's the top-left cell, terminate the search + if (i == 0 && j == 0) { + return grid[0][0]; + } + // If the row or column index is out of bounds, return a +∞ cost + if (i < 0 || j < 0) { + return INT_MAX; + } + // If there is a record, return it + if (mem[i][j] != -1) { + return mem[i][j]; + } + // The minimum path cost from the left and top cells + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // Record and return the minimum path cost from the top-left to (i, j) + mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* Minimum path sum: Dynamic programming */ +int minPathSumDP(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // Initialize dp table + vector> dp(n, vector(m)); + dp[0][0] = grid[0][0]; + // State transition: first row + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // State transition: first column + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // State transition: the rest of the rows and columns + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* Minimum path sum: Space-optimized dynamic programming */ +int minPathSumDPComp(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // Initialize dp table + vector dp(m); + // State transition: first row + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // State transition: the rest of the rows + for (int i = 1; i < n; i++) { + // State transition: first column + dp[0] = dp[0] + grid[i][0]; + // State transition: the rest of the columns + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +int main() { + vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = grid.size(), m = grid[0].size(); + + // Brute force search + int res = minPathSumDFS(grid, n - 1, m - 1); + cout << "The minimum path sum from the top left corner to the bottom right corner is " << res << endl; + + // Memoized search + vector> mem(n, vector(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + cout << "The minimum path sum from the top left corner to the bottom right corner is " << res << endl; + + // Dynamic programming + res = minPathSumDP(grid); + cout << "The minimum path sum from the top left corner to the bottom right corner is " << res << endl; + + // Space-optimized dynamic programming + res = minPathSumDPComp(grid); + cout << "The minimum path sum from the top left corner to the bottom right corner is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp b/en/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp new file mode 100644 index 0000000000..4ed94f07aa --- /dev/null +++ b/en/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -0,0 +1,64 @@ +/** + * File: unbounded_knapsack.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Complete knapsack: Dynamic programming */ +int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Initialize dp table + vector> dp(n + 1, vector(cap + 1, 0)); + // State transition + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // If exceeding the knapsack capacity, do not choose item i + dp[i][c] = dp[i - 1][c]; + } else { + // The greater value between not choosing and choosing item i + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* Complete knapsack: Space-optimized dynamic programming */ +int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Initialize dp table + vector dp(cap + 1, 0); + // State transition + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // If exceeding the knapsack capacity, do not choose item i + dp[c] = dp[c]; + } else { + // The greater value between not choosing and choosing item i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver code */ +int main() { + vector wgt = {1, 2, 3}; + vector val = {5, 11, 15}; + int cap = 4; + + // Dynamic programming + int res = unboundedKnapsackDP(wgt, val, cap); + cout << "The maximum value within the bag capacity is " << res << endl; + + // Space-optimized dynamic programming + res = unboundedKnapsackDPComp(wgt, val, cap); + cout << "The maximum value within the bag capacity is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_graph/CMakeLists.txt b/en/codes/cpp/chapter_graph/CMakeLists.txt new file mode 100644 index 0000000000..4a56ce35ba --- /dev/null +++ b/en/codes/cpp/chapter_graph/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(graph_bfs graph_bfs.cpp) +add_executable(graph_dfs graph_dfs.cpp) +# add_executable(graph_adjacency_list graph_adjacency_list.cpp) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) +add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) diff --git a/en/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/en/codes/cpp/chapter_graph/graph_adjacency_list.cpp new file mode 100644 index 0000000000..1fa9489c45 --- /dev/null +++ b/en/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -0,0 +1,90 @@ +/** + * File: graph_adjacency_list.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Undirected graph class based on adjacency list */ +class GraphAdjList { + public: + // Adjacency list, key: vertex, value: all adjacent vertices of that vertex + unordered_map> adjList; + + /* Remove a specified node from vector */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* Constructor */ + GraphAdjList(const vector> &edges) { + // Add all vertices and edges + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* Get the number of vertices */ + int size() { + return adjList.size(); + } + + /* Add edge */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("Vertex does not exist"); + // Add edge vet1 - vet2 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* Remove edge */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("Vertex does not exist"); + // Remove edge vet1 - vet2 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* Add vertex */ + void addVertex(Vertex *vet) { + if (adjList.count(vet)) + return; + // Add a new linked list to the adjacency list + adjList[vet] = vector(); + } + + /* Remove vertex */ + void removeVertex(Vertex *vet) { + if (!adjList.count(vet)) + throw invalid_argument("Vertex does not exist"); + // Remove the vertex vet's corresponding linked list from the adjacency list + adjList.erase(vet); + // Traverse other vertices' linked lists, removing all edges containing vet + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* Print the adjacency list */ + void print() { + cout << "Adjacency list =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } +}; + +// See test case in graph_adjacency_list_test.cpp diff --git a/en/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/en/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 0000000000..92d96effa8 --- /dev/null +++ b/en/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* Initialize undirected graph */ + vector v = valsToVets(vector{1, 3, 2, 5, 4}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + GraphAdjList graph(edges); + cout << "\nAfter initialization, the graph is" << endl; + graph.print(); + + /* Add edge */ + // Vertices 1, 2 i.e., v[0], v[2] + graph.addEdge(v[0], v[2]); + cout << "\nAfter adding edge 1-2, the graph is" << endl; + graph.print(); + + /* Remove edge */ + // Vertices 1, 3 i.e., v[0], v[1] + graph.removeEdge(v[0], v[1]); + cout << "\nAfter removing edge 1-3, the graph is" << endl; + graph.print(); + + /* Add vertex */ + Vertex *v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\nAfter adding vertex 6, the graph is" << endl; + graph.print(); + + /* Remove vertex */ + // Vertex 3 i.e., v[1] + graph.removeVertex(v[1]); + cout << "\nAfter removing vertex 3, the graph is" << endl; + graph.print(); + + // Free memory + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/en/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/en/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp new file mode 100644 index 0000000000..245ac42f26 --- /dev/null +++ b/en/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -0,0 +1,127 @@ +/** + * File: graph_adjacency_matrix.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* Undirected graph class based on adjacency matrix */ +class GraphAdjMat { + vector vertices; // Vertex list, elements represent "vertex value", index represents "vertex index" + vector> adjMat; // Adjacency matrix, row and column indices correspond to "vertex index" + + public: + /* Constructor */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // Add vertex + for (int val : vertices) { + addVertex(val); + } + // Add edge + // Edges elements represent vertex indices + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* Get the number of vertices */ + int size() const { + return vertices.size(); + } + + /* Add vertex */ + void addVertex(int val) { + int n = size(); + // Add new vertex value to the vertex list + vertices.push_back(val); + // Add a row to the adjacency matrix + adjMat.emplace_back(vector(n, 0)); + // Add a column to the adjacency matrix + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* Remove vertex */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("Vertex does not exist"); + } + // Remove vertex at `index` from the vertex list + vertices.erase(vertices.begin() + index); + // Remove the row at `index` from the adjacency matrix + adjMat.erase(adjMat.begin() + index); + // Remove the column at `index` from the adjacency matrix + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* Add edge */ + // Parameters i, j correspond to vertices element indices + void addEdge(int i, int j) { + // Handle index out of bounds and equality + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("Vertex does not exist"); + } + // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., satisfies (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* Remove edge */ + // Parameters i, j correspond to vertices element indices + void removeEdge(int i, int j) { + // Handle index out of bounds and equality + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("Vertex does not exist"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* Print adjacency matrix */ + void print() { + cout << "Vertex list = "; + printVector(vertices); + cout << "Adjacency matrix =" << endl; + printVectorMatrix(adjMat); + } +}; + +/* Driver Code */ +int main() { + /* Initialize undirected graph */ + // Edges elements represent vertex indices + vector vertices = {1, 3, 2, 5, 4}; + vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + GraphAdjMat graph(vertices, edges); + cout << "\nAfter initialization, the graph is" << endl; + graph.print(); + + /* Add edge */ + // Indices of vertices 1, 2 are 0, 2 respectively + graph.addEdge(0, 2); + cout << "\nAfter adding edge 1-2, the graph is" << endl; + graph.print(); + + /* Remove edge */ + // Indices of vertices 1, 3 are 0, 1 respectively + graph.removeEdge(0, 1); + cout << "\nAfter removing edge 1-3, the graph is" << endl; + graph.print(); + + /* Add vertex */ + graph.addVertex(6); + cout << "\nAfter adding vertex 6, the graph is" << endl; + graph.print(); + + /* Remove vertex */ + // Index of vertex 3 is 1 + graph.removeVertex(1); + cout << "\nAfter removing vertex 3, the graph is" << endl; + graph.print(); + + return 0; +} diff --git a/en/codes/cpp/chapter_graph/graph_bfs.cpp b/en/codes/cpp/chapter_graph/graph_bfs.cpp new file mode 100644 index 0000000000..93ab145bae --- /dev/null +++ b/en/codes/cpp/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* Breadth-first traversal */ +// Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // Vertex traversal sequence + vector res; + // Hash set, used to record visited vertices + unordered_set visited = {startVet}; + // Queue used to implement BFS + queue que; + que.push(startVet); + // Starting from vertex vet, loop until all vertices are visited + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // Dequeue the vertex at the head of the queue + res.push_back(vet); // Record visited vertex + // Traverse all adjacent vertices of that vertex + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // Skip already visited vertices + que.push(adjVet); // Only enqueue unvisited vertices + visited.emplace(adjVet); // Mark the vertex as visited + } + } + // Return the vertex traversal sequence + return res; +} + +/* Driver Code */ +int main() { + /* Initialize undirected graph */ + vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + vector> edges = {{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]}}; + GraphAdjList graph(edges); + cout << "\nAfter initialization, the graph is\n"; + graph.print(); + + /* Breadth-first traversal */ + vector res = graphBFS(graph, v[0]); + cout << "\nBreadth-first traversal (BFS) vertex sequence is" << endl; + printVector(vetsToVals(res)); + + // Free memory + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/en/codes/cpp/chapter_graph/graph_dfs.cpp b/en/codes/cpp/chapter_graph/graph_dfs.cpp new file mode 100644 index 0000000000..79e1e4356c --- /dev/null +++ b/en/codes/cpp/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* Depth-first traversal helper function */ +void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { + res.push_back(vet); // Record visited vertex + visited.emplace(vet); // Mark the vertex as visited + // Traverse all adjacent vertices of that vertex + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // Skip already visited vertices + // Recursively visit adjacent vertices + dfs(graph, visited, res, adjVet); + } +} + +/* Depth-first traversal */ +// Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex +vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // Vertex traversal sequence + vector res; + // Hash set, used to record visited vertices + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +int main() { + /* Initialize undirected graph */ + vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + GraphAdjList graph(edges); + cout << "\nAfter initialization, the graph is" << endl; + graph.print(); + + /* Depth-first traversal */ + vector res = graphDFS(graph, v[0]); + cout << "\nDepth-first traversal (DFS) vertex sequence is" << endl; + printVector(vetsToVals(res)); + + // Free memory + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/en/codes/cpp/chapter_greedy/CMakeLists.txt b/en/codes/cpp/chapter_greedy/CMakeLists.txt new file mode 100644 index 0000000000..91788668d4 --- /dev/null +++ b/en/codes/cpp/chapter_greedy/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(coin_change_greedy coin_change_greedy.cpp) +add_executable(fractional_knapsack fractional_knapsack.cpp) +add_executable(max_capacity max_capacity.cpp) \ No newline at end of file diff --git a/en/codes/cpp/chapter_greedy/coin_change_greedy.cpp b/en/codes/cpp/chapter_greedy/coin_change_greedy.cpp new file mode 100644 index 0000000000..c7d1135b95 --- /dev/null +++ b/en/codes/cpp/chapter_greedy/coin_change_greedy.cpp @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Coin change: Greedy */ +int coinChangeGreedy(vector &coins, int amt) { + // Assume coins list is ordered + int i = coins.size() - 1; + int count = 0; + // Loop for greedy selection until no remaining amount + while (amt > 0) { + // Find the smallest coin close to and less than the remaining amount + while (i > 0 && coins[i] > amt) { + i--; + } + // Choose coins[i] + amt -= coins[i]; + count++; + } + // If no feasible solution is found, return -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // Greedy: can ensure finding a global optimal solution + vector coins = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "The minimum number of coins required to make up " << amt << " is " << res << endl; + + // Greedy: cannot ensure finding a global optimal solution + coins = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "The minimum number of coins required to make up " << amt << " is " << res << endl; + cout << "In reality, the minimum number needed is 3, i.e., 20 + 20 + 20" << endl; + + // Greedy: cannot ensure finding a global optimal solution + coins = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "The minimum number of coins required to make up " << amt << " is " << res << endl; + cout << "In reality, the minimum number needed is 2, i.e., 49 + 49" << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_greedy/fractional_knapsack.cpp b/en/codes/cpp/chapter_greedy/fractional_knapsack.cpp new file mode 100644 index 0000000000..1781e865c9 --- /dev/null +++ b/en/codes/cpp/chapter_greedy/fractional_knapsack.cpp @@ -0,0 +1,56 @@ +/** + * File: fractional_knapsack.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Item */ +class Item { + public: + int w; // Item weight + int v; // Item value + + Item(int w, int v) : w(w), v(v) { + } +}; + +/* Fractional knapsack: Greedy */ +double fractionalKnapsack(vector &wgt, vector &val, int cap) { + // Create an item list, containing two properties: weight, value + vector items; + for (int i = 0; i < wgt.size(); i++) { + items.push_back(Item(wgt[i], val[i])); + } + // Sort by unit value item.v / item.w from high to low + sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); + // Loop for greedy selection + double res = 0; + for (auto &item : items) { + if (item.w <= cap) { + // If the remaining capacity is sufficient, put the entire item into the knapsack + res += item.v; + cap -= item.w; + } else { + // If the remaining capacity is insufficient, put part of the item into the knapsack + res += (double)item.v / item.w * cap; + // No remaining capacity left, thus break the loop + break; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + + // Greedy algorithm + double res = fractionalKnapsack(wgt, val, cap); + cout << "The maximum value within the bag capacity is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_greedy/max_capacity.cpp b/en/codes/cpp/chapter_greedy/max_capacity.cpp new file mode 100644 index 0000000000..d573d1e5cb --- /dev/null +++ b/en/codes/cpp/chapter_greedy/max_capacity.cpp @@ -0,0 +1,39 @@ +/** + * File: max_capacity.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Maximum capacity: Greedy */ +int maxCapacity(vector &ht) { + // Initialize i, j, making them split the array at both ends + int i = 0, j = ht.size() - 1; + // Initial maximum capacity is 0 + int res = 0; + // Loop for greedy selection until the two boards meet + while (i < j) { + // Update maximum capacity + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // Move the shorter board inward + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; + + // Greedy algorithm + int res = maxCapacity(ht); + cout << "The maximum capacity is " << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_greedy/max_product_cutting.cpp b/en/codes/cpp/chapter_greedy/max_product_cutting.cpp new file mode 100644 index 0000000000..f81ce63cd6 --- /dev/null +++ b/en/codes/cpp/chapter_greedy/max_product_cutting.cpp @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Maximum product of cutting: Greedy */ +int maxProductCutting(int n) { + // When n <= 3, must cut out a 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Greedy cut out 3s, a is the number of 3s, b is the remainder + int a = n / 3; + int b = n % 3; + if (b == 1) { + // When the remainder is 1, convert a pair of 1 * 3 into 2 * 2 + return (int)pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // When the remainder is 2, do nothing + return (int)pow(3, a) * 2; + } + // When the remainder is 0, do nothing + return (int)pow(3, a); +} + +/* Driver Code */ +int main() { + int n = 58; + + // Greedy algorithm + int res = maxProductCutting(n); + cout << "Maximum cutting product is" << res << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_hashing/CMakeLists.txt b/en/codes/cpp/chapter_hashing/CMakeLists.txt new file mode 100644 index 0000000000..6b583ef556 --- /dev/null +++ b/en/codes/cpp/chapter_hashing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(hash_map hash_map.cpp) +add_executable(array_hash_map_test array_hash_map_test.cpp) +add_executable(hash_map_chaining hash_map_chaining.cpp) +add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) +add_executable(simple_hash simple_hash.cpp) +add_executable(built_in_hash built_in_hash.cpp) \ No newline at end of file diff --git a/en/codes/cpp/chapter_hashing/array_hash_map.cpp b/en/codes/cpp/chapter_hashing/array_hash_map.cpp new file mode 100644 index 0000000000..bb49553033 --- /dev/null +++ b/en/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* Key-value pair */ +struct Pair { + public: + int key; + string val; + Pair(int key, string val) { + this->key = key; + this->val = val; + } +}; + +/* Hash table based on array implementation */ +class ArrayHashMap { + private: + vector buckets; + + public: + ArrayHashMap() { + // Initialize an array, containing 100 buckets + buckets = vector(100); + } + + ~ArrayHashMap() { + // Free memory + for (const auto &bucket : buckets) { + delete bucket; + } + buckets.clear(); + } + + /* Hash function */ + int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* Query operation */ + string get(int key) { + int index = hashFunc(key); + Pair *pair = buckets[index]; + if (pair == nullptr) + return ""; + return pair->val; + } + + /* Add operation */ + void put(int key, string val) { + Pair *pair = new Pair(key, val); + int index = hashFunc(key); + buckets[index] = pair; + } + + /* Remove operation */ + void remove(int key) { + int index = hashFunc(key); + // Free memory and set to nullptr + delete buckets[index]; + buckets[index] = nullptr; + } + + /* Get all key-value pairs */ + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + pairSet.push_back(pair); + } + } + return pairSet; + } + + /* Get all keys */ + vector keySet() { + vector keySet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + keySet.push_back(pair->key); + } + } + return keySet; + } + + /* Get all values */ + vector valueSet() { + vector valueSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + valueSet.push_back(pair->val); + } + } + return valueSet; + } + + /* Print hash table */ + void print() { + for (Pair *kv : pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + } +}; + +// See test case in array_hash_map_test.cpp diff --git a/en/codes/cpp/chapter_hashing/array_hash_map_test.cpp b/en/codes/cpp/chapter_hashing/array_hash_map_test.cpp new file mode 100644 index 0000000000..6ad35ec822 --- /dev/null +++ b/en/codes/cpp/chapter_hashing/array_hash_map_test.cpp @@ -0,0 +1,52 @@ +/** + * File: array_hash_map_test.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "./array_hash_map.cpp" + +/* Driver Code */ +int main() { + /* Initialize hash table */ + ArrayHashMap map = ArrayHashMap(); + + /* Add operation */ + // Add key-value pair (key, value) to the hash table + map.put(12836, "Ha"); + map.put(15937, "Luo"); + map.put(16750, "Suan"); + map.put(13276, "Fa"); + map.put(10583, "Ya"); + cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + map.print(); + + /* Query operation */ + // Enter key to the hash table, get value + string name = map.get(15937); + cout << "\nEnter student ID 15937, found name " << name << endl; + + /* Remove operation */ + // Remove key-value pair (key, value) from the hash table + map.remove(10583); + cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl; + map.print(); + + /* Traverse hash table */ + cout << "\nTraverse key-value pairs Key->Value" << endl; + for (auto kv : map.pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + + cout << "\nIndividually traverse keys Key" << endl; + for (auto key : map.keySet()) { + cout << key << endl; + } + + cout << "\nIndividually traverse values Value" << endl; + for (auto val : map.valueSet()) { + cout << val << endl; + } + + return 0; +} diff --git a/en/codes/cpp/chapter_hashing/built_in_hash.cpp b/en/codes/cpp/chapter_hashing/built_in_hash.cpp new file mode 100644 index 0000000000..8a3d3c51d3 --- /dev/null +++ b/en/codes/cpp/chapter_hashing/built_in_hash.cpp @@ -0,0 +1,29 @@ +/** + * File: built_in_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + int num = 3; + size_t hashNum = hash()(num); + cout << "The hash value of integer " << num << " is " << hashNum << "\n"; + + bool bol = true; + size_t hashBol = hash()(bol); + cout << "The hash value of boolean " << bol << " is " << hashBol << "\n"; + + double dec = 3.14159; + size_t hashDec = hash()(dec); + cout << "The hash value of decimal " << dec << " is " << hashDec << "\n"; + + string str = "Hello algorithm"; + size_t hashStr = hash()(str); + cout << "The hash value of string " << str << " is " << hashStr << "\n"; + + // In C++, the built-in std:hash() only provides hash values for basic data types + // Hash value calculation for arrays and objects must be implemented manually +} diff --git a/en/codes/cpp/chapter_hashing/hash_map.cpp b/en/codes/cpp/chapter_hashing/hash_map.cpp new file mode 100644 index 0000000000..53c149be8f --- /dev/null +++ b/en/codes/cpp/chapter_hashing/hash_map.cpp @@ -0,0 +1,46 @@ +/** + * File: hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Initialize hash table */ + unordered_map map; + + /* Add operation */ + // Add key-value pair (key, value) to the hash table + map[12836] = "Ha"; + map[15937] = "Luo"; + map[16750] = "Suan"; + map[13276] = "Fa"; + map[10583] = "Ya"; + cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + printHashMap(map); + + /* Query operation */ + // Enter key to the hash table, get value + string name = map[15937]; + cout << "\nEnter student ID 15937, found name " << name << endl; + + /* Remove operation */ + // Remove key-value pair (key, value) from the hash table + map.erase(10583); + cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl; + printHashMap(map); + + /* Traverse hash table */ + cout << "\nTraverse key-value pairs Key->Value" << endl; + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << endl; + } + cout << "\nIterate through Key->Value using an iterator" << endl; + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + + return 0; +} diff --git a/en/codes/cpp/chapter_hashing/hash_map_chaining.cpp b/en/codes/cpp/chapter_hashing/hash_map_chaining.cpp new file mode 100644 index 0000000000..7a903958a0 --- /dev/null +++ b/en/codes/cpp/chapter_hashing/hash_map_chaining.cpp @@ -0,0 +1,150 @@ +/** + * File: hash_map_chaining.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* Chained address hash table */ +class HashMapChaining { + private: + int size; // Number of key-value pairs + int capacity; // Hash table capacity + double loadThres; // Load factor threshold for triggering expansion + int extendRatio; // Expansion multiplier + vector> buckets; // Bucket array + + public: + /* Constructor */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { + buckets.resize(capacity); + } + + /* Destructor */ + ~HashMapChaining() { + for (auto &bucket : buckets) { + for (Pair *pair : bucket) { + // Free memory + delete pair; + } + } + } + + /* Hash function */ + int hashFunc(int key) { + return key % capacity; + } + + /* Load factor */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* Query operation */ + string get(int key) { + int index = hashFunc(key); + // Traverse the bucket, if the key is found, return the corresponding val + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // If key not found, return an empty string + return ""; + } + + /* Add operation */ + void put(int key, string val) { + // When the load factor exceeds the threshold, perform expansion + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // Traverse the bucket, if the specified key is encountered, update the corresponding val and return + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // If the key is not found, add the key-value pair to the end + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* Remove operation */ + void remove(int key) { + int index = hashFunc(key); + auto &bucket = buckets[index]; + // Traverse the bucket, remove the key-value pair from it + for (int i = 0; i < bucket.size(); i++) { + if (bucket[i]->key == key) { + Pair *tmp = bucket[i]; + bucket.erase(bucket.begin() + i); // Remove key-value pair + delete tmp; // Free memory + size--; + return; + } + } + } + + /* Extend hash table */ + void extend() { + // Temporarily store the original hash table + vector> bucketsTmp = buckets; + // Initialize the extended new hash table + capacity *= extendRatio; + buckets.clear(); + buckets.resize(capacity); + size = 0; + // Move key-value pairs from the original hash table to the new hash table + for (auto &bucket : bucketsTmp) { + for (Pair *pair : bucket) { + put(pair->key, pair->val); + // Free memory + delete pair; + } + } + } + + /* Print hash table */ + void print() { + for (auto &bucket : buckets) { + cout << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } +}; + +/* Driver Code */ +int main() { + /* Initialize hash table */ + HashMapChaining map = HashMapChaining(); + + /* Add operation */ + // Add key-value pair (key, value) to the hash table + map.put(12836, "Ha"); + map.put(15937, "Luo"); + map.put(16750, "Suan"); + map.put(13276, "Fa"); + map.put(10583, "Ya"); + cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + map.print(); + + /* Query operation */ + // Enter key to the hash table, get value + string name = map.get(13276); + cout << "\nEnter student ID 13276, found name " << name << endl; + + /* Remove operation */ + // Remove key-value pair (key, value) from the hash table + map.remove(12836); + cout << "\nAfter removing 12836, the hash table is\nKey -> Value" << endl; + map.print(); + + return 0; +} diff --git a/en/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp b/en/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp new file mode 100644 index 0000000000..a66233ad9a --- /dev/null +++ b/en/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -0,0 +1,171 @@ +/** + * File: hash_map_open_addressing.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* Open addressing hash table */ +class HashMapOpenAddressing { + private: + int size; // Number of key-value pairs + int capacity = 4; // Hash table capacity + const double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion + const int extendRatio = 2; // Expansion multiplier + vector buckets; // Bucket array + Pair *TOMBSTONE = new Pair(-1, "-1"); // Removal mark + + public: + /* Constructor */ + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* Destructor */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; + } + + /* Hash function */ + int hashFunc(int key) { + return key % capacity; + } + + /* Load factor */ + double loadFactor() { + return (double)size / capacity; + } + + /* Search for the bucket index corresponding to key */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // Linear probing, break when encountering an empty bucket + while (buckets[index] != nullptr) { + // If the key is encountered, return the corresponding bucket index + if (buckets[index]->key == key) { + // If a removal mark was encountered earlier, move the key-value pair to that index + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // Return the moved bucket index + } + return index; // Return bucket index + } + // Record the first encountered removal mark + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // Calculate the bucket index, return to the head if exceeding the tail + index = (index + 1) % capacity; + } + // If the key does not exist, return the index of the insertion point + return firstTombstone == -1 ? index : firstTombstone; + } + + /* Query operation */ + string get(int key) { + // Search for the bucket index corresponding to key + int index = findBucket(key); + // If the key-value pair is found, return the corresponding val + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + return buckets[index]->val; + } + // If key-value pair does not exist, return an empty string + return ""; + } + + /* Add operation */ + void put(int key, string val) { + // When the load factor exceeds the threshold, perform expansion + if (loadFactor() > loadThres) { + extend(); + } + // Search for the bucket index corresponding to key + int index = findBucket(key); + // If the key-value pair is found, overwrite val and return + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + buckets[index]->val = val; + return; + } + // If the key-value pair does not exist, add the key-value pair + buckets[index] = new Pair(key, val); + size++; + } + + /* Remove operation */ + void remove(int key) { + // Search for the bucket index corresponding to key + int index = findBucket(key); + // If the key-value pair is found, cover it with a removal mark + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; + } + } + + /* Extend hash table */ + void extend() { + // Temporarily store the original hash table + vector bucketsTmp = buckets; + // Initialize the extended new hash table + capacity *= extendRatio; + buckets = vector(capacity, nullptr); + size = 0; + // Move key-value pairs from the original hash table to the new hash table + for (Pair *pair : bucketsTmp) { + if (pair != nullptr && pair != TOMBSTONE) { + put(pair->key, pair->val); + delete pair; + } + } + } + + /* Print hash table */ + void print() { + for (Pair *pair : buckets) { + if (pair == nullptr) { + cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; + } + } + } +}; + +/* Driver Code */ +int main() { + // Initialize hash table + HashMapOpenAddressing hashmap; + + // Add operation + // Add key-value pair (key, val) to the hash table + hashmap.put(12836, "Ha"); + hashmap.put(15937, "Luo"); + hashmap.put(16750, "Suan"); + hashmap.put(13276, "Fa"); + hashmap.put(10583, "Ya"); + cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + hashmap.print(); + + // Query operation + // Enter key to the hash table, get value val + string name = hashmap.get(13276); + cout << "\nEnter student ID 13276, found name " << name << endl; + + // Remove operation + // Remove key-value pair (key, val) from the hash table + hashmap.remove(16750); + cout << "\nAfter removing 16750, the hash table is\nKey -> Value" << endl; + hashmap.print(); + + return 0; +} diff --git a/en/codes/cpp/chapter_hashing/simple_hash.cpp b/en/codes/cpp/chapter_hashing/simple_hash.cpp new file mode 100644 index 0000000000..1cfe03fabb --- /dev/null +++ b/en/codes/cpp/chapter_hashing/simple_hash.cpp @@ -0,0 +1,66 @@ +/** + * File: simple_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Additive hash */ +int addHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* Multiplicative hash */ +int mulHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (31 * hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* XOR hash */ +int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash ^= (int)c; + } + return hash & MODULUS; +} + +/* Rotational hash */ +int rotHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS; + } + return (int)hash; +} + +/* Driver Code */ +int main() { + string key = "Hello algorithm"; + + int hash = addHash(key); + cout << "Additive hash value is " << hash << endl; + + hash = mulHash(key); + cout << "Multiplicative hash value is " << hash << endl; + + hash = xorHash(key); + cout << "XOR hash value is " << hash << endl; + + hash = rotHash(key); + cout << "Rotational hash value is " << hash << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_heap/CMakeLists.txt b/en/codes/cpp/chapter_heap/CMakeLists.txt new file mode 100644 index 0000000000..1ac33a44fe --- /dev/null +++ b/en/codes/cpp/chapter_heap/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(heap heap.cpp) +add_executable(my_heap my_heap.cpp) +add_executable(top_k top_k.cpp) diff --git a/en/codes/cpp/chapter_heap/heap.cpp b/en/codes/cpp/chapter_heap/heap.cpp new file mode 100644 index 0000000000..a87d39e4af --- /dev/null +++ b/en/codes/cpp/chapter_heap/heap.cpp @@ -0,0 +1,66 @@ +/** + * File: heap.cpp + * Created Time: 2023-01-19 + * Author: LoneRanger(836253168@qq.com) + */ + +#include "../utils/common.hpp" + +void testPush(priority_queue &heap, int val) { + heap.push(val); // Push the element into heap + cout << "\nAfter element " << val << " is added to the heap" << endl; + printHeap(heap); +} + +void testPop(priority_queue &heap) { + int val = heap.top(); + heap.pop(); + cout << "\nAfter the top element " << val << " is removed from the heap" << endl; + printHeap(heap); +} + +/* Driver Code */ +int main() { + /* Initialize the heap */ + // Initialize min-heap + // priority_queue, greater> minHeap; + // Initialize max-heap + priority_queue, less> maxHeap; + + cout << "\nThe following test case is for max-heap" << endl; + + /* Push the element into heap */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* Access heap top element */ + int peek = maxHeap.top(); + cout << "\nTop element of the heap is " << peek << endl; + + /* Pop the element at the heap top */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* Get heap size */ + int size = maxHeap.size(); + cout << "\nNumber of elements in the heap is " << size << endl; + + /* Determine if heap is empty */ + bool isEmpty = maxHeap.empty(); + cout << "\nIs the heap empty " << isEmpty << endl; + + /* Enter list and build heap */ + // Time complexity is O(n), not O(nlogn) + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + cout << "After inputting the list and building a min-heap" << endl; + printHeap(minHeap); + + return 0; +} diff --git a/en/codes/cpp/chapter_heap/my_heap.cpp b/en/codes/cpp/chapter_heap/my_heap.cpp new file mode 100644 index 0000000000..92ee6e1ca7 --- /dev/null +++ b/en/codes/cpp/chapter_heap/my_heap.cpp @@ -0,0 +1,155 @@ +/** + * File: my_heap.cpp + * Created Time: 2023-02-04 + * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* Max-heap */ +class MaxHeap { + private: + // Using a dynamic array to avoid the need for resizing + vector maxHeap; + + /* Get index of left child node */ + int left(int i) { + return 2 * i + 1; + } + + /* Get index of right child node */ + int right(int i) { + return 2 * i + 2; + } + + /* Get index of parent node */ + int parent(int i) { + return (i - 1) / 2; // Integer division down + } + + /* Start heapifying node i, from bottom to top */ + void siftUp(int i) { + while (true) { + // Get parent node of node i + int p = parent(i); + // When "crossing the root node" or "node does not need repair", end heapification + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // Swap two nodes + swap(maxHeap[i], maxHeap[p]); + // Loop upwards heapification + i = p; + } + } + + /* Start heapifying node i, from top to bottom */ + void siftDown(int i) { + while (true) { + // Determine the largest node among i, l, r, noted as ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // If node i is the largest or indices l, r are out of bounds, no further heapification needed, break + if (ma == i) + break; + swap(maxHeap[i], maxHeap[ma]); + // Loop downwards heapification + i = ma; + } + } + + public: + /* Constructor, build heap based on input list */ + MaxHeap(vector nums) { + // Add all list elements into the heap + maxHeap = nums; + // Heapify all nodes except leaves + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* Get heap size */ + int size() { + return maxHeap.size(); + } + + /* Determine if heap is empty */ + bool isEmpty() { + return size() == 0; + } + + /* Access heap top element */ + int peek() { + return maxHeap[0]; + } + + /* Push the element into heap */ + void push(int val) { + // Add node + maxHeap.push_back(val); + // Heapify from bottom to top + siftUp(size() - 1); + } + + /* Element exits heap */ + void pop() { + // Empty handling + if (isEmpty()) { + throw out_of_range("Heap is empty"); + } + // Swap the root node with the rightmost leaf node (swap the first element with the last element) + swap(maxHeap[0], maxHeap[size() - 1]); + // Remove node + maxHeap.pop_back(); + // Heapify from top to bottom + siftDown(0); + } + + /* Print heap (binary tree)*/ + void print() { + cout << "Array representation of the heap:"; + printVector(maxHeap); + cout << "Tree representation of the heap:" << endl; + TreeNode *root = vectorToTree(maxHeap); + printTree(root); + freeMemoryTree(root); + } +}; + +/* Driver Code */ +int main() { + /* Initialize max-heap */ + vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap maxHeap(vec); + cout << "\nEnter list and build heap" << endl; + maxHeap.print(); + + /* Access heap top element */ + int peek = maxHeap.peek(); + cout << "\nTop element of the heap is " << peek << endl; + + /* Push the element into heap */ + int val = 7; + maxHeap.push(val); + cout << "\nAfter element " << val << " is added to the heap" << endl; + maxHeap.print(); + + /* Pop the element at the heap top */ + peek = maxHeap.peek(); + maxHeap.pop(); + cout << "\nAfter the top element " << peek << " is removed from the heap" << endl; + maxHeap.print(); + + /* Get heap size */ + int size = maxHeap.size(); + cout << "\nNumber of elements in the heap is " << size << endl; + + /* Determine if heap is empty */ + bool isEmpty = maxHeap.isEmpty(); + cout << "\nIs the heap empty " << isEmpty << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_heap/top_k.cpp b/en/codes/cpp/chapter_heap/top_k.cpp new file mode 100644 index 0000000000..a95de446a5 --- /dev/null +++ b/en/codes/cpp/chapter_heap/top_k.cpp @@ -0,0 +1,38 @@ +/** + * File: top_k.cpp + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Using heap to find the largest k elements in an array */ +priority_queue, greater> topKHeap(vector &nums, int k) { + // Initialize min-heap + priority_queue, greater> heap; + // Enter the first k elements of the array into the heap + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // From the k+1th element, keep the heap length as k + for (int i = k; i < nums.size(); i++) { + // If the current element is larger than the heap top element, remove the heap top element and enter the current element into the heap + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +// Driver Code +int main() { + vector nums = {1, 7, 6, 3, 2}; + int k = 3; + + priority_queue, greater> res = topKHeap(nums, k); + cout << "The largest " << k << " elements are:"; + printHeap(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_searching/CMakeLists.txt b/en/codes/cpp/chapter_searching/CMakeLists.txt new file mode 100644 index 0000000000..60a223d839 --- /dev/null +++ b/en/codes/cpp/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.cpp) +add_executable(binary_search_insertion binary_search_insertion.cpp) +add_executable(binary_search_edge binary_search_edge.cpp) +add_executable(two_sum two_sum.cpp) diff --git a/en/codes/cpp/chapter_searching/binary_search.cpp b/en/codes/cpp/chapter_searching/binary_search.cpp new file mode 100644 index 0000000000..cfa143eb07 --- /dev/null +++ b/en/codes/cpp/chapter_searching/binary_search.cpp @@ -0,0 +1,59 @@ +/** + * File: binary_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Binary search (double closed interval) */ +int binarySearch(vector &nums, int target) { + // Initialize double closed interval [0, n-1], i.e., i, j point to the first element and last element of the array respectively + int i = 0, j = nums.size() - 1; + // Loop until the search interval is empty (when i > j, it is empty) + while (i <= j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) // This situation indicates that target is in the interval [m+1, j] + i = m + 1; + else if (nums[m] > target) // This situation indicates that target is in the interval [i, m-1] + j = m - 1; + else // Found the target element, thus return its index + return m; + } + // Did not find the target element, thus return -1 + return -1; +} + +/* Binary search (left closed right open interval) */ +int binarySearchLCRO(vector &nums, int target) { + // Initialize left closed right open interval [0, n), i.e., i, j point to the first element and the last element +1 of the array respectively + int i = 0, j = nums.size(); + // Loop until the search interval is empty (when i = j, it is empty) + while (i < j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) // This situation indicates that target is in the interval [m+1, j) + i = m + 1; + else if (nums[m] > target) // This situation indicates that target is in the interval [i, m) + j = m; + else // Found the target element, thus return its index + return m; + } + // Did not find the target element, thus return -1 + return -1; +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* Binary search (double closed interval) */ + int index = binarySearch(nums, target); + cout << "Index of target element 6 =" << index << endl; + + /* Binary search (left closed right open interval) */ + index = binarySearchLCRO(nums, target); + cout << "Index of target element 6 =" << index << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_searching/binary_search_edge.cpp b/en/codes/cpp/chapter_searching/binary_search_edge.cpp new file mode 100644 index 0000000000..1960fda2d2 --- /dev/null +++ b/en/codes/cpp/chapter_searching/binary_search_edge.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_edge.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Binary search for insertion point (with duplicate elements) */ +int binarySearchInsertion(const vector &nums, int target) { + int i = 0, j = nums.size() - 1; // Initialize double closed interval [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) { + i = m + 1; // Target is in interval [m+1, j] + } else { + j = m - 1; // First element less than target is in interval [i, m-1] + } + } + // Return insertion point i + return i; +} + +/* Binary search for the leftmost target */ +int binarySearchLeftEdge(vector &nums, int target) { + // Equivalent to finding the insertion point of target + int i = binarySearchInsertion(nums, target); + // Did not find target, thus return -1 + if (i == nums.size() || nums[i] != target) { + return -1; + } + // Found target, return index i + return i; +} + +/* Binary search for the rightmost target */ +int binarySearchRightEdge(vector &nums, int target) { + // Convert to finding the leftmost target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j points to the rightmost target, i points to the first element greater than target + int j = i - 1; + // Did not find target, thus return -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // Found target, return index j + return j; +} + +/* Driver Code */ +int main() { + // Array with duplicate elements + vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\nArray nums = "; + printVector(nums); + + // Binary search for left and right boundaries + for (int target : {6, 7}) { + int index = binarySearchLeftEdge(nums, target); + cout << "The leftmost index of element " << target << " is " << index << endl; + index = binarySearchRightEdge(nums, target); + cout << "The rightmost index of element " << target << " is " << index << endl; + } + + return 0; +} diff --git a/en/codes/cpp/chapter_searching/binary_search_insertion.cpp b/en/codes/cpp/chapter_searching/binary_search_insertion.cpp new file mode 100644 index 0000000000..b8b56e8a32 --- /dev/null +++ b/en/codes/cpp/chapter_searching/binary_search_insertion.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_insertion.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Binary search for insertion point (no duplicate elements) */ +int binarySearchInsertionSimple(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // Initialize double closed interval [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) { + i = m + 1; // Target is in interval [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // Target is in interval [i, m-1] + } else { + return m; // Found target, return insertion point m + } + } + // Did not find target, return insertion point i + return i; +} + +/* Binary search for insertion point (with duplicate elements) */ +int binarySearchInsertion(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // Initialize double closed interval [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) { + i = m + 1; // Target is in interval [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // Target is in interval [i, m-1] + } else { + j = m - 1; // First element less than target is in interval [i, m-1] + } + } + // Return insertion point i + return i; +} + +/* Driver Code */ +int main() { + // Array without duplicate elements + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + cout << "\nArray nums = "; + printVector(nums); + // Binary search for insertion point + for (int target : {6, 9}) { + int index = binarySearchInsertionSimple(nums, target); + cout << "The insertion point index for element " << target << " is " << index << endl; + } + + // Array with duplicate elements + nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\nArray nums = "; + printVector(nums); + // Binary search for insertion point + for (int target : {2, 6, 20}) { + int index = binarySearchInsertion(nums, target); + cout << "The insertion point index for element " << target << " is " << index << endl; + } + + return 0; +} diff --git a/en/codes/cpp/chapter_searching/hashing_search.cpp b/en/codes/cpp/chapter_searching/hashing_search.cpp new file mode 100644 index 0000000000..ad2a9fcf78 --- /dev/null +++ b/en/codes/cpp/chapter_searching/hashing_search.cpp @@ -0,0 +1,53 @@ +/** + * File: hashing_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Hash search (array) */ +int hashingSearchArray(unordered_map map, int target) { + // Hash table's key: target element, value: index + // If the hash table does not contain this key, return -1 + if (map.find(target) == map.end()) + return -1; + return map[target]; +} + +/* Hash search (linked list) */ +ListNode *hashingSearchLinkedList(unordered_map map, int target) { + // Hash table key: target node value, value: node object + // If the key is not in the hash table, return nullptr + if (map.find(target) == map.end()) + return nullptr; + return map[target]; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* Hash search (array) */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + // Initialize hash table + unordered_map map; + for (int i = 0; i < nums.size(); i++) { + map[nums[i]] = i; // key: element, value: index + } + int index = hashingSearchArray(map, target); + cout << "The index of target element 3 is " << index << endl; + + /* Hash search (linked list) */ + ListNode *head = vecToLinkedList(nums); + // Initialize hash table + unordered_map map1; + while (head != nullptr) { + map1[head->val] = head; // key: node value, value: node + head = head->next; + } + ListNode *node = hashingSearchLinkedList(map1, target); + cout << "The corresponding node object for target node value 3 is " << node << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_searching/linear_search.cpp b/en/codes/cpp/chapter_searching/linear_search.cpp new file mode 100644 index 0000000000..fca7d2588d --- /dev/null +++ b/en/codes/cpp/chapter_searching/linear_search.cpp @@ -0,0 +1,49 @@ +/** + * File: linear_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Linear search (array) */ +int linearSearchArray(vector &nums, int target) { + // Traverse array + for (int i = 0; i < nums.size(); i++) { + // Found the target element, thus return its index + if (nums[i] == target) + return i; + } + // Did not find the target element, thus return -1 + return -1; +} + +/* Linear search (linked list) */ +ListNode *linearSearchLinkedList(ListNode *head, int target) { + // Traverse the list + while (head != nullptr) { + // Found the target node, return it + if (head->val == target) + return head; + head = head->next; + } + // If the target node is not found, return nullptr + return nullptr; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* Perform linear search in array */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + int index = linearSearchArray(nums, target); + cout << "The index of target element 3 is " << index << endl; + + /* Perform linear search in linked list */ + ListNode *head = vecToLinkedList(nums); + ListNode *node = linearSearchLinkedList(head, target); + cout << "The corresponding node object for target node value 3 is " << node << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_searching/two_sum.cpp b/en/codes/cpp/chapter_searching/two_sum.cpp new file mode 100644 index 0000000000..8aa22edc92 --- /dev/null +++ b/en/codes/cpp/chapter_searching/two_sum.cpp @@ -0,0 +1,54 @@ +/** + * File: two_sum.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Method one: Brute force enumeration */ +vector twoSumBruteForce(vector &nums, int target) { + int size = nums.size(); + // Two-layer loop, time complexity is O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return {i, j}; + } + } + return {}; +} + +/* Method two: Auxiliary hash table */ +vector twoSumHashTable(vector &nums, int target) { + int size = nums.size(); + // Auxiliary hash table, space complexity is O(n) + unordered_map dic; + // Single-layer loop, time complexity is O(n) + for (int i = 0; i < size; i++) { + if (dic.find(target - nums[i]) != dic.end()) { + return {dic[target - nums[i]], i}; + } + dic.emplace(nums[i], i); + } + return {}; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + vector nums = {2, 7, 11, 15}; + int target = 13; + + // ====== Driver Code ====== + // Method one + vector res = twoSumBruteForce(nums, target); + cout << "Method one res = "; + printVector(res); + // Method two + res = twoSumHashTable(nums, target); + cout << "Method two res = "; + printVector(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/CMakeLists.txt b/en/codes/cpp/chapter_sorting/CMakeLists.txt new file mode 100644 index 0000000000..e6347cf9f3 --- /dev/null +++ b/en/codes/cpp/chapter_sorting/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(selection_sort selection_sort.cpp) +add_executable(bubble_sort bubble_sort.cpp) +add_executable(insertion_sort insertion_sort.cpp) +add_executable(merge_sort merge_sort.cpp) +add_executable(quick_sort quick_sort.cpp) +add_executable(heap_sort heap_sort.cpp) \ No newline at end of file diff --git a/en/codes/cpp/chapter_sorting/bubble_sort.cpp b/en/codes/cpp/chapter_sorting/bubble_sort.cpp new file mode 100644 index 0000000000..490774b361 --- /dev/null +++ b/en/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -0,0 +1,56 @@ +/** + * File: bubble_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Bubble sort */ +void bubbleSort(vector &nums) { + // Outer loop: unsorted range is [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Swap nums[j] and nums[j + 1] + // Here, the std + swap(nums[j], nums[j + 1]); + } + } + } +} + +/* Bubble sort (optimized with flag)*/ +void bubbleSortWithFlag(vector &nums) { + // Outer loop: unsorted range is [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + bool flag = false; // Initialize flag + // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Swap nums[j] and nums[j + 1] + // Here, the std + swap(nums[j], nums[j + 1]); + flag = true; // Record swapped elements + } + } + if (!flag) + break; // If no elements were swapped in this round of "bubbling", exit + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + bubbleSort(nums); + cout << "After bubble sort, nums = "; + printVector(nums); + + vector nums1 = {4, 1, 3, 1, 5, 2}; + bubbleSortWithFlag(nums1); + cout << "After bubble sort, nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/bucket_sort.cpp b/en/codes/cpp/chapter_sorting/bucket_sort.cpp new file mode 100644 index 0000000000..eb1713ddfb --- /dev/null +++ b/en/codes/cpp/chapter_sorting/bucket_sort.cpp @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.cpp + * Created Time: 2023-03-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Bucket sort */ +void bucketSort(vector &nums) { + // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket + int k = nums.size() / 2; + vector> buckets(k); + // 1. Distribute array elements into various buckets + for (float num : nums) { + // Input data range is [0, 1), use num * k to map to index range [0, k-1] + int i = num * k; + // Add number to bucket_idx + buckets[i].push_back(num); + } + // 2. Sort each bucket + for (vector &bucket : buckets) { + // Use built-in sorting function, can also replace with other sorting algorithms + sort(bucket.begin(), bucket.end()); + } + // 3. Traverse buckets to merge results + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +int main() { + // Assume input data is floating point, range [0, 1) + vector nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums); + cout << "After bucket sort, nums = "; + printVector(nums); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/counting_sort.cpp b/en/codes/cpp/chapter_sorting/counting_sort.cpp new file mode 100644 index 0000000000..e538f28d0d --- /dev/null +++ b/en/codes/cpp/chapter_sorting/counting_sort.cpp @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cpp + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Counting sort */ +// Simple implementation, cannot be used for sorting objects +void countingSortNaive(vector &nums) { + // 1. Count the maximum element m in the array + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. Count the occurrence of each digit + // counter[num] represents the occurrence of num + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. Traverse counter, filling each element back into the original array nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* Counting sort */ +// Complete implementation, can sort objects and is a stable sort +void countingSort(vector &nums) { + // 1. Count the maximum element m in the array + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. Count the occurrence of each digit + // counter[num] represents the occurrence of num + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" + // counter[num]-1 is the last index where num appears in res + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Traverse nums in reverse order, placing each element into the result array res + // Initialize the array res to record results + int n = nums.size(); + vector res(n); + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // Place num at the corresponding index + counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num + } + // Use result array res to overwrite the original array nums + nums = res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSortNaive(nums); + cout << "After count sort (unable to sort objects), nums = "; + printVector(nums); + + vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSort(nums1); + cout << "After count sort, nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/heap_sort.cpp b/en/codes/cpp/chapter_sorting/heap_sort.cpp new file mode 100644 index 0000000000..d2527db81d --- /dev/null +++ b/en/codes/cpp/chapter_sorting/heap_sort.cpp @@ -0,0 +1,54 @@ +/** + * File: heap_sort.cpp + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Heap length is n, start heapifying node i, from top to bottom */ +void siftDown(vector &nums, int n, int i) { + while (true) { + // Determine the largest node among i, l, r, noted as 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; + // If node i is the largest or indices l, r are out of bounds, no further heapification needed, break + if (ma == i) { + break; + } + // Swap two nodes + swap(nums[i], nums[ma]); + // Loop downwards heapification + i = ma; + } +} + +/* Heap sort */ +void heapSort(vector &nums) { + // Build heap operation: heapify all nodes except leaves + for (int i = nums.size() / 2 - 1; i >= 0; --i) { + siftDown(nums, nums.size(), i); + } + // Extract the largest element from the heap and repeat for n-1 rounds + for (int i = nums.size() - 1; i > 0; --i) { + // Swap the root node with the rightmost leaf node (swap the first element with the last element) + swap(nums[0], nums[i]); + // Start heapifying the root node, from top to bottom + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + heapSort(nums); + cout << "After heap sort, nums = "; + printVector(nums); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/insertion_sort.cpp b/en/codes/cpp/chapter_sorting/insertion_sort.cpp new file mode 100644 index 0000000000..2ad2fe1ab0 --- /dev/null +++ b/en/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Insertion sort */ +void insertionSort(vector &nums) { + // Outer loop: sorted range is [0, i-1] + for (int i = 1; i < nums.size(); i++) { + int base = nums[i], j = i - 1; + // Inner loop: insert base into the correct position within the sorted range [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // Move nums[j] to the right by one position + j--; + } + nums[j + 1] = base; // Assign base to the correct position + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + insertionSort(nums); + cout << "After insertion sort, nums = "; + printVector(nums); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/merge_sort.cpp b/en/codes/cpp/chapter_sorting/merge_sort.cpp new file mode 100644 index 0000000000..0d320c0050 --- /dev/null +++ b/en/codes/cpp/chapter_sorting/merge_sort.cpp @@ -0,0 +1,58 @@ +/** + * File: merge_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Merge left subarray and right subarray */ +void merge(vector &nums, int left, int mid, int right) { + // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] + // Create a temporary array tmp to store the merged results + vector tmp(right - left + 1); + // Initialize the start indices of the left and right subarrays + int i = left, j = mid + 1, k = 0; + // While both subarrays still have elements, compare and copy the smaller element into the temporary array + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // Copy the remaining elements of the left and right subarrays into the temporary array + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval + for (k = 0; k < tmp.size(); k++) { + nums[left + k] = tmp[k]; + } +} + +/* Merge sort */ +void mergeSort(vector &nums, int left, int right) { + // Termination condition + if (left >= right) + return; // Terminate recursion when subarray length is 1 + // Partition stage + int mid = left + (right - left) / 2; // Calculate midpoint + mergeSort(nums, left, mid); // Recursively process the left subarray + mergeSort(nums, mid + 1, right); // Recursively process the right subarray + // Merge stage + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* Merge sort */ + vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; + mergeSort(nums, 0, nums.size() - 1); + cout << "After merge sort, nums = "; + printVector(nums); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/quick_sort.cpp b/en/codes/cpp/chapter_sorting/quick_sort.cpp new file mode 100644 index 0000000000..0a7be7ec8b --- /dev/null +++ b/en/codes/cpp/chapter_sorting/quick_sort.cpp @@ -0,0 +1,166 @@ +/** + * File: quick_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Quick sort class */ +class QuickSort { + private: + /* Swap elements */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Partition */ + static int partition(vector &nums, int left, int right) { + // Use nums[left] as the pivot + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Search from right to left for the first element smaller than the pivot + while (i < j && nums[i] <= nums[left]) + i++; // Search from left to right for the first element greater than the pivot + swap(nums, i, j); // Swap these two elements + } + swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays + return i; // Return the index of the pivot + } + + public: + /* Quick sort */ + static void quickSort(vector &nums, int left, int right) { + // Terminate recursion when subarray length is 1 + if (left >= right) + return; + // Partition + int pivot = partition(nums, left, right); + // Recursively process the left subarray and right subarray + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* Quick sort class (median pivot optimization) */ +class QuickSortMedian { + private: + /* Swap elements */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Select the median of three candidate elements */ + static int medianThree(vector &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 is between l and r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l is between m and r + return right; + } + + /* Partition (median of three) */ + static int partition(vector &nums, int left, int right) { + // Select the median of three candidate elements + int med = medianThree(nums, left, (left + right) / 2, right); + // Swap the median to the array's leftmost position + swap(nums, left, med); + // Use nums[left] as the pivot + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Search from right to left for the first element smaller than the pivot + while (i < j && nums[i] <= nums[left]) + i++; // Search from left to right for the first element greater than the pivot + swap(nums, i, j); // Swap these two elements + } + swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays + return i; // Return the index of the pivot + } + + public: + /* Quick sort */ + static void quickSort(vector &nums, int left, int right) { + // Terminate recursion when subarray length is 1 + if (left >= right) + return; + // Partition + int pivot = partition(nums, left, right); + // Recursively process the left subarray and right subarray + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* Quick sort class (tail recursion optimization) */ +class QuickSortTailCall { + private: + /* Swap elements */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Partition */ + static int partition(vector &nums, int left, int right) { + // Use nums[left] as the pivot + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Search from right to left for the first element smaller than the pivot + while (i < j && nums[i] <= nums[left]) + i++; // Search from left to right for the first element greater than the pivot + swap(nums, i, j); // Swap these two elements + } + swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays + return i; // Return the index of the pivot + } + + public: + /* Quick sort (tail recursion optimization) */ + static void quickSort(vector &nums, int left, int right) { + // Terminate when subarray length is 1 + while (left < right) { + // Partition operation + int pivot = partition(nums, left, right); + // Perform quick sort on the shorter of the two subarrays + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // Recursively sort the left subarray + left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // Recursively sort the right subarray + right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] + } + } + } +}; + +/* Driver Code */ +int main() { + /* Quick sort */ + vector nums{2, 4, 1, 0, 3, 5}; + QuickSort::quickSort(nums, 0, nums.size() - 1); + cout << "After quick sort, nums = "; + printVector(nums); + + /* Quick sort (median pivot optimization) */ + vector nums1 = {2, 4, 1, 0, 3, 5}; + QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); + cout << "Quick sort (median pivot optimization) completed, nums = "; + printVector(nums1); + + /* Quick sort (tail recursion optimization) */ + vector nums2 = {2, 4, 1, 0, 3, 5}; + QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); + cout << "Quick sort (tail recursion optimization) completed, nums = "; + printVector(nums2); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/radix_sort.cpp b/en/codes/cpp/chapter_sorting/radix_sort.cpp new file mode 100644 index 0000000000..e388f36f1a --- /dev/null +++ b/en/codes/cpp/chapter_sorting/radix_sort.cpp @@ -0,0 +1,65 @@ +/** + * File: radix_sort.cpp + * Created Time: 2023-03-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Get the k-th digit of element num, where exp = 10^(k-1) */ +int digit(int num, int exp) { + // Passing exp instead of k can avoid repeated expensive exponentiation here + return (num / exp) % 10; +} + +/* Counting sort (based on nums k-th digit) */ +void countingSortDigit(vector &nums, int exp) { + // Decimal digit range is 0~9, therefore need a bucket array of length 10 + vector counter(10, 0); + int n = nums.size(); + // Count the occurrence of digits 0~9 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d + counter[d]++; // Count the occurrence of digit d + } + // Calculate prefix sum, converting "occurrence count" into "array index" + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Traverse in reverse, based on bucket statistics, place each element into res + vector res(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // Get the index j for d in the array + res[j] = nums[i]; // Place the current element at index j + counter[d]--; // Decrease the count of d by 1 + } + // Use result to overwrite the original array nums + for (int i = 0; i < n; i++) + nums[i] = res[i]; +} + +/* Radix sort */ +void radixSort(vector &nums) { + // Get the maximum element of the array, used to determine the maximum number of digits + int m = *max_element(nums.begin(), nums.end()); + // Traverse from the lowest to the highest digit + for (int exp = 1; exp <= m; exp *= 10) + // Perform counting sort on the k-th digit of array elements + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // i.e., exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +int main() { + // Radix sort + vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + radixSort(nums); + cout << "After radix sort, nums = "; + printVector(nums); + + return 0; +} diff --git a/en/codes/cpp/chapter_sorting/selection_sort.cpp b/en/codes/cpp/chapter_sorting/selection_sort.cpp new file mode 100644 index 0000000000..2504e3a25c --- /dev/null +++ b/en/codes/cpp/chapter_sorting/selection_sort.cpp @@ -0,0 +1,34 @@ +/** + * File: selection_sort.cpp + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Selection sort */ +void selectionSort(vector &nums) { + int n = nums.size(); + // Outer loop: unsorted range is [i, n-1] + for (int i = 0; i < n - 1; i++) { + // Inner loop: find the smallest element within the unsorted range + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // Record the index of the smallest element + } + // Swap the smallest element with the first element of the unsorted range + swap(nums[i], nums[k]); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + selectionSort(nums); + + cout << "After selection sort, nums = "; + printVector(nums); + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/CMakeLists.txt b/en/codes/cpp/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 0000000000..b55878a174 --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(array_deque array_deque.cpp) +add_executable(array_queue array_queue.cpp) +add_executable(array_stack array_stack.cpp) +add_executable(deque deque.cpp) +add_executable(linkedlist_deque linkedlist_deque.cpp) +add_executable(linkedlist_queue linkedlist_queue.cpp) +add_executable(linkedlist_stack linkedlist_stack.cpp) +add_executable(queue queue.cpp) +add_executable(stack stack.cpp) diff --git a/en/codes/cpp/chapter_stack_and_queue/array_deque.cpp b/en/codes/cpp/chapter_stack_and_queue/array_deque.cpp new file mode 100644 index 0000000000..922cbe8424 --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/array_deque.cpp @@ -0,0 +1,156 @@ +/** + * File: array_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Double-ended queue class based on circular array */ +class ArrayDeque { + private: + vector nums; // Array used to store elements of the double-ended queue + int front; // Front pointer, pointing to the front element + int queSize; // Length of the double-ended queue + + public: + /* Constructor */ + ArrayDeque(int capacity) { + nums.resize(capacity); + front = queSize = 0; + } + + /* Get the capacity of the double-ended queue */ + int capacity() { + return nums.size(); + } + + /* Get the length of the double-ended queue */ + int size() { + return queSize; + } + + /* Determine if the double-ended queue is empty */ + bool isEmpty() { + return queSize == 0; + } + + /* Calculate circular array index */ + int index(int i) { + // Implement circular array by modulo operation + // When i exceeds the tail of the array, return to the head + // When i exceeds the head of the array, return to the tail + return (i + capacity()) % capacity(); + } + + /* Front enqueue */ + void pushFirst(int num) { + if (queSize == capacity()) { + cout << "Double-ended queue is full" << endl; + return; + } + // Move the front pointer one position to the left + // Implement front crossing the head of the array to return to the tail by modulo operation + front = index(front - 1); + // Add num to the front + nums[front] = num; + queSize++; + } + + /* Rear enqueue */ + void pushLast(int num) { + if (queSize == capacity()) { + cout << "Double-ended queue is full" << endl; + return; + } + // Calculate rear pointer, pointing to rear index + 1 + int rear = index(front + queSize); + // Add num to the rear + nums[rear] = num; + queSize++; + } + + /* Front dequeue */ + int popFirst() { + int num = peekFirst(); + // Move front pointer one position backward + front = index(front + 1); + queSize--; + return num; + } + + /* Rear dequeue */ + int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* Access front element */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("Double-ended queue is empty"); + return nums[front]; + } + + /* Access rear element */ + int peekLast() { + if (isEmpty()) + throw out_of_range("Double-ended queue is empty"); + // Calculate rear element index + int last = index(front + queSize - 1); + return nums[last]; + } + + /* Return array for printing */ + vector toVector() { + // Only convert elements within valid length range + vector res(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Initialize double-ended queue */ + ArrayDeque *deque = new ArrayDeque(10); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "Double-ended queue deque = "; + printVector(deque->toVector()); + + /* Access element */ + int peekFirst = deque->peekFirst(); + cout << "Front element peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "Back element peekLast = " << peekLast << endl; + + /* Element enqueue */ + deque->pushLast(4); + cout << "Element 4 enqueued at the tail, deque = "; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "Element 1 enqueued at the head, deque = "; + printVector(deque->toVector()); + + /* Element dequeue */ + int popLast = deque->popLast(); + cout << "Deque tail element = " << popLast << ", after dequeuing from the tail"; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "Deque front element = " << popFirst << ", after dequeuing from the front"; + printVector(deque->toVector()); + + /* Get the length of the double-ended queue */ + int size = deque->size(); + cout << "Length of the double-ended queue size = " << size << endl; + + /* Determine if the double-ended queue is empty */ + bool isEmpty = deque->isEmpty(); + cout << "Is the double-ended queue empty = " << boolalpha << isEmpty << endl; + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/en/codes/cpp/chapter_stack_and_queue/array_queue.cpp new file mode 100644 index 0000000000..d43abbb535 --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -0,0 +1,129 @@ +/** + * File: array_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Queue class based on circular array */ +class ArrayQueue { + private: + int *nums; // Array for storing queue elements + int front; // Front pointer, pointing to the front element + int queSize; // Queue length + int queCapacity; // Queue capacity + + public: + ArrayQueue(int capacity) { + // Initialize an array + nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; + } + + /* Get the capacity of the queue */ + int capacity() { + return queCapacity; + } + + /* Get the length of the queue */ + int size() { + return queSize; + } + + /* Determine if the queue is empty */ + bool isEmpty() { + return size() == 0; + } + + /* Enqueue */ + void push(int num) { + if (queSize == queCapacity) { + cout << "Queue is full" << endl; + return; + } + // Calculate rear pointer, pointing to rear index + 1 + // Use modulo operation to wrap the rear pointer from the end of the array back to the start + int rear = (front + queSize) % queCapacity; + // Add num to the rear + nums[rear] = num; + queSize++; + } + + /* Dequeue */ + int pop() { + int num = peek(); + // Move front pointer one position backward, returning to the head of the array if it exceeds the tail + front = (front + 1) % queCapacity; + queSize--; + return num; + } + + /* Access front element */ + int peek() { + if (isEmpty()) + throw out_of_range("Queue is empty"); + return nums[front]; + } + + /* Convert array to Vector and return */ + vector toVector() { + // Only convert elements within valid length range + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; + } + return arr; + } +}; + +/* Driver Code */ +int main() { + /* Initialize queue */ + int capacity = 10; + ArrayQueue *queue = new ArrayQueue(capacity); + + /* Element enqueue */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "Queue queue = "; + printVector(queue->toVector()); + + /* Access front element */ + int peek = queue->peek(); + cout << "Front element peek = " << peek << endl; + + /* Element dequeue */ + peek = queue->pop(); + cout << "Element dequeued = " << peek << ", after dequeuing"; + printVector(queue->toVector()); + + /* Get the length of the queue */ + int size = queue->size(); + cout << "Length of the queue size = " << size << endl; + + /* Determine if the queue is empty */ + bool empty = queue->isEmpty(); + cout << "Is the queue empty = " << empty << endl; + + /* Test circular array */ + for (int i = 0; i < 10; i++) { + queue->push(i); + queue->pop(); + cout << "After the " << i << "th round of enqueueing + dequeuing, queue = "; + printVector(queue->toVector()); + } + + // Free memory + delete queue; + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/en/codes/cpp/chapter_stack_and_queue/array_stack.cpp new file mode 100644 index 0000000000..4f5af36d2a --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -0,0 +1,85 @@ +/** + * File: array_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Stack class based on array */ +class ArrayStack { + private: + vector stack; + + public: + /* Get the length of the stack */ + int size() { + return stack.size(); + } + + /* Determine if the stack is empty */ + bool isEmpty() { + return stack.size() == 0; + } + + /* Push */ + void push(int num) { + stack.push_back(num); + } + + /* Pop */ + int pop() { + int num = top(); + stack.pop_back(); + return num; + } + + /* Access stack top element */ + int top() { + if (isEmpty()) + throw out_of_range("Stack is empty"); + return stack.back(); + } + + /* Return Vector */ + vector toVector() { + return stack; + } +}; + +/* Driver Code */ +int main() { + /* Initialize stack */ + ArrayStack *stack = new ArrayStack(); + + /* Element push */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "Stack stack = "; + printVector(stack->toVector()); + + /* Access stack top element */ + int top = stack->top(); + cout << "Top element of the stack top = " << top << endl; + + /* Element pop */ + top = stack->pop(); + cout << "Element popped from the stack = " << top << ", after popping"; + printVector(stack->toVector()); + + /* Get the length of the stack */ + int size = stack->size(); + cout << "Length of the stack size = " << size << endl; + + /* Determine if it's empty */ + bool empty = stack->isEmpty(); + cout << "Is the stack empty = " << empty << endl; + + // Free memory + delete stack; + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/deque.cpp b/en/codes/cpp/chapter_stack_and_queue/deque.cpp new file mode 100644 index 0000000000..f24332ee1b --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -0,0 +1,46 @@ +/** + * File: deque.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Initialize double-ended queue */ + deque deque; + + /* Element enqueue */ + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); + deque.push_front(1); + cout << "Double-ended queue deque = "; + printDeque(deque); + + /* Access element */ + int front = deque.front(); + cout << "Front element of the queue front = " << front << endl; + int back = deque.back(); + cout << "Back element of the queue back = " << back << endl; + + /* Element dequeue */ + deque.pop_front(); + cout << "Front element dequeued = " << front << ", after dequeuing from the front"; + printDeque(deque); + deque.pop_back(); + cout << "Back element dequeued = " << back << ", after dequeuing from the back"; + printDeque(deque); + + /* Get the length of the double-ended queue */ + int size = deque.size(); + cout << "Length of the double-ended queue size = " << size << endl; + + /* Determine if the double-ended queue is empty */ + bool empty = deque.empty(); + cout << "Is the double-ended queue empty = " << empty << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/en/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp new file mode 100644 index 0000000000..1bcf74e79b --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -0,0 +1,194 @@ +/** + * File: linkedlist_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Double-linked list node */ +struct DoublyListNode { + int val; // Node value + DoublyListNode *next; // Pointer to successor node + DoublyListNode *prev; // Pointer to predecessor node + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } +}; + +/* Double-ended queue class based on double-linked list */ +class LinkedListDeque { + private: + DoublyListNode *front, *rear; // Front node front, back node rear + int queSize = 0; // Length of the double-ended queue + + public: + /* Constructor */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* Destructor */ + ~LinkedListDeque() { + // Traverse the linked list, remove nodes, free memory + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* Get the length of the double-ended queue */ + int size() { + return queSize; + } + + /* Determine if the double-ended queue is empty */ + bool isEmpty() { + return size() == 0; + } + + /* Enqueue operation */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // If the list is empty, make front and rear both point to node + if (isEmpty()) + front = rear = node; + // Front enqueue operation + else if (isFront) { + // Add node to the head of the list + front->prev = node; + node->next = front; + front = node; // Update head node + // Rear enqueue operation + } else { + // Add node to the tail of the list + rear->next = node; + node->prev = rear; + rear = node; // Update tail node + } + queSize++; // Update queue length + } + + /* Front enqueue */ + void pushFirst(int num) { + push(num, true); + } + + /* Rear enqueue */ + void pushLast(int num) { + push(num, false); + } + + /* Dequeue operation */ + int pop(bool isFront) { + if (isEmpty()) + throw out_of_range("Queue is empty"); + int val; + // Front dequeue operation + if (isFront) { + val = front->val; // Temporarily store the head node value + // Remove head node + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + } + delete front; + front = fNext; // Update head node + // Rear dequeue operation + } else { + val = rear->val; // Temporarily store the tail node value + // Remove tail node + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + } + delete rear; + rear = rPrev; // Update tail node + } + queSize--; // Update queue length + return val; + } + + /* Front dequeue */ + int popFirst() { + return pop(true); + } + + /* Rear dequeue */ + int popLast() { + return pop(false); + } + + /* Access front element */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("Double-ended queue is empty"); + return front->val; + } + + /* Access rear element */ + int peekLast() { + if (isEmpty()) + throw out_of_range("Double-ended queue is empty"); + return rear->val; + } + + /* Return array for printing */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Initialize double-ended queue */ + LinkedListDeque *deque = new LinkedListDeque(); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "Double-ended queue deque = "; + printVector(deque->toVector()); + + /* Access element */ + int peekFirst = deque->peekFirst(); + cout << "Front element peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "Back element peekLast = " << peekLast << endl; + + /* Element enqueue */ + deque->pushLast(4); + cout << "Element 4 rear enqueued, deque ="; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "Element 1 enqueued at the head, deque = "; + printVector(deque->toVector()); + + /* Element dequeue */ + int popLast = deque->popLast(); + cout << "Deque tail element = " << popLast << ", after dequeuing from the tail"; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "Deque front element = " << popFirst << ", after dequeuing from the front"; + printVector(deque->toVector()); + + /* Get the length of the double-ended queue */ + int size = deque->size(); + cout << "Length of the double-ended queue size = " << size << endl; + + /* Determine if the double-ended queue is empty */ + bool isEmpty = deque->isEmpty(); + cout << "Is the double-ended queue empty = " << boolalpha << isEmpty << endl; + + // Free memory + delete deque; + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/en/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp new file mode 100644 index 0000000000..7326a9dc4d --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -0,0 +1,120 @@ +/** + * File: linkedlist_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Queue class based on linked list */ +class LinkedListQueue { + private: + ListNode *front, *rear; // Front node front, back node rear + int queSize; + + public: + LinkedListQueue() { + front = nullptr; + rear = nullptr; + queSize = 0; + } + + ~LinkedListQueue() { + // Traverse the linked list, remove nodes, free memory + freeMemoryLinkedList(front); + } + + /* Get the length of the queue */ + int size() { + return queSize; + } + + /* Determine if the queue is empty */ + bool isEmpty() { + return queSize == 0; + } + + /* Enqueue */ + void push(int num) { + // Add num behind the tail node + ListNode *node = new ListNode(num); + // If the queue is empty, make the head and tail nodes both point to that node + if (front == nullptr) { + front = node; + rear = node; + } + // If the queue is not empty, add that node behind the tail node + else { + rear->next = node; + rear = node; + } + queSize++; + } + + /* Dequeue */ + int pop() { + int num = peek(); + // Remove head node + ListNode *tmp = front; + front = front->next; + // Free memory + delete tmp; + queSize--; + return num; + } + + /* Access front element */ + int peek() { + if (size() == 0) + throw out_of_range("Queue is empty"); + return front->val; + } + + /* Convert the linked list to Vector and return */ + vector toVector() { + ListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Initialize queue */ + LinkedListQueue *queue = new LinkedListQueue(); + + /* Element enqueue */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "Queue queue = "; + printVector(queue->toVector()); + + /* Access front element */ + int peek = queue->peek(); + cout << "Front element peek = " << peek << endl; + + /* Element dequeue */ + peek = queue->pop(); + cout << "Element dequeued = " << peek << ", after dequeuing"; + printVector(queue->toVector()); + + /* Get the length of the queue */ + int size = queue->size(); + cout << "Length of the queue size = " << size << endl; + + /* Determine if the queue is empty */ + bool empty = queue->isEmpty(); + cout << "Is the queue empty = " << empty << endl; + + // Free memory + delete queue; + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/en/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp new file mode 100644 index 0000000000..655f9f02b6 --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -0,0 +1,109 @@ +/** + * File: linkedlist_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Stack class based on linked list */ +class LinkedListStack { + private: + ListNode *stackTop; // Use the head node as the top of the stack + int stkSize; // Length of the stack + + public: + LinkedListStack() { + stackTop = nullptr; + stkSize = 0; + } + + ~LinkedListStack() { + // Traverse the linked list, remove nodes, free memory + freeMemoryLinkedList(stackTop); + } + + /* Get the length of the stack */ + int size() { + return stkSize; + } + + /* Determine if the stack is empty */ + bool isEmpty() { + return size() == 0; + } + + /* Push */ + void push(int num) { + ListNode *node = new ListNode(num); + node->next = stackTop; + stackTop = node; + stkSize++; + } + + /* Pop */ + int pop() { + int num = top(); + ListNode *tmp = stackTop; + stackTop = stackTop->next; + // Free memory + delete tmp; + stkSize--; + return num; + } + + /* Access stack top element */ + int top() { + if (isEmpty()) + throw out_of_range("Stack is empty"); + return stackTop->val; + } + + /* Convert the List to Array and return */ + vector toVector() { + ListNode *node = stackTop; + vector res(size()); + for (int i = res.size() - 1; i >= 0; i--) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Initialize stack */ + LinkedListStack *stack = new LinkedListStack(); + + /* Element push */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "Stack stack = "; + printVector(stack->toVector()); + + /* Access stack top element */ + int top = stack->top(); + cout << "Top element of the stack top = " << top << endl; + + /* Element pop */ + top = stack->pop(); + cout << "Element popped from the stack = " << top << ", after popping"; + printVector(stack->toVector()); + + /* Get the length of the stack */ + int size = stack->size(); + cout << "Length of the stack size = " << size << endl; + + /* Determine if it's empty */ + bool empty = stack->isEmpty(); + cout << "Is the stack empty = " << empty << endl; + + // Free memory + delete stack; + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/queue.cpp b/en/codes/cpp/chapter_stack_and_queue/queue.cpp new file mode 100644 index 0000000000..6414ef0366 --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -0,0 +1,41 @@ +/** + * File: queue.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Initialize queue */ + queue queue; + + /* Element enqueue */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + cout << "Queue queue = "; + printQueue(queue); + + /* Access front element */ + int front = queue.front(); + cout << "Front element of the queue front = " << front << endl; + + /* Element dequeue */ + queue.pop(); + cout << "Element dequeued = " << front << ", after dequeuing"; + printQueue(queue); + + /* Get the length of the queue */ + int size = queue.size(); + cout << "Length of the queue size = " << size << endl; + + /* Determine if the queue is empty */ + bool empty = queue.empty(); + cout << "Is the queue empty = " << empty << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_stack_and_queue/stack.cpp b/en/codes/cpp/chapter_stack_and_queue/stack.cpp new file mode 100644 index 0000000000..6b78e18d70 --- /dev/null +++ b/en/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -0,0 +1,41 @@ +/** + * File: stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Initialize stack */ + stack stack; + + /* Element push */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + cout << "Stack stack = "; + printStack(stack); + + /* Access stack top element */ + int top = stack.top(); + cout << "Top element of the stack top = " << top << endl; + + /* Element pop */ + stack.pop(); // No return value + cout << "Element popped from the stack = " << top << ", after popping"; + printStack(stack); + + /* Get the length of the stack */ + int size = stack.size(); + cout << "Length of the stack size = " << size << endl; + + /* Determine if it's empty */ + bool empty = stack.empty(); + cout << "Is the stack empty = " << empty << endl; + + return 0; +} diff --git a/en/codes/cpp/chapter_tree/CMakeLists.txt b/en/codes/cpp/chapter_tree/CMakeLists.txt new file mode 100644 index 0000000000..fa7009bcb3 --- /dev/null +++ b/en/codes/cpp/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.cpp) +add_executable(binary_search_tree binary_search_tree.cpp) +add_executable(binary_tree binary_tree.cpp) +add_executable(binary_tree_bfs binary_tree_bfs.cpp) +add_executable(binary_tree_dfs binary_tree_dfs.cpp) +add_executable(array_binary_tree array_binary_tree.cpp) \ No newline at end of file diff --git a/en/codes/cpp/chapter_tree/array_binary_tree.cpp b/en/codes/cpp/chapter_tree/array_binary_tree.cpp new file mode 100644 index 0000000000..458fa20625 --- /dev/null +++ b/en/codes/cpp/chapter_tree/array_binary_tree.cpp @@ -0,0 +1,137 @@ +/** + * File: array_binary_tree.cpp + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Array-based binary tree class */ +class ArrayBinaryTree { + public: + /* Constructor */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* List capacity */ + int size() { + return tree.size(); + } + + /* Get the value of the node at index i */ + int val(int i) { + // If index is out of bounds, return INT_MAX, representing a null + if (i < 0 || i >= size()) + return INT_MAX; + return tree[i]; + } + + /* Get the index of the left child of the node at index i */ + int left(int i) { + return 2 * i + 1; + } + + /* Get the index of the right child of the node at index i */ + int right(int i) { + return 2 * i + 2; + } + + /* Get the index of the parent of the node at index i */ + int parent(int i) { + return (i - 1) / 2; + } + + /* Level-order traversal */ + vector levelOrder() { + vector res; + // Traverse array + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* Pre-order traversal */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* In-order traversal */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* Post-order traversal */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + private: + vector tree; + + /* Depth-first traversal */ + void dfs(int i, string order, vector &res) { + // If it is an empty spot, return + if (val(i) == INT_MAX) + return; + // Pre-order traversal + if (order == "pre") + res.push_back(val(i)); + dfs(left(i), order, res); + // In-order traversal + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // Post-order traversal + if (order == "post") + res.push_back(val(i)); + } +}; + +/* Driver Code */ +int main() { + // Initialize binary tree + // Use INT_MAX to represent an empty spot nullptr + vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + TreeNode *root = vectorToTree(arr); + cout << "\nInitialize binary tree\n"; + cout << "Binary tree in array representation:\n"; + printVector(arr); + cout << "Binary tree in linked list representation:\n"; + printTree(root); + + // Array-based binary tree class + ArrayBinaryTree abt(arr); + + // Access node + int i = 1; + int l = abt.left(i), r = abt.right(i), p = abt.parent(i); + cout << "\nCurrent node's index is " << i << ", value = " << abt.val(i) << "\n"; + cout << "Its left child's index is " << l << ", value = " << (l != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; + cout << "Its right child's index is " << r << ", value = " << (r != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; + cout << "Its parent's index is " << p << ", value = " << (p != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; + + // Traverse tree + vector res = abt.levelOrder(); + cout << "\nLevel-order traversal is:"; + printVector(res); + res = abt.preOrder(); + cout << "Pre-order traversal is:"; + printVector(res); + res = abt.inOrder(); + cout << "In-order traversal is:"; + printVector(res); + res = abt.postOrder(); + cout << "Post-order traversal is:"; + printVector(res); + + return 0; +} diff --git a/en/codes/cpp/chapter_tree/avl_tree.cpp b/en/codes/cpp/chapter_tree/avl_tree.cpp new file mode 100644 index 0000000000..ac3fbeec92 --- /dev/null +++ b/en/codes/cpp/chapter_tree/avl_tree.cpp @@ -0,0 +1,233 @@ +/** + * File: avl_tree.cpp + * Created Time: 2023-02-03 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* AVL tree */ +class AVLTree { + private: + /* Update node height */ + void updateHeight(TreeNode *node) { + // Node height equals the height of the tallest subtree + 1 + node->height = max(height(node->left), height(node->right)) + 1; + } + + /* Right rotation operation */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child = node->left; + TreeNode *grandChild = child->right; + // Rotate node to the right around child + child->right = node; + node->left = grandChild; + // Update node height + updateHeight(node); + updateHeight(child); + // Return the root of the subtree after rotation + return child; + } + + /* Left rotation operation */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child = node->right; + TreeNode *grandChild = child->left; + // Rotate node to the left around child + child->left = node; + node->right = grandChild; + // Update node height + updateHeight(node); + updateHeight(child); + // Return the root of the subtree after rotation + return child; + } + + /* Perform rotation operation to restore balance to the subtree */ + TreeNode *rotate(TreeNode *node) { + // Get the balance factor of node + int _balanceFactor = balanceFactor(node); + // Left-leaning tree + if (_balanceFactor > 1) { + if (balanceFactor(node->left) >= 0) { + // Right rotation + return rightRotate(node); + } else { + // First left rotation then right rotation + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // Right-leaning tree + if (_balanceFactor < -1) { + if (balanceFactor(node->right) <= 0) { + // Left rotation + return leftRotate(node); + } else { + // First right rotation then left rotation + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // Balanced tree, no rotation needed, return + return node; + } + + /* Recursively insert node (helper method) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == nullptr) + return new TreeNode(val); + /* 1. Find insertion position and insert node */ + if (val < node->val) + node->left = insertHelper(node->left, val); + else if (val > node->val) + node->right = insertHelper(node->right, val); + else + return node; // Do not insert duplicate nodes, return + updateHeight(node); // Update node height + /* 2. Perform rotation operation to restore balance to the subtree */ + node = rotate(node); + // Return the root node of the subtree + return node; + } + + /* Recursively remove node (helper method) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 1. Find and remove the node */ + if (val < node->val) + node->left = removeHelper(node->left, val); + else if (val > node->val) + node->right = removeHelper(node->right, val); + else { + if (node->left == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // Number of child nodes = 0, remove node and return + if (child == nullptr) { + delete node; + return nullptr; + } + // Number of child nodes = 1, remove node + else { + delete node; + node = child; + } + } else { + // Number of child nodes = 2, remove the next node in in-order traversal and replace the current node with it + TreeNode *temp = node->right; + while (temp->left != nullptr) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + updateHeight(node); // Update node height + /* 2. Perform rotation operation to restore balance to the subtree */ + node = rotate(node); + // Return the root node of the subtree + return node; + } + + public: + TreeNode *root; // Root node + + /* Get node height */ + int height(TreeNode *node) { + // Empty node height is -1, leaf node height is 0 + return node == nullptr ? -1 : node->height; + } + + /* Get balance factor */ + int balanceFactor(TreeNode *node) { + // Empty node balance factor is 0 + if (node == nullptr) + return 0; + // Node balance factor = left subtree height - right subtree height + return height(node->left) - height(node->right); + } + + /* Insert node */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* Remove node */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* Search node */ + TreeNode *search(int val) { + TreeNode *cur = root; + // Loop find, break after passing leaf nodes + while (cur != nullptr) { + // Target node is in cur's right subtree + if (cur->val < val) + cur = cur->right; + // Target node is in cur's left subtree + else if (cur->val > val) + cur = cur->left; + // Found target node, break loop + else + break; + } + // Return target node + return cur; + } + + /*Constructor*/ + AVLTree() : root(nullptr) { + } + + /*Destructor*/ + ~AVLTree() { + freeMemoryTree(root); + } +}; + +void testInsert(AVLTree &tree, int val) { + tree.insert(val); + cout << "\nAfter inserting node " << val << ", the AVL tree is" << endl; + printTree(tree.root); +} + +void testRemove(AVLTree &tree, int val) { + tree.remove(val); + cout << "\nAfter removing node " << val << ", the AVL tree is" << endl; + printTree(tree.root); +} + +/* Driver Code */ +int main() { + /* Initialize empty AVL tree */ + AVLTree avlTree; + + /* Insert node */ + // Notice how the AVL tree maintains balance after inserting nodes + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* Insert duplicate node */ + testInsert(avlTree, 7); + + /* Remove node */ + // Notice how the AVL tree maintains balance after removing nodes + testRemove(avlTree, 8); // Remove node with degree 0 + testRemove(avlTree, 5); // Remove node with degree 1 + testRemove(avlTree, 4); // Remove node with degree 2 + + /* Search node */ + TreeNode *node = avlTree.search(7); + cout << "\nThe found node object is " << node << ", node value =" << node->val << endl; +} diff --git a/en/codes/cpp/chapter_tree/binary_search_tree.cpp b/en/codes/cpp/chapter_tree/binary_search_tree.cpp new file mode 100644 index 0000000000..2f4d7895fd --- /dev/null +++ b/en/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -0,0 +1,170 @@ +/** + * File: binary_search_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Binary search tree */ +class BinarySearchTree { + private: + TreeNode *root; + + public: + /* Constructor */ + BinarySearchTree() { + // Initialize empty tree + root = nullptr; + } + + /* Destructor */ + ~BinarySearchTree() { + freeMemoryTree(root); + } + + /* Get binary tree root node */ + TreeNode *getRoot() { + return root; + } + + /* Search node */ + TreeNode *search(int num) { + TreeNode *cur = root; + // Loop find, break after passing leaf nodes + while (cur != nullptr) { + // Target node is in cur's right subtree + if (cur->val < num) + cur = cur->right; + // Target node is in cur's left subtree + else if (cur->val > num) + cur = cur->left; + // Found target node, break loop + else + break; + } + // Return target node + return cur; + } + + /* Insert node */ + void insert(int num) { + // If tree is empty, initialize root node + if (root == nullptr) { + root = new TreeNode(num); + return; + } + TreeNode *cur = root, *pre = nullptr; + // Loop find, break after passing leaf nodes + while (cur != nullptr) { + // Found duplicate node, thus return + if (cur->val == num) + return; + pre = cur; + // Insertion position is in cur's right subtree + if (cur->val < num) + cur = cur->right; + // Insertion position is in cur's left subtree + else + cur = cur->left; + } + // Insert node + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; + } + + /* Remove node */ + void remove(int num) { + // If tree is empty, return + if (root == nullptr) + return; + TreeNode *cur = root, *pre = nullptr; + // Loop find, break after passing leaf nodes + while (cur != nullptr) { + // Found node to be removed, break loop + if (cur->val == num) + break; + pre = cur; + // Node to be removed is in cur's right subtree + if (cur->val < num) + cur = cur->right; + // Node to be removed is in cur's left subtree + else + cur = cur->left; + } + // If no node to be removed, return + if (cur == nullptr) + return; + // Number of child nodes = 0 or 1 + if (cur->left == nullptr || cur->right == nullptr) { + // When the number of child nodes = 0 / 1, child = nullptr / that child node + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // Remove node cur + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // If the removed node is the root, reassign the root + root = child; + } + // Free memory + delete cur; + } + // Number of child nodes = 2 + else { + // Get the next node in in-order traversal of cur + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // Recursively remove node tmp + remove(tmp->val); + // Replace cur with tmp + cur->val = tmpVal; + } + } +}; + +/* Driver Code */ +int main() { + /* Initialize binary search tree */ + BinarySearchTree *bst = new BinarySearchTree(); + // Note that different insertion orders can result in various tree structures. This particular sequence creates a perfect binary tree + vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + for (int num : nums) { + bst->insert(num); + } + cout << endl << "The initialized binary tree is\n" << endl; + printTree(bst->getRoot()); + + /* Search node */ + TreeNode *node = bst->search(7); + cout << endl << "The found node object is " << node << ", node value =" << node->val << endl; + + /* Insert node */ + bst->insert(16); + cout << endl << "After inserting node 16, the binary tree is\n" << endl; + printTree(bst->getRoot()); + + /* Remove node */ + bst->remove(1); + cout << endl << "After removing node 1, the binary tree is\n" << endl; + printTree(bst->getRoot()); + bst->remove(2); + cout << endl << "After removing node 2, the binary tree is\n" << endl; + printTree(bst->getRoot()); + bst->remove(4); + cout << endl << "After removing node 4, the binary tree is\n" << endl; + printTree(bst->getRoot()); + + // Free memory + delete bst; + + return 0; +} diff --git a/en/codes/cpp/chapter_tree/binary_tree.cpp b/en/codes/cpp/chapter_tree/binary_tree.cpp new file mode 100644 index 0000000000..f1b13b2309 --- /dev/null +++ b/en/codes/cpp/chapter_tree/binary_tree.cpp @@ -0,0 +1,43 @@ +/** + * File: binary_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Initialize binary tree */ + // Initialize node + TreeNode *n1 = new TreeNode(1); + TreeNode *n2 = new TreeNode(2); + TreeNode *n3 = new TreeNode(3); + TreeNode *n4 = new TreeNode(4); + TreeNode *n5 = new TreeNode(5); + // Construct node references (pointers) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + cout << endl << "Initialize binary tree\n" << endl; + printTree(n1); + + /* Insert and remove nodes */ + TreeNode *P = new TreeNode(0); + // Insert node P between n1 -> n2 + n1->left = P; + P->left = n2; + cout << endl << "After inserting node P\n" << endl; + printTree(n1); + // Remove node P + n1->left = n2; + delete P; // Free memory + cout << endl << "After removing node P\n" << endl; + printTree(n1); + + // Free memory + freeMemoryTree(n1); + + return 0; +} diff --git a/en/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/en/codes/cpp/chapter_tree/binary_tree_bfs.cpp new file mode 100644 index 0000000000..80e094f198 --- /dev/null +++ b/en/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Level-order traversal */ +vector levelOrder(TreeNode *root) { + // Initialize queue, add root node + queue queue; + queue.push(root); + // Initialize a list to store the traversal sequence + vector vec; + while (!queue.empty()) { + TreeNode *node = queue.front(); + queue.pop(); // Queue dequeues + vec.push_back(node->val); // Save node value + if (node->left != nullptr) + queue.push(node->left); // Left child node enqueues + if (node->right != nullptr) + queue.push(node->right); // Right child node enqueues + } + return vec; +} + +/* Driver Code */ +int main() { + /* Initialize binary tree */ + // Use a specific function to convert an array into a binary tree + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "Initialize binary tree\n" << endl; + printTree(root); + + /* Level-order traversal */ + vector vec = levelOrder(root); + cout << endl << "Sequence of nodes in level-order traversal = "; + printVector(vec); + + return 0; +} diff --git a/en/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/en/codes/cpp/chapter_tree/binary_tree_dfs.cpp new file mode 100644 index 0000000000..c74ede2fe8 --- /dev/null +++ b/en/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +// Initialize the list for storing traversal sequences +vector vec; + +/* Pre-order traversal */ +void preOrder(TreeNode *root) { + if (root == nullptr) + return; + // Visit priority: root node -> left subtree -> right subtree + vec.push_back(root->val); + preOrder(root->left); + preOrder(root->right); +} + +/* In-order traversal */ +void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // Visit priority: left subtree -> root node -> right subtree + inOrder(root->left); + vec.push_back(root->val); + inOrder(root->right); +} + +/* Post-order traversal */ +void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // Visit priority: left subtree -> right subtree -> root node + postOrder(root->left); + postOrder(root->right); + vec.push_back(root->val); +} + +/* Driver Code */ +int main() { + /* Initialize binary tree */ + // Use a specific function to convert an array into a binary tree + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "Initialize binary tree\n" << endl; + printTree(root); + + /* Pre-order traversal */ + vec.clear(); + preOrder(root); + cout << endl << "Sequence of nodes in pre-order traversal = "; + printVector(vec); + + /* In-order traversal */ + vec.clear(); + inOrder(root); + cout << endl << "Sequence of nodes in in-order traversal = "; + printVector(vec); + + /* Post-order traversal */ + vec.clear(); + postOrder(root); + cout << endl << "Sequence of nodes in post-order traversal = "; + printVector(vec); + + return 0; +} diff --git a/en/codes/cpp/utils/CMakeLists.txt b/en/codes/cpp/utils/CMakeLists.txt new file mode 100644 index 0000000000..775a558690 --- /dev/null +++ b/en/codes/cpp/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(utils + common.hpp print_utils.hpp + list_node.hpp tree_node.hpp + vertex.hpp) \ No newline at end of file diff --git a/en/codes/cpp/utils/common.hpp b/en/codes/cpp/utils/common.hpp new file mode 100644 index 0000000000..c72dabd882 --- /dev/null +++ b/en/codes/cpp/utils/common.hpp @@ -0,0 +1,28 @@ +/** + * File: common.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.hpp" +#include "print_utils.hpp" +#include "tree_node.hpp" +#include "vertex.hpp" + +using namespace std; diff --git a/en/codes/cpp/utils/list_node.hpp b/en/codes/cpp/utils/list_node.hpp new file mode 100644 index 0000000000..b774a5610f --- /dev/null +++ b/en/codes/cpp/utils/list_node.hpp @@ -0,0 +1,42 @@ +/** + * File: list_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* Linked list node */ +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(nullptr) { + } +}; + +/* Deserialize a list into a linked list */ +ListNode *vecToLinkedList(vector list) { + ListNode *dum = new ListNode(0); + ListNode *head = dum; + for (int val : list) { + head->next = new ListNode(val); + head = head->next; + } + return dum->next; +} + +/* Free memory allocated to the linked list */ +void freeMemoryLinkedList(ListNode *cur) { + // Free memory + ListNode *pre; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } +} diff --git a/en/codes/cpp/utils/print_utils.hpp b/en/codes/cpp/utils/print_utils.hpp new file mode 100644 index 0000000000..08420244fd --- /dev/null +++ b/en/codes/cpp/utils/print_utils.hpp @@ -0,0 +1,228 @@ +/** + * File: print_utils.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com) + */ + +#pragma once + +#include "list_node.hpp" +#include "tree_node.hpp" +#include +#include +#include +#include + +/* Find an element in a vector */ +template int vecFind(const vector &vec, T ele) { + int j = INT_MAX; + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == ele) { + j = i; + } + } + return j; +} + +/* Concatenate a vector with a delim */ +template string strJoin(const string &delim, const T &vec) { + ostringstream s; + for (const auto &i : vec) { + if (&i != &vec[0]) { + s << delim; + } + s << i; + } + return s.str(); +} + +/* Repeat a string for n times */ +string strRepeat(string str, int n) { + ostringstream os; + for (int i = 0; i < n; i++) + os << str; + return os.str(); +} + +/* Print array */ +template void printArray(T *arr, int n) { + cout << "["; + for (int i = 0; i < n - 1; i++) { + cout << arr[i] << ", "; + } + if (n >= 1) + cout << arr[n - 1] << "]" << endl; + else + cout << "]" << endl; +} + +/* Get the Vector String object */ +template string getVectorString(vector &list) { + return "[" + strJoin(", ", list) + "]"; +} + +/* Print list */ +template void printVector(vector list) { + cout << getVectorString(list) << '\n'; +} + +/* Print matrix */ +template void printVectorMatrix(vector> &matrix) { + cout << "[" << '\n'; + for (vector &list : matrix) + cout << " " + getVectorString(list) + "," << '\n'; + cout << "]" << '\n'; +} + +/* Print linked list */ +void printLinkedList(ListNode *head) { + vector list; + while (head != nullptr) { + list.push_back(head->val); + head = head->next; + } + + cout << strJoin(" -> ", list) << '\n'; +} + +struct Trunk { + Trunk *prev; + string str; + Trunk(Trunk *prev, string str) { + this->prev = prev; + this->str = str; + } +}; + +void showTrunks(Trunk *p) { + if (p == nullptr) { + return; + } + + showTrunks(p->prev); + cout << p->str; +} + +/** + * 打印二叉树 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode *root, Trunk *prev, bool isRight) { + if (root == nullptr) { + return; + } + + string prev_str = " "; + Trunk trunk(prev, prev_str); + + printTree(root->right, &trunk, true); + + if (!prev) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev->str = prev_str; + } + + showTrunks(&trunk); + cout << " " << root->val << endl; + + if (prev) { + prev->str = prev_str; + } + trunk.str = " |"; + + printTree(root->left, &trunk, false); +} + +/* Print binary tree */ +void printTree(TreeNode *root) { + printTree(root, nullptr, false); +} + +/* Print stack */ +template void printStack(stack stk) { + // Reverse the input stack + stack tmp; + while (!stk.empty()) { + tmp.push(stk.top()); + stk.pop(); + } + // Generate the string to print + ostringstream s; + bool flag = true; + while (!tmp.empty()) { + if (flag) { + s << tmp.top(); + flag = false; + } else + s << ", " << tmp.top(); + tmp.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* Print queue */ +template void printQueue(queue queue) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!queue.empty()) { + if (flag) { + s << queue.front(); + flag = false; + } else + s << ", " << queue.front(); + queue.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* Print deque */ +template void printDeque(deque deque) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!deque.empty()) { + if (flag) { + s << deque.front(); + flag = false; + } else + s << ", " << deque.front(); + deque.pop_front(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* Print hash table */ +// Define template parameters TKey and TValue to specify the types of key-value pairs +template void printHashMap(unordered_map map) { + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << '\n'; + } +} + +/* Expose the underlying storage of the priority_queue container */ +template S &Container(priority_queue &pq) { + struct HackedQueue : private priority_queue { + static S &Container(priority_queue &pq) { + return pq.*&HackedQueue::c; + } + }; + return HackedQueue::Container(pq); +} + +/* Print heap (Priority queue) */ +template void printHeap(priority_queue &heap) { + vector vec = Container(heap); + cout << "Array representation of the heap:"; + printVector(vec); + cout << "Tree representation of the heap:" << endl; + TreeNode *root = vectorToTree(vec); + printTree(root); + freeMemoryTree(root); +} diff --git a/en/codes/cpp/utils/tree_node.hpp b/en/codes/cpp/utils/tree_node.hpp new file mode 100644 index 0000000000..b4c2b6fc76 --- /dev/null +++ b/en/codes/cpp/utils/tree_node.hpp @@ -0,0 +1,84 @@ +/** + * File: tree_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* Binary tree node structure */ +struct TreeNode { + int val{}; + int height = 0; + TreeNode *parent{}; + TreeNode *left{}; + TreeNode *right{}; + TreeNode() = default; + explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) { + } +}; + +// For serialization encoding rules, refer to: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// Array representation of the binary tree: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// Linked list representation of the binary tree: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* Deserialize a list into a binary tree: Recursively */ +TreeNode *vectorToTreeDFS(vector &arr, int i) { + if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) { + return nullptr; + } + TreeNode *root = new TreeNode(arr[i]); + root->left = vectorToTreeDFS(arr, 2 * i + 1); + root->right = vectorToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* Deserialize a list into a binary tree */ +TreeNode *vectorToTree(vector arr) { + return vectorToTreeDFS(arr, 0); +} + +/* Serialize a binary tree into a list: Recursively */ +void treeToVecorDFS(TreeNode *root, int i, vector &res) { + if (root == nullptr) + return; + while (i >= res.size()) { + res.push_back(INT_MAX); + } + res[i] = root->val; + treeToVecorDFS(root->left, 2 * i + 1, res); + treeToVecorDFS(root->right, 2 * i + 2, res); +} + +/* Serialize a binary tree into a list */ +vector treeToVecor(TreeNode *root) { + vector res; + treeToVecorDFS(root, 0, res); + return res; +} + +/* Free memory allocated to the binary tree */ +void freeMemoryTree(TreeNode *root) { + if (root == nullptr) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + delete root; +} diff --git a/en/codes/cpp/utils/vertex.hpp b/en/codes/cpp/utils/vertex.hpp new file mode 100644 index 0000000000..63d9a8146e --- /dev/null +++ b/en/codes/cpp/utils/vertex.hpp @@ -0,0 +1,36 @@ +/** + * File: vertex.hpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include + +using namespace std; + +/* Vertex class */ +struct Vertex { + int val; + Vertex(int x) : val(x) { + } +}; + +/* Input a list of values vals, return a list of vertices vets */ +vector valsToVets(vector vals) { + vector vets; + for (int val : vals) { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* Input a list of vertices vets, return a list of values vals */ +vector vetsToVals(vector vets) { + vector vals; + for (Vertex *vet : vets) { + vals.push_back(vet->val); + } + return vals; +} diff --git a/en/codes/java/.gitignore b/en/codes/java/.gitignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/en/codes/java/.gitignore @@ -0,0 +1 @@ +build diff --git a/en/codes/java/chapter_array_and_linkedlist/array.java b/en/codes/java/chapter_array_and_linkedlist/array.java new file mode 100644 index 0000000000..5e8ed875db --- /dev/null +++ b/en/codes/java/chapter_array_and_linkedlist/array.java @@ -0,0 +1,105 @@ +/** + * File: array.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class array { + /* Random access to elements */ + static int randomAccess(int[] nums) { + // Randomly select a number in the interval [0, nums.length) + int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); + // Retrieve and return a random element + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* Extend array length */ + static int[] extend(int[] nums, int enlarge) { + // Initialize an extended length array + int[] res = new int[nums.length + enlarge]; + // Copy all elements from the original array to the new array + for (int i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // Return the new array after expansion + return res; + } + + /* Insert element num at `index` */ + static void insert(int[] nums, int num, int index) { + // Move all elements after `index` one position backward + for (int i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Assign num to the element at index + nums[index] = num; + } + + /* Remove the element at `index` */ + static void remove(int[] nums, int index) { + // Move all elements after `index` one position forward + for (int i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* Traverse array */ + static void traverse(int[] nums) { + int count = 0; + // Traverse array by index + for (int i = 0; i < nums.length; i++) { + count += nums[i]; + } + // Traverse array elements + for (int num : nums) { + count += num; + } + } + + /* Search for a specified element in the array */ + static int find(int[] nums, int target) { + for (int i = 0; i < nums.length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* Initialize an array */ + int[] arr = new int[5]; + System.out.println("Array arr = " + Arrays.toString(arr)); + int[] nums = { 1, 3, 2, 5, 4 }; + System.out.println("Array nums = " + Arrays.toString(nums)); + + /* Random access */ + int randomNum = randomAccess(nums); + System.out.println("Get a random element from nums = " + randomNum); + + /* Length extension */ + nums = extend(nums, 3); + System.out.println("Extend the array length to 8, resulting in nums = " + Arrays.toString(nums)); + + /* Insert element */ + insert(nums, 6, 3); + System.out.println("Insert the number 6 at index 3, resulting in nums = " + Arrays.toString(nums)); + + /* Remove element */ + remove(nums, 2); + System.out.println("Remove the element at index 2, resulting in nums = " + Arrays.toString(nums)); + + /* Traverse array */ + traverse(nums); + + /* Search for elements */ + int index = find(nums, 3); + System.out.println("Find element 3 in nums, index = " + index); + } +} diff --git a/en/codes/java/chapter_array_and_linkedlist/linked_list.java b/en/codes/java/chapter_array_and_linkedlist/linked_list.java new file mode 100644 index 0000000000..e8f6194872 --- /dev/null +++ b/en/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -0,0 +1,86 @@ +/** + * File: linked_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import utils.*; + +public class linked_list { + /* Insert node P after node n0 in the linked list */ + static void insert(ListNode n0, ListNode P) { + ListNode n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* Remove the first node after node n0 in the linked list */ + static void remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode n1 = P.next; + n0.next = n1; + } + + /* Access the node at `index` in the linked list */ + static ListNode access(ListNode head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* Search for the first node with value target in the linked list */ + static int find(ListNode head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* Initialize linked list */ + // Initialize each node + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // Build references between nodes + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + System.out.println("The initialized linked list is"); + PrintUtil.printLinkedList(n0); + + /* Insert node */ + insert(n0, new ListNode(0)); + System.out.println("Linked list after inserting the node is"); + PrintUtil.printLinkedList(n0); + + /* Remove node */ + remove(n0); + System.out.println("Linked list after removing the node is"); + PrintUtil.printLinkedList(n0); + + /* Access node */ + ListNode node = access(n0, 3); + System.out.println("The value of the node at index 3 in the linked list = " + node.val); + + /* Search node */ + int index = find(n0, 2); + System.out.println("The index of the node with value 2 in the linked list = " + index); + } +} diff --git a/en/codes/java/chapter_array_and_linkedlist/list.java b/en/codes/java/chapter_array_and_linkedlist/list.java new file mode 100644 index 0000000000..ad3f7f809c --- /dev/null +++ b/en/codes/java/chapter_array_and_linkedlist/list.java @@ -0,0 +1,66 @@ +/** + * File: list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +public class list { + public static void main(String[] args) { + /* Initialize list */ + // The array's element type is Integer[], a wrapper class for int + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + System.out.println("List nums = " + nums); + + /* Access element */ + int num = nums.get(1); + System.out.println("Access the element at index 1, obtained num = " + num); + + /* Update element */ + nums.set(1, 0); + System.out.println("Update the element at index 1 to 0, resulting in nums = " + nums); + + /* Clear list */ + nums.clear(); + System.out.println("After clearing the list, nums = " + nums); + + /* Add element at the end */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("After adding elements, nums = " + nums); + + /* Insert element in the middle */ + nums.add(3, 6); + System.out.println("Insert the number 6 at index 3, resulting in nums = " + nums); + + /* Remove element */ + nums.remove(3); + System.out.println("Remove the element at index 3, resulting in nums = " + nums); + + /* Traverse the list by index */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + /* Traverse the list elements */ + for (int x : nums) { + count += x; + } + + /* Concatenate two lists */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); + System.out.println("Concatenate list nums1 to nums, resulting in nums = " + nums); + + /* Sort list */ + Collections.sort(nums); + System.out.println("After sorting the list, nums = " + nums); + } +} diff --git a/en/codes/java/chapter_array_and_linkedlist/my_list.java b/en/codes/java/chapter_array_and_linkedlist/my_list.java new file mode 100644 index 0000000000..98783b9415 --- /dev/null +++ b/en/codes/java/chapter_array_and_linkedlist/my_list.java @@ -0,0 +1,147 @@ +/** + * File: my_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +/* List class */ +class MyList { + private int[] arr; // Array (stores list elements) + private int capacity = 10; // List capacity + private int size = 0; // List length (current number of elements) + private int extendRatio = 2; // Multiple for each list expansion + + /* Constructor */ + public MyList() { + arr = new int[capacity]; + } + + /* Get list length (current number of elements) */ + public int size() { + return size; + } + + /* Get list capacity */ + public int capacity() { + return capacity; + } + + /* Access element */ + public int get(int index) { + // If the index is out of bounds, throw an exception, as below + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("Index out of bounds"); + return arr[index]; + } + + /* Update element */ + public void set(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("Index out of bounds"); + arr[index] = num; + } + + /* Add element at the end */ + public void add(int num) { + // When the number of elements exceeds capacity, trigger the expansion mechanism + if (size == capacity()) + extendCapacity(); + arr[size] = num; + // Update the number of elements + size++; + } + + /* Insert element in the middle */ + public void insert(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("Index out of bounds"); + // When the number of elements exceeds capacity, trigger the expansion mechanism + if (size == capacity()) + extendCapacity(); + // Move all elements after `index` one position backward + for (int j = size - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // Update the number of elements + size++; + } + + /* Remove element */ + public int remove(int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("Index out of bounds"); + int num = arr[index]; + // Move all elements after `index` one position forward + for (int j = index; j < size - 1; j++) { + arr[j] = arr[j + 1]; + } + // Update the number of elements + size--; + // Return the removed element + return num; + } + + /* Extend list */ + public void extendCapacity() { + // Create a new array with a length multiple of the original array by extendRatio, and copy the original array to the new array + arr = Arrays.copyOf(arr, capacity() * extendRatio); + // Update list capacity + capacity = arr.length; + } + + /* Convert the list to an array */ + public int[] toArray() { + int size = size(); + // Only convert elements within valid length range + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = get(i); + } + return arr; + } +} + +public class my_list { + /* Driver Code */ + public static void main(String[] args) { + /* Initialize list */ + MyList nums = new MyList(); + /* Add element at the end */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("List nums = " + Arrays.toString(nums.toArray()) + + ", capacity = " + nums.capacity() + ", length = " + nums.size()); + + /* Insert element in the middle */ + nums.insert(3, 6); + System.out.println("Insert the number 6 at index 3, resulting in nums = " + Arrays.toString(nums.toArray())); + + /* Remove element */ + nums.remove(3); + System.out.println("Remove the element at index 3, resulting in nums = " + Arrays.toString(nums.toArray())); + + /* Access element */ + int num = nums.get(1); + System.out.println("Access the element at index 1, obtained num = " + num); + + /* Update element */ + nums.set(1, 0); + System.out.println("Update the element at index 1 to 0, resulting in nums = " + Arrays.toString(nums.toArray())); + + /* Test expansion mechanism */ + for (int i = 0; i < 10; i++) { + // At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism at this time + nums.add(i); + } + System.out.println("After extending, list nums = " + Arrays.toString(nums.toArray()) + + ", capacity = " + nums.capacity() + ", length = " + nums.size()); + } +} diff --git a/en/codes/java/chapter_backtracking/n_queens.java b/en/codes/java/chapter_backtracking/n_queens.java new file mode 100644 index 0000000000..a533a8fedd --- /dev/null +++ b/en/codes/java/chapter_backtracking/n_queens.java @@ -0,0 +1,77 @@ +/** + * File: n_queens.java + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class n_queens { + /* Backtracking algorithm: n queens */ + public static void backtrack(int row, int n, List> state, List>> res, + boolean[] cols, boolean[] diags1, boolean[] diags2) { + // When all rows are placed, record the solution + if (row == n) { + List> copyState = new ArrayList<>(); + for (List sRow : state) { + copyState.add(new ArrayList<>(sRow)); + } + res.add(copyState); + return; + } + // Traverse all columns + for (int col = 0; col < n; col++) { + // Calculate the main and minor diagonals corresponding to the cell + int diag1 = row - col + n - 1; + int diag2 = row + col; + // Pruning: do not allow queens on the column, main diagonal, or minor diagonal of the cell + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Attempt: place the queen in the cell + state.get(row).set(col, "Q"); + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Place the next row + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Retract: restore the cell to an empty spot + state.get(row).set(col, "#"); + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* Solve n queens */ + public static List>> nQueens(int n) { + // Initialize an n*n size chessboard, where 'Q' represents the queen and '#' represents an empty spot + List> state = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List row = new ArrayList<>(); + for (int j = 0; j < n; j++) { + row.add("#"); + } + state.add(row); + } + boolean[] cols = new boolean[n]; // Record columns with queens + boolean[] diags1 = new boolean[2 * n - 1]; // Record main diagonals with queens + boolean[] diags2 = new boolean[2 * n - 1]; // Record minor diagonals with queens + List>> res = new ArrayList<>(); + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + public static void main(String[] args) { + int n = 4; + List>> res = nQueens(n); + + System.out.println("Input the dimensions of the chessboard as " + n); + System.out.println("Total number of queen placement solutions = " + res.size()); + for (List> state : res) { + System.out.println("--------------------"); + for (List row : state) { + System.out.println(row); + } + } + } +} diff --git a/en/codes/java/chapter_backtracking/permutations_i.java b/en/codes/java/chapter_backtracking/permutations_i.java new file mode 100644 index 0000000000..dd9b44a245 --- /dev/null +++ b/en/codes/java/chapter_backtracking/permutations_i.java @@ -0,0 +1,51 @@ +/** + * File: permutations_i.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_i { + /* Backtracking algorithm: Permutation I */ + public static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // When the state length equals the number of elements, record the solution + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // Traverse all choices + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // Pruning: do not allow repeated selection of elements + if (!selected[i]) { + // Attempt: make a choice, update the state + selected[i] = true; + state.add(choice); + // Proceed to the next round of selection + backtrack(state, choices, selected, res); + // Retract: undo the choice, restore to the previous state + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* Permutation I */ + static List> permutationsI(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3 }; + + List> res = permutationsI(nums); + + System.out.println("Input array nums = " + Arrays.toString(nums)); + System.out.println("All permutations res = " + res); + } +} diff --git a/en/codes/java/chapter_backtracking/permutations_ii.java b/en/codes/java/chapter_backtracking/permutations_ii.java new file mode 100644 index 0000000000..b91e0d8fb1 --- /dev/null +++ b/en/codes/java/chapter_backtracking/permutations_ii.java @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_ii { + /* Backtracking algorithm: Permutation II */ + static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // When the state length equals the number of elements, record the solution + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // Traverse all choices + Set duplicated = new HashSet(); + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements + if (!selected[i] && !duplicated.contains(choice)) { + // Attempt: make a choice, update the state + duplicated.add(choice); // Record selected element values + selected[i] = true; + state.add(choice); + // Proceed to the next round of selection + backtrack(state, choices, selected, res); + // Retract: undo the choice, restore to the previous state + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* Permutation II */ + static List> permutationsII(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 2 }; + + List> res = permutationsII(nums); + + System.out.println("Input array nums = " + Arrays.toString(nums)); + System.out.println("All permutations res = " + res); + } +} diff --git a/en/codes/java/chapter_backtracking/preorder_traversal_i_compact.java b/en/codes/java/chapter_backtracking/preorder_traversal_i_compact.java new file mode 100644 index 0000000000..fc77adbea4 --- /dev/null +++ b/en/codes/java/chapter_backtracking/preorder_traversal_i_compact.java @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_i_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_i_compact { + static List res; + + /* Pre-order traversal: Example one */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // Record solution + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nInitialize binary tree"); + PrintUtil.printTree(root); + + // Pre-order traversal + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nOutput all nodes with value 7"); + List vals = new ArrayList<>(); + for (TreeNode node : res) { + vals.add(node.val); + } + System.out.println(vals); + } +} diff --git a/en/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java b/en/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java new file mode 100644 index 0000000000..685c7404d8 --- /dev/null +++ b/en/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_ii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_ii_compact { + static List path; + static List> res; + + /* Pre-order traversal: Example two */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + // Attempt + path.add(root); + if (root.val == 7) { + // Record solution + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // Retract + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nInitialize binary tree"); + PrintUtil.printTree(root); + + // Pre-order traversal + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nOutput all root-to-node 7 paths"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/en/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java b/en/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java new file mode 100644 index 0000000000..b66a84e1fe --- /dev/null +++ b/en/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -0,0 +1,53 @@ +/** + * File: preorder_traversal_iii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_compact { + static List path; + static List> res; + + /* Pre-order traversal: Example three */ + static void preOrder(TreeNode root) { + // Pruning + if (root == null || root.val == 3) { + return; + } + // Attempt + path.add(root); + if (root.val == 7) { + // Record solution + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // Retract + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nInitialize binary tree"); + PrintUtil.printTree(root); + + // Pre-order traversal + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nOutput all root-to-node 7 paths, not including nodes with value 3"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/en/codes/java/chapter_backtracking/preorder_traversal_iii_template.java b/en/codes/java/chapter_backtracking/preorder_traversal_iii_template.java new file mode 100644 index 0000000000..c4dfa71c25 --- /dev/null +++ b/en/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -0,0 +1,77 @@ +/** + * File: preorder_traversal_iii_template.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_template { + /* Determine if the current state is a solution */ + static boolean isSolution(List state) { + return !state.isEmpty() && state.get(state.size() - 1).val == 7; + } + + /* Record solution */ + static void recordSolution(List state, List> res) { + res.add(new ArrayList<>(state)); + } + + /* Determine if the choice is legal under the current state */ + static boolean isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* Update state */ + static void makeChoice(List state, TreeNode choice) { + state.add(choice); + } + + /* Restore state */ + static void undoChoice(List state, TreeNode choice) { + state.remove(state.size() - 1); + } + + /* Backtracking algorithm: Example three */ + static void backtrack(List state, List choices, List> res) { + // Check if it's a solution + if (isSolution(state)) { + // Record solution + recordSolution(state, res); + } + // Traverse all choices + for (TreeNode choice : choices) { + // Pruning: check if the choice is legal + if (isValid(state, choice)) { + // Attempt: make a choice, update the state + makeChoice(state, choice); + // Proceed to the next round of selection + backtrack(state, Arrays.asList(choice.left, choice.right), res); + // Retract: undo the choice, restore to the previous state + undoChoice(state, choice); + } + } + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nInitialize binary tree"); + PrintUtil.printTree(root); + + // Backtracking algorithm + List> res = new ArrayList<>(); + backtrack(new ArrayList<>(), Arrays.asList(root), res); + + System.out.println("\nOutput all root-to-node 7 paths, requiring paths not to include nodes with value 3"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/en/codes/java/chapter_backtracking/subset_sum_i.java b/en/codes/java/chapter_backtracking/subset_sum_i.java new file mode 100644 index 0000000000..ce231bcbf6 --- /dev/null +++ b/en/codes/java/chapter_backtracking/subset_sum_i.java @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i { + /* Backtracking algorithm: Subset Sum I */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // When the subset sum equals target, record the solution + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // Traverse all choices + // Pruning two: start traversing from start to avoid generating duplicate subsets + for (int i = start; i < choices.length; i++) { + // Pruning one: if the subset sum exceeds target, end the loop immediately + // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target + if (target - choices[i] < 0) { + break; + } + // Attempt: make a choice, update target, start + state.add(choices[i]); + // Proceed to the next round of selection + backtrack(state, target - choices[i], choices, i, res); + // Retract: undo the choice, restore to the previous state + state.remove(state.size() - 1); + } + } + + /* Solve Subset Sum I */ + static List> subsetSumI(int[] nums, int target) { + List state = new ArrayList<>(); // State (subset) + Arrays.sort(nums); // Sort nums + int start = 0; // Start point for traversal + List> res = new ArrayList<>(); // Result list (subset list) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumI(nums, target); + + System.out.println("Input array nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("All subsets summing to " + target + " res = " + res); + } +} diff --git a/en/codes/java/chapter_backtracking/subset_sum_i_naive.java b/en/codes/java/chapter_backtracking/subset_sum_i_naive.java new file mode 100644 index 0000000000..2fc44ca98d --- /dev/null +++ b/en/codes/java/chapter_backtracking/subset_sum_i_naive.java @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i_naive.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i_naive { + /* Backtracking algorithm: Subset Sum I */ + static void backtrack(List state, int target, int total, int[] choices, List> res) { + // When the subset sum equals target, record the solution + if (total == target) { + res.add(new ArrayList<>(state)); + return; + } + // Traverse all choices + for (int i = 0; i < choices.length; i++) { + // Pruning: if the subset sum exceeds target, skip that choice + if (total + choices[i] > target) { + continue; + } + // Attempt: make a choice, update elements and total + state.add(choices[i]); + // Proceed to the next round of selection + backtrack(state, target, total + choices[i], choices, res); + // Retract: undo the choice, restore to the previous state + state.remove(state.size() - 1); + } + } + + /* Solve Subset Sum I (including duplicate subsets) */ + static List> subsetSumINaive(int[] nums, int target) { + List state = new ArrayList<>(); // State (subset) + int total = 0; // Subset sum + List> res = new ArrayList<>(); // Result list (subset list) + backtrack(state, target, total, nums, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + System.out.println("Input array nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("All subsets summing to " + target + " res = " + res); + System.out.println("The result of this method includes duplicate sets"); + } +} diff --git a/en/codes/java/chapter_backtracking/subset_sum_ii.java b/en/codes/java/chapter_backtracking/subset_sum_ii.java new file mode 100644 index 0000000000..9a503f672f --- /dev/null +++ b/en/codes/java/chapter_backtracking/subset_sum_ii.java @@ -0,0 +1,60 @@ +/** + * File: subset_sum_ii.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_ii { + /* Backtracking algorithm: Subset Sum II */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // When the subset sum equals target, record the solution + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // Traverse all choices + // Pruning two: start traversing from start to avoid generating duplicate subsets + // Pruning three: start traversing from start to avoid repeatedly selecting the same element + for (int i = start; i < choices.length; i++) { + // Pruning one: if the subset sum exceeds target, end the loop immediately + // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target + if (target - choices[i] < 0) { + break; + } + // Pruning four: if the element equals the left element, it indicates that the search branch is repeated, skip it + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // Attempt: make a choice, update target, start + state.add(choices[i]); + // Proceed to the next round of selection + backtrack(state, target - choices[i], choices, i + 1, res); + // Retract: undo the choice, restore to the previous state + state.remove(state.size() - 1); + } + } + + /* Solve Subset Sum II */ + static List> subsetSumII(int[] nums, int target) { + List state = new ArrayList<>(); // State (subset) + Arrays.sort(nums); // Sort nums + int start = 0; // Start point for traversal + List> res = new ArrayList<>(); // Result list (subset list) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 4, 4, 5 }; + int target = 9; + + List> res = subsetSumII(nums, target); + + System.out.println("Input array nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("All subsets summing to " + target + " res = " + res); + } +} diff --git a/en/codes/java/chapter_computational_complexity/iteration.java b/en/codes/java/chapter_computational_complexity/iteration.java new file mode 100644 index 0000000000..ce42c9439f --- /dev/null +++ b/en/codes/java/chapter_computational_complexity/iteration.java @@ -0,0 +1,76 @@ +/** + * File: iteration.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class iteration { + /* for loop */ + static int forLoop(int n) { + int res = 0; + // Loop sum 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* while loop */ + static int whileLoop(int n) { + int res = 0; + int i = 1; // Initialize condition variable + // Loop sum 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Update condition variable + } + return res; + } + + /* while loop (two updates) */ + static int whileLoopII(int n) { + int res = 0; + int i = 1; // Initialize condition variable + // Loop sum 1, 4, 10, ... + while (i <= n) { + res += i; + // Update condition variable + i++; + i *= 2; + } + return res; + } + + /* Double for loop */ + static String nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // Loop i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // Loop j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.append("(" + i + ", " + j + "), "); + } + } + return res.toString(); + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = forLoop(n); + System.out.println("\nSum result of the for loop res = " + res); + + res = whileLoop(n); + System.out.println("\nSum result of the while loop res = " + res); + + res = whileLoopII(n); + System.out.println("\nSum result of the while loop (with two updates) res = " + res); + + String resStr = nestedForLoop(n); + System.out.println("\nResult of the double for loop traversal = " + resStr); + } +} diff --git a/en/codes/java/chapter_computational_complexity/recursion.java b/en/codes/java/chapter_computational_complexity/recursion.java new file mode 100644 index 0000000000..cb1249abc8 --- /dev/null +++ b/en/codes/java/chapter_computational_complexity/recursion.java @@ -0,0 +1,79 @@ +/** + * File: recursion.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.Stack; + +public class recursion { + /* Recursion */ + static int recur(int n) { + // Termination condition + if (n == 1) + return 1; + // Recursive: recursive call + int res = recur(n - 1); + // Return: return result + return n + res; + } + + /* Simulate recursion with iteration */ + static int forLoopRecur(int n) { + // Use an explicit stack to simulate the system call stack + Stack stack = new Stack<>(); + int res = 0; + // Recursive: recursive call + for (int i = n; i > 0; i--) { + // Simulate "recursive" by "pushing onto the stack" + stack.push(i); + } + // Return: return result + while (!stack.isEmpty()) { + // Simulate "return" by "popping from the stack" + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* Tail recursion */ + static int tailRecur(int n, int res) { + // Termination condition + if (n == 0) + return res; + // Tail recursive call + return tailRecur(n - 1, res + n); + } + + /* Fibonacci sequence: Recursion */ + static int fib(int n) { + // Termination condition f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // Recursive call f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // Return result f(n) + return res; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = recur(n); + System.out.println("\nSum result of the recursive function res = " + res); + + res = forLoopRecur(n); + System.out.println("\nSum result using iteration to simulate recursion res = " + res); + + res = tailRecur(n, 0); + System.out.println("\nSum result of the tail-recursive function res = " + res); + + res = fib(n); + System.out.println("\nThe " + n + "th number in the Fibonacci sequence is " + res); + } +} diff --git a/en/codes/java/chapter_computational_complexity/space_complexity.java b/en/codes/java/chapter_computational_complexity/space_complexity.java new file mode 100644 index 0000000000..f158a206ed --- /dev/null +++ b/en/codes/java/chapter_computational_complexity/space_complexity.java @@ -0,0 +1,110 @@ +/** + * File: space_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import utils.*; +import java.util.*; + +public class space_complexity { + /* Function */ + static int function() { + // Perform some operations + return 0; + } + + /* Constant complexity */ + static void constant(int n) { + // Constants, variables, objects occupy O(1) space + final int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new ListNode(0); + // Variables in a loop occupy O(1) space + for (int i = 0; i < n; i++) { + int c = 0; + } + // Functions in a loop occupy O(1) space + for (int i = 0; i < n; i++) { + function(); + } + } + + /* Linear complexity */ + static void linear(int n) { + // Array of length n occupies O(n) space + int[] nums = new int[n]; + // A list of length n occupies O(n) space + List nodes = new ArrayList<>(); + for (int i = 0; i < n; i++) { + nodes.add(new ListNode(i)); + } + // A hash table of length n occupies O(n) space + Map map = new HashMap<>(); + for (int i = 0; i < n; i++) { + map.put(i, String.valueOf(i)); + } + } + + /* Linear complexity (recursive implementation) */ + static void linearRecur(int n) { + System.out.println("Recursion n = " + n); + if (n == 1) + return; + linearRecur(n - 1); + } + + /* Quadratic complexity */ + static void quadratic(int n) { + // Matrix occupies O(n^2) space + int[][] numMatrix = new int[n][n]; + // A two-dimensional list occupies O(n^2) space + List> numList = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List tmp = new ArrayList<>(); + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } + } + + /* Quadratic complexity (recursive implementation) */ + static int quadraticRecur(int n) { + if (n <= 0) + return 0; + // Array nums length = n, n-1, ..., 2, 1 + int[] nums = new int[n]; + System.out.println("Recursion n = " + n + " in the length of nums = " + nums.length); + return quadraticRecur(n - 1); + } + + /* Exponential complexity (building a full binary tree) */ + static TreeNode buildTree(int n) { + if (n == 0) + return null; + TreeNode root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + // Constant complexity + constant(n); + // Linear complexity + linear(n); + linearRecur(n); + // Quadratic complexity + quadratic(n); + quadraticRecur(n); + // Exponential complexity + TreeNode root = buildTree(n); + PrintUtil.printTree(root); + } +} diff --git a/en/codes/java/chapter_computational_complexity/time_complexity.java b/en/codes/java/chapter_computational_complexity/time_complexity.java new file mode 100644 index 0000000000..4036f7aa01 --- /dev/null +++ b/en/codes/java/chapter_computational_complexity/time_complexity.java @@ -0,0 +1,167 @@ +/** + * File: time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class time_complexity { + /* Constant complexity */ + static int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* Linear complexity */ + static int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* Linear complexity (traversing an array) */ + static int arrayTraversal(int[] nums) { + int count = 0; + // Loop count is proportional to the length of the array + for (int num : nums) { + count++; + } + return count; + } + + /* Quadratic complexity */ + static int quadratic(int n) { + int count = 0; + // Loop count is squared in relation to the data size n + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* Quadratic complexity (bubble sort) */ + static int bubbleSort(int[] nums) { + int count = 0; // Counter + // Outer loop: unsorted range is [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Swap nums[j] and nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Element swap includes 3 individual operations + } + } + } + return count; + } + + /* Exponential complexity (loop implementation) */ + static int exponential(int n) { + int count = 0, base = 1; + // Cells split into two every round, forming the sequence 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* Exponential complexity (recursive implementation) */ + static int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } + + /* Logarithmic complexity (loop implementation) */ + static int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; + } + + /* Logarithmic complexity (recursive implementation) */ + static int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; + } + + /* Linear logarithmic complexity */ + static 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; + } + + /* Factorial complexity (recursive implementation) */ + static int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // From 1 split into n + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; + } + + /* Driver Code */ + public static void main(String[] args) { + // Can modify n to experience the trend of operation count changes under various complexities + int n = 8; + System.out.println("Input data size n = " + n); + + int count = constant(n); + System.out.println("Number of constant complexity operations = " + count); + + count = linear(n); + System.out.println("Number of linear complexity operations = " + count); + count = arrayTraversal(new int[n]); + System.out.println("Number of linear complexity operations (traversing the array) = " + count); + + count = quadratic(n); + System.out.println("Number of quadratic order operations = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + System.out.println("Number of quadratic order operations (bubble sort) = " + count); + + count = exponential(n); + System.out.println("Number of exponential complexity operations (implemented by loop) = " + count); + count = expRecur(n); + System.out.println("Number of exponential complexity operations (implemented by recursion) = " + count); + + count = logarithmic(n); + System.out.println("Number of logarithmic complexity operations (implemented by loop) = " + count); + count = logRecur(n); + System.out.println("Number of logarithmic complexity operations (implemented by recursion) = " + count); + + count = linearLogRecur(n); + System.out.println("Number of linear logarithmic complexity operations (implemented by recursion) = " + count); + + count = factorialRecur(n); + System.out.println("Number of factorial complexity operations (implemented by recursion) = " + count); + } +} diff --git a/en/codes/java/chapter_computational_complexity/worst_best_time_complexity.java b/en/codes/java/chapter_computational_complexity/worst_best_time_complexity.java new file mode 100644 index 0000000000..386abb087e --- /dev/null +++ b/en/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -0,0 +1,50 @@ +/** + * File: worst_best_time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.*; + +public class worst_best_time_complexity { + /* Generate an array with elements {1, 2, ..., n} in a randomly shuffled order */ + static int[] randomNumbers(int n) { + Integer[] nums = new Integer[n]; + // Generate array nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Randomly shuffle array elements + Collections.shuffle(Arrays.asList(nums)); + // Integer[] -> int[] + int[] res = new int[n]; + for (int i = 0; i < n; i++) { + res[i] = nums[i]; + } + return res; + } + + /* Find the index of number 1 in array nums */ + static int findOne(int[] nums) { + for (int i = 0; i < nums.length; i++) { + // When element 1 is at the start of the array, achieve best time complexity O(1) + // When element 1 is at the end of the array, achieve worst time complexity O(n) + if (nums[i] == 1) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = randomNumbers(n); + int index = findOne(nums); + System.out.println("\nThe array [ 1, 2, ..., n ] after being shuffled = " + Arrays.toString(nums)); + System.out.println("The index of number 1 is " + index); + } + } +} diff --git a/en/codes/java/chapter_divide_and_conquer/binary_search_recur.java b/en/codes/java/chapter_divide_and_conquer/binary_search_recur.java new file mode 100644 index 0000000000..d80340100b --- /dev/null +++ b/en/codes/java/chapter_divide_and_conquer/binary_search_recur.java @@ -0,0 +1,45 @@ +/** + * File: binary_search_recur.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +public class binary_search_recur { + /* Binary search: problem f(i, j) */ + static int dfs(int[] nums, int target, int i, int j) { + // If the interval is empty, indicating no target element, return -1 + if (i > j) { + return -1; + } + // Calculate midpoint index m + int m = i + (j - i) / 2; + if (nums[m] < target) { + // Recursive subproblem f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Recursive subproblem f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Found the target element, thus return its index + return m; + } + } + + /* Binary search */ + static int binarySearch(int[] nums, int target) { + int n = nums.length; + // Solve problem f(0, n-1) + return dfs(nums, target, 0, n - 1); + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + // Binary search (double closed interval) + int index = binarySearch(nums, target); + System.out.println("Index of target element 6 =" + index); + } +} diff --git a/en/codes/java/chapter_divide_and_conquer/build_tree.java b/en/codes/java/chapter_divide_and_conquer/build_tree.java new file mode 100644 index 0000000000..678b20377c --- /dev/null +++ b/en/codes/java/chapter_divide_and_conquer/build_tree.java @@ -0,0 +1,51 @@ +/** + * File: build_tree.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import utils.*; +import java.util.*; + +public class build_tree { + /* Build binary tree: Divide and conquer */ + static TreeNode dfs(int[] preorder, Map inorderMap, int i, int l, int r) { + // Terminate when subtree interval is empty + if (r - l < 0) + return null; + // Initialize root node + TreeNode root = new TreeNode(preorder[i]); + // Query m to divide left and right subtrees + int m = inorderMap.get(preorder[i]); + // Subproblem: build left subtree + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // Subproblem: build right subtree + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Return root node + return root; + } + + /* Build binary tree */ + static TreeNode buildTree(int[] preorder, int[] inorder) { + // Initialize hash table, storing in-order elements to indices mapping + Map inorderMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderMap.put(inorder[i], i); + } + TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; + } + + public static void main(String[] args) { + int[] preorder = { 3, 9, 2, 1, 7 }; + int[] inorder = { 9, 3, 1, 2, 7 }; + System.out.println("Pre-order traversal = " + Arrays.toString(preorder)); + System.out.println("In-order traversal = " + Arrays.toString(inorder)); + + TreeNode root = buildTree(preorder, inorder); + System.out.println("The built binary tree is:"); + PrintUtil.printTree(root); + } +} diff --git a/en/codes/java/chapter_divide_and_conquer/hanota.java b/en/codes/java/chapter_divide_and_conquer/hanota.java new file mode 100644 index 0000000000..8e7ec569dc --- /dev/null +++ b/en/codes/java/chapter_divide_and_conquer/hanota.java @@ -0,0 +1,59 @@ +/** + * File: hanota.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import java.util.*; + +public class hanota { + /* Move a disc */ + static void move(List src, List tar) { + // Take out a disc from the top of src + Integer pan = src.remove(src.size() - 1); + // Place the disc on top of tar + tar.add(pan); + } + + /* Solve the Tower of Hanoi problem f(i) */ + static void dfs(int i, List src, List buf, List tar) { + // If only one disc remains on src, move it to tar + if (i == 1) { + move(src, tar); + return; + } + // Subproblem f(i-1): move the top i-1 discs from src with the help of tar to buf + dfs(i - 1, src, tar, buf); + // Subproblem f(1): move the remaining one disc from src to tar + move(src, tar); + // Subproblem f(i-1): move the top i-1 discs from buf with the help of src to tar + dfs(i - 1, buf, src, tar); + } + + /* Solve the Tower of Hanoi problem */ + static void solveHanota(List A, List B, List C) { + int n = A.size(); + // Move the top n discs from A with the help of B to C + dfs(n, A, B, C); + } + + public static void main(String[] args) { + // The tail of the list is the top of the pillar + List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); + List B = new ArrayList<>(); + List C = new ArrayList<>(); + System.out.println("Initial state:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + + solveHanota(A, B, C); + + System.out.println("After the discs are moved:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java b/en/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java new file mode 100644 index 0000000000..5d196607c4 --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.*; + +public class climbing_stairs_backtrack { + /* Backtracking */ + public static void backtrack(List choices, int state, int n, List res) { + // When climbing to the nth step, add 1 to the number of solutions + if (state == n) + res.set(0, res.get(0) + 1); + // Traverse all choices + for (Integer choice : choices) { + // Pruning: do not allow climbing beyond the nth step + if (state + choice > n) + continue; + // Attempt: make a choice, update the state + backtrack(choices, state + choice, n, res); + // Retract + } + } + + /* Climbing stairs: Backtracking */ + public static int climbingStairsBacktrack(int n) { + List choices = Arrays.asList(1, 2); // Can choose to climb up 1 step or 2 steps + int state = 0; // Start climbing from the 0th step + List res = new ArrayList<>(); + res.add(0); // Use res[0] to record the number of solutions + backtrack(choices, state, n, res); + return res.get(0); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsBacktrack(n); + System.out.println(String.format("There are %d solutions to climb %d stairs", n, res)); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java b/en/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java new file mode 100644 index 0000000000..a67cb719f0 --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.java + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* Constrained climbing stairs: Dynamic programming */ + static int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // Initialize dp table, used to store subproblem solutions + int[][] dp = new int[n + 1][3]; + // Initial state: preset the smallest subproblem solution + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // State transition: gradually solve larger subproblems from smaller ones + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsConstraintDP(n); + System.out.println(String.format("There are %d solutions to climb %d stairs", n, res)); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java b/en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java new file mode 100644 index 0000000000..68236b6538 --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java @@ -0,0 +1,31 @@ +/** + * File: climbing_stairs_dfs.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* Search */ + public static int dfs(int i) { + // Known dp[1] and dp[2], return them + 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; + } + + /* Climbing stairs: Search */ + public static int climbingStairsDFS(int n) { + return dfs(n); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFS(n); + System.out.println(String.format("There are %d solutions to climb %d stairs", n, res)); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java b/en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java new file mode 100644 index 0000000000..217964becc --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_dfs_mem.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class climbing_stairs_dfs_mem { + /* Memoized search */ + public static int dfs(int i, int[] mem) { + // Known dp[1] and dp[2], return them + if (i == 1 || i == 2) + return i; + // If there is a record for dp[i], return it + 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); + // Record dp[i] + mem[i] = count; + return count; + } + + /* Climbing stairs: Memoized search */ + public static int climbingStairsDFSMem(int n) { + // mem[i] records the total number of solutions for climbing to the ith step, -1 means no record + int[] mem = new int[n + 1]; + Arrays.fill(mem, -1); + return dfs(n, mem); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFSMem(n); + System.out.println(String.format("There are %d solutions to climb %d stairs", n, res)); + } +} \ No newline at end of file diff --git a/en/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java b/en/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java new file mode 100644 index 0000000000..07aed0017a --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java @@ -0,0 +1,48 @@ +/** + * File: climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* Climbing stairs: Dynamic programming */ + public static int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // Initialize dp table, used to store subproblem solutions + int[] dp = new int[n + 1]; + // Initial state: preset the smallest subproblem solution + dp[1] = 1; + dp[2] = 2; + // State transition: gradually solve larger subproblems from smaller ones + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* Climbing stairs: Space-optimized dynamic programming */ + public static 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; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDP(n); + System.out.println(String.format("There are %d solutions to climb %d stairs", n, res)); + + res = climbingStairsDPComp(n); + System.out.println(String.format("There are %d solutions to climb %d stairs", n, res)); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/coin_change.java b/en/codes/java/chapter_dynamic_programming/coin_change.java new file mode 100644 index 0000000000..ae0af6cc52 --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/coin_change.java @@ -0,0 +1,72 @@ +/** + * File: coin_change.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class coin_change { + /* Coin change: Dynamic programming */ + static int coinChangeDP(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // Initialize dp table + int[][] dp = new int[n + 1][amt + 1]; + // State transition: first row and first column + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // State transition: the rest of the rows and columns + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[i][a] = dp[i - 1][a]; + } else { + // The smaller value between not choosing and choosing coin i + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; + } + + /* Coin change: Space-optimized dynamic programming */ + static int coinChangeDPComp(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // Initialize dp table + int[] dp = new int[amt + 1]; + Arrays.fill(dp, MAX); + dp[0] = 0; + // State transition + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[a] = dp[a]; + } else { + // The smaller value between not choosing and choosing coin i + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 4; + + // Dynamic programming + int res = coinChangeDP(coins, amt); + System.out.println("The minimum number of coins required to make up the target amount is " + res); + + // Space-optimized dynamic programming + res = coinChangeDPComp(coins, amt); + System.out.println("The minimum number of coins required to make up the target amount is " + res); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/coin_change_ii.java b/en/codes/java/chapter_dynamic_programming/coin_change_ii.java new file mode 100644 index 0000000000..01987fb88c --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/coin_change_ii.java @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class coin_change_ii { + /* Coin change II: Dynamic programming */ + static int coinChangeIIDP(int[] coins, int amt) { + int n = coins.length; + // Initialize dp table + int[][] dp = new int[n + 1][amt + 1]; + // Initialize first column + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // State transition + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[i][a] = dp[i - 1][a]; + } else { + // The sum of the two options of not choosing and choosing coin i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; + } + + /* Coin change II: Space-optimized dynamic programming */ + static int coinChangeIIDPComp(int[] coins, int amt) { + int n = coins.length; + // Initialize dp table + int[] dp = new int[amt + 1]; + dp[0] = 1; + // State transition + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // If exceeding the target amount, do not choose coin i + dp[a] = dp[a]; + } else { + // The sum of the two options of not choosing and choosing coin i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 5; + + // Dynamic programming + int res = coinChangeIIDP(coins, amt); + System.out.println("The number of coin combinations to make up the target amount is " + res); + + // Space-optimized dynamic programming + res = coinChangeIIDPComp(coins, amt); + System.out.println("The number of coin combinations to make up the target amount is " + res); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/edit_distance.java b/en/codes/java/chapter_dynamic_programming/edit_distance.java new file mode 100644 index 0000000000..b37c7c13d7 --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/edit_distance.java @@ -0,0 +1,139 @@ +/** + * File: edit_distance.java + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class edit_distance { + /* Edit distance: Brute force search */ + static int editDistanceDFS(String s, String t, int i, int j) { + // If both s and t are empty, return 0 + if (i == 0 && j == 0) + return 0; + // If s is empty, return the length of t + if (i == 0) + return j; + // If t is empty, return the length of s + if (j == 0) + return i; + // If the two characters are equal, skip these two characters + if (s.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // Return the minimum number of edits + return Math.min(Math.min(insert, delete), replace) + 1; + } + + /* Edit distance: Memoized search */ + static int editDistanceDFSMem(String s, String t, int[][] mem, int i, int j) { + // If both s and t are empty, return 0 + if (i == 0 && j == 0) + return 0; + // If s is empty, return the length of t + if (i == 0) + return j; + // If t is empty, return the length of s + if (j == 0) + return i; + // If there is a record, return it + if (mem[i][j] != -1) + return mem[i][j]; + // If the two characters are equal, skip these two characters + if (s.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Record and return the minimum number of edits + mem[i][j] = Math.min(Math.min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* Edit distance: Dynamic programming */ + static int editDistanceDP(String s, String t) { + int n = s.length(), m = t.length(); + int[][] dp = new int[n + 1][m + 1]; + // State transition: first row and first column + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // State transition: the rest of the rows and columns + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // If the two characters are equal, skip these two characters + dp[i][j] = dp[i - 1][j - 1]; + } else { + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; + } + + /* Edit distance: Space-optimized dynamic programming */ + static int editDistanceDPComp(String s, String t) { + int n = s.length(), m = t.length(); + int[] dp = new int[m + 1]; + // State transition: first row + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // State transition: the rest of the rows + for (int i = 1; i <= n; i++) { + // State transition: first column + int leftup = dp[0]; // Temporarily store dp[i-1, j-1] + dp[0] = i; + // State transition: the rest of the columns + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // If the two characters are equal, skip these two characters + dp[j] = leftup; + } else { + // The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Update for the next round of dp[i-1, j-1] + } + } + return dp[m]; + } + + public static void main(String[] args) { + String s = "bag"; + String t = "pack"; + int n = s.length(), m = t.length(); + + // Brute force search + int res = editDistanceDFS(s, t, n, m); + System.out.println("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); + + // Memoized search + int[][] mem = new int[n + 1][m + 1]; + for (int[] row : mem) + Arrays.fill(row, -1); + res = editDistanceDFSMem(s, t, mem, n, m); + System.out.println("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); + + // Dynamic programming + res = editDistanceDP(s, t); + System.out.println("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); + + // Space-optimized dynamic programming + res = editDistanceDPComp(s, t); + System.out.println("Changing " + s + " to " + t + " requires a minimum of " + res + " edits"); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/knapsack.java b/en/codes/java/chapter_dynamic_programming/knapsack.java new file mode 100644 index 0000000000..a8bf6f3d59 --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/knapsack.java @@ -0,0 +1,116 @@ +/** + * File: knapsack.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class knapsack { + + /* 0-1 Knapsack: Brute force search */ + static int knapsackDFS(int[] wgt, int[] val, int i, int c) { + // If all items have been chosen or the knapsack has no remaining capacity, return value 0 + if (i == 0 || c == 0) { + return 0; + } + // If exceeding the knapsack capacity, can only choose not to put it in the knapsack + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Calculate the maximum value of not putting in and putting in item i + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Return the greater value of the two options + return Math.max(no, yes); + } + + /* 0-1 Knapsack: Memoized search */ + static int knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) { + // If all items have been chosen or the knapsack has no remaining capacity, return value 0 + if (i == 0 || c == 0) { + return 0; + } + // If there is a record, return it + if (mem[i][c] != -1) { + return mem[i][c]; + } + // If exceeding the knapsack capacity, can only choose not to put it in the knapsack + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Calculate the maximum value of not putting in and putting in item i + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Record and return the greater value of the two options + mem[i][c] = Math.max(no, yes); + return mem[i][c]; + } + + /* 0-1 Knapsack: Dynamic programming */ + static int knapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Initialize dp table + int[][] dp = new int[n + 1][cap + 1]; + // State transition + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // If exceeding the knapsack capacity, do not choose item i + dp[i][c] = dp[i - 1][c]; + } else { + // The greater value between not choosing and choosing item i + dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 0-1 Knapsack: Space-optimized dynamic programming */ + static int knapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Initialize dp table + int[] dp = new int[cap + 1]; + // State transition + for (int i = 1; i <= n; i++) { + // Traverse in reverse order + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // The greater value between not choosing and choosing item i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + int n = wgt.length; + + // Brute force search + int res = knapsackDFS(wgt, val, n, cap); + System.out.println("The maximum value within the bag capacity is " + res); + + // Memoized search + int[][] mem = new int[n + 1][cap + 1]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = knapsackDFSMem(wgt, val, mem, n, cap); + System.out.println("The maximum value within the bag capacity is " + res); + + // Dynamic programming + res = knapsackDP(wgt, val, cap); + System.out.println("The maximum value within the bag capacity is " + res); + + // Space-optimized dynamic programming + res = knapsackDPComp(wgt, val, cap); + System.out.println("The maximum value within the bag capacity is " + res); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java b/en/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java new file mode 100644 index 0000000000..26de37cb4b --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_cost_climbing_stairs_dp { + /* Climbing stairs with minimum cost: Dynamic programming */ + public static int minCostClimbingStairsDP(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + // Initialize dp table, used to store subproblem solutions + int[] dp = new int[n + 1]; + // Initial state: preset the smallest subproblem solution + dp[1] = cost[1]; + dp[2] = cost[2]; + // State transition: gradually solve larger subproblems from smaller ones + for (int i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* Climbing stairs with minimum cost: Space-optimized dynamic programming */ + public static int minCostClimbingStairsDPComp(int[] cost) { + int n = cost.length - 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 = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + public static void main(String[] args) { + int[] cost = { 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + System.out.println(String.format("Input the cost list for stairs as %s", Arrays.toString(cost))); + + int res = minCostClimbingStairsDP(cost); + System.out.println(String.format("Minimum cost to climb the stairs %d", res)); + + res = minCostClimbingStairsDPComp(cost); + System.out.println(String.format("Minimum cost to climb the stairs %d", res)); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/min_path_sum.java b/en/codes/java/chapter_dynamic_programming/min_path_sum.java new file mode 100644 index 0000000000..6c033ab3dd --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -0,0 +1,125 @@ +/** + * File: min_path_sum.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_path_sum { + /* Minimum path sum: Brute force search */ + static int minPathSumDFS(int[][] grid, int i, int j) { + // If it's the top-left cell, terminate the search + if (i == 0 && j == 0) { + return grid[0][0]; + } + // If the row or column index is out of bounds, return a +∞ cost + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // Calculate the minimum path cost from the top-left to (i-1, j) and (i, j-1) + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // Return the minimum path cost from the top-left to (i, j) + return Math.min(left, up) + grid[i][j]; + } + + /* Minimum path sum: Memoized search */ + static int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // If it's the top-left cell, terminate the search + if (i == 0 && j == 0) { + return grid[0][0]; + } + // If the row or column index is out of bounds, return a +∞ cost + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // If there is a record, return it + if (mem[i][j] != -1) { + return mem[i][j]; + } + // The minimum path cost from the left and top cells + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // Record and return the minimum path cost from the top-left to (i, j) + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* Minimum path sum: Dynamic programming */ + static int minPathSumDP(int[][] grid) { + int n = grid.length, m = grid[0].length; + // Initialize dp table + int[][] dp = new int[n][m]; + dp[0][0] = grid[0][0]; + // State transition: first row + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // State transition: first column + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // State transition: the rest of the rows and columns + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; + } + + /* Minimum path sum: Space-optimized dynamic programming */ + static int minPathSumDPComp(int[][] grid) { + int n = grid.length, m = grid[0].length; + // Initialize dp table + int[] dp = new int[m]; + // State transition: first row + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // State transition: the rest of the rows + for (int i = 1; i < n; i++) { + // State transition: first column + dp[0] = dp[0] + grid[i][0]; + // State transition: the rest of the columns + for (int j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + public static void main(String[] args) { + int[][] grid = { + { 1, 3, 1, 5 }, + { 2, 2, 4, 2 }, + { 5, 3, 2, 1 }, + { 4, 3, 5, 2 } + }; + int n = grid.length, m = grid[0].length; + + // Brute force search + int res = minPathSumDFS(grid, n - 1, m - 1); + System.out.println("The minimum path sum from the top left corner to the bottom right corner is " + res); + + // Memoized search + int[][] mem = new int[n][m]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + System.out.println("The minimum path sum from the top left corner to the bottom right corner is " + res); + + // Dynamic programming + res = minPathSumDP(grid); + System.out.println("The minimum path sum from the top left corner to the bottom right corner is " + res); + + // Space-optimized dynamic programming + res = minPathSumDPComp(grid); + System.out.println("The minimum path sum from the top left corner to the bottom right corner is " + res); + } +} diff --git a/en/codes/java/chapter_dynamic_programming/unbounded_knapsack.java b/en/codes/java/chapter_dynamic_programming/unbounded_knapsack.java new file mode 100644 index 0000000000..9e1f5111a2 --- /dev/null +++ b/en/codes/java/chapter_dynamic_programming/unbounded_knapsack.java @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class unbounded_knapsack { + /* Complete knapsack: Dynamic programming */ + static int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Initialize dp table + int[][] dp = new int[n + 1][cap + 1]; + // State transition + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // If exceeding the knapsack capacity, do not choose item i + dp[i][c] = dp[i - 1][c]; + } else { + // The greater value between not choosing and choosing item i + dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* Complete knapsack: Space-optimized dynamic programming */ + static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Initialize dp table + int[] dp = new int[cap + 1]; + // State transition + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // If exceeding the knapsack capacity, do not choose item i + dp[c] = dp[c]; + } else { + // The greater value between not choosing and choosing item i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 1, 2, 3 }; + int[] val = { 5, 11, 15 }; + int cap = 4; + + // Dynamic programming + int res = unboundedKnapsackDP(wgt, val, cap); + System.out.println("The maximum value within the bag capacity is " + res); + + // Space-optimized dynamic programming + res = unboundedKnapsackDPComp(wgt, val, cap); + System.out.println("The maximum value within the bag capacity is " + res); + } +} diff --git a/en/codes/java/chapter_graph/graph_adjacency_list.java b/en/codes/java/chapter_graph/graph_adjacency_list.java new file mode 100644 index 0000000000..c14b24b003 --- /dev/null +++ b/en/codes/java/chapter_graph/graph_adjacency_list.java @@ -0,0 +1,117 @@ +/** + * File: graph_adjacency_list.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +/* Undirected graph class based on adjacency list */ +class GraphAdjList { + // Adjacency list, key: vertex, value: all adjacent vertices of that vertex + Map> adjList; + + /* Constructor */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // Add all vertices and edges + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* Get the number of vertices */ + public int size() { + return adjList.size(); + } + + /* Add edge */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // Add edge vet1 - vet2 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* Remove edge */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // Remove edge vet1 - vet2 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* Add vertex */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // Add a new linked list to the adjacency list + adjList.put(vet, new ArrayList<>()); + } + + /* Remove vertex */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // Remove the vertex vet's corresponding linked list from the adjacency list + adjList.remove(vet); + // Traverse other vertices' linked lists, removing all edges containing vet + for (List list : adjList.values()) { + list.remove(vet); + } + } + + /* Print the adjacency list */ + public void print() { + System.out.println("Adjacency list ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } +} + +public class graph_adjacency_list { + public static void main(String[] args) { + /* Initialize undirected graph */ + Vertex[] v = Vertex.valsToVets(new int[] { 1, 3, 2, 5, 4 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\nAfter initialization, the graph is"); + graph.print(); + + /* Add edge */ + // Vertices 1, 2 i.e., v[0], v[2] + graph.addEdge(v[0], v[2]); + System.out.println("\nAfter adding edge 1-2, the graph is"); + graph.print(); + + /* Remove edge */ + // Vertices 1, 3 i.e., v[0], v[1] + graph.removeEdge(v[0], v[1]); + System.out.println("\nAfter removing edge 1-3, the graph is"); + graph.print(); + + /* Add vertex */ + Vertex v5 = new Vertex(6); + graph.addVertex(v5); + System.out.println("\nAfter adding vertex 6, the graph is"); + graph.print(); + + /* Remove vertex */ + // Vertex 3 i.e., v[1] + graph.removeVertex(v[1]); + System.out.println("\nAfter removing vertex 3, the graph is"); + graph.print(); + } +} diff --git a/en/codes/java/chapter_graph/graph_adjacency_matrix.java b/en/codes/java/chapter_graph/graph_adjacency_matrix.java new file mode 100644 index 0000000000..30d718a1f9 --- /dev/null +++ b/en/codes/java/chapter_graph/graph_adjacency_matrix.java @@ -0,0 +1,131 @@ +/** + * File: graph_adjacency_matrix.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import utils.*; +import java.util.*; + +/* Undirected graph class based on adjacency matrix */ +class GraphAdjMat { + List vertices; // Vertex list, elements represent "vertex value", index represents "vertex index" + List> adjMat; // Adjacency matrix, row and column indices correspond to "vertex index" + + /* Constructor */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // Add vertex + for (int val : vertices) { + addVertex(val); + } + // Add edge + // Edges elements represent vertex indices + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* Get the number of vertices */ + public int size() { + return vertices.size(); + } + + /* Add vertex */ + public void addVertex(int val) { + int n = size(); + // Add new vertex value to the vertex list + vertices.add(val); + // Add a row to the adjacency matrix + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // Add a column to the adjacency matrix + for (List row : adjMat) { + row.add(0); + } + } + + /* Remove vertex */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // Remove vertex at `index` from the vertex list + vertices.remove(index); + // Remove the row at `index` from the adjacency matrix + adjMat.remove(index); + // Remove the column at `index` from the adjacency matrix + for (List row : adjMat) { + row.remove(index); + } + } + + /* Add edge */ + // Parameters i, j correspond to vertices element indices + public void addEdge(int i, int j) { + // Handle index out of bounds and equality + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., satisfies (i, j) == (j, i) + adjMat.get(i).set(j, 1); + adjMat.get(j).set(i, 1); + } + + /* Remove edge */ + // Parameters i, j correspond to vertices element indices + public void removeEdge(int i, int j) { + // Handle index out of bounds and equality + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + adjMat.get(i).set(j, 0); + adjMat.get(j).set(i, 0); + } + + /* Print adjacency matrix */ + public void print() { + System.out.print("Vertex list = "); + System.out.println(vertices); + System.out.println("Adjacency matrix ="); + PrintUtil.printMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + public static void main(String[] args) { + /* Initialize undirected graph */ + // Edges elements represent vertex indices + int[] vertices = { 1, 3, 2, 5, 4 }; + int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; + GraphAdjMat graph = new GraphAdjMat(vertices, edges); + System.out.println("\nAfter initialization, the graph is"); + graph.print(); + + /* Add edge */ + // Indices of vertices 1, 2 are 0, 2 respectively + graph.addEdge(0, 2); + System.out.println("\nAfter adding edge 1-2, the graph is"); + graph.print(); + + /* Remove edge */ + // Indices of vertices 1, 3 are 0, 1 respectively + graph.removeEdge(0, 1); + System.out.println("\nAfter removing edge 1-3, the graph is"); + graph.print(); + + /* Add vertex */ + graph.addVertex(6); + System.out.println("\nAfter adding vertex 6, the graph is"); + graph.print(); + + /* Remove vertex */ + // Index of vertex 3 is 1 + graph.removeVertex(1); + System.out.println("\nAfter removing vertex 3, the graph is"); + graph.print(); + } +} diff --git a/en/codes/java/chapter_graph/graph_bfs.java b/en/codes/java/chapter_graph/graph_bfs.java new file mode 100644 index 0000000000..ba6519930d --- /dev/null +++ b/en/codes/java/chapter_graph/graph_bfs.java @@ -0,0 +1,55 @@ +/** + * File: graph_bfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_bfs { + /* Breadth-first traversal */ + // Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex + static List graphBFS(GraphAdjList graph, Vertex startVet) { + // Vertex traversal sequence + List res = new ArrayList<>(); + // Hash set, used to record visited vertices + Set visited = new HashSet<>(); + visited.add(startVet); + // Queue used to implement BFS + Queue que = new LinkedList<>(); + que.offer(startVet); + // Starting from vertex vet, loop until all vertices are visited + while (!que.isEmpty()) { + Vertex vet = que.poll(); // Dequeue the vertex at the head of the queue + res.add(vet); // Record visited vertex + // Traverse all adjacent vertices of that vertex + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // Skip already visited vertices + que.offer(adjVet); // Only enqueue unvisited vertices + visited.add(adjVet); // Mark the vertex as visited + } + } + // Return the vertex traversal sequence + return res; + } + + public static void main(String[] args) { + /* Initialize undirected graph */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + Vertex[][] edges = { { 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] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\nAfter initialization, the graph is"); + graph.print(); + + /* Breadth-first traversal */ + List res = graphBFS(graph, v[0]); + System.out.println("\nBreadth-first traversal (BFS) vertex sequence is"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/en/codes/java/chapter_graph/graph_dfs.java b/en/codes/java/chapter_graph/graph_dfs.java new file mode 100644 index 0000000000..7838f566b4 --- /dev/null +++ b/en/codes/java/chapter_graph/graph_dfs.java @@ -0,0 +1,51 @@ +/** + * File: graph_dfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_dfs { + /* Depth-first traversal helper function */ + static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // Record visited vertex + visited.add(vet); // Mark the vertex as visited + // Traverse all adjacent vertices of that vertex + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // Skip already visited vertices + // Recursively visit adjacent vertices + dfs(graph, visited, res, adjVet); + } + } + + /* Depth-first traversal */ + // Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex + static List graphDFS(GraphAdjList graph, Vertex startVet) { + // Vertex traversal sequence + List res = new ArrayList<>(); + // Hash set, used to record visited vertices + Set visited = new HashSet<>(); + dfs(graph, visited, res, startVet); + return res; + } + + public static void main(String[] args) { + /* Initialize undirected graph */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[5] }, { v[4], v[5] }, { v[5], v[6] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\nAfter initialization, the graph is"); + graph.print(); + + /* Depth-first traversal */ + List res = graphDFS(graph, v[0]); + System.out.println("\nDepth-first traversal (DFS) vertex sequence is"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/en/codes/java/chapter_greedy/coin_change_greedy.java b/en/codes/java/chapter_greedy/coin_change_greedy.java new file mode 100644 index 0000000000..b2da9e6372 --- /dev/null +++ b/en/codes/java/chapter_greedy/coin_change_greedy.java @@ -0,0 +1,55 @@ +/** + * File: coin_change_greedy.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; + +public class coin_change_greedy { + /* Coin change: Greedy */ + static int coinChangeGreedy(int[] coins, int amt) { + // Assume coins list is ordered + int i = coins.length - 1; + int count = 0; + // Loop for greedy selection until no remaining amount + while (amt > 0) { + // Find the smallest coin close to and less than the remaining amount + while (i > 0 && coins[i] > amt) { + i--; + } + // Choose coins[i] + amt -= coins[i]; + count++; + } + // If no feasible solution is found, return -1 + return amt == 0 ? count : -1; + } + + public static void main(String[] args) { + // Greedy: can ensure finding a global optimal solution + int[] coins = { 1, 5, 10, 20, 50, 100 }; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("The minimum number of coins required to make up " + amt + " is " + res); + + // Greedy: cannot ensure finding a global optimal solution + coins = new int[] { 1, 20, 50 }; + amt = 60; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("The minimum number of coins required to make up " + amt + " is " + res); + System.out.println("In reality, the minimum number needed is 3, i.e., 20 + 20 + 20"); + + // Greedy: cannot ensure finding a global optimal solution + coins = new int[] { 1, 49, 50 }; + amt = 98; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("The minimum number of coins required to make up " + amt + " is " + res); + System.out.println("In reality, the minimum number needed is 2, i.e., 49 + 49"); + } +} diff --git a/en/codes/java/chapter_greedy/fractional_knapsack.java b/en/codes/java/chapter_greedy/fractional_knapsack.java new file mode 100644 index 0000000000..9bb9303bde --- /dev/null +++ b/en/codes/java/chapter_greedy/fractional_knapsack.java @@ -0,0 +1,59 @@ +/** + * File: fractional_knapsack.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; +import java.util.Comparator; + +/* Item */ +class Item { + int w; // Item weight + int v; // Item value + + public Item(int w, int v) { + this.w = w; + this.v = v; + } +} + +public class fractional_knapsack { + /* Fractional knapsack: Greedy */ + static double fractionalKnapsack(int[] wgt, int[] val, int cap) { + // Create an item list, containing two properties: weight, value + Item[] items = new Item[wgt.length]; + for (int i = 0; i < wgt.length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // Sort by unit value item.v / item.w from high to low + Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); + // Loop for greedy selection + double res = 0; + for (Item item : items) { + if (item.w <= cap) { + // If the remaining capacity is sufficient, put the entire item into the knapsack + res += item.v; + cap -= item.w; + } else { + // If the remaining capacity is insufficient, put part of the item into the knapsack + res += (double) item.v / item.w * cap; + // No remaining capacity left, thus break the loop + break; + } + } + return res; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + + // Greedy algorithm + double res = fractionalKnapsack(wgt, val, cap); + System.out.println("The maximum value within the bag capacity is " + res); + } +} diff --git a/en/codes/java/chapter_greedy/max_capacity.java b/en/codes/java/chapter_greedy/max_capacity.java new file mode 100644 index 0000000000..ce14700298 --- /dev/null +++ b/en/codes/java/chapter_greedy/max_capacity.java @@ -0,0 +1,38 @@ +/** + * File: max_capacity.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +public class max_capacity { + /* Maximum capacity: Greedy */ + static int maxCapacity(int[] ht) { + // Initialize i, j, making them split the array at both ends + int i = 0, j = ht.length - 1; + // Initial maximum capacity is 0 + int res = 0; + // Loop for greedy selection until the two boards meet + while (i < j) { + // Update maximum capacity + int cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // Move the shorter board inward + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + public static void main(String[] args) { + int[] ht = { 3, 8, 5, 2, 7, 7, 3, 4 }; + + // Greedy algorithm + int res = maxCapacity(ht); + System.out.println("The maximum capacity is " + res); + } +} diff --git a/en/codes/java/chapter_greedy/max_product_cutting.java b/en/codes/java/chapter_greedy/max_product_cutting.java new file mode 100644 index 0000000000..006162cece --- /dev/null +++ b/en/codes/java/chapter_greedy/max_product_cutting.java @@ -0,0 +1,40 @@ +/** + * File: max_product_cutting.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.lang.Math; + +public class max_product_cutting { + /* Maximum product of cutting: Greedy */ + public static int maxProductCutting(int n) { + // When n <= 3, must cut out a 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Greedy cut out 3s, a is the number of 3s, b is the remainder + int a = n / 3; + int b = n % 3; + if (b == 1) { + // When the remainder is 1, convert a pair of 1 * 3 into 2 * 2 + return (int) Math.pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // When the remainder is 2, do nothing + return (int) Math.pow(3, a) * 2; + } + // When the remainder is 0, do nothing + return (int) Math.pow(3, a); + } + + public static void main(String[] args) { + int n = 58; + + // Greedy algorithm + int res = maxProductCutting(n); + System.out.println("The maximum product of division is " + res); + } +} diff --git a/en/codes/java/chapter_hashing/array_hash_map.java b/en/codes/java/chapter_hashing/array_hash_map.java new file mode 100644 index 0000000000..014721c3e7 --- /dev/null +++ b/en/codes/java/chapter_hashing/array_hash_map.java @@ -0,0 +1,141 @@ +/** + * File: array_hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; + +/* Key-value pair */ +class Pair { + public int key; + public String val; + + public Pair(int key, String val) { + this.key = key; + this.val = val; + } +} + +/* Hash table based on array implementation */ +class ArrayHashMap { + private List buckets; + + public ArrayHashMap() { + // Initialize an array, containing 100 buckets + buckets = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + buckets.add(null); + } + } + + /* Hash function */ + private int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* Query operation */ + public String get(int key) { + int index = hashFunc(key); + Pair pair = buckets.get(index); + if (pair == null) + return null; + return pair.val; + } + + /* Add operation */ + public void put(int key, String val) { + Pair pair = new Pair(key, val); + int index = hashFunc(key); + buckets.set(index, pair); + } + + /* Remove operation */ + public void remove(int key) { + int index = hashFunc(key); + // Set to null, indicating removal + buckets.set(index, null); + } + + /* Get all key-value pairs */ + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + pairSet.add(pair); + } + return pairSet; + } + + /* Get all keys */ + public List keySet() { + List keySet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + keySet.add(pair.key); + } + return keySet; + } + + /* Get all values */ + public List valueSet() { + List valueSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + valueSet.add(pair.val); + } + return valueSet; + } + + /* Print hash table */ + public void print() { + for (Pair kv : pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + } +} + +public class array_hash_map { + public static void main(String[] args) { + /* Initialize hash table */ + ArrayHashMap map = new ArrayHashMap(); + + /* Add operation */ + // Add key-value pair (key, value) to the hash table + map.put(12836, "Ha"); + map.put(15937, "Luo"); + map.put(16750, "Suan"); + map.put(13276, "Fa"); + map.put(10583, "Ya"); + System.out.println("\nAfter adding, the hash table is\nKey -> Value"); + map.print(); + + /* Query operation */ + // Enter key to the hash table, get value + String name = map.get(15937); + System.out.println("\nEnter student ID 15937, found name " + name); + + /* Remove operation */ + // Remove key-value pair (key, value) from the hash table + map.remove(10583); + System.out.println("\nAfter removing 10583, the hash table is\nKey -> Value"); + map.print(); + + /* Traverse hash table */ + System.out.println("\nTraverse key-value pairs Key->Value"); + for (Pair kv : map.pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + System.out.println("\nIndividually traverse keys Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\nIndividually traverse values Value"); + for (String val : map.valueSet()) { + System.out.println(val); + } + } +} diff --git a/en/codes/java/chapter_hashing/built_in_hash.java b/en/codes/java/chapter_hashing/built_in_hash.java new file mode 100644 index 0000000000..3d935af7ce --- /dev/null +++ b/en/codes/java/chapter_hashing/built_in_hash.java @@ -0,0 +1,38 @@ +/** + * File: built_in_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import utils.*; +import java.util.*; + +public class built_in_hash { + public static void main(String[] args) { + int num = 3; + int hashNum = Integer.hashCode(num); + System.out.println("The hash value of integer " + num + " is " + hashNum); + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + System.out.println("The hash value of boolean " + bol + " is " + hashBol); + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + System.out.println("The hash value of decimal " + dec + " is " + hashDec); + + String str = "Hello algorithm"; + int hashStr = str.hashCode(); + System.out.println("The hash value of string " + str + " is " + hashStr); + + Object[] arr = { 12836, "Ha" }; + int hashTup = Arrays.hashCode(arr); + System.out.println("The hash value of array " + Arrays.toString(arr) + " is " + hashTup); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + System.out.println("The hash value of node object " + obj + " is " + hashObj); + } +} diff --git a/en/codes/java/chapter_hashing/hash_map.java b/en/codes/java/chapter_hashing/hash_map.java new file mode 100644 index 0000000000..483a36150b --- /dev/null +++ b/en/codes/java/chapter_hashing/hash_map.java @@ -0,0 +1,52 @@ +/** + * File: hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; +import utils.*; + +public class hash_map { + public static void main(String[] args) { + /* Initialize hash table */ + Map map = new HashMap<>(); + + /* Add operation */ + // Add key-value pair (key, value) to the hash table + map.put(12836, "Ha"); + map.put(15937, "Luo"); + map.put(16750, "Suan"); + map.put(13276, "Fa"); + map.put(10583, "Ya"); + System.out.println("\nAfter adding, the hash table is\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* Query operation */ + // Enter key to the hash table, get value + String name = map.get(15937); + System.out.println("\nEnter student ID 15937, found name " + name); + + /* Remove operation */ + // Remove key-value pair (key, value) from the hash table + map.remove(10583); + System.out.println("\nAfter removing 10583, the hash table is\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* Traverse hash table */ + System.out.println("\nTraverse key-value pairs Key->Value"); + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + System.out.println("\nIndividually traverse keys Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\nIndividually traverse values Value"); + for (String val : map.values()) { + System.out.println(val); + } + } +} diff --git a/en/codes/java/chapter_hashing/hash_map_chaining.java b/en/codes/java/chapter_hashing/hash_map_chaining.java new file mode 100644 index 0000000000..cc213d8120 --- /dev/null +++ b/en/codes/java/chapter_hashing/hash_map_chaining.java @@ -0,0 +1,148 @@ +/** + * File: hash_map_chaining.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.ArrayList; +import java.util.List; + +/* Chained address hash table */ +class HashMapChaining { + int size; // Number of key-value pairs + int capacity; // Hash table capacity + double loadThres; // Load factor threshold for triggering expansion + int extendRatio; // Expansion multiplier + List> buckets; // Bucket array + + /* Constructor */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } + + /* Hash function */ + int hashFunc(int key) { + return key % capacity; + } + + /* Load factor */ + double loadFactor() { + return (double) size / capacity; + } + + /* Query operation */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // Traverse the bucket, if the key is found, return the corresponding val + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // If key is not found, return null + return null; + } + + /* Add operation */ + void put(int key, String val) { + // When the load factor exceeds the threshold, perform expansion + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // Traverse the bucket, if the specified key is encountered, update the corresponding val and return + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // If the key is not found, add the key-value pair to the end + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } + + /* Remove operation */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // Traverse the bucket, remove the key-value pair from it + for (Pair pair : bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* Extend hash table */ + void extend() { + // Temporarily store the original hash table + List> bucketsTmp = buckets; + // Initialize the extended new hash table + capacity *= extendRatio; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + size = 0; + // Move key-value pairs from the original hash table to the new hash table + for (List bucket : bucketsTmp) { + for (Pair pair : bucket) { + put(pair.key, pair.val); + } + } + } + + /* Print hash table */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } +} + +public class hash_map_chaining { + public static void main(String[] args) { + /* Initialize hash table */ + HashMapChaining map = new HashMapChaining(); + + /* Add operation */ + // Add key-value pair (key, value) to the hash table + map.put(12836, "Ha"); + map.put(15937, "Luo"); + map.put(16750, "Suan"); + map.put(13276, "Fa"); + map.put(10583, "Ya"); + System.out.println("\nAfter adding, the hash table is\nKey -> Value"); + map.print(); + + /* Query operation */ + // Enter key to the hash table, get value + String name = map.get(13276); + System.out.println("\nEnter student ID 13276, found name " + name); + + /* Remove operation */ + // Remove key-value pair (key, value) from the hash table + map.remove(12836); + System.out.println("\nAfter removing 12836, the hash table is\nKey -> Value"); + map.print(); + } +} diff --git a/en/codes/java/chapter_hashing/hash_map_open_addressing.java b/en/codes/java/chapter_hashing/hash_map_open_addressing.java new file mode 100644 index 0000000000..fc0ab4c5d2 --- /dev/null +++ b/en/codes/java/chapter_hashing/hash_map_open_addressing.java @@ -0,0 +1,158 @@ +/** + * File: hash_map_open_addressing.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +/* Open addressing hash table */ +class HashMapOpenAddressing { + private int size; // Number of key-value pairs + private int capacity = 4; // Hash table capacity + private final double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion + private final int extendRatio = 2; // Expansion multiplier + private Pair[] buckets; // Bucket array + private final Pair TOMBSTONE = new Pair(-1, "-1"); // Removal mark + + /* Constructor */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* Hash function */ + private int hashFunc(int key) { + return key % capacity; + } + + /* Load factor */ + private double loadFactor() { + return (double) size / capacity; + } + + /* Search for the bucket index corresponding to key */ + private int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // Linear probing, break when encountering an empty bucket + while (buckets[index] != null) { + // If the key is encountered, return the corresponding bucket index + if (buckets[index].key == key) { + // If a removal mark was encountered earlier, move the key-value pair to that index + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // Return the moved bucket index + } + return index; // Return bucket index + } + // Record the first encountered removal mark + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // Calculate the bucket index, return to the head if exceeding the tail + index = (index + 1) % capacity; + } + // If the key does not exist, return the index of the insertion point + return firstTombstone == -1 ? index : firstTombstone; + } + + /* Query operation */ + public String get(int key) { + // Search for the bucket index corresponding to key + int index = findBucket(key); + // If the key-value pair is found, return the corresponding val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // If the key-value pair does not exist, return null + return null; + } + + /* Add operation */ + public void put(int key, String val) { + // When the load factor exceeds the threshold, perform expansion + if (loadFactor() > loadThres) { + extend(); + } + // Search for the bucket index corresponding to key + int index = findBucket(key); + // If the key-value pair is found, overwrite val and return + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // If the key-value pair does not exist, add the key-value pair + buckets[index] = new Pair(key, val); + size++; + } + + /* Remove operation */ + public void remove(int key) { + // Search for the bucket index corresponding to key + int index = findBucket(key); + // If the key-value pair is found, cover it with a removal mark + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* Extend hash table */ + private void extend() { + // Temporarily store the original hash table + Pair[] bucketsTmp = buckets; + // Initialize the extended new hash table + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // Move key-value pairs from the original hash table to the new hash table + for (Pair pair : bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* Print hash table */ + public void print() { + for (Pair pair : buckets) { + if (pair == null) { + System.out.println("null"); + } else if (pair == TOMBSTONE) { + System.out.println("TOMBSTONE"); + } else { + System.out.println(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + public static void main(String[] args) { + // Initialize hash table + HashMapOpenAddressing hashmap = new HashMapOpenAddressing(); + + // Add operation + // Add key-value pair (key, val) to the hash table + hashmap.put(12836, "Ha"); + hashmap.put(15937, "Luo"); + hashmap.put(16750, "Suan"); + hashmap.put(13276, "Fa"); + hashmap.put(10583, "Ya"); + System.out.println("\nAfter adding, the hash table is\nKey -> Value"); + hashmap.print(); + + // Query operation + // Enter key to the hash table, get value val + String name = hashmap.get(13276); + System.out.println("\nEnter student ID 13276, found name " + name); + + // Remove operation + // Remove key-value pair (key, val) from the hash table + hashmap.remove(16750); + System.out.println("\nAfter removing 16750, the hash table is\nKey -> Value"); + hashmap.print(); + } +} diff --git a/en/codes/java/chapter_hashing/simple_hash.java b/en/codes/java/chapter_hashing/simple_hash.java new file mode 100644 index 0000000000..74f2777afc --- /dev/null +++ b/en/codes/java/chapter_hashing/simple_hash.java @@ -0,0 +1,65 @@ +/** + * File: simple_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +public class simple_hash { + /* Additive hash */ + static int addHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* Multiplicative hash */ + static int mulHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (31 * hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* XOR hash */ + static int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash ^= (int) c; + } + return hash & MODULUS; + } + + /* Rotational hash */ + static int rotHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; + } + return (int) hash; + } + + public static void main(String[] args) { + String key = "Hello algorithm"; + + int hash = addHash(key); + System.out.println("Additive hash value is " + hash); + + hash = mulHash(key); + System.out.println("Multiplicative hash value is " + hash); + + hash = xorHash(key); + System.out.println("XOR hash value is " + hash); + + hash = rotHash(key); + System.out.println("Rotational hash value is " + hash); + } +} diff --git a/en/codes/java/chapter_heap/heap.java b/en/codes/java/chapter_heap/heap.java new file mode 100644 index 0000000000..2ae404bd4d --- /dev/null +++ b/en/codes/java/chapter_heap/heap.java @@ -0,0 +1,66 @@ +/** + * File: heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class heap { + public static void testPush(Queue heap, int val) { + heap.offer(val); // Push the element into heap + System.out.format("\nAfter element %d is added to the heap\n", val); + PrintUtil.printHeap(heap); + } + + public static void testPop(Queue heap) { + int val = heap.poll(); // Pop the element at the heap top + System.out.format("\nAfter the top element %d is removed from the heap\n", val); + PrintUtil.printHeap(heap); + } + + public static void main(String[] args) { + /* Initialize the heap */ + // Initialize min-heap + Queue minHeap = new PriorityQueue<>(); + // Initialize the max-heap (using lambda expression to modify Comparator if necessary) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + System.out.println("\nThe following test case is for max-heap"); + + /* Push the element into heap */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* Access heap top element */ + int peek = maxHeap.peek(); + System.out.format("\nTop element of the heap is %d\n", peek); + + /* Pop the element at the heap top */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* Get heap size */ + int size = maxHeap.size(); + System.out.format("\nNumber of elements in the heap is %d\n", size); + + /* Determine if heap is empty */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\nIs the heap empty %b\n", isEmpty); + + /* Enter list and build heap */ + // Time complexity is O(n), not O(nlogn) + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + System.out.println("\nEnter list and build min-heap"); + PrintUtil.printHeap(minHeap); + } +} diff --git a/en/codes/java/chapter_heap/my_heap.java b/en/codes/java/chapter_heap/my_heap.java new file mode 100644 index 0000000000..de2b21a139 --- /dev/null +++ b/en/codes/java/chapter_heap/my_heap.java @@ -0,0 +1,159 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +/* Max-heap */ +class MaxHeap { + // Use list instead of array to avoid the need for resizing + private List maxHeap; + + /* Constructor, build heap based on input list */ + public MaxHeap(List nums) { + // Add all list elements into the heap + maxHeap = new ArrayList<>(nums); + // Heapify all nodes except leaves + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* Get index of left child node */ + private int left(int i) { + return 2 * i + 1; + } + + /* Get index of right child node */ + private int right(int i) { + return 2 * i + 2; + } + + /* Get index of parent node */ + private int parent(int i) { + return (i - 1) / 2; // Integer division down + } + + /* Swap elements */ + private void swap(int i, int j) { + int tmp = maxHeap.get(i); + maxHeap.set(i, maxHeap.get(j)); + maxHeap.set(j, tmp); + } + + /* Get heap size */ + public int size() { + return maxHeap.size(); + } + + /* Determine if heap is empty */ + public boolean isEmpty() { + return size() == 0; + } + + /* Access heap top element */ + public int peek() { + return maxHeap.get(0); + } + + /* Push the element into heap */ + public void push(int val) { + // Add node + maxHeap.add(val); + // Heapify from bottom to top + siftUp(size() - 1); + } + + /* Start heapifying node i, from bottom to top */ + private void siftUp(int i) { + while (true) { + // Get parent node of node i + int p = parent(i); + // When "crossing the root node" or "node does not need repair", end heapification + if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) + break; + // Swap two nodes + swap(i, p); + // Loop upwards heapification + i = p; + } + } + + /* Element exits heap */ + public int pop() { + // Empty handling + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // Swap the root node with the rightmost leaf node (swap the first element with the last element) + swap(0, size() - 1); + // Remove node + int val = maxHeap.remove(size() - 1); + // Heapify from top to bottom + siftDown(0); + // Return heap top element + return val; + } + + /* Start heapifying node i, from top to bottom */ + private void siftDown(int i) { + while (true) { + // Determine the largest node among i, l, r, noted as ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) + ma = l; + if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) + ma = r; + // If node i is the largest or indices l, r are out of bounds, no further heapification needed, break + if (ma == i) + break; + // Swap two nodes + swap(i, ma); + // Loop downwards heapification + i = ma; + } + } + + /* Print heap (binary tree) */ + public void print() { + Queue queue = new PriorityQueue<>((a, b) -> { return b - a; }); + queue.addAll(maxHeap); + PrintUtil.printHeap(queue); + } +} + +public class my_heap { + public static void main(String[] args) { + /* Initialize max-heap */ + MaxHeap maxHeap = new MaxHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)); + System.out.println("\nEnter list and build heap"); + maxHeap.print(); + + /* Access heap top element */ + int peek = maxHeap.peek(); + System.out.format("\nTop element of the heap is %d\n", peek); + + /* Push the element into heap */ + int val = 7; + maxHeap.push(val); + System.out.format("\nAfter element %d is added to the heap\n", val); + maxHeap.print(); + + /* Pop the element at the heap top */ + peek = maxHeap.pop(); + System.out.format("\nAfter the top element %d is removed from the heap\n", peek); + maxHeap.print(); + + /* Get heap size */ + int size = maxHeap.size(); + System.out.format("\nNumber of elements in the heap is %d\n", size); + + /* Determine if heap is empty */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\nIs the heap empty %b\n", isEmpty); + } +} diff --git a/en/codes/java/chapter_heap/top_k.java b/en/codes/java/chapter_heap/top_k.java new file mode 100644 index 0000000000..afbb5be1e8 --- /dev/null +++ b/en/codes/java/chapter_heap/top_k.java @@ -0,0 +1,40 @@ +/** + * File: top_k.java + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class top_k { + /* Using heap to find the largest k elements in an array */ + static Queue topKHeap(int[] nums, int k) { + // Initialize min-heap + Queue heap = new PriorityQueue(); + // Enter the first k elements of the array into the heap + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // From the k+1th element, keep the heap length as k + for (int i = k; i < nums.length; i++) { + // If the current element is larger than the heap top element, remove the heap top element and enter the current element into the heap + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + + public static void main(String[] args) { + int[] nums = { 1, 7, 6, 3, 2 }; + int k = 3; + + Queue res = topKHeap(nums, k); + System.out.println("The largest " + k + " elements are"); + PrintUtil.printHeap(res); + } +} diff --git a/en/codes/java/chapter_searching/binary_search.java b/en/codes/java/chapter_searching/binary_search.java new file mode 100644 index 0000000000..93c34dd362 --- /dev/null +++ b/en/codes/java/chapter_searching/binary_search.java @@ -0,0 +1,58 @@ +/** + * File: binary_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search { + /* Binary search (double closed interval) */ + static int binarySearch(int[] nums, int target) { + // Initialize double closed interval [0, n-1], i.e., i, j point to the first element and last element of the array respectively + int i = 0, j = nums.length - 1; + // Loop until the search interval is empty (when i > j, it is empty) + while (i <= j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) // This situation indicates that target is in the interval [m+1, j] + i = m + 1; + else if (nums[m] > target) // This situation indicates that target is in the interval [i, m-1] + j = m - 1; + else // Found the target element, thus return its index + return m; + } + // Did not find the target element, thus return -1 + return -1; + } + + /* Binary search (left closed right open interval) */ + static int binarySearchLCRO(int[] nums, int target) { + // Initialize left closed right open interval [0, n), i.e., i, j point to the first element and the last element +1 of the array respectively + int i = 0, j = nums.length; + // Loop until the search interval is empty (when i = j, it is empty) + while (i < j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) // This situation indicates that target is in the interval [m+1, j) + i = m + 1; + else if (nums[m] > target) // This situation indicates that target is in the interval [i, m) + j = m; + else // Found the target element, thus return its index + return m; + } + // Did not find the target element, thus return -1 + return -1; + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + /* Binary search (double closed interval) */ + int index = binarySearch(nums, target); + System.out.println("Index of target element 6 =" + index); + + /* Binary search (left closed right open interval) */ + index = binarySearchLCRO(nums, target); + System.out.println("Index of target element 6 =" + index); + } +} diff --git a/en/codes/java/chapter_searching/binary_search_edge.java b/en/codes/java/chapter_searching/binary_search_edge.java new file mode 100644 index 0000000000..54a668808e --- /dev/null +++ b/en/codes/java/chapter_searching/binary_search_edge.java @@ -0,0 +1,49 @@ +/** + * File: binary_search_edge.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search_edge { + /* Binary search for the leftmost target */ + static int binarySearchLeftEdge(int[] nums, int target) { + // Equivalent to finding the insertion point of target + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // Did not find target, thus return -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // Found target, return index i + return i; + } + + /* Binary search for the rightmost target */ + static int binarySearchRightEdge(int[] nums, int target) { + // Convert to finding the leftmost target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j points to the rightmost target, i points to the first element greater than target + int j = i - 1; + // Did not find target, thus return -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // Found target, return index j + return j; + } + + public static void main(String[] args) { + // Array with duplicate elements + int[] nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\nArray nums = " + java.util.Arrays.toString(nums)); + + // Binary search for left and right boundaries + for (int target : new int[] { 6, 7 }) { + int index = binarySearchLeftEdge(nums, target); + System.out.println("The leftmost index of element " + target + " is " + index); + index = binarySearchRightEdge(nums, target); + System.out.println("The rightmost index of element " + target + " is " + index); + } + } +} diff --git a/en/codes/java/chapter_searching/binary_search_insertion.java b/en/codes/java/chapter_searching/binary_search_insertion.java new file mode 100644 index 0000000000..0ce5ff8330 --- /dev/null +++ b/en/codes/java/chapter_searching/binary_search_insertion.java @@ -0,0 +1,63 @@ +/** + * File: binary_search_insertion.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +class binary_search_insertion { + /* Binary search for insertion point (no duplicate elements) */ + static int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.length - 1; // Initialize double closed interval [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) { + i = m + 1; // Target is in interval [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // Target is in interval [i, m-1] + } else { + return m; // Found target, return insertion point m + } + } + // Did not find target, return insertion point i + return i; + } + + /* Binary search for insertion point (with duplicate elements) */ + static int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.length - 1; // Initialize double closed interval [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Calculate midpoint index m + if (nums[m] < target) { + i = m + 1; // Target is in interval [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // Target is in interval [i, m-1] + } else { + j = m - 1; // First element less than target is in interval [i, m-1] + } + } + // Return insertion point i + return i; + } + + public static void main(String[] args) { + // Array without duplicate elements + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + System.out.println("\nArray nums = " + java.util.Arrays.toString(nums)); + // Binary search for insertion point + for (int target : new int[] { 6, 9 }) { + int index = binarySearchInsertionSimple(nums, target); + System.out.println("The insertion point index for element " + target + " is " + index); + } + + // Array with duplicate elements + nums = new int[] { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\nArray nums = " + java.util.Arrays.toString(nums)); + // Binary search for insertion point + for (int target : new int[] { 2, 6, 20 }) { + int index = binarySearchInsertion(nums, target); + System.out.println("The insertion point index for element " + target + " is " + index); + } + } +} diff --git a/en/codes/java/chapter_searching/hashing_search.java b/en/codes/java/chapter_searching/hashing_search.java new file mode 100644 index 0000000000..568984091d --- /dev/null +++ b/en/codes/java/chapter_searching/hashing_search.java @@ -0,0 +1,51 @@ +/** + * File: hashing_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; +import java.util.*; + +public class hashing_search { + /* Hash search (array) */ + static int hashingSearchArray(Map map, int target) { + // Hash table's key: target element, value: index + // If the hash table does not contain this key, return -1 + return map.getOrDefault(target, -1); + } + + /* Hash search (linked list) */ + static ListNode hashingSearchLinkedList(Map map, int target) { + // Hash table key: target node value, value: node object + // If the key is not in the hash table, return null + return map.getOrDefault(target, null); + } + + public static void main(String[] args) { + int target = 3; + + /* Hash search (array) */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // Initialize hash table + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + map.put(nums[i], i); // key: element, value: index + } + int index = hashingSearchArray(map, target); + System.out.println("The index of target element 3 is " + index); + + /* Hash search (linked list) */ + ListNode head = ListNode.arrToLinkedList(nums); + // Initialize hash table + Map map1 = new HashMap<>(); + while (head != null) { + map1.put(head.val, head); // key: node value, value: node + head = head.next; + } + ListNode node = hashingSearchLinkedList(map1, target); + System.out.println("The corresponding node object for target node value 3 is " + node); + } +} diff --git a/en/codes/java/chapter_searching/linear_search.java b/en/codes/java/chapter_searching/linear_search.java new file mode 100644 index 0000000000..eb7a2f84af --- /dev/null +++ b/en/codes/java/chapter_searching/linear_search.java @@ -0,0 +1,50 @@ +/** + * File: linear_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; + +public class linear_search { + /* Linear search (array) */ + static int linearSearchArray(int[] nums, int target) { + // Traverse array + for (int i = 0; i < nums.length; i++) { + // Found the target element, thus return its index + if (nums[i] == target) + return i; + } + // Did not find the target element, thus return -1 + return -1; + } + + /* Linear search (linked list) */ + static ListNode linearSearchLinkedList(ListNode head, int target) { + // Traverse the list + while (head != null) { + // Found the target node, return it + if (head.val == target) + return head; + head = head.next; + } + // If the target node is not found, return null + return null; + } + + public static void main(String[] args) { + int target = 3; + + /* Perform linear search in array */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + int index = linearSearchArray(nums, target); + System.out.println("The index of target element 3 is " + index); + + /* Perform linear search in linked list */ + ListNode head = ListNode.arrToLinkedList(nums); + ListNode node = linearSearchLinkedList(head, target); + System.out.println("The corresponding node object for target node value 3 is " + node); + } +} diff --git a/en/codes/java/chapter_searching/two_sum.java b/en/codes/java/chapter_searching/two_sum.java new file mode 100644 index 0000000000..22dbd5d831 --- /dev/null +++ b/en/codes/java/chapter_searching/two_sum.java @@ -0,0 +1,53 @@ +/** + * File: two_sum.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import java.util.*; + +public class two_sum { + /* Method one: Brute force enumeration */ + static int[] twoSumBruteForce(int[] nums, int target) { + int size = nums.length; + // Two-layer loop, time complexity is O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } + } + return new int[0]; + } + + /* Method two: Auxiliary hash table */ + static int[] twoSumHashTable(int[] nums, int target) { + int size = nums.length; + // Auxiliary hash table, space complexity is O(n) + Map dic = new HashMap<>(); + // Single-layer loop, time complexity is O(n) + for (int i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return new int[] { dic.get(target - nums[i]), i }; + } + dic.put(nums[i], i); + } + return new int[0]; + } + + public static void main(String[] args) { + // ======= Test Case ======= + int[] nums = { 2, 7, 11, 15 }; + int target = 13; + + // ====== Driver Code ====== + // Method one + int[] res = twoSumBruteForce(nums, target); + System.out.println("Method one res = " + Arrays.toString(res)); + // Method two + res = twoSumHashTable(nums, target); + System.out.println("Method two res = " + Arrays.toString(res)); + } +} diff --git a/en/codes/java/chapter_sorting/bubble_sort.java b/en/codes/java/chapter_sorting/bubble_sort.java new file mode 100644 index 0000000000..3880789aed --- /dev/null +++ b/en/codes/java/chapter_sorting/bubble_sort.java @@ -0,0 +1,57 @@ +/** + * File: bubble_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bubble_sort { + /* Bubble sort */ + static void bubbleSort(int[] nums) { + // Outer loop: unsorted range is [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Swap nums[j] and nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } + } + + /* Bubble sort (optimized with flag) */ + static void bubbleSortWithFlag(int[] nums) { + // Outer loop: unsorted range is [0, i] + for (int i = nums.length - 1; i > 0; i--) { + boolean flag = false; // Initialize flag + // Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Swap nums[j] and nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // Record swapped elements + } + } + if (!flag) + break; // If no elements were swapped in this round of "bubbling", exit + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + bubbleSort(nums); + System.out.println("After bubble sort, nums = " + Arrays.toString(nums)); + + int[] nums1 = { 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(nums1); + System.out.println("After bubble sort, nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/en/codes/java/chapter_sorting/bucket_sort.java b/en/codes/java/chapter_sorting/bucket_sort.java new file mode 100644 index 0000000000..07accafc15 --- /dev/null +++ b/en/codes/java/chapter_sorting/bucket_sort.java @@ -0,0 +1,47 @@ +/** + * File: bucket_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bucket_sort { + /* Bucket sort */ + static void bucketSort(float[] nums) { + // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket + int k = nums.length / 2; + List> buckets = new ArrayList<>(); + for (int i = 0; i < k; i++) { + buckets.add(new ArrayList<>()); + } + // 1. Distribute array elements into various buckets + for (float num : nums) { + // Input data range is [0, 1), use num * k to map to index range [0, k-1] + int i = (int) (num * k); + // Add num to bucket i + buckets.get(i).add(num); + } + // 2. Sort each bucket + for (List bucket : buckets) { + // Use built-in sorting function, can also replace with other sorting algorithms + Collections.sort(bucket); + } + // 3. Traverse buckets to merge results + int i = 0; + for (List bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } + } + + public static void main(String[] args) { + // Assume input data is floating point, range [0, 1) + float[] nums = { 0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f }; + bucketSort(nums); + System.out.println("After bucket sort, nums = " + Arrays.toString(nums)); + } +} diff --git a/en/codes/java/chapter_sorting/counting_sort.java b/en/codes/java/chapter_sorting/counting_sort.java new file mode 100644 index 0000000000..a240791ab0 --- /dev/null +++ b/en/codes/java/chapter_sorting/counting_sort.java @@ -0,0 +1,78 @@ +/** + * File: counting_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class counting_sort { + /* Counting sort */ + // Simple implementation, cannot be used for sorting objects + static void countingSortNaive(int[] nums) { + // 1. Count the maximum element m in the array + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. Count the occurrence of each digit + // counter[num] represents the occurrence of num + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. Traverse counter, filling each element back into the original array nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + + /* Counting sort */ + // Complete implementation, can sort objects and is a stable sort + static void countingSort(int[] nums) { + // 1. Count the maximum element m in the array + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. Count the occurrence of each digit + // counter[num] represents the occurrence of num + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" + // counter[num]-1 is the last index where num appears in res + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Traverse nums in reverse order, placing each element into the result array res + // Initialize the array res to record results + int n = nums.length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // Place num at the corresponding index + counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num + } + // Use result array res to overwrite the original array nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + public static void main(String[] args) { + int[] nums = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSortNaive(nums); + System.out.println("After count sort (unable to sort objects), nums = " + Arrays.toString(nums)); + + int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSort(nums1); + System.out.println("After count sort, nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/en/codes/java/chapter_sorting/heap_sort.java b/en/codes/java/chapter_sorting/heap_sort.java new file mode 100644 index 0000000000..1931ef5622 --- /dev/null +++ b/en/codes/java/chapter_sorting/heap_sort.java @@ -0,0 +1,57 @@ +/** + * File: heap_sort.java + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class heap_sort { + /* Heap length is n, start heapifying node i, from top to bottom */ + public static void siftDown(int[] nums, int n, int i) { + while (true) { + // Determine the largest node among i, l, r, noted as 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; + // If node i is the largest or indices l, r are out of bounds, no further heapification needed, break + if (ma == i) + break; + // Swap two nodes + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // Loop downwards heapification + i = ma; + } + } + + /* Heap sort */ + public static void heapSort(int[] nums) { + // Build heap operation: heapify all nodes except leaves + for (int i = nums.length / 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // Extract the largest element from the heap and repeat for n-1 rounds + for (int i = nums.length - 1; i > 0; i--) { + // Swap the root node with the rightmost leaf node (swap the first element with the last element) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // Start heapifying the root node, from top to bottom + siftDown(nums, i, 0); + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + heapSort(nums); + System.out.println("After heap sort, nums = " + Arrays.toString(nums)); + } +} diff --git a/en/codes/java/chapter_sorting/insertion_sort.java b/en/codes/java/chapter_sorting/insertion_sort.java new file mode 100644 index 0000000000..137552d518 --- /dev/null +++ b/en/codes/java/chapter_sorting/insertion_sort.java @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class insertion_sort { + /* Insertion sort */ + static void insertionSort(int[] nums) { + // Outer loop: sorted range is [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // Inner loop: insert base into the correct position within the sorted range [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // Move nums[j] to the right by one position + j--; + } + nums[j + 1] = base; // Assign base to the correct position + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + insertionSort(nums); + System.out.println("After insertion sort, nums = " + Arrays.toString(nums)); + } +} diff --git a/en/codes/java/chapter_sorting/merge_sort.java b/en/codes/java/chapter_sorting/merge_sort.java new file mode 100644 index 0000000000..3c6f3f2bff --- /dev/null +++ b/en/codes/java/chapter_sorting/merge_sort.java @@ -0,0 +1,58 @@ +/** + * File: merge_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class merge_sort { + /* Merge left subarray and right subarray */ + static void merge(int[] nums, int left, int mid, int right) { + // Left subarray interval is [left, mid], right subarray interval is [mid+1, right] + // Create a temporary array tmp to store the merged results + int[] tmp = new int[right - left + 1]; + // Initialize the start indices of the left and right subarrays + int i = left, j = mid + 1, k = 0; + // While both subarrays still have elements, compare and copy the smaller element into the temporary array + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // Copy the remaining elements of the left and right subarrays into the temporary array + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } + } + + /* Merge sort */ + static void mergeSort(int[] nums, int left, int right) { + // Termination condition + if (left >= right) + return; // Terminate recursion when subarray length is 1 + // Partition stage + int mid = left + (right - left) / 2; // Calculate midpoint + mergeSort(nums, left, mid); // Recursively process the left subarray + mergeSort(nums, mid + 1, right); // Recursively process the right subarray + // Merge stage + merge(nums, left, mid, right); + } + + public static void main(String[] args) { + /* Merge sort */ + int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; + mergeSort(nums, 0, nums.length - 1); + System.out.println("After merge sort, nums = " + Arrays.toString(nums)); + } +} diff --git a/en/codes/java/chapter_sorting/quick_sort.java b/en/codes/java/chapter_sorting/quick_sort.java new file mode 100644 index 0000000000..aa037eeee9 --- /dev/null +++ b/en/codes/java/chapter_sorting/quick_sort.java @@ -0,0 +1,158 @@ +/** + * File: quick_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +/* Quick sort class */ +class QuickSort { + /* Swap elements */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Partition */ + static int partition(int[] nums, int left, int right) { + // Use nums[left] as the pivot + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Search from right to left for the first element smaller than the pivot + while (i < j && nums[i] <= nums[left]) + i++; // Search from left to right for the first element greater than the pivot + swap(nums, i, j); // Swap these two elements + } + swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays + return i; // Return the index of the pivot + } + + /* Quick sort */ + public static void quickSort(int[] nums, int left, int right) { + // Terminate recursion when subarray length is 1 + if (left >= right) + return; + // Partition + int pivot = partition(nums, left, right); + // Recursively process the left subarray and right subarray + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* Quick sort class (median pivot optimization) */ +class QuickSortMedian { + /* Swap elements */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Select the median of three candidate elements */ + static 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 is between l and r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l is between m and r + return right; + } + + /* Partition (median of three) */ + static int partition(int[] nums, int left, int right) { + // Select the median of three candidate elements + int med = medianThree(nums, left, (left + right) / 2, right); + // Swap the median to the array's leftmost position + swap(nums, left, med); + // Use nums[left] as the pivot + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Search from right to left for the first element smaller than the pivot + while (i < j && nums[i] <= nums[left]) + i++; // Search from left to right for the first element greater than the pivot + swap(nums, i, j); // Swap these two elements + } + swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays + return i; // Return the index of the pivot + } + + /* Quick sort */ + public static void quickSort(int[] nums, int left, int right) { + // Terminate recursion when subarray length is 1 + if (left >= right) + return; + // Partition + int pivot = partition(nums, left, right); + // Recursively process the left subarray and right subarray + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* Quick sort class (tail recursion optimization) */ +class QuickSortTailCall { + /* Swap elements */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Partition */ + static int partition(int[] nums, int left, int right) { + // Use nums[left] as the pivot + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Search from right to left for the first element smaller than the pivot + while (i < j && nums[i] <= nums[left]) + i++; // Search from left to right for the first element greater than the pivot + swap(nums, i, j); // Swap these two elements + } + swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays + return i; // Return the index of the pivot + } + + /* Quick sort (tail recursion optimization) */ + public static void quickSort(int[] nums, int left, int right) { + // Terminate when subarray length is 1 + while (left < right) { + // Partition operation + int pivot = partition(nums, left, right); + // Perform quick sort on the shorter of the two subarrays + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // Recursively sort the left subarray + left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // Recursively sort the right subarray + right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1] + } + } + } +} + +public class quick_sort { + public static void main(String[] args) { + /* Quick sort */ + int[] nums = { 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(nums, 0, nums.length - 1); + System.out.println("After quick sort, nums = " + Arrays.toString(nums)); + + /* Quick sort (median pivot optimization) */ + int[] nums1 = { 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + System.out.println("After quick sort with median pivot optimization, nums1 = " + Arrays.toString(nums1)); + + /* Quick sort (tail recursion optimization) */ + int[] nums2 = { 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + System.out.println("After quick sort with tail recursion optimization, nums2 = " + Arrays.toString(nums2)); + } +} diff --git a/en/codes/java/chapter_sorting/radix_sort.java b/en/codes/java/chapter_sorting/radix_sort.java new file mode 100644 index 0000000000..28c295f76e --- /dev/null +++ b/en/codes/java/chapter_sorting/radix_sort.java @@ -0,0 +1,69 @@ +/** + * File: radix_sort.java + * Created Time: 2023-01-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class radix_sort { + /* Get the k-th digit of element num, where exp = 10^(k-1) */ + static int digit(int num, int exp) { + // Passing exp instead of k can avoid repeated expensive exponentiation here + return (num / exp) % 10; + } + + /* Counting sort (based on nums k-th digit) */ + static void countingSortDigit(int[] nums, int exp) { + // Decimal digit range is 0~9, therefore need a bucket array of length 10 + int[] counter = new int[10]; + int n = nums.length; + // Count the occurrence of digits 0~9 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d + counter[d]++; // Count the occurrence of digit d + } + // Calculate prefix sum, converting "occurrence count" into "array index" + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Traverse in reverse, based on bucket statistics, place each element into res + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // Get the index j for d in the array + res[j] = nums[i]; // Place the current element at index j + counter[d]--; // Decrease the count of d by 1 + } + // Use result to overwrite the original array nums + for (int i = 0; i < n; i++) + nums[i] = res[i]; + } + + /* Radix sort */ + static void radixSort(int[] nums) { + // Get the maximum element of the array, used to determine the maximum number of digits + int m = Integer.MIN_VALUE; + for (int num : nums) + if (num > m) + m = num; + // Traverse from the lowest to the highest digit + for (int exp = 1; exp <= m; exp *= 10) { + // Perform counting sort on the k-th digit of array elements + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // i.e., exp = 10^(k-1) + countingSortDigit(nums, exp); + } + } + + public static void main(String[] args) { + // Radix sort + int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 }; + radixSort(nums); + System.out.println("After radix sort, nums = " + Arrays.toString(nums)); + } +} diff --git a/en/codes/java/chapter_sorting/selection_sort.java b/en/codes/java/chapter_sorting/selection_sort.java new file mode 100644 index 0000000000..6389cd22c2 --- /dev/null +++ b/en/codes/java/chapter_sorting/selection_sort.java @@ -0,0 +1,35 @@ +/** + * File: selection_sort.java + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class selection_sort { + /* Selection sort */ + public static void selectionSort(int[] nums) { + int n = nums.length; + // Outer loop: unsorted range is [i, n-1] + for (int i = 0; i < n - 1; i++) { + // Inner loop: find the smallest element within the unsorted range + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // Record the index of the smallest element + } + // Swap the smallest element with the first element of the unsorted range + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + selectionSort(nums); + System.out.println("After selection sort, nums = " + Arrays.toString(nums)); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/array_deque.java b/en/codes/java/chapter_stack_and_queue/array_deque.java new file mode 100644 index 0000000000..13cf8ac85e --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/array_deque.java @@ -0,0 +1,151 @@ +/** + * File: array_deque.java + * Created Time: 2023-02-16 + * Author: krahets (krahets@163.com), FangYuan33 (374072213@qq.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Double-ended queue class based on circular array */ +class ArrayDeque { + private int[] nums; // Array used to store elements of the double-ended queue + private int front; // Front pointer, pointing to the front element + private int queSize; // Length of the double-ended queue + + /* Constructor */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* Get the capacity of the double-ended queue */ + public int capacity() { + return nums.length; + } + + /* Get the length of the double-ended queue */ + public int size() { + return queSize; + } + + /* Determine if the double-ended queue is empty */ + public boolean isEmpty() { + return queSize == 0; + } + + /* Calculate circular array index */ + private int index(int i) { + // Implement circular array by modulo operation + // When i exceeds the tail of the array, return to the head + // When i exceeds the head of the array, return to the tail + return (i + capacity()) % capacity(); + } + + /* Front enqueue */ + public void pushFirst(int num) { + if (queSize == capacity()) { + System.out.println("Double-ended queue is full"); + return; + } + // Move the front pointer one position to the left + // Implement front crossing the head of the array to return to the tail by modulo operation + front = index(front - 1); + // Add num to the front + nums[front] = num; + queSize++; + } + + /* Rear enqueue */ + public void pushLast(int num) { + if (queSize == capacity()) { + System.out.println("Double-ended queue is full"); + return; + } + // Calculate rear pointer, pointing to rear index + 1 + int rear = index(front + queSize); + // Add num to the rear + nums[rear] = num; + queSize++; + } + + /* Front dequeue */ + public int popFirst() { + int num = peekFirst(); + // Move front pointer one position backward + front = index(front + 1); + queSize--; + return num; + } + + /* Rear dequeue */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* Access front element */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* Access rear element */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // Calculate rear element index + int last = index(front + queSize - 1); + return nums[last]; + } + + /* Return array for printing */ + public int[] toArray() { + // Only convert elements within valid length range + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +} + +public class array_deque { + public static void main(String[] args) { + /* Initialize double-ended queue */ + ArrayDeque deque = new ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("Double-ended queue deque = " + Arrays.toString(deque.toArray())); + + /* Access element */ + int peekFirst = deque.peekFirst(); + System.out.println("Front element peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("Back element peekLast = " + peekLast); + + /* Element enqueue */ + deque.pushLast(4); + System.out.println("Element 4 enqueued at the tail, deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("Element 1 enqueued at the head, deque = " + Arrays.toString(deque.toArray())); + + /* Element dequeue */ + int popLast = deque.popLast(); + System.out.println("Deque tail element = " + popLast + ", after dequeuing from the tail" + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("Deque front element = " + popFirst + ", after dequeuing from the front" + Arrays.toString(deque.toArray())); + + /* Get the length of the double-ended queue */ + int size = deque.size(); + System.out.println("Length of the double-ended queue size = " + size); + + /* Determine if the double-ended queue is empty */ + boolean isEmpty = deque.isEmpty(); + System.out.println("Is the double-ended queue empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/array_queue.java b/en/codes/java/chapter_stack_and_queue/array_queue.java new file mode 100644 index 0000000000..aaf0ba370c --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/array_queue.java @@ -0,0 +1,115 @@ +/** + * File: array_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Queue class based on circular array */ +class ArrayQueue { + private int[] nums; // Array for storing queue elements + private int front; // Front pointer, pointing to the front element + private int queSize; // Queue length + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* Get the capacity of the queue */ + public int capacity() { + return nums.length; + } + + /* Get the length of the queue */ + public int size() { + return queSize; + } + + /* Determine if the queue is empty */ + public boolean isEmpty() { + return queSize == 0; + } + + /* Enqueue */ + public void push(int num) { + if (queSize == capacity()) { + System.out.println("Queue is full"); + return; + } + // Calculate rear pointer, pointing to rear index + 1 + // Use modulo operation to wrap the rear pointer from the end of the array back to the start + int rear = (front + queSize) % capacity(); + // Add num to the rear + nums[rear] = num; + queSize++; + } + + /* Dequeue */ + public int pop() { + int num = peek(); + // Move front pointer one position backward, returning to the head of the array if it exceeds the tail + front = (front + 1) % capacity(); + queSize--; + return num; + } + + /* Access front element */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* Return array */ + public int[] toArray() { + // Only convert elements within valid length range + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % capacity()]; + } + return res; + } +} + +public class array_queue { + public static void main(String[] args) { + /* Initialize queue */ + int capacity = 10; + ArrayQueue queue = new ArrayQueue(capacity); + + /* Element enqueue */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("Queue queue = " + Arrays.toString(queue.toArray())); + + /* Access front element */ + int peek = queue.peek(); + System.out.println("Front element peek = " + peek); + + /* Element dequeue */ + int pop = queue.pop(); + System.out.println("Dequeued element = " + pop + ", after dequeuing" + Arrays.toString(queue.toArray())); + + /* Get the length of the queue */ + int size = queue.size(); + System.out.println("Length of the queue size = " + size); + + /* Determine if the queue is empty */ + boolean isEmpty = queue.isEmpty(); + System.out.println("Is the queue empty = " + isEmpty); + + /* Test circular array */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + System.out.println("After the " + i + "th round of enqueueing + dequeuing, queue = " + Arrays.toString(queue.toArray())); + } + } +} diff --git a/en/codes/java/chapter_stack_and_queue/array_stack.java b/en/codes/java/chapter_stack_and_queue/array_stack.java new file mode 100644 index 0000000000..757ebf3b2b --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/array_stack.java @@ -0,0 +1,84 @@ +/** + * File: array_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Stack class based on array */ +class ArrayStack { + private ArrayList stack; + + public ArrayStack() { + // Initialize the list (dynamic array) + stack = new ArrayList<>(); + } + + /* Get the length of the stack */ + public int size() { + return stack.size(); + } + + /* Determine if the stack is empty */ + public boolean isEmpty() { + return size() == 0; + } + + /* Push */ + public void push(int num) { + stack.add(num); + } + + /* Pop */ + public int pop() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.remove(size() - 1); + } + + /* Access stack top element */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.get(size() - 1); + } + + /* Convert the List to Array and return */ + public Object[] toArray() { + return stack.toArray(); + } +} + +public class array_stack { + public static void main(String[] args) { + /* Initialize stack */ + ArrayStack stack = new ArrayStack(); + + /* Element push */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("Stack stack = " + Arrays.toString(stack.toArray())); + + /* Access stack top element */ + int peek = stack.peek(); + System.out.println("Top element peek = " + peek); + + /* Element pop */ + int pop = stack.pop(); + System.out.println("Popped element = " + pop + ", after popping" + Arrays.toString(stack.toArray())); + + /* Get the length of the stack */ + int size = stack.size(); + System.out.println("Length of the stack size = " + size); + + /* Determine if it's empty */ + boolean isEmpty = stack.isEmpty(); + System.out.println("Is the stack empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/deque.java b/en/codes/java/chapter_stack_and_queue/deque.java new file mode 100644 index 0000000000..838f7b3fd9 --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/deque.java @@ -0,0 +1,46 @@ +/** + * File: deque.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class deque { + public static void main(String[] args) { + /* Initialize double-ended queue */ + Deque deque = new LinkedList<>(); + deque.offerLast(3); + deque.offerLast(2); + deque.offerLast(5); + System.out.println("Double-ended queue deque = " + deque); + + /* Access element */ + int peekFirst = deque.peekFirst(); + System.out.println("Front element peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("Back element peekLast = " + peekLast); + + /* Element enqueue */ + deque.offerLast(4); + System.out.println("Element 4 enqueued at the tail, deque = " + deque); + deque.offerFirst(1); + System.out.println("Element 1 enqueued at the head, deque = " + deque); + + /* Element dequeue */ + int popLast = deque.pollLast(); + System.out.println("Deque tail element = " + popLast + ", after dequeuing from the tail" + deque); + int popFirst = deque.pollFirst(); + System.out.println("Deque front element = " + popFirst + ", after dequeuing from the front" + deque); + + /* Get the length of the double-ended queue */ + int size = deque.size(); + System.out.println("Length of the double-ended queue size = " + size); + + /* Determine if the double-ended queue is empty */ + boolean isEmpty = deque.isEmpty(); + System.out.println("Is the double-ended queue empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/linkedlist_deque.java b/en/codes/java/chapter_stack_and_queue/linkedlist_deque.java new file mode 100644 index 0000000000..6340c9009d --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -0,0 +1,175 @@ +/** + * File: linkedlist_deque.java + * Created Time: 2023-01-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Double-linked list node */ +class ListNode { + int val; // Node value + ListNode next; // Reference to successor node + ListNode prev; // Reference to predecessor node + + ListNode(int val) { + this.val = val; + prev = next = null; + } +} + +/* Double-ended queue class based on double-linked list */ +class LinkedListDeque { + private ListNode front, rear; // Front node front, back node rear + private int queSize = 0; // Length of the double-ended queue + + public LinkedListDeque() { + front = rear = null; + } + + /* Get the length of the double-ended queue */ + public int size() { + return queSize; + } + + /* Determine if the double-ended queue is empty */ + public boolean isEmpty() { + return size() == 0; + } + + /* Enqueue operation */ + private void push(int num, boolean isFront) { + ListNode node = new ListNode(num); + // If the list is empty, make front and rear both point to node + if (isEmpty()) + front = rear = node; + // Front enqueue operation + else if (isFront) { + // Add node to the head of the list + front.prev = node; + node.next = front; + front = node; // Update head node + // Rear enqueue operation + } else { + // Add node to the tail of the list + rear.next = node; + node.prev = rear; + rear = node; // Update tail node + } + queSize++; // Update queue length + } + + /* Front enqueue */ + public void pushFirst(int num) { + push(num, true); + } + + /* Rear enqueue */ + public void pushLast(int num) { + push(num, false); + } + + /* Dequeue operation */ + private int pop(boolean isFront) { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + int val; + // Front dequeue operation + if (isFront) { + val = front.val; // Temporarily store the head node value + // Remove head node + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + front = fNext; // Update head node + // Rear dequeue operation + } else { + val = rear.val; // Temporarily store the tail node value + // Remove tail node + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + rear = rPrev; // Update tail node + } + queSize--; // Update queue length + return val; + } + + /* Front dequeue */ + public int popFirst() { + return pop(true); + } + + /* Rear dequeue */ + public int popLast() { + return pop(false); + } + + /* Access front element */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* Access rear element */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return rear.val; + } + + /* Return array for printing */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_deque { + public static void main(String[] args) { + /* Initialize double-ended queue */ + LinkedListDeque deque = new LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("Double-ended queue deque = " + Arrays.toString(deque.toArray())); + + /* Access element */ + int peekFirst = deque.peekFirst(); + System.out.println("Front element peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("Back element peekLast = " + peekLast); + + /* Element enqueue */ + deque.pushLast(4); + System.out.println("Element 4 enqueued at the tail, deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("Element 1 enqueued at the head, deque = " + Arrays.toString(deque.toArray())); + + /* Element dequeue */ + int popLast = deque.popLast(); + System.out.println("Deque tail element = " + popLast + ", after dequeuing from the tail" + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("Deque front element = " + popFirst + ", after dequeuing from the front" + Arrays.toString(deque.toArray())); + + /* Get the length of the double-ended queue */ + int size = deque.size(); + System.out.println("Length of the double-ended queue size = " + size); + + /* Determine if the double-ended queue is empty */ + boolean isEmpty = deque.isEmpty(); + System.out.println("Is the double-ended queue empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/en/codes/java/chapter_stack_and_queue/linkedlist_queue.java new file mode 100644 index 0000000000..d60b27f4dc --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -0,0 +1,104 @@ +/** + * File: linkedlist_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Queue class based on linked list */ +class LinkedListQueue { + private ListNode front, rear; // Front node front, back node rear + private int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* Get the length of the queue */ + public int size() { + return queSize; + } + + /* Determine if the queue is empty */ + public boolean isEmpty() { + return size() == 0; + } + + /* Enqueue */ + public void push(int num) { + // Add num behind the tail node + ListNode node = new ListNode(num); + // If the queue is empty, make the head and tail nodes both point to that node + if (front == null) { + front = node; + rear = node; + // If the queue is not empty, add that node behind the tail node + } else { + rear.next = node; + rear = node; + } + queSize++; + } + + /* Dequeue */ + public int pop() { + int num = peek(); + // Remove head node + front = front.next; + queSize--; + return num; + } + + /* Access front element */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* Convert the linked list to Array and return */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + public static void main(String[] args) { + /* Initialize queue */ + LinkedListQueue queue = new LinkedListQueue(); + + /* Element enqueue */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("Queue queue = " + Arrays.toString(queue.toArray())); + + /* Access front element */ + int peek = queue.peek(); + System.out.println("Front element peek = " + peek); + + /* Element dequeue */ + int pop = queue.pop(); + System.out.println("Dequeued element = " + pop + ", after dequeuing" + Arrays.toString(queue.toArray())); + + /* Get the length of the queue */ + int size = queue.size(); + System.out.println("Length of the queue size = " + size); + + /* Determine if the queue is empty */ + boolean isEmpty = queue.isEmpty(); + System.out.println("Is the queue empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/en/codes/java/chapter_stack_and_queue/linkedlist_stack.java new file mode 100644 index 0000000000..c4438def8e --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -0,0 +1,95 @@ +/** + * File: linkedlist_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; +import utils.*; + +/* Stack class based on linked list */ +class LinkedListStack { + private ListNode stackPeek; // Use the head node as the top of the stack + private int stkSize = 0; // Length of the stack + + public LinkedListStack() { + stackPeek = null; + } + + /* Get the length of the stack */ + public int size() { + return stkSize; + } + + /* Determine if the stack is empty */ + public boolean isEmpty() { + return size() == 0; + } + + /* Push */ + public void push(int num) { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* Pop */ + public int pop() { + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* Access stack top element */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stackPeek.val; + } + + /* Convert the List to Array and return */ + public int[] toArray() { + ListNode node = stackPeek; + int[] res = new int[size()]; + for (int i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + public static void main(String[] args) { + /* Initialize stack */ + LinkedListStack stack = new LinkedListStack(); + + /* Element push */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("Stack stack = " + Arrays.toString(stack.toArray())); + + /* Access stack top element */ + int peek = stack.peek(); + System.out.println("Top element peek = " + peek); + + /* Element pop */ + int pop = stack.pop(); + System.out.println("Popped element = " + pop + ", after popping" + Arrays.toString(stack.toArray())); + + /* Get the length of the stack */ + int size = stack.size(); + System.out.println("Length of the stack size = " + size); + + /* Determine if it's empty */ + boolean isEmpty = stack.isEmpty(); + System.out.println("Is the stack empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/queue.java b/en/codes/java/chapter_stack_and_queue/queue.java new file mode 100644 index 0000000000..7b37e41c38 --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/queue.java @@ -0,0 +1,40 @@ +/** + * File: queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class queue { + public static void main(String[] args) { + /* Initialize queue */ + Queue queue = new LinkedList<>(); + + /* Element enqueue */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + System.out.println("Queue queue = " + queue); + + /* Access front element */ + int peek = queue.peek(); + System.out.println("Front element peek = " + peek); + + /* Element dequeue */ + int pop = queue.poll(); + System.out.println("Dequeued element = " + pop + ", after dequeuing" + queue); + + /* Get the length of the queue */ + int size = queue.size(); + System.out.println("Length of the queue size = " + size); + + /* Determine if the queue is empty */ + boolean isEmpty = queue.isEmpty(); + System.out.println("Is the queue empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_stack_and_queue/stack.java b/en/codes/java/chapter_stack_and_queue/stack.java new file mode 100644 index 0000000000..5270c018f1 --- /dev/null +++ b/en/codes/java/chapter_stack_and_queue/stack.java @@ -0,0 +1,40 @@ +/** + * File: stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class stack { + public static void main(String[] args) { + /* Initialize stack */ + Stack stack = new Stack<>(); + + /* Element push */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("Stack stack = " + stack); + + /* Access stack top element */ + int peek = stack.peek(); + System.out.println("Top element peek = " + peek); + + /* Element pop */ + int pop = stack.pop(); + System.out.println("Popped element = " + pop + ", after popping" + stack); + + /* Get the length of the stack */ + int size = stack.size(); + System.out.println("Length of the stack size = " + size); + + /* Determine if it's empty */ + boolean isEmpty = stack.isEmpty(); + System.out.println("Is the stack empty = " + isEmpty); + } +} diff --git a/en/codes/java/chapter_tree/array_binary_tree.java b/en/codes/java/chapter_tree/array_binary_tree.java new file mode 100644 index 0000000000..9adb135976 --- /dev/null +++ b/en/codes/java/chapter_tree/array_binary_tree.java @@ -0,0 +1,136 @@ +/** + * File: array_binary_tree.java + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +/* Array-based binary tree class */ +class ArrayBinaryTree { + private List tree; + + /* Constructor */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } + + /* List capacity */ + public int size() { + return tree.size(); + } + + /* Get the value of the node at index i */ + public Integer val(int i) { + // If the index is out of bounds, return null, representing an empty spot + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } + + /* Get the index of the left child of the node at index i */ + public Integer left(int i) { + return 2 * i + 1; + } + + /* Get the index of the right child of the node at index i */ + public Integer right(int i) { + return 2 * i + 2; + } + + /* Get the index of the parent of the node at index i */ + public Integer parent(int i) { + return (i - 1) / 2; + } + + /* Level-order traversal */ + public List levelOrder() { + List res = new ArrayList<>(); + // Traverse array + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + return res; + } + + /* Depth-first traversal */ + private void dfs(Integer i, String order, List res) { + // If it is an empty spot, return + if (val(i) == null) + return; + // Pre-order traversal + if ("pre".equals(order)) + res.add(val(i)); + dfs(left(i), order, res); + // In-order traversal + if ("in".equals(order)) + res.add(val(i)); + dfs(right(i), order, res); + // Post-order traversal + if ("post".equals(order)) + res.add(val(i)); + } + + /* Pre-order traversal */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } + + /* In-order traversal */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } + + /* Post-order traversal */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } +} + +public class array_binary_tree { + public static void main(String[] args) { + // Initialize binary tree + // Use a specific function to convert an array into a binary tree + List arr = Arrays.asList(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15); + + TreeNode root = TreeNode.listToTree(arr); + System.out.println("\nInitialize binary tree\n"); + System.out.println("Array representation of the binary tree:"); + System.out.println(arr); + System.out.println("Linked list representation of the binary tree:"); + PrintUtil.printTree(root); + + // Array-based binary tree class + ArrayBinaryTree abt = new ArrayBinaryTree(arr); + + // Access node + int i = 1; + Integer l = abt.left(i); + Integer r = abt.right(i); + Integer p = abt.parent(i); + System.out.println("\nThe current node's index is " + i + ", value = " + abt.val(i)); + System.out.println("Its left child's index is " + l + ", value = " + (l == null ? "null" : abt.val(l))); + System.out.println("Its right child's index is " + r + ", value = " + (r == null ? "null" : abt.val(r))); + System.out.println("Its parent's index is " + p + ", value = " + (p == null ? "null" : abt.val(p))); + + // Traverse tree + List res = abt.levelOrder(); + System.out.println("\nLevel-order traversal is:" + res); + res = abt.preOrder(); + System.out.println("Pre-order traversal is:" + res); + res = abt.inOrder(); + System.out.println("In-order traversal is:" + res); + res = abt.postOrder(); + System.out.println("Post-order traversal is:" + res); + } +} diff --git a/en/codes/java/chapter_tree/avl_tree.java b/en/codes/java/chapter_tree/avl_tree.java new file mode 100644 index 0000000000..6f34510473 --- /dev/null +++ b/en/codes/java/chapter_tree/avl_tree.java @@ -0,0 +1,220 @@ +/** + * File: avl_tree.java + * Created Time: 2022-12-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* AVL tree */ +class AVLTree { + TreeNode root; // Root node + + /* Get node height */ + public int height(TreeNode node) { + // Empty node height is -1, leaf node height is 0 + return node == null ? -1 : node.height; + } + + /* Update node height */ + private void updateHeight(TreeNode node) { + // Node height equals the height of the tallest subtree + 1 + node.height = Math.max(height(node.left), height(node.right)) + 1; + } + + /* Get balance factor */ + public int balanceFactor(TreeNode node) { + // Empty node balance factor is 0 + if (node == null) + return 0; + // Node balance factor = left subtree height - right subtree height + return height(node.left) - height(node.right); + } + + /* Right rotation operation */ + private TreeNode rightRotate(TreeNode node) { + TreeNode child = node.left; + TreeNode grandChild = child.right; + // Rotate node to the right around child + child.right = node; + node.left = grandChild; + // Update node height + updateHeight(node); + updateHeight(child); + // Return the root of the subtree after rotation + return child; + } + + /* Left rotation operation */ + private TreeNode leftRotate(TreeNode node) { + TreeNode child = node.right; + TreeNode grandChild = child.left; + // Rotate node to the left around child + child.left = node; + node.right = grandChild; + // Update node height + updateHeight(node); + updateHeight(child); + // Return the root of the subtree after rotation + return child; + } + + /* Perform rotation operation to restore balance to the subtree */ + private TreeNode rotate(TreeNode node) { + // Get the balance factor of node + int balanceFactor = balanceFactor(node); + // Left-leaning tree + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // Right rotation + return rightRotate(node); + } else { + // First left rotation then right rotation + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // Right-leaning tree + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // Left rotation + return leftRotate(node); + } else { + // First right rotation then left rotation + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // Balanced tree, no rotation needed, return + return node; + } + + /* Insert node */ + public void insert(int val) { + root = insertHelper(root, val); + } + + /* Recursively insert node (helper method) */ + private TreeNode insertHelper(TreeNode node, int val) { + if (node == null) + return new TreeNode(val); + /* 1. Find insertion position and insert node */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // Do not insert duplicate nodes, return + updateHeight(node); // Update node height + /* 2. Perform rotation operation to restore balance to the subtree */ + node = rotate(node); + // Return the root node of the subtree + return node; + } + + /* Remove node */ + public void remove(int val) { + root = removeHelper(root, val); + } + + /* Recursively remove node (helper method) */ + private TreeNode removeHelper(TreeNode node, int val) { + if (node == null) + return null; + /* 1. Find and remove the node */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode child = node.left != null ? node.left : node.right; + // Number of child nodes = 0, remove node and return + if (child == null) + return null; + // Number of child nodes = 1, remove node + else + node = child; + } else { + // Number of child nodes = 2, remove the next node in in-order traversal and replace the current node with it + TreeNode temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // Update node height + /* 2. Perform rotation operation to restore balance to the subtree */ + node = rotate(node); + // Return the root node of the subtree + return node; + } + + /* Search node */ + public TreeNode search(int val) { + TreeNode cur = root; + // Loop find, break after passing leaf nodes + while (cur != null) { + // Target node is in cur's right subtree + if (cur.val < val) + cur = cur.right; + // Target node is in cur's left subtree + else if (cur.val > val) + cur = cur.left; + // Found target node, break loop + else + break; + } + // Return target node + return cur; + } +} + +public class avl_tree { + static void testInsert(AVLTree tree, int val) { + tree.insert(val); + System.out.println("\nAfter inserting node " + val + ", the AVL tree is "); + PrintUtil.printTree(tree.root); + } + + static void testRemove(AVLTree tree, int val) { + tree.remove(val); + System.out.println("\nAfter removing node " + val + ", the AVL tree is "); + PrintUtil.printTree(tree.root); + } + + public static void main(String[] args) { + /* Initialize empty AVL tree */ + AVLTree avlTree = new AVLTree(); + + /* Insert node */ + // Notice how the AVL tree maintains balance after inserting nodes + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* Insert duplicate node */ + testInsert(avlTree, 7); + + /* Remove node */ + // Notice how the AVL tree maintains balance after removing nodes + testRemove(avlTree, 8); // Remove node with degree 0 + testRemove(avlTree, 5); // Remove node with degree 1 + testRemove(avlTree, 4); // Remove node with degree 2 + + /* Search node */ + TreeNode node = avlTree.search(7); + System.out.println("\nThe found node object is " + node + ", node value = " + node.val); + } +} diff --git a/en/codes/java/chapter_tree/binary_search_tree.java b/en/codes/java/chapter_tree/binary_search_tree.java new file mode 100644 index 0000000000..365290ac8b --- /dev/null +++ b/en/codes/java/chapter_tree/binary_search_tree.java @@ -0,0 +1,158 @@ +/** + * File: binary_search_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* Binary search tree */ +class BinarySearchTree { + private TreeNode root; + + /* Constructor */ + public BinarySearchTree() { + // Initialize empty tree + root = null; + } + + /* Get binary tree root node */ + public TreeNode getRoot() { + return root; + } + + /* Search node */ + public TreeNode search(int num) { + TreeNode cur = root; + // Loop find, break after passing leaf nodes + while (cur != null) { + // Target node is in cur's right subtree + if (cur.val < num) + cur = cur.right; + // Target node is in cur's left subtree + else if (cur.val > num) + cur = cur.left; + // Found target node, break loop + else + break; + } + // Return target node + return cur; + } + + /* Insert node */ + public void insert(int num) { + // If tree is empty, initialize root node + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode cur = root, pre = null; + // Loop find, break after passing leaf nodes + while (cur != null) { + // Found duplicate node, thus return + if (cur.val == num) + return; + pre = cur; + // Insertion position is in cur's right subtree + if (cur.val < num) + cur = cur.right; + // Insertion position is in cur's left subtree + else + cur = cur.left; + } + // Insert node + TreeNode node = new TreeNode(num); + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + + /* Remove node */ + public void remove(int num) { + // If tree is empty, return + if (root == null) + return; + TreeNode cur = root, pre = null; + // Loop find, break after passing leaf nodes + while (cur != null) { + // Found node to be removed, break loop + if (cur.val == num) + break; + pre = cur; + // Node to be removed is in cur's right subtree + if (cur.val < num) + cur = cur.right; + // Node to be removed is in cur's left subtree + else + cur = cur.left; + } + // If no node to be removed, return + if (cur == null) + return; + // Number of child nodes = 0 or 1 + if (cur.left == null || cur.right == null) { + // When the number of child nodes = 0/1, child = null/that child node + TreeNode child = cur.left != null ? cur.left : cur.right; + // Remove node cur + if (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // If the removed node is the root, reassign the root + root = child; + } + } + // Number of child nodes = 2 + else { + // Get the next node in in-order traversal of cur + TreeNode tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // Recursively remove node tmp + remove(tmp.val); + // Replace cur with tmp + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + public static void main(String[] args) { + /* Initialize binary search tree */ + BinarySearchTree bst = new BinarySearchTree(); + // Note that different insertion orders can result in various tree structures. This particular sequence creates a perfect binary tree + int[] nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }; + for (int num : nums) { + bst.insert(num); + } + System.out.println("\nInitialized binary tree is\n"); + PrintUtil.printTree(bst.getRoot()); + + /* Search node */ + TreeNode node = bst.search(7); + System.out.println("\nThe found node object is " + node + ", node value = " + node.val); + + /* Insert node */ + bst.insert(16); + System.out.println("\nAfter inserting node 16, the binary tree is\n"); + PrintUtil.printTree(bst.getRoot()); + + /* Remove node */ + bst.remove(1); + System.out.println("\nAfter removing node 1, the binary tree is\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(2); + System.out.println("\nAfter removing node 2, the binary tree is\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(4); + System.out.println("\nAfter removing node 4, the binary tree is\n"); + PrintUtil.printTree(bst.getRoot()); + } +} diff --git a/en/codes/java/chapter_tree/binary_tree.java b/en/codes/java/chapter_tree/binary_tree.java new file mode 100644 index 0000000000..df209d2e4c --- /dev/null +++ b/en/codes/java/chapter_tree/binary_tree.java @@ -0,0 +1,40 @@ +/** + * File: binary_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +public class binary_tree { + public static void main(String[] args) { + /* Initialize binary tree */ + // Initialize node + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Construct node references (pointers) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + System.out.println("\nInitialize binary tree\n"); + PrintUtil.printTree(n1); + + /* Insert and remove nodes */ + TreeNode P = new TreeNode(0); + // Insert node P between n1 -> n2 + n1.left = P; + P.left = n2; + System.out.println("\nAfter inserting node P\n"); + PrintUtil.printTree(n1); + // Remove node P + n1.left = n2; + System.out.println("\nAfter removing node P\n"); + PrintUtil.printTree(n1); + } +} diff --git a/en/codes/java/chapter_tree/binary_tree_bfs.java b/en/codes/java/chapter_tree/binary_tree_bfs.java new file mode 100644 index 0000000000..3a398500ff --- /dev/null +++ b/en/codes/java/chapter_tree/binary_tree_bfs.java @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_bfs { + /* Level-order traversal */ + static List levelOrder(TreeNode root) { + // Initialize queue, add root node + Queue queue = new LinkedList<>(); + queue.add(root); + // Initialize a list to store the traversal sequence + List list = new ArrayList<>(); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); // Queue dequeues + list.add(node.val); // Save node value + if (node.left != null) + queue.offer(node.left); // Left child node enqueues + if (node.right != null) + queue.offer(node.right); // Right child node enqueues + } + return list; + } + + public static void main(String[] args) { + /* Initialize binary tree */ + // Use a specific function to convert an array into a binary tree + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\nInitialize binary tree\n"); + PrintUtil.printTree(root); + + /* Level-order traversal */ + List list = levelOrder(root); + System.out.println("\nPrint sequence of nodes from level-order traversal = " + list); + } +} diff --git a/en/codes/java/chapter_tree/binary_tree_dfs.java b/en/codes/java/chapter_tree/binary_tree_dfs.java new file mode 100644 index 0000000000..2ca14ccaa1 --- /dev/null +++ b/en/codes/java/chapter_tree/binary_tree_dfs.java @@ -0,0 +1,68 @@ +/** + * File: binary_tree_dfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_dfs { + // Initialize the list for storing traversal sequences + static ArrayList list = new ArrayList<>(); + + /* Pre-order traversal */ + static void preOrder(TreeNode root) { + if (root == null) + return; + // Visit priority: root node -> left subtree -> right subtree + list.add(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* In-order traversal */ + static void inOrder(TreeNode root) { + if (root == null) + return; + // Visit priority: left subtree -> root node -> right subtree + inOrder(root.left); + list.add(root.val); + inOrder(root.right); + } + + /* Post-order traversal */ + static void postOrder(TreeNode root) { + if (root == null) + return; + // Visit priority: left subtree -> right subtree -> root node + postOrder(root.left); + postOrder(root.right); + list.add(root.val); + } + + public static void main(String[] args) { + /* Initialize binary tree */ + // Use a specific function to convert an array into a binary tree + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\nInitialize binary tree\n"); + PrintUtil.printTree(root); + + /* Pre-order traversal */ + list.clear(); + preOrder(root); + System.out.println("\nPrint sequence of nodes from pre-order traversal = " + list); + + /* In-order traversal */ + list.clear(); + inOrder(root); + System.out.println("\nPrint sequence of nodes from in-order traversal = " + list); + + /* Post-order traversal */ + list.clear(); + postOrder(root); + System.out.println("\nPrint sequence of nodes from post-order traversal = " + list); + } +} diff --git a/en/codes/java/utils/ListNode.java b/en/codes/java/utils/ListNode.java new file mode 100644 index 0000000000..2156d68a97 --- /dev/null +++ b/en/codes/java/utils/ListNode.java @@ -0,0 +1,28 @@ +/** + * File: ListNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +/* Linked list node */ +public class ListNode { + public int val; + public ListNode next; + + public ListNode(int x) { + val = x; + } + + /* Deserialize a list into a linked list */ + public static ListNode arrToLinkedList(int[] arr) { + ListNode dum = new ListNode(0); + ListNode head = dum; + for (int val : arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } +} diff --git a/en/codes/java/utils/PrintUtil.java b/en/codes/java/utils/PrintUtil.java new file mode 100644 index 0000000000..2ce4044f8c --- /dev/null +++ b/en/codes/java/utils/PrintUtil.java @@ -0,0 +1,116 @@ +/** + * File: PrintUtil.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +class Trunk { + Trunk prev; + String str; + + Trunk(Trunk prev, String str) { + this.prev = prev; + this.str = str; + } +}; + +public class PrintUtil { + /* Print matrix (Array) */ + public static void printMatrix(T[][] matrix) { + System.out.println("["); + for (T[] row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* Print matrix (List) */ + public static void printMatrix(List> matrix) { + System.out.println("["); + for (List row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* Print linked list */ + public static void printLinkedList(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + list.add(String.valueOf(head.val)); + head = head.next; + } + System.out.println(String.join(" -> ", list)); + } + + /* Print binary tree */ + public static void printTree(TreeNode root) { + printTree(root, null, false); + } + + /** + * Print binary tree + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void printTree(TreeNode root, Trunk prev, boolean isRight) { + if (root == null) { + return; + } + + String prev_str = " "; + Trunk trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + showTrunks(trunk); + System.out.println(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + printTree(root.left, trunk, false); + } + + public static void showTrunks(Trunk p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + System.out.print(p.str); + } + + /* Print hash table */ + public static void printHashMap(Map map) { + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + } + + /* Print heap (Priority queue) */ + public static void printHeap(Queue queue) { + List list = new ArrayList<>(queue); + System.out.print("Array representation of the heap:"); + System.out.println(list); + System.out.println("Tree representation of the heap:"); + TreeNode root = TreeNode.listToTree(list); + printTree(root); + } +} diff --git a/en/codes/java/utils/TreeNode.java b/en/codes/java/utils/TreeNode.java new file mode 100644 index 0000000000..b9af43219d --- /dev/null +++ b/en/codes/java/utils/TreeNode.java @@ -0,0 +1,73 @@ +/** + * File: TreeNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* Binary tree node class */ +public class TreeNode { + public int val; // Node value + public int height; // Node height + public TreeNode left; // Reference to the left child node + public TreeNode right; // Reference to the right child node + + /* Constructor */ + public TreeNode(int x) { + val = x; + } + + // For serialization encoding rules, refer to: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // Array representation of the binary tree: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // Linked list representation of the binary tree: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* Deserialize a list into a binary tree: Recursively */ + private static TreeNode listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.size() || arr.get(i) == null) { + return null; + } + TreeNode root = new TreeNode(arr.get(i)); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; + } + + /* Deserialize a list into a binary tree */ + public static TreeNode listToTree(List arr) { + return listToTreeDFS(arr, 0); + } + + /* Serialize a binary tree into a list: Recursively */ + private static void treeToListDFS(TreeNode root, int i, List res) { + if (root == null) + return; + while (i >= res.size()) { + res.add(null); + } + res.set(i, root.val); + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); + } + + /* Serialize a binary tree into a list */ + public static List treeToList(TreeNode root) { + List res = new ArrayList<>(); + treeToListDFS(root, 0, res); + return res; + } +} diff --git a/en/codes/java/utils/Vertex.java b/en/codes/java/utils/Vertex.java new file mode 100644 index 0000000000..92e950abe2 --- /dev/null +++ b/en/codes/java/utils/Vertex.java @@ -0,0 +1,36 @@ +/** + * File: Vertex.java + * Created Time: 2023-02-15 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* Vertex class */ +public class Vertex { + public int val; + + public Vertex(int val) { + this.val = val; + } + + /* Input a list of values vals, return a list of vertices vets */ + public static Vertex[] valsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.length]; + for (int i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* Input a list of vertices vets, return a list of values vals */ + public static List vetsToVals(List vets) { + List vals = new ArrayList<>(); + for (Vertex vet : vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/en/codes/python/.gitignore b/en/codes/python/.gitignore new file mode 100644 index 0000000000..bee8a64b79 --- /dev/null +++ b/en/codes/python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/en/codes/python/chapter_array_and_linkedlist/array.py b/en/codes/python/chapter_array_and_linkedlist/array.py new file mode 100644 index 0000000000..ab7810dcfd --- /dev/null +++ b/en/codes/python/chapter_array_and_linkedlist/array.py @@ -0,0 +1,100 @@ +""" +File: array.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_access(nums: list[int]) -> int: + """Random access to elements""" + # Randomly select a number from the interval [0, len(nums)-1] + random_index = random.randint(0, len(nums) - 1) + # Retrieve and return a random element + random_num = nums[random_index] + return random_num + + +# Note that Python's list is a dynamic array that can be extended +# For ease of learning, this function treats the list as a static array +def extend(nums: list[int], enlarge: int) -> list[int]: + """Extend array length""" + # Initialize an extended length array + res = [0] * (len(nums) + enlarge) + # Copy all elements from the original array to the new array + for i in range(len(nums)): + res[i] = nums[i] + # Return the new array after expansion + return res + + +def insert(nums: list[int], num: int, index: int): + """Insert element num at `index`""" + # Move all elements after `index` one position backward + for i in range(len(nums) - 1, index, -1): + nums[i] = nums[i - 1] + # Assign num to the element at index + nums[index] = num + + +def remove(nums: list[int], index: int): + """Remove the element at `index`""" + # Move all elements after `index` one position forward + for i in range(index, len(nums) - 1): + nums[i] = nums[i + 1] + + +def traverse(nums: list[int]): + """Traverse array""" + count = 0 + # Traverse array by index + for i in range(len(nums)): + count += nums[i] + # Traverse array elements + for num in nums: + count += num + # Traverse both data index and elements + for i, num in enumerate(nums): + count += nums[i] + count += num + + +def find(nums: list[int], target: int) -> int: + """Search for a specified element in the array""" + for i in range(len(nums)): + if nums[i] == target: + return i + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize an array + arr = [0] * 5 + print("Array arr =", arr) + nums = [1, 3, 2, 5, 4] + print("Array nums =", nums) + + # Random access + random_num: int = random_access(nums) + print("Retrieve a random element in nums", random_num) + + # Length extension + nums: list[int] = extend(nums, 3) + print("Extend the array length to 8, resulting in nums =", nums) + + # Insert element + insert(nums, 6, 3) + print("Insert number 6 at index 3, resulting in nums =", nums) + + # Remove element + remove(nums, 2) + print("Remove the element at index 2, resulting in nums =", nums) + + # Traverse array + traverse(nums) + + # Search for elements + index: int = find(nums, 3) + print("Search for element 3 in nums, resulting in index =", index) diff --git a/en/codes/python/chapter_array_and_linkedlist/linked_list.py b/en/codes/python/chapter_array_and_linkedlist/linked_list.py new file mode 100644 index 0000000000..c203c08629 --- /dev/null +++ b/en/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -0,0 +1,85 @@ +""" +File: linked_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, print_linked_list + + +def insert(n0: ListNode, P: ListNode): + """Insert node P after node n0 in the linked list""" + n1 = n0.next + P.next = n1 + n0.next = P + + +def remove(n0: ListNode): + """Remove the first node after node n0 in the linked list""" + if not n0.next: + return + # n0 -> P -> n1 + P = n0.next + n1 = P.next + n0.next = n1 + + +def access(head: ListNode, index: int) -> ListNode | None: + """Access the node at `index` in the linked list""" + for _ in range(index): + if not head: + return None + head = head.next + return head + + +def find(head: ListNode, target: int) -> int: + """Search for the first node with value target in the linked list""" + index = 0 + while head: + if head.val == target: + return index + head = head.next + index += 1 + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize linked list + # Initialize each node + n0 = ListNode(1) + n1 = ListNode(3) + n2 = ListNode(2) + n3 = ListNode(5) + n4 = ListNode(4) + # Build references between nodes + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("The initialized linked list is") + print_linked_list(n0) + + # Insert node + p = ListNode(0) + insert(n0, p) + print("Linked list after inserting the node is") + print_linked_list(n0) + + # Remove node + remove(n0) + print("Linked list after removing the node is") + print_linked_list(n0) + + # Access node + node: ListNode = access(n0, 3) + print("The value of the node at index 3 in the linked list = {}".format(node.val)) + + # Search node + index: int = find(n0, 2) + print("The index of the node with value 2 in the linked list = {}".format(index)) diff --git a/en/codes/python/chapter_array_and_linkedlist/list.py b/en/codes/python/chapter_array_and_linkedlist/list.py new file mode 100644 index 0000000000..32c2c0c24b --- /dev/null +++ b/en/codes/python/chapter_array_and_linkedlist/list.py @@ -0,0 +1,56 @@ +""" +File: list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # Initialize list + nums: list[int] = [1, 3, 2, 5, 4] + print("\nList nums =", nums) + + # Access element + x: int = nums[1] + print("\nAccess the element at index 1, resulting in x =", x) + + # Update element + nums[1] = 0 + print("\nUpdate the element at index 1 to 0, resulting in nums =", nums) + + # Clear list + nums.clear() + print("\nAfter clearing the list, nums =", nums) + + # Add element at the end + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("\nAfter adding the element, nums =", nums) + + # Insert element in the middle + nums.insert(3, 6) + print("\nInsert number 6 at index 3, resulting in nums =", nums) + + # Remove element + nums.pop(3) + print("\nRemove the element at index 3, resulting in nums =", nums) + + # Traverse the list by index + count = 0 + for i in range(len(nums)): + count += nums[i] + # Traverse the list elements + for num in nums: + count += num + + # Concatenate two lists + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + print("\nConcatenate list nums1 to nums, resulting in nums =", nums) + + # Sort list + nums.sort() + print("\nAfter sorting the list, nums =", nums) diff --git a/en/codes/python/chapter_array_and_linkedlist/my_list.py b/en/codes/python/chapter_array_and_linkedlist/my_list.py new file mode 100644 index 0000000000..c1c7361a79 --- /dev/null +++ b/en/codes/python/chapter_array_and_linkedlist/my_list.py @@ -0,0 +1,118 @@ +""" +File: my_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +class MyList: + """List class""" + + def __init__(self): + """Constructor""" + self._capacity: int = 10 # List capacity + self._arr: list[int] = [0] * self._capacity # Array (stores list elements) + self._size: int = 0 # List length (current number of elements) + self._extend_ratio: int = 2 # Multiple for each list expansion + + def size(self) -> int: + """Get list length (current number of elements)""" + return self._size + + def capacity(self) -> int: + """Get list capacity""" + return self._capacity + + def get(self, index: int) -> int: + """Access element""" + # If the index is out of bounds, throw an exception, as below + if index < 0 or index >= self._size: + raise IndexError("Index out of bounds") + return self._arr[index] + + def set(self, num: int, index: int): + """Update element""" + if index < 0 or index >= self._size: + raise IndexError("Index out of bounds") + self._arr[index] = num + + def add(self, num: int): + """Add element at the end""" + # When the number of elements exceeds capacity, trigger the expansion mechanism + if self.size() == self.capacity(): + self.extend_capacity() + self._arr[self._size] = num + self._size += 1 + + def insert(self, num: int, index: int): + """Insert element in the middle""" + if index < 0 or index >= self._size: + raise IndexError("Index out of bounds") + # When the number of elements exceeds capacity, trigger the expansion mechanism + if self._size == self.capacity(): + self.extend_capacity() + # Move all elements after `index` one position backward + for j in range(self._size - 1, index - 1, -1): + self._arr[j + 1] = self._arr[j] + self._arr[index] = num + # Update the number of elements + self._size += 1 + + def remove(self, index: int) -> int: + """Remove element""" + if index < 0 or index >= self._size: + raise IndexError("Index out of bounds") + num = self._arr[index] + # Move all elements after `index` one position forward + for j in range(index, self._size - 1): + self._arr[j] = self._arr[j + 1] + # Update the number of elements + self._size -= 1 + # Return the removed element + return num + + def extend_capacity(self): + """Extend list""" + # Create a new array of _extend_ratio times the length of the original array and copy the original array to the new array + self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1) + # Update list capacity + self._capacity = len(self._arr) + + def to_array(self) -> list[int]: + """Return a list of valid lengths""" + return self._arr[: self._size] + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize list + nums = MyList() + # Add element at the end + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + print(f"List nums = {nums.to_array()} ,capacity = {nums.capacity()} ,length = {nums.size()}") + + # Insert element in the middle + nums.insert(6, index=3) + print("Insert number 6 at index 3, resulting in nums =", nums.to_array()) + + # Remove element + nums.remove(3) + print("Remove the element at index 3, resulting in nums =", nums.to_array()) + + # Access element + num = nums.get(1) + print("Access the element at index 1, resulting in num =", num) + + # Update element + nums.set(0, 1) + print("Update the element at index 1 to 0, resulting in nums =", nums.to_array()) + + # Test expansion mechanism + for i in range(10): + # At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism at this time + nums.add(i) + print(f"After expansion, the list {nums.to_array()} ,capacity = {nums.capacity()} ,length = {nums.size()}") diff --git a/en/codes/python/chapter_backtracking/n_queens.py b/en/codes/python/chapter_backtracking/n_queens.py new file mode 100644 index 0000000000..5bb3004487 --- /dev/null +++ b/en/codes/python/chapter_backtracking/n_queens.py @@ -0,0 +1,62 @@ +""" +File: n_queens.py +Created Time: 2023-04-26 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + row: int, + n: int, + state: list[list[str]], + res: list[list[list[str]]], + cols: list[bool], + diags1: list[bool], + diags2: list[bool], +): + """Backtracking algorithm: n queens""" + # When all rows are placed, record the solution + if row == n: + res.append([list(row) for row in state]) + return + # Traverse all columns + for col in range(n): + # Calculate the main and minor diagonals corresponding to the cell + diag1 = row - col + n - 1 + diag2 = row + col + # Pruning: do not allow queens on the column, main diagonal, or minor diagonal of the cell + if not cols[col] and not diags1[diag1] and not diags2[diag2]: + # Attempt: place the queen in the cell + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = True + # Place the next row + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # Retract: restore the cell to an empty spot + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = False + + +def n_queens(n: int) -> list[list[list[str]]]: + """Solve n queens""" + # Initialize an n*n size chessboard, where 'Q' represents the queen and '#' represents an empty spot + state = [["#" for _ in range(n)] for _ in range(n)] + cols = [False] * n # Record columns with queens + diags1 = [False] * (2 * n - 1) # Record main diagonals with queens + diags2 = [False] * (2 * n - 1) # Record minor diagonals with queens + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 4 + res = n_queens(n) + + print(f"Input chessboard dimensions as {n}") + print(f"The total number of queen placement solutions is {len(res)}") + for state in res: + print("--------------------") + for row in state: + print(row) diff --git a/en/codes/python/chapter_backtracking/permutations_i.py b/en/codes/python/chapter_backtracking/permutations_i.py new file mode 100644 index 0000000000..cd4f164b77 --- /dev/null +++ b/en/codes/python/chapter_backtracking/permutations_i.py @@ -0,0 +1,44 @@ +""" +File: permutations_i.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """Backtracking algorithm: Permutation I""" + # When the state length equals the number of elements, record the solution + if len(state) == len(choices): + res.append(list(state)) + return + # Traverse all choices + for i, choice in enumerate(choices): + # Pruning: do not allow repeated selection of elements + if not selected[i]: + # Attempt: make a choice, update the state + selected[i] = True + state.append(choice) + # Proceed to the next round of selection + backtrack(state, choices, selected, res) + # Retract: undo the choice, restore to the previous state + selected[i] = False + state.pop() + + +def permutations_i(nums: list[int]) -> list[list[int]]: + """Permutation I""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 3] + + res = permutations_i(nums) + + print(f"Input array nums = {nums}") + print(f"All permutations res = {res}") diff --git a/en/codes/python/chapter_backtracking/permutations_ii.py b/en/codes/python/chapter_backtracking/permutations_ii.py new file mode 100644 index 0000000000..5f18076e89 --- /dev/null +++ b/en/codes/python/chapter_backtracking/permutations_ii.py @@ -0,0 +1,46 @@ +""" +File: permutations_ii.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """Backtracking algorithm: Permutation II""" + # When the state length equals the number of elements, record the solution + if len(state) == len(choices): + res.append(list(state)) + return + # Traverse all choices + duplicated = set[int]() + for i, choice in enumerate(choices): + # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements + if not selected[i] and choice not in duplicated: + # Attempt: make a choice, update the state + duplicated.add(choice) # Record selected element values + selected[i] = True + state.append(choice) + # Proceed to the next round of selection + backtrack(state, choices, selected, res) + # Retract: undo the choice, restore to the previous state + selected[i] = False + state.pop() + + +def permutations_ii(nums: list[int]) -> list[list[int]]: + """Permutation II""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 2] + + res = permutations_ii(nums) + + print(f"Input array nums = {nums}") + print(f"All permutations res = {res}") diff --git a/en/codes/python/chapter_backtracking/preorder_traversal_i_compact.py b/en/codes/python/chapter_backtracking/preorder_traversal_i_compact.py new file mode 100644 index 0000000000..91a7c9cddb --- /dev/null +++ b/en/codes/python/chapter_backtracking/preorder_traversal_i_compact.py @@ -0,0 +1,36 @@ +""" +File: preorder_traversal_i_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """Pre-order traversal: Example one""" + if root is None: + return + if root.val == 7: + # Record solution + res.append(root) + pre_order(root.left) + pre_order(root.right) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nInitialize binary tree") + print_tree(root) + + # Pre-order traversal + res = list[TreeNode]() + pre_order(root) + + print("\nOutput all nodes with value 7") + print([node.val for node in res]) diff --git a/en/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py b/en/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py new file mode 100644 index 0000000000..d6fc3469ee --- /dev/null +++ b/en/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py @@ -0,0 +1,42 @@ +""" +File: preorder_traversal_ii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """Pre-order traversal: Example two""" + if root is None: + return + # Attempt + path.append(root) + if root.val == 7: + # Record solution + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # Retract + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nInitialize binary tree") + print_tree(root) + + # Pre-order traversal + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\nOutput all root-to-node 7 paths") + for path in res: + print([node.val for node in path]) diff --git a/en/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/en/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py new file mode 100644 index 0000000000..9c98318bc0 --- /dev/null +++ b/en/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -0,0 +1,43 @@ +""" +File: preorder_traversal_iii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """Pre-order traversal: Example three""" + # Pruning + if root is None or root.val == 3: + return + # Attempt + path.append(root) + if root.val == 7: + # Record solution + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # Retract + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nInitialize binary tree") + print_tree(root) + + # Pre-order traversal + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\nOutput all root-to-node 7 paths, not including nodes with value 3") + for path in res: + print([node.val for node in path]) diff --git a/en/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/en/codes/python/chapter_backtracking/preorder_traversal_iii_template.py new file mode 100644 index 0000000000..f35c5a4851 --- /dev/null +++ b/en/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -0,0 +1,71 @@ +""" +File: preorder_traversal_iii_template.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def is_solution(state: list[TreeNode]) -> bool: + """Determine if the current state is a solution""" + return state and state[-1].val == 7 + + +def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): + """Record solution""" + res.append(list(state)) + + +def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: + """Determine if the choice is legal under the current state""" + return choice is not None and choice.val != 3 + + +def make_choice(state: list[TreeNode], choice: TreeNode): + """Update state""" + state.append(choice) + + +def undo_choice(state: list[TreeNode], choice: TreeNode): + """Restore state""" + state.pop() + + +def backtrack( + state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] +): + """Backtracking algorithm: Example three""" + # Check if it's a solution + if is_solution(state): + # Record solution + record_solution(state, res) + # Traverse all choices + for choice in choices: + # Pruning: check if the choice is legal + if is_valid(state, choice): + # Attempt: make a choice, update the state + make_choice(state, choice) + # Proceed to the next round of selection + backtrack(state, [choice.left, choice.right], res) + # Retract: undo the choice, restore to the previous state + undo_choice(state, choice) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nInitialize binary tree") + print_tree(root) + + # Backtracking algorithm + res = [] + backtrack(state=[], choices=[root], res=res) + + print("\nOutput all root-to-node 7 paths, requiring paths not to include nodes with value 3") + for path in res: + print([node.val for node in path]) diff --git a/en/codes/python/chapter_backtracking/subset_sum_i.py b/en/codes/python/chapter_backtracking/subset_sum_i.py new file mode 100644 index 0000000000..a8de835d04 --- /dev/null +++ b/en/codes/python/chapter_backtracking/subset_sum_i.py @@ -0,0 +1,48 @@ +""" +File: subset_sum_i.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """Backtracking algorithm: Subset Sum I""" + # When the subset sum equals target, record the solution + if target == 0: + res.append(list(state)) + return + # Traverse all choices + # Pruning two: start traversing from start to avoid generating duplicate subsets + for i in range(start, len(choices)): + # Pruning one: if the subset sum exceeds target, end the loop immediately + # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target + if target - choices[i] < 0: + break + # Attempt: make a choice, update target, start + state.append(choices[i]) + # Proceed to the next round of selection + backtrack(state, target - choices[i], choices, i, res) + # Retract: undo the choice, restore to the previous state + state.pop() + + +def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: + """Solve Subset Sum I""" + state = [] # State (subset) + nums.sort() # Sort nums + start = 0 # Start point for traversal + res = [] # Result list (subset list) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + print(f"Input array nums = {nums}, target = {target}") + print(f"All subsets equal to {target} res = {res}") diff --git a/en/codes/python/chapter_backtracking/subset_sum_i_naive.py b/en/codes/python/chapter_backtracking/subset_sum_i_naive.py new file mode 100644 index 0000000000..3c471dbb54 --- /dev/null +++ b/en/codes/python/chapter_backtracking/subset_sum_i_naive.py @@ -0,0 +1,50 @@ +""" +File: subset_sum_i_naive.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], + target: int, + total: int, + choices: list[int], + res: list[list[int]], +): + """Backtracking algorithm: Subset Sum I""" + # When the subset sum equals target, record the solution + if total == target: + res.append(list(state)) + return + # Traverse all choices + for i in range(len(choices)): + # Pruning: if the subset sum exceeds target, skip that choice + if total + choices[i] > target: + continue + # Attempt: make a choice, update elements and total + state.append(choices[i]) + # Proceed to the next round of selection + backtrack(state, target, total + choices[i], choices, res) + # Retract: undo the choice, restore to the previous state + state.pop() + + +def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: + """Solve Subset Sum I (including duplicate subsets)""" + state = [] # State (subset) + total = 0 # Subset sum + res = [] # Result list (subset list) + backtrack(state, target, total, nums, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + print(f"Input array nums = {nums}, target = {target}") + print(f"All subsets equal to {target} res = {res}") + print(f"The result of this method includes duplicate sets") diff --git a/en/codes/python/chapter_backtracking/subset_sum_ii.py b/en/codes/python/chapter_backtracking/subset_sum_ii.py new file mode 100644 index 0000000000..d404343e35 --- /dev/null +++ b/en/codes/python/chapter_backtracking/subset_sum_ii.py @@ -0,0 +1,52 @@ +""" +File: subset_sum_ii.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """Backtracking algorithm: Subset Sum II""" + # When the subset sum equals target, record the solution + if target == 0: + res.append(list(state)) + return + # Traverse all choices + # Pruning two: start traversing from start to avoid generating duplicate subsets + # Pruning three: start traversing from start to avoid repeatedly selecting the same element + for i in range(start, len(choices)): + # Pruning one: if the subset sum exceeds target, end the loop immediately + # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target + if target - choices[i] < 0: + break + # Pruning four: if the element equals the left element, it indicates that the search branch is repeated, skip it + if i > start and choices[i] == choices[i - 1]: + continue + # Attempt: make a choice, update target, start + state.append(choices[i]) + # Proceed to the next round of selection + backtrack(state, target - choices[i], choices, i + 1, res) + # Retract: undo the choice, restore to the previous state + state.pop() + + +def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: + """Solve Subset Sum II""" + state = [] # State (subset) + nums.sort() # Sort nums + start = 0 # Start point for traversal + res = [] # Result list (subset list) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + print(f"Input array nums = {nums}, target = {target}") + print(f"All subsets equal to {target} res = {res}") diff --git a/en/codes/python/chapter_computational_complexity/iteration.py b/en/codes/python/chapter_computational_complexity/iteration.py new file mode 100644 index 0000000000..0eb1984d4a --- /dev/null +++ b/en/codes/python/chapter_computational_complexity/iteration.py @@ -0,0 +1,65 @@ +""" +File: iteration.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def for_loop(n: int) -> int: + """for loop""" + res = 0 + # Loop sum 1, 2, ..., n-1, n + for i in range(1, n + 1): + res += i + return res + + +def while_loop(n: int) -> int: + """while loop""" + res = 0 + i = 1 # Initialize condition variable + # Loop sum 1, 2, ..., n-1, n + while i <= n: + res += i + i += 1 # Update condition variable + return res + + +def while_loop_ii(n: int) -> int: + """while loop (two updates)""" + res = 0 + i = 1 # Initialize condition variable + # Loop sum 1, 4, 10, ... + while i <= n: + res += i + # Update condition variable + i += 1 + i *= 2 + return res + + +def nested_for_loop(n: int) -> str: + """Double for loop""" + res = "" + # Loop i = 1, 2, ..., n-1, n + for i in range(1, n + 1): + # Loop j = 1, 2, ..., n-1, n + for j in range(1, n + 1): + res += f"({i}, {j}), " + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = for_loop(n) + print(f"\nfor loop sum result res = {res}") + + res = while_loop(n) + print(f"\nwhile loop sum result res = {res}") + + res = while_loop_ii(n) + print(f"\nwhile loop (two updates) sum result res = {res}") + + res = nested_for_loop(n) + print(f"\nDouble for loop traversal result {res}") diff --git a/en/codes/python/chapter_computational_complexity/recursion.py b/en/codes/python/chapter_computational_complexity/recursion.py new file mode 100644 index 0000000000..33bca52215 --- /dev/null +++ b/en/codes/python/chapter_computational_complexity/recursion.py @@ -0,0 +1,69 @@ +""" +File: recursion.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def recur(n: int) -> int: + """Recursion""" + # Termination condition + if n == 1: + return 1 + # Recursive: recursive call + res = recur(n - 1) + # Return: return result + return n + res + + +def for_loop_recur(n: int) -> int: + """Simulate recursion with iteration""" + # Use an explicit stack to simulate the system call stack + stack = [] + res = 0 + # Recursive: recursive call + for i in range(n, 0, -1): + # Simulate "recursive" by "pushing onto the stack" + stack.append(i) + # Return: return result + while stack: + # Simulate "return" by "popping from the stack" + res += stack.pop() + # res = 1+2+3+...+n + return res + + +def tail_recur(n, res): + """Tail recursion""" + # Termination condition + if n == 0: + return res + # Tail recursive call + return tail_recur(n - 1, res + n) + + +def fib(n: int) -> int: + """Fibonacci sequence: Recursion""" + # Termination condition f(1) = 0, f(2) = 1 + if n == 1 or n == 2: + return n - 1 + # Recursive call f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # Return result f(n) + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = recur(n) + print(f"\nRecursive function sum result res = {res}") + + res = for_loop_recur(n) + print(f"\nSimulate recursion with iteration sum result res = {res}") + + res = tail_recur(n, 0) + print(f"\nTail recursive function sum result res = {res}") + + res = fib(n) + print(f"\nThe n th term of the Fibonacci sequence is {res}") diff --git a/en/codes/python/chapter_computational_complexity/space_complexity.py b/en/codes/python/chapter_computational_complexity/space_complexity.py new file mode 100644 index 0000000000..7d91a16d12 --- /dev/null +++ b/en/codes/python/chapter_computational_complexity/space_complexity.py @@ -0,0 +1,90 @@ +""" +File: space_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, TreeNode, print_tree + + +def function() -> int: + """Function""" + # Perform some operations + return 0 + + +def constant(n: int): + """Constant complexity""" + # Constants, variables, objects occupy O(1) space + a = 0 + nums = [0] * 10000 + node = ListNode(0) + # Variables in a loop occupy O(1) space + for _ in range(n): + c = 0 + # Functions in a loop occupy O(1) space + for _ in range(n): + function() + + +def linear(n: int): + """Linear complexity""" + # A list of length n occupies O(n) space + nums = [0] * n + # A hash table of length n occupies O(n) space + hmap = dict[int, str]() + for i in range(n): + hmap[i] = str(i) + + +def linear_recur(n: int): + """Linear complexity (recursive implementation)""" + print("Recursive n =", n) + if n == 1: + return + linear_recur(n - 1) + + +def quadratic(n: int): + """Quadratic complexity""" + # A two-dimensional list occupies O(n^2) space + num_matrix = [[0] * n for _ in range(n)] + + +def quadratic_recur(n: int) -> int: + """Quadratic complexity (recursive implementation)""" + if n <= 0: + return 0 + # Array nums length = n, n-1, ..., 2, 1 + nums = [0] * n + return quadratic_recur(n - 1) + + +def build_tree(n: int) -> TreeNode | None: + """Exponential complexity (building a full binary tree)""" + if n == 0: + return None + root = TreeNode(0) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + # Constant complexity + constant(n) + # Linear complexity + linear(n) + linear_recur(n) + # Quadratic complexity + quadratic(n) + quadratic_recur(n) + # Exponential complexity + root = build_tree(n) + print_tree(root) diff --git a/en/codes/python/chapter_computational_complexity/time_complexity.py b/en/codes/python/chapter_computational_complexity/time_complexity.py new file mode 100644 index 0000000000..661d5dcee2 --- /dev/null +++ b/en/codes/python/chapter_computational_complexity/time_complexity.py @@ -0,0 +1,151 @@ +""" +File: time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def constant(n: int) -> int: + """Constant complexity""" + count = 0 + size = 100000 + for _ in range(size): + count += 1 + return count + + +def linear(n: int) -> int: + """Linear complexity""" + count = 0 + for _ in range(n): + count += 1 + return count + + +def array_traversal(nums: list[int]) -> int: + """Linear complexity (traversing an array)""" + count = 0 + # Loop count is proportional to the length of the array + for num in nums: + count += 1 + return count + + +def quadratic(n: int) -> int: + """Quadratic complexity""" + count = 0 + # Loop count is squared in relation to the data size n + for i in range(n): + for j in range(n): + count += 1 + return count + + +def bubble_sort(nums: list[int]) -> int: + """Quadratic complexity (bubble sort)""" + count = 0 # Counter + # Outer loop: unsorted range is [0, i] + for i in range(len(nums) - 1, 0, -1): + # Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for j in range(i): + if nums[j] > nums[j + 1]: + # Swap nums[j] and nums[j + 1] + tmp: int = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # Element swap includes 3 individual operations + return count + + +def exponential(n: int) -> int: + """Exponential complexity (loop implementation)""" + count = 0 + base = 1 + # Cells split into two every round, forming the sequence 1, 2, 4, 8, ..., 2^(n-1) + for _ in range(n): + for _ in range(base): + count += 1 + base *= 2 + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count + + +def exp_recur(n: int) -> int: + """Exponential complexity (recursive implementation)""" + if n == 1: + return 1 + return exp_recur(n - 1) + exp_recur(n - 1) + 1 + + +def logarithmic(n: int) -> int: + """Logarithmic complexity (loop implementation)""" + count = 0 + while n > 1: + n = n / 2 + count += 1 + return count + + +def log_recur(n: int) -> int: + """Logarithmic complexity (recursive implementation)""" + if n <= 1: + return 0 + return log_recur(n / 2) + 1 + + +def linear_log_recur(n: int) -> int: + """Linear logarithmic complexity""" + if n <= 1: + return 1 + count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2) + for _ in range(n): + count += 1 + return count + + +def factorial_recur(n: int) -> int: + """Factorial complexity (recursive implementation)""" + if n == 0: + return 1 + count = 0 + # From 1 split into n + for _ in range(n): + count += factorial_recur(n - 1) + return count + + +"""Driver Code""" +if __name__ == "__main__": + # Can modify n to experience the trend of operation count changes under various complexities + n = 8 + print("Input data size n =", n) + + count: int = constant(n) + print("Constant complexity operation count =", count) + + count: int = linear(n) + print("Linear complexity operation count =", count) + count: int = array_traversal([0] * n) + print("Linear complexity (traversing an array) operation count =", count) + + count: int = quadratic(n) + print("Quadratic complexity operation count =", count) + nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] + count: int = bubble_sort(nums) + print("Quadratic complexity (bubble sort) operation count =", count) + + count: int = exponential(n) + print("Exponential complexity (loop implementation) operation count =", count) + count: int = exp_recur(n) + print("Exponential complexity (recursive implementation) operation count =", count) + + count: int = logarithmic(n) + print("Logarithmic complexity (loop implementation) operation count =", count) + count: int = log_recur(n) + print("Logarithmic complexity (recursive implementation) operation count =", count) + + count: int = linear_log_recur(n) + print("Linear logarithmic complexity (recursive implementation) operation count =", count) + + count: int = factorial_recur(n) + print("Factorial complexity (recursive implementation) operation count =", count) diff --git a/en/codes/python/chapter_computational_complexity/worst_best_time_complexity.py b/en/codes/python/chapter_computational_complexity/worst_best_time_complexity.py new file mode 100644 index 0000000000..82a0e3a585 --- /dev/null +++ b/en/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -0,0 +1,36 @@ +""" +File: worst_best_time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_numbers(n: int) -> list[int]: + """Generate an array with elements: 1, 2, ..., n, order shuffled""" + # Generate array nums =: 1, 2, 3, ..., n + nums = [i for i in range(1, n + 1)] + # Randomly shuffle array elements + random.shuffle(nums) + return nums + + +def find_one(nums: list[int]) -> int: + """Find the index of number 1 in array nums""" + for i in range(len(nums)): + # When element 1 is at the start of the array, achieve best time complexity O(1) + # When element 1 is at the end of the array, achieve worst time complexity O(n) + if nums[i] == 1: + return i + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + for i in range(10): + n = 100 + nums: list[int] = random_numbers(n) + index: int = find_one(nums) + print("\nThe array [ 1, 2, ..., n ] after being shuffled =", nums) + print("Index of number 1 =", index) diff --git a/en/codes/python/chapter_divide_and_conquer/binary_search_recur.py b/en/codes/python/chapter_divide_and_conquer/binary_search_recur.py new file mode 100644 index 0000000000..2bd9e9bdc3 --- /dev/null +++ b/en/codes/python/chapter_divide_and_conquer/binary_search_recur.py @@ -0,0 +1,40 @@ +""" +File: binary_search_recur.py +Created Time: 2023-07-17 +Author: krahets (krahets@163.com) +""" + + +def dfs(nums: list[int], target: int, i: int, j: int) -> int: + """Binary search: problem f(i, j)""" + # If the interval is empty, indicating no target element, return -1 + if i > j: + return -1 + # Calculate midpoint index m + m = (i + j) // 2 + if nums[m] < target: + # Recursive subproblem f(m+1, j) + return dfs(nums, target, m + 1, j) + elif nums[m] > target: + # Recursive subproblem f(i, m-1) + return dfs(nums, target, i, m - 1) + else: + # Found the target element, thus return its index + return m + + +def binary_search(nums: list[int], target: int) -> int: + """Binary search""" + n = len(nums) + # Solve problem f(0, n-1) + return dfs(nums, target, 0, n - 1) + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # Binary search (double closed interval) + index = binary_search(nums, target) + print("Index of target element 6 =", index) diff --git a/en/codes/python/chapter_divide_and_conquer/build_tree.py b/en/codes/python/chapter_divide_and_conquer/build_tree.py new file mode 100644 index 0000000000..8c2e960271 --- /dev/null +++ b/en/codes/python/chapter_divide_and_conquer/build_tree.py @@ -0,0 +1,54 @@ +""" +File: build_tree.py +Created Time: 2023-07-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +def dfs( + preorder: list[int], + inorder_map: dict[int, int], + i: int, + l: int, + r: int, +) -> TreeNode | None: + """Build binary tree: Divide and conquer""" + # Terminate when subtree interval is empty + if r - l < 0: + return None + # Initialize root node + root = TreeNode(preorder[i]) + # Query m to divide left and right subtrees + m = inorder_map[preorder[i]] + # Subproblem: build left subtree + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # Subproblem: build right subtree + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + # Return root node + return root + + +def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: + """Build binary tree""" + # Initialize hash table, storing in-order elements to indices mapping + inorder_map = {val: i for i, val in enumerate(inorder)} + root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + print(f"Pre-order traversal = {preorder}") + print(f"In-order traversal = {inorder}") + + root = build_tree(preorder, inorder) + print("The built binary tree is:") + print_tree(root) diff --git a/en/codes/python/chapter_divide_and_conquer/hanota.py b/en/codes/python/chapter_divide_and_conquer/hanota.py new file mode 100644 index 0000000000..306d113129 --- /dev/null +++ b/en/codes/python/chapter_divide_and_conquer/hanota.py @@ -0,0 +1,53 @@ +""" +File: hanota.py +Created Time: 2023-07-16 +Author: krahets (krahets@163.com) +""" + + +def move(src: list[int], tar: list[int]): + """Move a disc""" + # Take out a disc from the top of src + pan = src.pop() + # Place the disc on top of tar + tar.append(pan) + + +def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): + """Solve the Tower of Hanoi problem f(i)""" + # If only one disc remains on src, move it to tar + if i == 1: + move(src, tar) + return + # Subproblem f(i-1): move the top i-1 discs from src with the help of tar to buf + dfs(i - 1, src, tar, buf) + # Subproblem f(1): move the remaining one disc from src to tar + move(src, tar) + # Subproblem f(i-1): move the top i-1 discs from buf with the help of src to tar + dfs(i - 1, buf, src, tar) + + +def solve_hanota(A: list[int], B: list[int], C: list[int]): + """Solve the Tower of Hanoi problem""" + n = len(A) + # Move the top n discs from A with the help of B to C + dfs(n, A, B, C) + + +"""Driver Code""" +if __name__ == "__main__": + # The tail of the list is the top of the pillar + A = [5, 4, 3, 2, 1] + B = [] + C = [] + print("Initial state:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") + + solve_hanota(A, B, C) + + print("After the discs are moved:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") diff --git a/en/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py b/en/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py new file mode 100644 index 0000000000..0b2ea84719 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py @@ -0,0 +1,37 @@ +""" +File: climbing_stairs_backtrack.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: + """Backtracking""" + # When climbing to the nth step, add 1 to the number of solutions + if state == n: + res[0] += 1 + # Traverse all choices + for choice in choices: + # Pruning: do not allow climbing beyond the nth step + if state + choice > n: + continue + # Attempt: make a choice, update the state + backtrack(choices, state + choice, n, res) + # Retract + + +def climbing_stairs_backtrack(n: int) -> int: + """Climbing stairs: Backtracking""" + choices = [1, 2] # Can choose to climb up 1 step or 2 steps + state = 0 # Start climbing from the 0th step + res = [0] # Use res[0] to record the number of solutions + backtrack(choices, state, n, res) + return res[0] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_backtrack(n) + print(f"Climb {n} steps, there are {res} solutions in total") diff --git a/en/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py b/en/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py new file mode 100644 index 0000000000..77b5b5030f --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py @@ -0,0 +1,29 @@ +""" +File: climbing_stairs_constraint_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_constraint_dp(n: int) -> int: + """Constrained climbing stairs: Dynamic programming""" + if n == 1 or n == 2: + return 1 + # Initialize dp table, used to store subproblem solutions + dp = [[0] * 3 for _ in range(n + 1)] + # Initial state: preset the smallest subproblem solution + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # State transition: gradually solve larger subproblems from smaller ones + for i in range(3, n + 1): + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + return dp[n][1] + dp[n][2] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_constraint_dp(n) + print(f"Climb {n} steps, there are {res} solutions in total") diff --git a/en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py b/en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py new file mode 100644 index 0000000000..018445ea7b --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py @@ -0,0 +1,28 @@ +""" +File: climbing_stairs_dfs.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int) -> int: + """Search""" + # Known dp[1] and dp[2], return them + if i == 1 or i == 2: + return i + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1) + dfs(i - 2) + return count + + +def climbing_stairs_dfs(n: int) -> int: + """Climbing stairs: Search""" + return dfs(n) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs(n) + print(f"Climb {n} steps, there are {res} solutions in total") diff --git a/en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py b/en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py new file mode 100644 index 0000000000..2f517a6497 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py @@ -0,0 +1,35 @@ +""" +File: climbing_stairs_dfs_mem.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int, mem: list[int]) -> int: + """Memoized search""" + # Known dp[1] and dp[2], return them + if i == 1 or i == 2: + return i + # If there is a record for dp[i], return it + if mem[i] != -1: + return mem[i] + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # Record dp[i] + mem[i] = count + return count + + +def climbing_stairs_dfs_mem(n: int) -> int: + """Climbing stairs: Memoized search""" + # mem[i] records the total number of solutions for climbing to the ith step, -1 means no record + mem = [-1] * (n + 1) + return dfs(n, mem) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs_mem(n) + print(f"Climb {n} steps, there are {res} solutions in total") diff --git a/en/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py b/en/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py new file mode 100644 index 0000000000..39cb581743 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py @@ -0,0 +1,40 @@ +""" +File: climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_dp(n: int) -> int: + """Climbing stairs: Dynamic programming""" + if n == 1 or n == 2: + return n + # Initialize dp table, used to store subproblem solutions + dp = [0] * (n + 1) + # Initial state: preset the smallest subproblem solution + dp[1], dp[2] = 1, 2 + # State transition: gradually solve larger subproblems from smaller ones + for i in range(3, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] + + +def climbing_stairs_dp_comp(n: int) -> int: + """Climbing stairs: Space-optimized dynamic programming""" + if n == 1 or n == 2: + return n + a, b = 1, 2 + for _ in range(3, n + 1): + a, b = b, a + b + return b + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dp(n) + print(f"Climb {n} steps, there are {res} solutions in total") + + res = climbing_stairs_dp_comp(n) + print(f"Climb {n} steps, there are {res} solutions in total") diff --git a/en/codes/python/chapter_dynamic_programming/coin_change.py b/en/codes/python/chapter_dynamic_programming/coin_change.py new file mode 100644 index 0000000000..8ed789c516 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/coin_change.py @@ -0,0 +1,60 @@ +""" +File: coin_change.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_dp(coins: list[int], amt: int) -> int: + """Coin change: Dynamic programming""" + n = len(coins) + MAX = amt + 1 + # Initialize dp table + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # State transition: first row and first column + for a in range(1, amt + 1): + dp[0][a] = MAX + # State transition: the rest of the rows and columns + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # If exceeding the target amount, do not choose coin i + dp[i][a] = dp[i - 1][a] + else: + # The smaller value between not choosing and choosing coin i + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + return dp[n][amt] if dp[n][amt] != MAX else -1 + + +def coin_change_dp_comp(coins: list[int], amt: int) -> int: + """Coin change: Space-optimized dynamic programming""" + n = len(coins) + MAX = amt + 1 + # Initialize dp table + dp = [MAX] * (amt + 1) + dp[0] = 0 + # State transition + for i in range(1, n + 1): + # Traverse in order + for a in range(1, amt + 1): + if coins[i - 1] > a: + # If exceeding the target amount, do not choose coin i + dp[a] = dp[a] + else: + # The smaller value between not choosing and choosing coin i + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + return dp[amt] if dp[amt] != MAX else -1 + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 4 + + # Dynamic programming + res = coin_change_dp(coins, amt) + print(f"Minimum number of coins required to reach the target amount = {res}") + + # Space-optimized dynamic programming + res = coin_change_dp_comp(coins, amt) + print(f"Minimum number of coins required to reach the target amount = {res}") diff --git a/en/codes/python/chapter_dynamic_programming/coin_change_ii.py b/en/codes/python/chapter_dynamic_programming/coin_change_ii.py new file mode 100644 index 0000000000..f389dd5369 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/coin_change_ii.py @@ -0,0 +1,58 @@ +""" +File: coin_change_ii.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_ii_dp(coins: list[int], amt: int) -> int: + """Coin change II: Dynamic programming""" + n = len(coins) + # Initialize dp table + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # Initialize first column + for i in range(n + 1): + dp[i][0] = 1 + # State transition + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # If exceeding the target amount, do not choose coin i + dp[i][a] = dp[i - 1][a] + else: + # The sum of the two options of not choosing and choosing coin i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + return dp[n][amt] + + +def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: + """Coin change II: Space-optimized dynamic programming""" + n = len(coins) + # Initialize dp table + dp = [0] * (amt + 1) + dp[0] = 1 + # State transition + for i in range(1, n + 1): + # Traverse in order + for a in range(1, amt + 1): + if coins[i - 1] > a: + # If exceeding the target amount, do not choose coin i + dp[a] = dp[a] + else: + # The sum of the two options of not choosing and choosing coin i + dp[a] = dp[a] + dp[a - coins[i - 1]] + return dp[amt] + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 5 + + # Dynamic programming + res = coin_change_ii_dp(coins, amt) + print(f"The number of coin combinations to make up the target amount is {res}") + + # Space-optimized dynamic programming + res = coin_change_ii_dp_comp(coins, amt) + print(f"The number of coin combinations to make up the target amount is {res}") diff --git a/en/codes/python/chapter_dynamic_programming/edit_distance.py b/en/codes/python/chapter_dynamic_programming/edit_distance.py new file mode 100644 index 0000000000..d62ad73551 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/edit_distance.py @@ -0,0 +1,123 @@ +""" +File: edit_distancde.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + + +def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: + """Edit distance: Brute force search""" + # If both s and t are empty, return 0 + if i == 0 and j == 0: + return 0 + # If s is empty, return the length of t + if i == 0: + return j + # If t is empty, return the length of s + if j == 0: + return i + # If the two characters are equal, skip these two characters + if s[i - 1] == t[j - 1]: + return edit_distance_dfs(s, t, i - 1, j - 1) + # The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # Return the minimum number of edits + return min(insert, delete, replace) + 1 + + +def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int: + """Edit distance: Memoized search""" + # If both s and t are empty, return 0 + if i == 0 and j == 0: + return 0 + # If s is empty, return the length of t + if i == 0: + return j + # If t is empty, return the length of s + if j == 0: + return i + # If there is a record, return it + if mem[i][j] != -1: + return mem[i][j] + # If the two characters are equal, skip these two characters + if s[i - 1] == t[j - 1]: + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # Record and return the minimum number of edits + mem[i][j] = min(insert, delete, replace) + 1 + return mem[i][j] + + +def edit_distance_dp(s: str, t: str) -> int: + """Edit distance: Dynamic programming""" + n, m = len(s), len(t) + dp = [[0] * (m + 1) for _ in range(n + 1)] + # State transition: first row and first column + for i in range(1, n + 1): + dp[i][0] = i + for j in range(1, m + 1): + dp[0][j] = j + # State transition: the rest of the rows and columns + for i in range(1, n + 1): + for j in range(1, m + 1): + if s[i - 1] == t[j - 1]: + # If the two characters are equal, skip these two characters + dp[i][j] = dp[i - 1][j - 1] + else: + # The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + return dp[n][m] + + +def edit_distance_dp_comp(s: str, t: str) -> int: + """Edit distance: Space-optimized dynamic programming""" + n, m = len(s), len(t) + dp = [0] * (m + 1) + # State transition: first row + for j in range(1, m + 1): + dp[j] = j + # State transition: the rest of the rows + for i in range(1, n + 1): + # State transition: first column + leftup = dp[0] # Temporarily store dp[i-1, j-1] + dp[0] += 1 + # State transition: the rest of the columns + for j in range(1, m + 1): + temp = dp[j] + if s[i - 1] == t[j - 1]: + # If the two characters are equal, skip these two characters + dp[j] = leftup + else: + # The minimum number of edits = the minimum number of edits from three operations (insert, remove, replace) + 1 + dp[j] = min(dp[j - 1], dp[j], leftup) + 1 + leftup = temp # Update for the next round of dp[i-1, j-1] + return dp[m] + + +"""Driver Code""" +if __name__ == "__main__": + s = "bag" + t = "pack" + n, m = len(s), len(t) + + # Brute force search + res = edit_distance_dfs(s, t, n, m) + print(f"To change {s} to {t}, the minimum number of edits required is {res}") + + # Memoized search + mem = [[-1] * (m + 1) for _ in range(n + 1)] + res = edit_distance_dfs_mem(s, t, mem, n, m) + print(f"To change {s} to {t}, the minimum number of edits required is {res}") + + # Dynamic programming + res = edit_distance_dp(s, t) + print(f"To change {s} to {t}, the minimum number of edits required is {res}") + + # Space-optimized dynamic programming + res = edit_distance_dp_comp(s, t) + print(f"To change {s} to {t}, the minimum number of edits required is {res}") diff --git a/en/codes/python/chapter_dynamic_programming/knapsack.py b/en/codes/python/chapter_dynamic_programming/knapsack.py new file mode 100644 index 0000000000..8c06208f56 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/knapsack.py @@ -0,0 +1,101 @@ +""" +File: knapsack.py +Created Time: 2023-07-03 +Author: krahets (krahets@163.com) +""" + + +def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: + """0-1 Knapsack: Brute force search""" + # If all items have been chosen or the knapsack has no remaining capacity, return value 0 + if i == 0 or c == 0: + return 0 + # If exceeding the knapsack capacity, can only choose not to put it in the knapsack + if wgt[i - 1] > c: + return knapsack_dfs(wgt, val, i - 1, c) + # Calculate the maximum value of not putting in and putting in item i + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # Return the greater value of the two options + return max(no, yes) + + +def knapsack_dfs_mem( + wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int +) -> int: + """0-1 Knapsack: Memoized search""" + # If all items have been chosen or the knapsack has no remaining capacity, return value 0 + if i == 0 or c == 0: + return 0 + # If there is a record, return it + if mem[i][c] != -1: + return mem[i][c] + # If exceeding the knapsack capacity, can only choose not to put it in the knapsack + if wgt[i - 1] > c: + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) + # Calculate the maximum value of not putting in and putting in item i + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # Record and return the greater value of the two options + mem[i][c] = max(no, yes) + return mem[i][c] + + +def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 Knapsack: Dynamic programming""" + n = len(wgt) + # Initialize dp table + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # State transition + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # If exceeding the knapsack capacity, do not choose item i + dp[i][c] = dp[i - 1][c] + else: + # The greater value between not choosing and choosing item i + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 Knapsack: Space-optimized dynamic programming""" + n = len(wgt) + # Initialize dp table + dp = [0] * (cap + 1) + # State transition + for i in range(1, n + 1): + # Traverse in reverse order + for c in range(cap, 0, -1): + if wgt[i - 1] > c: + # If exceeding the knapsack capacity, do not choose item i + dp[c] = dp[c] + else: + # The greater value between not choosing and choosing item i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # Brute force search + res = knapsack_dfs(wgt, val, n, cap) + print(f"The maximum item value without exceeding knapsack capacity is {res}") + + # Memoized search + mem = [[-1] * (cap + 1) for _ in range(n + 1)] + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + print(f"The maximum item value without exceeding knapsack capacity is {res}") + + # Dynamic programming + res = knapsack_dp(wgt, val, cap) + print(f"The maximum item value without exceeding knapsack capacity is {res}") + + # Space-optimized dynamic programming + res = knapsack_dp_comp(wgt, val, cap) + print(f"The maximum item value without exceeding knapsack capacity is {res}") diff --git a/en/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py b/en/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py new file mode 100644 index 0000000000..47730c1855 --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py @@ -0,0 +1,43 @@ +""" +File: min_cost_climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def min_cost_climbing_stairs_dp(cost: list[int]) -> int: + """Climbing stairs with minimum cost: Dynamic programming""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + # Initialize dp table, used to store subproblem solutions + dp = [0] * (n + 1) + # Initial state: preset the smallest subproblem solution + dp[1], dp[2] = cost[1], cost[2] + # State transition: gradually solve larger subproblems from smaller ones + for i in range(3, n + 1): + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + return dp[n] + + +def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: + """Climbing stairs with minimum cost: Space-optimized dynamic programming""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + a, b = cost[1], cost[2] + for i in range(3, n + 1): + a, b = b, min(a, b) + cost[i] + return b + + +"""Driver Code""" +if __name__ == "__main__": + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print(f"Enter the list of stair costs as {cost}") + + res = min_cost_climbing_stairs_dp(cost) + print(f"Minimum cost to climb the stairs {res}") + + res = min_cost_climbing_stairs_dp_comp(cost) + print(f"Minimum cost to climb the stairs {res}") diff --git a/en/codes/python/chapter_dynamic_programming/min_path_sum.py b/en/codes/python/chapter_dynamic_programming/min_path_sum.py new file mode 100644 index 0000000000..2c9a14739e --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/min_path_sum.py @@ -0,0 +1,104 @@ +""" +File: min_path_sum.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + +from math import inf + + +def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: + """Minimum path sum: Brute force search""" + # If it's the top-left cell, terminate the search + if i == 0 and j == 0: + return grid[0][0] + # If the row or column index is out of bounds, return a +∞ cost + if i < 0 or j < 0: + return inf + # Calculate the minimum path cost from the top-left to (i-1, j) and (i, j-1) + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # Return the minimum path cost from the top-left to (i, j) + return min(left, up) + grid[i][j] + + +def min_path_sum_dfs_mem( + grid: list[list[int]], mem: list[list[int]], i: int, j: int +) -> int: + """Minimum path sum: Memoized search""" + # If it's the top-left cell, terminate the search + if i == 0 and j == 0: + return grid[0][0] + # If the row or column index is out of bounds, return a +∞ cost + if i < 0 or j < 0: + return inf + # If there is a record, return it + if mem[i][j] != -1: + return mem[i][j] + # The minimum path cost from the left and top cells + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # Record and return the minimum path cost from the top-left to (i, j) + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] + + +def min_path_sum_dp(grid: list[list[int]]) -> int: + """Minimum path sum: Dynamic programming""" + n, m = len(grid), len(grid[0]) + # Initialize dp table + dp = [[0] * m for _ in range(n)] + dp[0][0] = grid[0][0] + # State transition: first row + for j in range(1, m): + dp[0][j] = dp[0][j - 1] + grid[0][j] + # State transition: first column + for i in range(1, n): + dp[i][0] = dp[i - 1][0] + grid[i][0] + # State transition: the rest of the rows and columns + for i in range(1, n): + for j in range(1, m): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + return dp[n - 1][m - 1] + + +def min_path_sum_dp_comp(grid: list[list[int]]) -> int: + """Minimum path sum: Space-optimized dynamic programming""" + n, m = len(grid), len(grid[0]) + # Initialize dp table + dp = [0] * m + # State transition: first row + dp[0] = grid[0][0] + for j in range(1, m): + dp[j] = dp[j - 1] + grid[0][j] + # State transition: the rest of the rows + for i in range(1, n): + # State transition: first column + dp[0] = dp[0] + grid[i][0] + # State transition: the rest of the columns + for j in range(1, m): + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + return dp[m - 1] + + +"""Driver Code""" +if __name__ == "__main__": + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = len(grid), len(grid[0]) + + # Brute force search + res = min_path_sum_dfs(grid, n - 1, m - 1) + print(f"The minimum path sum from the top-left to the bottom-right corner is {res}") + + # Memoized search + mem = [[-1] * m for _ in range(n)] + res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) + print(f"The minimum path sum from the top-left to the bottom-right corner is {res}") + + # Dynamic programming + res = min_path_sum_dp(grid) + print(f"The minimum path sum from the top-left to the bottom-right corner is {res}") + + # Space-optimized dynamic programming + res = min_path_sum_dp_comp(grid) + print(f"The minimum path sum from the top-left to the bottom-right corner is {res}") diff --git a/en/codes/python/chapter_dynamic_programming/unbounded_knapsack.py b/en/codes/python/chapter_dynamic_programming/unbounded_knapsack.py new file mode 100644 index 0000000000..e2ad1de7bf --- /dev/null +++ b/en/codes/python/chapter_dynamic_programming/unbounded_knapsack.py @@ -0,0 +1,55 @@ +""" +File: unbounded_knapsack.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """Complete knapsack: Dynamic programming""" + n = len(wgt) + # Initialize dp table + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # State transition + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # If exceeding the knapsack capacity, do not choose item i + dp[i][c] = dp[i - 1][c] + else: + # The greater value between not choosing and choosing item i + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """Complete knapsack: Space-optimized dynamic programming""" + n = len(wgt) + # Initialize dp table + dp = [0] * (cap + 1) + # State transition + for i in range(1, n + 1): + # Traverse in order + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # If exceeding the knapsack capacity, do not choose item i + dp[c] = dp[c] + else: + # The greater value between not choosing and choosing item i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # Dynamic programming + res = unbounded_knapsack_dp(wgt, val, cap) + print(f"The maximum item value without exceeding knapsack capacity is {res}") + + # Space-optimized dynamic programming + res = unbounded_knapsack_dp_comp(wgt, val, cap) + print(f"The maximum item value without exceeding knapsack capacity is {res}") diff --git a/en/codes/python/chapter_graph/graph_adjacency_list.py b/en/codes/python/chapter_graph/graph_adjacency_list.py new file mode 100644 index 0000000000..ecc24eb8d0 --- /dev/null +++ b/en/codes/python/chapter_graph/graph_adjacency_list.py @@ -0,0 +1,111 @@ +""" +File: graph_adjacency_list.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets + + +class GraphAdjList: + """Undirected graph class based on adjacency list""" + + def __init__(self, edges: list[list[Vertex]]): + """Constructor""" + # Adjacency list, key: vertex, value: all adjacent vertices of that vertex + self.adj_list = dict[Vertex, list[Vertex]]() + # Add all vertices and edges + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + def size(self) -> int: + """Get the number of vertices""" + return len(self.adj_list) + + def add_edge(self, vet1: Vertex, vet2: Vertex): + """Add edge""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # Add edge vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """Remove edge""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # Remove edge vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + def add_vertex(self, vet: Vertex): + """Add vertex""" + if vet in self.adj_list: + return + # Add a new linked list to the adjacency list + self.adj_list[vet] = [] + + def remove_vertex(self, vet: Vertex): + """Remove vertex""" + if vet not in self.adj_list: + raise ValueError() + # Remove the vertex vet's corresponding linked list from the adjacency list + self.adj_list.pop(vet) + # Traverse other vertices' linked lists, removing all edges containing vet + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) + + def print(self): + """Print the adjacency list""" + print("Adjacency list =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize undirected graph + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList(edges) + print("\nAfter initialization, the graph is") + graph.print() + + # Add edge + # Vertices 1, 2 i.e., v[0], v[2] + graph.add_edge(v[0], v[2]) + print("\nAfter adding edge 1-2, the graph is") + graph.print() + + # Remove edge + # Vertices 1, 3 i.e., v[0], v[1] + graph.remove_edge(v[0], v[1]) + print("\nAfter removing edge 1-3, the graph is") + graph.print() + + # Add vertex + v5 = Vertex(6) + graph.add_vertex(v5) + print("\nAfter adding vertex 6, the graph is") + graph.print() + + # Remove vertex + # Vertex 3 i.e., v[1] + graph.remove_vertex(v[1]) + print("\nAfter removing vertex 3, the graph is") + graph.print() diff --git a/en/codes/python/chapter_graph/graph_adjacency_matrix.py b/en/codes/python/chapter_graph/graph_adjacency_matrix.py new file mode 100644 index 0000000000..b404b680dd --- /dev/null +++ b/en/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -0,0 +1,116 @@ +""" +File: graph_adjacency_matrix.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, print_matrix + + +class GraphAdjMat: + """Undirected graph class based on adjacency matrix""" + + def __init__(self, vertices: list[int], edges: list[list[int]]): + """Constructor""" + # Vertex list, elements represent "vertex value", index represents "vertex index" + self.vertices: list[int] = [] + # Adjacency matrix, row and column indices correspond to "vertex index" + self.adj_mat: list[list[int]] = [] + # Add vertex + for val in vertices: + self.add_vertex(val) + # Add edge + # Edges elements represent vertex indices + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """Get the number of vertices""" + return len(self.vertices) + + def add_vertex(self, val: int): + """Add vertex""" + n = self.size() + # Add new vertex value to the vertex list + self.vertices.append(val) + # Add a row to the adjacency matrix + new_row = [0] * n + self.adj_mat.append(new_row) + # Add a column to the adjacency matrix + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """Remove vertex""" + if index >= self.size(): + raise IndexError() + # Remove vertex at `index` from the vertex list + self.vertices.pop(index) + # Remove the row at `index` from the adjacency matrix + self.adj_mat.pop(index) + # Remove the column at `index` from the adjacency matrix + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """Add edge""" + # Parameters i, j correspond to vertices element indices + # Handle index out of bounds and equality + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., satisfies (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + def remove_edge(self, i: int, j: int): + """Remove edge""" + # Parameters i, j correspond to vertices element indices + # Handle index out of bounds and equality + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 + + def print(self): + """Print adjacency matrix""" + print("Vertex list =", self.vertices) + print("Adjacency matrix =") + print_matrix(self.adj_mat) + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize undirected graph + # Edges elements represent vertex indices + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat(vertices, edges) + print("\nAfter initialization, the graph is") + graph.print() + + # Add edge + # Indices of vertices 1, 2 are 0, 2 respectively + graph.add_edge(0, 2) + print("\nAfter adding edge 1-2, the graph is") + graph.print() + + # Remove edge + # Indices of vertices 1, 3 are 0, 1 respectively + graph.remove_edge(0, 1) + print("\nAfter removing edge 1-3, the graph is") + graph.print() + + # Add vertex + graph.add_vertex(6) + print("\nAfter adding vertex 6, the graph is") + graph.print() + + # Remove vertex + # Index of vertex 3 is 1 + graph.remove_vertex(1) + print("\nAfter removing vertex 3, the graph is") + graph.print() diff --git a/en/codes/python/chapter_graph/graph_bfs.py b/en/codes/python/chapter_graph/graph_bfs.py new file mode 100644 index 0000000000..b1e73b9d38 --- /dev/null +++ b/en/codes/python/chapter_graph/graph_bfs.py @@ -0,0 +1,64 @@ +""" +File: graph_bfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets, vets_to_vals +from collections import deque +from graph_adjacency_list import GraphAdjList + + +def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """Breadth-first traversal""" + # Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex + # Vertex traversal sequence + res = [] + # Hash set, used to record visited vertices + visited = set[Vertex]([start_vet]) + # Queue used to implement BFS + que = deque[Vertex]([start_vet]) + # Starting from vertex vet, loop until all vertices are visited + while len(que) > 0: + vet = que.popleft() # Dequeue the vertex at the head of the queue + res.append(vet) # Record visited vertex + # Traverse all adjacent vertices of that vertex + for adj_vet in graph.adj_list[vet]: + if adj_vet in visited: + continue # Skip already visited vertices + que.append(adj_vet) # Only enqueue unvisited vertices + visited.add(adj_vet) # Mark the vertex as visited + # Return the vertex traversal sequence + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize undirected graph + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [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]], + ] + graph = GraphAdjList(edges) + print("\nAfter initialization, the graph is") + graph.print() + + # Breadth-first traversal + res = graph_bfs(graph, v[0]) + print("\nBreadth-first traversal (BFS) vertex sequence is") + print(vets_to_vals(res)) diff --git a/en/codes/python/chapter_graph/graph_dfs.py b/en/codes/python/chapter_graph/graph_dfs.py new file mode 100644 index 0000000000..de2b619e9a --- /dev/null +++ b/en/codes/python/chapter_graph/graph_dfs.py @@ -0,0 +1,57 @@ +""" +File: graph_dfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vets_to_vals, vals_to_vets +from graph_adjacency_list import GraphAdjList + + +def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """Depth-first traversal helper function""" + res.append(vet) # Record visited vertex + visited.add(vet) # Mark the vertex as visited + # Traverse all adjacent vertices of that vertex + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # Skip already visited vertices + # Recursively visit adjacent vertices + dfs(graph, visited, res, adjVet) + + +def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """Depth-first traversal""" + # Use adjacency list to represent the graph, to obtain all adjacent vertices of a specified vertex + # Vertex traversal sequence + res = [] + # Hash set, used to record visited vertices + visited = set[Vertex]() + dfs(graph, visited, res, start_vet) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize undirected graph + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList(edges) + print("\nAfter initialization, the graph is") + graph.print() + + # Depth-first traversal + res = graph_dfs(graph, v[0]) + print("\nDepth-first traversal (DFS) vertex sequence is") + print(vets_to_vals(res)) diff --git a/en/codes/python/chapter_greedy/coin_change_greedy.py b/en/codes/python/chapter_greedy/coin_change_greedy.py new file mode 100644 index 0000000000..9f3afb31a1 --- /dev/null +++ b/en/codes/python/chapter_greedy/coin_change_greedy.py @@ -0,0 +1,48 @@ +""" +File: coin_change_greedy.py +Created Time: 2023-07-18 +Author: krahets (krahets@163.com) +""" + + +def coin_change_greedy(coins: list[int], amt: int) -> int: + """Coin change: Greedy""" + # Assume coins list is ordered + i = len(coins) - 1 + count = 0 + # Loop for greedy selection until no remaining amount + while amt > 0: + # Find the smallest coin close to and less than the remaining amount + while i > 0 and coins[i] > amt: + i -= 1 + # Choose coins[i] + amt -= coins[i] + count += 1 + # If no feasible solution is found, return -1 + return count if amt == 0 else -1 + + +"""Driver Code""" +if __name__ == "__main__": + # Greedy: can ensure finding a global optimal solution + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"The minimum number of coins needed to make up {amt} is {res}") + + # Greedy: cannot ensure finding a global optimal solution + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"The minimum number of coins needed to make up {amt} is {res}") + print(f"In reality, the minimum number needed is 3, i.e., 20 + 20 + 20") + + # Greedy: cannot ensure finding a global optimal solution + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"The minimum number of coins needed to make up {amt} is {res}") + print(f"In reality, the minimum number needed is 2, i.e., 49 + 49") diff --git a/en/codes/python/chapter_greedy/fractional_knapsack.py b/en/codes/python/chapter_greedy/fractional_knapsack.py new file mode 100644 index 0000000000..96be870304 --- /dev/null +++ b/en/codes/python/chapter_greedy/fractional_knapsack.py @@ -0,0 +1,46 @@ +""" +File: fractional_knapsack.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + + +class Item: + """Item""" + + def __init__(self, w: int, v: int): + self.w = w # Item weight + self.v = v # Item value + + +def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: + """Fractional knapsack: Greedy""" + # Create an item list, containing two properties: weight, value + items = [Item(w, v) for w, v in zip(wgt, val)] + # Sort by unit value item.v / item.w from high to low + items.sort(key=lambda item: item.v / item.w, reverse=True) + # Loop for greedy selection + res = 0 + for item in items: + if item.w <= cap: + # If the remaining capacity is sufficient, put the entire item into the knapsack + res += item.v + cap -= item.w + else: + # If the remaining capacity is insufficient, put part of the item into the knapsack + res += (item.v / item.w) * cap + # No remaining capacity left, thus break the loop + break + return res + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # Greedy algorithm + res = fractional_knapsack(wgt, val, cap) + print(f"The maximum item value without exceeding knapsack capacity is {res}") diff --git a/en/codes/python/chapter_greedy/max_capacity.py b/en/codes/python/chapter_greedy/max_capacity.py new file mode 100644 index 0000000000..411d623570 --- /dev/null +++ b/en/codes/python/chapter_greedy/max_capacity.py @@ -0,0 +1,33 @@ +""" +File: max_capacity.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + + +def max_capacity(ht: list[int]) -> int: + """Maximum capacity: Greedy""" + # Initialize i, j, making them split the array at both ends + i, j = 0, len(ht) - 1 + # Initial maximum capacity is 0 + res = 0 + # Loop for greedy selection until the two boards meet + while i < j: + # Update maximum capacity + cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + # Move the shorter board inward + if ht[i] < ht[j]: + i += 1 + else: + j -= 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # Greedy algorithm + res = max_capacity(ht) + print(f"Maximum capacity is {res}") diff --git a/en/codes/python/chapter_greedy/max_product_cutting.py b/en/codes/python/chapter_greedy/max_product_cutting.py new file mode 100644 index 0000000000..923c953505 --- /dev/null +++ b/en/codes/python/chapter_greedy/max_product_cutting.py @@ -0,0 +1,33 @@ +""" +File: max_product_cutting.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + +import math + + +def max_product_cutting(n: int) -> int: + """Maximum product of cutting: Greedy""" + # When n <= 3, must cut out a 1 + if n <= 3: + return 1 * (n - 1) + # Greedy cut out 3s, a is the number of 3s, b is the remainder + a, b = n // 3, n % 3 + if b == 1: + # When the remainder is 1, convert a pair of 1 * 3 into 2 * 2 + return int(math.pow(3, a - 1)) * 2 * 2 + if b == 2: + # When the remainder is 2, do nothing + return int(math.pow(3, a)) * 2 + # When the remainder is 0, do nothing + return int(math.pow(3, a)) + + +"""Driver Code""" +if __name__ == "__main__": + n = 58 + + # Greedy algorithm + res = max_product_cutting(n) + print(f"Maximum product of cutting is {res}") diff --git a/en/codes/python/chapter_hashing/array_hash_map.py b/en/codes/python/chapter_hashing/array_hash_map.py new file mode 100644 index 0000000000..88daadf3aa --- /dev/null +++ b/en/codes/python/chapter_hashing/array_hash_map.py @@ -0,0 +1,117 @@ +""" +File: array_hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + + +class Pair: + """Key-value pair""" + + def __init__(self, key: int, val: str): + self.key = key + self.val = val + + +class ArrayHashMap: + """Hash table based on array implementation""" + + def __init__(self): + """Constructor""" + # Initialize an array, containing 100 buckets + self.buckets: list[Pair | None] = [None] * 100 + + def hash_func(self, key: int) -> int: + """Hash function""" + index = key % 100 + return index + + def get(self, key: int) -> str: + """Query operation""" + index: int = self.hash_func(key) + pair: Pair = self.buckets[index] + if pair is None: + return None + return pair.val + + def put(self, key: int, val: str): + """Add operation""" + pair = Pair(key, val) + index: int = self.hash_func(key) + self.buckets[index] = pair + + def remove(self, key: int): + """Remove operation""" + index: int = self.hash_func(key) + # Set to None, representing removal + self.buckets[index] = None + + def entry_set(self) -> list[Pair]: + """Get all key-value pairs""" + result: list[Pair] = [] + for pair in self.buckets: + if pair is not None: + result.append(pair) + return result + + def key_set(self) -> list[int]: + """Get all keys""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.key) + return result + + def value_set(self) -> list[str]: + """Get all values""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.val) + return result + + def print(self): + """Print hash table""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize hash table + hmap = ArrayHashMap() + + # Add operation + # Add key-value pair (key, value) to the hash table + hmap.put(12836, "Ha") + hmap.put(15937, "Luo") + hmap.put(16750, "Suan") + hmap.put(13276, "Fa") + hmap.put(10583, "Ya") + print("\nAfter adding, the hash table is\nKey -> Value") + hmap.print() + + # Query operation + # Enter key to the hash table, get value + name = hmap.get(15937) + print("\nEnter student ID 15937, found name " + name) + + # Remove operation + # Remove key-value pair (key, value) from the hash table + hmap.remove(10583) + print("\nAfter removing 10583, the hash table is\nKey -> Value") + hmap.print() + + # Traverse hash table + print("\nTraverse key-value pairs Key->Value") + for pair in hmap.entry_set(): + print(pair.key, "->", pair.val) + + print("\nIndividually traverse keys Key") + for key in hmap.key_set(): + print(key) + + print("\nIndividually traverse values Value") + for val in hmap.value_set(): + print(val) diff --git a/en/codes/python/chapter_hashing/built_in_hash.py b/en/codes/python/chapter_hashing/built_in_hash.py new file mode 100644 index 0000000000..323b6933c4 --- /dev/null +++ b/en/codes/python/chapter_hashing/built_in_hash.py @@ -0,0 +1,37 @@ +""" +File: built_in_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + +"""Driver Code""" +if __name__ == "__main__": + num = 3 + hash_num = hash(num) + print(f"Integer {num}'s hash value is {hash_num}") + + bol = True + hash_bol = hash(bol) + print(f"Boolean {bol}'s hash value is {hash_bol}") + + dec = 3.14159 + hash_dec = hash(dec) + print(f"Decimal {dec}'s hash value is {hash_dec}") + + str = "Hello algorithm" + hash_str = hash(str) + print(f"String {str}'s hash value is {hash_str}") + + tup = (12836, "Ha") + hash_tup = hash(tup) + print(f"Tuple {tup}'s hash value is {hash(hash_tup)}") + + obj = ListNode(0) + hash_obj = hash(obj) + print(f"Node object {obj}'s hash value is {hash_obj}") diff --git a/en/codes/python/chapter_hashing/hash_map.py b/en/codes/python/chapter_hashing/hash_map.py new file mode 100644 index 0000000000..e1f2b74620 --- /dev/null +++ b/en/codes/python/chapter_hashing/hash_map.py @@ -0,0 +1,50 @@ +""" +File: hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_dict + +"""Driver Code""" +if __name__ == "__main__": + # Initialize hash table + hmap = dict[int, str]() + + # Add operation + # Add key-value pair (key, value) to the hash table + hmap[12836] = "Ha" + hmap[15937] = "Luo" + hmap[16750] = "Suan" + hmap[13276] = "Fa" + hmap[10583] = "Ya" + print("\nAfter adding, the hash table is\nKey -> Value") + print_dict(hmap) + + # Query operation + # Enter key to the hash table, get value + name: str = hmap[15937] + print("\nEnter student ID 15937, found name " + name) + + # Remove operation + # Remove key-value pair (key, value) from the hash table + hmap.pop(10583) + print("\nAfter removing 10583, the hash table is\nKey -> Value") + print_dict(hmap) + + # Traverse hash table + print("\nTraverse key-value pairs Key->Value") + for key, value in hmap.items(): + print(key, "->", value) + + print("\nIndividually traverse keys Key") + for key in hmap.keys(): + print(key) + + print("\nIndividually traverse values Value") + for val in hmap.values(): + print(val) diff --git a/en/codes/python/chapter_hashing/hash_map_chaining.py b/en/codes/python/chapter_hashing/hash_map_chaining.py new file mode 100644 index 0000000000..f6e47c2eac --- /dev/null +++ b/en/codes/python/chapter_hashing/hash_map_chaining.py @@ -0,0 +1,118 @@ +""" +File: hash_map_chaining.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapChaining: + """Chained address hash table""" + + def __init__(self): + """Constructor""" + self.size = 0 # Number of key-value pairs + self.capacity = 4 # Hash table capacity + self.load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion + self.extend_ratio = 2 # Expansion multiplier + self.buckets = [[] for _ in range(self.capacity)] # Bucket array + + def hash_func(self, key: int) -> int: + """Hash function""" + return key % self.capacity + + def load_factor(self) -> float: + """Load factor""" + return self.size / self.capacity + + def get(self, key: int) -> str | None: + """Query operation""" + index = self.hash_func(key) + bucket = self.buckets[index] + # Traverse the bucket, if the key is found, return the corresponding val + for pair in bucket: + if pair.key == key: + return pair.val + # If the key is not found, return None + return None + + def put(self, key: int, val: str): + """Add operation""" + # When the load factor exceeds the threshold, perform expansion + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + bucket = self.buckets[index] + # Traverse the bucket, if the specified key is encountered, update the corresponding val and return + for pair in bucket: + if pair.key == key: + pair.val = val + return + # If the key is not found, add the key-value pair to the end + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 + + def remove(self, key: int): + """Remove operation""" + index = self.hash_func(key) + bucket = self.buckets[index] + # Traverse the bucket, remove the key-value pair from it + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + break + + def extend(self): + """Extend hash table""" + # Temporarily store the original hash table + buckets = self.buckets + # Initialize the extended new hash table + self.capacity *= self.extend_ratio + self.buckets = [[] for _ in range(self.capacity)] + self.size = 0 + # Move key-value pairs from the original hash table to the new hash table + for bucket in buckets: + for pair in bucket: + self.put(pair.key, pair.val) + + def print(self): + """Print hash table""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize hash table + hashmap = HashMapChaining() + + # Add operation + # Add key-value pair (key, value) to the hash table + hashmap.put(12836, "Ha") + hashmap.put(15937, "Luo") + hashmap.put(16750, "Suan") + hashmap.put(13276, "Fa") + hashmap.put(10583, "Ya") + print("\nAfter adding, the hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() + + # Query operation + # Enter key to the hash table, get value + name = hashmap.get(13276) + print("\nEnter student ID 13276, found name " + name) + + # Remove operation + # Remove key-value pair (key, value) from the hash table + hashmap.remove(12836) + print("\nAfter removing 12836, the hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() diff --git a/en/codes/python/chapter_hashing/hash_map_open_addressing.py b/en/codes/python/chapter_hashing/hash_map_open_addressing.py new file mode 100644 index 0000000000..9c9e7d14dd --- /dev/null +++ b/en/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -0,0 +1,138 @@ +""" +File: hash_map_open_addressing.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapOpenAddressing: + """Open addressing hash table""" + + def __init__(self): + """Constructor""" + self.size = 0 # Number of key-value pairs + self.capacity = 4 # Hash table capacity + self.load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion + self.extend_ratio = 2 # Expansion multiplier + self.buckets: list[Pair | None] = [None] * self.capacity # Bucket array + self.TOMBSTONE = Pair(-1, "-1") # Removal mark + + def hash_func(self, key: int) -> int: + """Hash function""" + return key % self.capacity + + def load_factor(self) -> float: + """Load factor""" + return self.size / self.capacity + + def find_bucket(self, key: int) -> int: + """Search for the bucket index corresponding to key""" + index = self.hash_func(key) + first_tombstone = -1 + # Linear probing, break when encountering an empty bucket + while self.buckets[index] is not None: + # If the key is encountered, return the corresponding bucket index + if self.buckets[index].key == key: + # If a removal mark was encountered earlier, move the key-value pair to that index + if first_tombstone != -1: + self.buckets[first_tombstone] = self.buckets[index] + self.buckets[index] = self.TOMBSTONE + return first_tombstone # Return the moved bucket index + return index # Return bucket index + # Record the first encountered removal mark + if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: + first_tombstone = index + # Calculate the bucket index, return to the head if exceeding the tail + index = (index + 1) % self.capacity + # If the key does not exist, return the index of the insertion point + return index if first_tombstone == -1 else first_tombstone + + def get(self, key: int) -> str: + """Query operation""" + # Search for the bucket index corresponding to key + index = self.find_bucket(key) + # If the key-value pair is found, return the corresponding val + if self.buckets[index] not in [None, self.TOMBSTONE]: + return self.buckets[index].val + # If the key-value pair does not exist, return None + return None + + def put(self, key: int, val: str): + """Add operation""" + # When the load factor exceeds the threshold, perform expansion + if self.load_factor() > self.load_thres: + self.extend() + # Search for the bucket index corresponding to key + index = self.find_bucket(key) + # If the key-value pair is found, overwrite val and return + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index].val = val + return + # If the key-value pair does not exist, add the key-value pair + self.buckets[index] = Pair(key, val) + self.size += 1 + + def remove(self, key: int): + """Remove operation""" + # Search for the bucket index corresponding to key + index = self.find_bucket(key) + # If the key-value pair is found, cover it with a removal mark + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index] = self.TOMBSTONE + self.size -= 1 + + def extend(self): + """Extend hash table""" + # Temporarily store the original hash table + buckets_tmp = self.buckets + # Initialize the extended new hash table + self.capacity *= self.extend_ratio + self.buckets = [None] * self.capacity + self.size = 0 + # Move key-value pairs from the original hash table to the new hash table + for pair in buckets_tmp: + if pair not in [None, self.TOMBSTONE]: + self.put(pair.key, pair.val) + + def print(self): + """Print hash table""" + for pair in self.buckets: + if pair is None: + print("None") + elif pair is self.TOMBSTONE: + print("TOMBSTONE") + else: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize hash table + hashmap = HashMapOpenAddressing() + + # Add operation + # Add key-value pair (key, val) to the hash table + hashmap.put(12836, "Ha") + hashmap.put(15937, "Luo") + hashmap.put(16750, "Suan") + hashmap.put(13276, "Fa") + hashmap.put(10583, "Ya") + print("\nAfter adding, the hash table is\nKey -> Value") + hashmap.print() + + # Query operation + # Enter key to the hash table, get value val + name = hashmap.get(13276) + print("\nEnter student ID 13276, found name " + name) + + # Remove operation + # Remove key-value pair (key, val) from the hash table + hashmap.remove(16750) + print("\nAfter removing 16750, the hash table is\nKey -> Value") + hashmap.print() diff --git a/en/codes/python/chapter_hashing/simple_hash.py b/en/codes/python/chapter_hashing/simple_hash.py new file mode 100644 index 0000000000..64614f558f --- /dev/null +++ b/en/codes/python/chapter_hashing/simple_hash.py @@ -0,0 +1,58 @@ +""" +File: simple_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + + +def add_hash(key: str) -> int: + """Additive hash""" + hash = 0 + modulus = 1000000007 + for c in key: + hash += ord(c) + return hash % modulus + + +def mul_hash(key: str) -> int: + """Multiplicative hash""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = 31 * hash + ord(c) + return hash % modulus + + +def xor_hash(key: str) -> int: + """XOR hash""" + hash = 0 + modulus = 1000000007 + for c in key: + hash ^= ord(c) + return hash % modulus + + +def rot_hash(key: str) -> int: + """Rotational hash""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = (hash << 4) ^ (hash >> 28) ^ ord(c) + return hash % modulus + + +"""Driver Code""" +if __name__ == "__main__": + key = "Hello algorithm" + + hash = add_hash(key) + print(f"Additive hash value is {hash}") + + hash = mul_hash(key) + print(f"Multiplicative hash value is {hash}") + + hash = xor_hash(key) + print(f"XOR hash value is {hash}") + + hash = rot_hash(key) + print(f"Rotational hash value is {hash}") diff --git a/en/codes/python/chapter_heap/heap.py b/en/codes/python/chapter_heap/heap.py new file mode 100644 index 0000000000..247e3cb46b --- /dev/null +++ b/en/codes/python/chapter_heap/heap.py @@ -0,0 +1,71 @@ +""" +File: heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def test_push(heap: list, val: int, flag: int = 1): + heapq.heappush(heap, flag * val) # Push the element into heap + print(f"\nElement {val} after pushed into heap") + print_heap([flag * val for val in heap]) + + +def test_pop(heap: list, flag: int = 1): + val = flag * heapq.heappop(heap) # Pop the element at the heap top + print(f"\nHeap top element {val} after exiting heap") + print_heap([flag * val for val in heap]) + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize min-heap + min_heap, flag = [], 1 + # Initialize max-heap + max_heap, flag = [], -1 + + print("\nThe following test case is for max-heap") + # Python's heapq module implements min-heap by default + # Consider "negating the elements" before entering the heap, thus reversing the comparator to implement a max-heap + # In this example, flag = 1 corresponds to min-heap, flag = -1 corresponds to max-heap + + # Push the element into heap + test_push(max_heap, 1, flag) + test_push(max_heap, 3, flag) + test_push(max_heap, 2, flag) + test_push(max_heap, 5, flag) + test_push(max_heap, 4, flag) + + # Access heap top element + peek: int = flag * max_heap[0] + print(f"\nHeap top element is {peek}") + + # Pop the element at the heap top + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + # Get heap size + size: int = len(max_heap) + print(f"\nNumber of heap elements is {size}") + + # Determine if heap is empty + is_empty: bool = not max_heap + print(f"\nIs the heap empty {is_empty}") + + # Enter list and build heap + # Time complexity is O(n), not O(nlogn) + min_heap = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + print("\nEnter list and build min-heap") + print_heap(min_heap) diff --git a/en/codes/python/chapter_heap/my_heap.py b/en/codes/python/chapter_heap/my_heap.py new file mode 100644 index 0000000000..bb78d94937 --- /dev/null +++ b/en/codes/python/chapter_heap/my_heap.py @@ -0,0 +1,137 @@ +""" +File: my_heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + + +class MaxHeap: + """Max-heap""" + + def __init__(self, nums: list[int]): + """Constructor, build heap based on input list""" + # Add all list elements into the heap + self.max_heap = nums + # Heapify all nodes except leaves + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) + + def left(self, i: int) -> int: + """Get index of left child node""" + return 2 * i + 1 + + def right(self, i: int) -> int: + """Get index of right child node""" + return 2 * i + 2 + + def parent(self, i: int) -> int: + """Get index of parent node""" + return (i - 1) // 2 # Integer division down + + def swap(self, i: int, j: int): + """Swap elements""" + self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] + + def size(self) -> int: + """Get heap size""" + return len(self.max_heap) + + def is_empty(self) -> bool: + """Determine if heap is empty""" + return self.size() == 0 + + def peek(self) -> int: + """Access heap top element""" + return self.max_heap[0] + + def push(self, val: int): + """Push the element into heap""" + # Add node + self.max_heap.append(val) + # Heapify from bottom to top + self.sift_up(self.size() - 1) + + def sift_up(self, i: int): + """Start heapifying node i, from bottom to top""" + while True: + # Get parent node of node i + p = self.parent(i) + # When "crossing the root node" or "node does not need repair", end heapification + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # Swap two nodes + self.swap(i, p) + # Loop upwards heapification + i = p + + def pop(self) -> int: + """Element exits heap""" + # Empty handling + if self.is_empty(): + raise IndexError("Heap is empty") + # Swap the root node with the rightmost leaf node (swap the first element with the last element) + self.swap(0, self.size() - 1) + # Remove node + val = self.max_heap.pop() + # Heapify from top to bottom + self.sift_down(0) + # Return heap top element + return val + + def sift_down(self, i: int): + """Start heapifying node i, from top to bottom""" + while True: + # Determine the largest node among i, l, r, noted as ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # If node i is the largest or indices l, r are out of bounds, no further heapification needed, break + if ma == i: + break + # Swap two nodes + self.swap(i, ma) + # Loop downwards heapification + i = ma + + def print(self): + """Print heap (binary tree)""" + print_heap(self.max_heap) + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize max-heap + max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\nEnter list and build heap") + max_heap.print() + + # Access heap top element + peek = max_heap.peek() + print(f"\nHeap top element is {peek}") + + # Push the element into heap + val = 7 + max_heap.push(val) + print(f"\nElement {val} after pushed into heap") + max_heap.print() + + # Pop the element at the heap top + peek = max_heap.pop() + print(f"\nHeap top element {peek} after exiting heap") + max_heap.print() + + # Get heap size + size = max_heap.size() + print(f"\nNumber of heap elements is {size}") + + # Determine if heap is empty + is_empty = max_heap.is_empty() + print(f"\nIs the heap empty {is_empty}") diff --git a/en/codes/python/chapter_heap/top_k.py b/en/codes/python/chapter_heap/top_k.py new file mode 100644 index 0000000000..804c297513 --- /dev/null +++ b/en/codes/python/chapter_heap/top_k.py @@ -0,0 +1,39 @@ +""" +File: top_k.py +Created Time: 2023-06-10 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def top_k_heap(nums: list[int], k: int) -> list[int]: + """Using heap to find the largest k elements in an array""" + # Initialize min-heap + heap = [] + # Enter the first k elements of the array into the heap + for i in range(k): + heapq.heappush(heap, nums[i]) + # From the k+1th element, keep the heap length as k + for i in range(k, len(nums)): + # If the current element is larger than the heap top element, remove the heap top element and enter the current element into the heap + if nums[i] > heap[0]: + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + return heap + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + print(f"The largest {k} elements are") + print_heap(res) diff --git a/en/codes/python/chapter_searching/binary_search.py b/en/codes/python/chapter_searching/binary_search.py new file mode 100644 index 0000000000..ec40fa69c3 --- /dev/null +++ b/en/codes/python/chapter_searching/binary_search.py @@ -0,0 +1,52 @@ +""" +File: binary_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + + +def binary_search(nums: list[int], target: int) -> int: + """Binary search (double closed interval)""" + # Initialize double closed interval [0, n-1], i.e., i, j point to the first element and last element of the array respectively + i, j = 0, len(nums) - 1 + # Loop until the search interval is empty (when i > j, it is empty) + while i <= j: + # Theoretically, Python's numbers can be infinitely large (depending on memory size), so there is no need to consider large number overflow + m = i + (j - i) // 2 # Calculate midpoint index m + if nums[m] < target: + i = m + 1 # This situation indicates that target is in the interval [m+1, j] + elif nums[m] > target: + j = m - 1 # This situation indicates that target is in the interval [i, m-1] + else: + return m # Found the target element, thus return its index + return -1 # Did not find the target element, thus return -1 + + +def binary_search_lcro(nums: list[int], target: int) -> int: + """Binary search (left closed right open interval)""" + # Initialize left closed right open interval [0, n), i.e., i, j point to the first element and the last element +1 of the array respectively + i, j = 0, len(nums) + # Loop until the search interval is empty (when i = j, it is empty) + while i < j: + m = i + (j - i) // 2 # Calculate midpoint index m + if nums[m] < target: + i = m + 1 # This situation indicates that target is in the interval [m+1, j) + elif nums[m] > target: + j = m # This situation indicates that target is in the interval [i, m) + else: + return m # Found the target element, thus return its index + return -1 # Did not find the target element, thus return -1 + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # Binary search (double closed interval) + index = binary_search(nums, target) + print("Index of target element 6 =", index) + + # Binary search (left closed right open interval) + index = binary_search_lcro(nums, target) + print("Index of target element 6 =", index) diff --git a/en/codes/python/chapter_searching/binary_search_edge.py b/en/codes/python/chapter_searching/binary_search_edge.py new file mode 100644 index 0000000000..9edf577d1a --- /dev/null +++ b/en/codes/python/chapter_searching/binary_search_edge.py @@ -0,0 +1,49 @@ +""" +File: binary_search_edge.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from binary_search_insertion import binary_search_insertion + + +def binary_search_left_edge(nums: list[int], target: int) -> int: + """Binary search for the leftmost target""" + # Equivalent to finding the insertion point of target + i = binary_search_insertion(nums, target) + # Did not find target, thus return -1 + if i == len(nums) or nums[i] != target: + return -1 + # Found target, return index i + return i + + +def binary_search_right_edge(nums: list[int], target: int) -> int: + """Binary search for the rightmost target""" + # Convert to finding the leftmost target + 1 + i = binary_search_insertion(nums, target + 1) + # j points to the rightmost target, i points to the first element greater than target + j = i - 1 + # Did not find target, thus return -1 + if j == -1 or nums[j] != target: + return -1 + # Found target, return index j + return j + + +"""Driver Code""" +if __name__ == "__main__": + # Array with duplicate elements + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\nArray nums = {nums}") + + # Binary search for left and right boundaries + for target in [6, 7]: + index = binary_search_left_edge(nums, target) + print(f"The index of the leftmost element {target} is {index}") + index = binary_search_right_edge(nums, target) + print(f"The index of the rightmost element {target} is {index}") diff --git a/en/codes/python/chapter_searching/binary_search_insertion.py b/en/codes/python/chapter_searching/binary_search_insertion.py new file mode 100644 index 0000000000..c532bc0ebd --- /dev/null +++ b/en/codes/python/chapter_searching/binary_search_insertion.py @@ -0,0 +1,54 @@ +""" +File: binary_search_insertion.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + + +def binary_search_insertion_simple(nums: list[int], target: int) -> int: + """Binary search for insertion point (no duplicate elements)""" + i, j = 0, len(nums) - 1 # Initialize double closed interval [0, n-1] + while i <= j: + m = i + (j - i) // 2 # Calculate midpoint index m + if nums[m] < target: + i = m + 1 # Target is in interval [m+1, j] + elif nums[m] > target: + j = m - 1 # Target is in interval [i, m-1] + else: + return m # Found target, return insertion point m + # Did not find target, return insertion point i + return i + + +def binary_search_insertion(nums: list[int], target: int) -> int: + """Binary search for insertion point (with duplicate elements)""" + i, j = 0, len(nums) - 1 # Initialize double closed interval [0, n-1] + while i <= j: + m = i + (j - i) // 2 # Calculate midpoint index m + if nums[m] < target: + i = m + 1 # Target is in interval [m+1, j] + elif nums[m] > target: + j = m - 1 # Target is in interval [i, m-1] + else: + j = m - 1 # First element less than target is in interval [i, m-1] + # Return insertion point i + return i + + +"""Driver Code""" +if __name__ == "__main__": + # Array without duplicate elements + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print(f"\nArray nums = {nums}") + # Binary search for insertion point + for target in [6, 9]: + index = binary_search_insertion_simple(nums, target) + print(f"Element {target}'s insertion point index is {index}") + + # Array with duplicate elements + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\nArray nums = {nums}") + # Binary search for insertion point + for target in [2, 6, 20]: + index = binary_search_insertion(nums, target) + print(f"Element {target}'s insertion point index is {index}") diff --git a/en/codes/python/chapter_searching/hashing_search.py b/en/codes/python/chapter_searching/hashing_search.py new file mode 100644 index 0000000000..835240639e --- /dev/null +++ b/en/codes/python/chapter_searching/hashing_search.py @@ -0,0 +1,51 @@ +""" +File: hashing_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def hashing_search_array(hmap: dict[int, int], target: int) -> int: + """Hash search (array)""" + # Hash table's key: target element, value: index + # If the hash table does not contain this key, return -1 + return hmap.get(target, -1) + + +def hashing_search_linkedlist( + hmap: dict[int, ListNode], target: int +) -> ListNode | None: + """Hash search (linked list)""" + # Hash table's key: target element, value: node object + # If the hash table does not contain this key, return None + return hmap.get(target, None) + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # Hash search (array) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # Initialize hash table + map0 = dict[int, int]() + for i in range(len(nums)): + map0[nums[i]] = i # key: element, value: index + index: int = hashing_search_array(map0, target) + print("Index of target element 3 =", index) + + # Hash search (linked list) + head: ListNode = list_to_linked_list(nums) + # Initialize hash table + map1 = dict[int, ListNode]() + while head: + map1[head.val] = head # key: node value, value: node + head = head.next + node: ListNode = hashing_search_linkedlist(map1, target) + print("Target node value 3's corresponding node object is", node) diff --git a/en/codes/python/chapter_searching/linear_search.py b/en/codes/python/chapter_searching/linear_search.py new file mode 100644 index 0000000000..84bd83301a --- /dev/null +++ b/en/codes/python/chapter_searching/linear_search.py @@ -0,0 +1,45 @@ +""" +File: linear_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def linear_search_array(nums: list[int], target: int) -> int: + """Linear search (array)""" + # Traverse array + for i in range(len(nums)): + if nums[i] == target: # Found the target element, thus return its index + return i + return -1 # Did not find the target element, thus return -1 + + +def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: + """Linear search (linked list)""" + # Traverse the list + while head: + if head.val == target: # Found the target node, return it + return head + head = head.next + return None # Did not find the target node, thus return None + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # Perform linear search in array + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index: int = linear_search_array(nums, target) + print("Index of target element 3 =", index) + + # Perform linear search in linked list + head: ListNode = list_to_linked_list(nums) + node: ListNode | None = linear_search_linkedlist(head, target) + print("Target node value 3's corresponding node object is", node) diff --git a/en/codes/python/chapter_searching/two_sum.py b/en/codes/python/chapter_searching/two_sum.py new file mode 100644 index 0000000000..52b34e4608 --- /dev/null +++ b/en/codes/python/chapter_searching/two_sum.py @@ -0,0 +1,42 @@ +""" +File: two_sum.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def two_sum_brute_force(nums: list[int], target: int) -> list[int]: + """Method one: Brute force enumeration""" + # Two-layer loop, time complexity is O(n^2) + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return [i, j] + return [] + + +def two_sum_hash_table(nums: list[int], target: int) -> list[int]: + """Method two: Auxiliary hash table""" + # Auxiliary hash table, space complexity is O(n) + dic = {} + # Single-layer loop, time complexity is O(n) + for i in range(len(nums)): + if target - nums[i] in dic: + return [dic[target - nums[i]], i] + dic[nums[i]] = i + return [] + + +"""Driver Code""" +if __name__ == "__main__": + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Driver Code ====== + # Method one + res: list[int] = two_sum_brute_force(nums, target) + print("Method one res =", res) + # Method two + res: list[int] = two_sum_hash_table(nums, target) + print("Method two res =", res) diff --git a/en/codes/python/chapter_sorting/bubble_sort.py b/en/codes/python/chapter_sorting/bubble_sort.py new file mode 100644 index 0000000000..3e2d2766c6 --- /dev/null +++ b/en/codes/python/chapter_sorting/bubble_sort.py @@ -0,0 +1,44 @@ +""" +File: bubble_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def bubble_sort(nums: list[int]): + """Bubble sort""" + n = len(nums) + # Outer loop: unsorted range is [0, i] + for i in range(n - 1, 0, -1): + # Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for j in range(i): + if nums[j] > nums[j + 1]: + # Swap nums[j] and nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + + +def bubble_sort_with_flag(nums: list[int]): + """Bubble sort (optimized with flag)""" + n = len(nums) + # Outer loop: unsorted range is [0, i] + for i in range(n - 1, 0, -1): + flag = False # Initialize flag + # Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range + for j in range(i): + if nums[j] > nums[j + 1]: + # Swap nums[j] and nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = True # Record swapped elements + if not flag: + break # If no elements were swapped in this round of "bubbling", exit + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + print("Bubble sort completed nums =", nums) + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + print("Bubble sort completed nums =", nums1) diff --git a/en/codes/python/chapter_sorting/bucket_sort.py b/en/codes/python/chapter_sorting/bucket_sort.py new file mode 100644 index 0000000000..6bcb71aca0 --- /dev/null +++ b/en/codes/python/chapter_sorting/bucket_sort.py @@ -0,0 +1,35 @@ +""" +File: bucket_sort.py +Created Time: 2023-03-30 +Author: krahets (krahets@163.com) +""" + + +def bucket_sort(nums: list[float]): + """Bucket sort""" + # Initialize k = n/2 buckets, expected to allocate 2 elements per bucket + k = len(nums) // 2 + buckets = [[] for _ in range(k)] + # 1. Distribute array elements into various buckets + for num in nums: + # Input data range is [0, 1), use num * k to map to index range [0, k-1] + i = int(num * k) + # Add num to bucket i + buckets[i].append(num) + # 2. Sort each bucket + for bucket in buckets: + # Use built-in sorting function, can also replace with other sorting algorithms + bucket.sort() + # 3. Traverse buckets to merge results + i = 0 + for bucket in buckets: + for num in bucket: + nums[i] = num + i += 1 + + +if __name__ == "__main__": + # Assume input data is floating point, range [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + print("Bucket sort completed nums =", nums) diff --git a/en/codes/python/chapter_sorting/counting_sort.py b/en/codes/python/chapter_sorting/counting_sort.py new file mode 100644 index 0000000000..5302d2a79f --- /dev/null +++ b/en/codes/python/chapter_sorting/counting_sort.py @@ -0,0 +1,64 @@ +""" +File: counting_sort.py +Created Time: 2023-03-21 +Author: krahets (krahets@163.com) +""" + + +def counting_sort_naive(nums: list[int]): + """Counting sort""" + # Simple implementation, cannot be used for sorting objects + # 1. Count the maximum element m in the array + m = 0 + for num in nums: + m = max(m, num) + # 2. Count the occurrence of each digit + # counter[num] represents the occurrence of num + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. Traverse counter, filling each element back into the original array nums + i = 0 + for num in range(m + 1): + for _ in range(counter[num]): + nums[i] = num + i += 1 + + +def counting_sort(nums: list[int]): + """Counting sort""" + # Complete implementation, can sort objects and is a stable sort + # 1. Count the maximum element m in the array + m = max(nums) + # 2. Count the occurrence of each digit + # counter[num] represents the occurrence of num + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index" + # counter[num]-1 is the last index where num appears in res + for i in range(m): + counter[i + 1] += counter[i] + # 4. Traverse nums in reverse order, placing each element into the result array res + # Initialize the array res to record results + n = len(nums) + res = [0] * n + for i in range(n - 1, -1, -1): + num = nums[i] + res[counter[num] - 1] = num # Place num at the corresponding index + counter[num] -= 1 # Decrement the prefix sum by 1, getting the next index to place num + # Use result array res to overwrite the original array nums + for i in range(n): + nums[i] = res[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + print(f"Counting sort (unable to sort objects) completed nums = {nums}") + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + print(f"Counting sort completed nums1 = {nums1}") diff --git a/en/codes/python/chapter_sorting/heap_sort.py b/en/codes/python/chapter_sorting/heap_sort.py new file mode 100644 index 0000000000..f3429785b7 --- /dev/null +++ b/en/codes/python/chapter_sorting/heap_sort.py @@ -0,0 +1,45 @@ +""" +File: heap_sort.py +Created Time: 2023-05-24 +Author: krahets (krahets@163.com) +""" + + +def sift_down(nums: list[int], n: int, i: int): + """Heap length is n, start heapifying node i, from top to bottom""" + while True: + # Determine the largest node among i, l, r, noted as ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + if l < n and nums[l] > nums[ma]: + ma = l + if r < n and nums[r] > nums[ma]: + ma = r + # If node i is the largest or indices l, r are out of bounds, no further heapification needed, break + if ma == i: + break + # Swap two nodes + nums[i], nums[ma] = nums[ma], nums[i] + # Loop downwards heapification + i = ma + + +def heap_sort(nums: list[int]): + """Heap sort""" + # Build heap operation: heapify all nodes except leaves + for i in range(len(nums) // 2 - 1, -1, -1): + sift_down(nums, len(nums), i) + # Extract the largest element from the heap and repeat for n-1 rounds + for i in range(len(nums) - 1, 0, -1): + # Swap the root node with the rightmost leaf node (swap the first element with the last element) + nums[0], nums[i] = nums[i], nums[0] + # Start heapifying the root node, from top to bottom + sift_down(nums, i, 0) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + print("Heap sort completed nums =", nums) diff --git a/en/codes/python/chapter_sorting/insertion_sort.py b/en/codes/python/chapter_sorting/insertion_sort.py new file mode 100644 index 0000000000..6ef4ace857 --- /dev/null +++ b/en/codes/python/chapter_sorting/insertion_sort.py @@ -0,0 +1,25 @@ +""" +File: insertion_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def insertion_sort(nums: list[int]): + """Insertion sort""" + # Outer loop: sorted range is [0, i-1] + for i in range(1, len(nums)): + base = nums[i] + j = i - 1 + # Inner loop: insert base into the correct position within the sorted range [0, i-1] + while j >= 0 and nums[j] > base: + nums[j + 1] = nums[j] # Move nums[j] to the right by one position + j -= 1 + nums[j + 1] = base # Assign base to the correct position + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + insertion_sort(nums) + print("Insertion sort completed nums =", nums) diff --git a/en/codes/python/chapter_sorting/merge_sort.py b/en/codes/python/chapter_sorting/merge_sort.py new file mode 100644 index 0000000000..863618844c --- /dev/null +++ b/en/codes/python/chapter_sorting/merge_sort.py @@ -0,0 +1,55 @@ +""" +File: merge_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com), krahets (krahets@163.com) +""" + + +def merge(nums: list[int], left: int, mid: int, right: int): + """Merge left subarray and right subarray""" + # Left subarray interval is [left, mid], right subarray interval is [mid+1, right] + # Create a temporary array tmp to store the merged results + tmp = [0] * (right - left + 1) + # Initialize the start indices of the left and right subarrays + i, j, k = left, mid + 1, 0 + # While both subarrays still have elements, compare and copy the smaller element into the temporary array + while i <= mid and j <= right: + if nums[i] <= nums[j]: + tmp[k] = nums[i] + i += 1 + else: + tmp[k] = nums[j] + j += 1 + k += 1 + # Copy the remaining elements of the left and right subarrays into the temporary array + while i <= mid: + tmp[k] = nums[i] + i += 1 + k += 1 + while j <= right: + tmp[k] = nums[j] + j += 1 + k += 1 + # Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval + for k in range(0, len(tmp)): + nums[left + k] = tmp[k] + + +def merge_sort(nums: list[int], left: int, right: int): + """Merge sort""" + # Termination condition + if left >= right: + return # Terminate recursion when subarray length is 1 + # Partition stage + mid = left + (right - left) // 2 # Calculate midpoint + merge_sort(nums, left, mid) # Recursively process the left subarray + merge_sort(nums, mid + 1, right) # Recursively process the right subarray + # Merge stage + merge(nums, left, mid, right) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, len(nums) - 1) + print("Merge sort completed nums =", nums) diff --git a/en/codes/python/chapter_sorting/quick_sort.py b/en/codes/python/chapter_sorting/quick_sort.py new file mode 100644 index 0000000000..652fb3cba8 --- /dev/null +++ b/en/codes/python/chapter_sorting/quick_sort.py @@ -0,0 +1,129 @@ +""" +File: quick_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +class QuickSort: + """Quick sort class""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """Partition""" + # Use nums[left] as the pivot + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # Search from right to left for the first element smaller than the pivot + while i < j and nums[i] <= nums[left]: + i += 1 # Search from left to right for the first element greater than the pivot + # Swap elements + nums[i], nums[j] = nums[j], nums[i] + # Swap the pivot to the boundary between the two subarrays + nums[i], nums[left] = nums[left], nums[i] + return i # Return the index of the pivot + + def quick_sort(self, nums: list[int], left: int, right: int): + """Quick sort""" + # Terminate recursion when subarray length is 1 + if left >= right: + return + # Partition + pivot = self.partition(nums, left, right) + # Recursively process the left subarray and right subarray + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortMedian: + """Quick sort class (median pivot optimization)""" + + def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: + """Select the median of three candidate elements""" + l, m, r = nums[left], nums[mid], nums[right] + if (l <= m <= r) or (r <= m <= l): + return mid # m is between l and r + if (m <= l <= r) or (r <= l <= m): + return left # l is between m and r + return right + + def partition(self, nums: list[int], left: int, right: int) -> int: + """Partition (median of three)""" + # Use nums[left] as the pivot + med = self.median_three(nums, left, (left + right) // 2, right) + # Swap the median to the array's leftmost position + nums[left], nums[med] = nums[med], nums[left] + # Use nums[left] as the pivot + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # Search from right to left for the first element smaller than the pivot + while i < j and nums[i] <= nums[left]: + i += 1 # Search from left to right for the first element greater than the pivot + # Swap elements + nums[i], nums[j] = nums[j], nums[i] + # Swap the pivot to the boundary between the two subarrays + nums[i], nums[left] = nums[left], nums[i] + return i # Return the index of the pivot + + def quick_sort(self, nums: list[int], left: int, right: int): + """Quick sort""" + # Terminate recursion when subarray length is 1 + if left >= right: + return + # Partition + pivot = self.partition(nums, left, right) + # Recursively process the left subarray and right subarray + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortTailCall: + """Quick sort class (tail recursion optimization)""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """Partition""" + # Use nums[left] as the pivot + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # Search from right to left for the first element smaller than the pivot + while i < j and nums[i] <= nums[left]: + i += 1 # Search from left to right for the first element greater than the pivot + # Swap elements + nums[i], nums[j] = nums[j], nums[i] + # Swap the pivot to the boundary between the two subarrays + nums[i], nums[left] = nums[left], nums[i] + return i # Return the index of the pivot + + def quick_sort(self, nums: list[int], left: int, right: int): + """Quick sort (tail recursion optimization)""" + # Terminate when subarray length is 1 + while left < right: + # Partition operation + pivot = self.partition(nums, left, right) + # Perform quick sort on the shorter of the two subarrays + if pivot - left < right - pivot: + self.quick_sort(nums, left, pivot - 1) # Recursively sort the left subarray + left = pivot + 1 # Remaining unsorted interval is [pivot + 1, right] + else: + self.quick_sort(nums, pivot + 1, right) # Recursively sort the right subarray + right = pivot - 1 # Remaining unsorted interval is [left, pivot - 1] + + +"""Driver Code""" +if __name__ == "__main__": + # Quick sort + nums = [2, 4, 1, 0, 3, 5] + QuickSort().quick_sort(nums, 0, len(nums) - 1) + print("Quick sort completed nums =", nums) + + # Quick sort (median pivot optimization) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) + print("Quick sort (median pivot optimization) completed nums =", nums1) + + # Quick sort (tail recursion optimization) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1) + print("Quick sort (tail recursion optimization) completed nums =", nums2) diff --git a/en/codes/python/chapter_sorting/radix_sort.py b/en/codes/python/chapter_sorting/radix_sort.py new file mode 100644 index 0000000000..ce80caf97a --- /dev/null +++ b/en/codes/python/chapter_sorting/radix_sort.py @@ -0,0 +1,69 @@ +""" +File: radix_sort.py +Created Time: 2023-03-26 +Author: krahets (krahets@163.com) +""" + + +def digit(num: int, exp: int) -> int: + """Get the k-th digit of element num, where exp = 10^(k-1)""" + # Passing exp instead of k can avoid repeated expensive exponentiation here + return (num // exp) % 10 + + +def counting_sort_digit(nums: list[int], exp: int): + """Counting sort (based on nums k-th digit)""" + # Decimal digit range is 0~9, therefore need a bucket array of length 10 + counter = [0] * 10 + n = len(nums) + # Count the occurrence of digits 0~9 + for i in range(n): + d = digit(nums[i], exp) # Get the k-th digit of nums[i], noted as d + counter[d] += 1 # Count the occurrence of digit d + # Calculate prefix sum, converting "occurrence count" into "array index" + for i in range(1, 10): + counter[i] += counter[i - 1] + # Traverse in reverse, based on bucket statistics, place each element into res + res = [0] * n + for i in range(n - 1, -1, -1): + d = digit(nums[i], exp) + j = counter[d] - 1 # Get the index j for d in the array + res[j] = nums[i] # Place the current element at index j + counter[d] -= 1 # Decrease the count of d by 1 + # Use result to overwrite the original array nums + for i in range(n): + nums[i] = res[i] + + +def radix_sort(nums: list[int]): + """Radix sort""" + # Get the maximum element of the array, used to determine the maximum number of digits + m = max(nums) + # Traverse from the lowest to the highest digit + exp = 1 + while exp <= m: + # Perform counting sort on the k-th digit of array elements + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # i.e., exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + + +"""Driver Code""" +if __name__ == "__main__": + # Radix sort + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + print("Radix sort completed nums =", nums) diff --git a/en/codes/python/chapter_sorting/selection_sort.py b/en/codes/python/chapter_sorting/selection_sort.py new file mode 100644 index 0000000000..e4fffa3cad --- /dev/null +++ b/en/codes/python/chapter_sorting/selection_sort.py @@ -0,0 +1,26 @@ +""" +File: selection_sort.py +Created Time: 2023-05-22 +Author: krahets (krahets@163.com) +""" + + +def selection_sort(nums: list[int]): + """Selection sort""" + n = len(nums) + # Outer loop: unsorted range is [i, n-1] + for i in range(n - 1): + # Inner loop: find the smallest element within the unsorted range + k = i + for j in range(i + 1, n): + if nums[j] < nums[k]: + k = j # Record the index of the smallest element + # Swap the smallest element with the first element of the unsorted range + nums[i], nums[k] = nums[k], nums[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + print("Selection sort completed nums =", nums) diff --git a/en/codes/python/chapter_stack_and_queue/array_deque.py b/en/codes/python/chapter_stack_and_queue/array_deque.py new file mode 100644 index 0000000000..270a56e6d4 --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/array_deque.py @@ -0,0 +1,129 @@ +""" +File: array_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ArrayDeque: + """Double-ended queue class based on circular array""" + + def __init__(self, capacity: int): + """Constructor""" + self._nums: list[int] = [0] * capacity + self._front: int = 0 + self._size: int = 0 + + def capacity(self) -> int: + """Get the capacity of the double-ended queue""" + return len(self._nums) + + def size(self) -> int: + """Get the length of the double-ended queue""" + return self._size + + def is_empty(self) -> bool: + """Determine if the double-ended queue is empty""" + return self._size == 0 + + def index(self, i: int) -> int: + """Calculate circular array index""" + # Implement circular array by modulo operation + # When i exceeds the tail of the array, return to the head + # When i exceeds the head of the array, return to the tail + return (i + self.capacity()) % self.capacity() + + def push_first(self, num: int): + """Front enqueue""" + if self._size == self.capacity(): + print("Double-ended queue is full") + return + # Move the front pointer one position to the left + # Implement front crossing the head of the array to return to the tail by modulo operation + self._front = self.index(self._front - 1) + # Add num to the front + self._nums[self._front] = num + self._size += 1 + + def push_last(self, num: int): + """Rear enqueue""" + if self._size == self.capacity(): + print("Double-ended queue is full") + return + # Calculate rear pointer, pointing to rear index + 1 + rear = self.index(self._front + self._size) + # Add num to the rear + self._nums[rear] = num + self._size += 1 + + def pop_first(self) -> int: + """Front dequeue""" + num = self.peek_first() + # Move front pointer one position backward + self._front = self.index(self._front + 1) + self._size -= 1 + return num + + def pop_last(self) -> int: + """Rear dequeue""" + num = self.peek_last() + self._size -= 1 + return num + + def peek_first(self) -> int: + """Access front element""" + if self.is_empty(): + raise IndexError("Double-ended queue is empty") + return self._nums[self._front] + + def peek_last(self) -> int: + """Access rear element""" + if self.is_empty(): + raise IndexError("Double-ended queue is empty") + # Calculate rear element index + last = self.index(self._front + self._size - 1) + return self._nums[last] + + def to_array(self) -> list[int]: + """Return array for printing""" + # Only convert elements within valid length range + res = [] + for i in range(self._size): + res.append(self._nums[self.index(self._front + i)]) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize double-ended queue + deque = ArrayDeque(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("Double-ended queue deque =", deque.to_array()) + + # Access element + peek_first: int = deque.peek_first() + print("Front element peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("Rear element peek_last =", peek_last) + + # Element enqueue + deque.push_last(4) + print("Element 4 rear enqueued, deque =", deque.to_array()) + deque.push_first(1) + print("Element 1 front enqueued, deque =", deque.to_array()) + + # Element dequeue + pop_last: int = deque.pop_last() + print("Rear dequeued element =", pop_last, ", deque after rear dequeue =", deque.to_array()) + pop_first: int = deque.pop_first() + print("Front dequeued element =", pop_first, ", deque after front dequeue =", deque.to_array()) + + # Get the length of the double-ended queue + size: int = deque.size() + print("Double-ended queue length size =", size) + + # Determine if the double-ended queue is empty + is_empty: bool = deque.is_empty() + print("Is the double-ended queue empty =", is_empty) diff --git a/en/codes/python/chapter_stack_and_queue/array_queue.py b/en/codes/python/chapter_stack_and_queue/array_queue.py new file mode 100644 index 0000000000..25f6356aaf --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/array_queue.py @@ -0,0 +1,98 @@ +""" +File: array_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayQueue: + """Queue class based on circular array""" + + def __init__(self, size: int): + """Constructor""" + self._nums: list[int] = [0] * size # Array for storing queue elements + self._front: int = 0 # Front pointer, pointing to the front element + self._size: int = 0 # Queue length + + def capacity(self) -> int: + """Get the capacity of the queue""" + return len(self._nums) + + def size(self) -> int: + """Get the length of the queue""" + return self._size + + def is_empty(self) -> bool: + """Determine if the queue is empty""" + return self._size == 0 + + def push(self, num: int): + """Enqueue""" + if self._size == self.capacity(): + raise IndexError("Queue is full") + # Calculate rear pointer, pointing to rear index + 1 + # Use modulo operation to wrap the rear pointer from the end of the array back to the start + rear: int = (self._front + self._size) % self.capacity() + # Add num to the rear + self._nums[rear] = num + self._size += 1 + + def pop(self) -> int: + """Dequeue""" + num: int = self.peek() + # Move front pointer one position backward, returning to the head of the array if it exceeds the tail + self._front = (self._front + 1) % self.capacity() + self._size -= 1 + return num + + def peek(self) -> int: + """Access front element""" + if self.is_empty(): + raise IndexError("Queue is empty") + return self._nums[self._front] + + def to_list(self) -> list[int]: + """Return array for printing""" + res = [0] * self.size() + j: int = self._front + for i in range(self.size()): + res[i] = self._nums[(j % self.capacity())] + j += 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize queue + queue = ArrayQueue(10) + + # Element enqueue + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("Queue queue =", queue.to_list()) + + # Access front element + peek: int = queue.peek() + print("Front element peek =", peek) + + # Element dequeue + pop: int = queue.pop() + print("Dequeued element pop =", pop) + print("Queue after dequeue =", queue.to_list()) + + # Get the length of the queue + size: int = queue.size() + print("Queue length size =", size) + + # Determine if the queue is empty + is_empty: bool = queue.is_empty() + print("Is the queue empty =", is_empty) + + # Test circular array + for i in range(10): + queue.push(i) + queue.pop() + print("In the ", i, "th round of enqueue + dequeue, queue = ", queue.to_list()) diff --git a/en/codes/python/chapter_stack_and_queue/array_stack.py b/en/codes/python/chapter_stack_and_queue/array_stack.py new file mode 100644 index 0000000000..2775c2602b --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/array_stack.py @@ -0,0 +1,72 @@ +""" +File: array_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayStack: + """Stack class based on array""" + + def __init__(self): + """Constructor""" + self._stack: list[int] = [] + + def size(self) -> int: + """Get the length of the stack""" + return len(self._stack) + + def is_empty(self) -> bool: + """Determine if the stack is empty""" + return self.size() == 0 + + def push(self, item: int): + """Push""" + self._stack.append(item) + + def pop(self) -> int: + """Pop""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self._stack.pop() + + def peek(self) -> int: + """Access stack top element""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self._stack[-1] + + def to_list(self) -> list[int]: + """Return array for printing""" + return self._stack + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize stack + stack = ArrayStack() + + # Element push + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("Stack stack =", stack.to_list()) + + # Access stack top element + peek: int = stack.peek() + print("Stack top element peek =", peek) + + # Element pop + pop: int = stack.pop() + print("Popped element pop =", pop) + print("Stack after pop =", stack.to_list()) + + # Get the length of the stack + size: int = stack.size() + print("Stack length size =", size) + + # Determine if it's empty + is_empty: bool = stack.is_empty() + print("Is the stack empty =", is_empty) diff --git a/en/codes/python/chapter_stack_and_queue/deque.py b/en/codes/python/chapter_stack_and_queue/deque.py new file mode 100644 index 0000000000..08406a96bb --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/deque.py @@ -0,0 +1,42 @@ +""" +File: deque.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # Initialize double-ended queue + deq: deque[int] = deque() + + # Element enqueue + deq.append(2) # Add to rear + deq.append(5) + deq.append(4) + deq.appendleft(3) # Add to front + deq.appendleft(1) + print("Double-ended queue deque =", deq) + + # Access element + front: int = deq[0] # Front element + print("Front element front =", front) + rear: int = deq[-1] # Rear element + print("Rear element rear =", rear) + + # Element dequeue + pop_front: int = deq.popleft() # Front element dequeue + print("Front dequeued element pop_front =", pop_front) + print("Deque after front dequeue =", deq) + pop_rear: int = deq.pop() # Rear element dequeue + print("Rear dequeued element pop_rear =", pop_rear) + print("Deque after rear dequeue =", deq) + + # Get the length of the double-ended queue + size: int = len(deq) + print("Double-ended queue length size =", size) + + # Determine if the double-ended queue is empty + is_empty: bool = len(deq) == 0 + print("Is the double-ended queue empty =", is_empty) diff --git a/en/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/en/codes/python/chapter_stack_and_queue/linkedlist_deque.py new file mode 100644 index 0000000000..a2cea20992 --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -0,0 +1,151 @@ +""" +File: linkedlist_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """Double-linked list node""" + + def __init__(self, val: int): + """Constructor""" + self.val: int = val + self.next: ListNode | None = None # Reference to successor node + self.prev: ListNode | None = None # Reference to predecessor node + + +class LinkedListDeque: + """Double-ended queue class based on double-linked list""" + + def __init__(self): + """Constructor""" + self._front: ListNode | None = None # Head node front + self._rear: ListNode | None = None # Tail node rear + self._size: int = 0 # Length of the double-ended queue + + def size(self) -> int: + """Get the length of the double-ended queue""" + return self._size + + def is_empty(self) -> bool: + """Determine if the double-ended queue is empty""" + return self._size == 0 + + def push(self, num: int, is_front: bool): + """Enqueue operation""" + node = ListNode(num) + # If the list is empty, make front and rear both point to node + if self.is_empty(): + self._front = self._rear = node + # Front enqueue operation + elif is_front: + # Add node to the head of the list + self._front.prev = node + node.next = self._front + self._front = node # Update head node + # Rear enqueue operation + else: + # Add node to the tail of the list + self._rear.next = node + node.prev = self._rear + self._rear = node # Update tail node + self._size += 1 # Update queue length + + def push_first(self, num: int): + """Front enqueue""" + self.push(num, True) + + def push_last(self, num: int): + """Rear enqueue""" + self.push(num, False) + + def pop(self, is_front: bool) -> int: + """Dequeue operation""" + if self.is_empty(): + raise IndexError("Double-ended queue is empty") + # Front dequeue operation + if is_front: + val: int = self._front.val # Temporarily store the head node value + # Remove head node + fnext: ListNode | None = self._front.next + if fnext is not None: + fnext.prev = None + self._front.next = None + self._front = fnext # Update head node + # Rear dequeue operation + else: + val: int = self._rear.val # Temporarily store the tail node value + # Remove tail node + rprev: ListNode | None = self._rear.prev + if rprev is not None: + rprev.next = None + self._rear.prev = None + self._rear = rprev # Update tail node + self._size -= 1 # Update queue length + return val + + def pop_first(self) -> int: + """Front dequeue""" + return self.pop(True) + + def pop_last(self) -> int: + """Rear dequeue""" + return self.pop(False) + + def peek_first(self) -> int: + """Access front element""" + if self.is_empty(): + raise IndexError("Double-ended queue is empty") + return self._front.val + + def peek_last(self) -> int: + """Access rear element""" + if self.is_empty(): + raise IndexError("Double-ended queue is empty") + return self._rear.val + + def to_array(self) -> list[int]: + """Return array for printing""" + node = self._front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize double-ended queue + deque = LinkedListDeque() + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("Double-ended queue deque =", deque.to_array()) + + # Access element + peek_first: int = deque.peek_first() + print("Front element peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("Rear element peek_last =", peek_last) + + # Element enqueue + deque.push_last(4) + print("Element 4 rear enqueued, deque =", deque.to_array()) + deque.push_first(1) + print("Element 1 front enqueued, deque =", deque.to_array()) + + # Element dequeue + pop_last: int = deque.pop_last() + print("Rear dequeued element =", pop_last, ", deque after rear dequeue =", deque.to_array()) + pop_first: int = deque.pop_first() + print("Front dequeued element =", pop_first, ", deque after front dequeue =", deque.to_array()) + + # Get the length of the double-ended queue + size: int = deque.size() + print("Double-ended queue length size =", size) + + # Determine if the double-ended queue is empty + is_empty: bool = deque.is_empty() + print("Is the double-ended queue empty =", is_empty) diff --git a/en/codes/python/chapter_stack_and_queue/linkedlist_queue.py b/en/codes/python/chapter_stack_and_queue/linkedlist_queue.py new file mode 100644 index 0000000000..1acee511e5 --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/linkedlist_queue.py @@ -0,0 +1,97 @@ +""" +File: linkedlist_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListQueue: + """Queue class based on linked list""" + + def __init__(self): + """Constructor""" + self._front: ListNode | None = None # Head node front + self._rear: ListNode | None = None # Tail node rear + self._size: int = 0 + + def size(self) -> int: + """Get the length of the queue""" + return self._size + + def is_empty(self) -> bool: + """Determine if the queue is empty""" + return self._size == 0 + + def push(self, num: int): + """Enqueue""" + # Add num behind the tail node + node = ListNode(num) + # If the queue is empty, make the head and tail nodes both point to that node + if self._front is None: + self._front = node + self._rear = node + # If the queue is not empty, add that node behind the tail node + else: + self._rear.next = node + self._rear = node + self._size += 1 + + def pop(self) -> int: + """Dequeue""" + num = self.peek() + # Remove head node + self._front = self._front.next + self._size -= 1 + return num + + def peek(self) -> int: + """Access front element""" + if self.is_empty(): + raise IndexError("Queue is empty") + return self._front.val + + def to_list(self) -> list[int]: + """Convert to a list for printing""" + queue = [] + temp = self._front + while temp: + queue.append(temp.val) + temp = temp.next + return queue + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize queue + queue = LinkedListQueue() + + # Element enqueue + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("Queue queue =", queue.to_list()) + + # Access front element + peek: int = queue.peek() + print("Front element front =", peek) + + # Element dequeue + pop_front: int = queue.pop() + print("Dequeued element pop =", pop_front) + print("Queue after dequeue =", queue.to_list()) + + # Get the length of the queue + size: int = queue.size() + print("Queue length size =", size) + + # Determine if the queue is empty + is_empty: bool = queue.is_empty() + print("Is the queue empty =", is_empty) diff --git a/en/codes/python/chapter_stack_and_queue/linkedlist_stack.py b/en/codes/python/chapter_stack_and_queue/linkedlist_stack.py new file mode 100644 index 0000000000..7efd39c7f2 --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/linkedlist_stack.py @@ -0,0 +1,89 @@ +""" +File: linkedlist_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListStack: + """Stack class based on linked list""" + + def __init__(self): + """Constructor""" + self._peek: ListNode | None = None + self._size: int = 0 + + def size(self) -> int: + """Get the length of the stack""" + return self._size + + def is_empty(self) -> bool: + """Determine if the stack is empty""" + return self._size == 0 + + def push(self, val: int): + """Push""" + node = ListNode(val) + node.next = self._peek + self._peek = node + self._size += 1 + + def pop(self) -> int: + """Pop""" + num = self.peek() + self._peek = self._peek.next + self._size -= 1 + return num + + def peek(self) -> int: + """Access stack top element""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self._peek.val + + def to_list(self) -> list[int]: + """Convert to a list for printing""" + arr = [] + node = self._peek + while node: + arr.append(node.val) + node = node.next + arr.reverse() + return arr + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize stack + stack = LinkedListStack() + + # Element push + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("Stack stack =", stack.to_list()) + + # Access stack top element + peek: int = stack.peek() + print("Stack top element peek =", peek) + + # Element pop + pop: int = stack.pop() + print("Popped element pop =", pop) + print("Stack after pop =", stack.to_list()) + + # Get the length of the stack + size: int = stack.size() + print("Stack length size =", size) + + # Determine if it's empty + is_empty: bool = stack.is_empty() + print("Is the stack empty =", is_empty) diff --git a/en/codes/python/chapter_stack_and_queue/queue.py b/en/codes/python/chapter_stack_and_queue/queue.py new file mode 100644 index 0000000000..495aae1279 --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/queue.py @@ -0,0 +1,39 @@ +""" +File: queue.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # Initialize queue + # In Python, we generally consider the deque class as a queue + # Although queue.Queue() is a pure queue class, it's not very user-friendly + que: deque[int] = deque() + + # Element enqueue + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + print("Queue que =", que) + + # Access front element + front: int = que[0] + print("Front element front =", front) + + # Element dequeue + pop: int = que.popleft() + print("Dequeued element pop =", pop) + print("Queue after dequeue =", que) + + # Get the length of the queue + size: int = len(que) + print("Queue length size =", size) + + # Determine if the queue is empty + is_empty: bool = len(que) == 0 + print("Is the queue empty =", is_empty) diff --git a/en/codes/python/chapter_stack_and_queue/stack.py b/en/codes/python/chapter_stack_and_queue/stack.py new file mode 100644 index 0000000000..1a3447eed9 --- /dev/null +++ b/en/codes/python/chapter_stack_and_queue/stack.py @@ -0,0 +1,36 @@ +""" +File: stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # Initialize stack + # Python does not have a built-in stack class, but you can use a list as a stack + stack: list[int] = [] + + # Element push + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("Stack stack =", stack) + + # Access stack top element + peek: int = stack[-1] + print("Stack top element peek =", peek) + + # Element pop + pop: int = stack.pop() + print("Popped element pop =", pop) + print("Stack after pop =", stack) + + # Get the length of the stack + size: int = len(stack) + print("Stack length size =", size) + + # Determine if it's empty + is_empty: bool = len(stack) == 0 + print("Is the stack empty =", is_empty) diff --git a/en/codes/python/chapter_tree/array_binary_tree.py b/en/codes/python/chapter_tree/array_binary_tree.py new file mode 100644 index 0000000000..47c35d2e1e --- /dev/null +++ b/en/codes/python/chapter_tree/array_binary_tree.py @@ -0,0 +1,119 @@ +""" +File: array_binary_tree.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree + + +class ArrayBinaryTree: + """Array-based binary tree class""" + + def __init__(self, arr: list[int | None]): + """Constructor""" + self._tree = list(arr) + + def size(self): + """List capacity""" + return len(self._tree) + + def val(self, i: int) -> int | None: + """Get the value of the node at index i""" + # If the index is out of bounds, return None, representing a vacancy + if i < 0 or i >= self.size(): + return None + return self._tree[i] + + def left(self, i: int) -> int | None: + """Get the index of the left child of the node at index i""" + return 2 * i + 1 + + def right(self, i: int) -> int | None: + """Get the index of the right child of the node at index i""" + return 2 * i + 2 + + def parent(self, i: int) -> int | None: + """Get the index of the parent of the node at index i""" + return (i - 1) // 2 + + def level_order(self) -> list[int]: + """Level-order traversal""" + self.res = [] + # Traverse array + for i in range(self.size()): + if self.val(i) is not None: + self.res.append(self.val(i)) + return self.res + + def dfs(self, i: int, order: str): + """Depth-first traversal""" + if self.val(i) is None: + return + # Pre-order traversal + if order == "pre": + self.res.append(self.val(i)) + self.dfs(self.left(i), order) + # In-order traversal + if order == "in": + self.res.append(self.val(i)) + self.dfs(self.right(i), order) + # Post-order traversal + if order == "post": + self.res.append(self.val(i)) + + def pre_order(self) -> list[int]: + """Pre-order traversal""" + self.res = [] + self.dfs(0, order="pre") + return self.res + + def in_order(self) -> list[int]: + """In-order traversal""" + self.res = [] + self.dfs(0, order="in") + return self.res + + def post_order(self) -> list[int]: + """Post-order traversal""" + self.res = [] + self.dfs(0, order="post") + return self.res + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize binary tree + # Use a specific function to convert an array into a binary tree + arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + root = list_to_tree(arr) + print("\nInitialize binary tree\n") + print("Array representation of the binary tree:") + print(arr) + print("Linked list representation of the binary tree:") + print_tree(root) + + # Array-based binary tree class + abt = ArrayBinaryTree(arr) + + # Access node + i = 1 + l, r, p = abt.left(i), abt.right(i), abt.parent(i) + print(f"\nCurrent node index is {i}, value is {abt.val(i)}") + print(f"Its left child node index is {l}, value is {abt.val(l)}") + print(f"Its right child node index is {r}, value is {abt.val(r)}") + print(f"Its parent node index is {p}, value is {abt.val(p)}") + + # Traverse tree + res = abt.level_order() + print("\nLevel-order traversal is:", res) + res = abt.pre_order() + print("Pre-order traversal is:", res) + res = abt.in_order() + print("In-order traversal is:", res) + res = abt.post_order() + print("Post-order traversal is:", res) diff --git a/en/codes/python/chapter_tree/avl_tree.py b/en/codes/python/chapter_tree/avl_tree.py new file mode 100644 index 0000000000..399515e864 --- /dev/null +++ b/en/codes/python/chapter_tree/avl_tree.py @@ -0,0 +1,200 @@ +""" +File: avl_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +class AVLTree: + """AVL tree""" + + def __init__(self): + """Constructor""" + self._root = None + + def get_root(self) -> TreeNode | None: + """Get binary tree root node""" + return self._root + + def height(self, node: TreeNode | None) -> int: + """Get node height""" + # Empty node height is -1, leaf node height is 0 + if node is not None: + return node.height + return -1 + + def update_height(self, node: TreeNode | None): + """Update node height""" + # Node height equals the height of the tallest subtree + 1 + node.height = max([self.height(node.left), self.height(node.right)]) + 1 + + def balance_factor(self, node: TreeNode | None) -> int: + """Get balance factor""" + # Empty node balance factor is 0 + if node is None: + return 0 + # Node balance factor = left subtree height - right subtree height + return self.height(node.left) - self.height(node.right) + + def right_rotate(self, node: TreeNode | None) -> TreeNode | None: + """Right rotation operation""" + child = node.left + grand_child = child.right + # Rotate node to the right around child + child.right = node + node.left = grand_child + # Update node height + self.update_height(node) + self.update_height(child) + # Return the root of the subtree after rotation + return child + + def left_rotate(self, node: TreeNode | None) -> TreeNode | None: + """Left rotation operation""" + child = node.right + grand_child = child.left + # Rotate node to the left around child + child.left = node + node.right = grand_child + # Update node height + self.update_height(node) + self.update_height(child) + # Return the root of the subtree after rotation + return child + + def rotate(self, node: TreeNode | None) -> TreeNode | None: + """Perform rotation operation to restore balance to the subtree""" + # Get the balance factor of node + balance_factor = self.balance_factor(node) + # Left-leaning tree + if balance_factor > 1: + if self.balance_factor(node.left) >= 0: + # Right rotation + return self.right_rotate(node) + else: + # First left rotation then right rotation + node.left = self.left_rotate(node.left) + return self.right_rotate(node) + # Right-leaning tree + elif balance_factor < -1: + if self.balance_factor(node.right) <= 0: + # Left rotation + return self.left_rotate(node) + else: + # First right rotation then left rotation + node.right = self.right_rotate(node.right) + return self.left_rotate(node) + # Balanced tree, no rotation needed, return + return node + + def insert(self, val): + """Insert node""" + self._root = self.insert_helper(self._root, val) + + def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: + """Recursively insert node (helper method)""" + if node is None: + return TreeNode(val) + # 1. Find insertion position and insert node + if val < node.val: + node.left = self.insert_helper(node.left, val) + elif val > node.val: + node.right = self.insert_helper(node.right, val) + else: + # Do not insert duplicate nodes, return + return node + # Update node height + self.update_height(node) + # 2. Perform rotation operation to restore balance to the subtree + return self.rotate(node) + + def remove(self, val: int): + """Remove node""" + self._root = self.remove_helper(self._root, val) + + def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: + """Recursively remove node (helper method)""" + if node is None: + return None + # 1. Find and remove the node + if val < node.val: + node.left = self.remove_helper(node.left, val) + elif val > node.val: + node.right = self.remove_helper(node.right, val) + else: + if node.left is None or node.right is None: + child = node.left or node.right + # Number of child nodes = 0, remove node and return + if child is None: + return None + # Number of child nodes = 1, remove node + else: + node = child + else: + # Number of child nodes = 2, remove the next node in in-order traversal and replace the current node with it + temp = node.right + while temp.left is not None: + temp = temp.left + node.right = self.remove_helper(node.right, temp.val) + node.val = temp.val + # Update node height + self.update_height(node) + # 2. Perform rotation operation to restore balance to the subtree + return self.rotate(node) + + def search(self, val: int) -> TreeNode | None: + """Search node""" + cur = self._root + # Loop find, break after passing leaf nodes + while cur is not None: + # Target node is in cur's right subtree + if cur.val < val: + cur = cur.right + # Target node is in cur's left subtree + elif cur.val > val: + cur = cur.left + # Found target node, break loop + else: + break + # Return target node + return cur + + +"""Driver Code""" +if __name__ == "__main__": + + def test_insert(tree: AVLTree, val: int): + tree.insert(val) + print("\nInsert node {} after, AVL tree is".format(val)) + print_tree(tree.get_root()) + + def test_remove(tree: AVLTree, val: int): + tree.remove(val) + print("\nRemove node {} after, AVL tree is".format(val)) + print_tree(tree.get_root()) + + # Initialize empty AVL tree + avl_tree = AVLTree() + + # Insert node + # Notice how the AVL tree maintains balance after inserting nodes + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]: + test_insert(avl_tree, val) + + # Insert duplicate node + test_insert(avl_tree, 7) + + # Remove node + # Notice how the AVL tree maintains balance after removing nodes + test_remove(avl_tree, 8) # Remove node with degree 0 + test_remove(avl_tree, 5) # Remove node with degree 1 + test_remove(avl_tree, 4) # Remove node with degree 2 + + result_node = avl_tree.search(7) + print("\nFound node object is {}, node value = {}".format(result_node, result_node.val)) diff --git a/en/codes/python/chapter_tree/binary_search_tree.py b/en/codes/python/chapter_tree/binary_search_tree.py new file mode 100644 index 0000000000..e4a6d1f3e7 --- /dev/null +++ b/en/codes/python/chapter_tree/binary_search_tree.py @@ -0,0 +1,146 @@ +""" +File: binary_search_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +class BinarySearchTree: + """Binary search tree""" + + def __init__(self): + """Constructor""" + # Initialize empty tree + self._root = None + + def get_root(self) -> TreeNode | None: + """Get binary tree root node""" + return self._root + + def search(self, num: int) -> TreeNode | None: + """Search node""" + cur = self._root + # Loop find, break after passing leaf nodes + while cur is not None: + # Target node is in cur's right subtree + if cur.val < num: + cur = cur.right + # Target node is in cur's left subtree + elif cur.val > num: + cur = cur.left + # Found target node, break loop + else: + break + return cur + + def insert(self, num: int): + """Insert node""" + # If tree is empty, initialize root node + if self._root is None: + self._root = TreeNode(num) + return + # Loop find, break after passing leaf nodes + cur, pre = self._root, None + while cur is not None: + # Found duplicate node, thus return + if cur.val == num: + return + pre = cur + # Insertion position is in cur's right subtree + if cur.val < num: + cur = cur.right + # Insertion position is in cur's left subtree + else: + cur = cur.left + # Insert node + node = TreeNode(num) + if pre.val < num: + pre.right = node + else: + pre.left = node + + def remove(self, num: int): + """Remove node""" + # If tree is empty, return + if self._root is None: + return + # Loop find, break after passing leaf nodes + cur, pre = self._root, None + while cur is not None: + # Found node to be removed, break loop + if cur.val == num: + break + pre = cur + # Node to be removed is in cur's right subtree + if cur.val < num: + cur = cur.right + # Node to be removed is in cur's left subtree + else: + cur = cur.left + # If no node to be removed, return + if cur is None: + return + + # Number of child nodes = 0 or 1 + if cur.left is None or cur.right is None: + # When the number of child nodes = 0/1, child = null/that child node + child = cur.left or cur.right + # Remove node cur + if cur != self._root: + if pre.left == cur: + pre.left = child + else: + pre.right = child + else: + # If the removed node is the root, reassign the root + self._root = child + # Number of child nodes = 2 + else: + # Get the next node in in-order traversal of cur + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # Recursively remove node tmp + self.remove(tmp.val) + # Replace cur with tmp + cur.val = tmp.val + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize binary search tree + bst = BinarySearchTree() + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # Note that different insertion orders can result in various tree structures. This particular sequence creates a perfect binary tree + for num in nums: + bst.insert(num) + print("\nInitialized binary tree is\n") + print_tree(bst.get_root()) + + # Search node + node = bst.search(7) + print("\nFound node object is: {}, node value = {}".format(node, node.val)) + + # Insert node + bst.insert(16) + print("\nAfter inserting node 16, the binary tree is\n") + print_tree(bst.get_root()) + + # Remove node + bst.remove(1) + print("\nAfter removing node 1, the binary tree is\n") + print_tree(bst.get_root()) + + bst.remove(2) + print("\nAfter removing node 2, the binary tree is\n") + print_tree(bst.get_root()) + + bst.remove(4) + print("\nAfter removing node 4, the binary tree is\n") + print_tree(bst.get_root()) diff --git a/en/codes/python/chapter_tree/binary_tree.py b/en/codes/python/chapter_tree/binary_tree.py new file mode 100644 index 0000000000..cdfb8416d3 --- /dev/null +++ b/en/codes/python/chapter_tree/binary_tree.py @@ -0,0 +1,41 @@ +""" +File: binary_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize binary tree + # Initialize node + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # Construct node references (pointers) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\nInitialize binary tree\n") + print_tree(n1) + + # Insert and remove nodes + P = TreeNode(0) + # Insert node P between n1 -> n2 + n1.left = P + P.left = n2 + print("\nAfter inserting node P\n") + print_tree(n1) + # Remove node + n1.left = n2 + print("\nAfter removing node P\n") + print_tree(n1) diff --git a/en/codes/python/chapter_tree/binary_tree_bfs.py b/en/codes/python/chapter_tree/binary_tree_bfs.py new file mode 100644 index 0000000000..77e1ceac79 --- /dev/null +++ b/en/codes/python/chapter_tree/binary_tree_bfs.py @@ -0,0 +1,42 @@ +""" +File: binary_tree_bfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree +from collections import deque + + +def level_order(root: TreeNode | None) -> list[int]: + """Level-order traversal""" + # Initialize queue, add root node + queue: deque[TreeNode] = deque() + queue.append(root) + # Initialize a list to store the traversal sequence + res = [] + while queue: + node: TreeNode = queue.popleft() # Queue dequeues + res.append(node.val) # Save node value + if node.left is not None: + queue.append(node.left) # Left child node enqueues + if node.right is not None: + queue.append(node.right) # Right child node enqueues + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize binary tree + # Use a specific function to convert an array into a binary tree + root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\nInitialize binary tree\n") + print_tree(root) + + # Level-order traversal + res: list[int] = level_order(root) + print("\nPrint sequence of nodes from level-order traversal = ", res) diff --git a/en/codes/python/chapter_tree/binary_tree_dfs.py b/en/codes/python/chapter_tree/binary_tree_dfs.py new file mode 100644 index 0000000000..d25afe649f --- /dev/null +++ b/en/codes/python/chapter_tree/binary_tree_dfs.py @@ -0,0 +1,65 @@ +""" +File: binary_tree_dfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree + + +def pre_order(root: TreeNode | None): + """Pre-order traversal""" + if root is None: + return + # Visit priority: root node -> left subtree -> right subtree + res.append(root.val) + pre_order(root=root.left) + pre_order(root=root.right) + + +def in_order(root: TreeNode | None): + """In-order traversal""" + if root is None: + return + # Visit priority: left subtree -> root node -> right subtree + in_order(root=root.left) + res.append(root.val) + in_order(root=root.right) + + +def post_order(root: TreeNode | None): + """Post-order traversal""" + if root is None: + return + # Visit priority: left subtree -> right subtree -> root node + post_order(root=root.left) + post_order(root=root.right) + res.append(root.val) + + +"""Driver Code""" +if __name__ == "__main__": + # Initialize binary tree + # Use a specific function to convert an array into a binary tree + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\nInitialize binary tree\n") + print_tree(root) + + # Pre-order traversal + res = [] + pre_order(root) + print("\nPrint sequence of nodes from pre-order traversal = ", res) + + # In-order traversal + res.clear() + in_order(root) + print("\nPrint sequence of nodes from in-order traversal = ", res) + + # Post-order traversal + res.clear() + post_order(root) + print("\nPrint sequence of nodes from post-order traversal = ", res) diff --git a/en/codes/python/modules/__init__.py b/en/codes/python/modules/__init__.py new file mode 100644 index 0000000000..b10799e3c5 --- /dev/null +++ b/en/codes/python/modules/__init__.py @@ -0,0 +1,19 @@ +# Follow the PEP 585 - Type Hinting Generics In Standard Collections +# https://peps.python.org/pep-0585/ +from __future__ import annotations + +# Import common libs here to simplify the code by `from module import *` +from .list_node import ( + ListNode, + list_to_linked_list, + linked_list_to_list, +) +from .tree_node import TreeNode, list_to_tree, tree_to_list +from .vertex import Vertex, vals_to_vets, vets_to_vals +from .print_util import ( + print_matrix, + print_linked_list, + print_tree, + print_dict, + print_heap, +) diff --git a/en/codes/python/modules/list_node.py b/en/codes/python/modules/list_node.py new file mode 100644 index 0000000000..1d158f7c4b --- /dev/null +++ b/en/codes/python/modules/list_node.py @@ -0,0 +1,32 @@ +""" +File: list_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """LinkedList node class""" + + def __init__(self, val: int): + self.val: int = val # Node value + self.next: ListNode | None = None # Reference to successor node + + +def list_to_linked_list(arr: list[int]) -> ListNode | None: + """Deserialize a list into a linked list""" + dum = head = ListNode(0) + for a in arr: + node = ListNode(a) + head.next = node + head = head.next + return dum.next + + +def linked_list_to_list(head: ListNode | None) -> list[int]: + """Serialize a linked list into a list""" + arr: list[int] = [] + while head: + arr.append(head.val) + head = head.next + return arr diff --git a/en/codes/python/modules/print_util.py b/en/codes/python/modules/print_util.py new file mode 100644 index 0000000000..0cf92db5d1 --- /dev/null +++ b/en/codes/python/modules/print_util.py @@ -0,0 +1,81 @@ +""" +File: print_util.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com) +""" + +from .tree_node import TreeNode, list_to_tree +from .list_node import ListNode, linked_list_to_list + + +def print_matrix(mat: list[list[int]]): + """Print matrix""" + s = [] + for arr in mat: + s.append(" " + str(arr)) + print("[\n" + ",\n".join(s) + "\n]") + + +def print_linked_list(head: ListNode | None): + """Print linked list""" + arr: list[int] = linked_list_to_list(head) + print(" -> ".join([str(a) for a in arr])) + + +class Trunk: + def __init__(self, prev, string: str | None = None): + self.prev = prev + self.str = string + + +def show_trunks(p: Trunk | None): + if p is None: + return + show_trunks(p.prev) + print(p.str, end="") + + +def print_tree( + root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False +): + """ + Print binary tree + This tree printer is borrowed from TECHIE DELIGHT + https://www.techiedelight.com/c-program-print-binary-tree/ + """ + if root is None: + return + + prev_str = " " + trunk = Trunk(prev, prev_str) + print_tree(root.right, trunk, True) + + if prev is None: + trunk.str = "———" + elif is_right: + trunk.str = "/———" + prev_str = " |" + else: + trunk.str = "\———" + prev.str = prev_str + + show_trunks(trunk) + print(" " + str(root.val)) + if prev: + prev.str = prev_str + trunk.str = " |" + print_tree(root.left, trunk, False) + + +def print_dict(hmap: dict): + """Print dictionary""" + for key, value in hmap.items(): + print(key, "->", value) + + +def print_heap(heap: list[int]): + """Print heap""" + print("Array representation of the heap:", heap) + print("Tree representation of the heap:") + root: TreeNode | None = list_to_tree(heap) + print_tree(root) diff --git a/en/codes/python/modules/tree_node.py b/en/codes/python/modules/tree_node.py new file mode 100644 index 0000000000..95bfef3fb7 --- /dev/null +++ b/en/codes/python/modules/tree_node.py @@ -0,0 +1,69 @@ +""" +File: tree_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + +from collections import deque + + +class TreeNode: + """Binary tree node class""" + + def __init__(self, val: int = 0): + self.val: int = val # Node value + self.height: int = 0 # Node height + self.left: TreeNode | None = None # Reference to the left child node + self.right: TreeNode | None = None # Reference to the right child node + + # For serialization encoding rules, refer to: + # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + # Array representation of the binary tree: + # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + # Linked list representation of the binary tree: + # /——— 15 + # /——— 7 + # /——— 3 + # | \——— 6 + # | \——— 12 + # ——— 1 + # \——— 2 + # | /——— 9 + # \——— 4 + # \——— 8 + + +def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: + """Deserialize a list into a binary tree: Recursively""" + # If the index is out of array bounds, or the corresponding element is None, return None + if i < 0 or i >= len(arr) or arr[i] is None: + return None + # Construct the current node + root = TreeNode(arr[i]) + # Recursively construct left and right subtrees + root.left = list_to_tree_dfs(arr, 2 * i + 1) + root.right = list_to_tree_dfs(arr, 2 * i + 2) + return root + + +def list_to_tree(arr: list[int]) -> TreeNode | None: + """Deserialize a list into a binary tree""" + return list_to_tree_dfs(arr, 0) + + +def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: + """Serialize a binary tree into a list: Recursively""" + if root is None: + return + if i >= len(res): + res += [None] * (i - len(res) + 1) + res[i] = root.val + tree_to_list_dfs(root.left, 2 * i + 1, res) + tree_to_list_dfs(root.right, 2 * i + 2, res) + + +def tree_to_list(root: TreeNode | None) -> list[int]: + """Serialize a binary tree into a list""" + res = [] + tree_to_list_dfs(root, 0, res) + return res diff --git a/en/codes/python/modules/vertex.py b/en/codes/python/modules/vertex.py new file mode 100644 index 0000000000..40602dde92 --- /dev/null +++ b/en/codes/python/modules/vertex.py @@ -0,0 +1,20 @@ +# File: vertex.py +# Created Time: 2023-02-23 +# Author: krahets (krahets@163.com) + + +class Vertex: + """Vertex class""" + + def __init__(self, val: int): + self.val = val + + +def vals_to_vets(vals: list[int]) -> list["Vertex"]: + """Input a list of values vals, return a list of vertices vets""" + return [Vertex(val) for val in vals] + + +def vets_to_vals(vets: list["Vertex"]) -> list[int]: + """Input a list of vertices vets, return a list of values vals""" + return [vet.val for vet in vets] diff --git a/en/codes/python/test_all.py b/en/codes/python/test_all.py new file mode 100644 index 0000000000..c1d8c3b034 --- /dev/null +++ b/en/codes/python/test_all.py @@ -0,0 +1,33 @@ +import os +import glob +import subprocess + +env = os.environ.copy() +env["PYTHONIOENCODING"] = "utf-8" + +if __name__ == "__main__": + # find source code files + src_paths = sorted(glob.glob("en/codes/python/chapter_*/*.py")) + errors = [] + + # run python code + for src_path in src_paths: + process = subprocess.Popen( + ["python", src_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + encoding='utf-8' + ) + # Wait for the process to complete, and get the output and error messages + stdout, stderr = process.communicate() + # Check the exit status + exit_status = process.returncode + if exit_status != 0: + errors.append(stderr) + + print(f"Tested {len(src_paths)} files") + print(f"Found exception in {len(errors)} files") + if len(errors) > 0: + raise RuntimeError("\n\n".join(errors)) diff --git a/en/docs/assets/covers/chapter_appendix.jpg b/en/docs/assets/covers/chapter_appendix.jpg new file mode 100644 index 0000000000..00a849b573 Binary files /dev/null and b/en/docs/assets/covers/chapter_appendix.jpg differ diff --git a/en/docs/assets/covers/chapter_array_and_linkedlist.jpg b/en/docs/assets/covers/chapter_array_and_linkedlist.jpg new file mode 100644 index 0000000000..d5092e8979 Binary files /dev/null and b/en/docs/assets/covers/chapter_array_and_linkedlist.jpg differ diff --git a/en/docs/assets/covers/chapter_backtracking.jpg b/en/docs/assets/covers/chapter_backtracking.jpg new file mode 100644 index 0000000000..1c91c7818f Binary files /dev/null and b/en/docs/assets/covers/chapter_backtracking.jpg differ diff --git a/en/docs/assets/covers/chapter_complexity_analysis.jpg b/en/docs/assets/covers/chapter_complexity_analysis.jpg new file mode 100644 index 0000000000..2a19ed3600 Binary files /dev/null and b/en/docs/assets/covers/chapter_complexity_analysis.jpg differ diff --git a/en/docs/assets/covers/chapter_data_structure.jpg b/en/docs/assets/covers/chapter_data_structure.jpg new file mode 100644 index 0000000000..590a6edaa0 Binary files /dev/null and b/en/docs/assets/covers/chapter_data_structure.jpg differ diff --git a/en/docs/assets/covers/chapter_divide_and_conquer.jpg b/en/docs/assets/covers/chapter_divide_and_conquer.jpg new file mode 100644 index 0000000000..8ad3560d1f Binary files /dev/null and b/en/docs/assets/covers/chapter_divide_and_conquer.jpg differ diff --git a/en/docs/assets/covers/chapter_dynamic_programming.jpg b/en/docs/assets/covers/chapter_dynamic_programming.jpg new file mode 100644 index 0000000000..5b5a6ec21d Binary files /dev/null and b/en/docs/assets/covers/chapter_dynamic_programming.jpg differ diff --git a/en/docs/assets/covers/chapter_graph.jpg b/en/docs/assets/covers/chapter_graph.jpg new file mode 100644 index 0000000000..bec81a2e13 Binary files /dev/null and b/en/docs/assets/covers/chapter_graph.jpg differ diff --git a/en/docs/assets/covers/chapter_greedy.jpg b/en/docs/assets/covers/chapter_greedy.jpg new file mode 100644 index 0000000000..6905df2d44 Binary files /dev/null and b/en/docs/assets/covers/chapter_greedy.jpg differ diff --git a/en/docs/assets/covers/chapter_hashing.jpg b/en/docs/assets/covers/chapter_hashing.jpg new file mode 100644 index 0000000000..b41dbbeadf Binary files /dev/null and b/en/docs/assets/covers/chapter_hashing.jpg differ diff --git a/en/docs/assets/covers/chapter_heap.jpg b/en/docs/assets/covers/chapter_heap.jpg new file mode 100644 index 0000000000..54fe1d4fa6 Binary files /dev/null and b/en/docs/assets/covers/chapter_heap.jpg differ diff --git a/en/docs/assets/covers/chapter_hello_algo.jpg b/en/docs/assets/covers/chapter_hello_algo.jpg new file mode 100644 index 0000000000..8e347b3a4b Binary files /dev/null and b/en/docs/assets/covers/chapter_hello_algo.jpg differ diff --git a/en/docs/assets/covers/chapter_introduction.jpg b/en/docs/assets/covers/chapter_introduction.jpg new file mode 100644 index 0000000000..a69a584d28 Binary files /dev/null and b/en/docs/assets/covers/chapter_introduction.jpg differ diff --git a/en/docs/assets/covers/chapter_preface.jpg b/en/docs/assets/covers/chapter_preface.jpg new file mode 100644 index 0000000000..d4dd753bdf Binary files /dev/null and b/en/docs/assets/covers/chapter_preface.jpg differ diff --git a/en/docs/assets/covers/chapter_searching.jpg b/en/docs/assets/covers/chapter_searching.jpg new file mode 100644 index 0000000000..f5d9c2a19d Binary files /dev/null and b/en/docs/assets/covers/chapter_searching.jpg differ diff --git a/en/docs/assets/covers/chapter_sorting.jpg b/en/docs/assets/covers/chapter_sorting.jpg new file mode 100644 index 0000000000..f77e7050aa Binary files /dev/null and b/en/docs/assets/covers/chapter_sorting.jpg differ diff --git a/en/docs/assets/covers/chapter_stack_and_queue.jpg b/en/docs/assets/covers/chapter_stack_and_queue.jpg new file mode 100644 index 0000000000..8c75e4aae6 Binary files /dev/null and b/en/docs/assets/covers/chapter_stack_and_queue.jpg differ diff --git a/en/docs/assets/covers/chapter_tree.jpg b/en/docs/assets/covers/chapter_tree.jpg new file mode 100644 index 0000000000..8d0001f020 Binary files /dev/null and b/en/docs/assets/covers/chapter_tree.jpg differ diff --git a/en/docs/chapter_appendix/contribution.assets/edit_markdown.png b/en/docs/chapter_appendix/contribution.assets/edit_markdown.png new file mode 100644 index 0000000000..6f99dc943f Binary files /dev/null and b/en/docs/chapter_appendix/contribution.assets/edit_markdown.png differ diff --git a/en/docs/chapter_appendix/contribution.md b/en/docs/chapter_appendix/contribution.md new file mode 100644 index 0000000000..0852364c54 --- /dev/null +++ b/en/docs/chapter_appendix/contribution.md @@ -0,0 +1,47 @@ +# Contributing + +Due to the limited abilities of the author, some omissions and errors are inevitable in this book. Please understand. If you discover any typos, broken links, missing content, textual ambiguities, unclear explanations, or unreasonable text structures, please assist us in making corrections to provide readers with better quality learning resources. + +The GitHub IDs of all [contributors](https://github.com/krahets/hello-algo/graphs/contributors) will be displayed on the repository, web, and PDF versions of the homepage of this book to thank them for their selfless contributions to the open-source community. + +!!! success "The charm of open source" + + The interval between two printings of a paper book is often long, making content updates very inconvenient. + + In this open-source book, however, the content update cycle is shortened to just a few days or even hours. + +### Content fine-tuning + +As shown in the figure below, there is an "edit icon" in the upper right corner of each page. You can follow these steps to modify text or code. + +1. Click the "edit icon". If prompted to "fork this repository", please agree to do so. +2. Modify the Markdown source file content, check the accuracy of the content, and try to keep the formatting consistent. +3. Fill in the modification description at the bottom of the page, then click the "Propose file change" button. After the page redirects, click the "Create pull request" button to initiate the pull request. + +![Edit page button](contribution.assets/edit_markdown.png) + +Figures cannot be directly modified and require the creation of a new [Issue](https://github.com/krahets/hello-algo/issues) or a comment to describe the problem. We will redraw and replace the figures as soon as possible. + +### Content creation + +If you are interested in participating in this open-source project, including translating code into other programming languages or expanding article content, then the following Pull Request workflow needs to be implemented. + +1. Log in to GitHub and Fork the [code repository](https://github.com/krahets/hello-algo) of this book to your personal account. +2. Go to your Forked repository web page and use the `git clone` command to clone the repository to your local machine. +3. Create content locally and perform complete tests to verify the correctness of the code. +4. Commit the changes made locally, then push them to the remote repository. +5. Refresh the repository webpage and click the "Create pull request" button to initiate the pull request. + +### Docker deployment + +In the `hello-algo` root directory, execute the following Docker script to access the project at `http://localhost:8000`: + +```shell +docker-compose up -d +``` + +Use the following command to remove the deployment: + +```shell +docker-compose down +``` diff --git a/en/docs/chapter_appendix/index.md b/en/docs/chapter_appendix/index.md new file mode 100644 index 0000000000..3a0456dbf3 --- /dev/null +++ b/en/docs/chapter_appendix/index.md @@ -0,0 +1,3 @@ +# Appendix + +![Appendix](../assets/covers/chapter_appendix.jpg) diff --git a/en/docs/chapter_appendix/installation.assets/vscode_extension_installation.png b/en/docs/chapter_appendix/installation.assets/vscode_extension_installation.png new file mode 100644 index 0000000000..6b8dbfb4ab Binary files /dev/null and b/en/docs/chapter_appendix/installation.assets/vscode_extension_installation.png differ diff --git a/en/docs/chapter_appendix/installation.assets/vscode_installation.png b/en/docs/chapter_appendix/installation.assets/vscode_installation.png new file mode 100644 index 0000000000..fce7764296 Binary files /dev/null and b/en/docs/chapter_appendix/installation.assets/vscode_installation.png differ diff --git a/en/docs/chapter_appendix/installation.md b/en/docs/chapter_appendix/installation.md new file mode 100644 index 0000000000..40e22b62fe --- /dev/null +++ b/en/docs/chapter_appendix/installation.md @@ -0,0 +1,68 @@ +# Installation + +## Install IDE + +We recommend using the open-source, lightweight VS Code as your local Integrated Development Environment (IDE). Visit the [VS Code official website](https://code.visualstudio.com/) and choose the version of VS Code appropriate for your operating system to download and install. + +![Download VS Code from the official website](installation.assets/vscode_installation.png) + +VS Code has a powerful extension ecosystem, supporting the execution and debugging of most programming languages. For example, after installing the "Python Extension Pack," you can debug Python code. The installation steps are shown in the figure below. + +![Install VS Code Extension Pack](installation.assets/vscode_extension_installation.png) + +## Install language environments + +### Python environment + +1. Download and install [Miniconda3](https://docs.conda.io/en/latest/miniconda.html), requiring Python 3.10 or newer. +2. In the VS Code extension marketplace, search for `python` and install the Python Extension Pack. +3. (Optional) Enter `pip install black` in the command line to install the code formatting tool. + +### C/C++ environment + +1. Windows systems need to install [MinGW](https://sourceforge.net/projects/mingw-w64/files/) ([Configuration tutorial](https://blog.csdn.net/qq_33698226/article/details/129031241)); MacOS comes with Clang, so no installation is necessary. +2. In the VS Code extension marketplace, search for `c++` and install the C/C++ Extension Pack. +3. (Optional) Open the Settings page, search for the `Clang_format_fallback Style` code formatting option, and set it to `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }`. + +### Java environment + +1. Download and install [OpenJDK](https://jdk.java.net/18/) (version must be > JDK 9). +2. In the VS Code extension marketplace, search for `java` and install the Extension Pack for Java. + +### C# environment + +1. Download and install [.Net 8.0](https://dotnet.microsoft.com/en-us/download). +2. In the VS Code extension marketplace, search for `C# Dev Kit` and install the C# Dev Kit ([Configuration tutorial](https://code.visualstudio.com/docs/csharp/get-started)). +3. You can also use Visual Studio ([Installation tutorial](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022)). + +### Go environment + +1. Download and install [go](https://go.dev/dl/). +2. In the VS Code extension marketplace, search for `go` and install Go. +3. Press `Ctrl + Shift + P` to call up the command bar, enter go, choose `Go: Install/Update Tools`, select all and install. + +### Swift environment + +1. Download and install [Swift](https://www.swift.org/download/). +2. In the VS Code extension marketplace, search for `swift` and install [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang). + +### JavaScript environment + +1. Download and install [Node.js](https://nodejs.org/en/). +2. (Optional) In the VS Code extension marketplace, search for `Prettier` and install the code formatting tool. + +### TypeScript environment + +1. Follow the same installation steps as the JavaScript environment. +2. Install [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation). +3. In the VS Code extension marketplace, search for `typescript` and install [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors). + +### Dart environment + +1. Download and install [Dart](https://dart.dev/get-dart). +2. In the VS Code extension marketplace, search for `dart` and install [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code). + +### Rust environment + +1. Download and install [Rust](https://www.rust-lang.org/tools/install). +2. In the VS Code extension marketplace, search for `rust` and install [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). diff --git a/en/docs/chapter_appendix/terminology.md b/en/docs/chapter_appendix/terminology.md new file mode 100644 index 0000000000..f46133c818 --- /dev/null +++ b/en/docs/chapter_appendix/terminology.md @@ -0,0 +1,137 @@ +# Glossary + +The table below lists the important terms that appear in the book, and it is worth noting the following points. + +- It is recommended to remember the English names of the terms to facilitate reading English literature. +- Some terms have different names in Simplified and Traditional Chinese. + +

Table   Important Terms in Data Structures and Algorithms

+ +| English | 简体中文 | 繁体中文 | +| ------------------------------ | -------------- | -------------- | +| algorithm | 算法 | 演算法 | +| data structure | 数据结构 | 資料結構 | +| code | 代码 | 程式碼 | +| file | 文件 | 檔案 | +| function | 函数 | 函式 | +| method | 方法 | 方法 | +| variable | 变量 | 變數 | +| asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 | +| time complexity | 时间复杂度 | 時間複雜度 | +| space complexity | 空间复杂度 | 空間複雜度 | +| loop | 循环 | 迴圈 | +| iteration | 迭代 | 迭代 | +| recursion | 递归 | 遞迴 | +| tail recursion | 尾递归 | 尾遞迴 | +| recursion tree | 递归树 | 遞迴樹 | +| big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 | +| asymptotic upper bound | 渐近上界 | 漸近上界 | +| sign-magnitude | 原码 | 原碼 | +| 1’s complement | 反码 | 一補數 | +| 2’s complement | 补码 | 二補數 | +| array | 数组 | 陣列 | +| index | 索引 | 索引 | +| linked list | 链表 | 鏈結串列 | +| linked list node, list node | 链表节点 | 鏈結串列節點 | +| head node | 头节点 | 頭節點 | +| tail node | 尾节点 | 尾節點 | +| list | 列表 | 串列 | +| dynamic array | 动态数组 | 動態陣列 | +| hard disk | 硬盘 | 硬碟 | +| random-access memory (RAM) | 内存 | 記憶體 | +| cache memory | 缓存 | 快取 | +| cache miss | 缓存未命中 | 快取未命中 | +| cache hit rate | 缓存命中率 | 快取命中率 | +| stack | 栈 | 堆疊 | +| top of the stack | 栈顶 | 堆疊頂 | +| bottom of the stack | 栈底 | 堆疊底 | +| queue | 队列 | 佇列 | +| double-ended queue | 双向队列 | 雙向佇列 | +| front of the queue | 队首 | 佇列首 | +| rear of the queue | 队尾 | 佇列尾 | +| hash table | 哈希表 | 雜湊表 | +| hash set | 哈希集合 | 雜湊集合 | +| bucket | 桶 | 桶 | +| hash function | 哈希函数 | 雜湊函式 | +| hash collision | 哈希冲突 | 雜湊衝突 | +| load factor | 负载因子 | 負載因子 | +| separate chaining | 链式地址 | 鏈結位址 | +| open addressing | 开放寻址 | 開放定址 | +| linear probing | 线性探测 | 線性探查 | +| lazy deletion | 懒删除 | 懶刪除 | +| binary tree | 二叉树 | 二元樹 | +| tree node | 树节点 | 樹節點 | +| left-child node | 左子节点 | 左子節點 | +| right-child node | 右子节点 | 右子節點 | +| parent node | 父节点 | 父節點 | +| left subtree | 左子树 | 左子樹 | +| right subtree | 右子树 | 右子樹 | +| root node | 根节点 | 根節點 | +| leaf node | 叶节点 | 葉節點 | +| edge | 边 | 邊 | +| level | 层 | 層 | +| degree | 度 | 度 | +| height | 高度 | 高度 | +| depth | 深度 | 深度 | +| perfect binary tree | 完美二叉树 | 完美二元樹 | +| complete binary tree | 完全二叉树 | 完全二元樹 | +| full binary tree | 完满二叉树 | 完滿二元樹 | +| balanced binary tree | 平衡二叉树 | 平衡二元樹 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| AVL tree | AVL 树 | AVL 樹 | +| red-black tree | 红黑树 | 紅黑樹 | +| level-order traversal | 层序遍历 | 層序走訪 | +| breadth-first traversal | 广度优先遍历 | 廣度優先走訪 | +| depth-first traversal | 深度优先遍历 | 深度優先走訪 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 | +| balance factor | 平衡因子 | 平衡因子 | +| heap | 堆 | 堆積 | +| max heap | 大顶堆 | 大頂堆積 | +| min heap | 小顶堆 | 小頂堆積 | +| priority queue | 优先队列 | 優先佇列 | +| heapify | 堆化 | 堆積化 | +| top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 | +| graph | 图 | 圖 | +| vertex | 顶点 | 頂點 | +| undirected graph | 无向图 | 無向圖 | +| directed graph | 有向图 | 有向圖 | +| connected graph | 连通图 | 連通圖 | +| disconnected graph | 非连通图 | 非連通圖 | +| weighted graph | 有权图 | 有權圖 | +| adjacency | 邻接 | 鄰接 | +| path | 路径 | 路徑 | +| in-degree | 入度 | 入度 | +| out-degree | 出度 | 出度 | +| adjacency matrix | 邻接矩阵 | 鄰接矩陣 | +| adjacency list | 邻接表 | 鄰接表 | +| breadth-first search | 广度优先搜索 | 廣度優先搜尋 | +| depth-first search | 深度优先搜索 | 深度優先搜尋 | +| binary search | 二分查找 | 二分搜尋 | +| searching algorithm | 搜索算法 | 搜尋演算法 | +| sorting algorithm | 排序算法 | 排序演算法 | +| selection sort | 选择排序 | 選擇排序 | +| bubble sort | 冒泡排序 | 泡沫排序 | +| insertion sort | 插入排序 | 插入排序 | +| quick sort | 快速排序 | 快速排序 | +| merge sort | 归并排序 | 合併排序 | +| heap sort | 堆排序 | 堆積排序 | +| bucket sort | 桶排序 | 桶排序 | +| counting sort | 计数排序 | 計數排序 | +| radix sort | 基数排序 | 基數排序 | +| divide and conquer | 分治 | 分治 | +| hanota problem | 汉诺塔问题 | 河內塔問題 | +| backtracking algorithm | 回溯算法 | 回溯演算法 | +| constraint | 约束 | 約束 | +| solution | 解 | 解 | +| state | 状态 | 狀態 | +| pruning | 剪枝 | 剪枝 | +| permutations problem | 全排列问题 | 全排列問題 | +| subset-sum problem | 子集和问题 | 子集合問題 | +| $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 | +| dynamic programming | 动态规划 | 動態規劃 | +| initial state | 初始状态 | 初始狀態 | +| state-transition equation | 状态转移方程 | 狀態轉移方程 | +| knapsack problem | 背包问题 | 背包問題 | +| edit distance problem | 编辑距离问题 | 編輯距離問題 | +| greedy algorithm | 贪心算法 | 貪婪演算法 | diff --git a/en/docs/chapter_array_and_linkedlist/array.assets/array_definition.png b/en/docs/chapter_array_and_linkedlist/array.assets/array_definition.png new file mode 100644 index 0000000000..97a67273dd Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/array.assets/array_definition.png differ diff --git a/en/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png b/en/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png new file mode 100644 index 0000000000..c9f81ea943 Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png differ diff --git a/en/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png b/en/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png new file mode 100644 index 0000000000..f24414b105 Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png differ diff --git a/en/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png b/en/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png new file mode 100644 index 0000000000..eb50cc17bc Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png differ diff --git a/en/docs/chapter_array_and_linkedlist/array.md b/en/docs/chapter_array_and_linkedlist/array.md new file mode 100755 index 0000000000..639f830185 --- /dev/null +++ b/en/docs/chapter_array_and_linkedlist/array.md @@ -0,0 +1,221 @@ +# Array + +An array is a linear data structure that operates as a lineup of similar items, stored together in a computer's memory in contiguous spaces. It's like a sequence that maintains organized storage. Each item in this lineup has its unique 'spot' known as an index. Please refer to the figure below to observe how arrays work and grasp these key terms. + +![Array definition and storage method](array.assets/array_definition.png) + +## Common operations on arrays + +### Initializing arrays + +Arrays can be initialized in two ways depending on the needs: either without initial values or with specified initial values. When initial values are not specified, most programming languages will set the array elements to $0$: + +=== "Python" + + ```python title="array.py" + # Initialize array + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* Initialize array */ + // Stored on stack + int arr[5]; + int nums[5] = { 1, 3, 2, 5, 4 }; + // Stored on heap (manual memory release needed) + int* arr1 = new int[5]; + int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="array.java" + /* Initialize array */ + int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } + int[] nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "C#" + + ```csharp title="array.cs" + /* Initialize array */ + int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] + int[] nums = [1, 3, 2, 5, 4]; + ``` + +=== "Go" + + ```go title="array.go" + /* Initialize array */ + var arr [5]int + // In Go, specifying the length ([5]int) denotes an array, while not specifying it ([]int) denotes a slice. + // Since Go's arrays are designed to have compile-time fixed length, only constants can be used to specify the length. + // For convenience in implementing the extend() method, the Slice will be considered as an Array here. + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="array.swift" + /* Initialize array */ + let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] + let nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="array.js" + /* Initialize array */ + var arr = new Array(5).fill(0); + var nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="array.ts" + /* Initialize array */ + let arr: number[] = new Array(5).fill(0); + let nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="array.dart" + /* Initialize array */ + List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="array.rs" + /* Initialize array */ + let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] + let slice: &[i32] = &[0; 5]; + // In Rust, specifying the length ([i32; 5]) denotes an array, while not specifying it (&[i32]) denotes a slice. + // Since Rust's arrays are designed to have compile-time fixed length, only constants can be used to specify the length. + // Vectors are generally used as dynamic arrays in Rust. + // For convenience in implementing the extend() method, the vector will be considered as an array here. + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="array.c" + /* Initialize array */ + int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } + int nums[5] = { 1, 3, 2, 5, 4 }; + ``` + +=== "Kotlin" + + ```kotlin title="array.kt" + + ``` + +=== "Zig" + + ```zig title="array.zig" + // Initialize array + var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 } + var nums = [_]i32{ 1, 3, 2, 5, 4 }; + ``` + +### Accessing elements + +Elements in an array are stored in contiguous memory spaces, making it simpler to compute each element's memory address. The formula shown in the Figure below aids in determining an element's memory address, utilizing the array's memory address (specifically, the first element's address) and the element's index. This computation streamlines direct access to the desired element. + +![Memory address calculation for array elements](array.assets/array_memory_location_calculation.png) + +As observed in the figure above, array indexing conventionally begins at $0$. While this might appear counterintuitive, considering counting usually starts at $1$, within the address calculation formula, **an index is essentially an offset from the memory address**. For the first element's address, this offset is $0$, validating its index as $0$. + +Accessing elements in an array is highly efficient, allowing us to randomly access any element in $O(1)$ time. + +```src +[file]{array}-[class]{}-[func]{random_access} +``` + +### Inserting elements + +Array elements are tightly packed in memory, with no space available to accommodate additional data between them. As illustrated in the figure below, inserting an element in the middle of an array requires shifting all subsequent elements back by one position to create room for the new element. + +![Array element insertion example](array.assets/array_insert_element.png) + +It's important to note that due to the fixed length of an array, inserting an element will unavoidably result in the loss of the last element in the array. Solutions to address this issue will be explored in the "List" chapter. + +```src +[file]{array}-[class]{}-[func]{insert} +``` + +### Deleting elements + +Similarly, as depicted in the figure below, to delete an element at index $i$, all elements following index $i$ must be moved forward by one position. + +![Array element deletion example](array.assets/array_remove_element.png) + +Please note that after deletion, the former last element becomes "meaningless," hence requiring no specific modification. + +```src +[file]{array}-[class]{}-[func]{remove} +``` + +In summary, the insertion and deletion operations in arrays present the following disadvantages: + +- **High time complexity**: Both insertion and deletion in an array have an average time complexity of $O(n)$, where $n$ is the length of the array. +- **Loss of elements**: Due to the fixed length of arrays, elements that exceed the array's capacity are lost during insertion. +- **Waste of memory**: Initializing a longer array and utilizing only the front part results in "meaningless" end elements during insertion, leading to some wasted memory space. + +### Traversing arrays + +In most programming languages, we can traverse an array either by using indices or by directly iterating over each element: + +```src +[file]{array}-[class]{}-[func]{traverse} +``` + +### Finding elements + +Locating a specific element within an array involves iterating through the array, checking each element to determine if it matches the desired value. + +Because arrays are linear data structures, this operation is commonly referred to as "linear search." + +```src +[file]{array}-[class]{}-[func]{find} +``` + +### Expanding arrays + +In complex system environments, ensuring the availability of memory space after an array for safe capacity extension becomes challenging. Consequently, in most programming languages, **the length of an array is immutable**. + +To expand an array, it's necessary to create a larger array and then copy the elements from the original array. This operation has a time complexity of $O(n)$ and can be time-consuming for large arrays. The code are as follows: + +```src +[file]{array}-[class]{}-[func]{extend} +``` + +## Advantages and limitations of arrays + +Arrays are stored in contiguous memory spaces and consist of elements of the same type. This approach provides substantial prior information that systems can leverage to optimize the efficiency of data structure operations. + +- **High space efficiency**: Arrays allocate a contiguous block of memory for data, eliminating the need for additional structural overhead. +- **Support for random access**: Arrays allow $O(1)$ time access to any element. +- **Cache locality**: When accessing array elements, the computer not only loads them but also caches the surrounding data, utilizing high-speed cache to enchance subsequent operation speeds. + +However, continuous space storage is a double-edged sword, with the following limitations: + +- **Low efficiency in insertion and deletion**: As arrays accumulate many elements, inserting or deleting elements requires shifting a large number of elements. +- **Fixed length**: The length of an array is fixed after initialization. Expanding an array requires copying all data to a new array, incurring significant costs. +- **Space wastage**: If the allocated array size exceeds the what is necessary, the extra space is wasted. + +## Typical applications of arrays + +Arrays are fundamental and widely used data structures. They find frequent application in various algorithms and serve in the implementation of complex data structures. + +- **Random access**: Arrays are ideal for storing data when random sampling is required. By generating a random sequence based on indices, we can achieve random sampling efficiently. +- **Sorting and searching**: Arrays are the most commonly used data structure for sorting and searching algorithms. Techniques like quick sort, merge sort, binary search, etc., are primarily operate on arrays. +- **Lookup tables**: Arrays serve as efficient lookup tables for quick element or relationship retrieval. For instance, mapping characters to ASCII codes becomes seamless by using the ASCII code values as indices and storing corresponding elements in the array. +- **Machine learning**: Within the domain of neural networks, arrays play a pivotal role in executing crucial linear algebra operations involving vectors, matrices, and tensors. Arrays serve as the primary and most extensively used data structure in neural network programming. +- **Data structure implementation**: Arrays serve as the building blocks for implementing various data structures like stacks, queues, hash tables, heaps, graphs, etc. For instance, the adjacency matrix representation of a graph is essentially a two-dimensional array. diff --git a/en/docs/chapter_array_and_linkedlist/index.md b/en/docs/chapter_array_and_linkedlist/index.md new file mode 100644 index 0000000000..764b753f2a --- /dev/null +++ b/en/docs/chapter_array_and_linkedlist/index.md @@ -0,0 +1,9 @@ +# Arrays and linked lists + +![Arrays and linked lists](../assets/covers/chapter_array_and_linkedlist.jpg) + +!!! abstract + + The world of data structures resembles a sturdy brick wall. + + In arrays, envision bricks snugly aligned, each resting seamlessly beside the next, creating a unified formation. Meanwhile, in linked lists, these bricks disperse freely, embraced by vines gracefully knitting connections between them. diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png new file mode 100644 index 0000000000..76dc542b69 Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png differ diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png new file mode 100644 index 0000000000..d6d9bee159 Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png differ diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png new file mode 100644 index 0000000000..f22930dcd8 Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png differ diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png new file mode 100644 index 0000000000..662e629b07 Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png differ diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.md b/en/docs/chapter_array_and_linkedlist/linked_list.md new file mode 100755 index 0000000000..6292b52846 --- /dev/null +++ b/en/docs/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,686 @@ +# Linked list + +Memory space is a shared resource among all programs. In a complex system environment, available memory can be dispersed throughout the memory space. We understand that the memory allocated for an array must be continuous. However, for very large arrays, finding a sufficiently large contiguous memory space might be challenging. This is where the flexible advantage of linked lists becomes evident. + +A linked list is a linear data structure in which each element is a node object, and the nodes are interconnected through "references". These references hold the memory addresses of subsequent nodes, enabling navigation from one node to the next. + +The design of linked lists allows for their nodes to be distributed across memory locations without requiring contiguous memory addresses. + +![Linked list definition and storage method](linked_list.assets/linkedlist_definition.png) + +As shown in the figure above, we see that the basic building block of a linked list is the node object. Each node comprises two key components: the node's "value" and a "reference" to the next node. + +- The first node in a linked list is the "head node", and the final one is the "tail node". +- The tail node points to "null", designated as `null` in Java, `nullptr` in C++, and `None` in Python. +- In languages that support pointers, like C, C++, Go, and Rust, this "reference" is typically implemented as a "pointer". + +As the code below illustrates, a `ListNode` in a linked list, besides holding a value, must also maintain an additional reference (or pointer). Therefore, **a linked list occupies more memory space than an array when storing the same quantity of data.**. + +=== "Python" + + ```python title="" + class ListNode: + """Linked list node class""" + def __init__(self, val: int): + self.val: int = val # Node value + self.next: ListNode | None = None # Reference to the next node + ``` + +=== "C++" + + ```cpp title="" + /* Linked list node structure */ + struct ListNode { + int val; // Node value + ListNode *next; // Pointer to the next node + ListNode(int x) : val(x), next(nullptr) {} // Constructor + }; + ``` + +=== "Java" + + ```java title="" + /* Linked list node class */ + class ListNode { + int val; // Node value + ListNode next; // Reference to the next node + ListNode(int x) { val = x; } // Constructor + } + ``` + +=== "C#" + + ```csharp title="" + /* Linked list node class */ + class ListNode(int x) { // Constructor + int val = x; // Node value + ListNode? next; // Reference to the next node + } + ``` + +=== "Go" + + ```go title="" + /* Linked list node structure */ + type ListNode struct { + Val int // Node value + Next *ListNode // Pointer to the next node + } + + // NewListNode Constructor, creates a new linked list + func NewListNode(val int) *ListNode { + return &ListNode{ + Val: val, + Next: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Linked list node class */ + class ListNode { + var val: Int // Node value + var next: ListNode? // Reference to the next node + + init(x: Int) { // Constructor + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Linked list node class */ + class ListNode { + constructor(val, next) { + this.val = (val === undefined ? 0 : val); // Node value + this.next = (next === undefined ? null : next); // Reference to the next node + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Linked list node class */ + class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; // Node value + this.next = next === undefined ? null : next; // Reference to the next node + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Linked list node class */ + class ListNode { + int val; // Node value + ListNode? next; // Reference to the next node + ListNode(this.val, [this.next]); // Constructor + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + /* Linked list node class */ + #[derive(Debug)] + struct ListNode { + val: i32, // Node value + next: Option>>, // Pointer to the next node + } + ``` + +=== "C" + + ```c title="" + /* Linked list node structure */ + typedef struct ListNode { + int val; // Node value + struct ListNode *next; // Pointer to the next node + } ListNode; + + /* Constructor */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + // Linked list node class + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, // Node value + next: ?*Self = null, // Pointer to the next node + + // Constructor + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; + } + ``` + +## Common operations on linked lists + +### Initializing a linked list + +Constructing a linked list is a two-step process: first, initializing each node object, and second, forming the reference links between the nodes. After initialization, we can traverse all nodes sequentially from the head node by following the `next` reference. + +=== "Python" + + ```python title="linked_list.py" + # Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 + # Initialize each node + n0 = ListNode(1) + n1 = ListNode(3) + n2 = ListNode(2) + n3 = ListNode(5) + n4 = ListNode(4) + # Build references between nodes + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "C++" + + ```cpp title="linked_list.cpp" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + ListNode* n0 = new ListNode(1); + ListNode* n1 = new ListNode(3); + ListNode* n2 = new ListNode(2); + ListNode* n3 = new ListNode(5); + ListNode* n4 = new ListNode(4); + // Build references between nodes + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + ``` + +=== "Java" + + ```java title="linked_list.java" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // Build references between nodes + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // Build references between nodes + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Go" + + ```go title="linked_list.go" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + // Build references between nodes + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + ``` + +=== "Swift" + + ```swift title="linked_list.swift" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // Build references between nodes + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "JS" + + ```javascript title="linked_list.js" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // Build references between nodes + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "TS" + + ```typescript title="linked_list.ts" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // Build references between nodes + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Dart" + + ```dart title="linked_list.dart" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // Build references between nodes + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Rust" + + ```rust title="linked_list.rs" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); + let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); + let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); + let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); + let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); + + // Build references between nodes + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + ``` + +=== "C" + + ```c title="linked_list.c" + /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */ + // Initialize each node + ListNode* n0 = newListNode(1); + ListNode* n1 = newListNode(3); + ListNode* n2 = newListNode(2); + ListNode* n3 = newListNode(5); + ListNode* n4 = newListNode(4); + // Build references between nodes + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + ``` + +=== "Kotlin" + + ```kotlin title="linked_list.kt" + + ``` + +=== "Zig" + + ```zig title="linked_list.zig" + // Initialize linked list + // Initialize each node + var n0 = inc.ListNode(i32){.val = 1}; + var n1 = inc.ListNode(i32){.val = 3}; + var n2 = inc.ListNode(i32){.val = 2}; + var n3 = inc.ListNode(i32){.val = 5}; + var n4 = inc.ListNode(i32){.val = 4}; + // Build references between nodes + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; + ``` + +The array as a whole is a variable, for instance, the array `nums` includes elements like `nums[0]`, `nums[1]`, and so on, whereas a linked list is made up of several distinct node objects. **We typically refer to a linked list by its head node**, for example, the linked list in the previous code snippet is referred to as `n0`. + +### Inserting nodes + +Inserting a node into a linked list is very easy. As shown in the figure below, let's assume we aim to insert a new node `P` between two adjacent nodes `n0` and `n1`. **This can be achieved by simply modifying two node references (pointers)**, with a time complexity of $O(1)$. + +By comparison, inserting an element into an array has a time complexity of $O(n)$, which becomes less efficient when dealing with large data volumes. + +![Linked list node insertion example](linked_list.assets/linkedlist_insert_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{insert} +``` + +### Deleting nodes + +As shown in the figure below, deleting a node from a linked list is also very easy, **involving only the modification of a single node's reference (pointer)**. + +It's important to note that even though node `P` continues to point to `n1` after being deleted, it becomes inaccessible during linked list traversal. This effectively means that `P` is no longer a part of the linked list. + +![Linked list node deletion](linked_list.assets/linkedlist_remove_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{remove} +``` + +### Accessing nodes + +**Accessing nodes in a linked list is less efficient**. As previously mentioned, any element in an array can be accessed in $O(1)$ time. In contrast, with a linked list, the program involves starting from the head node and sequentially traversing through the nodes until the desired node is found. In other words, to access the $i$-th node in a linked list, the program must iterate through $i - 1$ nodes, resulting in a time complexity of $O(n)$. + +```src +[file]{linked_list}-[class]{}-[func]{access} +``` + +### Finding nodes + +Traverse the linked list to locate a node whose value matches `target`, and then output the index of that node within the linked list. This procedure is also an example of linear search. The corresponding code is provided below: + +```src +[file]{linked_list}-[class]{}-[func]{find} +``` + +## Arrays vs. linked lists + +The table below summarizes the characteristics of arrays and linked lists, and it also compares their efficiencies in various operations. Because they utilize opposing storage strategies, their respective properties and operational efficiencies exhibit distinct contrasts. + +

Table   Efficiency comparison of arrays and linked lists

+ +| | Arrays | Linked Lists | +| ------------------ | ------------------------------------------------ | ----------------------- | +| Storage | Contiguous Memory Space | Dispersed Memory Space | +| Capacity Expansion | Fixed Length | Flexible Expansion | +| Memory Efficiency | Less Memory per Element, Potential Space Wastage | More Memory per Element | +| Accessing Elements | $O(1)$ | $O(n)$ | +| Adding Elements | $O(n)$ | $O(1)$ | +| Deleting Elements | $O(n)$ | $O(1)$ | + +## Common types of linked lists + +As shown in the figure below, there are three common types of linked lists. + +- **Singly linked list**: This is the standard linked list described earlier. Nodes in a singly linked list include a value and a reference to the next node. The first node is known as the head node, and the last node, which points to null (`None`), is the tail node. +- **Circular linked list**: This is formed when the tail node of a singly linked list points back to the head node, creating a loop. In a circular linked list, any node can function as the head node. +- **Doubly linked list**: In contrast to a singly linked list, a doubly linked list maintains references in two directions. Each node contains references (pointer) to both its successor (the next node) and predecessor (the previous node). Although doubly linked lists offer more flexibility for traversing in either direction, they also consume more memory space. + +=== "Python" + + ```python title="" + class ListNode: + """Bidirectional linked list node class""" + def __init__(self, val: int): + self.val: int = val # Node value + self.next: ListNode | None = None # Reference to the successor node + self.prev: ListNode | None = None # Reference to a predecessor node + ``` + +=== "C++" + + ```cpp title="" + /* Bidirectional linked list node structure */ + struct ListNode { + int val; // Node value + ListNode *next; // Pointer to the successor node + ListNode *prev; // Pointer to the predecessor node + ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // Constructor + }; + ``` + +=== "Java" + + ```java title="" + /* Bidirectional linked list node class */ + class ListNode { + int val; // Node value + ListNode next; // Reference to the next node + ListNode prev; // Reference to the predecessor node + ListNode(int x) { val = x; } // Constructor + } + ``` + +=== "C#" + + ```csharp title="" + /* Bidirectional linked list node class */ + class ListNode(int x) { // Constructor + int val = x; // Node value + ListNode next; // Reference to the next node + ListNode prev; // Reference to the predecessor node + } + ``` + +=== "Go" + + ```go title="" + /* Bidirectional linked list node structure */ + type DoublyListNode struct { + Val int // Node value + Next *DoublyListNode // Pointer to the successor node + Prev *DoublyListNode // Pointer to the predecessor node + } + + // NewDoublyListNode initialization + func NewDoublyListNode(val int) *DoublyListNode { + return &DoublyListNode{ + Val: val, + Next: nil, + Prev: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Bidirectional linked list node class */ + class ListNode { + var val: Int // Node value + var next: ListNode? // Reference to the next node + var prev: ListNode? // Reference to the predecessor node + + init(x: Int) { // Constructor + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Bidirectional linked list node class */ + class ListNode { + constructor(val, next, prev) { + this.val = val === undefined ? 0 : val; // Node value + this.next = next === undefined ? null : next; // Reference to the successor node + this.prev = prev === undefined ? null : prev; // Reference to the predecessor node + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Bidirectional linked list node class */ + class ListNode { + val: number; + next: ListNode | null; + prev: ListNode | null; + constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { + this.val = val === undefined ? 0 : val; // Node value + this.next = next === undefined ? null : next; // Reference to the successor node + this.prev = prev === undefined ? null : prev; // Reference to the predecessor node + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Bidirectional linked list node class */ + class ListNode { + int val; // Node value + ListNode next; // Reference to the next node + ListNode prev; // Reference to the predecessor node + ListNode(this.val, [this.next, this.prev]); // Constructor + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Bidirectional linked list node type */ + #[derive(Debug)] + struct ListNode { + val: i32, // Node value + next: Option>>, // Pointer to successor node + prev: Option>>, // Pointer to predecessor node + } + + /* Constructors */ + impl ListNode { + fn new(val: i32) -> Self { + ListNode { + val, + next: None, + prev: None, + } + } + } + ``` + +=== "C" + + ```c title="" + /* Bidirectional linked list node structure */ + typedef struct ListNode { + int val; // Node value + struct ListNode *next; // Pointer to the successor node + struct ListNode *prev; // Pointer to the predecessor node + } ListNode; + + /* Constructors */ + ListNode *newListNode(int val) { + ListNode *node, *next; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + node->prev = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + // Bidirectional linked list node class + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, // Node value + next: ?*Self = null, // Pointer to the successor node + prev: ?*Self = null, // Pointer to the predecessor node + + // Constructor + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; + } + ``` + +![Common types of linked lists](linked_list.assets/linkedlist_common_types.png) + +## Typical applications of linked lists + +Singly linked lists are frequently utilized in implementing stacks, queues, hash tables, and graphs. + +- **Stacks and queues**: In singly linked lists, if insertions and deletions occur at the same end, it behaves like a stack (last-in-first-out). Conversely, if insertions are at one end and deletions at the other, it functions like a queue (first-in-first-out). +- **Hash tables**: Linked lists are used in chaining, a popular method for resolving hash collisions. Here, all collided elements are grouped into a linked list. +- **Graphs**: Adjacency lists, a standard method for graph representation, associate each graph vertex with a linked list. This list contains elements that represent vertices connected to the corresponding vertex. + +Doubly linked lists are ideal for scenarios requiring rapid access to preceding and succeeding elements. + +- **Advanced data structures**: In structures like red-black trees and B-trees, accessing a node's parent is essential. This is achieved by incorporating a reference to the parent node in each node, akin to a doubly linked list. +- **Browser history**: In web browsers, doubly linked lists facilitate navigating the history of visited pages when users click forward or back. +- **LRU algorithm**: Doubly linked lists are apt for Least Recently Used (LRU) cache eviction algorithms, enabling swift identification of the least recently used data and facilitating fast node addition and removal. + +Circular linked lists are ideal for applications that require periodic operations, such as resource scheduling in operating systems. + +- **Round-robin scheduling algorithm**: In operating systems, the round-robin scheduling algorithm is a common CPU scheduling method, requiring cycling through a group of processes. Each process is assigned a time slice, and upon expiration, the CPU rotates to the next process. This cyclical operation can be efficiently realized using a circular linked list, allowing for a fair and time-shared system among all processes. +- **Data buffers**: Circular linked lists are also used in data buffers, like in audio and video players, where the data stream is divided into multiple buffer blocks arranged in a circular fashion for seamless playback. diff --git a/en/docs/chapter_array_and_linkedlist/list.md b/en/docs/chapter_array_and_linkedlist/list.md new file mode 100755 index 0000000000..6ab1068764 --- /dev/null +++ b/en/docs/chapter_array_and_linkedlist/list.md @@ -0,0 +1,906 @@ +# List + +A list is an abstract data structure concept that represents an ordered collection of elements, supporting operations such as element access, modification, addition, deletion, and traversal, without requiring users to consider capacity limitations. Lists can be implemented based on linked lists or arrays. + +- A linked list inherently serves as a list, supporting operations for adding, deleting, searching, and modifying elements, with the flexibility to dynamically adjust its size. +- Arrays also support these operations, but due to their immutable length, they can be considered as a list with a length limit. + +When implementing lists using arrays, **the immutability of length reduces the practicality of the list**. This is because predicting the amount of data to be stored in advance is often challenging, making it difficult to choose an appropriate list length. If the length is too small, it may not meet the requirements; if too large, it may waste memory space. + +To solve this problem, we can implement lists using a dynamic array. It inherits the advantages of arrays and can dynamically expand during program execution. + +In fact, **many programming languages' standard libraries implement lists using dynamic arrays**, such as Python's `list`, Java's `ArrayList`, C++'s `vector`, and C#'s `List`. In the following discussion, we will consider "list" and "dynamic array" as synonymous concepts. + +## Common list operations + +### Initializing a list + +We typically use two initialization methods: "without initial values" and "with initial values". + +=== "Python" + + ```python title="list.py" + # Initialize list + # Without initial values + nums1: list[int] = [] + # With initial values + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Initialize list */ + // Note, in C++ the vector is the equivalent of nums described here + // Without initial values + vector nums1; + // With initial values + vector nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="list.java" + /* Initialize list */ + // Without initial values + List nums1 = new ArrayList<>(); + // With initial values (note the element type should be the wrapper class Integer[] for int[]) + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Initialize list */ + // Without initial values + List nums1 = []; + // With initial values + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + ``` + +=== "Go" + + ```go title="list_test.go" + /* Initialize list */ + // Without initial values + nums1 := []int{} + // With initial values + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Initialize list */ + // Without initial values + let nums1: [Int] = [] + // With initial values + var nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="list.js" + /* Initialize list */ + // Without initial values + const nums1 = []; + // With initial values + const nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Initialize list */ + // Without initial values + const nums1: number[] = []; + // With initial values + const nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Initialize list */ + // Without initial values + List nums1 = []; + // With initial values + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Initialize list */ + // Without initial values + let nums1: Vec = Vec::new(); + // With initial values + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="list.c" + // C does not provide built-in dynamic arrays + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + + ``` + +=== "Zig" + + ```zig title="list.zig" + // Initialize list + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + ``` + +### Accessing elements + +Lists are essentially arrays, thus they can access and update elements in $O(1)$ time, which is very efficient. + +=== "Python" + + ```python title="list.py" + # Access elements + num: int = nums[1] # Access the element at index 1 + + # Update elements + nums[1] = 0 # Update the element at index 1 to 0 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Access elements */ + int num = nums[1]; // Access the element at index 1 + + /* Update elements */ + nums[1] = 0; // Update the element at index 1 to 0 + ``` + +=== "Java" + + ```java title="list.java" + /* Access elements */ + int num = nums.get(1); // Access the element at index 1 + + /* Update elements */ + nums.set(1, 0); // Update the element at index 1 to 0 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Access elements */ + int num = nums[1]; // Access the element at index 1 + + /* Update elements */ + nums[1] = 0; // Update the element at index 1 to 0 + ``` + +=== "Go" + + ```go title="list_test.go" + /* Access elements */ + num := nums[1] // Access the element at index 1 + + /* Update elements */ + nums[1] = 0 // Update the element at index 1 to 0 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Access elements */ + let num = nums[1] // Access the element at index 1 + + /* Update elements */ + nums[1] = 0 // Update the element at index 1 to 0 + ``` + +=== "JS" + + ```javascript title="list.js" + /* Access elements */ + const num = nums[1]; // Access the element at index 1 + + /* Update elements */ + nums[1] = 0; // Update the element at index 1 to 0 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Access elements */ + const num: number = nums[1]; // Access the element at index 1 + + /* Update elements */ + nums[1] = 0; // Update the element at index 1 to 0 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Access elements */ + int num = nums[1]; // Access the element at index 1 + + /* Update elements */ + nums[1] = 0; // Update the element at index 1 to 0 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Access elements */ + let num: i32 = nums[1]; // Access the element at index 1 + /* Update elements */ + nums[1] = 0; // Update the element at index 1 to 0 + ``` + +=== "C" + + ```c title="list.c" + // C does not provide built-in dynamic arrays + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + + ``` + +=== "Zig" + + ```zig title="list.zig" + // Access elements + var num = nums.items[1]; // Access the element at index 1 + + // Update elements + nums.items[1] = 0; // Update the element at index 1 to 0 + ``` + +### Inserting and removing elements + +Compared to arrays, lists offer more flexibility in adding and removing elements. While adding elements to the end of a list is an $O(1)$ operation, the efficiency of inserting and removing elements elsewhere in the list remains the same as in arrays, with a time complexity of $O(n)$. + +=== "Python" + + ```python title="list.py" + # Clear list + nums.clear() + + # Append elements at the end + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + # Insert element in the middle + nums.insert(3, 6) # Insert number 6 at index 3 + + # Remove elements + nums.pop(3) # Remove the element at index 3 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Clear list */ + nums.clear(); + + /* Append elements at the end */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + + /* Insert element in the middle */ + nums.insert(nums.begin() + 3, 6); // Insert number 6 at index 3 + + /* Remove elements */ + nums.erase(nums.begin() + 3); // Remove the element at index 3 + ``` + +=== "Java" + + ```java title="list.java" + /* Clear list */ + nums.clear(); + + /* Append elements at the end */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* Insert element in the middle */ + nums.add(3, 6); // Insert number 6 at index 3 + + /* Remove elements */ + nums.remove(3); // Remove the element at index 3 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Clear list */ + nums.Clear(); + + /* Append elements at the end */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + + /* Insert element in the middle */ + nums.Insert(3, 6); + + /* Remove elements */ + nums.RemoveAt(3); + ``` + +=== "Go" + + ```go title="list_test.go" + /* Clear list */ + nums = nil + + /* Append elements at the end */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + + /* Insert element in the middle */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert number 6 at index 3 + + /* Remove elements */ + nums = append(nums[:3], nums[4:]...) // Remove the element at index 3 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Clear list */ + nums.removeAll() + + /* Append elements at the end */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + /* Insert element in the middle */ + nums.insert(6, at: 3) // Insert number 6 at index 3 + + /* Remove elements */ + nums.remove(at: 3) // Remove the element at index 3 + ``` + +=== "JS" + + ```javascript title="list.js" + /* Clear list */ + nums.length = 0; + + /* Append elements at the end */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* Insert element in the middle */ + nums.splice(3, 0, 6); + + /* Remove elements */ + nums.splice(3, 1); + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Clear list */ + nums.length = 0; + + /* Append elements at the end */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* Insert element in the middle */ + nums.splice(3, 0, 6); + + /* Remove elements */ + nums.splice(3, 1); + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Clear list */ + nums.clear(); + + /* Append elements at the end */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* Insert element in the middle */ + nums.insert(3, 6); // Insert number 6 at index 3 + + /* Remove elements */ + nums.removeAt(3); // Remove the element at index 3 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Clear list */ + nums.clear(); + + /* Append elements at the end */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* Insert element in the middle */ + nums.insert(3, 6); // Insert number 6 at index 3 + + /* Remove elements */ + nums.remove(3); // Remove the element at index 3 + ``` + +=== "C" + + ```c title="list.c" + // C does not provide built-in dynamic arrays + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + + ``` + +=== "Zig" + + ```zig title="list.zig" + // Clear list + nums.clearRetainingCapacity(); + + // Append elements at the end + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + + // Insert element in the middle + try nums.insert(3, 6); // Insert number 6 at index 3 + + // Remove elements + _ = nums.orderedRemove(3); // Remove the element at index 3 + ``` + +### Iterating the list + +Similar to arrays, lists can be iterated either by using indices or by directly iterating through each element. + +=== "Python" + + ```python title="list.py" + # Iterate through the list by index + count = 0 + for i in range(len(nums)): + count += nums[i] + + # Iterate directly through list elements + for num in nums: + count += num + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Iterate through the list by index */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + + /* Iterate directly through list elements */ + count = 0; + for (int num : nums) { + count += num; + } + ``` + +=== "Java" + + ```java title="list.java" + /* Iterate through the list by index */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + + /* Iterate directly through list elements */ + for (int num : nums) { + count += num; + } + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Iterate through the list by index */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + + /* Iterate directly through list elements */ + count = 0; + foreach (int num in nums) { + count += num; + } + ``` + +=== "Go" + + ```go title="list_test.go" + /* Iterate through the list by index */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + + /* Iterate directly through list elements */ + count = 0 + for _, num := range nums { + count += num + } + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Iterate through the list by index */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + + /* Iterate directly through list elements */ + count = 0 + for num in nums { + count += num + } + ``` + +=== "JS" + + ```javascript title="list.js" + /* Iterate through the list by index */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* Iterate directly through list elements */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Iterate through the list by index */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* Iterate directly through list elements */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Iterate through the list by index */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* Iterate directly through list elements */ + count = 0; + for (var num in nums) { + count += num; + } + ``` + +=== "Rust" + + ```rust title="list.rs" + // Iterate through the list by index + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + + // Iterate directly through list elements + _count = 0; + for num in &nums { + _count += num; + } + ``` + +=== "C" + + ```c title="list.c" + // C does not provide built-in dynamic arrays + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + + ``` + +=== "Zig" + + ```zig title="list.zig" + // Iterate through the list by index + var count: i32 = 0; + var i: i32 = 0; + while (i < nums.items.len) : (i += 1) { + count += nums[i]; + } + + // Iterate directly through list elements + count = 0; + for (nums.items) |num| { + count += num; + } + ``` + +### Concatenating lists + +Given a new list `nums1`, we can append it to the end of the original list. + +=== "Python" + + ```python title="list.py" + # Concatenate two lists + nums1: list[int] = [6, 8, 7, 10, 9] + nums += nums1 # Concatenate nums1 to the end of nums + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Concatenate two lists */ + vector nums1 = { 6, 8, 7, 10, 9 }; + // Concatenate nums1 to the end of nums + nums.insert(nums.end(), nums1.begin(), nums1.end()); + ``` + +=== "Java" + + ```java title="list.java" + /* Concatenate two lists */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); // Concatenate nums1 to the end of nums + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Concatenate two lists */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); // Concatenate nums1 to the end of nums + ``` + +=== "Go" + + ```go title="list_test.go" + /* Concatenate two lists */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // Concatenate nums1 to the end of nums + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Concatenate two lists */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) // Concatenate nums1 to the end of nums + ``` + +=== "JS" + + ```javascript title="list.js" + /* Concatenate two lists */ + const nums1 = [6, 8, 7, 10, 9]; + nums.push(...nums1); // Concatenate nums1 to the end of nums + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Concatenate two lists */ + const nums1: number[] = [6, 8, 7, 10, 9]; + nums.push(...nums1); // Concatenate nums1 to the end of nums + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Concatenate two lists */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); // Concatenate nums1 to the end of nums + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Concatenate two lists */ + let nums1: Vec = vec![6, 8, 7, 10, 9]; + nums.extend(nums1); + ``` + +=== "C" + + ```c title="list.c" + // C does not provide built-in dynamic arrays + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + + ``` + +=== "Zig" + + ```zig title="list.zig" + // Concatenate two lists + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); // Concatenate nums1 to the end of nums + ``` + +### Sorting the list + +Once the list is sorted, we can employ algorithms commonly used in array-related algorithm problems, such as "binary search" and "two-pointer" algorithms. + +=== "Python" + + ```python title="list.py" + # Sort the list + nums.sort() # After sorting, the list elements are in ascending order + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Sort the list */ + sort(nums.begin(), nums.end()); // After sorting, the list elements are in ascending order + ``` + +=== "Java" + + ```java title="list.java" + /* Sort the list */ + Collections.sort(nums); // After sorting, the list elements are in ascending order + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Sort the list */ + nums.Sort(); // After sorting, the list elements are in ascending order + ``` + +=== "Go" + + ```go title="list_test.go" + /* Sort the list */ + sort.Ints(nums) // After sorting, the list elements are in ascending order + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Sort the list */ + nums.sort() // After sorting, the list elements are in ascending order + ``` + +=== "JS" + + ```javascript title="list.js" + /* Sort the list */ + nums.sort((a, b) => a - b); // After sorting, the list elements are in ascending order + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Sort the list */ + nums.sort((a, b) => a - b); // After sorting, the list elements are in ascending order + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Sort the list */ + nums.sort(); // After sorting, the list elements are in ascending order + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Sort the list */ + nums.sort(); // After sorting, the list elements are in ascending order + ``` + +=== "C" + + ```c title="list.c" + // C does not provide built-in dynamic arrays + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + + ``` + +=== "Zig" + + ```zig title="list.zig" + // Sort the list + std.sort.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + ``` + +## List implementation + +Many programming languages come with built-in lists, including Java, C++, Python, etc. Their implementations tend to be intricate, featuring carefully considered settings for various parameters, like initial capacity and expansion factors. Readers who are curious can delve into the source code for further learning. + +To enhance our understanding of how lists work, we will attempt to implement a simplified version of a list, focusing on three crucial design aspects: + +- **Initial capacity**: Choose a reasonable initial capacity for the array. In this example, we choose 10 as the initial capacity. +- **Size recording**: Declare a variable `size` to record the current number of elements in the list, updating in real-time with element insertion and deletion. With this variable, we can locate the end of the list and determine whether expansion is needed. +- **Expansion mechanism**: If the list reaches full capacity upon an element insertion, an expansion process is required. This involves creating a larger array based on the expansion factor, and then transferring all elements from the current array to the new one. In this example, we stipulate that the array size should double with each expansion. + +```src +[file]{my_list}-[class]{my_list}-[func]{} +``` diff --git a/en/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png b/en/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png new file mode 100644 index 0000000000..e923af240a Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png differ diff --git a/en/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png b/en/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png new file mode 100644 index 0000000000..479dac0b59 Binary files /dev/null and b/en/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png differ diff --git a/en/docs/chapter_array_and_linkedlist/ram_and_cache.md b/en/docs/chapter_array_and_linkedlist/ram_and_cache.md new file mode 100644 index 0000000000..eadfcd5b4e --- /dev/null +++ b/en/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -0,0 +1,71 @@ +# Memory and cache * + +In the first two sections of this chapter, we explored arrays and linked lists, two fundamental data structures that represent "continuous storage" and "dispersed storage," respectively. + +In fact, **the physical structure largely determines how efficiently a program utilizes memory and cache**, which in turn affects the overall performance of the algorithm. + +## Computer storage devices + +There are three types of storage devices in computers: hard disk, random-access memory (RAM), and cache memory. The following table shows their respective roles and performance characteristics in computer systems. + +

Table   Computer storage devices

+ +| | Hard Disk | Memory | Cache | +| ---------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| Usage | Long-term storage of data, including OS, programs, files, etc. | Temporary storage of currently running programs and data being processed | Stores frequently accessed data and instructions, reducing the number of CPU accesses to memory | +| Volatility | Data is not lost after power off | Data is lost after power off | Data is lost after power off | +| Capacity | Larger, TB level | Smaller, GB level | Very small, MB level | +| Speed | Slower, several hundred to thousands MB/s | Faster, several tens of GB/s | Very fast, several tens to hundreds of GB/s | +| Price | Cheaper, a few cents to a few dollars / GB | More expensive, tens to hundreds of dollars / GB | Very expensive, priced with CPU | + +The computer storage system can be visualized as a pyramid, as shown in the figure below. The storage devices at the top of the pyramid are faster, have smaller capacities, and are more expensive. This multi-level design is not accidental, but a deliberate outcome of careful consideration by computer scientists and engineers. + +- **Replacing hard disks with memory is challenging**. Firstly, data in memory is lost after power off, making it unsuitable for long-term data storage; secondly, memory is significantly more expensive than hard disks, limiting its feasibility for widespread use in the consumer market. +- **Caches face a trade-off between large capacity and high speed**. As the capacity of L1, L2, and L3 caches increases, their physical size grows, increasing the distance from the CPU core. This results in longer data transfer times and higher access latency. With current technology, a multi-level cache structure provides the optimal balance between capacity, speed, and cost. + +![Computer storage system](ram_and_cache.assets/storage_pyramid.png) + +!!! tip + + The storage hierarchy in computers reflects a careful balance between speed, capacity, and cost. This type of trade-off is common across various industries, where finding the optimal balance between benefits and limitations is essential. + +Overall, **hard disks provide long-term storage for large volumes of data, memory serves as temporary storage for data being processed during program execution, and cache stores frequently accessed data and instructions to enhance execution efficiency**. Together, they ensure the efficient operation of computer systems. + +As shown in the figure below, during program execution, data is read from the hard disk into memory for CPU computation. The cache, acting as an extension of the CPU, **intelligently preloads data from memory**, enabling faster data access for the CPU. This greatly improves program execution efficiency while reducing reliance on slower memory. + +![Data flow between hard disk, memory, and cache](ram_and_cache.assets/computer_storage_devices.png) + +## Memory efficiency of data structures + +In terms of memory space utilization, arrays and linked lists have their advantages and limitations. + +On one hand, **memory is limited and cannot be shared by multiple programs**, so optimizing space usage in data structures is crucial. Arrays are space-efficient because their elements are tightly packed, without requiring extra memory for references (pointers) as in linked lists. However, arrays require pre-allocating a contiguous block of memory, which can lead to waste if the allocated space exceeds the actual need. Expanding an array also incurs additional time and space overhead. In contrast, linked lists allocate and free memory dynamically for each node, offering greater flexibility at the cost of additional memory for pointers. + +On the other hand, during program execution, **repeated memory allocation and deallocation increase memory fragmentation**, reducing memory utilization efficiency. Arrays, due to their continuous storage method, are relatively less likely to cause memory fragmentation. In contrast, linked lists store elements in non-contiguous locations, and frequent insertions and deletions can exacerbate memory fragmentation. + +## Cache efficiency of data structures + +Although caches are much smaller in space capacity than memory, they are much faster and play a crucial role in program execution speed. Due to their limited capacity, caches can only store a subset of frequently accessed data. When the CPU attempts to access data not present in the cache, a cache miss occurs, requiring the CPU to retrieve the needed data from slower memory, which can impact performance. + +Clearly, **the fewer the cache misses, the higher the CPU's data read-write efficiency**, and the better the program performance. The proportion of successful data retrieval from the cache by the CPU is called the cache hit rate, a metric often used to measure cache efficiency. + +To achieve higher efficiency, caches adopt the following data loading mechanisms. + +- **Cache lines**: Caches operate by storing and loading data in units called cache lines, rather than individual bytes. This approach improves efficiency by transferring larger blocks of data at once. +- **Prefetch mechanism**: Processors predict data access patterns (e.g., sequential or fixed-stride access) and preload data into the cache based on these patterns to increase the cache hit rate. +- **Spatial locality**: When a specific piece of data is accessed, nearby data is likely to be accessed soon. To leverage this, caches load adjacent data along with the requested data, improving hit rates. +- **Temporal locality**: If data is accessed, it's likely to be accessed again in the near future. Caches use this principle to retain recently accessed data to improve the hit rate. + +In fact, **arrays and linked lists have different cache utilization efficiencies**, which is mainly reflected in the following aspects. + +- **Occupied space**: Linked list elements take up more space than array elements, resulting in less effective data being held in the cache. +- **Cache lines**: Linked list data is scattered throughout the memory, and cache is "loaded by row", so the proportion of invalid data loaded is higher. +- **Prefetch mechanism**: The data access pattern of arrays is more "predictable" than that of linked lists, that is, it is easier for the system to guess the data that is about to be loaded. +- **Spatial locality**: Arrays are stored in a continuous memory space, so data near the data being loaded is more likely to be accessed soon. + +Overall, **arrays have a higher cache hit rate and are generally more efficient in operation than linked lists**. This makes data structures based on arrays more popular in solving algorithmic problems. + +It should be noted that **high cache efficiency does not mean that arrays are always better than linked lists**. The choice of data structure should depend on specific application requirements. For example, both arrays and linked lists can implement the "stack" data structure (which will be detailed in the next chapter), but they are suitable for different scenarios. + +- In algorithm problems, we tend to choose stacks based on arrays because they provide higher operational efficiency and random access capabilities, with the only cost being the need to pre-allocate a certain amount of memory space for the array. +- If the data volume is very large, highly dynamic, and the expected size of the stack is difficult to estimate, then a stack based on a linked list is a better choice. Linked lists can distribute a large amount of data in different parts of the memory and avoid the additional overhead of array expansion. diff --git a/en/docs/chapter_array_and_linkedlist/summary.md b/en/docs/chapter_array_and_linkedlist/summary.md new file mode 100644 index 0000000000..73b021c7ad --- /dev/null +++ b/en/docs/chapter_array_and_linkedlist/summary.md @@ -0,0 +1,81 @@ +# Summary + +### Key review + +- Arrays and linked lists are two basic data structures, representing two storage methods in computer memory: contiguous space storage and non-contiguous space storage. Their characteristics complement each other. +- Arrays support random access and use less memory; however, they are inefficient in inserting and deleting elements and have a fixed length after initialization. +- Linked lists implement efficient node insertion and deletion through changing references (pointers) and can flexibly adjust their length; however, they have lower node access efficiency and consume more memory. +- Common types of linked lists include singly linked lists, circular linked lists, and doubly linked lists, each with its own application scenarios. +- Lists are ordered collections of elements that support addition, deletion, and modification, typically implemented based on dynamic arrays, retaining the advantages of arrays while allowing flexible length adjustment. +- The advent of lists significantly enhanced the practicality of arrays but may lead to some memory space wastage. +- During program execution, data is mainly stored in memory. Arrays provide higher memory space efficiency, while linked lists are more flexible in memory usage. +- Caches provide fast data access to CPUs through mechanisms like cache lines, prefetching, spatial locality, and temporal locality, significantly enhancing program execution efficiency. +- Due to higher cache hit rates, arrays are generally more efficient than linked lists. When choosing a data structure, the appropriate choice should be made based on specific needs and scenarios. + +### Q & A + +**Q**: Does storing arrays on the stack versus the heap affect time and space efficiency? + +Arrays stored on both the stack and heap are stored in contiguous memory spaces, and data operation efficiency is essentially the same. However, stacks and heaps have their own characteristics, leading to the following differences. + +1. Allocation and release efficiency: The stack is a smaller memory block, allocated automatically by the compiler; the heap memory is relatively larger and can be dynamically allocated in the code, more prone to fragmentation. Therefore, allocation and release operations on the heap are generally slower than on the stack. +2. Size limitation: Stack memory is relatively small, while the heap size is generally limited by available memory. Therefore, the heap is more suitable for storing large arrays. +3. Flexibility: The size of arrays on the stack needs to be determined at compile-time, while the size of arrays on the heap can be dynamically determined at runtime. + +**Q**: Why do arrays require elements of the same type, while linked lists do not emphasize same-type elements? + +Linked lists consist of nodes connected by references (pointers), and each node can store data of different types, such as int, double, string, object, etc. + +In contrast, array elements must be of the same type, allowing the calculation of offsets to access the corresponding element positions. For example, an array containing both int and long types, with single elements occupying 4 bytes and 8 bytes respectively, cannot use the following formula to calculate offsets, as the array contains elements of two different lengths. + +```shell +# Element memory address = array memory address + element length * element index +``` + +**Q**: After deleting a node, is it necessary to set `P.next` to `None`? + +Not modifying `P.next` is also acceptable. From the perspective of the linked list, traversing from the head node to the tail node will no longer encounter `P`. This means that node `P` has been effectively removed from the list, and where `P` points no longer affects the list. + +From a garbage collection perspective, for languages with automatic garbage collection mechanisms like Java, Python, and Go, whether node `P` is collected depends on whether there are still references pointing to it, not on the value of `P.next`. In languages like C and C++, we need to manually free the node's memory. + +**Q**: In linked lists, the time complexity for insertion and deletion operations is `O(1)`. But searching for the element before insertion or deletion takes `O(n)` time, so why isn't the time complexity `O(n)`? + +If an element is searched first and then deleted, the time complexity is indeed `O(n)`. However, the `O(1)` advantage of linked lists in insertion and deletion can be realized in other applications. For example, in the implementation of double-ended queues using linked lists, we maintain pointers always pointing to the head and tail nodes, making each insertion and deletion operation `O(1)`. + +**Q**: In the figure "Linked List Definition and Storage Method", do the light blue storage nodes occupy a single memory address, or do they share half with the node value? + +The figure is just a qualitative representation; quantitative analysis depends on specific situations. + +- Different types of node values occupy different amounts of space, such as int, long, double, and object instances. +- The memory space occupied by pointer variables depends on the operating system and compilation environment used, usually 8 bytes or 4 bytes. + +**Q**: Is adding elements to the end of a list always `O(1)`? + +If adding an element exceeds the list length, the list needs to be expanded first. The system will request a new memory block and move all elements of the original list over, in which case the time complexity becomes `O(n)`. + +**Q**: The statement "The emergence of lists greatly improves the practicality of arrays, but may lead to some memory space wastage" - does this refer to the memory occupied by additional variables like capacity, length, and expansion multiplier? + +The space wastage here mainly refers to two aspects: on the one hand, lists are set with an initial length, which we may not always need; on the other hand, to prevent frequent expansion, expansion usually multiplies by a coefficient, such as $\times 1.5$. This results in many empty slots, which we typically cannot fully fill. + +**Q**: In Python, after initializing `n = [1, 2, 3]`, the addresses of these 3 elements are contiguous, but initializing `m = [2, 1, 3]` shows that each element's `id` is not consecutive but identical to those in `n`. If the addresses of these elements are not contiguous, is `m` still an array? + +If we replace list elements with linked list nodes `n = [n1, n2, n3, n4, n5]`, these 5 node objects are also typically dispersed throughout memory. However, given a list index, we can still access the node's memory address in `O(1)` time, thereby accessing the corresponding node. This is because the array stores references to the nodes, not the nodes themselves. + +Unlike many languages, in Python, numbers are also wrapped as objects, and lists store references to these numbers, not the numbers themselves. Therefore, we find that the same number in two arrays has the same `id`, and these numbers' memory addresses need not be contiguous. + +**Q**: The `std::list` in C++ STL has already implemented a doubly linked list, but it seems that some algorithm books don't directly use it. Is there any limitation? + +On the one hand, we often prefer to use arrays to implement algorithms, only using linked lists when necessary, mainly for two reasons. + +- Space overhead: Since each element requires two additional pointers (one for the previous element and one for the next), `std::list` usually occupies more space than `std::vector`. +- Cache unfriendly: As the data is not stored continuously, `std::list` has a lower cache utilization rate. Generally, `std::vector` performs better. + +On the other hand, linked lists are primarily necessary for binary trees and graphs. Stacks and queues are often implemented using the programming language's `stack` and `queue` classes, rather than linked lists. + +**Q**: Does initializing a list `res = [0] * self.size()` result in each element of `res` referencing the same address? + +No. However, this issue arises with two-dimensional arrays, for example, initializing a two-dimensional list `res = [[0]] * self.size()` would reference the same list `[0]` multiple times. + +**Q**: In deleting a node, is it necessary to break the reference to its successor node? + +From the perspective of data structures and algorithms (problem-solving), it's okay not to break the link, as long as the program's logic is correct. From the perspective of standard libraries, breaking the link is safer and more logically clear. If the link is not broken, and the deleted node is not properly recycled, it could affect the recycling of the successor node's memory. diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png new file mode 100644 index 0000000000..23d0622729 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png new file mode 100644 index 0000000000..eeb8edc5cb Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png new file mode 100644 index 0000000000..ffb4509d19 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png new file mode 100644 index 0000000000..c79b452477 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png new file mode 100644 index 0000000000..2d38db2381 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png new file mode 100644 index 0000000000..75b9a8bef2 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png new file mode 100644 index 0000000000..624078c545 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png new file mode 100644 index 0000000000..c997f49375 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png new file mode 100644 index 0000000000..af9ec5c830 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png new file mode 100644 index 0000000000..8810966f15 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png new file mode 100644 index 0000000000..140db2496d Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png new file mode 100644 index 0000000000..30d818277c Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png new file mode 100644 index 0000000000..4c545f7634 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png new file mode 100644 index 0000000000..cc53a2ed13 Binary files /dev/null and b/en/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png differ diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.md b/en/docs/chapter_backtracking/backtracking_algorithm.md new file mode 100644 index 0000000000..7f4bfbb5e5 --- /dev/null +++ b/en/docs/chapter_backtracking/backtracking_algorithm.md @@ -0,0 +1,489 @@ +# Backtracking algorithms + +Backtracking algorithm is a method to solve problems by exhaustive search. Its core concept is to start from an initial state and brutally search for all possible solutions. The algorithm records the correct ones until a solution is found or all possible solutions have been tried but no solution can be found. + +Backtracking typically employs "depth-first search" to traverse the solution space. In the "Binary tree" chapter, we mentioned that pre-order, in-order, and post-order traversals are all depth-first searches. Next, we are going to use pre-order traversal to solve a backtracking problem. This helps us to understand how the algorithm works gradually. + +!!! question "Example One" + + Given a binary tree, search and record all nodes with a value of $7$ and return them in a list. + +To solve this problem, we traverse this tree in pre-order and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The process is shown in the figure below: + +```src +[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} +``` + +![Searching nodes in pre-order traversal](backtracking_algorithm.assets/preorder_find_nodes.png) + +## Trial and retreat + +**It is called a backtracking algorithm because it uses a "trial" and "retreat" strategy when searching the solution space**. During the search, whenever it encounters a state where it can no longer proceed to obtain a satisfying solution, it undoes the previous choice and reverts to the previous state so that other possible choices can be chosen for the next attempt. + +In Example One, visiting each node starts a "trial". And passing a leaf node or the `return` statement to going back to the parent node suggests "retreat". + +It's worth noting that **retreat is not merely about function returns**. We'll expand slightly on Example One question to explain what it means. + +!!! question "Example Two" + + In a binary tree, search for all nodes with a value of $7$ and for all matching nodes, **please return the paths from the root node to that node**. + +Based on the code from Example One, we need to use a list called `path` to record the visited node paths. When a node with a value of $7$ is reached, we copy `path` and add it to the result list `res`. After the traversal, `res` holds all the solutions. The code is as shown: + +```src +[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} +``` + +In each "trial", we record the path by adding the current node to `path`. Whenever we need to "retreat", we pop the node from `path` **to restore the state prior to this failed attempt**. + +By observing the process shown in the figure below, **the trial is like "advancing", and retreat is like "undoing"**. The later pairs can be seen as a reverse operation to their counterpart. + +=== "<1>" + ![Trying and retreating](backtracking_algorithm.assets/preorder_find_paths_step1.png) + +=== "<2>" + ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) + +=== "<3>" + ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) + +=== "<4>" + ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) + +=== "<5>" + ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) + +=== "<6>" + ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) + +=== "<7>" + ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) + +=== "<8>" + ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) + +=== "<9>" + ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) + +=== "<10>" + ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) + +=== "<11>" + ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) + +## Prune + +Complex backtracking problems usually involve one or more constraints, **which are often used for "pruning"**. + +!!! question "Example Three" + + In a binary tree, search for all nodes with a value of $7$ and return the paths from the root to these nodes, **with restriction that the paths do not contain nodes with a value of $3$**. + +To meet the above constraints, **we need to add a pruning operation**: during the search process, if a node with a value of $3$ is encountered, it aborts further searching down through the path immediately. The code is as shown: + +```src +[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} +``` + +"Pruning" is a very vivid noun. As shown in the figure below, in the search process, **we "cut off" the search branches that do not meet the constraints**. It avoids further unnecessary attempts, thus enhances the search efficiency. + +![Pruning based on constraints](backtracking_algorithm.assets/preorder_find_constrained_paths.png) + +## Framework code + +Now, let's try to distill the main framework of "trial, retreat, and prune" from backtracking to enhance the code's universality. + +In the following framework code, `state` represents the current state of the problem, `choices` represents the choices available under the current state: + +=== "Python" + + ```python title="" + def backtrack(state: State, choices: list[choice], res: list[state]): + """Backtracking algorithm framework""" + # Check if it's a solution + if is_solution(state): + # Record the solution + record_solution(state, res) + # Stop searching + return + # Iterate through all choices + for choice in choices: + # Prune: check if the choice is valid + if is_valid(state, choice): + # Trial: make a choice, update the state + make_choice(state, choice) + backtrack(state, choices, res) + # Retreat: undo the choice, revert to the previous state + undo_choice(state, choice) + ``` + +=== "C++" + + ```cpp title="" + /* Backtracking algorithm framework */ + void backtrack(State *state, vector &choices, vector &res) { + // Check if it's a solution + if (isSolution(state)) { + // Record the solution + recordSolution(state, res); + // Stop searching + return; + } + // Iterate through all choices + for (Choice choice : choices) { + // Prune: check if the choice is valid + if (isValid(state, choice)) { + // Trial: make a choice, update the state + makeChoice(state, choice); + backtrack(state, choices, res); + // Retreat: undo the choice, revert to the previous state + undoChoice(state, choice); + } + } + } + ``` + +=== "Java" + + ```java title="" + /* Backtracking algorithm framework */ + void backtrack(State state, List choices, List res) { + // Check if it's a solution + if (isSolution(state)) { + // Record the solution + recordSolution(state, res); + // Stop searching + return; + } + // Iterate through all choices + for (Choice choice : choices) { + // Prune: check if the choice is valid + if (isValid(state, choice)) { + // Trial: make a choice, update the state + makeChoice(state, choice); + backtrack(state, choices, res); + // Retreat: undo the choice, revert to the previous state + undoChoice(state, choice); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + /* Backtracking algorithm framework */ + void Backtrack(State state, List choices, List res) { + // Check if it's a solution + if (IsSolution(state)) { + // Record the solution + RecordSolution(state, res); + // Stop searching + return; + } + // Iterate through all choices + foreach (Choice choice in choices) { + // Prune: check if the choice is valid + if (IsValid(state, choice)) { + // Trial: make a choice, update the state + MakeChoice(state, choice); + Backtrack(state, choices, res); + // Retreat: undo the choice, revert to the previous state + UndoChoice(state, choice); + } + } + } + ``` + +=== "Go" + + ```go title="" + /* Backtracking algorithm framework */ + func backtrack(state *State, choices []Choice, res *[]State) { + // Check if it's a solution + if isSolution(state) { + // Record the solution + recordSolution(state, res) + // Stop searching + return + } + // Iterate through all choices + for _, choice := range choices { + // Prune: check if the choice is valid + if isValid(state, choice) { + // Trial: make a choice, update the state + makeChoice(state, choice) + backtrack(state, choices, res) + // Retreat: undo the choice, revert to the previous state + undoChoice(state, choice) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Backtracking algorithm framework */ + func backtrack(state: inout State, choices: [Choice], res: inout [State]) { + // Check if it's a solution + if isSolution(state: state) { + // Record the solution + recordSolution(state: state, res: &res) + // Stop searching + return + } + // Iterate through all choices + for choice in choices { + // Prune: check if the choice is valid + if isValid(state: state, choice: choice) { + // Trial: make a choice, update the state + makeChoice(state: &state, choice: choice) + backtrack(state: &state, choices: choices, res: &res) + // Retreat: undo the choice, revert to the previous state + undoChoice(state: &state, choice: choice) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Backtracking algorithm framework */ + function backtrack(state, choices, res) { + // Check if it's a solution + if (isSolution(state)) { + // Record the solution + recordSolution(state, res); + // Stop searching + return; + } + // Iterate through all choices + for (let choice of choices) { + // Prune: check if the choice is valid + if (isValid(state, choice)) { + // Trial: make a choice, update the state + makeChoice(state, choice); + backtrack(state, choices, res); + // Retreat: undo the choice, revert to the previous state + undoChoice(state, choice); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Backtracking algorithm framework */ + function backtrack(state: State, choices: Choice[], res: State[]): void { + // Check if it's a solution + if (isSolution(state)) { + // Record the solution + recordSolution(state, res); + // Stop searching + return; + } + // Iterate through all choices + for (let choice of choices) { + // Prune: check if the choice is valid + if (isValid(state, choice)) { + // Trial: make a choice, update the state + makeChoice(state, choice); + backtrack(state, choices, res); + // Retreat: undo the choice, revert to the previous state + undoChoice(state, choice); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Backtracking algorithm framework */ + void backtrack(State state, List, List res) { + // Check if it's a solution + if (isSolution(state)) { + // Record the solution + recordSolution(state, res); + // Stop searching + return; + } + // Iterate through all choices + for (Choice choice in choices) { + // Prune: check if the choice is valid + if (isValid(state, choice)) { + // Trial: make a choice, update the state + makeChoice(state, choice); + backtrack(state, choices, res); + // Retreat: undo the choice, revert to the previous state + undoChoice(state, choice); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + /* Backtracking algorithm framework */ + fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { + // Check if it's a solution + if is_solution(state) { + // Record the solution + record_solution(state, res); + // Stop searching + return; + } + // Iterate through all choices + for choice in choices { + // Prune: check if the choice is valid + if is_valid(state, choice) { + // Trial: make a choice, update the state + make_choice(state, choice); + backtrack(state, choices, res); + // Retreat: undo the choice, revert to the previous state + undo_choice(state, choice); + } + } + } + ``` + +=== "C" + + ```c title="" + /* Backtracking algorithm framework */ + void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { + // Check if it's a solution + if (isSolution(state)) { + // Record the solution + recordSolution(state, res, numRes); + // Stop searching + return; + } + // Iterate through all choices + for (int i = 0; i < numChoices; i++) { + // Prune: check if the choice is valid + if (isValid(state, &choices[i])) { + // Trial: make a choice, update the state + makeChoice(state, &choices[i]); + backtrack(state, choices, numChoices, res, numRes); + // Retreat: undo the choice, revert to the previous state + undoChoice(state, &choices[i]); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Backtracking algorithm framework */ + fun backtrack(state: State?, choices: List, res: List?) { + // Check if it's a solution + if (isSolution(state)) { + // Record the solution + recordSolution(state, res) + // Stop searching + return + } + // Iterate through all choices + for (choice in choices) { + // Prune: check if the choice is valid + if (isValid(state, choice)) { + // Trial: make a choice, update the state + makeChoice(state, choice) + backtrack(state, choices, res) + // Retreat: undo the choice, revert to the previous state + undoChoice(state, choice) + } + } + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +Now, we are able to solve Example Three using the framework code. The `state` is the node traversal path, `choices` are the current node's left and right children, and the result `res` is the list of paths: + +```src +[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} +``` + +As per the requirements, after finding a node with a value of $7$, the search should continue. **As a result, the `return` statement after recording the solution should be removed**. The figure below compares the search processes with and without retaining the `return` statement. + +![Comparison of retaining and removing the return in the search process](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) + +Compared to the implementation based on pre-order traversal, the code using the backtracking algorithm framework seems verbose. However, it has better universality. In fact, **many backtracking problems can be solved within this framework**. We just need to define `state` and `choices` according to the specific problem and implement the methods in the framework. + +## Common terminology + +To analyze algorithmic problems more clearly, we summarize the meanings of commonly used terminology in backtracking algorithms and provide corresponding examples from Example Three as shown in the table below. + +

Table   Common backtracking algorithm terminology

+ +| Term | Definition | Example Three | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Solution | A solution is an answer that satisfies specific conditions of the problem, which may have one or more | All paths from the root node to node $7$ that meet the constraint | +| Constraint | Constraints are conditions in the problem that limit the feasibility of solutions, often used for pruning | Paths do not contain node $3$ | +| State | State represents the situation of the problem at a certain moment, including choices made | Current visited node path, i.e., `path` node list | +| Trial | A trial is the process of exploring the solution space based on available choices, including making choices, updating the state, and checking if it's a solution | Recursively visiting left (right) child nodes, adding nodes to `path`, checking if the node's value is $7$ | +| Retreat | Retreat refers to the action of undoing previous choices and returning to the previous state when encountering states that do not meet the constraints | When passing leaf nodes, ending node visits, encountering nodes with a value of $3$, terminating the search, and the recursion function returns | +| Prune | Prune is a method to avoid meaningless search paths based on the characteristics and constraints of the problem, which can enhance search efficiency | When encountering a node with a value of $3$, no further search is required | + +!!! tip + + Concepts like problems, solutions, states, etc., are universal, and are involved in divide and conquer, backtracking, dynamic programming, and greedy algorithms, among others. + +## Advantages and limitations + +The backtracking algorithm is essentially a depth-first search algorithm that attempts all possible solutions until a satisfying solution is found. The advantage of this method is that it can find all possible solutions, and with reasonable pruning operations, it can be highly efficient. + +However, when dealing with large-scale or complex problems, **the running efficiency of backtracking algorithm may not be acceptable**. + +- **Time complexity**: Backtracking algorithms usually need to traverse all possible states in the state space, which can reach exponential or factorial time complexity. +- **Space complexity**: In recursive calls, it is necessary to save the current state (such as paths, auxiliary variables for pruning, etc.). When the depth is very large, the space need may become significantly bigger. + +Even so, **backtracking remains the best solution for certain search problems and constraint satisfaction problems**. For these problems, there is no way to predict which choices can generate valid solutions. We have to traverse all possible choices. In this case, **the key is about how to optimize the efficiency**. There are two common efficiency optimization methods. + +- **Prune**: Avoid searching paths that definitely will not produce a solution, thus saving time and space. +- **Heuristic search**: Introduce some strategies or estimates during the search process to prioritize the paths that are most likely to produce valid solutions. + +## Typical backtracking problems + +Backtracking algorithms can be used to solve many search problems, constraint satisfaction problems, and combinatorial optimization problems. + +**Search problems**: The goal of these problems is to find solutions that meet specific conditions. + +- Full permutation problem: Given a set, find all possible permutations and combinations of it. +- Subset sum problem: Given a set and a target sum, find all subsets of the set that sum to the target. +- Tower of Hanoi problem: Given three rods and a series of different-sized discs, the goal is to move all the discs from one rod to another, moving only one disc at a time, and never placing a larger disc on a smaller one. + +**Constraint satisfaction problems**: The goal of these problems is to find solutions that satisfy all the constraints. + +- $n$ queens: Place $n$ queens on an $n \times n$ chessboard so that they do not attack each other. +- Sudoku: Fill a $9 \times 9$ grid with the numbers $1$ to $9$, ensuring that the numbers do not repeat in each row, each column, and each $3 \times 3$ subgrid. +- Graph coloring problem: Given an undirected graph, color each vertex with the fewest possible colors so that adjacent vertices have different colors. + +**Combinatorial optimization problems**: The goal of these problems is to find the optimal solution within a combination space that meets certain conditions. + +- 0-1 knapsack problem: Given a set of items and a backpack, each item has a certain value and weight. The goal is to choose items to maximize the total value within the backpack's capacity limit. +- Traveling salesman problem: In a graph, starting from one point, visit all other points exactly once and then return to the starting point, seeking the shortest path. +- Maximum clique problem: Given an undirected graph, find the largest complete subgraph, i.e., a subgraph where any two vertices are connected by an edge. + +Please note that for many combinatorial optimization problems, backtracking is not the optimal solution. + +- The 0-1 knapsack problem is usually solved using dynamic programming to achieve higher time efficiency. +- The traveling salesman is a well-known NP-Hard problem, commonly solved using genetic algorithms and ant colony algorithms, among others. +- The maximum clique problem is a classic problem in graph theory, which can be solved using greedy algorithms and other heuristic methods. diff --git a/en/docs/chapter_backtracking/index.md b/en/docs/chapter_backtracking/index.md new file mode 100644 index 0000000000..862581c932 --- /dev/null +++ b/en/docs/chapter_backtracking/index.md @@ -0,0 +1,9 @@ +# Backtracking + +![Backtracking](../assets/covers/chapter_backtracking.jpg) + +!!! abstract + + Like explorers in a maze, we may encounter obstacles on our path forward. + + The power of backtracking lets us begin anew, keep trying, and eventually find the exit leading to the light. diff --git a/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png b/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png new file mode 100644 index 0000000000..fa8510ea07 Binary files /dev/null and b/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png differ diff --git a/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png b/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png new file mode 100644 index 0000000000..8072791ef5 Binary files /dev/null and b/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png differ diff --git a/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png b/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png new file mode 100644 index 0000000000..a052d9c0e7 Binary files /dev/null and b/en/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png differ diff --git a/en/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png b/en/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png new file mode 100644 index 0000000000..1e841201a8 Binary files /dev/null and b/en/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png differ diff --git a/en/docs/chapter_backtracking/n_queens_problem.md b/en/docs/chapter_backtracking/n_queens_problem.md new file mode 100644 index 0000000000..4ec02a6da0 --- /dev/null +++ b/en/docs/chapter_backtracking/n_queens_problem.md @@ -0,0 +1,53 @@ +# n queens problem + +!!! question + + According to the rules of chess, a queen can attack pieces in the same row, column, or diagonal line. Given $n$ queens and an $n \times n$ chessboard, find arrangements where no two queens can attack each other. + +As shown in the figure below, there are two solutions when $n = 4$. From the perspective of the backtracking algorithm, an $n \times n$ chessboard has $n^2$ squares, presenting all possible choices `choices`. The state of the chessboard `state` changes continuously as each queen is placed. + +![Solution to the 4 queens problem](n_queens_problem.assets/solution_4_queens.png) + +The figure below shows the three constraints of this problem: **multiple queens cannot occupy the same row, column, or diagonal**. It is important to note that diagonals are divided into the main diagonal `\` and the secondary diagonal `/`. + +![Constraints of the n queens problem](n_queens_problem.assets/n_queens_constraints.png) + +### Row-by-row placing strategy + +As the number of queens equals the number of rows on the chessboard, both being $n$, it is easy to conclude that **each row on the chessboard allows and only allows one queen to be placed**. + +This means that we can adopt a row-by-row placing strategy: starting from the first row, place one queen per row until the last row is reached. + +The figure below shows the row-by-row placing process for the 4 queens problem. Due to space limitations, the figure only expands one search branch of the first row, and prunes any placements that do not meet the column and diagonal constraints. + +![Row-by-row placing strategy](n_queens_problem.assets/n_queens_placing.png) + +Essentially, **the row-by-row placing strategy serves as a pruning function**, eliminating all search branches that would place multiple queens in the same row. + +### Column and diagonal pruning + +To satisfy column constraints, we can use a boolean array `cols` of length $n$ to track whether a queen occupies each column. Before each placement decision, `cols` is used to prune the columns that already have queens, and it is dynamically updated during backtracking. + +!!! tip + + Note that the origin of the matrix is located in the upper left corner, where the row index increases from top to bottom, and the column index increases from left to right. + +How about the diagonal constraints? Let the row and column indices of a certain cell on the chessboard be $(row, col)$. By selecting a specific main diagonal, we notice that the difference $row - col$ is the same for all cells on that diagonal, **meaning that $row - col$ is a constant value on the main diagonal**. + +In other words, if two cells satisfy $row_1 - col_1 = row_2 - col_2$, they are definitely on the same main diagonal. Using this pattern, we can utilize the array `diags1` shown in the figure below to track whether a queen is on any main diagonal. + +Similarly, **the sum of $row + col$ is a constant value for all cells on the secondary diagonal**. We can also use the array `diags2` to handle secondary diagonal constraints. + +![Handling column and diagonal constraints](n_queens_problem.assets/n_queens_cols_diagonals.png) + +### Code implementation + +Please note, in an $n$-dimensional square matrix, the range of $row - col$ is $[-n + 1, n - 1]$, and the range of $row + col$ is $[0, 2n - 2]$. Consequently, the number of both main and secondary diagonals is $2n - 1$, meaning the length of the arrays `diags1` and `diags2` is $2n - 1$. + +```src +[file]{n_queens}-[class]{}-[func]{n_queens} +``` + +Placing $n$ queens row-by-row, considering column constraints, from the first row to the last row, there are $n$, $n-1$, $\dots$, $2$, $1$ choices, using $O(n!)$ time. When recording a solution, it is necessary to copy the matrix `state` and add it to `res`, with the copying operation using $O(n^2)$ time. Therefore, **the overall time complexity is $O(n! \cdot n^2)$**. In practice, pruning based on diagonal constraints can significantly reduce the search space, thus often the search efficiency is better than the aforementioned time complexity. + +Array `state` uses $O(n^2)$ space, and arrays `cols`, `diags1`, and `diags2` each use $O(n)$ space as well. The maximum recursion depth is $n$, using $O(n)$ stack frame space. Therefore, **the space complexity is $O(n^2)$**. diff --git a/en/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png new file mode 100644 index 0000000000..d5ed6b5bca Binary files /dev/null and b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png differ diff --git a/en/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png new file mode 100644 index 0000000000..d069d0c204 Binary files /dev/null and b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png differ diff --git a/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png new file mode 100644 index 0000000000..f0d686b029 Binary files /dev/null and b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png differ diff --git a/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png new file mode 100644 index 0000000000..09cea37e05 Binary files /dev/null and b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png differ diff --git a/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png new file mode 100644 index 0000000000..cd7e49d712 Binary files /dev/null and b/en/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png differ diff --git a/en/docs/chapter_backtracking/permutations_problem.md b/en/docs/chapter_backtracking/permutations_problem.md new file mode 100644 index 0000000000..ff3b87b648 --- /dev/null +++ b/en/docs/chapter_backtracking/permutations_problem.md @@ -0,0 +1,95 @@ +# Permutation problem + +The permutation problem is a typical application of the backtracking algorithm. It involves finding all possible arrangements (permutations) of elements from a given set, such as an array or a string. + +The table below shows several examples, including input arrays and their corresponding permutations. + +

Table   Permutation examples

+ +| Input array | Permutations | +| :---------- | :----------------------------------------------------------------- | +| $[1]$ | $[1]$ | +| $[1, 2]$ | $[1, 2], [2, 1]$ | +| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | + +## Cases without duplicate elements + +!!! question + + Given an integer array with no duplicate elements, return all possible permutations. + +From a backtracking perspective, **we can view the process of generating permutations as a series of choices.** Suppose the input array is $[1, 2, 3]$. If we choose $1$ first, then $3$, and finally $2$, we get the permutation $[1, 3, 2]$. "Backtracking" means undoing a previous choice and exploring alternative options. + +From a coding perspective, the candidate set `choices` consists of all elements in the input array, while `state` holds the elements selected so far. Since each element can only be chosen once, **all elements in `state` must be unique**. + +As illustrated in the figure below, we can expand the search process into a recursive tree, where each node represents the current `state`. Starting from the root node, after three rounds of selections, we reach the leaf nodes—each corresponding to a permutation. + +![Permutation recursive tree](permutations_problem.assets/permutations_i.png) + +### Repeated-choice pruning + +To ensure each element is selected only once, we introduce a boolean array `selected`, where `selected[i]` indicates whether `choices[i]` has been chosen. We then base our pruning steps on this array: + +- After choosing `choice[i]`, set `selected[i]` to $\text{True}$ to mark it as chosen. +- While iterating through `choices`, skip all elements marked as chosen (i.e., prune those branches). + +As shown in the figure below, suppose we choose 1 in the first round, then 3 in the second round, and finally 2 in the third round. We need to prune the branch for element 1 in the second round and the branches for elements 1 and 3 in the third round. + +![Permutation pruning example](permutations_problem.assets/permutations_i_pruning.png) + +From the figure, we can see that this pruning process reduces the search space from $O(n^n)$ to $O(n!)$. + +### Code implementation + +With this understanding, we can "fill in the blanks" of our framework code. To keep the overall code concise, we won’t implement each part of the framework separately but instead expand everything in the `backtrack()` function: + +```src +[file]{permutations_i}-[class]{}-[func]{permutations_i} +``` + +## Considering duplicate elements + +!!! question + + Given an integer array**that may contain duplicate elements**, return all unique permutations. + +Suppose the input array is $[1, 1, 2]$. To distinguish between the two identical elements $1$, we label the second one as $\hat{1}$. + +As shown in the figure below, half of the permutations produced by this method are duplicates: + +![Duplicate permutations](permutations_problem.assets/permutations_ii.png) + +So how can we eliminate these duplicate permutations? One direct approach is to use a hash set to remove duplicates after generating all permutations. However, this is less elegant **because branches that produce duplicates are inherently unnecessary and should be pruned in advance,** thus improving the algorithm’s efficiency. + +### Equal-element pruning + +Looking at the figure below, in the first round, choosing $1$ or $\hat{1}$ leads to the same permutations, so we prune $\hat{1}$. + +Similarly, after choosing $2$ in the first round, choosing $1$ or $\hat{1}$ in the second round also leads to duplicate branches, so we prune $\hat{1}$ then as well. + +Essentially, **our goal is to ensure that multiple identical elements are only selected once per round of choices.** + +![Duplicate permutations pruning](permutations_problem.assets/permutations_ii_pruning.png) + +### Code implementation + +Based on the code from the previous problem, we introduce a hash set `duplicated` in each round. This set keeps track of elements we have already attempted, so we can prune duplicates: + +```src +[file]{permutations_ii}-[class]{}-[func]{permutations_ii} +``` + +Assuming all elements are distinct, there are $n!$ (factorial) permutations of $n$ elements. Recording each result requires copying a list of length $n$, which takes $O(n)$ time. **Hence, the total time complexity is $O(n!n)$.** + +The maximum recursion depth is $n$, using $O(n)$ stack space. The `selected` array also requires $O(n)$ space. Because there can be up to $n$ separate `duplicated` sets at any one time, they collectively occupy $O(n^2)$ space. **Therefore, the space complexity is $O(n^2)$.** + +### Comparing the two pruning methods + +Although both `selected` and `duplicated` serve as pruning mechanisms, they target different issues: + +- **Repeated-choice pruning**(via `selected`): There is a single `selected` array for the entire search, indicating which elements are already in the current state. This prevents the same element from appearing more than once in `state`. +- **Equal-element pruning**(via `duplicated`): Each call to the `backtrack` function uses its own `duplicated` set, recording which elements have already been chosen in that specific iteration (`for` loop). This ensures that equal elements are selected only once per round of choices. + +The figure below shows the scope of these two pruning strategies. Each node in the tree represents a choice; the path from the root to any leaf corresponds to one complete permutation. + +![Scope of the two pruning conditions](permutations_problem.assets/permutations_ii_pruning_summary.png) diff --git a/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png new file mode 100644 index 0000000000..890fc21be5 Binary files /dev/null and b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png differ diff --git a/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png new file mode 100644 index 0000000000..7fb0d6b02e Binary files /dev/null and b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png differ diff --git a/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png new file mode 100644 index 0000000000..2c374dd518 Binary files /dev/null and b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png differ diff --git a/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png new file mode 100644 index 0000000000..46ef6edd50 Binary files /dev/null and b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png differ diff --git a/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png new file mode 100644 index 0000000000..1317bfb072 Binary files /dev/null and b/en/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png differ diff --git a/en/docs/chapter_backtracking/subset_sum_problem.md b/en/docs/chapter_backtracking/subset_sum_problem.md new file mode 100644 index 0000000000..51638edb93 --- /dev/null +++ b/en/docs/chapter_backtracking/subset_sum_problem.md @@ -0,0 +1,95 @@ +# Subset sum problem + +## Case without duplicate elements + +!!! question + + Given an array of positive integers `nums` and a target positive integer `target`, find all possible combinations such that the sum of the elements in the combination equals `target`. The given array has no duplicate elements, and each element can be chosen multiple times. Please return these combinations as a list, which should not contain duplicate combinations. + +For example, for the input set $\{3, 4, 5\}$ and target integer $9$, the solutions are $\{3, 3, 3\}, \{4, 5\}$. Note the following two points. + +- Elements in the input set can be chosen an unlimited number of times. +- Subsets do not distinguish the order of elements, for example $\{4, 5\}$ and $\{5, 4\}$ are the same subset. + +### Reference permutation solution + +Similar to the permutation problem, we can imagine the generation of subsets as a series of choices, updating the "element sum" in real-time during the choice process. When the element sum equals `target`, the subset is recorded in the result list. + +Unlike the permutation problem, **elements in this problem can be chosen an unlimited number of times**, thus there is no need to use a `selected` boolean list to record whether an element has been chosen. We can make minor modifications to the permutation code to initially solve the problem: + +```src +[file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} +``` + +Inputting the array $[3, 4, 5]$ and target element $9$ into the above code yields the results $[3, 3, 3], [4, 5], [5, 4]$. **Although it successfully finds all subsets with a sum of $9$, it includes the duplicate subset $[4, 5]$ and $[5, 4]$**. + +This is because the search process distinguishes the order of choices, however, subsets do not distinguish the choice order. As shown in the figure below, choosing $4$ before $5$ and choosing $5$ before $4$ are different branches, but correspond to the same subset. + +![Subset search and pruning out of bounds](subset_sum_problem.assets/subset_sum_i_naive.png) + +To eliminate duplicate subsets, **a straightforward idea is to deduplicate the result list**. However, this method is very inefficient for two reasons. + +- When there are many array elements, especially when `target` is large, the search process produces a large number of duplicate subsets. +- Comparing subsets (arrays) for differences is very time-consuming, requiring arrays to be sorted first, then comparing the differences of each element in the arrays. + +### Duplicate subset pruning + +**We consider deduplication during the search process through pruning**. Observing the figure below, duplicate subsets are generated when choosing array elements in different orders, for example in the following situations. + +1. When choosing $3$ in the first round and $4$ in the second round, all subsets containing these two elements are generated, denoted as $[3, 4, \dots]$. +2. Later, when $4$ is chosen in the first round, **the second round should skip $3$** because the subset $[4, 3, \dots]$ generated by this choice completely duplicates the subset from step `1.`. + +In the search process, each layer's choices are tried one by one from left to right, so the more to the right a branch is, the more it is pruned. + +1. First two rounds choose $3$ and $5$, generating subset $[3, 5, \dots]$. +2. First two rounds choose $4$ and $5$, generating subset $[4, 5, \dots]$. +3. If $5$ is chosen in the first round, **then the second round should skip $3$ and $4$** as the subsets $[5, 3, \dots]$ and $[5, 4, \dots]$ completely duplicate the subsets described in steps `1.` and `2.`. + +![Different choice orders leading to duplicate subsets](subset_sum_problem.assets/subset_sum_i_pruning.png) + +In summary, given the input array $[x_1, x_2, \dots, x_n]$, the choice sequence in the search process should be $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$, which needs to satisfy $i_1 \leq i_2 \leq \dots \leq i_m$. **Any choice sequence that does not meet this condition will cause duplicates and should be pruned**. + +### Code implementation + +To implement this pruning, we initialize the variable `start`, which indicates the starting point for traversal. **After making the choice $x_{i}$, set the next round to start from index $i$**. This will ensure the choice sequence satisfies $i_1 \leq i_2 \leq \dots \leq i_m$, thereby ensuring the uniqueness of the subsets. + +Besides, we have made the following two optimizations to the code. + +- Before starting the search, sort the array `nums`. In the traversal of all choices, **end the loop directly when the subset sum exceeds `target`** as subsequent elements are larger and their subset sum will definitely exceed `target`. +- Eliminate the element sum variable `total`, **by performing subtraction on `target` to count the element sum**. When `target` equals $0$, record the solution. + +```src +[file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} +``` + +The figure below shows the overall backtracking process after inputting the array $[3, 4, 5]$ and target element $9$ into the above code. + +![Subset sum I backtracking process](subset_sum_problem.assets/subset_sum_i.png) + +## Considering cases with duplicate elements + +!!! question + + Given an array of positive integers `nums` and a target positive integer `target`, find all possible combinations such that the sum of the elements in the combination equals `target`. **The given array may contain duplicate elements, and each element can only be chosen once**. Please return these combinations as a list, which should not contain duplicate combinations. + +Compared to the previous question, **this question's input array may contain duplicate elements**, introducing new problems. For example, given the array $[4, \hat{4}, 5]$ and target element $9$, the existing code's output results in $[4, 5], [\hat{4}, 5]$, resulting in duplicate subsets. + +**The reason for this duplication is that equal elements are chosen multiple times in a certain round**. In the figure below, the first round has three choices, two of which are $4$, generating two duplicate search branches, thus outputting duplicate subsets; similarly, the two $4$s in the second round also produce duplicate subsets. + +![Duplicate subsets caused by equal elements](subset_sum_problem.assets/subset_sum_ii_repeat.png) + +### Equal element pruning + +To solve this issue, **we need to limit equal elements to being chosen only once per round**. The implementation is quite clever: since the array is sorted, equal elements are adjacent. This means that in a certain round of choices, if the current element is equal to its left-hand element, it means it has already been chosen, so skip the current element directly. + +At the same time, **this question stipulates that each array element can only be chosen once**. Fortunately, we can also use the variable `start` to meet this constraint: after making the choice $x_{i}$, set the next round to start from index $i + 1$ going forward. This not only eliminates duplicate subsets but also avoids repeated selection of elements. + +### Code implementation + +```src +[file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} +``` + +The figure below shows the backtracking process for the array $[4, 4, 5]$ and target element $9$, including four types of pruning operations. Please combine the illustration with the code comments to understand the entire search process and how each type of pruning operation works. + +![Subset sum II backtracking process](subset_sum_problem.assets/subset_sum_ii.png) diff --git a/en/docs/chapter_backtracking/summary.md b/en/docs/chapter_backtracking/summary.md new file mode 100644 index 0000000000..4631b4c1e0 --- /dev/null +++ b/en/docs/chapter_backtracking/summary.md @@ -0,0 +1,23 @@ +# Summary + +### Key review + +- The essence of the backtracking algorithm is exhaustive search. It seeks solutions that meet the conditions by performing a depth-first traversal of the solution space. During the search, if a satisfying solution is found, it is recorded, until all solutions are found or the traversal is completed. +- The search process of the backtracking algorithm includes trying and backtracking. It uses depth-first search to explore various choices, and when a choice does not meet the constraints, the previous choice is undone. Then it reverts to the previous state and continues to try other options. Trying and backtracking are operations in opposite directions. +- Backtracking problems usually contain multiple constraints. These constraints can be used to perform pruning operations. Pruning can terminate unnecessary search branches in advance, greatly enhancing search efficiency. +- The backtracking algorithm is mainly used to solve search problems and constraint satisfaction problems. Although combinatorial optimization problems can be solved using backtracking, there are often more efficient or effective solutions available. +- The permutation problem aims to search for all possible permutations of the elements in a given set. We use an array to record whether each element has been chosen, avoiding repeated selection of the same element. This ensures that each element is chosen only once. +- In permutation problems, if the set contains duplicate elements, the final result will include duplicate permutations. We need to restrict that identical elements can only be selected once in each round, which is usually implemented using a hash set. +- The subset-sum problem aims to find all subsets in a given set that sum to a target value. The set does not distinguish the order of elements, but the search process may generate duplicate subsets. This occurs because the algorithm explores different element orders as unique paths. Before backtracking, we sort the data and set a variable to indicate the starting point of the traversal for each round. This allows us to prune the search branches that generate duplicate subsets. +- For the subset-sum problem, equal elements in the array can produce duplicate sets. Using the precondition that the array is already sorted, we prune by determining if adjacent elements are equal. This ensures that equal elements are only selected once per round. +- The $n$ queens problem aims to find schemes to place $n$ queens on an $n \times n$ chessboard such that no two queens can attack each other. The constraints of the problem include row constraints, column constraints, and constraints on the main and secondary diagonals. To meet the row constraint, we adopt a strategy of placing one queen per row, ensuring each row has one queen placed. +- The handling of column constraints and diagonal constraints is similar. For column constraints, we use an array to record whether there is a queen in each column, thereby indicating whether the selected cell is legal. For diagonal constraints, we use two arrays to respectively record the presence of queens on the main and secondary diagonals. The challenge is to determine the relationship between row and column indices for cells on the same main or secondary diagonal. + +### Q & A + +**Q**: How can we understand the relationship between backtracking and recursion? + +Overall, backtracking is an "algorithmic strategy," while recursion is more of a "tool." + +- Backtracking algorithms are typically based on recursion. However, backtracking is one of the application scenarios of recursion, specifically in search problems. +- The structure of recursion reflects the problem-solving paradigm of "sub-problem decomposition." It is commonly used in solving problems involving divide and conquer, backtracking, and dynamic programming (memoized recursion). diff --git a/en/docs/chapter_computational_complexity/index.md b/en/docs/chapter_computational_complexity/index.md new file mode 100644 index 0000000000..1fbbaefe0b --- /dev/null +++ b/en/docs/chapter_computational_complexity/index.md @@ -0,0 +1,9 @@ +# Complexity analysis + +![Complexity analysis](../assets/covers/chapter_complexity_analysis.jpg) + +!!! abstract + + Complexity analysis is like a space-time navigator in the vast universe of algorithms. + + It guides us in exploring deeper within the dimensions of time and space, seeking more elegant solutions. diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png new file mode 100644 index 0000000000..11eb3081d9 Binary files /dev/null and b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png differ diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png new file mode 100644 index 0000000000..bff1b5ec66 Binary files /dev/null and b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png differ diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png new file mode 100644 index 0000000000..5c06e19110 Binary files /dev/null and b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png differ diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png new file mode 100644 index 0000000000..3707783107 Binary files /dev/null and b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png differ diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png new file mode 100644 index 0000000000..df343d5c1d Binary files /dev/null and b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png differ diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png new file mode 100644 index 0000000000..ec23d50df4 Binary files /dev/null and b/en/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png differ diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.md b/en/docs/chapter_computational_complexity/iteration_and_recursion.md new file mode 100644 index 0000000000..f873eb5fa9 --- /dev/null +++ b/en/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -0,0 +1,194 @@ +# Iteration and recursion + +In algorithms, the repeated execution of a task is quite common and is closely related to the analysis of complexity. Therefore, before delving into the concepts of time complexity and space complexity, let's first explore how to implement repetitive tasks in programming. This involves understanding two fundamental programming control structures: iteration and recursion. + +## Iteration + +Iteration is a control structure for repeatedly performing a task. In iteration, a program repeats a block of code as long as a certain condition is met until this condition is no longer satisfied. + +### For loops + +The `for` loop is one of the most common forms of iteration, and **it's particularly suitable when the number of iterations is known in advance**. + +The following function uses a `for` loop to perform a summation of $1 + 2 + \dots + n$, with the sum being stored in the variable `res`. It's important to note that in Python, `range(a, b)` creates an interval that is inclusive of `a` but exclusive of `b`, meaning it iterates over the range from $a$ up to $b−1$. + +```src +[file]{iteration}-[class]{}-[func]{for_loop} +``` + +The figure below represents this sum function. + +![Flowchart of the sum function](iteration_and_recursion.assets/iteration.png) + +The number of operations in this summation function is proportional to the size of the input data $n$, or in other words, it has a linear relationship. **This "linear relationship" is what time complexity describes**. This topic will be discussed in more detail in the next section. + +### While loops + +Similar to `for` loops, `while` loops are another approach for implementing iteration. In a `while` loop, the program checks a condition at the beginning of each iteration; if the condition is true, the execution continues, otherwise, the loop ends. + +Below we use a `while` loop to implement the sum $1 + 2 + \dots + n$. + +```src +[file]{iteration}-[class]{}-[func]{while_loop} +``` + +**`while` loops provide more flexibility than `for` loops**, especially since they allow for custom initialization and modification of the condition variable at each step. + +For example, in the following code, the condition variable $i$ is updated twice each round, which would be inconvenient to implement with a `for` loop. + +```src +[file]{iteration}-[class]{}-[func]{while_loop_ii} +``` + +Overall, **`for` loops are more concise, while `while` loops are more flexible**. Both can implement iterative structures. Which one to use should be determined based on the specific requirements of the problem. + +### Nested loops + +We can nest one loop structure within another. Below is an example using `for` loops: + +```src +[file]{iteration}-[class]{}-[func]{nested_for_loop} +``` + +The figure below represents this nested loop. + +![Flowchart of the nested loop](iteration_and_recursion.assets/nested_iteration.png) + +In such cases, the number of operations of the function is proportional to $n^2$, meaning the algorithm's runtime and the size of the input data $n$ has a 'quadratic relationship.' + +We can further increase the complexity by adding more nested loops, each level of nesting effectively "increasing the dimension," which raises the time complexity to "cubic," "quartic," and so on. + +## Recursion + +Recursion is an algorithmic strategy where a function solves a problem by calling itself. It primarily involves two phases: + +1. **Calling**: This is where the program repeatedly calls itself, often with progressively smaller or simpler arguments, moving towards the "termination condition." +2. **Returning**: Upon triggering the "termination condition," the program begins to return from the deepest recursive function, aggregating the results of each layer. + +From an implementation perspective, recursive code mainly includes three elements. + +1. **Termination Condition**: Determines when to switch from "calling" to "returning." +2. **Recursive Call**: Corresponds to "calling," where the function calls itself, usually with smaller or more simplified parameters. +3. **Return Result**: Corresponds to "returning," where the result of the current recursion level is returned to the previous layer. + +Observe the following code, where simply calling the function `recur(n)` can compute the sum of $1 + 2 + \dots + n$: + +```src +[file]{recursion}-[class]{}-[func]{recur} +``` + +The figure below shows the recursive process of this function. + +![Recursive process of the sum function](iteration_and_recursion.assets/recursion_sum.png) + +Although iteration and recursion can achieve the same results from a computational standpoint, **they represent two entirely different paradigms of thinking and problem-solving**. + +- **Iteration**: Solves problems "from the bottom up." It starts with the most basic steps, and then repeatedly adds or accumulates these steps until the task is complete. +- **Recursion**: Solves problems "from the top down." It breaks down the original problem into smaller sub-problems, each of which has the same form as the original problem. These sub-problems are then further decomposed into even smaller sub-problems, stopping at the base case whose solution is known. + +Let's take the earlier example of the summation function, defined as $f(n) = 1 + 2 + \dots + n$. + +- **Iteration**: In this approach, we simulate the summation process within a loop. Starting from $1$ and traversing to $n$, we perform the summation operation in each iteration to eventually compute $f(n)$. +- **Recursion**: Here, the problem is broken down into a sub-problem: $f(n) = n + f(n-1)$. This decomposition continues recursively until reaching the base case, $f(1) = 1$, at which point the recursion terminates. + +### Call stack + +Every time a recursive function calls itself, the system allocates memory for the newly initiated function to store local variables, the return address, and other relevant information. This leads to two primary outcomes. + +- The function's context data is stored in a memory area called "stack frame space" and is only released after the function returns. Therefore, **recursion generally consumes more memory space than iteration**. +- Recursive calls introduce additional overhead. **Hence, recursion is usually less time-efficient than loops.** + +As shown in the figure below, there are $n$ unreturned recursive functions before triggering the termination condition, indicating a **recursion depth of $n$**. + +![Recursion call depth](iteration_and_recursion.assets/recursion_sum_depth.png) + +In practice, the depth of recursion allowed by programming languages is usually limited, and excessively deep recursion can lead to stack overflow errors. + +### Tail recursion + +Interestingly, **if a function performs its recursive call as the very last step before returning,** it can be optimized by the compiler or interpreter to be as space-efficient as iteration. This scenario is known as tail recursion. + +- **Regular recursion**: In standard recursion, when the function returns to the previous level, it continues to execute more code, requiring the system to save the context of the previous call. +- **Tail recursion**: Here, the recursive call is the final operation before the function returns. This means that upon returning to the previous level, no further actions are needed, so the system does not need to save the context of the previous level. + +For example, in calculating $1 + 2 + \dots + n$, we can make the result variable `res` a parameter of the function, thereby achieving tail recursion: + +```src +[file]{recursion}-[class]{}-[func]{tail_recur} +``` + +The execution process of tail recursion is shown in the figure below. Comparing regular recursion and tail recursion, the point of the summation operation is different. + +- **Regular recursion**: The summation operation occurs during the "returning" phase, requiring another summation after each layer returns. +- **Tail recursion**: The summation operation occurs during the "calling" phase, and the "returning" phase only involves returning through each layer. + +![Tail recursion process](iteration_and_recursion.assets/tail_recursion_sum.png) + +!!! tip + + Note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if the function is in the form of tail recursion, it may still encounter stack overflow issues. + +### Recursion tree + +When dealing with algorithms related to "divide and conquer", recursion often offers a more intuitive approach and more readable code than iteration. Take the "Fibonacci sequence" as an example. + +!!! question + + Given a Fibonacci sequence $0, 1, 1, 2, 3, 5, 8, 13, \dots$, find the $n$th number in the sequence. + +Let the $n$th number of the Fibonacci sequence be $f(n)$, it's easy to deduce two conclusions: + +- The first two numbers of the sequence are $f(1) = 0$ and $f(2) = 1$. +- Each number in the sequence is the sum of the two preceding ones, that is, $f(n) = f(n - 1) + f(n - 2)$. + +Using the recursive relation, and considering the first two numbers as termination conditions, we can write the recursive code. Calling `fib(n)` will yield the $n$th number of the Fibonacci sequence: + +```src +[file]{recursion}-[class]{}-[func]{fib} +``` + +Observing the above code, we see that it recursively calls two functions within itself, **meaning that one call generates two branching calls**. As illustrated in the figure below, this continuous recursive calling eventually creates a recursion tree with a depth of $n$. + +![Fibonacci sequence recursion tree](iteration_and_recursion.assets/recursion_tree.png) + +Fundamentally, recursion embodies the paradigm of "breaking down a problem into smaller sub-problems." This divide-and-conquer strategy is crucial. + +- From an algorithmic perspective, many important strategies like searching, sorting, backtracking, divide-and-conquer, and dynamic programming directly or indirectly use this way of thinking. +- From a data structure perspective, recursion is naturally suited for dealing with linked lists, trees, and graphs, as they are well suited for analysis using the divide-and-conquer approach. + +## Comparison + +Summarizing the above content, the following table shows the differences between iteration and recursion in terms of implementation, performance, and applicability. + +

Table: Comparison of iteration and recursion characteristics

+ +| | Iteration | Recursion | +| ----------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Approach | Loop structure | Function calls itself | +| Time Efficiency | Generally higher efficiency, no function call overhead | Each function call generates overhead | +| Memory Usage | Typically uses a fixed size of memory space | Accumulative function calls can use a substantial amount of stack frame space | +| Suitable Problems | Suitable for simple loop tasks, intuitive and readable code | Suitable for problem decomposition, like trees, graphs, divide-and-conquer, backtracking, etc., concise and clear code structure | + +!!! tip + + If you find the following content difficult to understand, consider revisiting it after reading the "Stack" chapter. + +So, what is the intrinsic connection between iteration and recursion? Taking the above recursive function as an example, the summation operation occurs during the recursion's "return" phase. This means that the initially called function is the last to complete its summation operation, **mirroring the "last in, first out" principle of a stack**. + +Recursive terms like "call stack" and "stack frame space" hint at the close relationship between recursion and stacks. + +1. **Calling**: When a function is called, the system allocates a new stack frame on the "call stack" for that function, storing local variables, parameters, return addresses, and other data. +2. **Returning**: When a function completes execution and returns, the corresponding stack frame is removed from the "call stack," restoring the execution environment of the previous function. + +Therefore, **we can use an explicit stack to simulate the behavior of the call stack**, thus transforming recursion into an iterative form: + +```src +[file]{recursion}-[class]{}-[func]{for_loop_recur} +``` + +Observing the above code, when recursion is transformed into iteration, the code becomes more complex. Although iteration and recursion can often be transformed into each other, it's not always advisable to do so for two reasons: + +- The transformed code may become more challenging to understand and less readable. +- For some complex problems, simulating the behavior of the system's call stack can be quite challenging. + +In conclusion, **whether to choose iteration or recursion depends on the specific nature of the problem**. In programming practice, it's crucial to weigh the pros and cons of both and choose the most suitable approach for the situation at hand. diff --git a/en/docs/chapter_computational_complexity/performance_evaluation.md b/en/docs/chapter_computational_complexity/performance_evaluation.md new file mode 100644 index 0000000000..445a5f4556 --- /dev/null +++ b/en/docs/chapter_computational_complexity/performance_evaluation.md @@ -0,0 +1,49 @@ +# Algorithm efficiency assessment + +In algorithm design, we pursue the following two objectives in sequence. + +1. **Finding a Solution to the Problem**: The algorithm should reliably find the correct solution within the specified range of inputs. +2. **Seeking the Optimal Solution**: For the same problem, multiple solutions might exist, and we aim to find the most efficient algorithm possible. + +In other words, under the premise of being able to solve the problem, algorithm efficiency has become the main criterion for evaluating an algorithm, which includes the following two dimensions. + +- **Time efficiency**: The speed at which an algorithm runs. +- **Space efficiency**: The size of the memory space occupied by an algorithm. + +In short, **our goal is to design data structures and algorithms that are both fast and memory-efficient**. Effectively assessing algorithm efficiency is crucial because only then can we compare various algorithms and guide the process of algorithm design and optimization. + +There are mainly two methods of efficiency assessment: actual testing and theoretical estimation. + +## Actual testing + +Suppose we have algorithms `A` and `B`, both capable of solving the same problem, and we need to compare their efficiencies. The most direct method is to use a computer to run these two algorithms, monitor and record their runtime and memory usage. This assessment method reflects the actual situation, but it has significant limitations. + +On one hand, **it's difficult to eliminate interference from the testing environment**. Hardware configurations can affect algorithm performance. For example, an algorithm with a high degree of parallelism is better suited for running on multi-core CPUs, while an algorithm that involves intensive memory operations performs better with high-performance memory. The test results of an algorithm may vary across different machines. This means testing across multiple machines to calculate average efficiency becomes impractical. + +On the other hand, **conducting a full test is very resource-intensive**. Algorithm efficiency varies with input data size. For example, with smaller data volumes, algorithm `A` might run faster than `B`, but with larger data volumes, the test results may be the opposite. Therefore, to draw convincing conclusions, we need to test a wide range of input data sizes, which requires excessive computational resources. + +## Theoretical estimation + +Due to the significant limitations of actual testing, we can consider evaluating algorithm efficiency solely through calculations. This estimation method is known as asymptotic complexity analysis, or simply complexity analysis. + +Complexity analysis reflects the relationship between the time and space resources required for algorithm execution and the size of the input data. **It describes the trend of growth in the time and space required by the algorithm as the size of the input data increases**. This definition might sound complex, but we can break it down into three key points to understand it better. + +- "Time and space resources" correspond to time complexity and space complexity, respectively. +- "As the size of input data increases" means that complexity reflects the relationship between algorithm efficiency and the volume of input data. +- "The trend of growth in time and space" indicates that complexity analysis focuses not on the specific values of runtime or space occupied, but on the "rate" at which time or space increases. + +**Complexity analysis overcomes the disadvantages of actual testing methods**, reflected in the following aspects: + +- It does not require actually running the code, making it more environmentally friendly and energy efficient. +- It is independent of the testing environment and applicable to all operating platforms. +- It can reflect algorithm efficiency under different data volumes, especially in the performance of algorithms with large data volumes. + +!!! tip + + If you're still confused about the concept of complexity, don't worry. We will cover it in detail in subsequent chapters. + +Complexity analysis provides us with a "ruler" to evaluate the efficiency of an algorithm, enabling us to measure the time and space resources required to execute it and compare the efficiency of different algorithms. + +Complexity is a mathematical concept that might be abstract and challenging for beginners. From this perspective, complexity analysis might not be the most suitable topic to introduce first. However, when discussing the characteristics of a particular data structure or algorithm, it's hard to avoid analyzing its speed and space usage. + +In summary, it is recommended to develop a basic understanding of complexity analysis before diving deep into data structures and algorithms, **so that you can perform complexity analysis on simple algorithms**. \ No newline at end of file diff --git a/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png new file mode 100644 index 0000000000..3f617fd78e Binary files /dev/null and b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png differ diff --git a/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png new file mode 100644 index 0000000000..6be2f14a22 Binary files /dev/null and b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png differ diff --git a/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png new file mode 100644 index 0000000000..acb2e4b1e9 Binary files /dev/null and b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png differ diff --git a/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png new file mode 100644 index 0000000000..e54ef20b3c Binary files /dev/null and b/en/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png differ diff --git a/en/docs/chapter_computational_complexity/space_complexity.assets/space_types.png b/en/docs/chapter_computational_complexity/space_complexity.assets/space_types.png new file mode 100644 index 0000000000..a2fac7482b Binary files /dev/null and b/en/docs/chapter_computational_complexity/space_complexity.assets/space_types.png differ diff --git a/en/docs/chapter_computational_complexity/space_complexity.md b/en/docs/chapter_computational_complexity/space_complexity.md new file mode 100644 index 0000000000..20157220e3 --- /dev/null +++ b/en/docs/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,803 @@ +# Space complexity + +Space complexity is used to measure the growth trend of the memory space occupied by an algorithm as the amount of data increases. This concept is very similar to time complexity, except that "running time" is replaced with "occupied memory space". + +## Space related to algorithms + +The memory space used by an algorithm during its execution mainly includes the following types. + +- **Input space**: Used to store the input data of the algorithm. +- **Temporary space**: Used to store variables, objects, function contexts, and other data during the algorithm's execution. +- **Output space**: Used to store the output data of the algorithm. + +Generally, the scope of space complexity statistics includes both "Temporary Space" and "Output Space". + +Temporary space can be further divided into three parts. + +- **Temporary data**: Used to save various constants, variables, objects, etc., during the algorithm's execution. +- **Stack frame space**: Used to save the context data of the called function. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns. +- **Instruction space**: Used to store compiled program instructions, which are usually negligible in actual statistics. + +When analyzing the space complexity of a program, **we typically count the Temporary Data, Stack Frame Space, and Output Data**, as shown in the figure below. + +![Space types used in algorithms](space_complexity.assets/space_types.png) + +The relevant code is as follows: + +=== "Python" + + ```python title="" + class Node: + """Classes""" + def __init__(self, x: int): + self.val: int = x # node value + self.next: Node | None = None # reference to the next node + + def function() -> int: + """Functions""" + # Perform certain operations... + return 0 + + def algorithm(n) -> int: # input data + A = 0 # temporary data (constant, usually in uppercase) + b = 0 # temporary data (variable) + node = Node(0) # temporary data (object) + c = function() # Stack frame space (call function) + return A + b + c # output data + ``` + +=== "C++" + + ```cpp title="" + /* Structures */ + struct Node { + int val; + Node *next; + Node(int x) : val(x), next(nullptr) {} + }; + + /* Functions */ + int func() { + // Perform certain operations... + return 0; + } + + int algorithm(int n) { // input data + const int a = 0; // temporary data (constant) + int b = 0; // temporary data (variable) + Node* node = new Node(0); // temporary data (object) + int c = func(); // stack frame space (call function) + return a + b + c; // output data + } + ``` + +=== "Java" + + ```java title="" + /* Classes */ + class Node { + int val; + Node next; + Node(int x) { val = x; } + } + + /* Functions */ + int function() { + // Perform certain operations... + return 0; + } + + int algorithm(int n) { // input data + final int a = 0; // temporary data (constant) + int b = 0; // temporary data (variable) + Node node = new Node(0); // temporary data (object) + int c = function(); // stack frame space (call function) + return a + b + c; // output data + } + ``` + +=== "C#" + + ```csharp title="" + /* Classes */ + class Node { + int val; + Node next; + Node(int x) { val = x; } + } + + /* Functions */ + int Function() { + // Perform certain operations... + return 0; + } + + int Algorithm(int n) { // input data + const int a = 0; // temporary data (constant) + int b = 0; // temporary data (variable) + Node node = new(0); // temporary data (object) + int c = Function(); // stack frame space (call function) + return a + b + c; // output data + } + ``` + +=== "Go" + + ```go title="" + /* Structures */ + type node struct { + val int + next *node + } + + /* Create node structure */ + func newNode(val int) *node { + return &node{val: val} + } + + /* Functions */ + func function() int { + // Perform certain operations... + return 0 + } + + func algorithm(n int) int { // input data + const a = 0 // temporary data (constant) + b := 0 // temporary storage of data (variable) + newNode(0) // temporary data (object) + c := function() // stack frame space (call function) + return a + b + c // output data + } + ``` + +=== "Swift" + + ```swift title="" + /* Classes */ + class Node { + var val: Int + var next: Node? + + init(x: Int) { + val = x + } + } + + /* Functions */ + func function() -> Int { + // Perform certain operations... + return 0 + } + + func algorithm(n: Int) -> Int { // input data + let a = 0 // temporary data (constant) + var b = 0 // temporary data (variable) + let node = Node(x: 0) // temporary data (object) + let c = function() // stack frame space (call function) + return a + b + c // output data + } + ``` + +=== "JS" + + ```javascript title="" + /* Classes */ + class Node { + val; + next; + constructor(val) { + this.val = val === undefined ? 0 : val; // node value + this.next = null; // reference to the next node + } + } + + /* Functions */ + function constFunc() { + // Perform certain operations + return 0; + } + + function algorithm(n) { // input data + const a = 0; // temporary data (constant) + let b = 0; // temporary data (variable) + const node = new Node(0); // temporary data (object) + const c = constFunc(); // Stack frame space (calling function) + return a + b + c; // output data + } + ``` + +=== "TS" + + ```typescript title="" + /* Classes */ + class Node { + val: number; + next: Node | null; + constructor(val?: number) { + this.val = val === undefined ? 0 : val; // node value + this.next = null; // reference to the next node + } + } + + /* Functions */ + function constFunc(): number { + // Perform certain operations + return 0; + } + + function algorithm(n: number): number { // input data + const a = 0; // temporary data (constant) + let b = 0; // temporary data (variable) + const node = new Node(0); // temporary data (object) + const c = constFunc(); // Stack frame space (calling function) + return a + b + c; // output data + } + ``` + +=== "Dart" + + ```dart title="" + /* Classes */ + class Node { + int val; + Node next; + Node(this.val, [this.next]); + } + + /* Functions */ + int function() { + // Perform certain operations... + return 0; + } + + int algorithm(int n) { // input data + const int a = 0; // temporary data (constant) + int b = 0; // temporary data (variable) + Node node = Node(0); // temporary data (object) + int c = function(); // stack frame space (call function) + return a + b + c; // output data + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Structures */ + struct Node { + val: i32, + next: Option>>, + } + + /* Constructor */ + impl Node { + fn new(val: i32) -> Self { + Self { val: val, next: None } + } + } + + /* Functions */ + fn function() -> i32 { + // Perform certain operations... + return 0; + } + + fn algorithm(n: i32) -> i32 { // input data + const a: i32 = 0; // temporary data (constant) + let mut b = 0; // temporary data (variable) + let node = Node::new(0); // temporary data (object) + let c = function(); // stack frame space (call function) + return a + b + c; // output data + } + ``` + +=== "C" + + ```c title="" + /* Functions */ + int func() { + // Perform certain operations... + return 0; + } + + int algorithm(int n) { // input data + const int a = 0; // temporary data (constant) + int b = 0; // temporary data (variable) + int c = func(); // stack frame space (call function) + return a + b + c; // output data + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +## Calculation method + +The method for calculating space complexity is roughly similar to that of time complexity, with the only change being the shift of the statistical object from "number of operations" to "size of used space". + +However, unlike time complexity, **we usually only focus on the worst-case space complexity**. This is because memory space is a hard requirement, and we must ensure that there is enough memory space reserved under all input data. + +Consider the following code, the term "worst-case" in worst-case space complexity has two meanings. + +1. **Based on the worst input data**: When $n < 10$, the space complexity is $O(1)$; but when $n > 10$, the initialized array `nums` occupies $O(n)$ space, thus the worst-case space complexity is $O(n)$. +2. **Based on the peak memory used during the algorithm's execution**: For example, before executing the last line, the program occupies $O(1)$ space; when initializing the array `nums`, the program occupies $O(n)$ space, hence the worst-case space complexity is $O(n)$. + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 0 # O(1) + b = [0] * 10000 # O(1) + if n > 10: + nums = [0] * n # O(n) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 0; // O(1) + vector b(10000); // O(1) + if (n > 10) + vector nums(n); // O(n) + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) + int[] nums = new int[n]; // O(n) + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) { + int[] nums = new int[n]; // O(n) + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 0 // O(1) + b := make([]int, 10000) // O(1) + var nums []int + if n > 10 { + nums := make([]int, n) // O(n) + } + fmt.Println(a, b, nums) + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + let a = 0 // O(1) + let b = Array(repeating: 0, count: 10000) // O(1) + if n > 10 { + let nums = Array(repeating: 0, count: n) // O(n) + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 0; // O(1) + List b = List.filled(10000, 0); // O(1) + if (n > 10) { + List nums = List.filled(n, 0); // O(n) + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let a = 0; // O(1) + let b = [0; 10000]; // O(1) + if n > 10 { + let nums = vec![0; n as usize]; // O(n) + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 0; // O(1) + int b[10000]; // O(1) + if (n > 10) + int nums[n] = {0}; // O(n) + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +**In recursive functions, stack frame space must be taken into count**. Consider the following code: + +=== "Python" + + ```python title="" + def function() -> int: + # Perform certain operations + return 0 + + def loop(n: int): + """Loop O(1)""" + for _ in range(n): + function() + + def recur(n: int): + """Recursion O(n)""" + if n == 1: + return + return recur(n - 1) + ``` + +=== "C++" + + ```cpp title="" + int func() { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* Recursion O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Java" + + ```java title="" + int function() { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* Recursion O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "C#" + + ```csharp title="" + int Function() { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + void Loop(int n) { + for (int i = 0; i < n; i++) { + Function(); + } + } + /* Recursion O(n) */ + int Recur(int n) { + if (n == 1) return 1; + return Recur(n - 1); + } + ``` + +=== "Go" + + ```go title="" + func function() int { + // Perform certain operations + return 0 + } + + /* Cycle O(1) */ + func loop(n int) { + for i := 0; i < n; i++ { + function() + } + } + + /* Recursion O(n) */ + func recur(n int) { + if n == 1 { + return + } + recur(n - 1) + } + ``` + +=== "Swift" + + ```swift title="" + @discardableResult + func function() -> Int { + // Perform certain operations + return 0 + } + + /* Cycle O(1) */ + func loop(n: Int) { + for _ in 0 ..< n { + function() + } + } + + /* Recursion O(n) */ + func recur(n: Int) { + if n == 1 { + return + } + recur(n: n - 1) + } + ``` + +=== "JS" + + ```javascript title="" + function constFunc() { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + function loop(n) { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* Recursion O(n) */ + function recur(n) { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "TS" + + ```typescript title="" + function constFunc(): number { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + function loop(n: number): void { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* Recursion O(n) */ + function recur(n: number): void { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "Dart" + + ```dart title="" + int function() { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* Recursion O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Rust" + + ```rust title="" + fn function() -> i32 { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + fn loop(n: i32) { + for i in 0..n { + function(); + } + } + /* Recursion O(n) */ + void recur(n: i32) { + if n == 1 { + return; + } + recur(n - 1); + } + ``` + +=== "C" + + ```c title="" + int func() { + // Perform certain operations + return 0; + } + /* Cycle O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* Recursion O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +The time complexity of both `loop()` and `recur()` functions is $O(n)$, but their space complexities differ. + +- The `loop()` function calls `function()` $n$ times in a loop, where each iteration's `function()` returns and releases its stack frame space, so the space complexity remains $O(1)$. +- The recursive function `recur()` will have $n$ instances of unreturned `recur()` existing simultaneously during its execution, thus occupying $O(n)$ stack frame space. + +## Common types + +Let the size of the input data be $n$, the figure below displays common types of space complexities (arranged from low to high). + +$$ +\begin{aligned} +& O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline +& \text{Constant} < \text{Logarithmic} < \text{Linear} < \text{Quadratic} < \text{Exponential} +\end{aligned} +$$ + +![Common types of space complexity](space_complexity.assets/space_complexity_common_types.png) + +### Constant order $O(1)$ + +Constant order is common in constants, variables, objects that are independent of the size of input data $n$. + +Note that memory occupied by initializing variables or calling functions in a loop, which is released upon entering the next cycle, does not accumulate over space, thus the space complexity remains $O(1)$: + +```src +[file]{space_complexity}-[class]{}-[func]{constant} +``` + +### Linear order $O(n)$ + +Linear order is common in arrays, linked lists, stacks, queues, etc., where the number of elements is proportional to $n$: + +```src +[file]{space_complexity}-[class]{}-[func]{linear} +``` + +As shown in the figure below, this function's recursive depth is $n$, meaning there are $n$ instances of unreturned `linear_recur()` function, using $O(n)$ size of stack frame space: + +```src +[file]{space_complexity}-[class]{}-[func]{linear_recur} +``` + +![Recursive function generating linear order space complexity](space_complexity.assets/space_complexity_recursive_linear.png) + +### Quadratic order $O(n^2)$ + +Quadratic order is common in matrices and graphs, where the number of elements is quadratic to $n$: + +```src +[file]{space_complexity}-[class]{}-[func]{quadratic} +``` + +As shown in the figure below, the recursive depth of this function is $n$, and in each recursive call, an array is initialized with lengths $n$, $n-1$, $\dots$, $2$, $1$, averaging $n/2$, thus overall occupying $O(n^2)$ space: + +```src +[file]{space_complexity}-[class]{}-[func]{quadratic_recur} +``` + +![Recursive function generating quadratic order space complexity](space_complexity.assets/space_complexity_recursive_quadratic.png) + +### Exponential order $O(2^n)$ + +Exponential order is common in binary trees. Observe the figure below, a "full binary tree" with $n$ levels has $2^n - 1$ nodes, occupying $O(2^n)$ space: + +```src +[file]{space_complexity}-[class]{}-[func]{build_tree} +``` + +![Full binary tree generating exponential order space complexity](space_complexity.assets/space_complexity_exponential.png) + +### Logarithmic order $O(\log n)$ + +Logarithmic order is common in divide-and-conquer algorithms. For example, in merge sort, an array of length $n$ is recursively divided in half each round, forming a recursion tree of height $\log n$, using $O(\log n)$ stack frame space. + +Another example is converting a number to a string. Given a positive integer $n$, its number of digits is $\log_{10} n + 1$, corresponding to the length of the string, thus the space complexity is $O(\log_{10} n + 1) = O(\log n)$. + +## Balancing time and space + +Ideally, we aim for both time complexity and space complexity to be optimal. However, in practice, optimizing both simultaneously is often difficult. + +**Lowering time complexity usually comes at the cost of increased space complexity, and vice versa**. The approach of sacrificing memory space to improve algorithm speed is known as "space-time tradeoff"; the reverse is known as "time-space tradeoff". + +The choice depends on which aspect we value more. In most cases, time is more precious than space, so "space-time tradeoff" is often the more common strategy. Of course, controlling space complexity is also very important when dealing with large volumes of data. diff --git a/en/docs/chapter_computational_complexity/summary.md b/en/docs/chapter_computational_complexity/summary.md new file mode 100644 index 0000000000..1ca8ded4ed --- /dev/null +++ b/en/docs/chapter_computational_complexity/summary.md @@ -0,0 +1,49 @@ +# Summary + +### Key review + +**Algorithm Efficiency Assessment** + +- Time efficiency and space efficiency are the two main criteria for assessing the merits of an algorithm. +- We can assess algorithm efficiency through actual testing, but it's challenging to eliminate the influence of the test environment, and it consumes substantial computational resources. +- Complexity analysis can overcome the disadvantages of actual testing. Its results are applicable across all operating platforms and can reveal the efficiency of algorithms at different data scales. + +**Time Complexity** + +- Time complexity measures the trend of an algorithm's running time with the increase in data volume, effectively assessing algorithm efficiency. However, it can fail in certain cases, such as with small input data volumes or when time complexities are the same, making it challenging to precisely compare the efficiency of algorithms. +- Worst-case time complexity is denoted using big-$O$ notation, representing the asymptotic upper bound, reflecting the growth level of the number of operations $T(n)$ as $n$ approaches infinity. +- Calculating time complexity involves two steps: first counting the number of operations, then determining the asymptotic upper bound. +- Common time complexities, arranged from low to high, include $O(1)$, $O(\log n)$, $O(n)$, $O(n \log n)$, $O(n^2)$, $O(2^n)$, and $O(n!)$, among others. +- The time complexity of some algorithms is not fixed and depends on the distribution of input data. Time complexities are divided into worst, best, and average cases. The best case is rarely used because input data generally needs to meet strict conditions to achieve the best case. +- Average time complexity reflects the efficiency of an algorithm under random data inputs, closely resembling the algorithm's performance in actual applications. Calculating average time complexity requires accounting for the distribution of input data and the subsequent mathematical expectation. + +**Space Complexity** + +- Space complexity, similar to time complexity, measures the trend of memory space occupied by an algorithm with the increase in data volume. +- The relevant memory space used during the algorithm's execution can be divided into input space, temporary space, and output space. Generally, input space is not included in space complexity calculations. Temporary space can be divided into temporary data, stack frame space, and instruction space, where stack frame space usually affects space complexity only in recursive functions. +- We usually focus only on the worst-case space complexity, which means calculating the space complexity of the algorithm under the worst input data and at the worst moment of operation. +- Common space complexities, arranged from low to high, include $O(1)$, $O(\log n)$, $O(n)$, $O(n^2)$, and $O(2^n)$, among others. + +### Q & A + +**Q**: Is the space complexity of tail recursion $O(1)$? + +Theoretically, the space complexity of a tail-recursive function can be optimized to $O(1)$. However, most programming languages (such as Java, Python, C++, Go, C#) do not support automatic optimization of tail recursion, so it's generally considered to have a space complexity of $O(n)$. + +**Q**: What is the difference between the terms "function" and "method"? + +A function can be executed independently, with all parameters passed explicitly. A method is associated with an object and is implicitly passed to the object calling it, able to operate on the data contained within an instance of a class. + +Here are some examples from common programming languages: + +- C is a procedural programming language without object-oriented concepts, so it only has functions. However, we can simulate object-oriented programming by creating structures (struct), and functions associated with these structures are equivalent to methods in other programming languages. +- Java and C# are object-oriented programming languages where code blocks (methods) are typically part of a class. Static methods behave like functions because they are bound to the class and cannot access specific instance variables. +- C++ and Python support both procedural programming (functions) and object-oriented programming (methods). + +**Q**: Does the "Common Types of Space Complexity" figure reflect the absolute size of occupied space? + +No, the figure shows space complexities, which reflect growth trends, not the absolute size of the occupied space. + +If you take $n = 8$, you might find that the values of each curve don't correspond to their functions. This is because each curve includes a constant term, intended to compress the value range into a visually comfortable range. + +In practice, since we usually don't know the "constant term" complexity of each method, it's generally not possible to choose the best solution for $n = 8$ based solely on complexity. However, for $n = 8^5$, it's much easier to choose, as the growth trend becomes dominant. diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png b/en/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png new file mode 100644 index 0000000000..a1f8f93d5b Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png new file mode 100644 index 0000000000..805513ec5a Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png new file mode 100644 index 0000000000..71f87f4257 Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png new file mode 100644 index 0000000000..262ba476a7 Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png new file mode 100644 index 0000000000..bfda3c3f1d Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png new file mode 100644 index 0000000000..2ad918f113 Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png new file mode 100644 index 0000000000..428cbef06c Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png new file mode 100644 index 0000000000..b877d0fcae Binary files /dev/null and b/en/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png differ diff --git a/en/docs/chapter_computational_complexity/time_complexity.md b/en/docs/chapter_computational_complexity/time_complexity.md new file mode 100644 index 0000000000..83882debd9 --- /dev/null +++ b/en/docs/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,1112 @@ +# Time complexity + +The runtime can intuitively assess the efficiency of an algorithm. How can we accurately estimate the runtime of a piece of an algorithm? + +1. **Determining the Running Platform**: This includes hardware configuration, programming language, system environment, etc., all of which can affect the efficiency of code execution. +2. **Evaluating the Run Time for Various Computational Operations**: For instance, an addition operation `+` might take 1 ns, a multiplication operation `*` might take 10 ns, a print operation `print()` might take 5 ns, etc. +3. **Counting All the Computational Operations in the Code**: Summing the execution times of all these operations gives the total run time. + +For example, consider the following code with an input size of $n$: + +=== "Python" + + ```python title="" + # Under an operating platform + def algorithm(n: int): + a = 2 # 1 ns + a = a + 1 # 1 ns + a = a * 2 # 10 ns + # Cycle n times + for _ in range(n): # 1 ns + print(0) # 5 ns + ``` + +=== "C++" + + ```cpp title="" + // Under a particular operating platform + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed + cout << 0 << endl; // 5 ns + } + } + ``` + +=== "Java" + + ```java title="" + // Under a particular operating platform + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed + System.out.println(0); // 5 ns + } + } + ``` + +=== "C#" + + ```csharp title="" + // Under a particular operating platform + void Algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed + Console.WriteLine(0); // 5 ns + } + } + ``` + +=== "Go" + + ```go title="" + // Under a particular operating platform + func algorithm(n int) { + a := 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // Loop n times + for i := 0; i < n; i++ { // 1 ns + fmt.Println(a) // 5 ns + } + } + ``` + +=== "Swift" + + ```swift title="" + // Under a particular operating platform + func algorithm(n: Int) { + var a = 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // Loop n times + for _ in 0 ..< n { // 1 ns + print(0) // 5 ns + } + } + ``` + +=== "JS" + + ```javascript title="" + // Under a particular operating platform + function algorithm(n) { + var a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed + console.log(0); // 5 ns + } + } + ``` + +=== "TS" + + ```typescript title="" + // Under a particular operating platform + function algorithm(n: number): void { + var a: number = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed + console.log(0); // 5 ns + } + } + ``` + +=== "Dart" + + ```dart title="" + // Under a particular operating platform + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed + print(0); // 5 ns + } + } + ``` + +=== "Rust" + + ```rust title="" + // Under a particular operating platform + fn algorithm(n: i32) { + let mut a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for _ in 0..n { // 1 ns for each round i++ + println!("{}", 0); // 5 ns + } + } + ``` + +=== "C" + + ```c title="" + // Under a particular operating platform + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // Loop n times + for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed + printf("%d", 0); // 5 ns + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + // Under a particular operating platform + fn algorithm(n: usize) void { + var a: i32 = 2; // 1 ns + a += 1; // 1 ns + a *= 2; // 10 ns + // Loop n times + for (0..n) |_| { // 1 ns + std.debug.print("{}\n", .{0}); // 5 ns + } + } + ``` + +Using the above method, the run time of the algorithm can be calculated as $(6n + 12)$ ns: + +$$ +1 + 1 + 10 + (1 + 5) \times n = 6n + 12 +$$ + +However, in practice, **counting the run time of an algorithm is neither practical nor reasonable**. First, we don't want to tie the estimated time to the running platform, as algorithms need to run on various platforms. Second, it's challenging to know the run time for each type of operation, making the estimation process difficult. + +## Assessing time growth trend + +Time complexity analysis does not count the algorithm's run time, **but rather the growth trend of the run time as the data volume increases**. + +Let's understand this concept of "time growth trend" with an example. Assume the input data size is $n$, and consider three algorithms `A`, `B`, and `C`: + +=== "Python" + + ```python title="" + # Time complexity of algorithm A: constant order + def algorithm_A(n: int): + print(0) + # Time complexity of algorithm B: linear order + def algorithm_B(n: int): + for _ in range(n): + print(0) + # Time complexity of algorithm C: constant order + def algorithm_C(n: int): + for _ in range(1000000): + print(0) + ``` + +=== "C++" + + ```cpp title="" + // Time complexity of algorithm A: constant order + void algorithm_A(int n) { + cout << 0 << endl; + } + // Time complexity of algorithm B: linear order + void algorithm_B(int n) { + for (int i = 0; i < n; i++) { + cout << 0 << endl; + } + } + // Time complexity of algorithm C: constant order + void algorithm_C(int n) { + for (int i = 0; i < 1000000; i++) { + cout << 0 << endl; + } + } + ``` + +=== "Java" + + ```java title="" + // Time complexity of algorithm A: constant order + void algorithm_A(int n) { + System.out.println(0); + } + // Time complexity of algorithm B: linear order + void algorithm_B(int n) { + for (int i = 0; i < n; i++) { + System.out.println(0); + } + } + // Time complexity of algorithm C: constant order + void algorithm_C(int n) { + for (int i = 0; i < 1000000; i++) { + System.out.println(0); + } + } + ``` + +=== "C#" + + ```csharp title="" + // Time complexity of algorithm A: constant order + void AlgorithmA(int n) { + Console.WriteLine(0); + } + // Time complexity of algorithm B: linear order + void AlgorithmB(int n) { + for (int i = 0; i < n; i++) { + Console.WriteLine(0); + } + } + // Time complexity of algorithm C: constant order + void AlgorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + Console.WriteLine(0); + } + } + ``` + +=== "Go" + + ```go title="" + // Time complexity of algorithm A: constant order + func algorithm_A(n int) { + fmt.Println(0) + } + // Time complexity of algorithm B: linear order + func algorithm_B(n int) { + for i := 0; i < n; i++ { + fmt.Println(0) + } + } + // Time complexity of algorithm C: constant order + func algorithm_C(n int) { + for i := 0; i < 1000000; i++ { + fmt.Println(0) + } + } + ``` + +=== "Swift" + + ```swift title="" + // Time complexity of algorithm A: constant order + func algorithmA(n: Int) { + print(0) + } + + // Time complexity of algorithm B: linear order + func algorithmB(n: Int) { + for _ in 0 ..< n { + print(0) + } + } + + // Time complexity of algorithm C: constant order + func algorithmC(n: Int) { + for _ in 0 ..< 1_000_000 { + print(0) + } + } + ``` + +=== "JS" + + ```javascript title="" + // Time complexity of algorithm A: constant order + function algorithm_A(n) { + console.log(0); + } + // Time complexity of algorithm B: linear order + function algorithm_B(n) { + for (let i = 0; i < n; i++) { + console.log(0); + } + } + // Time complexity of algorithm C: constant order + function algorithm_C(n) { + for (let i = 0; i < 1000000; i++) { + console.log(0); + } + } + + ``` + +=== "TS" + + ```typescript title="" + // Time complexity of algorithm A: constant order + function algorithm_A(n: number): void { + console.log(0); + } + // Time complexity of algorithm B: linear order + function algorithm_B(n: number): void { + for (let i = 0; i < n; i++) { + console.log(0); + } + } + // Time complexity of algorithm C: constant order + function algorithm_C(n: number): void { + for (let i = 0; i < 1000000; i++) { + console.log(0); + } + } + ``` + +=== "Dart" + + ```dart title="" + // Time complexity of algorithm A: constant order + void algorithmA(int n) { + print(0); + } + // Time complexity of algorithm B: linear order + void algorithmB(int n) { + for (int i = 0; i < n; i++) { + print(0); + } + } + // Time complexity of algorithm C: constant order + void algorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + print(0); + } + } + ``` + +=== "Rust" + + ```rust title="" + // Time complexity of algorithm A: constant order + fn algorithm_A(n: i32) { + println!("{}", 0); + } + // Time complexity of algorithm B: linear order + fn algorithm_B(n: i32) { + for _ in 0..n { + println!("{}", 0); + } + } + // Time complexity of algorithm C: constant order + fn algorithm_C(n: i32) { + for _ in 0..1000000 { + println!("{}", 0); + } + } + ``` + +=== "C" + + ```c title="" + // Time complexity of algorithm A: constant order + void algorithm_A(int n) { + printf("%d", 0); + } + // Time complexity of algorithm B: linear order + void algorithm_B(int n) { + for (int i = 0; i < n; i++) { + printf("%d", 0); + } + } + // Time complexity of algorithm C: constant order + void algorithm_C(int n) { + for (int i = 0; i < 1000000; i++) { + printf("%d", 0); + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + // Time complexity of algorithm A: constant order + fn algorithm_A(n: usize) void { + _ = n; + std.debug.print("{}\n", .{0}); + } + // Time complexity of algorithm B: linear order + fn algorithm_B(n: i32) void { + for (0..n) |_| { + std.debug.print("{}\n", .{0}); + } + } + // Time complexity of algorithm C: constant order + fn algorithm_C(n: i32) void { + _ = n; + for (0..1000000) |_| { + std.debug.print("{}\n", .{0}); + } + } + ``` + +The figure below shows the time complexities of these three algorithms. + +- Algorithm `A` has just one print operation, and its run time does not grow with $n$. Its time complexity is considered "constant order." +- Algorithm `B` involves a print operation looping $n$ times, and its run time grows linearly with $n$. Its time complexity is "linear order." +- Algorithm `C` has a print operation looping 1,000,000 times. Although it takes a long time, it is independent of the input data size $n$. Therefore, the time complexity of `C` is the same as `A`, which is "constant order." + +![Time growth trend of algorithms a, b, and c](time_complexity.assets/time_complexity_simple_example.png) + +Compared to directly counting the run time of an algorithm, what are the characteristics of time complexity analysis? + +- **Time complexity effectively assesses algorithm efficiency**. For instance, algorithm `B` has linearly growing run time, which is slower than algorithm `A` when $n > 1$ and slower than `C` when $n > 1,000,000$. In fact, as long as the input data size $n$ is sufficiently large, a "constant order" complexity algorithm will always be better than a "linear order" one, demonstrating the essence of time growth trend. +- **Time complexity analysis is more straightforward**. Obviously, the running platform and the types of computational operations are irrelevant to the trend of run time growth. Therefore, in time complexity analysis, we can simply treat the execution time of all computational operations as the same "unit time," simplifying the "computational operation run time count" to a "computational operation count." This significantly reduces the complexity of estimation. +- **Time complexity has its limitations**. For example, although algorithms `A` and `C` have the same time complexity, their actual run times can be quite different. Similarly, even though algorithm `B` has a higher time complexity than `C`, it is clearly superior when the input data size $n$ is small. In these cases, it's difficult to judge the efficiency of algorithms based solely on time complexity. Nonetheless, despite these issues, complexity analysis remains the most effective and commonly used method for evaluating algorithm efficiency. + +## Asymptotic upper bound + +Consider a function with an input size of $n$: + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +1 + a = a + 1 # +1 + a = a * 2 # +1 + # Cycle n times + for i in range(n): # +1 + print(0) # +1 + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Loop n times + for (int i = 0; i < n; i++) { // +1 (execute i ++ every round) + cout << 0 << endl; // +1 + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Loop n times + for (int i = 0; i < n; i++) { // +1 (execute i ++ every round) + System.out.println(0); // +1 + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Loop n times + for (int i = 0; i < n; i++) { // +1 (execute i ++ every round) + Console.WriteLine(0); // +1 + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // Loop n times + for i := 0; i < n; i++ { // +1 + fmt.Println(a) // +1 + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // Loop n times + for _ in 0 ..< n { // +1 + print(0) // +1 + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + var a = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // Loop n times + for(let i = 0; i < n; i++){ // +1 (execute i ++ every round) + console.log(0); // +1 + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void{ + var a: number = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // Loop n times + for(let i = 0; i < n; i++){ // +1 (execute i ++ every round) + console.log(0); // +1 + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Loop n times + for (int i = 0; i < n; i++) { // +1 (execute i ++ every round) + print(0); // +1 + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + + // Loop n times + for _ in 0..n { // +1 (execute i ++ every round) + println!("{}", 0); // +1 + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Loop n times + for (int i = 0; i < n; i++) { // +1 (execute i ++ every round) + printf("%d", 0); // +1 + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + fn algorithm(n: usize) void { + var a: i32 = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // Loop n times + for (0..n) |_| { // +1 (execute i ++ every round) + std.debug.print("{}\n", .{0}); // +1 + } + } + ``` + +Given a function that represents the number of operations of an algorithm as a function of the input size $n$, denoted as $T(n)$, consider the following example: + +$$ +T(n) = 3 + 2n +$$ + +Since $T(n)$ is a linear function, its growth trend is linear, and therefore, its time complexity is of linear order, denoted as $O(n)$. This mathematical notation, known as big-O notation, represents the asymptotic upper bound of the function $T(n)$. + +In essence, time complexity analysis is about finding the asymptotic upper bound of the "number of operations $T(n)$". It has a precise mathematical definition. + +!!! note "Asymptotic Upper Bound" + + If there exist positive real numbers $c$ and $n_0$ such that for all $n > n_0$, $T(n) \leq c \cdot f(n)$, then $f(n)$ is considered an asymptotic upper bound of $T(n)$, denoted as $T(n) = O(f(n))$. + +As shown in the figure below, calculating the asymptotic upper bound involves finding a function $f(n)$ such that, as $n$ approaches infinity, $T(n)$ and $f(n)$ have the same growth order, differing only by a constant factor $c$. + +![Asymptotic upper bound of a function](time_complexity.assets/asymptotic_upper_bound.png) + +## Calculation method + +While the concept of asymptotic upper bound might seem mathematically dense, you don't need to fully grasp it right away. Let's first understand the method of calculation, which can be practiced and comprehended over time. + +Once $f(n)$ is determined, we obtain the time complexity $O(f(n))$. But how do we determine the asymptotic upper bound $f(n)$? This process generally involves two steps: counting the number of operations and determining the asymptotic upper bound. + +### Step 1: counting the number of operations + +This step involves going through the code line by line. However, due to the presence of the constant $c$ in $c \cdot f(n)$, **all coefficients and constant terms in $T(n)$ can be ignored**. This principle allows for simplification techniques in counting operations. + +1. **Ignore constant terms in $T(n)$**, as they do not affect the time complexity being independent of $n$. +2. **Omit all coefficients**. For example, looping $2n$, $5n + 1$ times, etc., can be simplified to $n$ times since the coefficient before $n$ does not impact the time complexity. +3. **Use multiplication for nested loops**. The total number of operations equals the product of the number of operations in each loop, applying the simplification techniques from points 1 and 2 for each loop level. + +Given a function, we can use these techniques to count operations: + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +0 (trick 1) + a = a + n # +0 (trick 1) + # +n (technique 2) + for i in range(5 * n + 1): + print(0) + # +n*n (technique 3) + for i in range(2 * n): + for j in range(n + 1): + print(0) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + // +n (technique 2) + for (int i = 0; i < 5 * n + 1; i++) { + cout << 0 << endl; + } + // +n*n (technique 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + cout << 0 << endl; + } + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + // +n (technique 2) + for (int i = 0; i < 5 * n + 1; i++) { + System.out.println(0); + } + // +n*n (technique 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + System.out.println(0); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + // +n (technique 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n (technique 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +0 (trick 1) + a = a + n // +0 (trick 1) + // +n (technique 2) + for i := 0; i < 5 * n + 1; i++ { + fmt.Println(0) + } + // +n*n (technique 3) + for i := 0; i < 2 * n; i++ { + for j := 0; j < n + 1; j++ { + fmt.Println(0) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +0 (trick 1) + a = a + n // +0 (trick 1) + // +n (technique 2) + for _ in 0 ..< (5 * n + 1) { + print(0) + } + // +n*n (technique 3) + for _ in 0 ..< (2 * n) { + for _ in 0 ..< (n + 1) { + print(0) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + let a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + // +n (technique 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n (technique 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + let a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + // +n (technique 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n (technique 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + // +n (technique 2) + for (int i = 0; i < 5 * n + 1; i++) { + print(0); + } + // +n*n (technique 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + print(0); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + + // +n (technique 2) + for i in 0..(5 * n + 1) { + println!("{}", 0); + } + + // +n*n (technique 3) + for i in 0..(2 * n) { + for j in 0..(n + 1) { + println!("{}", 0); + } + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +0 (trick 1) + a = a + n; // +0 (trick 1) + // +n (technique 2) + for (int i = 0; i < 5 * n + 1; i++) { + printf("%d", 0); + } + // +n*n (technique 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + printf("%d", 0); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + fn algorithm(n: usize) void { + var a: i32 = 1; // +0 (trick 1) + a = a + @as(i32, @intCast(n)); // +0 (trick 1) + + // +n (technique 2) + for(0..(5 * n + 1)) |_| { + std.debug.print("{}\n", .{0}); + } + + // +n*n (technique 3) + for(0..(2 * n)) |_| { + for(0..(n + 1)) |_| { + std.debug.print("{}\n", .{0}); + } + } + } + ``` + +The formula below shows the counting results before and after simplification, both leading to a time complexity of $O(n^2)$: + +$$ +\begin{aligned} +T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{Complete Count (-.-|||)} \newline +& = 2n^2 + 7n + 3 \newline +T(n) & = n^2 + n & \text{Simplified Count (o.O)} +\end{aligned} +$$ + +### Step 2: determining the asymptotic upper bound + +**The time complexity is determined by the highest order term in $T(n)$**. This is because, as $n$ approaches infinity, the highest order term dominates, rendering the influence of other terms negligible. + +The following table illustrates examples of different operation counts and their corresponding time complexities. Some exaggerated values are used to emphasize that coefficients cannot alter the order of growth. When $n$ becomes very large, these constants become insignificant. + +

Table: Time complexity for different operation counts

+ +| Operation Count $T(n)$ | Time Complexity $O(f(n))$ | +| ---------------------- | ------------------------- | +| $100000$ | $O(1)$ | +| $3n + 2$ | $O(n)$ | +| $2n^2 + 3n + 2$ | $O(n^2)$ | +| $n^3 + 10000n^2$ | $O(n^3)$ | +| $2^n + 10000n^{10000}$ | $O(2^n)$ | + +## Common types of time complexity + +Let's consider the input data size as $n$. The common types of time complexities are shown in the figure below, arranged from lowest to highest: + +$$ +\begin{aligned} +& O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline +& \text{Constant} < \text{Log} < \text{Linear} < \text{Linear-Log} < \text{Quadratic} < \text{Exp} < \text{Factorial} +\end{aligned} +$$ + +![Common types of time complexity](time_complexity.assets/time_complexity_common_types.png) + +### Constant order $O(1)$ + +Constant order means the number of operations is independent of the input data size $n$. In the following function, although the number of operations `size` might be large, the time complexity remains $O(1)$ as it's unrelated to $n$: + +```src +[file]{time_complexity}-[class]{}-[func]{constant} +``` + +### Linear order $O(n)$ + +Linear order indicates the number of operations grows linearly with the input data size $n$. Linear order commonly appears in single-loop structures: + +```src +[file]{time_complexity}-[class]{}-[func]{linear} +``` + +Operations like array traversal and linked list traversal have a time complexity of $O(n)$, where $n$ is the length of the array or list: + +```src +[file]{time_complexity}-[class]{}-[func]{array_traversal} +``` + +It's important to note that **the input data size $n$ should be determined based on the type of input data**. For example, in the first example, $n$ represents the input data size, while in the second example, the length of the array $n$ is the data size. + +### Quadratic order $O(n^2)$ + +Quadratic order means the number of operations grows quadratically with the input data size $n$. Quadratic order typically appears in nested loops, where both the outer and inner loops have a time complexity of $O(n)$, resulting in an overall complexity of $O(n^2)$: + +```src +[file]{time_complexity}-[class]{}-[func]{quadratic} +``` + +The figure below compares constant order, linear order, and quadratic order time complexities. + +![Constant, linear, and quadratic order time complexities](time_complexity.assets/time_complexity_constant_linear_quadratic.png) + +For instance, in bubble sort, the outer loop runs $n - 1$ times, and the inner loop runs $n-1$, $n-2$, ..., $2$, $1$ times, averaging $n / 2$ times, resulting in a time complexity of $O((n - 1) n / 2) = O(n^2)$: + +```src +[file]{time_complexity}-[class]{}-[func]{bubble_sort} +``` + +### Exponential order $O(2^n)$ + +Biological "cell division" is a classic example of exponential order growth: starting with one cell, it becomes two after one division, four after two divisions, and so on, resulting in $2^n$ cells after $n$ divisions. + +The figure below and code simulate the cell division process, with a time complexity of $O(2^n)$: + +```src +[file]{time_complexity}-[class]{}-[func]{exponential} +``` + +![Exponential order time complexity](time_complexity.assets/time_complexity_exponential.png) + +In practice, exponential order often appears in recursive functions. For example, in the code below, it recursively splits into two halves, stopping after $n$ divisions: + +```src +[file]{time_complexity}-[class]{}-[func]{exp_recur} +``` + +Exponential order growth is extremely rapid and is commonly seen in exhaustive search methods (brute force, backtracking, etc.). For large-scale problems, exponential order is unacceptable, often requiring dynamic programming or greedy algorithms as solutions. + +### Logarithmic order $O(\log n)$ + +In contrast to exponential order, logarithmic order reflects situations where "the size is halved each round." Given an input data size $n$, since the size is halved each round, the number of iterations is $\log_2 n$, the inverse function of $2^n$. + +The figure below and code simulate the "halving each round" process, with a time complexity of $O(\log_2 n)$, commonly abbreviated as $O(\log n)$: + +```src +[file]{time_complexity}-[class]{}-[func]{logarithmic} +``` + +![Logarithmic order time complexity](time_complexity.assets/time_complexity_logarithmic.png) + +Like exponential order, logarithmic order also frequently appears in recursive functions. The code below forms a recursive tree of height $\log_2 n$: + +```src +[file]{time_complexity}-[class]{}-[func]{log_recur} +``` + +Logarithmic order is typical in algorithms based on the divide-and-conquer strategy, embodying the "split into many" and "simplify complex problems" approach. It's slow-growing and is the most ideal time complexity after constant order. + +!!! tip "What is the base of $O(\log n)$?" + + Technically, "splitting into $m$" corresponds to a time complexity of $O(\log_m n)$. Using the logarithm base change formula, we can equate different logarithmic complexities: + + $$ + O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) + $$ + + This means the base $m$ can be changed without affecting the complexity. Therefore, we often omit the base $m$ and simply denote logarithmic order as $O(\log n)$. + +### Linear-logarithmic order $O(n \log n)$ + +Linear-logarithmic order often appears in nested loops, with the complexities of the two loops being $O(\log n)$ and $O(n)$ respectively. The related code is as follows: + +```src +[file]{time_complexity}-[class]{}-[func]{linear_log_recur} +``` + +The figure below demonstrates how linear-logarithmic order is generated. Each level of a binary tree has $n$ operations, and the tree has $\log_2 n + 1$ levels, resulting in a time complexity of $O(n \log n)$. + +![Linear-logarithmic order time complexity](time_complexity.assets/time_complexity_logarithmic_linear.png) + +Mainstream sorting algorithms typically have a time complexity of $O(n \log n)$, such as quicksort, mergesort, and heapsort. + +### Factorial order $O(n!)$ + +Factorial order corresponds to the mathematical problem of "full permutation." Given $n$ distinct elements, the total number of possible permutations is: + +$$ +n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 +$$ + +Factorials are typically implemented using recursion. As shown in the code and the figure below, the first level splits into $n$ branches, the second level into $n - 1$ branches, and so on, stopping after the $n$th level: + +```src +[file]{time_complexity}-[class]{}-[func]{factorial_recur} +``` + +![Factorial order time complexity](time_complexity.assets/time_complexity_factorial.png) + +Note that factorial order grows even faster than exponential order; it's unacceptable for larger $n$ values. + +## Worst, best, and average time complexities + +**The time efficiency of an algorithm is often not fixed but depends on the distribution of the input data**. Assume we have an array `nums` of length $n$, consisting of numbers from $1$ to $n$, each appearing only once, but in a randomly shuffled order. The task is to return the index of the element $1$. We can draw the following conclusions: + +- When `nums = [?, ?, ..., 1]`, that is, when the last element is $1$, it requires a complete traversal of the array, **achieving the worst-case time complexity of $O(n)$**. +- When `nums = [1, ?, ?, ...]`, that is, when the first element is $1$, no matter the length of the array, no further traversal is needed, **achieving the best-case time complexity of $\Omega(1)$**. + +The "worst-case time complexity" corresponds to the asymptotic upper bound, denoted by the big $O$ notation. Correspondingly, the "best-case time complexity" corresponds to the asymptotic lower bound, denoted by $\Omega$: + +```src +[file]{worst_best_time_complexity}-[class]{}-[func]{find_one} +``` + +It's important to note that the best-case time complexity is rarely used in practice, as it is usually only achievable under very low probabilities and might be misleading. **The worst-case time complexity is more practical as it provides a safety value for efficiency**, allowing us to confidently use the algorithm. + +From the above example, it's clear that both the worst-case and best-case time complexities only occur under "special data distributions," which may have a small probability of occurrence and may not accurately reflect the algorithm's run efficiency. In contrast, **the average time complexity can reflect the algorithm's efficiency under random input data**, denoted by the $\Theta$ notation. + +For some algorithms, we can simply estimate the average case under a random data distribution. For example, in the aforementioned example, since the input array is shuffled, the probability of element $1$ appearing at any index is equal. Therefore, the average number of loops for the algorithm is half the length of the array $n / 2$, giving an average time complexity of $\Theta(n / 2) = \Theta(n)$. + +However, calculating the average time complexity for more complex algorithms can be quite difficult, as it's challenging to analyze the overall mathematical expectation under the data distribution. In such cases, we usually use the worst-case time complexity as the standard for judging the efficiency of the algorithm. + +!!! question "Why is the $\Theta$ symbol rarely seen?" + + Possibly because the $O$ notation is more commonly spoken, it is often used to represent the average time complexity. However, strictly speaking, this practice is not accurate. In this book and other materials, if you encounter statements like "average time complexity $O(n)$", please understand it directly as $\Theta(n)$. diff --git a/en/docs/chapter_data_structure/basic_data_types.md b/en/docs/chapter_data_structure/basic_data_types.md new file mode 100644 index 0000000000..126666852a --- /dev/null +++ b/en/docs/chapter_data_structure/basic_data_types.md @@ -0,0 +1,170 @@ +# Basic data types + +When discussing data in computers, various forms like text, images, videos, voice and 3D models comes to mind. Despite their different organizational forms, they are all composed of various basic data types. + +**Basic data types are those that the CPU can directly operate on** and are directly used in algorithms, mainly including the following. + +- Integer types: `byte`, `short`, `int`, `long`. +- Floating-point types: `float`, `double`, used to represent decimals. +- Character type: `char`, used to represent letters, punctuation, and even emojis in various languages. +- Boolean type: `bool`, used to represent "yes" or "no" decisions. + +**Basic data types are stored in computers in binary form**. One binary digit is 1 bit. In most modern operating systems, 1 byte consists of 8 bits. + +The range of values for basic data types depends on the size of the space they occupy. Below, we take Java as an example. + +- The integer type `byte` occupies 1 byte = 8 bits and can represent $2^8$ numbers. +- The integer type `int` occupies 4 bytes = 32 bits and can represent $2^{32}$ numbers. + +The following table lists the space occupied, value range, and default values of various basic data types in Java. While memorizing this table isn't necessary, having a general understanding of it and referencing it when required is recommended. + +

Table   Space occupied and value range of basic data types

+ +| Type | Symbol | Space Occupied | Minimum Value | Maximum Value | Default Value | +| ------- | -------- | -------------- | ------------------------ | ----------------------- | -------------- | +| Integer | `byte` | 1 byte | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | 0 | +| | `short` | 2 bytes | $-2^{15}$ | $2^{15} - 1$ | 0 | +| | `int` | 4 bytes | $-2^{31}$ | $2^{31} - 1$ | 0 | +| | `long` | 8 bytes | $-2^{63}$ | $2^{63} - 1$ | 0 | +| Float | `float` | 4 bytes | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | +| | `double` | 8 bytes | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | 0.0 | +| Char | `char` | 2 bytes | 0 | $2^{16} - 1$ | 0 | +| Boolean | `bool` | 1 byte | $\text{false}$ | $\text{true}$ | $\text{false}$ | + +Please note that the above table is specific to Java's basic data types. Every programming language has its own data type definitions, which might differ in space occupied, value ranges, and default values. + +- In Python, the integer type `int` can be of any size, limited only by available memory; the floating-point `float` is double precision 64-bit; there is no `char` type, as a single character is actually a string `str` of length 1. +- C and C++ do not specify the size of basic data types, it varies with implementation and platform. The above table follows the LP64 [data model](https://en.cppreference.com/w/cpp/language/types#Properties), used for Unix 64-bit operating systems including Linux and macOS. +- The size of `char` in C and C++ is 1 byte, while in most programming languages, it depends on the specific character encoding method, as detailed in the "Character Encoding" chapter. +- Even though representing a boolean only requires 1 bit (0 or 1), it is usually stored in memory as 1 byte. This is because modern computer CPUs typically use 1 byte as the smallest addressable memory unit. + +So, what is the connection between basic data types and data structures? We know that data structures are ways to organize and store data in computers. The focus here is on "structure" rather than "data". + +If we want to represent "a row of numbers", we naturally think of using an array. This is because the linear structure of an array can represent the adjacency and the ordering of the numbers, but whether the stored content is an integer `int`, a decimal `float`, or a character `char`, is irrelevant to the "data structure". + +In other words, **basic data types provide the "content type" of data, while data structures provide the "way of organizing" data**. For example, in the following code, we use the same data structure (array) to store and represent different basic data types, including `int`, `float`, `char`, `bool`, etc. + +=== "Python" + + ```python title="" + # Using various basic data types to initialize arrays + numbers: list[int] = [0] * 5 + decimals: list[float] = [0.0] * 5 + # Python's characters are actually strings of length 1 + characters: list[str] = ['0'] * 5 + bools: list[bool] = [False] * 5 + # Python's lists can freely store various basic data types and object references + data = [0, 0.0, 'a', False, ListNode(0)] + ``` + +=== "C++" + + ```cpp title="" + // Using various basic data types to initialize arrays + int numbers[5]; + float decimals[5]; + char characters[5]; + bool bools[5]; + ``` + +=== "Java" + + ```java title="" + // Using various basic data types to initialize arrays + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + boolean[] bools = new boolean[5]; + ``` + +=== "C#" + + ```csharp title="" + // Using various basic data types to initialize arrays + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + bool[] bools = new bool[5]; + ``` + +=== "Go" + + ```go title="" + // Using various basic data types to initialize arrays + var numbers = [5]int{} + var decimals = [5]float64{} + var characters = [5]byte{} + var bools = [5]bool{} + ``` + +=== "Swift" + + ```swift title="" + // Using various basic data types to initialize arrays + let numbers = Array(repeating: 0, count: 5) + let decimals = Array(repeating: 0.0, count: 5) + let characters: [Character] = Array(repeating: "a", count: 5) + let bools = Array(repeating: false, count: 5) + ``` + +=== "JS" + + ```javascript title="" + // JavaScript's arrays can freely store various basic data types and objects + const array = [0, 0.0, 'a', false]; + ``` + +=== "TS" + + ```typescript title="" + // Using various basic data types to initialize arrays + const numbers: number[] = []; + const characters: string[] = []; + const bools: boolean[] = []; + ``` + +=== "Dart" + + ```dart title="" + // Using various basic data types to initialize arrays + List numbers = List.filled(5, 0); + List decimals = List.filled(5, 0.0); + List characters = List.filled(5, 'a'); + List bools = List.filled(5, false); + ``` + +=== "Rust" + + ```rust title="" + // Using various basic data types to initialize arrays + let numbers: Vec = vec![0; 5]; + let decimals: Vec = vec![0.0, 5]; + let characters: Vec = vec!['0'; 5]; + let bools: Vec = vec![false; 5]; + ``` + +=== "C" + + ```c title="" + // Using various basic data types to initialize arrays + int numbers[10]; + float decimals[10]; + char characters[10]; + bool bools[10]; + ``` + +=== "Kotlin" + + ```kotlin title="" + + ``` + +=== "Zig" + + ```zig title="" + // Using various basic data types to initialize arrays + var numbers: [5]i32 = undefined; + var decimals: [5]f32 = undefined; + var characters: [5]u8 = undefined; + var bools: [5]bool = undefined; + ``` diff --git a/en/docs/chapter_data_structure/character_encoding.assets/ascii_table.png b/en/docs/chapter_data_structure/character_encoding.assets/ascii_table.png new file mode 100644 index 0000000000..f54ae6a347 Binary files /dev/null and b/en/docs/chapter_data_structure/character_encoding.assets/ascii_table.png differ diff --git a/en/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png b/en/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png new file mode 100644 index 0000000000..6a3e0befae Binary files /dev/null and b/en/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png differ diff --git a/en/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png b/en/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png new file mode 100644 index 0000000000..57b7091309 Binary files /dev/null and b/en/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png differ diff --git a/en/docs/chapter_data_structure/character_encoding.md b/en/docs/chapter_data_structure/character_encoding.md new file mode 100644 index 0000000000..8ba4bd1cad --- /dev/null +++ b/en/docs/chapter_data_structure/character_encoding.md @@ -0,0 +1,87 @@ +# Character encoding * + +In the computer system, all data is stored in binary form, and `char` is no exception. To represent characters, we need to develop a "character set" that defines a one-to-one mapping between each character and binary numbers. With the character set, computers can convert binary numbers to characters by looking up the table. + +## ASCII character set + +The ASCII code is one of the earliest character sets, officially known as the American Standard Code for Information Interchange. It uses 7 binary digits (the lower 7 bits of a byte) to represent a character, allowing for a maximum of 128 different characters. As shown in the figure below, ASCII includes uppercase and lowercase English letters, numbers 0 ~ 9, various punctuation marks, and certain control characters (such as newline and tab). + +![ASCII code](character_encoding.assets/ascii_table.png) + +However, **ASCII can only represent English characters**. With the globalization of computers, a character set called EASCII was developed to represent more languages. It expands from the 7-bit structure of ASCII to 8 bits, enabling the representation of 256 characters. + +Globally, various region-specific EASCII character sets have been introduced. The first 128 characters of these sets are consistent with the ASCII, while the remaining 128 characters are defined differently to accommodate the requirements of different languages. + +## GBK character set + +Later, it was found that **EASCII still could not meet the character requirements of many languages**. For instance, there are nearly a hundred thousand Chinese characters, with several thousand used regularly. In 1980, the Standardization Administration of China released the GB2312 character set, which included 6763 Chinese characters, essentially fulfilling the computer processing needs for the Chinese language. + +However, GB2312 could not handle some rare and traditional characters. The GBK character set expands GB2312 and includes 21886 Chinese characters. In the GBK encoding scheme, ASCII characters are represented with one byte, while Chinese characters use two bytes. + +## Unicode character set + +With the rapid evolution of computer technology and a plethora of character sets and encoding standards, numerous problems arose. On the one hand, these character sets generally only defined characters for specific languages and could not function properly in multilingual environments. On the other hand, the existence of multiple character set standards for the same language caused garbled text when information was exchanged between computers using different encoding standards. + +Researchers of that era thought: **What if a comprehensive character set encompassing all global languages and symbols was developed? Wouldn't this resolve the issues associated with cross-linguistic environments and garbled text?** Inspired by this idea, the extensive character set, Unicode, was born. + +Unicode is referred to as "统一码" (Unified Code) in Chinese, theoretically capable of accommodating over a million characters. It aims to incorporate characters from all over the world into a single set, providing a universal character set for processing and displaying various languages and reducing the issues of garbled text due to different encoding standards. + +Since its release in 1991, Unicode has continually expanded to include new languages and characters. As of September 2022, Unicode contains 149,186 characters, including characters, symbols, and even emojis from various languages. In the vast Unicode character set, commonly used characters occupy 2 bytes, while some rare characters may occupy 3 or even 4 bytes. + +Unicode is a universal character set that assigns a number (called a "code point") to each character, **but it does not specify how these character code points should be stored in a computer system**. One might ask: How does a system interpret Unicode code points of varying lengths within a text? For example, given a 2-byte code, how does the system determine if it represents a single 2-byte character or two 1-byte characters? + +**A straightforward solution to this problem is to store all characters as equal-length encodings**. As shown in the figure below, each character in "Hello" occupies 1 byte, while each character in "算法" (algorithm) occupies 2 bytes. We could encode all characters in "Hello 算法" as 2 bytes by padding the higher bits with zeros. This method would enable the system to interpret a character every 2 bytes, recovering the content of the phrase. + +![Unicode encoding example](character_encoding.assets/unicode_hello_algo.png) + +However, as ASCII has shown us, encoding English only requires 1 byte. Using the above approach would double the space occupied by English text compared to ASCII encoding, which is a waste of memory space. Therefore, a more efficient Unicode encoding method is needed. + +## UTF-8 encoding + +Currently, UTF-8 has become the most widely used Unicode encoding method internationally. **It is a variable-length encoding**, using 1 to 4 bytes to represent a character, depending on the complexity of the character. ASCII characters need only 1 byte, Latin and Greek letters require 2 bytes, commonly used Chinese characters need 3 bytes, and some other rare characters need 4 bytes. + +The encoding rules for UTF-8 are not complex and can be divided into two cases: + +- For 1-byte characters, set the highest bit to $0$, and the remaining 7 bits to the Unicode code point. Notably, ASCII characters occupy the first 128 code points in the Unicode set. This means that **UTF-8 encoding is backward compatible with ASCII**. This implies that UTF-8 can be used to parse ancient ASCII text. +- For characters of length $n$ bytes (where $n > 1$), set the highest $n$ bits of the first byte to $1$, and the $(n + 1)^{\text{th}}$ bit to $0$; starting from the second byte, set the highest 2 bits of each byte to $10$; the rest of the bits are used to fill the Unicode code point. + +The figure below shows the UTF-8 encoding for "Hello算法". It can be observed that since the highest $n$ bits are set to $1$, the system can determine the length of the character as $n$ by counting the number of highest bits set to $1$. + +But why set the highest 2 bits of the remaining bytes to $10$? Actually, this $10$ serves as a kind of checksum. If the system starts parsing text from an incorrect byte, the $10$ at the beginning of the byte can help the system quickly detect anomalies. + +The reason for using $10$ as a checksum is that, under UTF-8 encoding rules, it's impossible for the highest two bits of a character to be $10$. This can be proven by contradiction: If the highest two bits of a character are $10$, it indicates that the character's length is $1$, corresponding to ASCII. However, the highest bit of an ASCII character should be $0$, which contradicts the assumption. + +![UTF-8 encoding example](character_encoding.assets/utf-8_hello_algo.png) + +Apart from UTF-8, other common encoding methods include: + +- **UTF-16 encoding**: Uses 2 or 4 bytes to represent a character. All ASCII characters and commonly used non-English characters are represented with 2 bytes; a few characters require 4 bytes. For 2-byte characters, the UTF-16 encoding equals the Unicode code point. +- **UTF-32 encoding**: Every character uses 4 bytes. This means UTF-32 occupies more space than UTF-8 and UTF-16, especially for texts with a high proportion of ASCII characters. + +From the perspective of storage space, using UTF-8 to represent English characters is very efficient because it only requires 1 byte; using UTF-16 to encode some non-English characters (such as Chinese) can be more efficient because it only requires 2 bytes, while UTF-8 might need 3 bytes. + +From a compatibility perspective, UTF-8 is the most versatile, with many tools and libraries supporting UTF-8 as a priority. + +## Character encoding in programming languages + +Historically, many programming languages utilized fixed-length encodings such as UTF-16 or UTF-32 for processing strings during program execution. This allows strings to be handled as arrays, offering several advantages: + +- **Random access**: Strings encoded in UTF-16 can be accessed randomly with ease. For UTF-8, which is a variable-length encoding, locating the $i^{th}$ character requires traversing the string from the start to the $i^{th}$ position, taking $O(n)$ time. +- **Character counting**: Similar to random access, counting the number of characters in a UTF-16 encoded string is an $O(1)$ operation. However, counting characters in a UTF-8 encoded string requires traversing the entire string. +- **String operations**: Many string operations like splitting, concatenating, inserting, and deleting are easier on UTF-16 encoded strings. These operations generally require additional computation on UTF-8 encoded strings to ensure the validity of the UTF-8 encoding. + +The design of character encoding schemes in programming languages is an interesting topic involving various factors: + +- Java’s `String` type uses UTF-16 encoding, with each character occupying 2 bytes. This was based on the initial belief that 16 bits were sufficient to represent all possible characters and proven incorrect later. As the Unicode standard expanded beyond 16 bits, characters in Java may now be represented by a pair of 16-bit values, known as “surrogate pairs.” +- JavaScript and TypeScript use UTF-16 encoding for similar reasons as Java. When JavaScript was first introduced by Netscape in 1995, Unicode was still in its early stages, and 16-bit encoding was sufficient to represent all Unicode characters. +- C# uses UTF-16 encoding, largely because the .NET platform, designed by Microsoft, and many Microsoft technologies, including the Windows operating system, extensively use UTF-16 encoding. + +Due to the underestimation of character counts, these languages had to use "surrogate pairs" to represent Unicode characters exceeding 16 bits. This approach has its drawbacks: strings containing surrogate pairs may have characters occupying 2 or 4 bytes, losing the advantage of fixed-length encoding. Additionally, handling surrogate pairs adds complexity and debugging difficulty to programming. + +Addressing these challenges, some languages have adopted alternative encoding strategies: + +- Python’s `str` type uses Unicode encoding with a flexible representation where the storage length of characters depends on the largest Unicode code point in the string. If all characters are ASCII, each character occupies 1 byte, 2 bytes for characters within the Basic Multilingual Plane (BMP), and 4 bytes for characters beyond the BMP. +- Go’s `string` type internally uses UTF-8 encoding. Go also provides the `rune` type for representing individual Unicode code points. +- Rust’s `str` and `String` types use UTF-8 encoding internally. Rust also offers the `char` type for individual Unicode code points. + +It’s important to note that the above discussion pertains to how strings are stored in programming languages, **which is different from how strings are stored in files or transmitted over networks**. For file storage or network transmission, strings are usually encoded in UTF-8 format for optimal compatibility and space efficiency. diff --git a/en/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png b/en/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png new file mode 100644 index 0000000000..73eeb13d8d Binary files /dev/null and b/en/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png differ diff --git a/en/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png b/en/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png new file mode 100644 index 0000000000..b07ef9b61a Binary files /dev/null and b/en/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png differ diff --git a/en/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png b/en/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png new file mode 100644 index 0000000000..941ca7996a Binary files /dev/null and b/en/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png differ diff --git a/en/docs/chapter_data_structure/classification_of_data_structure.md b/en/docs/chapter_data_structure/classification_of_data_structure.md new file mode 100644 index 0000000000..b848eceb99 --- /dev/null +++ b/en/docs/chapter_data_structure/classification_of_data_structure.md @@ -0,0 +1,48 @@ +# Classification of data structures + +Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified into "logical structure" and "physical structure". + +## Logical structure: linear and non-linear + +**The logical structures reveal the logical relationships between data elements**. In arrays and linked lists, data are arranged in a specific sequence, demonstrating the linear relationship between data; while in trees, data are arranged hierarchically from the top down, showing the derived relationship between "ancestors" and "descendants"; and graphs are composed of nodes and edges, reflecting the intricate network relationship. + +As shown in the figure below, logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating data is arranged linearly in logical relationships; non-linear structures, conversely, are arranged non-linearly. + +- **Linear data structures**: Arrays, Linked Lists, Stacks, Queues, Hash Tables. +- **Non-linear data structures**: Trees, Heaps, Graphs, Hash Tables. + +Non-linear data structures can be further divided into tree structures and network structures. + +- **Tree structures**: Trees, Heaps, Hash Tables, where elements have a one-to-many relationship. +- **Network structures**: Graphs, where elements have a many-to-many relationships. + +![Linear and non-linear data structures](classification_of_data_structure.assets/classification_logic_structure.png) + +## Physical structure: contiguous and dispersed + +**During the execution of an algorithm, the data being processed is stored in memory**. The figure below shows a computer memory stick where each black square is a physical memory space. We can think of memory as a vast Excel spreadsheet, with each cell capable of storing a certain amount of data. + +**The system accesses the data at the target location by means of a memory address**. As shown in the figure below, the computer assigns a unique identifier to each cell in the table according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access the data stored in memory. + +![Memory stick, memory spaces, memory addresses](classification_of_data_structure.assets/computer_memory_location.png) + +!!! tip + + It's worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is more complex, involving concepts like address space, memory management, cache mechanisms, virtual memory, and physical memory. + +Memory is a shared resource for all programs. When a block of memory is occupied by one program, it cannot be simultaneously used by other programs. **Therefore, considering memory resources is crucial in designing data structures and algorithms**. For instance, the algorithm's peak memory usage should not exceed the remaining free memory of the system; if there is a lack of contiguous memory blocks, then the data structure chosen must be able to be stored in non-contiguous memory blocks. + +As illustrated in the figure below, **the physical structure reflects the way data is stored in computer memory** and it can be divided into contiguous space storage (arrays) and non-contiguous space storage (linked lists). The two types of physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency. + +![Contiguous space storage and dispersed space storage](classification_of_data_structure.assets/classification_phisical_structure.png) + +**It is worth noting that all data structures are implemented based on arrays, linked lists, or a combination of both**. For example, stacks and queues can be implemented using either arrays or linked lists; while implementations of hash tables may involve both arrays and linked lists. + +- **Array-based implementations**: Stacks, Queues, Hash Tables, Trees, Heaps, Graphs, Matrices, Tensors (arrays with dimensions $\geq 3$). +- **Linked-list-based implementations**: Stacks, Queues, Hash Tables, Trees, Heaps, Graphs, etc. + +Data structures implemented based on arrays are also called “Static Data Structures,” meaning their length cannot be changed after initialization. Conversely, those based on linked lists are called “Dynamic Data Structures,” which can still adjust their size during program execution. + +!!! tip + + If you find it challenging to comprehend the physical structure, it is recommended that you read the next chapter, "Arrays and Linked Lists," and revisit this section later. diff --git a/en/docs/chapter_data_structure/index.md b/en/docs/chapter_data_structure/index.md new file mode 100644 index 0000000000..a417f14e20 --- /dev/null +++ b/en/docs/chapter_data_structure/index.md @@ -0,0 +1,9 @@ +# Data structures + +![Data structures](../assets/covers/chapter_data_structure.jpg) + +!!! abstract + + Data structures serve as a robust and diverse framework. + + They offer a blueprint for the orderly organization of data, upon which algorithms come to life. diff --git a/en/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png b/en/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png new file mode 100644 index 0000000000..234bdcc66a Binary files /dev/null and b/en/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png differ diff --git a/en/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png b/en/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png new file mode 100644 index 0000000000..d11183bbe3 Binary files /dev/null and b/en/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png differ diff --git a/en/docs/chapter_data_structure/number_encoding.md b/en/docs/chapter_data_structure/number_encoding.md new file mode 100644 index 0000000000..41703f7562 --- /dev/null +++ b/en/docs/chapter_data_structure/number_encoding.md @@ -0,0 +1,150 @@ +# Number encoding * + +!!! tip + + In this book, chapters marked with an asterisk '*' are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters. + +## Integer encoding + +In the table from the previous section, we observed that all integer types can represent one more negative number than positive numbers, such as the `byte` range of $[-128, 127]$. This phenomenon seems counterintuitive, and its underlying reason involves knowledge of sign-magnitude, one's complement, and two's complement encoding. + +Firstly, it's important to note that **numbers are stored in computers using the two's complement form**. Before analyzing why this is the case, let's define these three encoding methods: + +- **Sign-magnitude**: The highest bit of a binary representation of a number is considered the sign bit, where $0$ represents a positive number and $1$ represents a negative number. The remaining bits represent the value of the number. +- **One's complement**: The one's complement of a positive number is the same as its sign-magnitude. For negative numbers, it's obtained by inverting all bits except the sign bit. +- **Two's complement**: The two's complement of a positive number is the same as its sign-magnitude. For negative numbers, it's obtained by adding $1$ to their one's complement. + +The figure below illustrates the conversions among sign-magnitude, one's complement, and two's complement: + +![Conversions between sign-magnitude, one's complement, and two's complement](number_encoding.assets/1s_2s_complement.png) + +Although sign-magnitude is the most intuitive, it has limitations. For one, **negative numbers in sign-magnitude cannot be directly used in calculations**. For example, in sign-magnitude, calculating $1 + (-2)$ results in $-3$, which is incorrect. + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline +& = 1000 \; 0011 \newline +& \rightarrow -3 +\end{aligned} +$$ + +To address this, computers introduced the one's complement. If we convert to one's complement and calculate $1 + (-2)$, then convert the result back to sign-magnitude, we get the correct result of $-1$. + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 \; \text{(Sign-magnitude)} + 1000 \; 0010 \; \text{(Sign-magnitude)} \newline +& = 0000 \; 0001 \; \text{(One's complement)} + 1111 \; 1101 \; \text{(One's complement)} \newline +& = 1111 \; 1110 \; \text{(One's complement)} \newline +& = 1000 \; 0001 \; \text{(Sign-magnitude)} \newline +& \rightarrow -1 +\end{aligned} +$$ + +Additionally, **there are two representations of zero in sign-magnitude**: $+0$ and $-0$. This means two different binary encodings for zero, which could lead to ambiguity. For example, in conditional checks, not differentiating between positive and negative zero might result in incorrect outcomes. Addressing this ambiguity would require additional checks, potentially reducing computational efficiency. + +$$ +\begin{aligned} ++0 & \rightarrow 0000 \; 0000 \newline +-0 & \rightarrow 1000 \; 0000 +\end{aligned} +$$ + +Like sign-magnitude, one's complement also suffers from the positive and negative zero ambiguity. Therefore, computers further introduced the two's complement. Let's observe the conversion process for negative zero in sign-magnitude, one's complement, and two's complement: + +$$ +\begin{aligned} +-0 \rightarrow \; & 1000 \; 0000 \; \text{(Sign-magnitude)} \newline += \; & 1111 \; 1111 \; \text{(One's complement)} \newline += 1 \; & 0000 \; 0000 \; \text{(Two's complement)} \newline +\end{aligned} +$$ + +Adding $1$ to the one's complement of negative zero produces a carry, but with `byte` length being only 8 bits, the carried-over $1$ to the 9th bit is discarded. Therefore, **the two's complement of negative zero is $0000 \; 0000$**, the same as positive zero, thus resolving the ambiguity. + +One last puzzle is the $[-128, 127]$ range for `byte`, with an additional negative number, $-128$. We observe that for the interval $[-127, +127]$, all integers have corresponding sign-magnitude, one's complement, and two's complement, allowing for mutual conversion between them. + +However, **the two's complement $1000 \; 0000$ is an exception without a corresponding sign-magnitude**. According to the conversion method, its sign-magnitude would be $0000 \; 0000$, indicating zero. This presents a contradiction because its two's complement should represent itself. Computers designate this special two's complement $1000 \; 0000$ as representing $-128$. In fact, the calculation of $(-1) + (-127)$ in two's complement results in $-128$. + +$$ +\begin{aligned} +& (-127) + (-1) \newline +& \rightarrow 1111 \; 1111 \; \text{(Sign-magnitude)} + 1000 \; 0001 \; \text{(Sign-magnitude)} \newline +& = 1000 \; 0000 \; \text{(One's complement)} + 1111 \; 1110 \; \text{(One's complement)} \newline +& = 1000 \; 0001 \; \text{(Two's complement)} + 1111 \; 1111 \; \text{(Two's complement)} \newline +& = 1000 \; 0000 \; \text{(Two's complement)} \newline +& \rightarrow -128 +\end{aligned} +$$ + +As you might have noticed, all these calculations are additions, hinting at an important fact: **computers' internal hardware circuits are primarily designed around addition operations**. This is because addition is simpler to implement in hardware compared to other operations like multiplication, division, and subtraction, allowing for easier parallelization and faster computation. + +It's important to note that this doesn't mean computers can only perform addition. **By combining addition with basic logical operations, computers can execute a variety of other mathematical operations**. For example, the subtraction $a - b$ can be translated into $a + (-b)$; multiplication and division can be translated into multiple additions or subtractions. + +We can now summarize the reason for using two's complement in computers: with two's complement representation, computers can use the same circuits and operations to handle both positive and negative number addition, eliminating the need for special hardware circuits for subtraction and avoiding the ambiguity of positive and negative zero. This greatly simplifies hardware design and enhances computational efficiency. + +The design of two's complement is quite ingenious, and due to space constraints, we'll stop here. Interested readers are encouraged to explore further. + +## Floating-point number encoding + +You might have noticed something intriguing: despite having the same length of 4 bytes, why does a `float` have a much larger range of values compared to an `int`? This seems counterintuitive, as one would expect the range to shrink for `float` since it needs to represent fractions. + +In fact, **this is due to the different representation method used by floating-point numbers (`float`)**. Let's consider a 32-bit binary number as: + +$$ +b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 +$$ + +According to the IEEE 754 standard, a 32-bit `float` consists of the following three parts: + +- Sign bit $\mathrm{S}$: Occupies 1 bit, corresponding to $b_{31}$. +- Exponent bit $\mathrm{E}$: Occupies 8 bits, corresponding to $b_{30} b_{29} \ldots b_{23}$. +- Fraction bit $\mathrm{N}$: Occupies 23 bits, corresponding to $b_{22} b_{21} \ldots b_0$. + +The value of a binary `float` number is calculated as: + +$$ +\text{val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2 - 127} \times \left(1 . b_{22} b_{21} \ldots b_0\right)_2 +$$ + +Converted to a decimal formula, this becomes: + +$$ +\text{val} = (-1)^{\mathrm{S}} \times 2^{\mathrm{E} - 127} \times (1 + \mathrm{N}) +$$ + +The range of each component is: + +$$ +\begin{aligned} +\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline +(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} \times 2^{-i}) \subset [1, 2 - 2^{-23}] +\end{aligned} +$$ + +![Example calculation of a float in IEEE 754 standard](number_encoding.assets/ieee_754_float.png) + +Observing the figure above, given an example data $\mathrm{S} = 0$, $\mathrm{E} = 124$, $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$, we have: + +$$ +\text{val} = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 +$$ + +Now we can answer the initial question: **The representation of `float` includes an exponent bit, leading to a much larger range than `int`**. Based on the above calculation, the maximum positive number representable by `float` is approximately $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$, and the minimum negative number is obtained by switching the sign bit. + +**However, the trade-off for `float`'s expanded range is a sacrifice in precision**. The integer type `int` uses all 32 bits to represent the number, with values evenly distributed; but due to the exponent bit, the larger the value of a `float`, the greater the difference between adjacent numbers. + +As shown in the table below, exponent bits $\mathrm{E} = 0$ and $\mathrm{E} = 255$ have special meanings, **used to represent zero, infinity, $\mathrm{NaN}$, etc.** + +

Table   Meaning of exponent bits

+ +| Exponent Bit E | Fraction Bit $\mathrm{N} = 0$ | Fraction Bit $\mathrm{N} \ne 0$ | Calculation Formula | +| ------------------ | ----------------------------- | ------------------------------- | ---------------------------------------------------------------------- | +| $0$ | $\pm 0$ | Subnormal Numbers | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | +| $1, 2, \dots, 254$ | Normal Numbers | Normal Numbers | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | +| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | + +It's worth noting that subnormal numbers significantly improve the precision of floating-point numbers. The smallest positive normal number is $2^{-126}$, and the smallest positive subnormal number is $2^{-126} \times 2^{-23}$. + +Double-precision `double` also uses a similar representation method to `float`, which is not elaborated here for brevity. diff --git a/en/docs/chapter_data_structure/summary.md b/en/docs/chapter_data_structure/summary.md new file mode 100644 index 0000000000..1defdf79b0 --- /dev/null +++ b/en/docs/chapter_data_structure/summary.md @@ -0,0 +1,66 @@ +# Summary + +### Key review + +- Data structures can be categorized from two perspectives: logical structure and physical structure. Logical structure describes the logical relationships between data, while physical structure describes how data is stored in memory. +- Frequently used logical structures include linear structures, trees, and networks. We usually divide data structures into linear (arrays, linked lists, stacks, queues) and non-linear (trees, graphs, heaps) based on their logical structure. The implementation of hash tables may involve both linear and non-linear data structures. +- When a program is running, data is stored in memory. Each memory space has a corresponding address, and the program accesses data through these addresses. +- Physical structures can be divided into continuous space storage (arrays) and discrete space storage (linked lists). All data structures are implemented using arrays, linked lists, or a combination of both. +- The basic data types in computers include integers (`byte`, `short`, `int`, `long`), floating-point numbers (`float`, `double`), characters (`char`), and booleans (`bool`). The value range of a data type depends on its size and representation. +- Sign-magnitude, 1's complement, 2's complement are three methods of encoding integers in computers, and they can be converted into each other. The most significant bit of the sign-magnitude is the sign bit, and the remaining bits represent the value of the number. +- Integers are encoded by 2's complement in computers. The benefits of this representation include (i) the computer can unify the addition of positive and negative integers, (ii) no need to design special hardware circuits for subtraction, and (iii) no ambiguity of positive and negative zero. +- The encoding of floating-point numbers consists of 1 sign bit, 8 exponent bits, and 23 fraction bits. Due to the exponent bit, the range of floating-point numbers is much greater than that of integers, but at the cost of precision. +- ASCII is the earliest English character set, with 1 byte in length and a total of 127 characters. GBK is a popular Chinese character set, which includes more than 20,000 Chinese characters. Unicode aims to provide a complete character set standard that includes characters from various languages in the world, thus solving the garbled character problem caused by inconsistent character encoding methods. +- UTF-8 is the most popular and general Unicode encoding method. It is a variable-length encoding method with good scalability and space efficiency. UTF-16 and UTF-32 are fixed-length encoding methods. When encoding Chinese characters, UTF-16 takes up less space than UTF-8. Programming languages like Java and C# use UTF-16 encoding by default. + +### Q & A + +**Q**: Why does a hash table contain both linear and non-linear data structures? + +The underlying structure of a hash table is an array. To resolve hash collisions, we may use "chaining" (discussed in a later section, "Hash collision"): each bucket in the array points to a linked list, which may transform into a tree (usually a red-black tree) when its length is larger than a certain threshold. +From a storage perspective, the underlying structure of a hash table is an array, where each bucket might contain a value, a linked list, or a tree. Therefore, hash tables may contain both linear data structures (arrays, linked lists) and non-linear data structures (trees). + +**Q**: Is the length of the `char` type 1 byte? + +The length of the `char` type is determined by the encoding method of the programming language. For example, Java, JavaScript, TypeScript, and C# all use UTF-16 encoding (to save Unicode code points), so the length of the `char` type is 2 bytes. + +**Q**: Is there any ambiguity when we refer to array-based data structures as "static data structures"? The stack can also perform "dynamic" operations such as popping and pushing. + +The stack can implement dynamic data operations, but the data structure is still "static" (the length is fixed). Although array-based data structures can dynamically add or remove elements, their capacity is fixed. If the stack size exceeds the pre-allocated size, then the old array will be copied into a newly created and larger array. + +**Q**: When building a stack (queue), its size is not specified, so why are they "static data structures"? + +In high-level programming languages, we do not need to manually specify the initial capacity of stacks (queues); this task is automatically completed within the class. For example, the initial capacity of Java's `ArrayList` is usually 10. Furthermore, the expansion operation is also completed automatically. See the subsequent "List" chapter for details. + +**Q**:The method of converting the sign-magnitude to the 2's complement is "first negate and then add 1", so converting the 2's complement to the sign-magnitude should be its inverse operation "first subtract 1 and then negate". +However, the 2's complement can also be converted to the sign-magnitude through "first negate and then add 1", why is this? + +**A**:This is because the mutual conversion between the sign-magnitude and the 2's complement is equivalent to computing the "complement". We first define the complement: assuming $a + b = c$, then we say that $a$ is the complement of $b$ to $c$, and vice versa, $b$ is the complement of $a$ to $c$. + +Given a binary number $0010$ with length $n = 4$, if this number is the sign-magnitude (ignoring the sign bit), then its 2's complement can be obtained by "first negating and then adding 1": + +$$ +0010 \rightarrow 1101 \rightarrow 1110 +$$ + +Observe that the sum of the sign-magnitude and the 2's complement is $0010 + 1110 = 10000$, i.e., the 2's complement $1110$ is the "complement" of the sign-magnitude $0010$ to $10000$. **This means that the above "first negate and then add 1" is equivalent to computing the complement to $10000$**. + +So, what is the "complement" of $1110$ to $10000$? We can still compute it by "negating first and then adding 1": + +$$ +1110 \rightarrow 0001 \rightarrow 0010 +$$ + +In other words, the sign-magnitude and the 2's complement are each other's "complement" to $10000$, so "sign-magnitude to 2's complement" and "2's complement to sign-magnitude" can be implemented with the same operation (first negate and then add 1). + +Of course, we can also use the inverse operation of "first negate and then add 1" to find the sign-magnitude of the 2's complement $1110$, that is, "first subtract 1 and then negate": + +$$ +1110 \rightarrow 1101 \rightarrow 0010 +$$ + +To sum up, "first negate and then add 1" and "first subtract 1 and then negate" are both computing the complement to $10000$, and they are equivalent. + +Essentially, the "negate" operation is actually to find the complement to $1111$ (because `sign-magnitude + 1's complement = 1111` always holds); and the 1's complement plus 1 is equal to the 2's complement to $10000$. + +We take $n = 4$ as an example in the above, and it can be generalized to any binary number with any number of digits. \ No newline at end of file diff --git a/en/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png b/en/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png new file mode 100644 index 0000000000..9d58c7fe7a Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png differ diff --git a/en/docs/chapter_divide_and_conquer/binary_search_recur.md b/en/docs/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 0000000000..2f5fb674cd --- /dev/null +++ b/en/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,45 @@ +# Divide and conquer search strategy + +We have learned that search algorithms fall into two main categories. + +- **Brute-force search**: It is implemented by traversing the data structure, with a time complexity of $O(n)$. +- **Adaptive search**: It utilizes a unique data organization form or prior information, and its time complexity can reach $O(\log n)$ or even $O(1)$. + +In fact, **search algorithms with a time complexity of $O(\log n)$ are usually based on the divide-and-conquer strategy**, such as binary search and trees. + +- Each step of binary search divides the problem (searching for a target element in an array) into a smaller problem (searching for the target element in half of the array), continuing until the array is empty or the target element is found. +- Trees represent the divide-and-conquer idea, where in data structures like binary search trees, AVL trees, and heaps, the time complexity of various operations is $O(\log n)$. + +The divide-and-conquer strategy of binary search is as follows. + +- **The problem can be divided**: Binary search recursively divides the original problem (searching in an array) into subproblems (searching in half of the array), achieved by comparing the middle element with the target element. +- **Subproblems are independent**: In binary search, each round handles one subproblem, unaffected by other subproblems. +- **The solutions of subproblems do not need to be merged**: Binary search aims to find a specific element, so there is no need to merge the solutions of subproblems. When a subproblem is solved, the original problem is also solved. + +Divide-and-conquer can enhance search efficiency because brute-force search can only eliminate one option per round, **whereas divide-and-conquer can eliminate half of the options**. + +### Implementing binary search based on divide-and-conquer + +In previous chapters, binary search was implemented based on iteration. Now, we implement it based on divide-and-conquer (recursion). + +!!! question + + Given an ordered array `nums` of length $n$, where all elements are unique, please find the element `target`. + +From a divide-and-conquer perspective, we denote the subproblem corresponding to the search interval $[i, j]$ as $f(i, j)$. + +Starting from the original problem $f(0, n-1)$, perform the binary search through the following steps. + +1. Calculate the midpoint $m$ of the search interval $[i, j]$, and use it to eliminate half of the search interval. +2. Recursively solve the subproblem reduced by half in size, which could be $f(i, m-1)$ or $f(m+1, j)$. +3. Repeat steps `1.` and `2.`, until `target` is found or the interval is empty and returns. + +The figure below shows the divide-and-conquer process of binary search for element $6$ in an array. + +![The divide-and-conquer process of binary search](binary_search_recur.assets/binary_search_recur.png) + +In the implementation code, we declare a recursive function `dfs()` to solve the problem $f(i, j)$: + +```src +[file]{binary_search_recur}-[class]{}-[func]{binary_search} +``` diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png new file mode 100644 index 0000000000..f6f0b7f4ef Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png new file mode 100644 index 0000000000..7e31ef351f Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png new file mode 100644 index 0000000000..9096b3b05f Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png new file mode 100644 index 0000000000..56f3200564 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png new file mode 100644 index 0000000000..6ee5ba1e26 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png new file mode 100644 index 0000000000..939ea29716 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png new file mode 100644 index 0000000000..4ad88bbc4a Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png new file mode 100644 index 0000000000..bb486173f0 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png new file mode 100644 index 0000000000..187a1c4df1 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png new file mode 100644 index 0000000000..3ccb0bea18 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png new file mode 100644 index 0000000000..17bcb9283b Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png new file mode 100644 index 0000000000..6004fd0a1b Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png new file mode 100644 index 0000000000..7c4b825d7a Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png differ diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md new file mode 100644 index 0000000000..e2b8d5f9ea --- /dev/null +++ b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -0,0 +1,99 @@ +# Building a binary tree problem + +!!! question + + Given the pre-order traversal `preorder` sequence and the in-order traversal `inorder` sequence of a binary tree, construct the binary tree and return its root node. Assume there are no duplicate node values in the binary tree (as shown in the figure below). + +![Example data for building a binary tree](build_binary_tree_problem.assets/build_tree_example.png) + +### Determining if it is a divide-and-conquer problem + +The original problem of building a binary tree from the `preorder` and the `inorder` sequences is a typical divide-and-conquer problem. + +- **The problem can be decomposed**: From the perspective of divide-and-conquer, we can divide the original problem into two subproblems—building the left subtree and building the right subtree—plus one operation of initializing the root node. For each subtree (subproblem), we continue applying the same approach, partitioning it into smaller subtrees (subproblems), until reaching the smallest subproblem (an empty subtree). +- **The subproblems are independent**: The left and right subtrees do not overlap. When building the left subtree, we only need the segments of the in-order and pre-order traversals that correspond to the left subtree. The same approach applies to the right subtree. +- **Solutions to subproblems can be combined**: Once we have constructed the left and right subtrees (the subproblem solutions), we can attach them to the root node to obtain the solution to the original problem. + +### How to divide the subtrees + +Based on the above analysis, this problem can be solved using divide-and-conquer. **However, how do we use the pre-order traversal `preorder` sequence and the in-order traversal `inorder` sequence to divide the left and right subtrees?** + +By definition, both the `preorder` and `inorder` sequences can be divided into three parts: + +- Pre-order traversal: `[ Root | Left Subtree | Right Subtree ]`. For example, in the figure, the tree corresponds to `[ 3 | 9 | 2 1 7 ]`. +- In-order traversal: `[ Left Subtree | Root | Right Subtree ]`. For example, in the figure, the tree corresponds to `[ 9 | 3 | 1 2 7 ]`. + +Using the data from the preceding figure, we can follow the steps shown in the next figure to obtain the division results: + +1. The first element 3 in the pre-order traversal is the value of the root node. +2. Find the index of the root node 3 in the `inorder` sequence, and use this index to split `inorder` into `[ 9 | 3 | 1 2 7 ]`. +3. According to the split of the `inorder` sequence, it is straightforward to determine that the left and right subtrees contain 1 and 3 nodes, respectively, so we can split the `preorder` sequence into `[ 3 | 9 | 2 1 7 ]` accordingly. + +![Dividing the subtrees in pre-order and in-order traversals](build_binary_tree_problem.assets/build_tree_pre-order_in-order_division.png) + +### Describing subtree ranges based on variables + +Based on the above division method, **we have now obtained the index ranges of the root, left subtree, and right subtree in the `preorder` and `inorder` sequences**. To describe these index ranges, we use several pointer variables. + +- Let the index of the current tree's root node in the `preorder` sequence be denoted as $i$. +- Let the index of the current tree's root node in the `inorder` sequence be denoted as $m$. +- Let the index range of the current tree in the `inorder` sequence be denoted as $[l, r]$. + +As shown in the table below, these variables represent the root node’s index in the `preorder` sequence and the index ranges of the subtrees in the `inorder` sequence. + +

Table   Indexes of the root node and subtrees in pre-order and in-order traversals

+ +| | Root node index in `preorder` | Subtree index range in `inorder` | +| ------------- | ----------------------------- | ----------------------------------- | +| Current tree | $i$ | $[l, r]$ | +| Left subtree | $i + 1$ | $[l, m-1]$ | +| Right subtree | $i + 1 + (m - l)$ | $[m+1, r]$ | + +Please note that $(m-l)$ in the right subtree root index represents "the number of nodes in the left subtree." It may help to consult the figure below for a clearer understanding. + +![Indexes of the root node and left and right subtrees](build_binary_tree_problem.assets/build_tree_division_pointers.png) + +### Code implementation + +To improve the efficiency of querying $m$, we use a hash table `hmap` to store the mapping from elements in the `inorder` sequence to their indexes: + +```src +[file]{build_tree}-[class]{}-[func]{build_tree} +``` + +The figure below shows the recursive process of building the binary tree. Each node is created during the "descending" phase of the recursion, and each edge (reference) is formed during the "ascending" phase. + +=== "<1>" + ![Recursive process of building a binary tree](build_binary_tree_problem.assets/built_tree_step1.png) + +=== "<2>" + ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) + +=== "<3>" + ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) + +=== "<4>" + ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) + +=== "<5>" + ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) + +=== "<6>" + ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) + +=== "<7>" + ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) + +=== "<8>" + ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) + +=== "<9>" + ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) + +Each recursive function's division of the `preorder` and `inorder` sequences is illustrated in the figure below. + +![Division in each recursive function](build_binary_tree_problem.assets/built_tree_overall.png) + +Assuming the binary tree has $n$ nodes, initializing each node (calling the recursive function `dfs()`) takes $O(1)$ time. **Therefore, the overall time complexity is $O(n)$**. + +Because the hash table stores the mapping from `inorder` elements to their indexes, it requires $O(n)$ space. In the worst case, if the binary tree degenerates into a linked list, the recursive depth can reach $n$, consuming $O(n)$ stack space. **Hence, the overall space complexity is $O(n)$**. diff --git a/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png b/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png new file mode 100644 index 0000000000..a2ecf4b336 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png differ diff --git a/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png b/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png new file mode 100644 index 0000000000..349b5fe9b7 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png differ diff --git a/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png b/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png new file mode 100644 index 0000000000..293b4eab99 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png differ diff --git a/en/docs/chapter_divide_and_conquer/divide_and_conquer.md b/en/docs/chapter_divide_and_conquer/divide_and_conquer.md new file mode 100644 index 0000000000..012b101032 --- /dev/null +++ b/en/docs/chapter_divide_and_conquer/divide_and_conquer.md @@ -0,0 +1,91 @@ +# Divide and conquer algorithms + +Divide and conquer is an important and popular algorithm strategy. As the name suggests, the algorithm is typically implemented recursively and consists of two steps: "divide" and "conquer". + +1. **Divide (partition phase)**: Recursively break down the original problem into two or more smaller sub-problems until the smallest sub-problem is reached. +2. **Conquer (merge phase)**: Starting from the smallest sub-problem with known solution, we construct the solution to the original problem by merging the solutions of sub-problems in a bottom-up manner. + +As shown in the figure below, "merge sort" is one of the typical applications of the divide and conquer strategy. + +1. **Divide**: Recursively divide the original array (original problem) into two sub-arrays (sub-problems), until the sub-array has only one element (smallest sub-problem). +2. **Conquer**: Merge the ordered sub-arrays (solutions to the sub-problems) from bottom to top to obtain an ordered original array (solution to the original problem). + +![Merge sort's divide and conquer strategy](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) + +## How to identify divide and conquer problems + +Whether a problem is suitable for a divide-and-conquer solution can usually be decided based on the following criteria. + +1. **The problem can be broken down into smaller ones**: The original problem can be divided into smaller, similar sub-problems and such process can be recursively done in the same manner. +2. **Sub-problems are independent**: There is no overlap between sub-problems, and they are independent and can be solved separately. +3. **Solutions to sub-problems can be merged**: The solution to the original problem is derived by combining the solutions of the sub-problems. + +Clearly, merge sort meets these three criteria. + +1. **The problem can be broken down into smaller ones**: Recursively divide the array (original problem) into two sub-arrays (sub-problems). +2. **Sub-problems are independent**: Each sub-array can be sorted independently (sub-problems can be solved independently). +3. **Solutions to sub-problems can be merged**: Two ordered sub-arrays (solutions to the sub-problems) can be merged into one ordered array (solution to the original problem). + +## Improve efficiency through divide and conquer + +The **divide-and-conquer strategy not only effectively solves algorithm problems but also often enhances efficiency**. In sorting algorithms, quick sort, merge sort, and heap sort are faster than selection sort, bubble sort, and insertion sort because they apply the divide-and-conquer strategy. + +We may have a question in mind: **Why can divide and conquer improve algorithm efficiency, and what is the underlying logic?** In other words, why is breaking a problem into sub-problems, solving them, and combining their solutions to address the original problem offer more efficiency than directly solving the original problem? This question can be analyzed from two aspects: operation count and parallel computation. + +### Optimization of operation count + +Taking "bubble sort" as an example, it requires $O(n^2)$ time to process an array of length $n$. Suppose we divide the array from the midpoint into two sub-arrays as shown in the figure below, such division requires $O(n)$ time. Sorting each sub-array requires $O((n / 2)^2)$ time. And merging the two sub-arrays requires $O(n)$ time. Thus, the overall time complexity is: + +$$ +O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) +$$ + +![Bubble sort before and after array partition](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) + +Let's calculate the following inequality, where the left side represents the total number of operations before division and the right side represents the total number of operations after division, respectively: + +$$ +\begin{aligned} +n^2 & > \frac{n^2}{2} + 2n \newline +n^2 - \frac{n^2}{2} - 2n & > 0 \newline +n(n - 4) & > 0 +\end{aligned} +$$ + +**This means that when $n > 4$, the number of operations after partitioning is fewer, leading to better performance**. Please note that the time complexity after partitioning is still quadratic $O(n^2)$, but the constant factor in the complexity has decreased. + +We can go even further. **How about keeping dividing the sub-arrays from their midpoints into two sub-arrays** until the sub-arrays have only one element left? This idea is actually "merge sort," with a time complexity of $O(n \log n)$. + +Let's try something a bit different again. **How about splitting into more partitions instead of just two?** For example, we evenly divide the original array into $k$ sub-arrays? This approach is very similar to "bucket sort," which is very suitable for sorting massive data. Theoretically, the time complexity can reach $O(n + k)$. + +### Optimization through parallel computation + +We know that the sub-problems generated by divide and conquer are independent of each other, **which means that they can be solved in parallel.** As a result, divide and conquer not only reduces the algorithm's time complexity, **but also facilitates parallel optimization by modern operating systems.** + +Parallel optimization is particularly effective in environments with multiple cores or processors. As the system can process multiple sub-problems simultaneously, fully utilizing computing resources, the overall runtime is significantly reduced. + +For example, in the "bucket sort" shown in the figure below, we break massive data evenly into various buckets. The jobs of sorting each bucket can be allocated to available computing units. Once all jobs are done, all sorted buckets are merged to produce the final result. + +![Bucket sort's parallel computation](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) + +## Common applications of divide and conquer + +Divide and conquer can be used to solve many classic algorithm problems. + +- **Finding the closest pair of points**: This algorithm works by dividing the set of points into two halves. Then it recursively finds the closest pair in each half. Finally it considers pairs that span the two halves to find the overall closest pair. +- **Large integer multiplication**: One algorithm is called Karatsuba. It breaks down large integer multiplication into several smaller integer multiplications and additions. +- **Matrix multiplication**: One example is the Strassen algorithm. It breaks down a large matrix multiplication into multiple small matrix multiplications and additions. +- **Tower of Hanoi problem**: The Tower of Hanoi problem can be solved recursively, a typical application of the divide-and-conquer strategy. +- **Solving inversion pairs**: In a sequence, if a preceding number is greater than a following number, then these two numbers constitute an inversion pair. Solving inversion pair problem can utilize the idea of divide and conquer, with the aid of merge sort. + +Divide and conquer is also widely applied in the design of algorithms and data structures. + +- **Binary search**: Binary search divides a sorted array into two halves from the midpoint index. And then based on the comparison result between the target value and the middle element value, one half is discarded. The search continues on the remaining half with the same process until the target is found or there is no remaining element. +- **Merge sort**: Already introduced at the beginning of this section, no further elaboration is needed. +- **Quicksort**: Quicksort picks a pivot value to divide the array into two sub-arrays, one with elements smaller than the pivot and the other with elements larger than the pivot. Such process goes on against each of these two sub-arrays until they hold only one element. +- **Bucket sort**: The basic idea of bucket sort is to distribute data to multiple buckets. After sorting the elements within each bucket, retrieve the elements from the buckets in order to obtain an ordered array. +- **Trees**: For example, binary search trees, AVL trees, red-black trees, B-trees, and B+ trees, etc. Their operations, such as search, insertion, and deletion, can all be regarded as applications of the divide-and-conquer strategy. +- **Heap**: A heap is a special type of complete binary tree. Its various operations, such as insertion, deletion, and heapify, actually imply the idea of divide and conquer. +- **Hash table**: Although hash tables do not directly apply divide and conquer, some hash collision resolution solutions indirectly apply the strategy. For example, long lists in chained addressing may be converted to red-black trees to improve query efficiency. + +It can be seen that **divide and conquer is a subtly pervasive algorithmic idea**, embedded within various algorithms and data structures. diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png new file mode 100644 index 0000000000..2174c0d3c4 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png new file mode 100644 index 0000000000..3f7d4e0c47 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png new file mode 100644 index 0000000000..8171c7e9ef Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png new file mode 100644 index 0000000000..4307571eeb Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png new file mode 100644 index 0000000000..84a9a294f8 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png new file mode 100644 index 0000000000..80e9c036d4 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png new file mode 100644 index 0000000000..a44fdfe8cf Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png new file mode 100644 index 0000000000..faee49ce42 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png new file mode 100644 index 0000000000..33a75be5ac Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png new file mode 100644 index 0000000000..9bb30ab09a Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png new file mode 100644 index 0000000000..8fb0a9af2a Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png new file mode 100644 index 0000000000..7cf7906262 Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png new file mode 100644 index 0000000000..b6e026d8be Binary files /dev/null and b/en/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png differ diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.md b/en/docs/chapter_divide_and_conquer/hanota_problem.md new file mode 100644 index 0000000000..c6fc8b6364 --- /dev/null +++ b/en/docs/chapter_divide_and_conquer/hanota_problem.md @@ -0,0 +1,97 @@ +# Tower of Hanoi Problem + +In both merge sort and binary tree construction, we break the original problem into two subproblems, each half the size of the original problem. However, for the Tower of Hanoi, we adopt a different decomposition strategy. + +!!! question + + We are given three pillars, denoted as `A`, `B`, and `C`. Initially, pillar `A` has $n$ discs, arranged from top to bottom in ascending size. Our task is to move these $n$ discs to pillar `C`, maintaining their original order (as shown in the figure below). The following rules apply during the movement: + + 1. A disc can be removed only from the top of a pillar and must be placed on the top of another pillar. + 2. Only one disc can be moved at a time. + 3. A smaller disc must always be on top of a larger disc. + +![Example of the Tower of Hanoi](hanota_problem.assets/hanota_example.png) + +**We denote the Tower of Hanoi problem of size $i$ as $f(i)$**. For example, $f(3)$ represents moving $3$ discs from pillar `A` to pillar `C`. + +### Consider the base cases + +As shown in the figure below, for the problem $f(1)$—which has only one disc—we can directly move it from `A` to `C`. + +=== "<1>" + ![Solution for a problem of size 1](hanota_problem.assets/hanota_f1_step1.png) + +=== "<2>" + ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) + +For $f(2)$—which has two discs—**we rely on pillar `B` to help keep the smaller disc above the larger disc**, as illustrated in the following figure: + +1. First, move the smaller disc from `A` to `B`. +2. Then move the larger disc from `A` to `C`. +3. Finally, move the smaller disc from `B` to `C`. + +=== "<1>" + ![Solution for a problem of size 2](hanota_problem.assets/hanota_f2_step1.png) + +=== "<2>" + ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) + +=== "<3>" + ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) + +=== "<4>" + ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) + +The process of solving $f(2)$ can be summarized as: **moving two discs from `A` to `C` with the help of `B`**. Here, `C` is called the target pillar, and `B` is called the buffer pillar. + +### Decomposition of subproblems + +For the problem $f(3)$—that is, when there are three discs—the situation becomes slightly more complicated. + +Since we already know the solutions to $f(1)$ and $f(2)$, we can adopt a divide-and-conquer perspective and **treat the top two discs on `A` as a single unit**, performing the steps shown in the figure below. This allows the three discs to be successfully moved from `A` to `C`. + +1. Let `B` be the target pillar and `C` the buffer pillar, then move the two discs from `A` to `B`. +2. Move the remaining disc from `A` directly to `C`. +3. Let `C` be the target pillar and `A` the buffer pillar, then move the two discs from `B` to `C`. + +=== "<1>" + ![Solution for a problem of size 3](hanota_problem.assets/hanota_f3_step1.png) + +=== "<2>" + ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) + +=== "<3>" + ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) + +=== "<4>" + ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) + +Essentially, **we decompose $f(3)$ into two $f(2)$ subproblems and one $f(1)$ subproblem**. By solving these three subproblems in sequence, the original problem is solved, indicating that the subproblems are independent and their solutions can be merged. + +From this, we can summarize the divide-and-conquer strategy for the Tower of Hanoi, illustrated in the figure below. We divide the original problem $f(n)$ into two subproblems $f(n-1)$ and one subproblem $f(1)$, and solve these three subproblems in the following order: + +1. Move $n-1$ discs from `A` to `B`, using `C` as a buffer. +2. Move the remaining disc directly from `A` to `C`. +3. Move $n-1$ discs from `B` to `C`, using `A` as a buffer. + +For each $f(n-1)$ subproblem, **we can apply the same recursive partition** until we reach the smallest subproblem $f(1)$. Because $f(1)$ is already known to require just a single move, it is trivial to solve. + +![Divide-and-conquer strategy for solving the Tower of Hanoi](hanota_problem.assets/hanota_divide_and_conquer.png) + +### Code implementation + +In the code, we define a recursive function `dfs(i, src, buf, tar)` which moves the top $i$ discs from pillar `src` to pillar `tar`, using pillar `buf` as a buffer: + +```src +[file]{hanota}-[class]{}-[func]{solve_hanota} +``` + +As shown in the figure below, the Tower of Hanoi problem can be visualized as a recursive tree of height $n$. Each node represents a subproblem, corresponding to a call to `dfs()`, **Hence, the time complexity is $O(2^n)$, and the space complexity is $O(n)$.** + +![Recursive tree of the Tower of Hanoi](hanota_problem.assets/hanota_recursive_tree.png) + +!!! quote + + The Tower of Hanoi originates from an ancient legend. In a temple in ancient India, monks had three tall diamond pillars and $64$ differently sized golden discs. They believed that when the last disc was correctly placed, the world would end. + + However, even if the monks moved one disc every second, it would take about $2^{64} \approx 1.84×10^{19}$ —approximately 585 billion years—far exceeding current estimates of the age of the universe. Thus, if the legend is true, we probably do not need to worry about the world ending. diff --git a/en/docs/chapter_divide_and_conquer/index.md b/en/docs/chapter_divide_and_conquer/index.md new file mode 100644 index 0000000000..ed20fcdac1 --- /dev/null +++ b/en/docs/chapter_divide_and_conquer/index.md @@ -0,0 +1,9 @@ +# Divide and conquer + +![Divide and Conquer](../assets/covers/chapter_divide_and_conquer.jpg) + +!!! abstract + + Difficult problems are decomposed layer by layer, with each decomposition making them simpler. + + Divide and conquer unveils a profound truth: begin with simplicity, and complexity dissolves. diff --git a/en/docs/chapter_divide_and_conquer/summary.md b/en/docs/chapter_divide_and_conquer/summary.md new file mode 100644 index 0000000000..3fd75beffa --- /dev/null +++ b/en/docs/chapter_divide_and_conquer/summary.md @@ -0,0 +1,11 @@ +# Summary + +- Divide and conquer is a common algorithm design strategy that consists of two stages—divide (partition) and conquer (merge)—and is generally implemented using recursion. +- To determine whether a problem is suited for a divide and conquer approach, we check if the problem can be decomposed, whether the subproblems are independent, and whether the subproblems can be merged. +- Merge sort is a typical example of the divide and conquer strategy. It recursively splits an array into two equal-length subarrays until only one element remains, and then merges these subarrays layer by layer to complete the sorting. +- Introducing the divide and conquer strategy often improves algorithm efficiency. On one hand, it reduces the number of operations; on the other hand, it facilitates parallel optimization of the system after division. +- Divide and conquer can be applied to numerous algorithmic problems and is widely used in data structures and algorithm design, appearing in many scenarios. +- Compared to brute force search, adaptive search is more efficient. Search algorithms with a time complexity of $O(\log n)$ are typically based on the divide and conquer strategy. +- Binary search is another classic application of the divide-and-conquer strategy. It does not involve merging subproblem solutions and can be implemented via a recursive divide-and-conquer approach. +- In the problem of constructing binary trees, building the tree (the original problem) can be divided into building the left subtree and right subtree (the subproblems). This can be achieved by partitioning the index ranges of the preorder and inorder traversals. +- In the Tower of Hanoi problem, a problem of size $n$ can be broken down into two subproblems of size $n-1$ and one subproblem of size $1$. By solving these three subproblems in sequence, the original problem is resolved. diff --git a/en/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png new file mode 100644 index 0000000000..50be86d2e8 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png new file mode 100644 index 0000000000..51a3e27d73 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png new file mode 100644 index 0000000000..fd438ba179 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png new file mode 100644 index 0000000000..3c83f92dd8 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_problem_features.md b/en/docs/chapter_dynamic_programming/dp_problem_features.md new file mode 100644 index 0000000000..858b7f8dcd --- /dev/null +++ b/en/docs/chapter_dynamic_programming/dp_problem_features.md @@ -0,0 +1,101 @@ +# Characteristics of dynamic programming problems + +In the previous section, we learned how dynamic programming solves the original problem by decomposing it into subproblems. In fact, subproblem decomposition is a general algorithmic approach, with different emphases in divide and conquer, dynamic programming, and backtracking. + +- Divide and conquer algorithms recursively divide the original problem into multiple independent subproblems until the smallest subproblems are reached, and combine the solutions of the subproblems during backtracking to ultimately obtain the solution to the original problem. +- Dynamic programming also decomposes the problem recursively, but the main difference from divide and conquer algorithms is that the subproblems in dynamic programming are interdependent, and many overlapping subproblems will appear during the decomposition process. +- Backtracking algorithms exhaust all possible solutions through trial and error and avoid unnecessary search branches by pruning. The solution to the original problem consists of a series of decision steps, and we can consider each sub-sequence before each decision step as a subproblem. + +In fact, dynamic programming is commonly used to solve optimization problems, which not only include overlapping subproblems but also have two other major characteristics: optimal substructure and statelessness. + +## Optimal substructure + +We make a slight modification to the stair climbing problem to make it more suitable to demonstrate the concept of optimal substructure. + +!!! question "Minimum cost of climbing stairs" + + Given a staircase, you can step up 1 or 2 steps at a time, and each step on the staircase has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array $cost$, where $cost[i]$ represents the cost you need to pay at the $i$-th step, $cost[0]$ is the ground (starting point). What is the minimum cost required to reach the top? + +As shown in the figure below, if the costs of the 1st, 2nd, and 3rd steps are $1$, $10$, and $1$ respectively, then the minimum cost to climb to the 3rd step from the ground is $2$. + +![Minimum cost to climb to the 3rd step](dp_problem_features.assets/min_cost_cs_example.png) + +Let $dp[i]$ be the cumulative cost of climbing to the $i$-th step. Since the $i$-th step can only come from the $i-1$ or $i-2$ step, $dp[i]$ can only be either $dp[i-1] + cost[i]$ or $dp[i-2] + cost[i]$. To minimize the cost, we should choose the smaller of the two: + +$$ +dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] +$$ + +This leads us to the meaning of optimal substructure: **The optimal solution to the original problem is constructed from the optimal solutions of subproblems**. + +This problem obviously has optimal substructure: we select the better one from the optimal solutions of the two subproblems, $dp[i-1]$ and $dp[i-2]$, and use it to construct the optimal solution for the original problem $dp[i]$. + +So, does the stair climbing problem from the previous section have optimal substructure? Its goal is to solve for the number of solutions, which seems to be a counting problem, but if we ask in another way: "Solve for the maximum number of solutions". We surprisingly find that **although the problem has changed, the optimal substructure has emerged**: the maximum number of solutions at the $n$-th step equals the sum of the maximum number of solutions at the $n-1$ and $n-2$ steps. Thus, the interpretation of optimal substructure is quite flexible and will have different meanings in different problems. + +According to the state transition equation, and the initial states $dp[1] = cost[1]$ and $dp[2] = cost[2]$, we can obtain the dynamic programming code: + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} +``` + +The figure below shows the dynamic programming process for the above code. + +![Dynamic programming process for minimum cost of climbing stairs](dp_problem_features.assets/min_cost_cs_dp.png) + +This problem can also be space-optimized, compressing one dimension to zero, reducing the space complexity from $O(n)$ to $O(1)$: + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} +``` + +## Statelessness + +Statelessness is one of the important characteristics that make dynamic programming effective in solving problems. Its definition is: **Given a certain state, its future development is only related to the current state and unrelated to all past states experienced**. + +Taking the stair climbing problem as an example, given state $i$, it will develop into states $i+1$ and $i+2$, corresponding to jumping 1 step and 2 steps respectively. When making these two choices, we do not need to consider the states before state $i$, as they do not affect the future of state $i$. + +However, if we add a constraint to the stair climbing problem, the situation changes. + +!!! question "Stair climbing with constraints" + + Given a staircase with $n$ steps, you can go up 1 or 2 steps each time, **but you cannot jump 1 step twice in a row**. How many ways are there to climb to the top? + +As shown in the figure below, there are only 2 feasible options for climbing to the 3rd step, among which the option of jumping 1 step three times in a row does not meet the constraint condition and is therefore discarded. + +![Number of feasible options for climbing to the 3rd step with constraints](dp_problem_features.assets/climbing_stairs_constraint_example.png) + +In this problem, if the last round was a jump of 1 step, then the next round must be a jump of 2 steps. This means that **the next step choice cannot be independently determined by the current state (current stair step), but also depends on the previous state (last round's stair step)**. + +It is not difficult to find that this problem no longer satisfies statelessness, and the state transition equation $dp[i] = dp[i-1] + dp[i-2]$ also fails, because $dp[i-1]$ represents this round's jump of 1 step, but it includes many "last round was a jump of 1 step" options, which, to meet the constraint, cannot be directly included in $dp[i]$. + +For this, we need to expand the state definition: **State $[i, j]$ represents being on the $i$-th step and the last round was a jump of $j$ steps**, where $j \in \{1, 2\}$. This state definition effectively distinguishes whether the last round was a jump of 1 step or 2 steps, and we can judge accordingly where the current state came from. + +- When the last round was a jump of 1 step, the round before last could only choose to jump 2 steps, that is, $dp[i, 1]$ can only be transferred from $dp[i-1, 2]$. +- When the last round was a jump of 2 steps, the round before last could choose to jump 1 step or 2 steps, that is, $dp[i, 2]$ can be transferred from $dp[i-2, 1]$ or $dp[i-2, 2]$. + +As shown in the figure below, $dp[i, j]$ represents the number of solutions for state $[i, j]$. At this point, the state transition equation is: + +$$ +\begin{cases} +dp[i, 1] = dp[i-1, 2] \\ +dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] +\end{cases} +$$ + +![Recursive relationship considering constraints](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) + +In the end, returning $dp[n, 1] + dp[n, 2]$ will do, the sum of the two representing the total number of solutions for climbing to the $n$-th step: + +```src +[file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} +``` + +In the above cases, since we only need to consider the previous state, we can still meet the statelessness by expanding the state definition. However, some problems have very serious "state effects". + +!!! question "Stair climbing with obstacle generation" + + Given a staircase with $n$ steps, you can go up 1 or 2 steps each time. **It is stipulated that when climbing to the $i$-th step, the system automatically places an obstacle on the $2i$-th step, and thereafter all rounds are not allowed to jump to the $2i$-th step**. For example, if the first two rounds jump to the 2nd and 3rd steps, then later you cannot jump to the 4th and 6th steps. How many ways are there to climb to the top? + +In this problem, the next jump depends on all past states, as each jump places obstacles on higher steps, affecting future jumps. For such problems, dynamic programming often struggles to solve. + +In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy statelessness. For these kinds of problems, we usually choose to use other methods, such as heuristic search, genetic algorithms, reinforcement learning, etc., to obtain usable local optimal solutions within a limited time. diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png new file mode 100644 index 0000000000..6174530091 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png new file mode 100644 index 0000000000..51e5c99e8f Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png new file mode 100644 index 0000000000..42739737ff Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png new file mode 100644 index 0000000000..852987207a Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png new file mode 100644 index 0000000000..b62327fa3a Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png new file mode 100644 index 0000000000..4c756fb44b Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png new file mode 100644 index 0000000000..1bb0f85bc8 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png new file mode 100644 index 0000000000..1cab4e4136 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png new file mode 100644 index 0000000000..71ac186cf3 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png new file mode 100644 index 0000000000..495fde1ecb Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png new file mode 100644 index 0000000000..e711b80a31 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png new file mode 100644 index 0000000000..50cfccda41 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png new file mode 100644 index 0000000000..77d250e419 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png new file mode 100644 index 0000000000..ff572fa775 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png new file mode 100644 index 0000000000..57cbbcf183 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png new file mode 100644 index 0000000000..f2b27b9a4b Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png new file mode 100644 index 0000000000..bbd30a0476 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png new file mode 100644 index 0000000000..5135b0e21e Binary files /dev/null and b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png differ diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md new file mode 100644 index 0000000000..1c3b465c28 --- /dev/null +++ b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -0,0 +1,183 @@ +# Dynamic programming problem-solving approach + +The last two sections introduced the main characteristics of dynamic programming problems. Next, let's explore two more practical issues together. + +1. How to determine whether a problem is a dynamic programming problem? +2. What are the complete steps to solve a dynamic programming problem? + +## Problem determination + +Generally speaking, if a problem contains overlapping subproblems, optimal substructure, and exhibits no aftereffects, it is usually suitable for dynamic programming solutions. However, it is often difficult to directly extract these characteristics from the problem description. Therefore, we usually relax the conditions and **first observe whether the problem is suitable for resolution using backtracking (exhaustive search)**. + +**Problems suitable for backtracking usually fit the "decision tree model"**, which can be described using a tree structure, where each node represents a decision, and each path represents a sequence of decisions. + +In other words, if the problem contains explicit decision concepts, and the solution is produced through a series of decisions, then it fits the decision tree model and can usually be solved using backtracking. + +On this basis, there are some "bonus points" for determining dynamic programming problems. + +- The problem contains descriptions of maximization (minimization) or finding the most (least) optimal solution. +- The problem's states can be represented using a list, multi-dimensional matrix, or tree, and a state has a recursive relationship with its surrounding states. + +Correspondingly, there are also some "penalty points". + +- The goal of the problem is to find all possible solutions, not just the optimal solution. +- The problem description has obvious characteristics of permutations and combinations, requiring the return of specific multiple solutions. + +If a problem fits the decision tree model and has relatively obvious "bonus points", we can assume it is a dynamic programming problem and verify it during the solution process. + +## Problem-solving steps + +The dynamic programming problem-solving process varies with the nature and difficulty of the problem but generally follows these steps: describe decisions, define states, establish a $dp$ table, derive state transition equations, and determine boundary conditions, etc. + +To illustrate the problem-solving steps more vividly, we use a classic problem, "Minimum Path Sum", as an example. + +!!! question + + Given an $n \times m$ two-dimensional grid `grid`, each cell in the grid contains a non-negative integer representing the cost of that cell. The robot starts from the top-left cell and can only move down or right at each step until it reaches the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right. + +The figure below shows an example, where the given grid's minimum path sum is $13$. + +![Minimum Path Sum Example Data](dp_solution_pipeline.assets/min_path_sum_example.png) + +**First step: Think about each round of decisions, define the state, and thereby obtain the $dp$ table** + +Each round of decisions in this problem is to move one step down or right from the current cell. Suppose the row and column indices of the current cell are $[i, j]$, then after moving down or right, the indices become $[i+1, j]$ or $[i, j+1]$. Therefore, the state should include two variables: the row index and the column index, denoted as $[i, j]$. + +The state $[i, j]$ corresponds to the subproblem: the minimum path sum from the starting point $[0, 0]$ to $[i, j]$, denoted as $dp[i, j]$. + +Thus, we obtain the two-dimensional $dp$ matrix shown in the figure below, whose size is the same as the input grid $grid$. + +![State definition and DP table](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) + +!!! note + + Dynamic programming and backtracking can be described as a sequence of decisions, while a state consists of all decision variables. It should include all variables that describe the progress of solving the problem, containing enough information to derive the next state. + + Each state corresponds to a subproblem, and we define a $dp$ table to store the solutions to all subproblems. Each independent variable of the state is a dimension of the $dp$ table. Essentially, the $dp$ table is a mapping between states and solutions to subproblems. + +**Second step: Identify the optimal substructure, then derive the state transition equation** + +For the state $[i, j]$, it can only be derived from the cell above $[i-1, j]$ or the cell to the left $[i, j-1]$. Therefore, the optimal substructure is: the minimum path sum to reach $[i, j]$ is determined by the smaller of the minimum path sums of $[i, j-1]$ and $[i-1, j]$. + +Based on the above analysis, the state transition equation shown in the figure below can be derived: + +$$ +dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] +$$ + +![Optimal substructure and state transition equation](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) + +!!! note + + Based on the defined $dp$ table, think about the relationship between the original problem and the subproblems, and find out how to construct the optimal solution to the original problem from the optimal solutions to the subproblems, i.e., the optimal substructure. + + Once we have identified the optimal substructure, we can use it to build the state transition equation. + +**Third step: Determine boundary conditions and state transition order** + +In this problem, the states in the first row can only come from the states to their left, and the states in the first column can only come from the states above them, so the first row $i = 0$ and the first column $j = 0$ are the boundary conditions. + +As shown in the figure below, since each cell is derived from the cell to its left and the cell above it, we use loops to traverse the matrix, the outer loop iterating over the rows and the inner loop iterating over the columns. + +![Boundary conditions and state transition order](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) + +!!! note + + Boundary conditions are used in dynamic programming to initialize the $dp$ table, and in search to prune. + + The core of the state transition order is to ensure that when calculating the solution to the current problem, all the smaller subproblems it depends on have already been correctly calculated. + +Based on the above analysis, we can directly write the dynamic programming code. However, the decomposition of subproblems is a top-down approach, so implementing it in the order of "brute-force search → memoized search → dynamic programming" is more in line with habitual thinking. + +### Method 1: Brute-force search + +Start searching from the state $[i, j]$, constantly decomposing it into smaller states $[i-1, j]$ and $[i, j-1]$. The recursive function includes the following elements. + +- **Recursive parameter**: state $[i, j]$. +- **Return value**: the minimum path sum from $[0, 0]$ to $[i, j]$ $dp[i, j]$. +- **Termination condition**: when $i = 0$ and $j = 0$, return the cost $grid[0, 0]$. +- **Pruning**: when $i < 0$ or $j < 0$ index out of bounds, return the cost $+\infty$, representing infeasibility. + +Implementation code as follows: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} +``` + +The figure below shows the recursive tree rooted at $dp[2, 1]$, which includes some overlapping subproblems, the number of which increases sharply as the size of the grid `grid` increases. + +Essentially, the reason for overlapping subproblems is: **there are multiple paths to reach a certain cell from the top-left corner**. + +![Brute-force search recursive tree](dp_solution_pipeline.assets/min_path_sum_dfs.png) + +Each state has two choices, down and right, so the total number of steps from the top-left corner to the bottom-right corner is $m + n - 2$, so the worst-case time complexity is $O(2^{m + n})$. Please note that this calculation method does not consider the situation near the grid edge, where there is only one choice left when reaching the network edge, so the actual number of paths will be less. + +### Method 2: Memoized search + +We introduce a memo list `mem` of the same size as the grid `grid`, used to record the solutions to various subproblems, and prune overlapping subproblems: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} +``` + +As shown in the figure below, after introducing memoization, all subproblem solutions only need to be calculated once, so the time complexity depends on the total number of states, i.e., the grid size $O(nm)$. + +![Memoized search recursive tree](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) + +### Method 3: Dynamic programming + +Implement the dynamic programming solution iteratively, code as shown below: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} +``` + +The figure below show the state transition process of the minimum path sum, traversing the entire grid, **thus the time complexity is $O(nm)$**. + +The array `dp` is of size $n \times m$, **therefore the space complexity is $O(nm)$**. + +=== "<1>" + ![Dynamic programming process of minimum path sum](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) + +=== "<2>" + ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) + +=== "<3>" + ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) + +=== "<4>" + ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) + +=== "<5>" + ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) + +=== "<6>" + ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) + +=== "<7>" + ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) + +=== "<8>" + ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) + +=== "<9>" + ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) + +=== "<10>" + ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) + +=== "<11>" + ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) + +=== "<12>" + ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) + +### Space optimization + +Since each cell is only related to the cell to its left and above, we can use a single-row array to implement the $dp$ table. + +Please note, since the array `dp` can only represent the state of one row, we cannot initialize the first column state in advance, but update it as we traverse each row: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} +``` diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png new file mode 100644 index 0000000000..e3e19c6f93 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png new file mode 100644 index 0000000000..087050f37e Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png new file mode 100644 index 0000000000..13cc5be809 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png new file mode 100644 index 0000000000..29def1363f Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png new file mode 100644 index 0000000000..0d1ad2bcaf Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png new file mode 100644 index 0000000000..be84f2c0a2 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png new file mode 100644 index 0000000000..70f06072c5 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png new file mode 100644 index 0000000000..c4151a7bb5 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png new file mode 100644 index 0000000000..e4f7146f56 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png new file mode 100644 index 0000000000..f95bddd148 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png new file mode 100644 index 0000000000..478bd93ee9 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png new file mode 100644 index 0000000000..fbc90d3a71 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png new file mode 100644 index 0000000000..059ca2e923 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png new file mode 100644 index 0000000000..9911aaac46 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png new file mode 100644 index 0000000000..c98d0f9996 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png new file mode 100644 index 0000000000..744cd0f9bf Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png new file mode 100644 index 0000000000..8a0bf59dc5 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png new file mode 100644 index 0000000000..dbe1d42000 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png differ diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.md b/en/docs/chapter_dynamic_programming/edit_distance_problem.md new file mode 100644 index 0000000000..34dad38650 --- /dev/null +++ b/en/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -0,0 +1,129 @@ +# Edit distance problem + +Edit distance, also known as Levenshtein distance, refers to the minimum number of modifications required to transform one string into another, commonly used in information retrieval and natural language processing to measure the similarity between two sequences. + +!!! question + + Given two strings $s$ and $t$, return the minimum number of edits required to transform $s$ into $t$. + + You can perform three types of edits on a string: insert a character, delete a character, or replace a character with any other character. + +As shown in the figure below, transforming `kitten` into `sitting` requires 3 edits, including 2 replacements and 1 insertion; transforming `hello` into `algo` requires 3 steps, including 2 replacements and 1 deletion. + +![Example data of edit distance](edit_distance_problem.assets/edit_distance_example.png) + +**The edit distance problem can naturally be explained with a decision tree model**. Strings correspond to tree nodes, and a round of decision (an edit operation) corresponds to an edge of the tree. + +As shown in the figure below, with unrestricted operations, each node can derive many edges, each corresponding to one operation, meaning there are many possible paths to transform `hello` into `algo`. + +From the perspective of the decision tree, the goal of this problem is to find the shortest path between the node `hello` and the node `algo`. + +![Edit distance problem represented based on decision tree model](edit_distance_problem.assets/edit_distance_decision_tree.png) + +### Dynamic programming approach + +**Step one: Think about each round of decision, define the state, thus obtaining the $dp$ table** + +Each round of decision involves performing one edit operation on string $s$. + +We aim to gradually reduce the problem size during the edit process, which enables us to construct subproblems. Let the lengths of strings $s$ and $t$ be $n$ and $m$, respectively. We first consider the tail characters of both strings $s[n-1]$ and $t[m-1]$. + +- If $s[n-1]$ and $t[m-1]$ are the same, we can skip them and directly consider $s[n-2]$ and $t[m-2]$. +- If $s[n-1]$ and $t[m-1]$ are different, we need to perform one edit on $s$ (insert, delete, replace) so that the tail characters of the two strings match, allowing us to skip them and consider a smaller-scale problem. + +Thus, each round of decision (edit operation) in string $s$ changes the remaining characters in $s$ and $t$ to be matched. Therefore, the state is the $i$-th and $j$-th characters currently considered in $s$ and $t$, denoted as $[i, j]$. + +State $[i, j]$ corresponds to the subproblem: **The minimum number of edits required to change the first $i$ characters of $s$ into the first $j$ characters of $t$**. + +From this, we obtain a two-dimensional $dp$ table of size $(i+1) \times (j+1)$. + +**Step two: Identify the optimal substructure and then derive the state transition equation** + +Consider the subproblem $dp[i, j]$, whose corresponding tail characters of the two strings are $s[i-1]$ and $t[j-1]$, which can be divided into three scenarios as shown in the figure below. + +1. Add $t[j-1]$ after $s[i-1]$, then the remaining subproblem is $dp[i, j-1]$. +2. Delete $s[i-1]$, then the remaining subproblem is $dp[i-1, j]$. +3. Replace $s[i-1]$ with $t[j-1]$, then the remaining subproblem is $dp[i-1, j-1]$. + +![State transition of edit distance](edit_distance_problem.assets/edit_distance_state_transfer.png) + +Based on the analysis above, we can determine the optimal substructure: The minimum number of edits for $dp[i, j]$ is the minimum among $dp[i, j-1]$, $dp[i-1, j]$, and $dp[i-1, j-1]$, plus the edit step $1$. The corresponding state transition equation is: + +$$ +dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 +$$ + +Please note, **when $s[i-1]$ and $t[j-1]$ are the same, no edit is required for the current character**, in which case the state transition equation is: + +$$ +dp[i, j] = dp[i-1, j-1] +$$ + +**Step three: Determine the boundary conditions and the order of state transitions** + +When both strings are empty, the number of edits is $0$, i.e., $dp[0, 0] = 0$. When $s$ is empty but $t$ is not, the minimum number of edits equals the length of $t$, that is, the first row $dp[0, j] = j$. When $s$ is not empty but $t$ is, the minimum number of edits equals the length of $s$, that is, the first column $dp[i, 0] = i$. + +Observing the state transition equation, solving $dp[i, j]$ depends on the solutions to the left, above, and upper left, so a double loop can be used to traverse the entire $dp$ table in the correct order. + +### Code implementation + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp} +``` + +As shown in the figure below, the process of state transition in the edit distance problem is very similar to that in the knapsack problem, which can be seen as filling a two-dimensional grid. + +=== "<1>" + ![Dynamic programming process of edit distance](edit_distance_problem.assets/edit_distance_dp_step1.png) + +=== "<2>" + ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) + +=== "<3>" + ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) + +=== "<4>" + ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) + +=== "<5>" + ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) + +=== "<6>" + ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) + +=== "<7>" + ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) + +=== "<8>" + ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) + +=== "<9>" + ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) + +=== "<10>" + ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) + +=== "<11>" + ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) + +=== "<12>" + ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) + +=== "<13>" + ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) + +=== "<14>" + ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) + +=== "<15>" + ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) + +### Space optimization + +Since $dp[i, j]$ is derived from the solutions above $dp[i-1, j]$, to the left $dp[i, j-1]$, and to the upper left $dp[i-1, j-1]$, and direct traversal will lose the upper left solution $dp[i-1, j-1]$, and reverse traversal cannot build $dp[i, j-1]$ in advance, therefore, both traversal orders are not feasible. + +For this reason, we can use a variable `leftup` to temporarily store the solution from the upper left $dp[i-1, j-1]$, thus only needing to consider the solutions to the left and above. This situation is similar to the unbounded knapsack problem, allowing for direct traversal. The code is as follows: + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} +``` diff --git a/en/docs/chapter_dynamic_programming/index.md b/en/docs/chapter_dynamic_programming/index.md new file mode 100644 index 0000000000..f949566f10 --- /dev/null +++ b/en/docs/chapter_dynamic_programming/index.md @@ -0,0 +1,9 @@ +# Dynamic programming + +![Dynamic programming](../assets/covers/chapter_dynamic_programming.jpg) + +!!! abstract + + Streams merge into rivers, and rivers merge into the sea. + + Dynamic programming weaves smaller problems’ solutions into larger ones, guiding us step by step toward the far shore—where the ultimate answer awaits. diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png new file mode 100644 index 0000000000..0c73cdb142 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png differ diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png new file mode 100644 index 0000000000..557d2475f6 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png differ diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png new file mode 100644 index 0000000000..d519f468bd Binary files /dev/null and b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png differ diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png new file mode 100644 index 0000000000..0a5f131d37 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png differ diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png new file mode 100644 index 0000000000..6b4963da06 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png differ diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md new file mode 100644 index 0000000000..0031cd13cf --- /dev/null +++ b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -0,0 +1,110 @@ +# Introduction to dynamic programming + +Dynamic programming is an important algorithmic paradigm that decomposes a problem into a series of smaller subproblems, and stores the solutions of these subproblems to avoid redundant computations, thereby significantly improving time efficiency. + +In this section, we start with a classic problem, first presenting its brute force backtracking solution, observing the overlapping subproblems contained within, and then gradually deriving a more efficient dynamic programming solution. + +!!! question "Climbing stairs" + + Given a staircase with $n$ steps, where you can climb $1$ or $2$ steps at a time, how many different ways are there to reach the top? + +As shown in the figure below, there are $3$ ways to reach the top of a $3$-step staircase. + +![Number of ways to reach the 3rd step](intro_to_dynamic_programming.assets/climbing_stairs_example.png) + +The goal of this problem is to determine the number of ways, **considering using backtracking to exhaust all possibilities**. Specifically, imagine climbing stairs as a multi-round choice process: starting from the ground, choosing to go up $1$ or $2$ steps each round, adding one to the count of ways upon reaching the top of the stairs, and pruning the process when exceeding the top. The code is as follows: + +```src +[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} +``` + +## Method 1: Brute force search + +Backtracking algorithms do not explicitly decompose the problem but treat solving the problem as a series of decision steps, searching for all possible solutions through exploration and pruning. + +We can try to analyze this problem from the perspective of decomposition. Let $dp[i]$ be the number of ways to reach the $i^{th}$ step, then $dp[i]$ is the original problem, and its subproblems include: + +$$ +dp[i-1], dp[i-2], \dots, dp[2], dp[1] +$$ + +Since each round can only advance $1$ or $2$ steps, when we stand on the $i^{th}$ step, the previous round must have been either on the $i-1^{th}$ or the $i-2^{th}$ step. In other words, we can only step from the $i-1^{th}$ or the $i-2^{th}$ step to the $i^{th}$ step. + +This leads to an important conclusion: **the number of ways to reach the $i-1^{th}$ step plus the number of ways to reach the $i-2^{th}$ step equals the number of ways to reach the $i^{th}$ step**. The formula is as follows: + +$$ +dp[i] = dp[i-1] + dp[i-2] +$$ + +This means that in the stair climbing problem, there is a recursive relationship between the subproblems, **the solution to the original problem can be constructed from the solutions to the subproblems**. The figure below shows this recursive relationship. + +![Recursive relationship of solution counts](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) + +We can obtain the brute force search solution according to the recursive formula. Starting with $dp[n]$, **recursively decompose a larger problem into the sum of two smaller problems**, until reaching the smallest subproblems $dp[1]$ and $dp[2]$ where the solutions are known, with $dp[1] = 1$ and $dp[2] = 2$, representing $1$ and $2$ ways to climb to the first and second steps, respectively. + +Observe the following code, which, like standard backtracking code, belongs to depth-first search but is more concise: + +```src +[file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} +``` + +The figure below shows the recursive tree formed by brute force search. For the problem $dp[n]$, the depth of its recursive tree is $n$, with a time complexity of $O(2^n)$. Exponential order represents explosive growth, and entering a long wait if a relatively large $n$ is input. + +![Recursive tree for climbing stairs](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) + +Observing the figure above, **the exponential time complexity is caused by 'overlapping subproblems'**. For example, $dp[9]$ is decomposed into $dp[8]$ and $dp[7]$, $dp[8]$ into $dp[7]$ and $dp[6]$, both containing the subproblem $dp[7]$. + +Thus, subproblems include even smaller overlapping subproblems, endlessly. A vast majority of computational resources are wasted on these overlapping subproblems. + +## Method 2: Memoized search + +To enhance algorithm efficiency, **we hope that all overlapping subproblems are calculated only once**. For this purpose, we declare an array `mem` to record the solution of each subproblem, and prune overlapping subproblems during the search process. + +1. When $dp[i]$ is calculated for the first time, we record it in `mem[i]` for later use. +2. When $dp[i]$ needs to be calculated again, we can directly retrieve the result from `mem[i]`, thus avoiding redundant calculations of that subproblem. + +The code is as follows: + +```src +[file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} +``` + +Observe the figure below, **after memoization, all overlapping subproblems need to be calculated only once, optimizing the time complexity to $O(n)$**, which is a significant leap. + +![Recursive tree with memoized search](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) + +## Method 3: Dynamic programming + +**Memoized search is a 'top-down' method**: we start with the original problem (root node), recursively decompose larger subproblems into smaller ones until the solutions to the smallest known subproblems (leaf nodes) are reached. Subsequently, by backtracking, we collect the solutions of the subproblems, constructing the solution to the original problem. + +On the contrary, **dynamic programming is a 'bottom-up' method**: starting with the solutions to the smallest subproblems, iteratively construct the solutions to larger subproblems until the original problem is solved. + +Since dynamic programming does not include a backtracking process, it only requires looping iteration to implement, without needing recursion. In the following code, we initialize an array `dp` to store the solutions to the subproblems, serving the same recording function as the array `mem` in memoized search: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} +``` + +The figure below simulates the execution process of the above code. + +![Dynamic programming process for climbing stairs](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) + +Like the backtracking algorithm, dynamic programming also uses the concept of "states" to represent specific stages in problem solving, each state corresponding to a subproblem and its local optimal solution. For example, the state of the climbing stairs problem is defined as the current step number $i$. + +Based on the above content, we can summarize the commonly used terminology in dynamic programming. + +- The array `dp` is referred to as the DP table, with $dp[i]$ representing the solution to the subproblem corresponding to state $i$. +- The states corresponding to the smallest subproblems (steps $1$ and $2$) are called initial states. +- The recursive formula $dp[i] = dp[i-1] + dp[i-2]$ is called the state transition equation. + +## Space optimization + +Observant readers may have noticed that **since $dp[i]$ is only related to $dp[i-1]$ and $dp[i-2]$, we do not need to use an array `dp` to store the solutions to all subproblems**, but can simply use two variables to progress iteratively. The code is as follows: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} +``` + +Observing the above code, since the space occupied by the array `dp` is eliminated, the space complexity is reduced from $O(n)$ to $O(1)$. + +In dynamic programming problems, the current state is often only related to a limited number of previous states, allowing us to retain only the necessary states and save memory space by "dimension reduction". **This space optimization technique is known as 'rolling variable' or 'rolling array'**. diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png new file mode 100644 index 0000000000..7f7ecd4586 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png new file mode 100644 index 0000000000..4e2bdd2c31 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png new file mode 100644 index 0000000000..04f4c093e8 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png new file mode 100644 index 0000000000..0c83e5bc0c Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png new file mode 100644 index 0000000000..e68a11e637 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png new file mode 100644 index 0000000000..6b20d191f9 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png new file mode 100644 index 0000000000..fc00941046 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png new file mode 100644 index 0000000000..1c26665820 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png new file mode 100644 index 0000000000..3205b1d182 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png new file mode 100644 index 0000000000..2afea2035d Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png new file mode 100644 index 0000000000..3625f07845 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png new file mode 100644 index 0000000000..7a223e88f0 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png new file mode 100644 index 0000000000..369c41614d Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png new file mode 100644 index 0000000000..c9008e904f Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png new file mode 100644 index 0000000000..985ac4309b Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png new file mode 100644 index 0000000000..f3d2557b15 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png new file mode 100644 index 0000000000..de2a6a4e71 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png new file mode 100644 index 0000000000..884617a7d4 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png new file mode 100644 index 0000000000..b1e2f126c8 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png new file mode 100644 index 0000000000..1131ac2c9e Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png new file mode 100644 index 0000000000..06f09b799e Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png new file mode 100644 index 0000000000..603e68a82e Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png new file mode 100644 index 0000000000..8ca1b7a62b Binary files /dev/null and b/en/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png differ diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.md b/en/docs/chapter_dynamic_programming/knapsack_problem.md new file mode 100644 index 0000000000..6536c36693 --- /dev/null +++ b/en/docs/chapter_dynamic_programming/knapsack_problem.md @@ -0,0 +1,168 @@ +# 0-1 Knapsack problem + +The knapsack problem is an excellent introductory problem for dynamic programming and is the most common type of problem in dynamic programming. It has many variants, such as the 0-1 knapsack problem, the unbounded knapsack problem, and the multiple knapsack problem, etc. + +In this section, we will first solve the most common 0-1 knapsack problem. + +!!! question + + Given $n$ items, the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with a capacity of $cap$. Each item can be chosen only once. What is the maximum value of items that can be placed in the knapsack under the capacity limit? + +Observe the figure below, since the item number $i$ starts counting from 1, and the array index starts from 0, thus the weight of item $i$ corresponds to $wgt[i-1]$ and the value corresponds to $val[i-1]$. + +![Example data of the 0-1 knapsack](knapsack_problem.assets/knapsack_example.png) + +We can consider the 0-1 knapsack problem as a process consisting of $n$ rounds of decisions, where for each item there are two decisions: not to put it in or to put it in, thus the problem fits the decision tree model. + +The objective of this problem is to "maximize the value of the items that can be put in the knapsack under the limited capacity," thus it is more likely a dynamic programming problem. + +**First step: Think about each round of decisions, define states, thereby obtaining the $dp$ table** + +For each item, if not put into the knapsack, the capacity remains unchanged; if put in, the capacity is reduced. From this, the state definition can be obtained: the current item number $i$ and knapsack capacity $c$, denoted as $[i, c]$. + +State $[i, c]$ corresponds to the sub-problem: **the maximum value of the first $i$ items in a knapsack of capacity $c$**, denoted as $dp[i, c]$. + +The solution we are looking for is $dp[n, cap]$, so we need a two-dimensional $dp$ table of size $(n+1) \times (cap+1)$. + +**Second step: Identify the optimal substructure, then derive the state transition equation** + +After making the decision for item $i$, what remains is the sub-problem of decisions for the first $i-1$ items, which can be divided into two cases. + +- **Not putting item $i$**: The knapsack capacity remains unchanged, state changes to $[i-1, c]$. +- **Putting item $i$**: The knapsack capacity decreases by $wgt[i-1]$, and the value increases by $val[i-1]$, state changes to $[i-1, c-wgt[i-1]]$. + +The above analysis reveals the optimal substructure of this problem: **the maximum value $dp[i, c]$ is equal to the larger value of the two schemes of not putting item $i$ and putting item $i$**. From this, the state transition equation can be derived: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) +$$ + +It is important to note that if the current item's weight $wgt[i - 1]$ exceeds the remaining knapsack capacity $c$, then the only option is not to put it in the knapsack. + +**Third step: Determine the boundary conditions and the order of state transitions** + +When there are no items or the knapsack capacity is $0$, the maximum value is $0$, i.e., the first column $dp[i, 0]$ and the first row $dp[0, c]$ are both equal to $0$. + +The current state $[i, c]$ transitions from the state directly above $[i-1, c]$ and the state to the upper left $[i-1, c-wgt[i-1]]$, thus, the entire $dp$ table is traversed in order through two layers of loops. + +Following the above analysis, we will next implement the solutions in the order of brute force search, memoized search, and dynamic programming. + +### Method one: Brute force search + +The search code includes the following elements. + +- **Recursive parameters**: State $[i, c]$. +- **Return value**: Solution to the sub-problem $dp[i, c]$. +- **Termination condition**: When the item number is out of bounds $i = 0$ or the remaining capacity of the knapsack is $0$, terminate the recursion and return the value $0$. +- **Pruning**: If the current item's weight exceeds the remaining capacity of the knapsack, the only option is not to put it in the knapsack. + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs} +``` + +As shown in the figure below, since each item generates two search branches of not selecting and selecting, the time complexity is $O(2^n)$. + +Observing the recursive tree, it is easy to see that there are overlapping sub-problems, such as $dp[1, 10]$, etc. When there are many items and the knapsack capacity is large, especially when there are many items of the same weight, the number of overlapping sub-problems will increase significantly. + +![The brute force search recursive tree of the 0-1 knapsack problem](knapsack_problem.assets/knapsack_dfs.png) + +### Method two: Memoized search + +To ensure that overlapping sub-problems are only calculated once, we use a memoization list `mem` to record the solutions to sub-problems, where `mem[i][c]` corresponds to $dp[i, c]$. + +After introducing memoization, **the time complexity depends on the number of sub-problems**, which is $O(n \times cap)$. The implementation code is as follows: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} +``` + +The figure below shows the search branches that are pruned in memoized search. + +![The memoized search recursive tree of the 0-1 knapsack problem](knapsack_problem.assets/knapsack_dfs_mem.png) + +### Method three: Dynamic programming + +Dynamic programming essentially involves filling the $dp$ table during the state transition, the code is shown in the figure below: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp} +``` + +As shown in the figure below, both the time complexity and space complexity are determined by the size of the array `dp`, i.e., $O(n \times cap)$. + +=== "<1>" + ![The dynamic programming process of the 0-1 knapsack problem](knapsack_problem.assets/knapsack_dp_step1.png) + +=== "<2>" + ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) + +=== "<3>" + ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) + +=== "<4>" + ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) + +=== "<5>" + ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) + +=== "<6>" + ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) + +=== "<7>" + ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) + +=== "<8>" + ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) + +=== "<9>" + ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) + +=== "<10>" + ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) + +=== "<11>" + ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) + +=== "<12>" + ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) + +=== "<13>" + ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) + +=== "<14>" + ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) + +### Space optimization + +Since each state is only related to the state in the row above it, we can use two arrays to roll forward, reducing the space complexity from $O(n^2)$ to $O(n)$. + +Further thinking, can we use just one array to achieve space optimization? It can be observed that each state is transferred from the cell directly above or from the upper left cell. If there is only one array, when starting to traverse the $i$-th row, that array still stores the state of row $i-1$. + +- If using normal order traversal, then when traversing to $dp[i, j]$, the values from the upper left $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ may have already been overwritten, thus the correct state transition result cannot be obtained. +- If using reverse order traversal, there will be no overwriting problem, and the state transition can be conducted correctly. + +The figures below show the transition process from row $i = 1$ to row $i = 2$ in a single array. Please think about the differences between normal order traversal and reverse order traversal. + +=== "<1>" + ![The space-optimized dynamic programming process of the 0-1 knapsack](knapsack_problem.assets/knapsack_dp_comp_step1.png) + +=== "<2>" + ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) + +=== "<3>" + ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) + +=== "<4>" + ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) + +=== "<5>" + ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) + +=== "<6>" + ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) + +In the code implementation, we only need to delete the first dimension $i$ of the array `dp` and change the inner loop to reverse traversal: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} +``` diff --git a/en/docs/chapter_dynamic_programming/summary.md b/en/docs/chapter_dynamic_programming/summary.md new file mode 100644 index 0000000000..d7e48eb41c --- /dev/null +++ b/en/docs/chapter_dynamic_programming/summary.md @@ -0,0 +1,23 @@ +# Summary + +- Dynamic programming decomposes problems and improves computational efficiency by avoiding redundant computations through storing solutions of subproblems. +- Without considering time, all dynamic programming problems can be solved using backtracking (brute force search), but the recursion tree has many overlapping subproblems, resulting in very low efficiency. By introducing a memorization list, it's possible to store solutions of all computed subproblems, ensuring that overlapping subproblems are only computed once. +- Memorization search is a top-down recursive solution, whereas dynamic programming corresponds to a bottom-up iterative approach, akin to "filling out a table." Since the current state only depends on certain local states, we can eliminate one dimension of the dp table to reduce space complexity. +- Decomposition of subproblems is a universal algorithmic approach, differing in characteristics among divide and conquer, dynamic programming, and backtracking. +- Dynamic programming problems have three main characteristics: overlapping subproblems, optimal substructure, and no aftereffects. +- If the optimal solution of the original problem can be constructed from the optimal solutions of its subproblems, it has an optimal substructure. +- No aftereffects mean that the future development of a state depends only on the current state and not on all past states experienced. Many combinatorial optimization problems do not have this property and cannot be quickly solved using dynamic programming. + +**Knapsack problem** + +- The knapsack problem is one of the most typical dynamic programming problems, with variants including the 0-1 knapsack, unbounded knapsack, and multiple knapsacks. +- The state definition of the 0-1 knapsack is the maximum value in a knapsack of capacity $c$ with the first $i$ items. Based on decisions not to include or to include an item in the knapsack, optimal substructures can be identified and state transition equations constructed. In space optimization, since each state depends on the state directly above and to the upper left, the list should be traversed in reverse order to avoid overwriting the upper left state. +- In the unbounded knapsack problem, there is no limit on the number of each kind of item that can be chosen, thus the state transition for including items differs from the 0-1 knapsack. Since the state depends on the state directly above and to the left, space optimization should involve forward traversal. +- The coin change problem is a variant of the unbounded knapsack problem, shifting from seeking the “maximum” value to seeking the “minimum” number of coins, thus the state transition equation should change $\max()$ to $\min()$. From pursuing “not exceeding” the capacity of the knapsack to seeking exactly the target amount, thus use $amt + 1$ to represent the invalid solution of “unable to make up the target amount.” +- Coin Change Problem II shifts from seeking the “minimum number of coins” to seeking the “number of coin combinations,” changing the state transition equation accordingly from $\min()$ to summation operator. + +**Edit distance problem** + +- Edit distance (Levenshtein distance) measures the similarity between two strings, defined as the minimum number of editing steps needed to change one string into another, with editing operations including adding, deleting, or replacing. +- The state definition for the edit distance problem is the minimum number of editing steps needed to change the first $i$ characters of $s$ into the first $j$ characters of $t$. When $s[i] \ne t[j]$, there are three decisions: add, delete, replace, each with their corresponding residual subproblems. From this, optimal substructures can be identified, and state transition equations built. When $s[i] = t[j]$, no editing of the current character is necessary. +- In edit distance, the state depends on the state directly above, to the left, and to the upper left. Therefore, after space optimization, neither forward nor reverse traversal can correctly perform state transitions. To address this, we use a variable to temporarily store the upper left state, making it equivalent to the situation in the unbounded knapsack problem, allowing for forward traversal after space optimization. diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png new file mode 100644 index 0000000000..2d73d69e53 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png new file mode 100644 index 0000000000..52de0a4cc7 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png new file mode 100644 index 0000000000..982d3266f6 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png new file mode 100644 index 0000000000..3cd9b096f6 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png new file mode 100644 index 0000000000..8fbcc5e4a6 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png new file mode 100644 index 0000000000..ecbc2eec41 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png new file mode 100644 index 0000000000..a28cce9d77 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png new file mode 100644 index 0000000000..19d8c8140a Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png new file mode 100644 index 0000000000..221ccf1cf3 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png new file mode 100644 index 0000000000..fa0b85dcf7 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png new file mode 100644 index 0000000000..4b4a90fc20 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png new file mode 100644 index 0000000000..1a6c363f81 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png new file mode 100644 index 0000000000..d5092907d3 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png new file mode 100644 index 0000000000..25bf25fd6f Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png new file mode 100644 index 0000000000..398b2324fe Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png new file mode 100644 index 0000000000..157cf43ad6 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png new file mode 100644 index 0000000000..51828b353c Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png new file mode 100644 index 0000000000..f9726a7130 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png new file mode 100644 index 0000000000..6234281fa3 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png new file mode 100644 index 0000000000..27370c3d64 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png new file mode 100644 index 0000000000..1c333deb43 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png new file mode 100644 index 0000000000..c97eeefa6a Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png new file mode 100644 index 0000000000..98f8e78964 Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png new file mode 100644 index 0000000000..7f29ba918d Binary files /dev/null and b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png differ diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md new file mode 100644 index 0000000000..309ee21b48 --- /dev/null +++ b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -0,0 +1,207 @@ +# Unbounded knapsack problem + +In this section, we first solve another common knapsack problem: the unbounded knapsack, and then explore a special case of it: the coin change problem. + +## Unbounded knapsack problem + +!!! question + + Given $n$ items, where the weight of the $i^{th}$ item is $wgt[i-1]$ and its value is $val[i-1]$, and a backpack with a capacity of $cap$. **Each item can be selected multiple times**. What is the maximum value of the items that can be put into the backpack without exceeding its capacity? See the example below. + +![Example data for the unbounded knapsack problem](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) + +### Dynamic programming approach + +The unbounded knapsack problem is very similar to the 0-1 knapsack problem, **the only difference being that there is no limit on the number of times an item can be chosen**. + +- In the 0-1 knapsack problem, there is only one of each item, so after placing item $i$ into the backpack, you can only choose from the previous $i-1$ items. +- In the unbounded knapsack problem, the quantity of each item is unlimited, so after placing item $i$ in the backpack, **you can still choose from the previous $i$ items**. + +Under the rules of the unbounded knapsack problem, the state $[i, c]$ can change in two ways. + +- **Not putting item $i$ in**: As with the 0-1 knapsack problem, transition to $[i-1, c]$. +- **Putting item $i$ in**: Unlike the 0-1 knapsack problem, transition to $[i, c-wgt[i-1]]$. + +The state transition equation thus becomes: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) +$$ + +### Code implementation + +Comparing the code for the two problems, the state transition changes from $i-1$ to $i$, the rest is completely identical: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} +``` + +### Space optimization + +Since the current state comes from the state to the left and above, **the space-optimized solution should perform a forward traversal for each row in the $dp$ table**. + +This traversal order is the opposite of that for the 0-1 knapsack. Please refer to the figure below to understand the difference. + +=== "<1>" + ![Dynamic programming process for the unbounded knapsack problem after space optimization](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) + +=== "<2>" + ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) + +=== "<3>" + ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) + +=== "<4>" + ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) + +=== "<5>" + ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) + +=== "<6>" + ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) + +The code implementation is quite simple, just remove the first dimension of the array `dp`: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} +``` + +## Coin change problem + +The knapsack problem is a representative of a large class of dynamic programming problems and has many variants, such as the coin change problem. + +!!! question + + Given $n$ types of coins, the denomination of the $i^{th}$ type of coin is $coins[i - 1]$, and the target amount is $amt$. **Each type of coin can be selected multiple times**. What is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return $-1$. See the example below. + +![Example data for the coin change problem](unbounded_knapsack_problem.assets/coin_change_example.png) + +### Dynamic programming approach + +**The coin change can be seen as a special case of the unbounded knapsack problem**, sharing the following similarities and differences. + +- The two problems can be converted into each other: "item" corresponds to "coin", "item weight" corresponds to "coin denomination", and "backpack capacity" corresponds to "target amount". +- The optimization goals are opposite: the unbounded knapsack problem aims to maximize the value of items, while the coin change problem aims to minimize the number of coins. +- The unbounded knapsack problem seeks solutions "not exceeding" the backpack capacity, while the coin change seeks solutions that "exactly" make up the target amount. + +**First step: Think through each round's decision-making, define the state, and thus derive the $dp$ table** + +The state $[i, a]$ corresponds to the sub-problem: **the minimum number of coins that can make up the amount $a$ using the first $i$ types of coins**, denoted as $dp[i, a]$. + +The two-dimensional $dp$ table is of size $(n+1) \times (amt+1)$. + +**Second step: Identify the optimal substructure and derive the state transition equation** + +This problem differs from the unbounded knapsack problem in two aspects of the state transition equation. + +- This problem seeks the minimum, so the operator $\max()$ needs to be changed to $\min()$. +- The optimization is focused on the number of coins, so simply add $+1$ when a coin is chosen. + +$$ +dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) +$$ + +**Third step: Define boundary conditions and state transition order** + +When the target amount is $0$, the minimum number of coins needed to make it up is $0$, so all $dp[i, 0]$ in the first column are $0$. + +When there are no coins, **it is impossible to make up any amount >0**, which is an invalid solution. To allow the $\min()$ function in the state transition equation to recognize and filter out invalid solutions, consider using $+\infty$ to represent them, i.e., set all $dp[0, a]$ in the first row to $+\infty$. + +### Code implementation + +Most programming languages do not provide a $+\infty$ variable, only the maximum value of an integer `int` can be used as a substitute. This can lead to overflow: the $+1$ operation in the state transition equation may overflow. + +For this reason, we use the number $amt + 1$ to represent an invalid solution, because the maximum number of coins needed to make up $amt$ is at most $amt$. Before returning the result, check if $dp[n, amt]$ equals $amt + 1$, and if so, return $-1$, indicating that the target amount cannot be made up. The code is as follows: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp} +``` + +The figure below show the dynamic programming process for the coin change problem, which is very similar to the unbounded knapsack problem. + +=== "<1>" + ![Dynamic programming process for the coin change problem](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) + +=== "<2>" + ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) + +=== "<3>" + ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) + +=== "<4>" + ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) + +=== "<5>" + ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) + +=== "<6>" + ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) + +=== "<7>" + ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) + +=== "<8>" + ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) + +=== "<9>" + ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) + +=== "<10>" + ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) + +=== "<11>" + ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) + +=== "<12>" + ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) + +=== "<13>" + ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) + +=== "<14>" + ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) + +=== "<15>" + ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) + +### Space optimization + +The space optimization for the coin change problem is handled in the same way as for the unbounded knapsack problem: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} +``` + +## Coin change problem II + +!!! question + + Given $n$ types of coins, where the denomination of the $i^{th}$ type of coin is $coins[i - 1]$, and the target amount is $amt$. Each type of coin can be selected multiple times, **ask how many combinations of coins can make up the target amount**. See the example below. + +![Example data for Coin Change Problem II](unbounded_knapsack_problem.assets/coin_change_ii_example.png) + +### Dynamic programming approach + +Compared to the previous problem, the goal of this problem is to determine the number of combinations, so the sub-problem becomes: **the number of combinations that can make up amount $a$ using the first $i$ types of coins**. The $dp$ table remains a two-dimensional matrix of size $(n+1) \times (amt + 1)$. + +The number of combinations for the current state is the sum of the combinations from not selecting the current coin and selecting the current coin. The state transition equation is: + +$$ +dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] +$$ + +When the target amount is $0$, no coins are needed to make up the target amount, so all $dp[i, 0]$ in the first column should be initialized to $1$. When there are no coins, it is impossible to make up any amount >0, so all $dp[0, a]$ in the first row should be set to $0$. + +### Code implementation + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} +``` + +### Space optimization + +The space optimization approach is the same, just remove the coin dimension: + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} +``` diff --git a/en/docs/chapter_graph/graph.assets/adjacency_list.png b/en/docs/chapter_graph/graph.assets/adjacency_list.png new file mode 100644 index 0000000000..2ff974dcee Binary files /dev/null and b/en/docs/chapter_graph/graph.assets/adjacency_list.png differ diff --git a/en/docs/chapter_graph/graph.assets/adjacency_matrix.png b/en/docs/chapter_graph/graph.assets/adjacency_matrix.png new file mode 100644 index 0000000000..53eee55d4c Binary files /dev/null and b/en/docs/chapter_graph/graph.assets/adjacency_matrix.png differ diff --git a/en/docs/chapter_graph/graph.assets/connected_graph.png b/en/docs/chapter_graph/graph.assets/connected_graph.png new file mode 100644 index 0000000000..ce6291762b Binary files /dev/null and b/en/docs/chapter_graph/graph.assets/connected_graph.png differ diff --git a/en/docs/chapter_graph/graph.assets/directed_graph.png b/en/docs/chapter_graph/graph.assets/directed_graph.png new file mode 100644 index 0000000000..8c4929688f Binary files /dev/null and b/en/docs/chapter_graph/graph.assets/directed_graph.png differ diff --git a/en/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png b/en/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png new file mode 100644 index 0000000000..796b73f2c5 Binary files /dev/null and b/en/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png differ diff --git a/en/docs/chapter_graph/graph.assets/weighted_graph.png b/en/docs/chapter_graph/graph.assets/weighted_graph.png new file mode 100644 index 0000000000..8e752536c7 Binary files /dev/null and b/en/docs/chapter_graph/graph.assets/weighted_graph.png differ diff --git a/en/docs/chapter_graph/graph.md b/en/docs/chapter_graph/graph.md new file mode 100644 index 0000000000..0e081237ae --- /dev/null +++ b/en/docs/chapter_graph/graph.md @@ -0,0 +1,83 @@ +# Graph + +A graph is a type of nonlinear data structure, consisting of vertices and edges. A graph $G$ can be abstractly represented as a collection of a set of vertices $V$ and a set of edges $E$. The following example shows a graph containing 5 vertices and 7 edges. + +$$ +\begin{aligned} +V & = \{ 1, 2, 3, 4, 5 \} \newline +E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline +G & = \{ V, E \} \newline +\end{aligned} +$$ + +If vertices are viewed as nodes and edges as references (pointers) connecting the nodes, graphs can be seen as a data structure that extends from linked lists. As shown in the figure below, **compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) are more complex due to their higher degree of freedom**. + +![Relationship between linked lists, trees, and graphs](graph.assets/linkedlist_tree_graph.png) + +## Common types and terminologies of graphs + +Graphs can be divided into undirected graphs and directed graphs depending on whether edges have direction, as shown in the figure below. + +- In undirected graphs, edges represent a "bidirectional" connection between two vertices, for example, the "friends" in Facebook. +- In directed graphs, edges have directionality, that is, the edges $A \rightarrow B$ and $A \leftarrow B$ are independent of each other. For example, the "follow" and "followed" relationship on Instagram or TikTok. + +![Directed and undirected graphs](graph.assets/directed_graph.png) + +Depending on whether all vertices are connected, graphs can be divided into connected graphs and disconnected graphs, as shown in the figure below. + +- For connected graphs, it is possible to reach any other vertex starting from an arbitrary vertex. +- For disconnected graphs, there is at least one vertex that cannot be reached from an arbitrary starting vertex. + +![Connected and disconnected graphs](graph.assets/connected_graph.png) + +We can also add a weight variable to edges, resulting in weighted graphs as shown in the figure below. For example, in Instagram, the system sorts your follower and following list by the level of interaction between you and other users (likes, views, comments, etc.). Such an interaction network can be represented by a weighted graph. + +![Weighted and unweighted graphs](graph.assets/weighted_graph.png) + +Graph data structures include the following commonly used terms. + +- Adjacency: When there is an edge connecting two vertices, these two vertices are said to be "adjacent". In the figure above, the adjacent vertices of vertex 1 are vertices 2, 3, and 5. +- Path: The sequence of edges passed from vertex A to vertex B is called a path from A to B. In the figure above, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4. +- Degree: The number of edges a vertex has. For directed graphs, in-degree refers to how many edges point to the vertex, and out-degree refers to how many edges point out from the vertex. + +## Representation of graphs + +Common representations of graphs include "adjacency matrix" and "adjacency list". The following examples use undirected graphs. + +### Adjacency matrix + +Let the number of vertices in the graph be $n$, the adjacency matrix uses an $n \times n$ matrix to represent the graph, where each row (column) represents a vertex, and the matrix elements represent edges, with $1$ or $0$ indicating whether there is an edge between two vertices. + +As shown in the figure below, let the adjacency matrix be $M$, and the list of vertices be $V$, then the matrix element $M[i, j] = 1$ indicates there is an edge between vertex $V[i]$ and vertex $V[j]$, conversely $M[i, j] = 0$ indicates there is no edge between the two vertices. + +![Representation of a graph with an adjacency matrix](graph.assets/adjacency_matrix.png) + +Adjacency matrices have the following characteristics. + +- A vertex cannot be connected to itself, so the elements on the main diagonal of the adjacency matrix are meaningless. +- For undirected graphs, edges in both directions are equivalent, thus the adjacency matrix is symmetric with regard to the main diagonal. +- By replacing the elements of the adjacency matrix from $1$ and $0$ to weights, we can represent weighted graphs. + +When representing graphs with adjacency matrices, it is possible to directly access matrix elements to obtain edges, resulting in efficient operations of addition, deletion, lookup, and modification, all with a time complexity of $O(1)$. However, the space complexity of the matrix is $O(n^2)$, which consumes more memory. + +### Adjacency list + +The adjacency list uses $n$ linked lists to represent the graph, with each linked list node representing a vertex. The $i$-th linked list corresponds to vertex $i$ and contains all adjacent vertices (vertices connected to that vertex). The figure below shows an example of a graph stored using an adjacency list. + +![Representation of a graph with an adjacency list](graph.assets/adjacency_list.png) + +The adjacency list only stores actual edges, and the total number of edges is often much less than $n^2$, making it more space-efficient. However, finding edges in the adjacency list requires traversing the linked list, so its time efficiency is not as good as that of the adjacency matrix. + +Observing the figure above, **the structure of the adjacency list is very similar to the "chaining" in hash tables, hence we can use similar methods to optimize efficiency**. For example, when the linked list is long, it can be transformed into an AVL tree or red-black tree, thus optimizing the time efficiency from $O(n)$ to $O(\log n)$; the linked list can also be transformed into a hash table, thus reducing the time complexity to $O(1)$. + +## Common applications of graphs + +As shown in the table below, many real-world systems can be modeled with graphs, and corresponding problems can be reduced to graph computing problems. + +

Table   Common graphs in real life

+ +| | Vertices | Edges | Graph Computing Problem | +| --------------- | ---------------- | --------------------------------------------- | -------------------------------- | +| Social Networks | Users | Follow / Followed | Potential Following Recommendations | +| Subway Lines | Stations | Connectivity Between Stations | Shortest Route Recommendations | +| Solar System | Celestial Bodies | Gravitational Forces Between Celestial Bodies | Planetary Orbit Calculations | diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png new file mode 100644 index 0000000000..6dd2d12eba Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png new file mode 100644 index 0000000000..21a6fd7357 Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png new file mode 100644 index 0000000000..a739d2e4b4 Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png new file mode 100644 index 0000000000..8e6f63a2fc Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png new file mode 100644 index 0000000000..c2e5250b70 Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png new file mode 100644 index 0000000000..6496a4531b Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png new file mode 100644 index 0000000000..b52f84e894 Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png new file mode 100644 index 0000000000..f00284f8a5 Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png new file mode 100644 index 0000000000..226a3024ff Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png differ diff --git a/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png new file mode 100644 index 0000000000..64e030b928 Binary files /dev/null and b/en/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png differ diff --git a/en/docs/chapter_graph/graph_operations.md b/en/docs/chapter_graph/graph_operations.md new file mode 100644 index 0000000000..40c74d71ba --- /dev/null +++ b/en/docs/chapter_graph/graph_operations.md @@ -0,0 +1,86 @@ +# Basic operations on graphs + +The basic operations on graphs can be divided into operations on "edges" and operations on "vertices". Under the two representation methods of "adjacency matrix" and "adjacency list", the implementations are different. + +## Implementation based on adjacency matrix + +Given an undirected graph with $n$ vertices, the various operations are implemented as shown in the figure below. + +- **Adding or removing an edge**: Directly modify the specified edge in the adjacency matrix, using $O(1)$ time. Since it is an undirected graph, it is necessary to update the edges in both directions simultaneously. +- **Adding a vertex**: Add a row and a column at the end of the adjacency matrix and fill them all with $0$s, using $O(n)$ time. +- **Removing a vertex**: Delete a row and a column in the adjacency matrix. The worst case is when the first row and column are removed, requiring $(n-1)^2$ elements to be "moved up and to the left", thus using $O(n^2)$ time. +- **Initialization**: Pass in $n$ vertices, initialize a vertex list `vertices` of length $n$, using $O(n)$ time; initialize an $n \times n$ size adjacency matrix `adjMat`, using $O(n^2)$ time. + +=== "Initialize adjacency matrix" + ![Initialization, adding and removing edges, adding and removing vertices in adjacency matrix](graph_operations.assets/adjacency_matrix_step1_initialization.png) + +=== "Add an edge" + ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) + +=== "Remove an edge" + ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) + +=== "Add a vertex" + ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) + +=== "Remove a vertex" + ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) + +Below is the implementation code for graphs represented using an adjacency matrix: + +```src +[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} +``` + +## Implementation based on adjacency list + +Given an undirected graph with a total of $n$ vertices and $m$ edges, the various operations can be implemented as shown in the figure below. + +- **Adding an edge**: Simply add the edge at the end of the corresponding vertex's linked list, using $O(1)$ time. Because it is an undirected graph, it is necessary to add edges in both directions simultaneously. +- **Removing an edge**: Find and remove the specified edge in the corresponding vertex's linked list, using $O(m)$ time. In an undirected graph, it is necessary to remove edges in both directions simultaneously. +- **Adding a vertex**: Add a linked list in the adjacency list and make the new vertex the head node of the list, using $O(1)$ time. +- **Removing a vertex**: It is necessary to traverse the entire adjacency list, removing all edges that include the specified vertex, using $O(n + m)$ time. +- **Initialization**: Create $n$ vertices and $2m$ edges in the adjacency list, using $O(n + m)$ time. + +=== "Initialize adjacency list" + ![Initialization, adding and removing edges, adding and removing vertices in adjacency list](graph_operations.assets/adjacency_list_step1_initialization.png) + +=== "Add an edge" + ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) + +=== "Remove an edge" + ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) + +=== "Add a vertex" + ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) + +=== "Remove a vertex" + ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) + +Below is the adjacency list code implementation. Compared to the figure above, the actual code has the following differences. + +- For convenience in adding and removing vertices, and to simplify the code, we use lists (dynamic arrays) instead of linked lists. +- Use a hash table to store the adjacency list, `key` being the vertex instance, `value` being the list (linked list) of adjacent vertices of that vertex. + +Additionally, we use the `Vertex` class to represent vertices in the adjacency list. The reason for this is: if, like with the adjacency matrix, list indexes were used to distinguish different vertices, then suppose you want to delete the vertex at index $i$, you would need to traverse the entire adjacency list and decrement all indexes greater than $i$ by $1$, which is very inefficient. However, if each vertex is a unique `Vertex` instance, then deleting a vertex does not require any changes to other vertices. + +```src +[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} +``` + +## Efficiency comparison + +Assuming there are $n$ vertices and $m$ edges in the graph, the table below compares the time efficiency and space efficiency of the adjacency matrix and adjacency list. + +

Table   Comparison of adjacency matrix and adjacency list

+ +| | Adjacency matrix | Adjacency list (Linked list) | Adjacency list (Hash table) | +| ------------------- | ---------------- | ---------------------------- | --------------------------- | +| Determine adjacency | $O(1)$ | $O(m)$ | $O(1)$ | +| Add an edge | $O(1)$ | $O(1)$ | $O(1)$ | +| Remove an edge | $O(1)$ | $O(m)$ | $O(1)$ | +| Add a vertex | $O(n)$ | $O(1)$ | $O(1)$ | +| Remove a vertex | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| Memory space usage | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | + +Observing the table above, it seems that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, the adjacency matrix exemplifies the principle of "space for time", while the adjacency list exemplifies "time for space". diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs.png new file mode 100644 index 0000000000..807377d584 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png new file mode 100644 index 0000000000..7ca47cc7c3 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png new file mode 100644 index 0000000000..de10b49ea7 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png new file mode 100644 index 0000000000..7af032066c Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png new file mode 100644 index 0000000000..df8c986bc2 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png new file mode 100644 index 0000000000..c534b15a7a Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png new file mode 100644 index 0000000000..cf6bdd7d2e Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png new file mode 100644 index 0000000000..6963b7cc25 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png new file mode 100644 index 0000000000..af5f1752da Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png new file mode 100644 index 0000000000..c0ad73f8a3 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png new file mode 100644 index 0000000000..dea7a54f12 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png new file mode 100644 index 0000000000..04f6d0f132 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs.png new file mode 100644 index 0000000000..2cad4f5cd3 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png new file mode 100644 index 0000000000..e6f5b4d4f3 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png new file mode 100644 index 0000000000..390332724c Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png new file mode 100644 index 0000000000..881d7abe95 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png new file mode 100644 index 0000000000..09b351e4ff Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png new file mode 100644 index 0000000000..477e4a3ab3 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png new file mode 100644 index 0000000000..3e77e375dc Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png new file mode 100644 index 0000000000..2a17ab3e11 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png new file mode 100644 index 0000000000..04d78d90a3 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png new file mode 100644 index 0000000000..a7e9b10aca Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png new file mode 100644 index 0000000000..e9ac80973f Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png differ diff --git a/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png new file mode 100644 index 0000000000..d8530b3bc7 Binary files /dev/null and b/en/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png differ diff --git a/en/docs/chapter_graph/graph_traversal.md b/en/docs/chapter_graph/graph_traversal.md new file mode 100644 index 0000000000..9c7f9640e2 --- /dev/null +++ b/en/docs/chapter_graph/graph_traversal.md @@ -0,0 +1,136 @@ +# Graph traversal + +Trees represent a "one-to-many" relationship, while graphs have a higher degree of freedom and can represent any "many-to-many" relationship. Therefore, we can consider tree as a special case of graph. Clearly, **tree traversal operations are also a special case of graph traversal operations**. + +Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal can be divided into two types: Breadth-First Search (BFS) and Depth-First Search (DFS). + +## Breadth-first search + +**Breadth-first search is a near-to-far traversal method, starting from a certain node, always prioritizing the visit to the nearest vertices and expanding outwards layer by layer**. As shown in the figure below, starting from the top left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited. + +![Breadth-first traversal of a graph](graph_traversal.assets/graph_bfs.png) + +### Algorithm implementation + +BFS is usually implemented with the help of a queue, as shown in the code below. The queue is "first in, first out", which aligns with the BFS idea of traversing "from near to far". + +1. Add the starting vertex `startVet` to the queue and start the loop. +2. In each iteration of the loop, pop the vertex at the front of the queue and record it as visited, then add all adjacent vertices of that vertex to the back of the queue. +3. Repeat step `2.` until all vertices have been visited. + +To prevent revisiting vertices, we use a hash set `visited` to record which nodes have been visited. + +```src +[file]{graph_bfs}-[class]{}-[func]{graph_bfs} +``` + +The code is relatively abstract, you can compare it with the figure below to get a better understanding. + +=== "<1>" + ![Steps of breadth-first search of a graph](graph_traversal.assets/graph_bfs_step1.png) + +=== "<2>" + ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) + +=== "<3>" + ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) + +=== "<4>" + ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) + +=== "<5>" + ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) + +=== "<6>" + ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) + +=== "<7>" + ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) + +=== "<8>" + ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) + +=== "<9>" + ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) + +=== "<10>" + ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) + +=== "<11>" + ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) + +!!! question "Is the sequence of breadth-first traversal unique?" + + Not unique. Breadth-first traversal only requires traversing in a "near to far" order, **and the traversal order of the vertices with the same distance can be arbitrary**. For example, in the figure above, the visit order of vertices $1$ and $3$ can be swapped, as can the order of vertices $2$, $4$, and $6$. + +### Complexity analysis + +**Time complexity**: All vertices will be enqueued and dequeued once, using $O(|V|)$ time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited $2$ times, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time. + +**Space complexity**: The maximum number of vertices in list `res`, hash set `visited`, and queue `que` is $|V|$, using $O(|V|)$ space. + +## Depth-first search + +**Depth-first search is a traversal method that prioritizes going as far as possible and then backtracks when no further path is available**. As shown in the figure below, starting from the top left vertex, visit some adjacent vertex of the current vertex until no further path is available, then return and continue until all vertices are traversed. + +![Depth-first traversal of a graph](graph_traversal.assets/graph_dfs.png) + +### Algorithm implementation + +This "go as far as possible and then return" algorithm paradigm is usually implemented based on recursion. Similar to breadth-first search, in depth-first search, we also need the help of a hash set `visited` to record the visited vertices to avoid revisiting. + +```src +[file]{graph_dfs}-[class]{}-[func]{graph_dfs} +``` + +The algorithm process of depth-first search is shown in the figure below. + +- **Dashed lines represent downward recursion**, indicating that a new recursive method has been initiated to visit a new vertex. +- **Curved dashed lines represent upward backtracking**, indicating that this recursive method has returned to the position where this method was initiated. + +To deepen the understanding, it is suggested to combine the figure below with the code to simulate (or draw) the entire DFS process in your mind, including when each recursive method is initiated and when it returns. + +=== "<1>" + ![Steps of depth-first search of a graph](graph_traversal.assets/graph_dfs_step1.png) + +=== "<2>" + ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) + +=== "<3>" + ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) + +=== "<4>" + ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) + +=== "<5>" + ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) + +=== "<6>" + ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) + +=== "<7>" + ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) + +=== "<8>" + ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) + +=== "<9>" + ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) + +=== "<10>" + ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) + +=== "<11>" + ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) + +!!! question "Is the sequence of depth-first traversal unique?" + + Similar to breadth-first traversal, the order of the depth-first traversal sequence is also not unique. Given a certain vertex, exploring in any direction first is possible, that is, the order of adjacent vertices can be arbitrarily shuffled, all being part of depth-first traversal. + + Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to pre-order, in-order, and post-order traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal. + +### Complexity analysis + +**Time complexity**: All vertices will be visited once, using $O(|V|)$ time; all edges will be visited twice, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time. + +**Space complexity**: The maximum number of vertices in list `res`, hash set `visited` is $|V|$, and the maximum recursion depth is $|V|$, therefore using $O(|V|)$ space. diff --git a/en/docs/chapter_graph/index.md b/en/docs/chapter_graph/index.md new file mode 100644 index 0000000000..46d9c96872 --- /dev/null +++ b/en/docs/chapter_graph/index.md @@ -0,0 +1,9 @@ +# Graph + +![Graph](../assets/covers/chapter_graph.jpg) + +!!! abstract + + In the journey of life, each of us is a node, connected by countless invisible edges. + + Each encounter and parting leaves a unique imprint on this vast graph of life. diff --git a/en/docs/chapter_graph/summary.md b/en/docs/chapter_graph/summary.md new file mode 100644 index 0000000000..8480efe26d --- /dev/null +++ b/en/docs/chapter_graph/summary.md @@ -0,0 +1,31 @@ +# Summary + +### Key review + +- A graph is made up of vertices and edges. It can be described as a set of vertices and a set of edges. +- Compared to linear relationships (like linked lists) and hierarchical relationships (like trees), network relationships (graphs) offer greater flexibility, making them more complex. +- In a directed graph, edges have directions. In a connected graph, any vertex can be reached from any other vertex. In a weighted graph, each edge has an associated weight variable. +- An adjacency matrix is a way to represent a graph using matrix (2D array). The rows and columns represent the vertices. The matrix element value indicates whether there is an edge between two vertices, using $1$ for an edge or $0$ for no edge. Adjacency matrices are highly efficient for operations like adding, deleting, or checking edges, but they require more space. +- An adjacency list is another common way to represent a graph using a collection of linked lists. Each vertex in the graph has a list that contains all its adjacent vertices. The $i^{th}$ list represents vertex $i$. Adjacency lists use less space compared to adjacency matrices. However, since it requires traversing the list to find edges, the time efficiency is lower. +- When the linked lists in an adjacency list are long enough, they can be converted into red-black trees or hash tables to improve lookup efficiency. +- From the perspective of algorithmic design, an adjacency matrix reflects the concept of "trading space for time", whereas an adjacency list reflects "trading time for space". +- Graphs can be used to model various real-world systems, such as social networks, subway routes. +- A tree is a special case of a graph, and tree traversal is also a special case of graph traversal. +- Breadth-first traversal of a graph is a search method that expands layer by layer from near to far, typically using a queue. +- Depth-first traversal of a graph is a search method that prioritizes reaching the end before backtracking when no further path is available. It is often implemented using recursion. + +### Q & A + +**Q**: Is a path defined as a sequence of vertices or a sequence of edges? + +In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. + +In this document, a path is considered a sequence of edges, rather than a sequence of vertices. This is because there might be multiple edges connecting two vertices, in which case each edge corresponds to a path. + +**Q**: In a disconnected graph, are there points that cannot be traversed? + +In a disconnected graph, there is at least one vertex that cannot be reached from a specific point. To traverse a disconnected graph, you need to set multiple starting points to traverse all the connected components of the graph. + +**Q**: In an adjacency list, does the order of "all vertices connected to that vertex" matter? + +It can be in any order. However, in real-world applications, it might be necessary to sort them according to certain rules, such as the order in which vertices are added, or the order of vertex values. This can help find vertices quickly with certain extreme values. diff --git a/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png new file mode 100644 index 0000000000..6a1e64fa20 Binary files /dev/null and b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png differ diff --git a/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png new file mode 100644 index 0000000000..9e10560cb3 Binary files /dev/null and b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png differ diff --git a/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png new file mode 100644 index 0000000000..4eda645c7e Binary files /dev/null and b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png differ diff --git a/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png new file mode 100644 index 0000000000..8424858f1e Binary files /dev/null and b/en/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png differ diff --git a/en/docs/chapter_greedy/fractional_knapsack_problem.md b/en/docs/chapter_greedy/fractional_knapsack_problem.md new file mode 100644 index 0000000000..feb9239f77 --- /dev/null +++ b/en/docs/chapter_greedy/fractional_knapsack_problem.md @@ -0,0 +1,50 @@ +# Fractional knapsack problem + +!!! question + + Given $n$ items, the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with a capacity of $cap$. Each item can be chosen only once, **but a part of the item can be selected, with its value calculated based on the proportion of the weight chosen**, what is the maximum value of the items in the knapsack under the limited capacity? An example is shown in the figure below. + +![Example data of the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_example.png) + +The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, involving the current item $i$ and capacity $c$, aiming to maximize the value within the limited capacity of the knapsack. + +The difference is that, in this problem, only a part of an item can be chosen. As shown in the figure below, **we can arbitrarily split the items and calculate the corresponding value based on the weight proportion**. + +1. For item $i$, its value per unit weight is $val[i-1] / wgt[i-1]$, referred to as the unit value. +2. Suppose we put a part of item $i$ with weight $w$ into the knapsack, then the value added to the knapsack is $w \times val[i-1] / wgt[i-1]$. + +![Value per unit weight of the item](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) + +### Greedy strategy determination + +Maximizing the total value of the items in the knapsack **essentially means maximizing the value per unit weight**. From this, the greedy strategy shown in the figure below can be deduced. + +1. Sort the items by their unit value from high to low. +2. Iterate over all items, **greedily choosing the item with the highest unit value in each round**. +3. If the remaining capacity of the knapsack is insufficient, use part of the current item to fill the knapsack. + +![Greedy strategy of the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) + +### Code implementation + +We have created an `Item` class in order to sort the items by their unit value. We loop and make greedy choices until the knapsack is full, then exit and return the solution: + +```src +[file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} +``` + +Apart from sorting, in the worst case, the entire list of items needs to be traversed, **hence the time complexity is $O(n)$**, where $n$ is the number of items. + +Since an `Item` object list is initialized, **the space complexity is $O(n)$**. + +### Correctness proof + +Using proof by contradiction. Suppose item $x$ has the highest unit value, and some algorithm yields a maximum value `res`, but the solution does not include item $x$. + +Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item $x$. Since the unit value of item $x$ is the highest, the total value after replacement will definitely be greater than `res`. **This contradicts the assumption that `res` is the optimal solution, proving that the optimal solution must include item $x$**. + +For other items in this solution, we can also construct the above contradiction. Overall, **items with greater unit value are always better choices**, proving that the greedy strategy is effective. + +As shown in the figure below, if the item weight and unit value are viewed as the horizontal and vertical axes of a two-dimensional chart respectively, the fractional knapsack problem can be transformed into "seeking the largest area enclosed within a limited horizontal axis range". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective. + +![Geometric representation of the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) diff --git a/en/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png b/en/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png new file mode 100644 index 0000000000..0412e0ce30 Binary files /dev/null and b/en/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png differ diff --git a/en/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png b/en/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png new file mode 100644 index 0000000000..b53735aa61 Binary files /dev/null and b/en/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png differ diff --git a/en/docs/chapter_greedy/greedy_algorithm.md b/en/docs/chapter_greedy/greedy_algorithm.md new file mode 100644 index 0000000000..9e61c09782 --- /dev/null +++ b/en/docs/chapter_greedy/greedy_algorithm.md @@ -0,0 +1,94 @@ +# Greedy algorithms + +Greedy algorithm is a common algorithm for solving optimization problems, which fundamentally involves making the seemingly best choice at each decision-making stage of the problem, i.e., greedily making locally optimal decisions in hopes of finding a globally optimal solution. Greedy algorithms are concise and efficient, and are widely used in many practical problems. + +Greedy algorithms and dynamic programming are both commonly used to solve optimization problems. They share some similarities, such as relying on the property of optimal substructure, but they operate differently. + +- Dynamic programming considers all previous decisions at the current decision stage and uses solutions to past subproblems to construct solutions for the current subproblem. +- Greedy algorithms do not consider past decisions; instead, they proceed with greedy choices, continually narrowing the scope of the problem until it is solved. + +Let's first understand the working principle of the greedy algorithm through the example of "coin change," which has been introduced in the "Complete Knapsack Problem" chapter. I believe you are already familiar with it. + +!!! question + + Given $n$ types of coins, where the denomination of the $i$th type of coin is $coins[i - 1]$, and the target amount is $amt$, with each type of coin available indefinitely, what is the minimum number of coins needed to make up the target amount? If it is not possible to make up the target amount, return $-1$. + +The greedy strategy adopted in this problem is shown in the figure below. Given the target amount, **we greedily choose the coin that is closest to and not greater than it**, repeatedly following this step until the target amount is met. + +![Greedy strategy for coin change](greedy_algorithm.assets/coin_change_greedy_strategy.png) + +The implementation code is as follows: + +```src +[file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} +``` + +You might exclaim: So clean! The greedy algorithm solves the coin change problem in about ten lines of code. + +## Advantages and limitations of greedy algorithms + +**Greedy algorithms are not only straightforward and simple to implement, but they are also usually very efficient**. In the code above, if the smallest coin denomination is $\min(coins)$, the greedy choice loops at most $amt / \min(coins)$ times, giving a time complexity of $O(amt / \min(coins))$. This is an order of magnitude smaller than the time complexity of the dynamic programming solution, which is $O(n \times amt)$. + +However, **for some combinations of coin denominations, greedy algorithms cannot find the optimal solution**. The figure below provides two examples. + +- **Positive example $coins = [1, 5, 10, 20, 50, 100]$**: In this coin combination, given any $amt$, the greedy algorithm can find the optimal solution. +- **Negative example $coins = [1, 20, 50]$**: Suppose $amt = 60$, the greedy algorithm can only find the combination $50 + 1 \times 10$, totaling 11 coins, but dynamic programming can find the optimal solution of $20 + 20 + 20$, needing only 3 coins. +- **Negative example $coins = [1, 49, 50]$**: Suppose $amt = 98$, the greedy algorithm can only find the combination $50 + 1 \times 48$, totaling 49 coins, but dynamic programming can find the optimal solution of $49 + 49$, needing only 2 coins. + +![Examples where greedy algorithms do not find the optimal solution](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) + +This means that for the coin change problem, greedy algorithms cannot guarantee finding the globally optimal solution, and they might find a very poor solution. They are better suited for dynamic programming. + +Generally, the suitability of greedy algorithms falls into two categories. + +1. **Guaranteed to find the optimal solution**: In these cases, greedy algorithms are often the best choice, as they tend to be more efficient than backtracking or dynamic programming. +2. **Can find a near-optimal solution**: Greedy algorithms are also applicable here. For many complex problems, finding the global optimal solution is very challenging, and being able to find a high-efficiency suboptimal solution is also very commendable. + +## Characteristics of greedy algorithms + +So, what kind of problems are suitable for solving with greedy algorithms? Or rather, under what conditions can greedy algorithms guarantee to find the optimal solution? + +Compared to dynamic programming, greedy algorithms have stricter usage conditions, focusing mainly on two properties of the problem. + +- **Greedy choice property**: Only when the locally optimal choice can always lead to a globally optimal solution can greedy algorithms guarantee to obtain the optimal solution. +- **Optimal substructure**: The optimal solution to the original problem contains the optimal solutions to its subproblems. + +Optimal substructure has already been introduced in the "Dynamic Programming" chapter, so it is not discussed further here. It's important to note that some problems do not have an obvious optimal substructure, but can still be solved using greedy algorithms. + +We mainly explore the method for determining the greedy choice property. Although its description seems simple, **in practice, proving the greedy choice property for many problems is not easy**. + +For example, in the coin change problem, although we can easily cite counterexamples to disprove the greedy choice property, proving it is much more challenging. If asked, **what conditions must a coin combination meet to be solvable using a greedy algorithm**? We often have to rely on intuition or examples to provide an ambiguous answer, as it is difficult to provide a rigorous mathematical proof. + +!!! quote + + A paper presents an algorithm with a time complexity of $O(n^3)$ for determining whether a coin combination can use a greedy algorithm to find the optimal solution for any amount. + + Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. + +## Steps for solving problems with greedy algorithms + +The problem-solving process for greedy problems can generally be divided into the following three steps. + +1. **Problem analysis**: Sort out and understand the characteristics of the problem, including state definition, optimization objectives, and constraints, etc. This step is also involved in backtracking and dynamic programming. +2. **Determine the greedy strategy**: Determine how to make a greedy choice at each step. This strategy can reduce the scale of the problem at each step and eventually solve the entire problem. +3. **Proof of correctness**: It is usually necessary to prove that the problem has both a greedy choice property and optimal substructure. This step may require mathematical proofs, such as induction or reductio ad absurdum. + +Determining the greedy strategy is the core step in solving the problem, but it may not be easy to implement, mainly for the following reasons. + +- **Greedy strategies vary greatly between different problems**. For many problems, the greedy strategy is fairly straightforward, and we can come up with it through some general thinking and attempts. However, for some complex problems, the greedy strategy may be very elusive, which is a real test of individual problem-solving experience and algorithmic capability. +- **Some greedy strategies are quite misleading**. When we confidently design a greedy strategy, write the code, and submit it for testing, it is quite possible that some test cases will not pass. This is because the designed greedy strategy is only "partially correct," as described above with the coin change example. + +To ensure accuracy, we should provide rigorous mathematical proofs for the greedy strategy, **usually involving reductio ad absurdum or mathematical induction**. + +However, proving correctness may not be an easy task. If we are at a loss, we usually choose to debug the code based on test cases, modifying and verifying the greedy strategy step by step. + +## Typical problems solved by greedy algorithms + +Greedy algorithms are often applied to optimization problems that satisfy the properties of greedy choice and optimal substructure. Below are some typical greedy algorithm problems. + +- **Coin change problem**: In some coin combinations, the greedy algorithm always provides the optimal solution. +- **Interval scheduling problem**: Suppose you have several tasks, each of which takes place over a period of time. Your goal is to complete as many tasks as possible. If you always choose the task that ends the earliest, then the greedy algorithm can achieve the optimal solution. +- **Fractional knapsack problem**: Given a set of items and a carrying capacity, your goal is to select a set of items such that the total weight does not exceed the carrying capacity and the total value is maximized. If you always choose the item with the highest value-to-weight ratio (value / weight), the greedy algorithm can achieve the optimal solution in some cases. +- **Stock trading problem**: Given a set of historical stock prices, you can make multiple trades, but you cannot buy again until after you have sold if you already own stocks. The goal is to achieve the maximum profit. +- **Huffman coding**: Huffman coding is a greedy algorithm used for lossless data compression. By constructing a Huffman tree, it always merges the two nodes with the lowest frequency, resulting in a Huffman tree with the minimum weighted path length (coding length). +- **Dijkstra's algorithm**: It is a greedy algorithm for solving the shortest path problem from a given source vertex to all other vertices. diff --git a/en/docs/chapter_greedy/index.md b/en/docs/chapter_greedy/index.md new file mode 100644 index 0000000000..ba4314f7a3 --- /dev/null +++ b/en/docs/chapter_greedy/index.md @@ -0,0 +1,9 @@ +# Greedy + +![Greedy](../assets/covers/chapter_greedy.jpg) + +!!! abstract + + Sunflowers turn towards the sun, always seeking the greatest possible growth for themselves. + + Greedy strategy guides to the best answer step by step through rounds of simple choices. diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png new file mode 100644 index 0000000000..5ec41e8217 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png new file mode 100644 index 0000000000..85a2ebb8d6 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png new file mode 100644 index 0000000000..e28de2d467 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png new file mode 100644 index 0000000000..30ac2d34c0 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png new file mode 100644 index 0000000000..042772dc33 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png new file mode 100644 index 0000000000..1f84708649 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png new file mode 100644 index 0000000000..b7a4f0caad Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png new file mode 100644 index 0000000000..98e58671c4 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png new file mode 100644 index 0000000000..5af2484004 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png new file mode 100644 index 0000000000..207d0e11c4 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png new file mode 100644 index 0000000000..0ed314f4d5 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png new file mode 100644 index 0000000000..6b49362052 Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png new file mode 100644 index 0000000000..ed2688703c Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png new file mode 100644 index 0000000000..758254d65d Binary files /dev/null and b/en/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png differ diff --git a/en/docs/chapter_greedy/max_capacity_problem.md b/en/docs/chapter_greedy/max_capacity_problem.md new file mode 100644 index 0000000000..ba2ca009ff --- /dev/null +++ b/en/docs/chapter_greedy/max_capacity_problem.md @@ -0,0 +1,99 @@ +# Maximum capacity problem + +!!! question + + Input an array $ht$, where each element represents the height of a vertical partition. Any two partitions in the array, along with the space between them, can form a container. + + The capacity of the container is the product of the height and the width (area), where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions. + + Please select two partitions in the array that maximize the container's capacity and return this maximum capacity. An example is shown in the figure below. + +![Example data for the maximum capacity problem](max_capacity_problem.assets/max_capacity_example.png) + +The container is formed by any two partitions, **therefore the state of this problem is represented by the indices of the two partitions, denoted as $[i, j]$**. + +According to the problem statement, the capacity equals the product of height and width, where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions. The formula for capacity $cap[i, j]$ is: + +$$ +cap[i, j] = \min(ht[i], ht[j]) \times (j - i) +$$ + +Assuming the length of the array is $n$, the number of combinations of two partitions (total number of states) is $C_n^2 = \frac{n(n - 1)}{2}$. The most straightforward approach is to **enumerate all possible states**, resulting in a time complexity of $O(n^2)$. + +### Determination of a greedy strategy + +There is a more efficient solution to this problem. As shown in the figure below, we select a state $[i, j]$ where the indices $i < j$ and the height $ht[i] < ht[j]$, meaning $i$ is the shorter partition, and $j$ is the taller one. + +![Initial state](max_capacity_problem.assets/max_capacity_initial_state.png) + +As shown in the figure below, **if we move the taller partition $j$ closer to the shorter partition $i$, the capacity will definitely decrease**. + +This is because when moving the taller partition $j$, the width $j-i$ definitely decreases; and since the height is determined by the shorter partition, the height can only remain the same (if $i$ remains the shorter partition) or decrease (if the moved $j$ becomes the shorter partition). + +![State after moving the taller partition inward](max_capacity_problem.assets/max_capacity_moving_long_board.png) + +Conversely, **we can only possibly increase the capacity by moving the shorter partition $i$ inward**. Although the width will definitely decrease, **the height may increase** (if the moved shorter partition $i$ becomes taller). For example, in the figure below, the area increases after moving the shorter partition. + +![State after moving the shorter partition inward](max_capacity_problem.assets/max_capacity_moving_short_board.png) + +This leads us to the greedy strategy for this problem: initialize two pointers at the ends of the container, and in each round, move the pointer corresponding to the shorter partition inward until the two pointers meet. + +The figure below illustrate the execution of the greedy strategy. + +1. Initially, the pointers $i$ and $j$ are positioned at the ends of the array. +2. Calculate the current state's capacity $cap[i, j]$ and update the maximum capacity. +3. Compare the heights of partitions $i$ and $j$, and move the shorter partition inward by one step. +4. Repeat steps `2.` and `3.` until $i$ and $j$ meet. + +=== "<1>" + ![The greedy process for maximum capacity problem](max_capacity_problem.assets/max_capacity_greedy_step1.png) + +=== "<2>" + ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) + +=== "<3>" + ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) + +=== "<4>" + ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) + +=== "<5>" + ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) + +=== "<6>" + ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) + +=== "<7>" + ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) + +=== "<8>" + ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) + +=== "<9>" + ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) + +### Implementation + +The code loops at most $n$ times, **thus the time complexity is $O(n)$**. + +The variables $i$, $j$, and $res$ use a constant amount of extra space, **thus the space complexity is $O(1)$**. + +```src +[file]{max_capacity}-[class]{}-[func]{max_capacity} +``` + +### Proof of correctness + +The reason why the greedy method is faster than enumeration is that each round of greedy selection "skips" some states. + +For example, under the state $cap[i, j]$ where $i$ is the shorter partition and $j$ is the taller partition, greedily moving the shorter partition $i$ inward by one step leads to the "skipped" states shown in the figure below. **This means that these states' capacities cannot be verified later**. + +$$ +cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] +$$ + +![States skipped by moving the shorter partition](max_capacity_problem.assets/max_capacity_skipped_states.png) + +It is observed that **these skipped states are actually all states where the taller partition $j$ is moved inward**. We have already proven that moving the taller partition inward will definitely decrease the capacity. Therefore, the skipped states cannot possibly be the optimal solution, **and skipping them does not lead to missing the optimal solution**. + +The analysis shows that the operation of moving the shorter partition is "safe", and the greedy strategy is effective. diff --git a/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png new file mode 100644 index 0000000000..2a5df97c8e Binary files /dev/null and b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png differ diff --git a/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png new file mode 100644 index 0000000000..244c9a32cb Binary files /dev/null and b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png differ diff --git a/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png new file mode 100644 index 0000000000..0251cb1478 Binary files /dev/null and b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png differ diff --git a/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png new file mode 100644 index 0000000000..2006934af8 Binary files /dev/null and b/en/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png differ diff --git a/en/docs/chapter_greedy/max_product_cutting_problem.md b/en/docs/chapter_greedy/max_product_cutting_problem.md new file mode 100644 index 0000000000..165af4f9f9 --- /dev/null +++ b/en/docs/chapter_greedy/max_product_cutting_problem.md @@ -0,0 +1,85 @@ +# Maximum product cutting problem + +!!! question + + Given a positive integer $n$, split it into at least two positive integers that sum up to $n$, and find the maximum product of these integers, as illustrated in the figure below. + +![Definition of the maximum product cutting problem](max_product_cutting_problem.assets/max_product_cutting_definition.png) + +Assume we split $n$ into $m$ integer factors, where the $i$-th factor is denoted as $n_i$, that is, + +$$ +n = \sum_{i=1}^{m}n_i +$$ + +The goal of this problem is to find the maximum product of all integer factors, namely, + +$$ +\max(\prod_{i=1}^{m}n_i) +$$ + +We need to consider: How large should the number of splits $m$ be, and what should each $n_i$ be? + +### Greedy strategy determination + +Experience suggests that the product of two integers is often greater than their sum. Suppose we split a factor of $2$ from $n$, then their product is $2(n-2)$. Compare this product with $n$: + +$$ +\begin{aligned} +2(n-2) & \geq n \newline +2n - n - 4 & \geq 0 \newline +n & \geq 4 +\end{aligned} +$$ + +As shown in the figure below, when $n \geq 4$, splitting out a $2$ increases the product, **which indicates that integers greater than or equal to $4$ should be split**. + +**Greedy strategy one**: If the splitting scheme includes factors $\geq 4$, they should be further split. The final split should only include factors $1$, $2$, and $3$. + +![Product increase due to splitting](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) + +Next, consider which factor is optimal. Among the factors $1$, $2$, and $3$, clearly $1$ is the worst, as $1 \times (n-1) < n$ always holds, meaning splitting out $1$ actually decreases the product. + +As shown in the figure below, when $n = 6$, $3 \times 3 > 2 \times 2 \times 2$. **This means splitting out $3$ is better than splitting out $2$**. + +**Greedy strategy two**: In the splitting scheme, there should be at most two $2$s. Because three $2$s can always be replaced by two $3$s to obtain a higher product. + +![Optimal splitting factors](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) + +From the above, the following greedy strategies can be derived. + +1. Input integer $n$, continually split out factor $3$ until the remainder is $0$, $1$, or $2$. +2. When the remainder is $0$, it means $n$ is a multiple of $3$, so no further action is taken. +3. When the remainder is $2$, do not continue to split, keep it. +4. When the remainder is $1$, since $2 \times 2 > 1 \times 3$, the last $3$ should be replaced with $2$. + +### Code implementation + +As shown in the figure below, we do not need to use loops to split the integer but can use the floor division operation to get the number of $3$s, $a$, and the modulo operation to get the remainder, $b$, thus: + +$$ +n = 3a + b +$$ + +Please note, for the boundary case where $n \leq 3$, a $1$ must be split out, with a product of $1 \times (n - 1)$. + +```src +[file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} +``` + +![Calculation method of the maximum product after cutting](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) + +**Time complexity depends on the implementation of the power operation in the programming language**. For Python, the commonly used power calculation functions are three types: + +- Both the operator `**` and the function `pow()` have a time complexity of $O(\log⁡ a)$. +- The `math.pow()` function internally calls the C language library's `pow()` function, performing floating-point exponentiation, with a time complexity of $O(1)$. + +Variables $a$ and $b$ use constant size of extra space, **hence the space complexity is $O(1)$**. + +### Correctness proof + +Using the proof by contradiction, only analyze cases where $n \geq 3$. + +1. **All factors $\leq 3$**: Assume the optimal splitting scheme includes a factor $x \geq 4$, then it can definitely be further split into $2(x-2)$, obtaining a larger product. This contradicts the assumption. +2. **The splitting scheme does not contain $1$**: Assume the optimal splitting scheme includes a factor of $1$, then it can definitely be merged into another factor to obtain a larger product. This contradicts the assumption. +3. **The splitting scheme contains at most two $2$s**: Assume the optimal splitting scheme includes three $2$s, then they can definitely be replaced by two $3$s, achieving a higher product. This contradicts the assumption. diff --git a/en/docs/chapter_greedy/summary.md b/en/docs/chapter_greedy/summary.md new file mode 100644 index 0000000000..bf71f93cdb --- /dev/null +++ b/en/docs/chapter_greedy/summary.md @@ -0,0 +1,12 @@ +# Summary + +- Greedy algorithms are often used to solve optimization problems, where the principle is to make locally optimal decisions at each decision stage in order to achieve a globally optimal solution. +- Greedy algorithms iteratively make one greedy choice after another, transforming the problem into a smaller sub-problem with each round, until the problem is resolved. +- Greedy algorithms are not only simple to implement but also have high problem-solving efficiency. Compared to dynamic programming, greedy algorithms generally have a lower time complexity. +- In the problem of coin change, greedy algorithms can guarantee the optimal solution for certain combinations of coins; for others, however, the greedy algorithm might find a very poor solution. +- Problems suitable for greedy algorithm solutions possess two main properties: greedy-choice property and optimal substructure. The greedy-choice property represents the effectiveness of the greedy strategy. +- For some complex problems, proving the greedy-choice property is not straightforward. Contrarily, proving the invalidity is often easier, such as with the coin change problem. +- Solving greedy problems mainly consists of three steps: problem analysis, determining the greedy strategy, and proving correctness. Among these, determining the greedy strategy is the key step, while proving correctness often poses the challenge. +- The fractional knapsack problem builds on the 0-1 knapsack problem by allowing the selection of a part of the items, hence it can be solved using a greedy algorithm. The correctness of the greedy strategy can be proved by contradiction. +- The maximum capacity problem can be solved using the exhaustive method, with a time complexity of $O(n^2)$. By designing a greedy strategy, each round moves inwardly shortening the board, optimizing the time complexity to $O(n)$. +- In the problem of maximum product after cutting, we deduce two greedy strategies: integers $\geq 4$ should continue to be cut, with the optimal cutting factor being $3$. The code includes power operations, and the time complexity depends on the method of implementing power operations, generally being $O(1)$ or $O(\log n)$. diff --git a/en/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png b/en/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png new file mode 100644 index 0000000000..3250199f34 Binary files /dev/null and b/en/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png differ diff --git a/en/docs/chapter_hashing/hash_algorithm.md b/en/docs/chapter_hashing/hash_algorithm.md new file mode 100644 index 0000000000..d84ce1f5e0 --- /dev/null +++ b/en/docs/chapter_hashing/hash_algorithm.md @@ -0,0 +1,366 @@ +# Hash algorithms + +The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and chaining can **only ensure that the hash table functions normally when collisions occur, but cannot reduce the frequency of hash collisions**. + +If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in the figure below, for a chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to $O(n)$. + +![Ideal and worst cases of hash collisions](hash_algorithm.assets/hash_collision_best_worst_condition.png) + +**The distribution of key-value pairs is determined by the hash function**. Recalling the steps of calculating a hash function, first compute the hash value, then modulo it by the array length: + +```shell +index = hash(key) % capacity +``` + +Observing the above formula, when the hash table capacity `capacity` is fixed, **the hash algorithm `hash()` determines the output value**, thereby determining the distribution of key-value pairs in the hash table. + +This means that, to reduce the probability of hash collisions, we should focus on the design of the hash algorithm `hash()`. + +## Goals of hash algorithms + +To achieve a "fast and stable" hash table data structure, hash algorithms should have the following characteristics: + +- **Determinism**: For the same input, the hash algorithm should always produce the same output. Only then can the hash table be reliable. +- **High efficiency**: The process of computing the hash value should be fast enough. The smaller the computational overhead, the more practical the hash table. +- **Uniform distribution**: The hash algorithm should ensure that key-value pairs are evenly distributed in the hash table. The more uniform the distribution, the lower the probability of hash collisions. + +In fact, hash algorithms are not only used to implement hash tables but are also widely applied in other fields. + +- **Password storage**: To protect the security of user passwords, systems usually do not store the plaintext passwords but rather the hash values of the passwords. When a user enters a password, the system calculates the hash value of the input and compares it with the stored hash value. If they match, the password is considered correct. +- **Data integrity check**: The data sender can calculate the hash value of the data and send it along; the receiver can recalculate the hash value of the received data and compare it with the received hash value. If they match, the data is considered intact. + +For cryptographic applications, to prevent reverse engineering such as deducing the original password from the hash value, hash algorithms need higher-level security features. + +- **Unidirectionality**: It should be impossible to deduce any information about the input data from the hash value. +- **Collision resistance**: It should be extremely difficult to find two different inputs that produce the same hash value. +- **Avalanche effect**: Minor changes in the input should lead to significant and unpredictable changes in the output. + +Note that **"Uniform Distribution" and "Collision Resistance" are two separate concepts**. Satisfying uniform distribution does not necessarily mean collision resistance. For example, under random input `key`, the hash function `key % 100` can produce a uniformly distributed output. However, this hash algorithm is too simple, and all `key` with the same last two digits will have the same output, making it easy to deduce a usable `key` from the hash value, thereby cracking the password. + +## Design of hash algorithms + +The design of hash algorithms is a complex issue that requires consideration of many factors. However, for some less demanding scenarios, we can also design some simple hash algorithms. + +- **Additive hash**: Add up the ASCII codes of each character in the input and use the total sum as the hash value. +- **Multiplicative hash**: Utilize the non-correlation of multiplication, multiplying each round by a constant, accumulating the ASCII codes of each character into the hash value. +- **XOR hash**: Accumulate the hash value by XORing each element of the input data. +- **Rotating hash**: Accumulate the ASCII code of each character into a hash value, performing a rotation operation on the hash value before each accumulation. + +```src +[file]{simple_hash}-[class]{}-[func]{rot_hash} +``` + +It is observed that the last step of each hash algorithm is to take the modulus of the large prime number $1000000007$ to ensure that the hash value is within an appropriate range. It is worth pondering why emphasis is placed on modulo a prime number, or what are the disadvantages of modulo a composite number? This is an interesting question. + +To conclude: **Using a large prime number as the modulus can maximize the uniform distribution of hash values**. Since a prime number does not share common factors with other numbers, it can reduce the periodic patterns caused by the modulo operation, thus avoiding hash collisions. + +For example, suppose we choose the composite number $9$ as the modulus, which can be divided by $3$, then all `key` divisible by $3$ will be mapped to hash values $0$, $3$, $6$. + +$$ +\begin{aligned} +\text{modulus} & = 9 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} +\end{aligned} +$$ + +If the input `key` happens to have this kind of arithmetic sequence distribution, then the hash values will cluster, thereby exacerbating hash collisions. Now, suppose we replace `modulus` with the prime number $13$, since there are no common factors between `key` and `modulus`, the uniformity of the output hash values will be significantly improved. + +$$ +\begin{aligned} +\text{modulus} & = 13 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} +\end{aligned} +$$ + +It is worth noting that if the `key` is guaranteed to be randomly and uniformly distributed, then choosing a prime number or a composite number as the modulus can both produce uniformly distributed hash values. However, when the distribution of `key` has some periodicity, modulo a composite number is more likely to result in clustering. + +In summary, we usually choose a prime number as the modulus, and this prime number should be large enough to eliminate periodic patterns as much as possible, enhancing the robustness of the hash algorithm. + +## Common hash algorithms + +It is not hard to see that the simple hash algorithms mentioned above are quite "fragile" and far from reaching the design goals of hash algorithms. For example, since addition and XOR obey the commutative law, additive hash and XOR hash cannot distinguish strings with the same content but in different order, which may exacerbate hash collisions and cause security issues. + +In practice, we usually use some standard hash algorithms, such as MD5, SHA-1, SHA-2, and SHA-3. They can map input data of any length to a fixed-length hash value. + +Over the past century, hash algorithms have been in a continuous process of upgrading and optimization. Some researchers strive to improve the performance of hash algorithms, while others, including hackers, are dedicated to finding security issues in hash algorithms. The table below shows hash algorithms commonly used in practical applications. + +- MD5 and SHA-1 have been successfully attacked multiple times and are thus abandoned in various security applications. +- SHA-2 series, especially SHA-256, is one of the most secure hash algorithms to date, with no successful attacks reported, hence commonly used in various security applications and protocols. +- SHA-3 has lower implementation costs and higher computational efficiency compared to SHA-2, but its current usage coverage is not as extensive as the SHA-2 series. + +

Table   Common hash algorithms

+ +| | MD5 | SHA-1 | SHA-2 | SHA-3 | +| --------------- | ----------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------- | ---------------------------- | +| Release Year | 1992 | 1995 | 2002 | 2008 | +| Output Length | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | +| Hash Collisions | Frequent | Frequent | Rare | Rare | +| Security Level | Low, has been successfully attacked | Low, has been successfully attacked | High | High | +| Applications | Abandoned, still used for data integrity checks | Abandoned | Cryptocurrency transaction verification, digital signatures, etc. | Can be used to replace SHA-2 | + +# Hash values in data structures + +We know that the keys in a hash table can be of various data types such as integers, decimals, or strings. Programming languages usually provide built-in hash algorithms for these data types to calculate the bucket indices in the hash table. Taking Python as an example, we can use the `hash()` function to compute the hash values for various data types. + +- The hash values of integers and booleans are their own values. +- The calculation of hash values for floating-point numbers and strings is more complex, and interested readers are encouraged to study this on their own. +- The hash value of a tuple is a combination of the hash values of each of its elements, resulting in a single hash value. +- The hash value of an object is generated based on its memory address. By overriding the hash method of an object, hash values can be generated based on content. + +!!! tip + + Be aware that the definition and methods of the built-in hash value calculation functions in different programming languages vary. + +=== "Python" + + ```python title="built_in_hash.py" + num = 3 + hash_num = hash(num) + # Hash value of integer 3 is 3 + + bol = True + hash_bol = hash(bol) + # Hash value of boolean True is 1 + + dec = 3.14159 + hash_dec = hash(dec) + # Hash value of decimal 3.14159 is 326484311674566659 + + str = "Hello 算法" + hash_str = hash(str) + # Hash value of string "Hello 算法" is 4617003410720528961 + + tup = (12836, "小哈") + hash_tup = hash(tup) + # Hash value of tuple (12836, '小哈') is 1029005403108185979 + + obj = ListNode(0) + hash_obj = hash(obj) + # Hash value of ListNode object at 0x1058fd810 is 274267521 + ``` + +=== "C++" + + ```cpp title="built_in_hash.cpp" + int num = 3; + size_t hashNum = hash()(num); + // Hash value of integer 3 is 3 + + bool bol = true; + size_t hashBol = hash()(bol); + // Hash value of boolean 1 is 1 + + double dec = 3.14159; + size_t hashDec = hash()(dec); + // Hash value of decimal 3.14159 is 4614256650576692846 + + string str = "Hello 算法"; + size_t hashStr = hash()(str); + // Hash value of string "Hello 算法" is 15466937326284535026 + + // In C++, built-in std::hash() only provides hash values for basic data types + // Hash values for arrays and objects need to be implemented separately + ``` + +=== "Java" + + ```java title="built_in_hash.java" + int num = 3; + int hashNum = Integer.hashCode(num); + // Hash value of integer 3 is 3 + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + // Hash value of boolean true is 1231 + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + // Hash value of decimal 3.14159 is -1340954729 + + String str = "Hello 算法"; + int hashStr = str.hashCode(); + // Hash value of string "Hello 算法" is -727081396 + + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + // Hash value of array [12836, 小哈] is 1151158 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + // Hash value of ListNode object utils.ListNode@7dc5e7b4 is 2110121908 + ``` + +=== "C#" + + ```csharp title="built_in_hash.cs" + int num = 3; + int hashNum = num.GetHashCode(); + // Hash value of integer 3 is 3; + + bool bol = true; + int hashBol = bol.GetHashCode(); + // Hash value of boolean true is 1; + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + // Hash value of decimal 3.14159 is -1340954729; + + string str = "Hello 算法"; + int hashStr = str.GetHashCode(); + // Hash value of string "Hello 算法" is -586107568; + + object[] arr = [12836, "小哈"]; + int hashTup = arr.GetHashCode(); + // Hash value of array [12836, 小哈] is 42931033; + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + // Hash value of ListNode object 0 is 39053774; + ``` + +=== "Go" + + ```go title="built_in_hash.go" + // Go does not provide built-in hash code functions + ``` + +=== "Swift" + + ```swift title="built_in_hash.swift" + let num = 3 + let hashNum = num.hashValue + // Hash value of integer 3 is 9047044699613009734 + + let bol = true + let hashBol = bol.hashValue + // Hash value of boolean true is -4431640247352757451 + + let dec = 3.14159 + let hashDec = dec.hashValue + // Hash value of decimal 3.14159 is -2465384235396674631 + + let str = "Hello 算法" + let hashStr = str.hashValue + // Hash value of string "Hello 算法" is -7850626797806988787 + + let arr = [AnyHashable(12836), AnyHashable("小哈")] + let hashTup = arr.hashValue + // Hash value of array [AnyHashable(12836), AnyHashable("小哈")] is -2308633508154532996 + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + // Hash value of ListNode object utils.ListNode is -2434780518035996159 + ``` + +=== "JS" + + ```javascript title="built_in_hash.js" + // JavaScript does not provide built-in hash code functions + ``` + +=== "TS" + + ```typescript title="built_in_hash.ts" + // TypeScript does not provide built-in hash code functions + ``` + +=== "Dart" + + ```dart title="built_in_hash.dart" + int num = 3; + int hashNum = num.hashCode; + // Hash value of integer 3 is 34803 + + bool bol = true; + int hashBol = bol.hashCode; + // Hash value of boolean true is 1231 + + double dec = 3.14159; + int hashDec = dec.hashCode; + // Hash value of decimal 3.14159 is 2570631074981783 + + String str = "Hello 算法"; + int hashStr = str.hashCode; + // Hash value of string "Hello 算法" is 468167534 + + List arr = [12836, "小哈"]; + int hashArr = arr.hashCode; + // Hash value of array [12836, 小哈] is 976512528 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + // Hash value of ListNode object Instance of 'ListNode' is 1033450432 + ``` + +=== "Rust" + + ```rust title="built_in_hash.rs" + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + // Hash value of integer 3 is 568126464209439262 + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + // Hash value of boolean true is 4952851536318644461 + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + // Hash value of decimal 3.14159 is 2566941990314602357 + + let str = "Hello 算法"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + // Hash value of string "Hello 算法" is 16092673739211250988 + + let arr = (&12836, &"小哈"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + // Hash value of tuple (12836, "小哈") is 1885128010422702749 + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + // Hash value of ListNode object RefCell { value: ListNode { val: 42, next: None } } is 15387811073369036852 + ``` + +=== "C" + + ```c title="built_in_hash.c" + // C does not provide built-in hash code functions + ``` + +=== "Kotlin" + + ```kotlin title="built_in_hash.kt" + + ``` + +=== "Zig" + + ```zig title="built_in_hash.zig" + + ``` + +??? pythontutor "Code Visualization" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +In many programming languages, **only immutable objects can serve as the `key` in a hash table**. If we use a list (dynamic array) as a `key`, when the contents of the list change, its hash value also changes, and we would no longer be able to find the original `value` in the hash table. + +Although the member variables of a custom object (such as a linked list node) are mutable, it is hashable. **This is because the hash value of an object is usually generated based on its memory address**, and even if the contents of the object change, the memory address remains the same, so the hash value remains unchanged. + +You might have noticed that the hash values output in different consoles are different. **This is because the Python interpreter adds a random salt to the string hash function each time it starts up**. This approach effectively prevents HashDoS attacks and enhances the security of the hash algorithm. diff --git a/en/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png b/en/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png new file mode 100644 index 0000000000..e99da4f7a6 Binary files /dev/null and b/en/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png differ diff --git a/en/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png b/en/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png new file mode 100644 index 0000000000..473f877fbd Binary files /dev/null and b/en/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png differ diff --git a/en/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png b/en/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png new file mode 100644 index 0000000000..f2fb0fafbb Binary files /dev/null and b/en/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png differ diff --git a/en/docs/chapter_hashing/hash_collision.md b/en/docs/chapter_hashing/hash_collision.md new file mode 100644 index 0000000000..7d10b5bade --- /dev/null +++ b/en/docs/chapter_hashing/hash_collision.md @@ -0,0 +1,108 @@ +# Hash collision + +The previous section mentioned that, **in most cases, the input space of a hash function is much larger than the output space**, so theoretically, hash collisions are inevitable. For example, if the input space is all integers and the output space is the size of the array capacity, then multiple integers will inevitably be mapped to the same bucket index. + +Hash collisions can lead to incorrect query results, severely impacting the usability of the hash table. To address this issue, whenever a hash collision occurs, we perform hash table resizing until the collision disappears. This approach is pretty simple, straightforward, and working well. However, it appears to be pretty inefficient as the table expansion involves a lot of data migration as well as recalculation of hash code, which are expansive. To improve efficiency, we can adopt the following strategies: + +1. Improve the hash table data structure in a way that **locating target element is still functioning well in the event of a hash collision**. +2. Expansion is the last resort before it becomes necessary, when severe collisions are observed. + +There are mainly two methods for improving the structure of hash tables: "Separate Chaining" and "Open Addressing". + +## Separate chaining + +In the original hash table, each bucket can store only one key-value pair. Separate chaining converts a single element into a linked list, treating key-value pairs as list nodes, storing all colliding key-value pairs in the same linked list. The figure below shows an example of a hash table with separate chaining. + +![Separate chaining hash table](hash_collision.assets/hash_table_chaining.png) + +The operations of a hash table implemented with separate chaining have changed as follows: + +- **Querying Elements**: Input `key`, obtain the bucket index through the hash function, then access the head node of the linked list. Traverse the linked list and compare key to find the target key-value pair. +- **Adding Elements**: Access the head node of the linked list via the hash function, then append the node (key-value pair) to the list. +- **Deleting Elements**: Access the head of the linked list based on the result of the hash function, then traverse the linked list to find the target node and delete it. + +Separate chaining has the following limitations: + +- **Increased Space Usage**: The linked list contains node pointers, which consume more memory space than arrays. +- **Reduced Query Efficiency**: This is because linear traversal of the linked list is required to find the corresponding element. + +The code below provides a simple implementation of a separate chaining hash table, with two things to note: + +- Lists (dynamic arrays) are used instead of linked lists for simplicity. In this setup, the hash table (array) contains multiple buckets, each of which is a list. +- This implementation includes a hash table resizing method. When the load factor exceeds $\frac{2}{3}$, we expand the hash table to twice its original size. + +```src +[file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} +``` + +It's worth noting that when the linked list is very long, the query efficiency $O(n)$ is poor. **In this case, the list can be converted to an "AVL tree" or "Red-Black tree"** to optimize the time complexity of the query operation to $O(\log n)$. + +## Open addressing + +Open addressing does not introduce additional data structures but instead handles hash collisions through "multiple probing". The probing methods mainly include linear probing, quadratic probing, and double hashing. + +Let's use linear probing as an example to introduce the mechanism of open addressing hash tables. + +### Linear probing + +Linear probing uses a fixed-step linear search for probing, differing from ordinary hash tables. + +- **Inserting Elements**: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of $1$) until an empty bucket is found, then insert the element. +- **Searching for Elements**: If a hash collision is encountered, use the same step size to linearly traverse forward until the corresponding element is found and return `value`; if an empty bucket is encountered, it means the target element is not in the hash table, so return `None`. + +The figure below shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored sequentially in that bucket and the buckets below it. + +![Distribution of key-value pairs in open addressing (linear probing) hash table](hash_collision.assets/hash_table_linear_probing.png) + +However, **linear probing is prone to create "clustering"**. Specifically, the longer the continuously occupied positions in the array, the greater the probability of hash collisions occurring in these continuous positions, further promoting the growth of clustering at that position, forming a vicious cycle, and ultimately leading to degraded efficiency of insertion, deletion, query, and update operations. + +It's important to note that **we cannot directly delete elements in an open addressing hash table**. Deleting an element creates an empty bucket `None` in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in the figure below. + +![Query issues caused by deletion in open addressing](hash_collision.assets/hash_table_open_addressing_deletion.png) + +To solve this problem, we can adopt the lazy deletion mechanism: instead of directly removing elements from the hash table, **use a constant `TOMBSTONE` to mark the bucket**. In this mechanism, both `None` and `TOMBSTONE` represent empty buckets and can hold key-value pairs. However, when linear probing encounters `TOMBSTONE`, it should continue traversing since there may still be key-value pairs below it. + +However, **lazy deletion may accelerate the performance degradation of the hash table**. Every deletion operation produces a delete mark, and as `TOMBSTONE` increases, the search time will also increase because linear probing may need to skip multiple `TOMBSTONE` to find the target element. + +To address this, consider recording the index of the first encountered `TOMBSTONE` during linear probing and swapping the positions of the searched target element with that `TOMBSTONE`. The benefit of doing this is that each time an element is queried or added, the element will be moved to a bucket closer to its ideal position (the starting point of probing), thereby optimizing query efficiency. + +The code below implements an open addressing (linear probing) hash table with lazy deletion. To make better use of the hash table space, we treat the hash table as a "circular array,". When going beyond the end of the array, we return to the beginning and continue traversing. + +```src +[file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} +``` + +### Quadratic probing + +Quadratic probing is similar to linear probing and is one of the common strategies of open addressing. When a collision occurs, quadratic probing does not simply skip a fixed number of steps but skips a number of steps equal to the "square of the number of probes", i.e., $1, 4, 9, \dots$ steps. + +Quadratic probing has the following advantages: + +- Quadratic probing attempts to alleviate the clustering effect of linear probing by skipping the distance of the square of the number of probes. +- Quadratic probing skips larger distances to find empty positions, which helps to distribute data more evenly. + +However, quadratic probing is not perfect: + +- Clustering still exists, i.e., some positions are more likely to be occupied than others. +- Due to the growth of squares, quadratic probing may not probe the entire hash table, meaning that even if there are empty buckets in the hash table, quadratic probing may not be able to access them. + +### Double hashing + +As the name suggests, the double hashing method uses multiple hash functions $f_1(x)$, $f_2(x)$, $f_3(x)$, $\dots$ for probing. + +- **Inserting Elements**: If hash function $f_1(x)$ encounters a conflict, it tries $f_2(x)$, and so on, until an empty position is found and the element is inserted. +- **Searching for Elements**: Search in the same order of hash functions until the target element is found and returned; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return `None`. + +Compared to linear probing, the double hashing method is less prone to clustering, but multiple hash functions introduce additional computational overhead. + +!!! tip + + Please note that open addressing (linear probing, quadratic probing, and double hashing) hash tables all have the problem of "can not directly delete elements." + +## Choice of programming languages + +Different programming languages adopt different hash table implementation strategies. Here are a few examples: + +- Python uses open addressing. The `dict` dictionary uses pseudo-random numbers for probing. +- Java uses separate chaining. Since JDK 1.8, when the array length in `HashMap` reaches 64 and the length of a linked list reaches 8, the linked list is converted to a red-black tree to improve search performance. +- Go uses separate chaining. Go stipulates that each bucket can store up to 8 key-value pairs, and if the capacity is exceeded, an overflow bucket is linked; when there are too many overflow buckets, a special equal-capacity resizing operation is performed to ensure performance. diff --git a/en/docs/chapter_hashing/hash_map.assets/hash_collision.png b/en/docs/chapter_hashing/hash_map.assets/hash_collision.png new file mode 100644 index 0000000000..a57bbe2264 Binary files /dev/null and b/en/docs/chapter_hashing/hash_map.assets/hash_collision.png differ diff --git a/en/docs/chapter_hashing/hash_map.assets/hash_function.png b/en/docs/chapter_hashing/hash_map.assets/hash_function.png new file mode 100644 index 0000000000..b6377e6fba Binary files /dev/null and b/en/docs/chapter_hashing/hash_map.assets/hash_function.png differ diff --git a/en/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png b/en/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png new file mode 100644 index 0000000000..0656e6a82b Binary files /dev/null and b/en/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png differ diff --git a/en/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png b/en/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png new file mode 100644 index 0000000000..2586a54103 Binary files /dev/null and b/en/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png differ diff --git a/en/docs/chapter_hashing/hash_map.md b/en/docs/chapter_hashing/hash_map.md new file mode 100755 index 0000000000..76576648cf --- /dev/null +++ b/en/docs/chapter_hashing/hash_map.md @@ -0,0 +1,537 @@ +# Hash table + +A hash table, also known as a hash map, is a data structure that establishes a mapping between keys and values, enabling efficient element retrieval. Specifically, when we input a `key` into the hash table, we can retrieve the corresponding `value` in $O(1)$ time complexity. + +As shown in the figure below, given $n$ students, each student has two data fields: "Name" and "Student ID". If we want to implement a query function that takes a student ID as input and returns the corresponding name, we can use the hash table shown in the figure below. + +![Abstract representation of a hash table](hash_map.assets/hash_table_lookup.png) + +In addition to hash tables, arrays and linked lists can also be used to implement query functionality, but the time complexity is different. Their efficiency is compared in the table below: + +- **Inserting an element**: Simply append the element to the tail of the array (or linked list). The time complexity of this operation is $O(1)$. +- **Searching for an element**: As the array (or linked list) is unsorted, searching for an element requires traversing through all of the elements. The time complexity of this operation is $O(n)$. +- **Deleting an element**: To remove an element, we first need to locate it. Then, we delete it from the array (or linked list). The time complexity of this operation is $O(n)$. + +

Table   Comparison of time efficiency for common operations

+ +| | Array | Linked List | Hash Table | +| -------------- | ------ | ----------- | ---------- | +| Search Elements | $O(n)$ | $O(n)$ | $O(1)$ | +| Insert Elements | $O(1)$ | $O(1)$ | $O(1)$ | +| Delete Elements | $O(n)$ | $O(n)$ | $O(1)$ | + +As observed, **the time complexity for operations (insertion, deletion, searching, and modification) in a hash table is $O(1)$**, which is highly efficient. + +## Common operations of hash table + +Common operations of a hash table include: initialization, querying, adding key-value pairs, and deleting key-value pairs. Here is an example code: + +=== "Python" + + ```python title="hash_map.py" + # Initialize hash table + hmap: dict = {} + + # Add operation + # Add key-value pair (key, value) to the hash table + hmap[12836] = "Xiao Ha" + hmap[15937] = "Xiao Luo" + hmap[16750] = "Xiao Suan" + hmap[13276] = "Xiao Fa" + hmap[10583] = "Xiao Ya" + + # Query operation + # Input key into hash table, get value + name: str = hmap[15937] + + # Delete operation + # Delete key-value pair (key, value) from hash table + hmap.pop(10583) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* Initialize hash table */ + unordered_map map; + + /* Add operation */ + // Add key-value pair (key, value) to hash table + map[12836] = "Xiao Ha"; + map[15937] = "Xiao Luo"; + map[16750] = "Xiao Suan"; + map[13276] = "Xiao Fa"; + map[10583] = "Xiao Ya"; + + /* Query operation */ + // Input key into hash table, get value + string name = map[15937]; + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + map.erase(10583); + ``` + +=== "Java" + + ```java title="hash_map.java" + /* Initialize hash table */ + Map map = new HashMap<>(); + + /* Add operation */ + // Add key-value pair (key, value) to hash table + map.put(12836, "Xiao Ha"); + map.put(15937, "Xiao Luo"); + map.put(16750, "Xiao Suan"); + map.put(13276, "Xiao Fa"); + map.put(10583, "Xiao Ya"); + + /* Query operation */ + // Input key into hash table, get value + String name = map.get(15937); + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + map.remove(10583); + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* Initialize hash table */ + Dictionary map = new() { + /* Add operation */ + // Add key-value pair (key, value) to hash table + { 12836, "Xiao Ha" }, + { 15937, "Xiao Luo" }, + { 16750, "Xiao Suan" }, + { 13276, "Xiao Fa" }, + { 10583, "Xiao Ya" } + }; + + /* Query operation */ + // Input key into hash table, get value + string name = map[15937]; + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + map.Remove(10583); + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* Initialize hash table */ + hmap := make(map[int]string) + + /* Add operation */ + // Add key-value pair (key, value) to hash table + hmap[12836] = "Xiao Ha" + hmap[15937] = "Xiao Luo" + hmap[16750] = "Xiao Suan" + hmap[13276] = "Xiao Fa" + hmap[10583] = "Xiao Ya" + + /* Query operation */ + // Input key into hash table, get value + name := hmap[15937] + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + delete(hmap, 10583) + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* Initialize hash table */ + var map: [Int: String] = [:] + + /* Add operation */ + // Add key-value pair (key, value) to hash table + map[12836] = "Xiao Ha" + map[15937] = "Xiao Luo" + map[16750] = "Xiao Suan" + map[13276] = "Xiao Fa" + map[10583] = "Xiao Ya" + + /* Query operation */ + // Input key into hash table, get value + let name = map[15937]! + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + map.removeValue(forKey: 10583) + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* Initialize hash table */ + const map = new Map(); + /* Add operation */ + // Add key-value pair (key, value) to the hash table + map.set(12836, 'Xiao Ha'); + map.set(15937, 'Xiao Luo'); + map.set(16750, 'Xiao Suan'); + map.set(13276, 'Xiao Fa'); + map.set(10583, 'Xiao Ya'); + + /* Query operation */ + // Input key into hash table, get value + let name = map.get(15937); + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + map.delete(10583); + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* Initialize hash table */ + const map = new Map(); + /* Add operation */ + // Add key-value pair (key, value) to hash table + map.set(12836, 'Xiao Ha'); + map.set(15937, 'Xiao Luo'); + map.set(16750, 'Xiao Suan'); + map.set(13276, 'Xiao Fa'); + map.set(10583, 'Xiao Ya'); + console.info('\nAfter adding, the hash table is\nKey -> Value'); + console.info(map); + + /* Query operation */ + // Input key into hash table, get value + let name = map.get(15937); + console.info('\nInput student number 15937, query name ' + name); + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + map.delete(10583); + console.info('\nAfter deleting 10583, the hash table is\nKey -> Value'); + console.info(map); + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* Initialize hash table */ + Map map = {}; + + /* Add operation */ + // Add key-value pair (key, value) to hash table + map[12836] = "Xiao Ha"; + map[15937] = "Xiao Luo"; + map[16750] = "Xiao Suan"; + map[13276] = "Xiao Fa"; + map[10583] = "Xiao Ya"; + + /* Query operation */ + // Input key into hash table, get value + String name = map[15937]; + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + map.remove(10583); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + use std::collections::HashMap; + + /* Initialize hash table */ + let mut map: HashMap = HashMap::new(); + + /* Add operation */ + // Add key-value pair (key, value) to hash table + map.insert(12836, "Xiao Ha".to_string()); + map.insert(15937, "Xiao Luo".to_string()); + map.insert(16750, "Xiao Suan".to_string()); + map.insert(13279, "Xiao Fa".to_string()); + map.insert(10583, "Xiao Ya".to_string()); + + /* Query operation */ + // Input key into hash table, get value + let _name: Option<&String> = map.get(&15937); + + /* Delete operation */ + // Delete key-value pair (key, value) from hash table + let _removed_value: Option = map.remove(&10583); + ``` + +=== "C" + + ```c title="hash_map.c" + // C does not provide a built-in hash table + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + +??? pythontutor "Code Visualization" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +There are three common ways to traverse a hash table: traversing key-value pairs, traversing keys, and traversing values. Here is an example code: + +=== "Python" + + ```python title="hash_map.py" + # Traverse hash table + # Traverse key-value pairs key->value + for key, value in hmap.items(): + print(key, "->", value) + # Traverse keys only + for key in hmap.keys(): + print(key) + # Traverse values only + for value in hmap.values(): + print(value) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* Traverse hash table */ + // Traverse key-value pairs key->value + for (auto kv: map) { + cout << kv.first << " -> " << kv.second << endl; + } + // Traverse using iterator key->value + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + ``` + +=== "Java" + + ```java title="hash_map.java" + /* Traverse hash table */ + // Traverse key-value pairs key->value + for (Map.Entry kv: map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + // Traverse keys only + for (int key: map.keySet()) { + System.out.println(key); + } + // Traverse values only + for (String val: map.values()) { + System.out.println(val); + } + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* Traverse hash table */ + // Traverse key-value pairs Key->Value + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + // Traverse keys only + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + // Traverse values only + foreach (string val in map.Values) { + Console.WriteLine(val); + } + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* Traverse hash table */ + // Traverse key-value pairs key->value + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // Traverse keys only + for key := range hmap { + fmt.Println(key) + } + // Traverse values only + for _, value := range hmap { + fmt.Println(value) + } + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* Traverse hash table */ + // Traverse key-value pairs Key->Value + for (key, value) in map { + print("\(key) -> \(value)") + } + // Traverse keys only + for key in map.keys { + print(key) + } + // Traverse values only + for value in map.values { + print(value) + } + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* Traverse hash table */ + console.info('\nTraverse key-value pairs Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\nTraverse keys only Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\nTraverse values only Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* Traverse hash table */ + console.info('\nTraverse key-value pairs Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\nTraverse keys only Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\nTraverse values only Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* Traverse hash table */ + // Traverse key-value pairs Key->Value + map.forEach((key, value) { + print('$key -> $value'); + }); + + // Traverse keys only Key + map.keys.forEach((key) { + print(key); + }); + + // Traverse values only Value + map.values.forEach((value) { + print(value); + }); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + /* Traverse hash table */ + // Traverse key-value pairs Key->Value + for (key, value) in &map { + println!("{key} -> {value}"); + } + + // Traverse keys only Key + for key in map.keys() { + println!("{key}"); + } + + // Traverse values only Value + for value in map.values() { + println!("{value}"); + } + ``` + +=== "C" + + ```c title="hash_map.c" + // C does not provide a built-in hash table + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + // Zig example is not provided + ``` + +??? pythontutor "Code Visualization" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Simple implementation of a hash table + +First, let's consider the simplest case: **implementing a hash table using only one array**. In the hash table, each empty slot in the array is called a bucket, and each bucket can store a key-value pair. Therefore, the query operation involves finding the bucket corresponding to the `key` and retrieving the `value` from it. + +So, how do we locate the corresponding bucket based on the `key`? This is achieved through a hash function. The role of the hash function is to map a larger input space to a smaller output space. In a hash table, the input space consists of all the keys, and the output space consists of all the buckets (array indices). In other words, given a `key`, **we can use the hash function to determine the storage location of the corresponding key-value pair in the array**. + +With a given `key`, the calculation of the hash function consists of two steps: + +1. Calculate the hash value by using a certain hash algorithm `hash()`. +2. Take the modulus of the hash value with the bucket count (array length) `capacity` to obtain the array `index` corresponding to the key. + +```shell +index = hash(key) % capacity +``` + +Afterward, we can use the `index` to access the corresponding bucket in the hash table and thereby retrieve the `value`. + +Let's assume that the array length is `capacity = 100`, and the hash algorithm is defined as `hash(key) = key`. Therefore, the hash function can be expressed as `key % 100`. The following figure illustrates the working principle of the hash function using `key` as student ID and `value` as name. + +![Working principle of hash function](hash_map.assets/hash_function.png) + +The following code implements a simple hash table. Here, we encapsulate `key` and `value` into a class `Pair` to represent the key-value pair. + +```src +[file]{array_hash_map}-[class]{array_hash_map}-[func]{} +``` + +## Hash collision and resizing + +Essentially, the role of the hash function is to map the entire input space of all keys to the output space of all array indices. However, the input space is often much larger than the output space. Therefore, **theoretically, there will always be cases where "multiple inputs correspond to the same output"**. + +In the example above, with the given hash function, when the last two digits of the input `key` are the same, the hash function produces the same output. For instance, when querying two students with student IDs 12836 and 20336, we find: + +```shell +12836 % 100 = 36 +20336 % 100 = 36 +``` + +As shown in the figure below, both student IDs point to the same name, which is obviously incorrect. This situation where multiple inputs correspond to the same output is called hash collision. + +![Example of hash collision](hash_map.assets/hash_collision.png) + +It is easy to understand that as the capacity $n$ of the hash table increases, the probability of multiple keys being assigned to the same bucket decreases, resulting in fewer collisions. Therefore, **we can reduce hash collisions by resizing the hash table**. + +As shown in the figure below, before resizing, the key-value pairs `(136, A)` and `(236, D)` collide. However, after resizing, the collision is resolved. + +![Hash table resizing](hash_map.assets/hash_table_reshash.png) + +Similar to array expansion, resizing a hash table requires migrating all key-value pairs from the original hash table to the new one, which is time-consuming. Furthermore, since the `capacity` of the hash table changes, we need to recalculate the storage positions of all key-value pairs using the hash function, further increasing the computational overhead of the resizing process. Therefore, programming languages often allocate a sufficiently large capacity for the hash table to prevent frequent resizing. + +The load factor is an important concept in hash tables. It is defined as the ratio of the number of elements in the hash table to the number of buckets. It is used to measure the severity of hash collisions and **often serves as a trigger for hash table resizing**. For example, in Java, when the load factor exceeds $0.75$, the system will resize the hash table to twice its original size. diff --git a/en/docs/chapter_hashing/index.md b/en/docs/chapter_hashing/index.md new file mode 100644 index 0000000000..1090ec0530 --- /dev/null +++ b/en/docs/chapter_hashing/index.md @@ -0,0 +1,9 @@ +# Hash table + +![Hash table](../assets/covers/chapter_hashing.jpg) + +!!! abstract + + In the world of computing, a hash table is akin to an intelligent librarian. + + It understands how to compute index numbers, enabling swift retrieval of the desired book. diff --git a/en/docs/chapter_hashing/summary.md b/en/docs/chapter_hashing/summary.md new file mode 100644 index 0000000000..21aaf97f5d --- /dev/null +++ b/en/docs/chapter_hashing/summary.md @@ -0,0 +1,47 @@ +# Summary + +### Key review + +- Given an input `key`, a hash table can retrieve the corresponding `value` in $O(1)$ time, which is highly efficient. +- Common hash table operations include querying, adding key-value pairs, deleting key-value pairs, and traversing the hash table. +- The hash function maps a `key` to an array index, allowing access to the corresponding bucket and retrieval of the `value`. +- Two different keys may end up with the same array index after hashing, leading to erroneous query results. This phenomenon is known as hash collision. +- The larger the capacity of the hash table, the lower the probability of hash collisions. Therefore, hash table resizing can mitigate hash collisions. Similar to array resizing, hash table resizing is costly. +- The load factor, defined as the number of elements divided by the number of buckets, reflects the severity of hash collisions and is often used as a condition to trigger hash table resizing. +- Chaining addresses hash collisions by converting each element into a linked list, storing all colliding elements in the same list. However, excessively long lists can reduce query efficiency, which can be improved by converting the lists into red-black trees. +- Open addressing handles hash collisions through multiple probes. Linear probing uses a fixed step size but it cannot delete elements and is prone to clustering. Multiple hashing uses several hash functions for probing which reduces clustering compared to linear probing but increases computational overhead. +- Different programming languages adopt various hash table implementations. For example, Java's `HashMap` uses chaining, while Python's `dict` employs open addressing. +- In hash tables, we desire hash algorithms with determinism, high efficiency, and uniform distribution. In cryptography, hash algorithms should also possess collision resistance and the avalanche effect. +- Hash algorithms typically use large prime numbers as moduli to ensure uniform distribution of hash values and reduce hash collisions. +- Common hash algorithms include MD5, SHA-1, SHA-2, and SHA-3. MD5 is often used for file integrity checks, while SHA-2 is commonly used in secure applications and protocols. +- Programming languages usually provide built-in hash algorithms for data types to calculate bucket indices in hash tables. Generally, only immutable objects are hashable. + +### Q & A + +**Q**: When does the time complexity of a hash table degrade to $O(n)$? + +The time complexity of a hash table can degrade to $O(n)$ when hash collisions are severe. When the hash function is well-designed, the capacity is set appropriately, and collisions are evenly distributed, the time complexity is $O(1)$. We usually consider the time complexity to be $O(1)$ when using built-in hash tables in programming languages. + +**Q**: Why not use the hash function $f(x) = x$? This would eliminate collisions. + +Under the hash function $f(x) = x$, each element corresponds to a unique bucket index, which is equivalent to an array. However, the input space is usually much larger than the output space (array length), so the last step of a hash function is often to take the modulo of the array length. In other words, the goal of a hash table is to map a larger state space to a smaller one while providing $O(1)$ query efficiency. + +**Q**: Why can hash tables be more efficient than arrays, linked lists, or binary trees, even though hash tables are implemented using these structures? + +Firstly, hash tables have higher time efficiency but lower space efficiency. A significant portion of memory in hash tables remains unused. + +Secondly, hash tables are only more time-efficient in specific use cases. If a feature can be implemented with the same time complexity using an array or a linked list, it's usually faster than using a hash table. This is because the computation of the hash function incurs overhead, making the constant factor in the time complexity larger. + +Lastly, the time complexity of hash tables can degrade. For example, in chaining, we perform search operations in a linked list or red-black tree, which still risks degrading to $O(n)$ time. + +**Q**: Does multiple hashing also have the flaw of not being able to delete elements directly? Can space marked as deleted be reused? + +Multiple hashing is a form of open addressing, and all open addressing methods have the drawback of not being able to delete elements directly; they require marking elements as deleted. Marked spaces can be reused. When inserting new elements into the hash table, and the hash function points to a position marked as deleted, that position can be used by the new element. This maintains the probing sequence of the hash table while ensuring efficient use of space. + +**Q**: Why do hash collisions occur during the search process in linear probing? + +During the search process, the hash function points to the corresponding bucket and key-value pair. If the `key` doesn't match, it indicates a hash collision. Therefore, linear probing will search downwards at a predetermined step size until the correct key-value pair is found or the search fails. + +**Q**: Why can resizing a hash table alleviate hash collisions? + +The last step of a hash function often involves taking the modulo of the array length $n$, to keep the output within the array index range. When resizing, the array length $n$ changes, and the indices corresponding to the keys may also change. Keys that were previously mapped to the same bucket might be distributed across multiple buckets after resizing, thereby mitigating hash collisions. diff --git a/en/docs/chapter_heap/build_heap.assets/heapify_operations_count.png b/en/docs/chapter_heap/build_heap.assets/heapify_operations_count.png new file mode 100644 index 0000000000..c5f93069f8 Binary files /dev/null and b/en/docs/chapter_heap/build_heap.assets/heapify_operations_count.png differ diff --git a/en/docs/chapter_heap/build_heap.md b/en/docs/chapter_heap/build_heap.md new file mode 100644 index 0000000000..b66f15e830 --- /dev/null +++ b/en/docs/chapter_heap/build_heap.md @@ -0,0 +1,74 @@ +# Heap construction operation + +In some cases, we want to build a heap using all elements of a list, and this process is known as "heap construction operation." + +## Implementing with heap insertion operation + +First, we create an empty heap and then iterate through the list, performing the "heap insertion operation" on each element in turn. This means adding the element to the end of the heap and then "heapifying" it from bottom to top. + +Each time an element is added to the heap, the length of the heap increases by one. Since nodes are added to the binary tree from top to bottom, the heap is constructed "from top to bottom." + +Let the number of elements be $n$, and each element's insertion operation takes $O(\log{n})$ time, thus the time complexity of this heap construction method is $O(n \log n)$. + +## Implementing by heapifying through traversal + +In fact, we can implement a more efficient method of heap construction in two steps. + +1. Add all elements of the list as they are into the heap, at this point the properties of the heap are not yet satisfied. +2. Traverse the heap in reverse order (reverse of level-order traversal), and perform "top to bottom heapify" on each non-leaf node. + +**After heapifying a node, the subtree with that node as the root becomes a valid sub-heap**. Since the traversal is in reverse order, the heap is built "from bottom to top." + +The reason for choosing reverse traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective. + +It's worth mentioning that **since leaf nodes have no children, they naturally form valid sub-heaps and do not need to be heapified**. As shown in the following code, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{__init__} +``` + +## Complexity analysis + +Next, let's attempt to calculate the time complexity of this second method of heap construction. + +- Assuming the number of nodes in the complete binary tree is $n$, then the number of leaf nodes is $(n + 1) / 2$, where $/$ is integer division. Therefore, the number of nodes that need to be heapified is $(n - 1) / 2$. +- In the process of "top to bottom heapification," each node is heapified to the leaf nodes at most, so the maximum number of iterations is the height of the binary tree $\log n$. + +Multiplying the two, we get the time complexity of the heap construction process as $O(n \log n)$. **But this estimate is not accurate, because it does not take into account the nature of the binary tree having far more nodes at the lower levels than at the top.** + +Let's perform a more accurate calculation. To simplify the calculation, assume a "perfect binary tree" with $n$ nodes and height $h$; this assumption does not affect the correctness of the result. + +![Node counts at each level of a perfect binary tree](build_heap.assets/heapify_operations_count.png) + +As shown in the figure above, the maximum number of iterations for a node "to be heapified from top to bottom" is equal to the distance from that node to the leaf nodes, which is precisely "node height." Therefore, we can sum the "number of nodes $\times$ node height" at each level, **to get the total number of heapification iterations for all nodes**. + +$$ +T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 +$$ + +To simplify the above equation, we need to use knowledge of sequences from high school, first multiply $T(h)$ by $2$, to get: + +$$ +\begin{aligned} +T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline +2T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^h\times1 \newline +\end{aligned} +$$ + +By subtracting $T(h)$ from $2T(h)$ using the method of displacement, we get: + +$$ +2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h +$$ + +Observing the equation, $T(h)$ is an geometric series, which can be directly calculated using the sum formula, resulting in a time complexity of: + +$$ +\begin{aligned} +T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline +& = 2^{h+1} - h - 2 \newline +& = O(2^h) +\end{aligned} +$$ + +Further, a perfect binary tree with height $h$ has $n = 2^{h+1} - 1$ nodes, thus the complexity is $O(2^h) = O(n)$. This calculation shows that **the time complexity of inputting a list and constructing a heap is $O(n)$, which is very efficient**. diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step1.png b/en/docs/chapter_heap/heap.assets/heap_pop_step1.png new file mode 100644 index 0000000000..1ea61b55e0 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step1.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step10.png b/en/docs/chapter_heap/heap.assets/heap_pop_step10.png new file mode 100644 index 0000000000..f26eba0188 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step10.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step2.png b/en/docs/chapter_heap/heap.assets/heap_pop_step2.png new file mode 100644 index 0000000000..4fe8aedf14 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step2.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step3.png b/en/docs/chapter_heap/heap.assets/heap_pop_step3.png new file mode 100644 index 0000000000..48bfb7322b Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step3.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step4.png b/en/docs/chapter_heap/heap.assets/heap_pop_step4.png new file mode 100644 index 0000000000..fca547d727 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step4.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step5.png b/en/docs/chapter_heap/heap.assets/heap_pop_step5.png new file mode 100644 index 0000000000..6ee7cddd06 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step5.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step6.png b/en/docs/chapter_heap/heap.assets/heap_pop_step6.png new file mode 100644 index 0000000000..1e98b949ed Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step6.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step7.png b/en/docs/chapter_heap/heap.assets/heap_pop_step7.png new file mode 100644 index 0000000000..faf5d8b9cf Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step7.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step8.png b/en/docs/chapter_heap/heap.assets/heap_pop_step8.png new file mode 100644 index 0000000000..003a0552c1 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step8.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_pop_step9.png b/en/docs/chapter_heap/heap.assets/heap_pop_step9.png new file mode 100644 index 0000000000..5129a49b84 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_pop_step9.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step1.png b/en/docs/chapter_heap/heap.assets/heap_push_step1.png new file mode 100644 index 0000000000..03fed3b6c9 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step1.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step2.png b/en/docs/chapter_heap/heap.assets/heap_push_step2.png new file mode 100644 index 0000000000..3d41cfee04 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step2.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step3.png b/en/docs/chapter_heap/heap.assets/heap_push_step3.png new file mode 100644 index 0000000000..cc2f5212cc Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step3.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step4.png b/en/docs/chapter_heap/heap.assets/heap_push_step4.png new file mode 100644 index 0000000000..048764e16a Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step4.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step5.png b/en/docs/chapter_heap/heap.assets/heap_push_step5.png new file mode 100644 index 0000000000..7e90e461d9 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step5.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step6.png b/en/docs/chapter_heap/heap.assets/heap_push_step6.png new file mode 100644 index 0000000000..dcd9c49b3d Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step6.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step7.png b/en/docs/chapter_heap/heap.assets/heap_push_step7.png new file mode 100644 index 0000000000..35697ffbf8 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step7.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step8.png b/en/docs/chapter_heap/heap.assets/heap_push_step8.png new file mode 100644 index 0000000000..2044132df3 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step8.png differ diff --git a/en/docs/chapter_heap/heap.assets/heap_push_step9.png b/en/docs/chapter_heap/heap.assets/heap_push_step9.png new file mode 100644 index 0000000000..8f0384d770 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/heap_push_step9.png differ diff --git a/en/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png b/en/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png new file mode 100644 index 0000000000..f4c307ffb6 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png differ diff --git a/en/docs/chapter_heap/heap.assets/representation_of_heap.png b/en/docs/chapter_heap/heap.assets/representation_of_heap.png new file mode 100644 index 0000000000..c7c9fcc637 Binary files /dev/null and b/en/docs/chapter_heap/heap.assets/representation_of_heap.png differ diff --git a/en/docs/chapter_heap/heap.md b/en/docs/chapter_heap/heap.md new file mode 100644 index 0000000000..5424fa7649 --- /dev/null +++ b/en/docs/chapter_heap/heap.md @@ -0,0 +1,538 @@ +# Heap + +A heap is a complete binary tree that satisfies specific conditions and can be mainly categorized into two types, as shown in the figure below. + +- min heap: The value of any node $\leq$ the values of its child nodes. +- max heap: The value of any node $\geq$ the values of its child nodes. + +![Min heap and max heap](heap.assets/min_heap_and_max_heap.png) + +As a special case of a complete binary tree, a heap has the following characteristics: + +- The bottom layer nodes are filled from left to right, and nodes in other layers are fully filled. +- The root node of the binary tree is called the "top" of the heap, and the bottom-rightmost node is called the "bottom" of the heap. +- For max heaps (min heaps), the value of the top element (root) is the largest (smallest) among all elements. + +## Common heap operations + +It should be noted that many programming languages provide a priority queue, which is an abstract data structure defined as a queue with priority sorting. + +In practice, **heaps are often used to implement priority queues. A max heap corresponds to a priority queue where elements are dequeued in descending order**. From a usage perspective, we can consider "priority queue" and "heap" as equivalent data structures. Therefore, this book does not make a special distinction between the two, uniformly referring to them as "heap." + +Common operations on heaps are shown in the table below, and the method names may vary based on the programming language. + +

Table   Efficiency of Heap Operations

+ +| Method name | Description | Time complexity | +| ----------- | ------------------------------------------------------------ | --------------- | +| `push()` | Add an element to the heap | $O(\log n)$ | +| `pop()` | Remove the top element from the heap | $O(\log n)$ | +| `peek()` | Access the top element (for max/min heap, the max/min value) | $O(1)$ | +| `size()` | Get the number of elements in the heap | $O(1)$ | +| `isEmpty()` | Check if the heap is empty | $O(1)$ | + +In practice, we can directly use the heap class (or priority queue class) provided by programming languages. + +Similar to sorting algorithms where we have "ascending order" and "descending order", we can switch between "min heap" and "max heap" by setting a `flag` or modifying the `Comparator`. The code is as follows: + +=== "Python" + + ```python title="heap.py" + # Initialize a min heap + min_heap, flag = [], 1 + # Initialize a max heap + max_heap, flag = [], -1 + + # Python's heapq module implements a min heap by default + # By negating the elements before pushing them to the heap, we invert the order and thus implement a max heap + # In this example, flag = 1 corresponds to a min heap, while flag = -1 corresponds to a max heap + + # Push elements into the heap + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + # Retrieve the top element of the heap + peek: int = flag * max_heap[0] # 5 + + # Pop the top element of the heap + # The popped elements will form a sequence in descending order + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 + + # Get the size of the heap + size: int = len(max_heap) + + # Check if the heap is empty + is_empty: bool = not max_heap + + # Create a heap from a list + min_heap: list[int] = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + ``` + +=== "C++" + + ```cpp title="heap.cpp" + /* Initialize a heap */ + // Initialize a min heap + priority_queue, greater> minHeap; + // Initialize a max heap + priority_queue, less> maxHeap; + + /* Push elements into the heap */ + maxHeap.push(1); + maxHeap.push(3); + maxHeap.push(2); + maxHeap.push(5); + maxHeap.push(4); + + /* Retrieve the top element of the heap */ + int peek = maxHeap.top(); // 5 + + /* Pop the top element of the heap */ + // The popped elements will form a sequence in descending order + maxHeap.pop(); // 5 + maxHeap.pop(); // 4 + maxHeap.pop(); // 3 + maxHeap.pop(); // 2 + maxHeap.pop(); // 1 + + /* Get the size of the heap */ + int size = maxHeap.size(); + + /* Check if the heap is empty */ + bool isEmpty = maxHeap.empty(); + + /* Create a heap from a list */ + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + ``` + +=== "Java" + + ```java title="heap.java" + /* Initialize a heap */ + // Initialize a min heap + Queue minHeap = new PriorityQueue<>(); + // Initialize a max heap (Simply modify the Comparator using a lambda expression) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + /* Push elements into the heap */ + maxHeap.offer(1); + maxHeap.offer(3); + maxHeap.offer(2); + maxHeap.offer(5); + maxHeap.offer(4); + + /* Retrieve the top element of the heap */ + int peek = maxHeap.peek(); // 5 + + /* Pop the top element of the heap */ + // The popped elements will form a sequence in descending order + peek = maxHeap.poll(); // 5 + peek = maxHeap.poll(); // 4 + peek = maxHeap.poll(); // 3 + peek = maxHeap.poll(); // 2 + peek = maxHeap.poll(); // 1 + + /* Get the size of the heap */ + int size = maxHeap.size(); + + /* Check if the heap is empty */ + boolean isEmpty = maxHeap.isEmpty(); + + /* Create a heap from a list */ + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + ``` + +=== "C#" + + ```csharp title="heap.cs" + /* Initialize a heap */ + // Initialize a min heap + PriorityQueue minHeap = new(); + // Initialize a max heap (Simply modify the Comparator using a lambda expression) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y - x)); + + /* Push elements into the heap */ + maxHeap.Enqueue(1, 1); + maxHeap.Enqueue(3, 3); + maxHeap.Enqueue(2, 2); + maxHeap.Enqueue(5, 5); + maxHeap.Enqueue(4, 4); + + /* Retrieve the top element of the heap */ + int peek = maxHeap.Peek();//5 + + /* Pop the top element of the heap */ + // The popped elements will form a sequence in descending order + peek = maxHeap.Dequeue(); // 5 + peek = maxHeap.Dequeue(); // 4 + peek = maxHeap.Dequeue(); // 3 + peek = maxHeap.Dequeue(); // 2 + peek = maxHeap.Dequeue(); // 1 + + /* Get the size of the heap */ + int size = maxHeap.Count; + + /* Check if the heap is empty */ + bool isEmpty = maxHeap.Count == 0; + + /* Create a heap from a list */ + minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); + ``` + +=== "Go" + + ```go title="heap.go" + // In Go, we can construct a max heap of integers by implementing heap.Interface + // Note that implementing heap.Interface requires also implementing sort.Interface + type intHeap []any + + // Push method of heap.Interface, which pushes an element into the heap + func (h *intHeap) Push(x any) { + // Both Push and Pop use a pointer receiver + // because they not only adjust the elements of the slice but also change its length + *h = append(*h, x.(int)) + } + + // Pop method of heap.Interface, which removes the top element of the heap + func (h *intHeap) Pop() any { + // The element to pop from the heap is stored at the end + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last + } + + // Len method of sort.Interface + func (h *intHeap) Len() int { + return len(*h) + } + + // Less method of sort.Interface + func (h *intHeap) Less(i, j int) bool { + // If you want to implement a min heap, you would change this to a less-than comparison + return (*h)[i].(int) > (*h)[j].(int) + } + + // Swap method of sort.Interface + func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + } + + // Top Retrieve the top element of the heap + func (h *intHeap) Top() any { + return (*h)[0] + } + + /* Driver Code */ + func TestHeap(t *testing.T) { + /* Initialize a heap */ + // Initialize a max heap + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* Push elements into the heap */ + // Call the methods of heap.Interface to add elements + heap.Push(maxHeap, 1) + heap.Push(maxHeap, 3) + heap.Push(maxHeap, 2) + heap.Push(maxHeap, 4) + heap.Push(maxHeap, 5) + + /* Retrieve the top element of the heap */ + top := maxHeap.Top() + fmt.Printf("The top element of the heap is %d\n", top) + + /* Pop the top element of the heap */ + // Call the methods of heap.Interface to remove elements + heap.Pop(maxHeap) // 5 + heap.Pop(maxHeap) // 4 + heap.Pop(maxHeap) // 3 + heap.Pop(maxHeap) // 2 + heap.Pop(maxHeap) // 1 + + /* Get the size of the heap */ + size := len(*maxHeap) + fmt.Printf("The number of elements in the heap is %d\n", size) + + /* Check if the heap is empty */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("Is the heap empty? %t\n", isEmpty) + } + ``` + +=== "Swift" + + ```swift title="heap.swift" + /* Initialize a heap */ + // Swift’s Heap type supports both max heaps and min heaps, and need the swift-collections library + var heap = Heap() + + /* Push elements into the heap */ + heap.insert(1) + heap.insert(3) + heap.insert(2) + heap.insert(5) + heap.insert(4) + + /* Retrieve the top element of the heap */ + var peek = heap.max()! + + /* Pop the top element of the heap */ + peek = heap.removeMax() // 5 + peek = heap.removeMax() // 4 + peek = heap.removeMax() // 3 + peek = heap.removeMax() // 2 + peek = heap.removeMax() // 1 + + /* Get the size of the heap */ + let size = heap.count + + /* Check if the heap is empty */ + let isEmpty = heap.isEmpty + + /* Create a heap from a list */ + let heap2 = Heap([1, 3, 2, 5, 4]) + ``` + +=== "JS" + + ```javascript title="heap.js" + // JavaScript does not provide a built-in Heap class + ``` + +=== "TS" + + ```typescript title="heap.ts" + // TypeScript does not provide a built-in Heap class + ``` + +=== "Dart" + + ```dart title="heap.dart" + // Dart does not provide a built-in Heap class + ``` + +=== "Rust" + + ```rust title="heap.rs" + use std::collections::BinaryHeap; + use std::cmp::Reverse; + + /* Initialize a heap */ + // Initialize a min heap + let mut min_heap = BinaryHeap::>::new(); + // Initialize a max heap + let mut max_heap = BinaryHeap::new(); + + /* Push elements into the heap */ + max_heap.push(1); + max_heap.push(3); + max_heap.push(2); + max_heap.push(5); + max_heap.push(4); + + /* Retrieve the top element of the heap */ + let peek = max_heap.peek().unwrap(); // 5 + + /* Pop the top element of the heap */ + // The popped elements will form a sequence in descending order + let peek = max_heap.pop().unwrap(); // 5 + let peek = max_heap.pop().unwrap(); // 4 + let peek = max_heap.pop().unwrap(); // 3 + let peek = max_heap.pop().unwrap(); // 2 + let peek = max_heap.pop().unwrap(); // 1 + + /* Get the size of the heap */ + let size = max_heap.len(); + + /* Check if the heap is empty */ + let is_empty = max_heap.is_empty(); + + /* Create a heap from a list */ + let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); + ``` + +=== "C" + + ```c title="heap.c" + // C does not provide a built-in Heap class + ``` + +=== "Kotlin" + + ```kotlin title="heap.kt" + /* Initialize a heap */ + // Initialize a min heap + var minHeap = PriorityQueue() + // Initialize a max heap (Simply modify the Comparator using a lambda expression) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + /* Push elements into the heap */ + maxHeap.offer(1) + maxHeap.offer(3) + maxHeap.offer(2) + maxHeap.offer(5) + maxHeap.offer(4) + + /* Retrieve the top element of the heap */ + var peek = maxHeap.peek() // 5 + + /* Pop the top element of the heap */ + // The popped elements will form a sequence in descending order + peek = maxHeap.poll() // 5 + peek = maxHeap.poll() // 4 + peek = maxHeap.poll() // 3 + peek = maxHeap.poll() // 2 + peek = maxHeap.poll() // 1 + + /* Get the size of the heap */ + val size = maxHeap.size + + /* Check if the heap is empty */ + val isEmpty = maxHeap.isEmpty() + + /* Create a heap from a list */ + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + ``` + +=== "Ruby" + + ```ruby title="heap.rb" + + ``` + +=== "Zig" + + ```zig title="heap.zig" + + ``` + +??? pythontutor "Code visualization" + + https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Implementation of the heap + +The following implementation is of a max heap. To convert it into a min heap, simply invert all size logic comparisons (for example, replace $\geq$ with $\leq$). Interested readers are encouraged to implement it on their own. + +### Heap storage and representation + +As mentioned in the "Binary Trees" section, complete binary trees are highly suitable for array representation. Since heaps are a type of complete binary tree, **we will use arrays to store heaps**. + +When using an array to represent a binary tree, elements represent node values, and indexes represent node positions in the binary tree. **Node pointers are implemented through an index mapping formula**. + +As shown in the figure below, given an index $i$, the index of its left child is $2i + 1$, the index of its right child is $2i + 2$, and the index of its parent is $(i - 1) / 2$ (floor division). When the index is out of bounds, it signifies a null node or the node does not exist. + +![Representation and storage of heaps](heap.assets/representation_of_heap.png) + +We can encapsulate the index mapping formula into functions for convenient later use: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{parent} +``` + +### Accessing the top element of the heap + +The top element of the heap is the root node of the binary tree, which is also the first element of the list: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{peek} +``` + +### Inserting an element into the heap + +Given an element `val`, we first add it to the bottom of the heap. After addition, since `val` may be larger than other elements in the heap, the heap's integrity might be compromised, **thus it's necessary to repair the path from the inserted node to the root node**. This operation is called heapify. + +Considering starting from the node inserted, **perform heapify from bottom to top**. As shown in the figure below, we compare the value of the inserted node with its parent node, and if the inserted node is larger, we swap them. Then continue this operation, repairing each node in the heap from bottom to top until reaching the root or a node that does not need swapping. + +=== "<1>" + ![Steps of element insertion into the heap](heap.assets/heap_push_step1.png) + +=== "<2>" + ![heap_push_step2](heap.assets/heap_push_step2.png) + +=== "<3>" + ![heap_push_step3](heap.assets/heap_push_step3.png) + +=== "<4>" + ![heap_push_step4](heap.assets/heap_push_step4.png) + +=== "<5>" + ![heap_push_step5](heap.assets/heap_push_step5.png) + +=== "<6>" + ![heap_push_step6](heap.assets/heap_push_step6.png) + +=== "<7>" + ![heap_push_step7](heap.assets/heap_push_step7.png) + +=== "<8>" + ![heap_push_step8](heap.assets/heap_push_step8.png) + +=== "<9>" + ![heap_push_step9](heap.assets/heap_push_step9.png) + +Given a total of $n$ nodes, the height of the tree is $O(\log n)$. Hence, the loop iterations for the heapify operation are at most $O(\log n)$, **making the time complexity of the element insertion operation $O(\log n)$**. The code is as shown: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_up} +``` + +### Removing the top element from the heap + +The top element of the heap is the root node of the binary tree, that is, the first element of the list. If we directly remove the first element from the list, all node indexes in the binary tree will change, making it difficult to use heapify for subsequent repairs. To minimize changes in element indexes, we use the following steps. + +1. Swap the top element with the bottom element of the heap (swap the root node with the rightmost leaf node). +2. After swapping, remove the bottom of the heap from the list (note that since it has been swapped, the original top element is actually being removed). +3. Starting from the root node, **perform heapify from top to bottom**. + +As shown in the figure below, **the direction of "heapify from top to bottom" is opposite to "heapify from bottom to top"**. We compare the value of the root node with its two children and swap it with the largest child. Then, repeat this operation until reaching the leaf node or encountering a node that does not need swapping. + +=== "<1>" + ![Steps of removing the top element from the heap](heap.assets/heap_pop_step1.png) + +=== "<2>" + ![heap_pop_step2](heap.assets/heap_pop_step2.png) + +=== "<3>" + ![heap_pop_step3](heap.assets/heap_pop_step3.png) + +=== "<4>" + ![heap_pop_step4](heap.assets/heap_pop_step4.png) + +=== "<5>" + ![heap_pop_step5](heap.assets/heap_pop_step5.png) + +=== "<6>" + ![heap_pop_step6](heap.assets/heap_pop_step6.png) + +=== "<7>" + ![heap_pop_step7](heap.assets/heap_pop_step7.png) + +=== "<8>" + ![heap_pop_step8](heap.assets/heap_pop_step8.png) + +=== "<9>" + ![heap_pop_step9](heap.assets/heap_pop_step9.png) + +=== "<10>" + ![heap_pop_step10](heap.assets/heap_pop_step10.png) + +Similar to the element insertion operation, the time complexity of the top element removal operation is also $O(\log n)$. The code is as follows: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_down} +``` + +## Common applications of heaps + +- **Priority Queue**: Heaps are often the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of $O(\log n)$, and building a queue having a time complexity of $O(n)$, all of which are very efficient. +- **Heap Sort**: Given a set of data, we can create a heap from them and then continually perform element removal operations to obtain ordered data. However, there is a more elegant way to implement heap sort, as explained in the "Heap Sort" chapter. +- **Finding the Largest $k$ Elements**: This is a classic algorithm problem and also a common use case, such as selecting the top 10 hot news for Weibo hot search, picking the top 10 selling products, etc. diff --git a/en/docs/chapter_heap/index.md b/en/docs/chapter_heap/index.md new file mode 100644 index 0000000000..30f2e3e10d --- /dev/null +++ b/en/docs/chapter_heap/index.md @@ -0,0 +1,9 @@ +# Heap + +![Heap](../assets/covers/chapter_heap.jpg) + +!!! abstract + + Heaps resemble mountains and their jagged peaks, layered and undulating, each with its unique form. + + Each mountain peak rises and falls in scattered heights, yet the tallest always captures attention first. diff --git a/en/docs/chapter_heap/summary.md b/en/docs/chapter_heap/summary.md new file mode 100644 index 0000000000..83b126f5fb --- /dev/null +++ b/en/docs/chapter_heap/summary.md @@ -0,0 +1,17 @@ +# Summary + +### Key review + +- A heap is a complete binary tree that can be categorized as either a max heap or a min heap based on its building property, where the top element of a max heap is the largest and the top element of a min heap is the smallest. +- A priority queue is defined as a queue with dequeue priority, usually implemented using a heap. +- Common operations of a heap and their corresponding time complexities include: element insertion into the heap $O(\log n)$, removing the top element from the heap $O(\log n)$, and accessing the top element of the heap $O(1)$. +- A complete binary tree is well-suited to be represented by an array, thus heaps are commonly stored using arrays. +- Heapify operations are used to maintain the properties of the heap and are used in both heap insertion and removal operations. +- The time complexity of building a heap given an input of $n$ elements can be optimized to $O(n)$, which is highly efficient. +- Top-k is a classic algorithm problem that can be efficiently solved using the heap data structure, with a time complexity of $O(n \log k)$. + +### Q & A + +**Q**: Is the "heap" in data structures the same concept as the "heap" in memory management? + +The two are not the same concept, even though they are both referred to as "heap". The heap in computer system memory is part of dynamic memory allocation, where the program can use it to store data during execution. The program can request a certain amount of heap memory to store complex structures like objects and arrays. When the allocated data is no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, the management and usage of heap memory demands more caution, as improper use may lead to memory leaks and dangling pointers. diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step1.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step1.png new file mode 100644 index 0000000000..e23da68579 Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step1.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step2.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step2.png new file mode 100644 index 0000000000..53ae93b870 Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step2.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step3.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step3.png new file mode 100644 index 0000000000..f1a919eb01 Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step3.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step4.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step4.png new file mode 100644 index 0000000000..e59e4775bb Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step4.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step5.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step5.png new file mode 100644 index 0000000000..d83d929c3c Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step5.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step6.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step6.png new file mode 100644 index 0000000000..53c669e177 Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step6.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step7.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step7.png new file mode 100644 index 0000000000..b77c839d3b Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step7.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step8.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step8.png new file mode 100644 index 0000000000..73283b928b Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step8.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_heap_step9.png b/en/docs/chapter_heap/top_k.assets/top_k_heap_step9.png new file mode 100644 index 0000000000..56309172a5 Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_heap_step9.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_sorting.png b/en/docs/chapter_heap/top_k.assets/top_k_sorting.png new file mode 100644 index 0000000000..2d1c7f37c2 Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_sorting.png differ diff --git a/en/docs/chapter_heap/top_k.assets/top_k_traversal.png b/en/docs/chapter_heap/top_k.assets/top_k_traversal.png new file mode 100644 index 0000000000..62d67b1e00 Binary files /dev/null and b/en/docs/chapter_heap/top_k.assets/top_k_traversal.png differ diff --git a/en/docs/chapter_heap/top_k.md b/en/docs/chapter_heap/top_k.md new file mode 100644 index 0000000000..f267c03202 --- /dev/null +++ b/en/docs/chapter_heap/top_k.md @@ -0,0 +1,73 @@ +# Top-k problem + +!!! question + + Given an unordered array `nums` of length $n$, return the largest $k$ elements in the array. + +For this problem, we will first introduce two straightforward solutions, then explain a more efficient heap-based method. + +## Method 1: Iterative selection + +We can perform $k$ rounds of iterations as shown in the figure below, extracting the $1^{st}$, $2^{nd}$, $\dots$, $k^{th}$ largest elements in each round, with a time complexity of $O(nk)$. + +This method is only suitable when $k \ll n$, as the time complexity approaches $O(n^2)$ when $k$ is close to $n$, which is very time-consuming. + +![Iteratively finding the largest k elements](top_k.assets/top_k_traversal.png) + +!!! tip + + When $k = n$, we can obtain a complete ordered sequence, which is equivalent to the "selection sort" algorithm. + +## Method 2: Sorting + +As shown in the figure below, we can first sort the array `nums` and then return the last $k$ elements, with a time complexity of $O(n \log n)$. + +Clearly, this method "overachieves" the task, as we only need to find the largest $k$ elements, without the need to sort the other elements. + +![Sorting to find the largest k elements](top_k.assets/top_k_sorting.png) + +## Method 3: Heap + +We can solve the Top-k problem more efficiently based on heaps, as shown in the following process. + +1. Initialize a min heap, where the top element is the smallest. +2. First, insert the first $k$ elements of the array into the heap. +3. Starting from the $k + 1^{th}$ element, if the current element is greater than the top element of the heap, remove the top element of the heap and insert the current element into the heap. +4. After completing the traversal, the heap contains the largest $k$ elements. + +=== "<1>" + ![Find the largest k elements based on heap](top_k.assets/top_k_heap_step1.png) + +=== "<2>" + ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) + +=== "<3>" + ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) + +=== "<4>" + ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) + +=== "<5>" + ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) + +=== "<6>" + ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) + +=== "<7>" + ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) + +=== "<8>" + ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) + +=== "<9>" + ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) + +Example code is as follows: + +```src +[file]{top_k}-[class]{}-[func]{top_k_heap} +``` + +A total of $n$ rounds of heap insertions and deletions are performed, with the maximum heap size being $k$, hence the time complexity is $O(n \log k)$. This method is very efficient; when $k$ is small, the time complexity tends towards $O(n)$; when $k$ is large, the time complexity will not exceed $O(n \log n)$. + +Additionally, this method is suitable for scenarios with dynamic data streams. By continuously adding data, we can maintain the elements within the heap, thereby achieving dynamic updates of the largest $k$ elements. diff --git a/en/docs/chapter_hello_algo/index.md b/en/docs/chapter_hello_algo/index.md new file mode 100644 index 0000000000..0dc41f39e3 --- /dev/null +++ b/en/docs/chapter_hello_algo/index.md @@ -0,0 +1,30 @@ +--- +comments: true +icon: material/rocket-launch-outline +--- + +# Before starting + +A few years ago, I shared the "Sword for Offer" problem solutions on LeetCode, receiving encouragement and support from many readers. During interactions with readers, the most common question I encountered was "how to get started with algorithms." Gradually, I developed a keen interest in this question. + +Directly solving problems seems to be the most popular method — it's simple, direct, and effective. However, problem-solving is like playing a game of Minesweeper: those with strong self-study abilities can defuse the mines one by one, but those with insufficient basics might end up metaphorically bruised from explosions, retreating step by step in frustration. Going through textbooks is also common, but for those aiming for job applications, the energy spent on thesis writing, resume submissions, and preparation for written tests and interviews leaves little for tackling thick books, turning it into a daunting challenge. + +If you're facing similar troubles, then this book is lucky to have found you. This book is my answer to the question. While it may not be the best solution, it is at least a positive attempt. This book may not directly land you an offer, but it will guide you through the "knowledge map" in data structures and algorithms, help you understand the shapes, sizes, and locations of different "mines," and enable you to master various "demining methods." With these skills, I believe you can solve problems and read literature more comfortably, gradually building a knowledge system. + +I deeply agree with Professor Feynman's statement: "Knowledge isn't free. You have to pay attention." In this sense, this book is not entirely "free." To not disappoint the precious "attention" you pay for this book, I will do my best, dedicating my utmost "attention" to this book. + +Knowing my limitations, although the content of this book has been refined over time, there are surely many errors remaining. I sincerely request critiques and corrections from all teachers and students. + +![Hello Algo](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" } + +
+

Hello, Algo!

+
+ +The advent of computers has brought significant changes to the world. With their high-speed computing power and excellent programmability, they have become the ideal medium for executing algorithms and processing data. Whether it's the realistic graphics of video games, the intelligent decisions in autonomous driving, the brilliant Go games of AlphaGo, or the natural interactions of ChatGPT, these applications are all exquisite demonstrations of algorithms at work on computers. + +In fact, before the advent of computers, algorithms and data structures already existed in every corner of the world. Early algorithms were relatively simple, such as ancient counting methods and tool-making procedures. As civilization progressed, algorithms became more refined and complex. From the exquisite craftsmanship of artisans, to industrial products that liberate productive forces, to the scientific laws governing the universe, almost every ordinary or astonishing thing has behind it the ingenious thought of algorithms. + +Similarly, data structures are everywhere: from social networks to subway lines, many systems can be modeled as "graphs"; from a country to a family, the main forms of social organization exhibit characteristics of "trees"; winter clothes are like a "stack", where the first item worn is the last to be taken off; a badminton shuttle tube resembles a "queue", with one end for insertion and the other for retrieval; a dictionary is like a "hash table", enabling quick search for target entries. + +This book aims to help readers understand the core concepts of algorithms and data structures through clear, easy-to-understand animated illustrations and runnable code examples, and to be able to implement them through programming. On this basis, this book strives to reveal the vivid manifestations of algorithms in the complex world, showcasing the beauty of algorithms. I hope this book can help you! diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png new file mode 100644 index 0000000000..11b2ea834f Binary files /dev/null and b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png differ diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png new file mode 100644 index 0000000000..76527f4e96 Binary files /dev/null and b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png differ diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png new file mode 100644 index 0000000000..3cf8566c20 Binary files /dev/null and b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png differ diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png new file mode 100644 index 0000000000..1d703ee138 Binary files /dev/null and b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png differ diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png new file mode 100644 index 0000000000..a71cc39b14 Binary files /dev/null and b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png differ diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png new file mode 100644 index 0000000000..977b5b9692 Binary files /dev/null and b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png differ diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png new file mode 100644 index 0000000000..12df9dfca2 Binary files /dev/null and b/en/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png differ diff --git a/en/docs/chapter_introduction/algorithms_are_everywhere.md b/en/docs/chapter_introduction/algorithms_are_everywhere.md new file mode 100644 index 0000000000..a4b7638e2b --- /dev/null +++ b/en/docs/chapter_introduction/algorithms_are_everywhere.md @@ -0,0 +1,56 @@ +# Algorithms are everywhere + +When we hear the word "algorithm," we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which can be seen everywhere in our daily lives. + +Before formally discussing algorithms, there's an interesting fact worth sharing: **you have already unconsciously learned many algorithms and have become accustomed to applying them in your daily life**. Here, I will give a few specific examples to prove this point. + +**Example 1: Looking Up a Dictionary**. In an English dictionary, words are listed alphabetically. Suppose we're searching for a word that starts with the letter $r$. This is typically done in the following way: + +1. Open the dictionary to about halfway and check the first letter on the page, let's say the letter is $m$. +2. Since $r$ comes after $m$ in the alphabet, we can ignore the first half of the dictionary and focus on the latter half. +3. Repeat steps `1.` and `2.` until you find the page where the word starts with $r$. + +=== "<1>" + ![Process of Looking Up a Dictionary](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) + +=== "<2>" + ![Binary Search in Dictionary Step 2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) + +=== "<3>" + ![Binary Search in Dictionary Step 3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) + +=== "<4>" + ![Binary Search in Dictionary Step 4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) + +=== "<5>" + ![Binary Search in Dictionary Step 5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) + +This essential skill for elementary students, looking up a dictionary, is actually the famous "Binary Search" algorithm. From a data structure perspective, we can consider the dictionary as a sorted "array"; from an algorithmic perspective, the series of actions taken to look up a word in the dictionary can be viewed as "Binary Search." + +**Example 2: Organizing Playing Cards**. When playing cards, we need to arrange the cards in our hand in ascending order, as shown in the following process. + +1. Divide the playing cards into "ordered" and "unordered" sections, assuming initially the leftmost card is already in order. +2. Take out a card from the unordered section and insert it into the correct position in the ordered section; after this, the leftmost two cards are in order. +3. Continue to repeat step `2.` until all cards are in order. + +![Playing cards sorting process](algorithms_are_everywhere.assets/playing_cards_sorting.png) + +The above method of organizing playing cards is essentially the "Insertion Sort" algorithm, which is very efficient for small datasets. Many programming languages' sorting functions include the insertion sort. + +**Example 3: Making Change**. Suppose we buy goods worth $69$ yuan at a supermarket and give the cashier $100$ yuan, then the cashier needs to give us $31$ yuan in change. They would naturally complete the thought process as shown in the figure below. + +1. The options are currencies smaller than $31$, including $1$, $5$, $10$, and $20$. +2. Take out the largest $20$ from the options, leaving $31 - 20 = 11$. +3. Take out the largest $10$ from the remaining options, leaving $11 - 10 = 1$. +4. Take out the largest $1$ from the remaining options, leaving $1 - 1 = 0$. +5. Complete the change-making, with the solution being $20 + 10 + 1 = 31$. + +![Change making process](algorithms_are_everywhere.assets/greedy_change.png) + +In the above steps, we make the best choice at each step (using the largest denomination possible), ultimately resulting in a feasible change-making plan. From the perspective of data structures and algorithms, this method is essentially a "Greedy" algorithm. + +From cooking a meal to interstellar travel, almost all problem-solving involves algorithms. The advent of computers allows us to store data structures in memory and write code to call the CPU and GPU to execute algorithms. In this way, we can transfer real-life problems to computers, solving various complex issues more efficiently. + +!!! tip + + If concepts such as data structures, algorithms, arrays, and binary search still seem somewhat obscure, I encourage you to continue reading. This book will gently guide you into the realm of understanding data structures and algorithms. diff --git a/en/docs/chapter_introduction/index.md b/en/docs/chapter_introduction/index.md new file mode 100644 index 0000000000..33c6c0daf5 --- /dev/null +++ b/en/docs/chapter_introduction/index.md @@ -0,0 +1,9 @@ +# Encounter with algorithms + +![Encounter with algorithms](../assets/covers/chapter_introduction.jpg) + +!!! abstract + + A graceful maiden dances, intertwined with the data, her skirt swaying to the melody of algorithms. + + She invites you to a dance, follow her steps, and enter the world of algorithms full of logic and beauty. diff --git a/en/docs/chapter_introduction/summary.md b/en/docs/chapter_introduction/summary.md new file mode 100644 index 0000000000..acfa2d4a5d --- /dev/null +++ b/en/docs/chapter_introduction/summary.md @@ -0,0 +1,9 @@ +# Summary + +- Algorithms are ubiquitous in daily life and are not as inaccessible and complex as they might seem. In fact, we have already unconsciously learned many algorithms to solve various problems in life. +- The principle of looking up a word in a dictionary is consistent with the binary search algorithm. The binary search algorithm embodies the important algorithmic concept of divide and conquer. +- The process of organizing playing cards is very similar to the insertion sort algorithm. The insertion sort algorithm is suitable for sorting small datasets. +- The steps of making change in currency essentially follow the greedy algorithm, where each step involves making the best possible choice at the moment. +- An algorithm is a set of instructions or steps used to solve a specific problem within a finite amount of time, while a data structure is the way data is organized and stored in a computer. +- Data structures and algorithms are closely linked. Data structures are the foundation of algorithms, and algorithms are the stage to utilize the functions of data structures. +- We can liken data structures and algorithms to building blocks. The blocks represent data, the shape and connection method of the blocks represent data structures, and the steps of assembling the blocks correspond to algorithms. diff --git a/en/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png b/en/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png new file mode 100644 index 0000000000..5f7ac5fe3e Binary files /dev/null and b/en/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png differ diff --git a/en/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png b/en/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png new file mode 100644 index 0000000000..8ecd3bdde4 Binary files /dev/null and b/en/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png differ diff --git a/en/docs/chapter_introduction/what_is_dsa.md b/en/docs/chapter_introduction/what_is_dsa.md new file mode 100644 index 0000000000..93aad451dc --- /dev/null +++ b/en/docs/chapter_introduction/what_is_dsa.md @@ -0,0 +1,53 @@ +# What is an algorithm + +## Definition of an algorithm + +An algorithm is a set of instructions or steps to solve a specific problem within a finite amount of time. It has the following characteristics: + +- The problem is clearly defined, including unambiguous definitions of input and output. +- The algorithm is feasible, meaning it can be completed within a finite number of steps, time, and memory space. +- Each step has a definitive meaning. The output is consistently the same under the same inputs and conditions. + +## Definition of a data structure + +A data structure is a way of organizing and storing data in a computer, with the following design goals: + +- Minimize space occupancy to save computer memory. +- Make data operations as fast as possible, covering data access, addition, deletion, updating, etc. +- Provide concise data representation and logical information to enable efficient algorithm execution. + +**Designing data structures is a balancing act, often requiring trade-offs**. If you want to improve in one aspect, you often need to compromise in another. Here are two examples: + +- Compared to arrays, linked lists offer more convenience in data addition and deletion but sacrifice data access speed. +- Graphs, compared to linked lists, provide richer logical information but require more memory space. + +## Relationship between data structures and algorithms + +As shown in the figure below, data structures and algorithms are highly related and closely integrated, specifically in the following three aspects: + +- Data structures are the foundation of algorithms. They provide structured data storage and methods for manipulating data for algorithms. +- Algorithms are the stage where data structures come into play. The data structure alone only stores data information; it is through the application of algorithms that specific problems can be solved. +- Algorithms can often be implemented based on different data structures, but their execution efficiency can vary greatly. Choosing the right data structure is key. + +![Relationship between data structures and algorithms](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) + +Data structures and algorithms can be likened to a set of building blocks, as illustrated in the figure below. A building block set includes numerous pieces, accompanied by detailed assembly instructions. Following these instructions step by step allows us to construct an intricate block model. + +![Assembling blocks](what_is_dsa.assets/assembling_blocks.png) + +The detailed correspondence between the two is shown in the table below. + +

Table   Comparing data structures and algorithms to building blocks

+ +| Data Structures and Algorithms | Building Blocks | +| ------------------------------ | --------------------------------------------------------------- | +| Input data | Unassembled blocks | +| Data structure | Organization of blocks, including shape, size, connections, etc | +| Algorithm | A series of steps to assemble the blocks into the desired shape | +| Output data | Completed Block model | + +It's worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations in multiple programming languages. + +!!! tip "Conventional Abbreviation" + + In real-life discussions, we often refer to "Data Structures and Algorithms" simply as "Algorithms". For example, the well-known LeetCode algorithm problems actually test both data structure and algorithm knowledge. diff --git a/en/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png b/en/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png new file mode 100644 index 0000000000..cc061a0b0f Binary files /dev/null and b/en/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png differ diff --git a/en/docs/chapter_preface/about_the_book.md b/en/docs/chapter_preface/about_the_book.md new file mode 100644 index 0000000000..010bfe34b9 --- /dev/null +++ b/en/docs/chapter_preface/about_the_book.md @@ -0,0 +1,52 @@ +# About this book + +This open-source project aims to create a free, and beginner-friendly crash course on data structures and algorithms. + +- Animated illustrations, easy-to-understand content, and a smooth learning curve help beginners explore the "knowledge map" of data structures and algorithms. +- Run code with just one click, helping readers improve their programming skills and understand the working principle of algorithms and the underlying implementation of data structures. +- Promoting learning by teaching, feel free to ask questions and share insights. Let's grow together through discussion. + +## Target audience + +If you are new to algorithms with limited exposure, or you have accumulated some experience in algorithms, but you only have a vague understanding of data structures and algorithms, and you are constantly jumping between "yep" and "hmm", then this book is for you! + +If you have already accumulated a certain amount of problem-solving experience, and are familiar with most types of problems, then this book can help you review and organize your algorithm knowledge system. The repository's source code can be used as a "problem-solving toolkit" or an "algorithm cheat sheet". + +If you are an algorithm expert, we look forward to receiving your valuable suggestions, or [join us and collaborate](https://www.hello-algo.com/chapter_appendix/contribution/). + +!!! success "Prerequisites" + + You should know how to write and read simple code in at least one programming language. + +## Content structure + +The main content of the book is shown in the figure below. + +- **Complexity analysis**: explores aspects and methods for evaluating data structures and algorithms. Covers methods of deriving time complexity and space complexity, along with common types and examples. +- **Data structures**: focuses on fundamental data types, classification methods, definitions, pros and cons, common operations, types, applications, and implementation methods of data structures such as array, linked list, stack, queue, hash table, tree, heap, graph, etc. +- **Algorithms**: defines algorithms, discusses their pros and cons, efficiency, application scenarios, problem-solving steps, and includes sample questions for various algorithms such as search, sorting, divide and conquer, backtracking, dynamic programming, greedy algorithms, and more. + +![Main content of the book](about_the_book.assets/hello_algo_mindmap.png) + +## Acknowledgements + +This book is continuously improved with the joint efforts of many contributors from the open-source community. Thanks to each writer who invested their time and energy, listed in the order generated by GitHub: krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, msk397, gvenusleo, khoaxuantu, RiverTwilight, rongyi, gyt95, zhuoqinyue, K3v123, Zuoxun, mingXta, hello-ikun, FangYuan33, GN-Yu, yuelinxin, longsizhuo, Cathay-Chen, guowei-gong, xBLACKICEx, IsChristina, JoseHung, qualifier1024, QiLOL, pengchzn, Guanngxu, L-Super, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, theNefelibatas, longranger2, cy-by-side, xiongsp, JeffersonHuang, Transmigration-zhou, magentaqin, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, Shyam-Chen, nanlei, hongyun-robot, Phoenix0415, MolDuM, Nigh, he-weilai, junminhong, mgisr, iron-irax, yd-j, XiaChuerwu, XC-Zero, seven1240, SamJin98, wodray, reeswell, NI-SW, Horbin-Magician, Enlightenus, xjr7670, YangXuanyi, DullSword, boloboloda, iStig, qq909244296, jiaxianhua, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, liuxjerry, lucaswangdev, lyl625760, hts0000, gledfish, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, luluxia, xb534, bitsmi, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, steventimes, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, ZhongGuanbin, shanghai-Jerry, JackYang-hellobobo, Javesun99, lipusheng, BlindTerran, ShiMaRing, FreddieLi, FloranceYeh, iFleey, fanchenggang, gltianwen, goerll, Dr-XYZ, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, fanenr, eagleanurag, LifeGoesOnionOnionOnion, 52coder, foursevenlove, KorsChen, hezhizhen, linzeyan, ZJKung, GaochaoZhu, hopkings2008, yang-le, Evilrabbit520, Turing-1024-Lee, thomasq0, Suremotoo, Allen-Scai, Risuntsy, Richard-Zhang1019, qingpeng9802, primexiao, nidhoggfgg, 1ch0, MwumLi, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai and 0130w. + +The code review work for this book was completed by coderonion, Gonglja, gvenusleo, hpstory, justin‐tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi (listed in alphabetical order). Thanks to them for their time and effort, ensuring the standardization and uniformity of the code in various languages. + +The Traditional Chinese version of this book was reviewed by Shyam-Chen and Dr-XYZ, while the English version was reviewed by yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0, and magentaqin. It is thanks to their continuous contributions that this book can reach and serve a broader audience. + +Throughout the creation of this book, numerous individuals provided invaluable assistance, including but not limited to: + +- Thanks to my mentor at the company, Dr. Xi Li, who encouraged me in a conversation to "get moving fast," which solidified my determination to write this book; +- Thanks to my girlfriend Bubble, as the first reader of this book, for offering many valuable suggestions from the perspective of a beginner in algorithms, making this book more suitable for newbies; +- Thanks to Tengbao, Qibao, and Feibao for coming up with a creative name for this book, evoking everyone's fond memories of writing their first line of code "Hello World!"; +- Thanks to Xiaoquan for providing professional help in intellectual property, which has played a significant role in the development of this open-source book; +- Thanks to Sutong for designing a beautiful cover and logo for this book, and for patiently making multiple revisions under my insistence; +- Thanks to @squidfunk for providing writing and typesetting suggestions, as well as his developed open-source documentation theme [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master). + +Throughout the writing journey, I delved into numerous textbooks and articles on data structures and algorithms. These works served as exemplary models, ensuring the accuracy and quality of this book's content. I extend my gratitude to all who preceded me for their invaluable contributions! + +This book advocates a combination of hands-on and minds-on learning, inspired in this regard by ["Dive into Deep Learning"](https://github.com/d2l-ai/d2l-en). I highly recommend this excellent book to all readers. + +**Heartfelt thanks to my parents, whose ongoing support and encouragement have allowed me to do this interesting work**. diff --git a/en/docs/chapter_preface/index.md b/en/docs/chapter_preface/index.md new file mode 100644 index 0000000000..332d997c79 --- /dev/null +++ b/en/docs/chapter_preface/index.md @@ -0,0 +1,9 @@ +# Preface + +![Preface](../assets/covers/chapter_preface.jpg) + +!!! abstract + + Algorithms are like a beautiful symphony, with each line of code flowing like a rhythm. + + May this book ring softly in your mind, leaving a unique and profound melody. diff --git a/en/docs/chapter_preface/suggestions.assets/code_md_to_repo.png b/en/docs/chapter_preface/suggestions.assets/code_md_to_repo.png new file mode 100644 index 0000000000..f448405b18 Binary files /dev/null and b/en/docs/chapter_preface/suggestions.assets/code_md_to_repo.png differ diff --git a/en/docs/chapter_preface/suggestions.assets/download_code.png b/en/docs/chapter_preface/suggestions.assets/download_code.png new file mode 100644 index 0000000000..b37e0d67a7 Binary files /dev/null and b/en/docs/chapter_preface/suggestions.assets/download_code.png differ diff --git a/en/docs/chapter_preface/suggestions.assets/learning_route.png b/en/docs/chapter_preface/suggestions.assets/learning_route.png new file mode 100644 index 0000000000..bec486c79e Binary files /dev/null and b/en/docs/chapter_preface/suggestions.assets/learning_route.png differ diff --git a/en/docs/chapter_preface/suggestions.assets/pythontutor_example.png b/en/docs/chapter_preface/suggestions.assets/pythontutor_example.png new file mode 100644 index 0000000000..c76de89f0a Binary files /dev/null and b/en/docs/chapter_preface/suggestions.assets/pythontutor_example.png differ diff --git a/en/docs/chapter_preface/suggestions.md b/en/docs/chapter_preface/suggestions.md new file mode 100644 index 0000000000..393e50447f --- /dev/null +++ b/en/docs/chapter_preface/suggestions.md @@ -0,0 +1,239 @@ +# How to read + +!!! tip + + For the best reading experience, it is recommended that you read through this section. + +## Writing conventions + +- Chapters marked with '*' after the title are optional and contain relatively challenging content. If you are short on time, it is advisable to skip them. +- Technical terms will be in boldface (in the print and PDF versions) or underlined (in the web version), for instance, array. It's advisable to familiarize yourself with these for better comprehension of technical texts. +- **Bolded text** indicates key content or summary statements, which deserve special attention. +- Words and phrases with specific meanings are indicated with “quotation marks” to avoid ambiguity. +- When it comes to terms that are inconsistent between programming languages, this book follows Python, for example using `None` to mean `null`. +- This book partially ignores the comment conventions for programming languages in exchange for a more compact layout of the content. The comments primarily consist of three types: title comments, content comments, and multi-line comments. + +=== "Python" + + ```python title="" + """Header comments for labeling functions, classes, test samples, etc""" + + # Comments for explaining details + + """ + Multiline + comments + """ + ``` + +=== "C++" + + ```cpp title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "Java" + + ```java title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "C#" + + ```csharp title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "Go" + + ```go title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "Swift" + + ```swift title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "JS" + + ```javascript title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "TS" + + ```typescript title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "Dart" + + ```dart title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "Rust" + + ```rust title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "C" + + ```c title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Header comments for labeling functions, classes, test samples, etc */ + + // Comments for explaining details. + + /** + * Multiline + * comments + */ + ``` + +=== "Zig" + + ```zig title="" + // Header comments for labeling functions, classes, test samples, etc + + // Comments for explaining details. + + // Multiline + // comments + ``` + +## Efficient learning via animated illustrations + +Compared with text, videos and pictures have a higher density of information and are more structured, making them easier to understand. In this book, **key and difficult concepts are mainly presented through animations and illustrations**, with text serving as explanations and supplements. + +When encountering content with animations or illustrations as shown in the figure below, **prioritize understanding the figure, with text as supplementary**, integrating both for a comprehensive understanding. + +![Animated illustration example](../index.assets/animation.gif) + +## Deepen understanding through coding practice + +The source code of this book is hosted on the [GitHub Repository](https://github.com/krahets/hello-algo). As shown in the figure below, **the source code comes with test examples and can be executed with just a single click**. + +If time permits, **it's recommended to type out the code yourself**. If pressed for time, at least read and run all the codes. + +Compared to just reading code, writing code often yields more learning. **Learning by doing is the real way to learn.** + +![Running code example](../index.assets/running_code.gif) + +Setting up to run the code involves three main steps. + +**Step 1: Install a local programming environment**. Follow the [tutorial](https://www.hello-algo.com/chapter_appendix/installation/) in the appendix for installation, or skip this step if already installed. + +**Step 2: Clone or download the code repository**. Visit the [GitHub Repository](https://github.com/krahets/hello-algo). + +If [Git](https://git-scm.com/downloads) is installed, use the following command to clone the repository: + +```shell +git clone https://github.com/krahets/hello-algo.git +``` + +Alternatively, you can also click the "Download ZIP" button at the location shown in the figure below to directly download the code as a compressed ZIP file. Then, you can simply extract it locally. + +![Cloning repository and downloading code](suggestions.assets/download_code.png) + +**Step 3: Run the source code**. As shown in the figure below, for the code block labeled with the file name at the top, we can find the corresponding source code file in the `codes` folder of the repository. These files can be executed with a single click, which will help you save unnecessary debugging time and allow you to focus on learning. + +![Code block and corresponding source code file](suggestions.assets/code_md_to_repo.png) + +## Learning together in discussion + +While reading this book, please don't skip over the points that you didn't learn. **Feel free to post your questions in the comment section**. We will be happy to answer them and can usually respond within two days. + +As illustrated in the figure below, each chapter features a comment section at the bottom. I encourage you to pay attention to these comments. They not only expose you to others' encountered problems, aiding in identifying knowledge gaps and sparking deeper contemplation, but also invite you to generously contribute by answering fellow readers' inquiries, sharing insights, and fostering mutual improvement. + +![Comment section example](../index.assets/comment.gif) + +## Algorithm learning path + +Overall, the journey of mastering data structures and algorithms can be divided into three stages: + +1. **Stage 1: Introduction to algorithms**. We need to familiarize ourselves with the characteristics and usage of various data structures and learn about the principles, processes, uses, and efficiency of different algorithms. +2. **Stage 2: Practicing algorithm problems**. It is recommended to start from popular problems, such as [Sword for Offer](https://leetcode.cn/studyplan/coding-interviews/) and [LeetCode Hot 100](https://leetcode.cn/studyplan/top-100- liked/), and accumulate at least 100 questions to familiarize yourself with mainstream algorithmic problems. Forgetfulness can be a challenge when you start practicing, but rest assured that this is normal. We can follow the "Ebbinghaus Forgetting Curve" to review the questions, and usually after 3~5 rounds of repetitions, we will be able to memorize them. +3. **Stage 3: Building the knowledge system**. In terms of learning, we can read algorithm column articles, solution frameworks, and algorithm textbooks to continuously enrich the knowledge system. In terms of practicing, we can try advanced strategies, such as categorizing by topic, multiple solutions for a single problem, and one solution for multiple problems, etc. Insights on these strategies can be found in various communities. + +As shown in the figure below, this book mainly covers “Stage 1,” aiming to help you more efficiently embark on Stages 2 and 3. + +![Algorithm learning path](suggestions.assets/learning_route.png) diff --git a/en/docs/chapter_preface/summary.md b/en/docs/chapter_preface/summary.md new file mode 100644 index 0000000000..a1f02862a3 --- /dev/null +++ b/en/docs/chapter_preface/summary.md @@ -0,0 +1,8 @@ +# Summary + +- The main audience of this book is beginners in algorithm. If you already have some basic knowledge, this book can help you systematically review your algorithm knowledge, and the source code in this book can also be used as a "Coding Toolkit". +- The book consists of three main sections, Complexity Analysis, Data Structures, and Algorithms, covering most of the topics in the field. +- For newcomers to algorithms, it is crucial to read an introductory book in the beginning stages to avoid many detours or common pitfalls. +- Animations and figures within the book are usually used to introduce key points and difficult knowledge. These should be given more attention when reading the book. +- Practice is the best way to learn programming. It is highly recommended that you run the source code and type in the code yourself. +- Each chapter in the web version of this book features a discussion section, and you are welcome to share your questions and insights at any time. diff --git a/en/docs/chapter_reference/index.md b/en/docs/chapter_reference/index.md new file mode 100644 index 0000000000..39cf6a52cd --- /dev/null +++ b/en/docs/chapter_reference/index.md @@ -0,0 +1,25 @@ +--- +icon: material/bookshelf +--- + +# References + +[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). + +[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). + +[3] Robert Sedgewick, et al. Algorithms (4th Edition). + +[4] Yan Weimin. Data Structures (C Language Version). + +[5] Deng Junhui. Data Structures (C++ Language Version, Third Edition). + +[6] Mark Allen Weiss, translated by Chen Yue. Data Structures and Algorithm Analysis in Java (Third Edition). + +[7] Cheng Jie. Speaking of Data Structures. + +[8] Wang Zheng. The Beauty of Data Structures and Algorithms. + +[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). + +[10] Aston Zhang, et al. Dive into Deep Learning. diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_example.png b/en/docs/chapter_searching/binary_search.assets/binary_search_example.png new file mode 100644 index 0000000000..00132378e5 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_example.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_ranges.png b/en/docs/chapter_searching/binary_search.assets/binary_search_ranges.png new file mode 100644 index 0000000000..f057a8c3d8 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_ranges.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_step1.png b/en/docs/chapter_searching/binary_search.assets/binary_search_step1.png new file mode 100644 index 0000000000..99a6683065 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_step1.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_step2.png b/en/docs/chapter_searching/binary_search.assets/binary_search_step2.png new file mode 100644 index 0000000000..fd85ce7703 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_step2.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_step3.png b/en/docs/chapter_searching/binary_search.assets/binary_search_step3.png new file mode 100644 index 0000000000..a48f988ec0 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_step3.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_step4.png b/en/docs/chapter_searching/binary_search.assets/binary_search_step4.png new file mode 100644 index 0000000000..753f578137 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_step4.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_step5.png b/en/docs/chapter_searching/binary_search.assets/binary_search_step5.png new file mode 100644 index 0000000000..11b6fd3bdb Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_step5.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_step6.png b/en/docs/chapter_searching/binary_search.assets/binary_search_step6.png new file mode 100644 index 0000000000..362d4e43a8 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_step6.png differ diff --git a/en/docs/chapter_searching/binary_search.assets/binary_search_step7.png b/en/docs/chapter_searching/binary_search.assets/binary_search_step7.png new file mode 100644 index 0000000000..a158321be0 Binary files /dev/null and b/en/docs/chapter_searching/binary_search.assets/binary_search_step7.png differ diff --git a/en/docs/chapter_searching/binary_search.md b/en/docs/chapter_searching/binary_search.md new file mode 100644 index 0000000000..5384dd66ff --- /dev/null +++ b/en/docs/chapter_searching/binary_search.md @@ -0,0 +1,83 @@ +# Binary search + +Binary search is an efficient search algorithm that uses a divide-and-conquer strategy. It takes advantage of the sorted order of elements in an array by reducing the search interval by half in each iteration, continuing until either the target element is found or the search interval becomes empty. + +!!! question + + Given an array `nums` of length $n$, where elements are arranged in ascending order without duplicates. Please find and return the index of element `target` in this array. If the array does not contain the element, return $-1$. An example is shown in the figure below. + +![Binary search example data](binary_search.assets/binary_search_example.png) + +As shown in the figure below, we firstly initialize pointers with $i = 0$ and $j = n - 1$, pointing to the first and last element of the array respectively. They also represent the whole search interval $[0, n - 1]$. Please note that square brackets indicate a closed interval, which includes the boundary values themselves. + +And then the following two steps may be performed in a loop. + +1. Calculate the midpoint index $m = \lfloor {(i + j) / 2} \rfloor$, where $\lfloor \: \rfloor$ denotes the floor operation. +2. Based on the comparison between the value of `nums[m]` and `target`, one of the following three cases will be chosen to execute. + 1. If `nums[m] < target`, it indicates that `target` is in the interval $[m + 1, j]$, thus set $i = m + 1$. + 2. If `nums[m] > target`, it indicates that `target` is in the interval $[i, m - 1]$, thus set $j = m - 1$. + 3. If `nums[m] = target`, it indicates that `target` is found, thus return index $m$. + +If the array does not contain the target element, the search interval will eventually reduce to empty, ending up returning $-1$. + +=== "<1>" + ![Binary search process](binary_search.assets/binary_search_step1.png) + +=== "<2>" + ![binary_search_step2](binary_search.assets/binary_search_step2.png) + +=== "<3>" + ![binary_search_step3](binary_search.assets/binary_search_step3.png) + +=== "<4>" + ![binary_search_step4](binary_search.assets/binary_search_step4.png) + +=== "<5>" + ![binary_search_step5](binary_search.assets/binary_search_step5.png) + +=== "<6>" + ![binary_search_step6](binary_search.assets/binary_search_step6.png) + +=== "<7>" + ![binary_search_step7](binary_search.assets/binary_search_step7.png) + +It's worth noting that as $i$ and $j$ are both of type `int`, **$i + j$ might exceed the range of `int` type**. To avoid large number overflow, we usually use the formula $m = \lfloor {i + (j - i) / 2} \rfloor$ to calculate the midpoint. + +The code is as follows: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search} +``` + +**Time complexity is $O(\log n)$** : In the binary loop, the interval decreases by half each round, hence the number of iterations is $\log_2 n$. + +**Space complexity is $O(1)$** : Pointers $i$ and $j$ occupies constant size of space. + +## Interval representation methods + +Besides the above closed interval, another common interval representation is the "left-closed right-open" interval, defined as $[0, n)$, where the left boundary includes itself, and the right boundary does not. In this representation, the interval $[i, j)$ is empty when $i = j$. + +We can implement a binary search algorithm with the same functionality based on this representation: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search_lcro} +``` + +As shown in the figure below, under the two types of interval representations, the initialization, loop condition, and narrowing interval operation of the binary search algorithm differ. + +Since both boundaries in the "closed interval" representation are inclusive, the operations to narrow the interval through pointers $i$ and $j$ are also symmetrical. This makes it less prone to errors, **therefore, it is generally recommended to use the "closed interval" approach**. + +![Two types of interval definitions](binary_search.assets/binary_search_ranges.png) + +## Advantages and limitations + +Binary search performs well in both time and space aspects. + +- Binary search is time-efficient. With large dataset, the logarithmic time complexity offers a major advantage. For instance, given a dataset with size $n = 2^{20}$, linear search requires $2^{20} = 1048576$ iterations, while binary search only demands $\log_2 2^{20} = 20$ loops. +- Binary search does not need extra space. Compared to search algorithms that rely on additional space (like hash search), binary search is more space-efficient. + +However, binary search may not be suitable for all scenarios due to the following concerns. + +- Binary search can only be applied to sorted data. Unsorted data must be sorted before applying binary search, which may not be worthwhile as sorting algorithm typically has a time complexity of $O(n \log n)$. Such cost is even higher than linear search, not to mention binary search itself. For scenarios with frequent insertion, the cost of remaining the array in order is pretty high as the time complexity of inserting new elements into specific positions is $O(n)$. +- Binary search may use array only. Binary search requires non-continuous (jumping) element access, which is inefficient in linked list. As a result, linked list or data structures based on linked list may not be suitable for this algorithm. +- Linear search performs better on small dataset. In linear search, only 1 decision operation is required for each iteration; whereas in binary search, it involves 1 addition, 1 division, 1 to 3 decision operations, 1 addition (subtraction), totaling 4 to 6 operations. Therefore, if data size $n$ is small, linear search is faster than binary search. diff --git a/en/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png b/en/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png new file mode 100644 index 0000000000..3706d9b545 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png differ diff --git a/en/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png b/en/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png new file mode 100644 index 0000000000..829ee0994a Binary files /dev/null and b/en/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png differ diff --git a/en/docs/chapter_searching/binary_search_edge.md b/en/docs/chapter_searching/binary_search_edge.md new file mode 100644 index 0000000000..844c2fb201 --- /dev/null +++ b/en/docs/chapter_searching/binary_search_edge.md @@ -0,0 +1,56 @@ +# Binary search boundaries + +## Find the left boundary + +!!! question + + Given a sorted array `nums` of length $n$, which may contain duplicate elements, return the index of the leftmost element `target`. If the element is not present in the array, return $-1$. + +Recalling the method of binary search for an insertion point, after the search is completed, the index $i$ will point to the leftmost occurrence of `target`. Therefore, **searching for the insertion point is essentially the same as finding the index of the leftmost `target`**. + +We can use the function for finding an insertion point to find the left boundary of `target`. Note that the array might not contain `target`, which could lead to the following two results: + +- The index $i$ of the insertion point is out of bounds. +- The element `nums[i]` is not equal to `target`. + +In these cases, simply return $-1$. The code is as follows: + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} +``` + +## Find the right boundary + +How do we find the rightmost occurrence of `target`? The most straightforward way is to modify the traditional binary search logic by changing how we adjust the search boundaries in the case of `nums[m] == target`. The code is omitted here. If you are interested, try to implement the code on your own. + +Below we are going to introduce two more ingenious methods. + +### Reuse the left boundary search + +To find the rightmost occurrence of `target`, we can reuse the function used for locating the leftmost `target`. Specifically, we transform the search for the rightmost target into a search for the leftmost target + 1. + +As shown in the figure below, after the search is complete, pointer $i$ will point to the leftmost `target + 1` (if exists), while pointer $j$ will point to the rightmost occurrence of `target`. Therefore, returning $j$ will give us the right boundary. + +![Transforming the search for the right boundary into the search for the left boundary](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) + +Note that the insertion point returned is $i$, therefore, it should be subtracted by $1$ to obtain $j$: + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} +``` + +### Transform into an element search + +When the array does not contain `target`, $i$ and $j$ will eventually point to the first element greater and smaller than `target` respectively. + +Thus, as shown in the figure below, we can construct an element that does not exist in the array, to search for the left and right boundaries. + +- To find the leftmost `target`: it can be transformed into searching for `target - 0.5`, and return the pointer $i$. +- To find the rightmost `target`: it can be transformed into searching for `target + 0.5`, and return the pointer $j$. + +![Transforming the search for boundaries into the search for an element](binary_search_edge.assets/binary_search_edge_by_element.png) + +The code is omitted here, but here are two important points to note about this approach. + +- The given array `nums` does not contain decimal, so handling equal cases is not a concern. +- However, introducing decimals in this approach requires modifying the `target` variable to a floating-point type (no change needed in Python). diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png new file mode 100644 index 0000000000..facef94851 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png new file mode 100644 index 0000000000..bb76c5ab47 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png new file mode 100644 index 0000000000..f2add31d05 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png new file mode 100644 index 0000000000..09e73f03c5 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png new file mode 100644 index 0000000000..f64ab93a40 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png new file mode 100644 index 0000000000..5ba6b0f224 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png new file mode 100644 index 0000000000..c12343a816 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png new file mode 100644 index 0000000000..286035b295 Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png new file mode 100644 index 0000000000..9113545ddb Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png new file mode 100644 index 0000000000..4fa214235e Binary files /dev/null and b/en/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png differ diff --git a/en/docs/chapter_searching/binary_search_insertion.md b/en/docs/chapter_searching/binary_search_insertion.md new file mode 100644 index 0000000000..dd2c8a310e --- /dev/null +++ b/en/docs/chapter_searching/binary_search_insertion.md @@ -0,0 +1,91 @@ +# Binary search insertion + +Binary search is not only used to search for target elements but also to solve many variant problems, such as searching for the insertion position of target elements. + +## Case with no duplicate elements + +!!! question + + Given a sorted array `nums` of length $n$ with unique elements and an element `target`, insert `target` into `nums` while maintaining its sorted order. If `target` already exists in the array, insert it to the left of the existing element. Return the index of `target` in the array after insertion. See the example shown in the figure below. + +![Example data for binary search insertion point](binary_search_insertion.assets/binary_search_insertion_example.png) + +If you want to reuse the binary search code from the previous section, you need to answer the following two questions. + +**Question one**: If the array already contains `target`, would the insertion point be the index of existing element? + +The requirement to insert `target` to the left of equal elements means that the newly inserted `target` will replace the original `target` position. In other words, **when the array contains `target`, the insertion point is indeed the index of that `target`**. + +**Question two**: When the array does not contain `target`, at which index would it be inserted? + +Let's further consider the binary search process: when `nums[m] < target`, pointer $i$ moves, meaning that pointer $i$ is approaching an element greater than or equal to `target`. Similarly, pointer $j$ is always approaching an element less than or equal to `target`. + +Therefore, at the end of the binary, it is certain that: $i$ points to the first element greater than `target`, and $j$ points to the first element less than `target`. **It is easy to see that when the array does not contain `target`, the insertion point is $i$**. The code is as follows: + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} +``` + +## Case with duplicate elements + +!!! question + + Based on the previous question, assume the array may contain duplicate elements, all else remains the same. + +When there are multiple occurrences of `target` in the array, a regular binary search can only return the index of one occurrence of `target`, **and it cannot determine how many occurrences of `target` are to the left and right of that position**. + +The problem requires inserting the target element at the leftmost position, **so we need to find the index of the leftmost `target` in the array**. Initially consider implementing this through the steps shown in the figure below. + +1. Perform a binary search to find any index of `target`, say $k$. +2. Starting from index $k$, conduct a linear search to the left until the leftmost occurrence of `target` is found, then return this index. + +![Linear search for the insertion point of duplicate elements](binary_search_insertion.assets/binary_search_insertion_naive.png) + +Although this method is feasible, it includes linear search, so its time complexity is $O(n)$. This method is inefficient when the array contains many duplicate `target`s. + +Now consider extending the binary search code. As shown in the figure below, the overall process remains the same. In each round, we first calculate the middle index $m$, then compare the value of `target` with `nums[m]`, leading to the following cases. + +- When `nums[m] < target` or `nums[m] > target`, it means `target` has not been found yet, thus use the normal binary search to narrow the search range, **bringing pointers $i$ and $j$ closer to `target`**. +- When `nums[m] == target`, it indicates that the elements less than `target` are in the range $[i, m - 1]$, therefore use $j = m - 1$ to narrow the range, **thus bringing pointer $j$ closer to the elements less than `target`**. + +After the loop, $i$ points to the leftmost `target`, and $j$ points to the first element less than `target`, **therefore index $i$ is the insertion point**. + +=== "<1>" + ![Steps for binary search insertion point of duplicate elements](binary_search_insertion.assets/binary_search_insertion_step1.png) + +=== "<2>" + ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) + +=== "<3>" + ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) + +=== "<4>" + ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) + +=== "<5>" + ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) + +=== "<6>" + ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) + +=== "<7>" + ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) + +=== "<8>" + ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) + +Observe the following code. The operations in the branches `nums[m] > target` and `nums[m] == target` are the same, so these two branches can be merged. + +Even so, we can still keep the conditions expanded, as it makes the logic clearer and improves readability. + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} +``` + +!!! tip + + The code in this section uses "closed interval". If you are interested in "left-closed, right-open", try to implement the code on your own. + +In summary, binary search essentially involves setting search targets for pointers $i$ and $j$. These targets could be a specific element (like `target`) or a range of elements (such as those smaller than `target`). + +In the continuous loop of binary search, pointers $i$ and $j$ gradually approach the predefined target. Ultimately, they either find the answer or stop after crossing the boundary. diff --git a/en/docs/chapter_searching/index.md b/en/docs/chapter_searching/index.md new file mode 100644 index 0000000000..cf8ac7f2fa --- /dev/null +++ b/en/docs/chapter_searching/index.md @@ -0,0 +1,9 @@ +# Searching + +![Searching](../assets/covers/chapter_searching.jpg) + +!!! abstract + + Searching is an adventure into the unknown; where we may need to traverse every corner of a mysterious space, or perhaps we’ll quickly locate our target. + + On this journey of discovery, each exploration may end up with an unexpected answer. diff --git a/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png new file mode 100644 index 0000000000..8481ea4ecc Binary files /dev/null and b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png differ diff --git a/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png new file mode 100644 index 0000000000..8a74994f46 Binary files /dev/null and b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png differ diff --git a/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png new file mode 100644 index 0000000000..a0d1fe74e6 Binary files /dev/null and b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png differ diff --git a/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png new file mode 100644 index 0000000000..215beb34f4 Binary files /dev/null and b/en/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png differ diff --git a/en/docs/chapter_searching/replace_linear_by_hashing.md b/en/docs/chapter_searching/replace_linear_by_hashing.md new file mode 100644 index 0000000000..c34ea97b9b --- /dev/null +++ b/en/docs/chapter_searching/replace_linear_by_hashing.md @@ -0,0 +1,47 @@ +# Hash optimization strategies + +In algorithm problems, **we often reduce the time complexity of an algorithm by replacing a linear search with a hash-based search**. Let's use an algorithm problem to deepen the understanding. + +!!! question + + Given an integer array `nums` and a target element `target`, please search for two elements in the array whose "sum" equals `target`, and return their array indices. Any solution is acceptable. + +## Linear search: trading time for space + +Consider traversing through all possible combinations directly. As shown in the figure below, we initiate a nested loop, and in each iteration, we determine whether the sum of the two integers equals `target`. If so, we return their indices. + +![Linear search solution for two-sum problem](replace_linear_by_hashing.assets/two_sum_brute_force.png) + +The code is shown below: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_brute_force} +``` + +This method has a time complexity of $O(n^2)$ and a space complexity of $O(1)$, which can be very time-consuming with large data volumes. + +## Hash search: trading space for time + +Consider using a hash table, where the key-value pairs are the array elements and their indices, respectively. Loop through the array, performing the steps shown in the figure below during each iteration. + +1. Check if the number `target - nums[i]` is in the hash table. If so, directly return the indices of these two elements. +2. Add the key-value pair `nums[i]` and index `i` to the hash table. + +=== "<1>" + ![Help hash table solve two-sum](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + +=== "<2>" + ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) + +=== "<3>" + ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) + +The implementation code is shown below, requiring only a single loop: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_hash_table} +``` + +This method reduces the time complexity from $O(n^2)$ to $O(n)$ by using hash search, significantly enhancing runtime efficiency. + +As it requires maintaining an additional hash table, the space complexity is $O(n)$. **Nevertheless, this method has a more balanced time-space efficiency overall, making it the optimal solution for this problem**. diff --git a/en/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png b/en/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png new file mode 100644 index 0000000000..d8822564dd Binary files /dev/null and b/en/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png differ diff --git a/en/docs/chapter_searching/searching_algorithm_revisited.md b/en/docs/chapter_searching/searching_algorithm_revisited.md new file mode 100644 index 0000000000..edee2ff283 --- /dev/null +++ b/en/docs/chapter_searching/searching_algorithm_revisited.md @@ -0,0 +1,84 @@ +# Search algorithms revisited + +Searching algorithms (search algorithms) are used to retrieve one or more elements that meet specific criteria within data structures such as arrays, linked lists, trees, or graphs. + +Searching algorithms can be divided into the following two categories based on their approach. + +- **Locating the target element by traversing the data structure**, such as traversals of arrays, linked lists, trees, and graphs, etc. +- **Using the organizational structure of the data or existing data to achieve efficient element searches**, such as binary search, hash search, binary search tree search, etc. + +These topics were introduced in previous chapters, so they are not unfamiliar to us. In this section, we will revisit searching algorithms from a more systematic perspective. + +## Brute-force search + +A Brute-force search locates the target element by traversing every element of the data structure. + +- "Linear search" is suitable for linear data structures such as arrays and linked lists. It starts from one end of the data structure and accesses each element one by one until the target element is found or the other end is reached without finding the target element. +- "Breadth-first search" and "Depth-first search" are two traversal strategies for graphs and trees. Breadth-first search starts from the initial node and searches layer by layer (left to right), accessing nodes from near to far. Depth-first search starts from the initial node, follows a path until the end (top to bottom), then backtracks and tries other paths until the entire data structure is traversed. + +The advantage of brute-force search is its simplicity and versatility, **no need for data preprocessing or the help of additional data structures**. + +However, **the time complexity of this type of algorithm is $O(n)$**, where $n$ is the number of elements, so the performance is poor with large data sets. + +## Adaptive search + +An Adaptive search uses the unique properties of data (such as order) to optimize the search process, thereby locating the target element more efficiently. + +- "Binary search" uses the orderliness of data to achieve efficient searching, only suitable for arrays. +- "Hash search" uses a hash table to establish a key-value mapping between search data and target data, thus implementing the query operation. +- "Tree search" in a specific tree structure (such as a binary search tree), quickly eliminates nodes based on node value comparisons, thus locating the target element. + +The advantage of these algorithms is high efficiency, **with time complexities reaching $O(\log n)$ or even $O(1)$**. + +However, **using these algorithms often requires data preprocessing**. For example, binary search requires sorting the array in advance, and hash search and tree search both require the help of additional data structures. Maintaining these structures also requires more overhead in terms of time and space. + +!!! tip + + Adaptive search algorithms are often referred to as search algorithms, **mainly used for quickly retrieving target elements in specific data structures**. + +## Choosing a search method + +Given a set of data of size $n$, we can use a linear search, binary search, tree search, hash search, or other methods to retrieve the target element. The working principles of these methods are shown in the figure below. + +![Various search strategies](searching_algorithm_revisited.assets/searching_algorithms.png) + +The characteristics and operational efficiency of the aforementioned methods are shown in the following table. + +

Table   Comparison of search algorithm efficiency

+ +| | Linear search | Binary search | Tree search | Hash search | +| ------------------ | ------------- | --------------------- | --------------------------- | -------------------------- | +| Search element | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | +| Insert element | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| Delete element | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| Extra space | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | +| Data preprocessing | / | Sorting $O(n \log n)$ | Building tree $O(n \log n)$ | Building hash table $O(n)$ | +| Data orderliness | Unordered | Ordered | Ordered | Unordered | + +The choice of search algorithm also depends on the volume of data, search performance requirements, frequency of data queries and updates, etc. + +**Linear search** + +- Good versatility, no need for any data preprocessing operations. If we only need to query the data once, then the time for data preprocessing in the other three methods would be longer than the time for a linear search. +- Suitable for small volumes of data, where time complexity has a smaller impact on efficiency. +- Suitable for scenarios with very frequent data updates, because this method does not require any additional maintenance of the data. + +**Binary search** + +- Suitable for larger data volumes, with stable performance and a worst-case time complexity of $O(\log n)$. +- However, the data volume cannot be too large, because storing arrays requires contiguous memory space. +- Not suitable for scenarios with frequent additions and deletions, because maintaining an ordered array incurs a lot of overhead. + +**Hash search** + +- Suitable for scenarios where fast query performance is essential, with an average time complexity of $O(1)$. +- Not suitable for scenarios needing ordered data or range searches, because hash tables cannot maintain data orderliness. +- High dependency on hash functions and hash collision handling strategies, with significant performance degradation risks. +- Not suitable for overly large data volumes, because hash tables need extra space to minimize collisions and provide good query performance. + +**Tree search** + +- Suitable for massive data, because tree nodes are stored scattered in memory. +- Suitable for maintaining ordered data or range searches. +- With the continuous addition and deletion of nodes, the binary search tree may become skewed, degrading the time complexity to $O(n)$. +- If using AVL trees or red-black trees, operations can run stably at $O(\log n)$ efficiency, but the operation to maintain tree balance adds extra overhead. diff --git a/en/docs/chapter_searching/summary.md b/en/docs/chapter_searching/summary.md new file mode 100644 index 0000000000..d6169a17a6 --- /dev/null +++ b/en/docs/chapter_searching/summary.md @@ -0,0 +1,8 @@ +# Summary + +- Binary search depends on the order of data and performs the search by iteratively halving the search interval. It requires the input data to be sorted and is only applicable to arrays or array-based data structures. +- Brute force search may be required to locate an entry in an unordered dataset. Different search algorithms can be applied based on the data structure: Linear search is suitable for arrays and linked lists, while breadth-first search (BFS) and depth-first search (DFS) are suitable for graphs and trees. These algorithms are highly versatile, requiring no preprocessing of data, but they have a higher time complexity of $O(n)$. +- Hash search, tree search, and binary search are efficient search methods that can quickly locate target elements within specific data structures. These algorithms are highly efficient, with time complexities reaching $O(\log n)$ or even $O(1)$, but they usually require extra space to accommodate additional data structures. +- In practice, we need to analyze factors such as data volume, search performance requirements, data query and update frequencies, etc., to choose an appropriate search method. +- Linear search is ideal for small or frequently updated (volatile) data. Binary search works well for large and sorted data. Hash search is suitable for data that requires high query efficiency and does not need range queries. Tree search is best suited for large dynamic data that require maintaining order and need to support range queries. +- Replacing linear search with hash search is a common strategy to optimize runtime performance, reducing the time complexity from $O(n)$ to $O(1)$. diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png new file mode 100644 index 0000000000..a0f12a959a Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png new file mode 100644 index 0000000000..9784d2552b Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png new file mode 100644 index 0000000000..2a443e5b75 Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png new file mode 100644 index 0000000000..2f2ef74cdd Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png new file mode 100644 index 0000000000..2291b3b868 Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png new file mode 100644 index 0000000000..9aa77c42a2 Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png new file mode 100644 index 0000000000..2b3620e058 Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png b/en/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png new file mode 100644 index 0000000000..e9699f7d4a Binary files /dev/null and b/en/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png differ diff --git a/en/docs/chapter_sorting/bubble_sort.md b/en/docs/chapter_sorting/bubble_sort.md new file mode 100644 index 0000000000..577ec2eff1 --- /dev/null +++ b/en/docs/chapter_sorting/bubble_sort.md @@ -0,0 +1,59 @@ +# Bubble sort + +Bubble sort works by continuously comparing and swapping adjacent elements. This process is like bubbles rising from the bottom to the top, hence the name "bubble sort." + +As shown in the figure below, the bubbling process can be simulated using element swaps: start from the leftmost end of the array and move right, comparing each pair of adjacent elements. If the left element is greater than the right element, swap them. After the traversal, the largest element will have bubbled up to the rightmost end of the array. + +=== "<1>" + ![Simulating bubble process using element swap](bubble_sort.assets/bubble_operation_step1.png) + +=== "<2>" + ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) + +=== "<3>" + ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) + +=== "<4>" + ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) + +=== "<5>" + ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) + +=== "<6>" + ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) + +=== "<7>" + ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) + +## Algorithm process + +Assume the array has length $n$. The steps of bubble sort are shown in the figure below: + +1. First, perform one "bubble" pass on $n$ elements, **swapping the largest element to its correct position**. +2. Next, perform a "bubble" pass on the remaining $n - 1$ elements, **swapping the second largest element to its correct position**. +3. Continue in this manner; after $n - 1$ such passes, **the largest $n - 1$ elements will have been moved to their correct positions**. +4. The only remaining element **must** be the smallest, so **no** further sorting is required. At this point, the array is sorted. + +![Bubble sort process](bubble_sort.assets/bubble_sort_overview.png) + +Example code is as follows: + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort} +``` + +## Efficiency optimization + +If no swaps occur during a round of "bubbling," the array is already sorted, so we can return immediately. To detect this, we can add a `flag` variable; whenever no swaps are made in a pass, we set the flag and return early. + +Even with this optimization, the worst time complexity and average time complexity of bubble sort remains $O(n^2)$. However, if the input array is already sorted, the best-case time complexity can be as low as $O(n)$. + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} +``` + +## Algorithm characteristics + +- **Time complexity of $O(n^2)$, adaptive sorting.** Each round of "bubbling" traverses array segments of length $n - 1$, $n - 2$, $\dots$, $2$, $1$, which sums to $(n - 1) n / 2$. With a `flag` optimization, the best-case time complexity can reach $O(n)$ when the array is already sorted. +- **Space complexity of $O(1)$, in-place sorting.** Only a constant amount of extra space is used by pointers $i$ and $j$. +- **Stable sorting.** Because equal elements are not swapped during "bubbling," their original order is preserved, making this a stable sort. diff --git a/en/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png b/en/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png new file mode 100644 index 0000000000..143a436684 Binary files /dev/null and b/en/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png differ diff --git a/en/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png b/en/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png new file mode 100644 index 0000000000..1c56c04110 Binary files /dev/null and b/en/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png differ diff --git a/en/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png b/en/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png new file mode 100644 index 0000000000..3e768bc4c0 Binary files /dev/null and b/en/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png differ diff --git a/en/docs/chapter_sorting/bucket_sort.md b/en/docs/chapter_sorting/bucket_sort.md new file mode 100644 index 0000000000..0ff796b37e --- /dev/null +++ b/en/docs/chapter_sorting/bucket_sort.md @@ -0,0 +1,45 @@ +# Bucket sort + +The previously mentioned sorting algorithms are all "comparison-based sorting algorithms," which sort elements by comparing their values. Such sorting algorithms cannot have better time complexity of $O(n \log n)$. Next, we will discuss several "non-comparison sorting algorithms" that could achieve linear time complexity. + +Bucket sort is a typical application of the divide-and-conquer strategy. It works by setting up a series of ordered buckets, each containing a range of data, and distributing the input data evenly across these buckets. And then, the data in each bucket is sorted individually. Finally, the sorted data from all the buckets is merged in sequence to produce the final result. + +## Algorithm process + +Consider an array of length $n$, with float numbers in the range $[0, 1)$. The bucket sort process is illustrated in the figure below. + +1. Initialize $k$ buckets and distribute $n$ elements into these $k$ buckets. +2. Sort each bucket individually (using the built-in sorting function of the programming language). +3. Merge the results in the order from the smallest to the largest bucket. + +![Bucket sort algorithm process](bucket_sort.assets/bucket_sort_overview.png) + +The code is shown as follows: + +```src +[file]{bucket_sort}-[class]{}-[func]{bucket_sort} +``` + +## Algorithm characteristics + +Bucket sort is suitable for handling very large data sets. For example, if the input data includes 1 million elements, and system memory limitations prevent loading all the data at the same time, you can divide the data into 1,000 buckets and sort each bucket separately before merging the results. + +- **Time complexity is $O(n + k)$**: Assuming the elements are evenly distributed across the buckets, the number of elements in each bucket is $n/k$. Assuming sorting a single bucket takes $O(n/k \log(n/k))$ time, sorting all buckets takes $O(n \log(n/k))$ time. **When the number of buckets $k$ is relatively large, the time complexity approaches $O(n)$**. Merging the results requires traversing all buckets and elements, taking $O(n + k)$ time. In the worst case, all data is distributed into a single bucket, and sorting that bucket takes $O(n^2)$ time. +- **Space complexity is $O(n + k)$, non-in-place sorting**: It requires additional space for $k$ buckets and a total of $n$ elements. +- Whether bucket sort is stable depends on whether the sorting algorithm used within each bucket is stable. + +## How to achieve even distribution + +The theoretical time complexity of bucket sort can reach $O(n)$. **The key is to evenly distribute the elements across all buckets** as real-world data is often not uniformly distributed. For example, we may want to evenly distribute all products on eBay by price range into 10 buckets. However, the distribution of product prices may not be even, with many under $100 and few over $500. If the price range is evenly divided into 10, the difference in the number of products in each bucket will be significant. + +To achieve even distribution, we can initially set an approximate boundary to roughly divide the data into 3 buckets. **After the distribution is complete, the buckets with more items can be further divided into 3 buckets, until the number of elements in all buckets is roughly equal**. + +As shown in the figure below, this method essentially constructs a recursive tree, aiming to ensure the element counts in leaf nodes are as even as possible. Of course, you don't have to divide the data into 3 buckets each round - the partitioning strategy can be adaptively tailored to the data's unique characteristics. + +![Recursive division of buckets](bucket_sort.assets/scatter_in_buckets_recursively.png) + +If we know the probability distribution of product prices in advance, **we can set the price boundaries for each bucket based on the data probability distribution**. It is worth noting that it is not necessarily required to specifically calculate the data distribution; instead, it can be approximated based on data characteristics using a probability model. + +As shown in the figure below, assuming that product prices follow a normal distribution, we can define reasonable price intervals to balance the distribution of items across the buckets. + +![Dividing buckets based on probability distribution](bucket_sort.assets/scatter_in_buckets_distribution.png) diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png new file mode 100644 index 0000000000..f4db88af37 Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png new file mode 100644 index 0000000000..dcd3e4109f Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png new file mode 100644 index 0000000000..231abb0acb Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png new file mode 100644 index 0000000000..8023c33bb8 Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png new file mode 100644 index 0000000000..ab1e32f6a2 Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png new file mode 100644 index 0000000000..db0f420c13 Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png new file mode 100644 index 0000000000..193cfb602c Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png new file mode 100644 index 0000000000..065b025f27 Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png differ diff --git a/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png new file mode 100644 index 0000000000..85191884c2 Binary files /dev/null and b/en/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png differ diff --git a/en/docs/chapter_sorting/counting_sort.md b/en/docs/chapter_sorting/counting_sort.md new file mode 100644 index 0000000000..281b0d3150 --- /dev/null +++ b/en/docs/chapter_sorting/counting_sort.md @@ -0,0 +1,84 @@ +# Counting sort + +Counting sort achieves sorting by counting the number of elements, usually applied to integer arrays. + +## Simple implementation + +Let's start with a simple example. Given an array `nums` of length $n$, where all elements are "non-negative integers", the overall process of counting sort is shown in the figure below. + +1. Traverse the array to find the maximum number, denoted as $m$, then create an auxiliary array `counter` of length $m + 1$. +2. **Use `counter` to count the occurrence of each number in `nums`**, where `counter[num]` corresponds to the occurrence of the number `num`. The counting method is simple, just traverse `nums` (suppose the current number is `num`), and increase `counter[num]` by $1$ each round. +3. **Since the indices of `counter` are naturally ordered, all numbers are essentially sorted already**. Next, we traverse `counter`, and fill in `nums` in ascending order of occurrence. + +![Counting sort process](counting_sort.assets/counting_sort_overview.png) + +The code is shown below: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort_naive} +``` + +!!! note "Connection between counting sort and bucket sort" + + From the perspective of bucket sort, we can consider each index of the counting array `counter` in counting sort as a bucket, and the process of counting as distributing elements into the corresponding buckets. Essentially, counting sort is a special case of bucket sort for integer data. + +## Complete implementation + +Observant readers might notice, **if the input data is an object, the above step `3.` is invalid**. Suppose the input data is a product object, we want to sort the products by the price (a class member variable), but the above algorithm can only give the sorted price as the result. + +So how can we get the sorting result for the original data? First, we calculate the "prefix sum" of `counter`. As the name suggests, the prefix sum at index `i`, `prefix[i]`, equals the sum of the first `i` elements of the array: + +$$ +\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} +$$ + +**The prefix sum has a clear meaning, `prefix[num] - 1` represents the index of the last occurrence of element `num` in the result array `res`**. This information is crucial, as it tells us where each element should appear in the result array. Next, we traverse each element `num` of the original array `nums` in reverse order, performing the following two steps in each iteration. + +1. Fill `num` into the array `res` at the index `prefix[num] - 1`. +2. Decrease the prefix sum `prefix[num]` by $1$ to obtain the next index to place `num`. + +After the traversal, the array `res` contains the sorted result, and finally, `res` replaces the original array `nums`. The complete counting sort process is shown in the figure below. + +=== "<1>" + ![Counting sort process](counting_sort.assets/counting_sort_step1.png) + +=== "<2>" + ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) + +=== "<3>" + ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) + +=== "<4>" + ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) + +=== "<5>" + ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) + +=== "<6>" + ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) + +=== "<7>" + ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) + +=== "<8>" + ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) + +The implementation code of counting sort is shown below: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort} +``` + +## Algorithm characteristics + +- **Time complexity is $O(n + m)$, non-adaptive sort**: It involves traversing `nums` and `counter`, both using linear time. Generally, $n \gg m$, and the time complexity tends towards $O(n)$. +- **Space complexity is $O(n + m)$, non-in-place sort**: It uses array `res` of lengths $n$ and array `counter` of length $m$ respectively. +- **Stable sort**: Since elements are filled into `res` in a "right-to-left" order, reversing the traversal of `nums` can prevent changing the relative position between equal elements, thereby achieving a stable sort. Actually, traversing `nums` in order can also produce the correct sorting result, but the outcome is unstable. + +## Limitations + +By now, you might find counting sort very clever, as it can achieve efficient sorting merely by counting quantities. However, the prerequisites for using counting sort are relatively strict. + +**Counting sort is only suitable for non-negative integers**. If you want to apply it to other types of data, you need to ensure that these data can be converted to non-negative integers without changing the original order of the elements. For example, for an array containing negative integers, you can first add a constant to all numbers, converting them all to positive numbers, and then convert them back after sorting is complete. + +**Counting sort is suitable for large datasets with a small range of values**. For example, in the above example, $m$ should not be too large, otherwise, it will occupy too much space. And when $n \ll m$, counting sort uses $O(m)$ time, which may be slower than $O(n \log n)$ sorting algorithms. diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png new file mode 100644 index 0000000000..670b26a6b1 Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png new file mode 100644 index 0000000000..1f1b5180de Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png new file mode 100644 index 0000000000..4476f52107 Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png new file mode 100644 index 0000000000..ed065e5db9 Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png new file mode 100644 index 0000000000..587eaec820 Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png new file mode 100644 index 0000000000..4178dc6f7f Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png new file mode 100644 index 0000000000..c28742f840 Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png new file mode 100644 index 0000000000..568984c3fd Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png new file mode 100644 index 0000000000..ca48e5fbc0 Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png new file mode 100644 index 0000000000..aba72afd41 Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png new file mode 100644 index 0000000000..62540bb5da Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png differ diff --git a/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png new file mode 100644 index 0000000000..89aea66abd Binary files /dev/null and b/en/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png differ diff --git a/en/docs/chapter_sorting/heap_sort.md b/en/docs/chapter_sorting/heap_sort.md new file mode 100644 index 0000000000..0a735056ae --- /dev/null +++ b/en/docs/chapter_sorting/heap_sort.md @@ -0,0 +1,73 @@ +# Heap sort + +!!! tip + + Before reading this section, please ensure you have completed the "Heap" chapter. + +Heap sort is an efficient sorting algorithm based on the heap data structure. We can implement heap sort using the "heap creation" and "element extraction" operations we have already learned. + +1. Input the array and construct a min-heap, where the smallest element is at the top of the heap. +2. Continuously perform the extraction operation, record the extracted elements sequentially to obtain a sorted list from smallest to largest. + +Although the above method is feasible, it requires an additional array to store the popped elements, which is somewhat space-consuming. In practice, we usually use a more elegant implementation. + +## Algorithm flow + +Suppose the array length is $n$, the heap sort process is as follows. + +1. Input the array and establish a max-heap. After this step, the largest element is positioned at the top of the heap. +2. Swap the top element of the heap (the first element) with the heap's bottom element (the last element). Following this swap, reduce the heap's length by $1$ and increase the sorted elements count by $1$. +3. Starting from the heap top, perform the sift-down operation from top to bottom. After the sift-down, the heap's property is restored. +4. Repeat steps `2.` and `3.` Loop for $n - 1$ rounds to complete the sorting of the array. + +!!! tip + + In fact, the element extraction operation also includes steps `2.` and `3.`, with an additional step to pop (remove) the extracted element from the heap. + +=== "<1>" + ![Heap sort process](heap_sort.assets/heap_sort_step1.png) + +=== "<2>" + ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) + +=== "<3>" + ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) + +=== "<4>" + ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) + +=== "<5>" + ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) + +=== "<6>" + ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) + +=== "<7>" + ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) + +=== "<8>" + ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) + +=== "<9>" + ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) + +=== "<10>" + ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) + +=== "<11>" + ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) + +=== "<12>" + ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) + +In the code implementation, we used the sift-down function `sift_down()` from the "Heap" chapter. It is important to note that since the heap's length decreases as the maximum element is extracted, we need to add a length parameter $n$ to the `sift_down()` function to specify the current effective length of the heap. The code is shown below: + +```src +[file]{heap_sort}-[class]{}-[func]{heap_sort} +``` + +## Algorithm characteristics + +- **Time complexity is $O(n \log n)$, non-adaptive sort**: The heap creation uses $O(n)$ time. Extracting the largest element from the heap takes $O(\log n)$ time, looping for $n - 1$ rounds. +- **Space complexity is $O(1)$, in-place sort**: A few pointer variables use $O(1)$ space. The element swapping and heapifying operations are performed on the original array. +- **Non-stable sort**: The relative positions of equal elements may change during the swapping of the heap's top and bottom elements. diff --git a/en/docs/chapter_sorting/index.md b/en/docs/chapter_sorting/index.md new file mode 100644 index 0000000000..6694befbdf --- /dev/null +++ b/en/docs/chapter_sorting/index.md @@ -0,0 +1,9 @@ +# Sorting + +![Sorting](../assets/covers/chapter_sorting.jpg) + +!!! abstract + + Sorting is like a magical key that turns chaos into order, enabling us to understand and handle data more efficiently. + + Whether it's simple ascending order or complex categorical arrangements, sorting reveals the harmonious beauty of data. diff --git a/en/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png b/en/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png new file mode 100644 index 0000000000..8a5b35cd72 Binary files /dev/null and b/en/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png differ diff --git a/en/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png b/en/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png new file mode 100644 index 0000000000..b952187563 Binary files /dev/null and b/en/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png differ diff --git a/en/docs/chapter_sorting/insertion_sort.md b/en/docs/chapter_sorting/insertion_sort.md new file mode 100644 index 0000000000..aca1f8af1d --- /dev/null +++ b/en/docs/chapter_sorting/insertion_sort.md @@ -0,0 +1,46 @@ +# Insertion sort + +Insertion sort is a simple sorting algorithm that works very much like the process of manually sorting a deck of cards. + +Specifically, we select a base element from the unsorted interval, compare it with the elements in the sorted interval to its left, and insert the element into the correct position. + +The figure below illustrates how an element is inserted into the array. Assuming the base element is `base`, we need to shift all elements from the target index up to `base` one position to the right, then assign `base` to the target index. + +![Single insertion operation](insertion_sort.assets/insertion_operation.png) + +## Algorithm process + +The overall process of insertion sort is shown in the figure below. + +1. Consider the first element of the array as sorted. +2. Select the second element as `base`, insert it into its correct position, **leaving the first two elements sorted**. +3. Select the third element as `base`, insert it into its correct position, **leaving the first three elements sorted**. +4. Continuing in this manner, in the final iteration, the last element is taken as `base`, and after inserting it into the correct position, **all elements are sorted**. + +![Insertion sort process](insertion_sort.assets/insertion_sort_overview.png) + +Example code is as follows: + +```src +[file]{insertion_sort}-[class]{}-[func]{insertion_sort} +``` + +## Algorithm characteristics + +- **Time complexity is $O(n^2)$, adaptive sorting**: In the worst case, each insertion operation requires $n - 1$, $n-2$, ..., $2$, $1$ loops, summing up to $(n - 1) n / 2$, thus the time complexity is $O(n^2)$. In the case of ordered data, the insertion operation will terminate early. When the input array is completely ordered, insertion sort achieves the best time complexity of $O(n)$. +- **Space complexity is $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space. +- **Stable sorting**: During the insertion operation, we insert elements to the right of equal elements, not changing their order. + +## Advantages of insertion sort + +The time complexity of insertion sort is $O(n^2)$, while the time complexity of quicksort, which we will study next, is $O(n \log n)$. Although insertion sort has a higher time complexity, **it is usually faster in small input sizes**. + +This conclusion is similar to that for linear and binary search. Algorithms like quicksort that have a time complexity of $O(n \log n)$ and are based on the divide-and-conquer strategy often involve more unit operations. For small input sizes, the numerical values of $n^2$ and $n \log n$ are close, and complexity does not dominate, with the number of unit operations per round playing a decisive role. + +In fact, many programming languages (such as Java) use insertion sort within their built-in sorting functions. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategies, such as quicksort; for short arrays, use insertion sort directly. + +Although bubble sort, selection sort, and insertion sort all have a time complexity of $O(n^2)$, in practice, **insertion sort is commonly used than bubble sort and selection sort**, mainly for the following reasons. + +- Bubble sort is based on element swapping, which requires the use of a temporary variable, involving 3 unit operations; insertion sort is based on element assignment, requiring only 1 unit operation. Therefore, **the computational overhead of bubble sort is generally higher than that of insertion sort**. +- The time complexity of selection sort is always $O(n^2)$. **Given a set of partially ordered data, insertion sort is usually more efficient than selection sort**. +- Selection sort is unstable and cannot be applied to multi-level sorting. diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png new file mode 100644 index 0000000000..cdeaf9e704 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png new file mode 100644 index 0000000000..06f40f51c7 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png new file mode 100644 index 0000000000..466ee150d8 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png new file mode 100644 index 0000000000..0c48cbd5ee Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png new file mode 100644 index 0000000000..c58ca5c61b Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png new file mode 100644 index 0000000000..4ce1375bcb Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png new file mode 100644 index 0000000000..7a3eb200e0 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png new file mode 100644 index 0000000000..644d20b656 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png new file mode 100644 index 0000000000..3afa045b79 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png new file mode 100644 index 0000000000..7acf85f154 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png differ diff --git a/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png new file mode 100644 index 0000000000..5fee275b38 Binary files /dev/null and b/en/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png differ diff --git a/en/docs/chapter_sorting/merge_sort.md b/en/docs/chapter_sorting/merge_sort.md new file mode 100644 index 0000000000..7073ec387a --- /dev/null +++ b/en/docs/chapter_sorting/merge_sort.md @@ -0,0 +1,73 @@ +# Merge sort + +Merge sort is a sorting algorithm based on the divide-and-conquer strategy, involving the "divide" and "merge" phases shown in the figure below. + +1. **Divide phase**: Recursively split the array from the midpoint, transforming the sorting problem of a long array into shorter arrays. +2. **Merge phase**: Stop dividing when the length of the sub-array is 1, and then begin merging. The two shorter sorted arrays are continuously merged into a longer sorted array until the process is complete. + +![The divide and merge phases of merge sort](merge_sort.assets/merge_sort_overview.png) + +## Algorithm workflow + +As shown in the figure below, the "divide phase" recursively splits the array from the midpoint into two sub-arrays from top to bottom. + +1. Calculate the midpoint `mid`, recursively divide the left sub-array (interval `[left, mid]`) and the right sub-array (interval `[mid + 1, right]`). +2. Continue with step `1.` recursively until sub-array length becomes 1, then stops. + +The "merge phase" combines the left and right sub-arrays into a sorted array from bottom to top. It is important to note that, merging starts with sub-arrays of length 1, and each sub-array is sorted during the merge phase. + +=== "<1>" + ![Merge sort process](merge_sort.assets/merge_sort_step1.png) + +=== "<2>" + ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) + +=== "<3>" + ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) + +=== "<4>" + ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) + +=== "<5>" + ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) + +=== "<6>" + ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) + +=== "<7>" + ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) + +=== "<8>" + ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) + +=== "<9>" + ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) + +=== "<10>" + ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) + +It can be observed that the order of recursion in merge sort is consistent with the post-order traversal of a binary tree. + +- **Post-order traversal**: First recursively traverse the left subtree, then the right subtree, and finally process the root node. +- **Merge sort**: First recursively process the left sub-array, then the right sub-array, and finally perform the merge. + +The implementation of merge sort is shown in the following code. Note that the interval to be merged in `nums` is `[left, right]`, while the corresponding interval in `tmp` is `[0, right - left]`. + +```src +[file]{merge_sort}-[class]{}-[func]{merge_sort} +``` + +## Algorithm characteristics + +- **Time complexity of $O(n \log n)$, non-adaptive sort**: The division creates a recursion tree of height $\log n$, with each layer merging a total of $n$ operations, resulting in an overall time complexity of $O(n \log n)$. +- **Space complexity of $O(n)$, non-in-place sort**: The recursion depth is $\log n$, using $O(\log n)$ stack frame space. The merging operation requires auxiliary arrays, using an additional space of $O(n)$. +- **Stable sort**: During the merging process, the order of equal elements remains unchanged. + +## Linked List sorting + +For linked lists, merge sort has significant advantages over other sorting algorithms. **It can optimize the space complexity of the linked list sorting task to $O(1)$**. + +- **Divide phase**: "Iteration" can be used instead of "recursion" to perform the linked list division work, thus saving the stack frame space used by recursion. +- **Merge phase**: In linked lists, node insertion and deletion operations can be achieved by changing references (pointers), so no extra lists need to be created during the merge phase (combining two short ordered lists into one long ordered list). + +The implementation details are relatively complex, and interested readers can consult related materials for learning. diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png new file mode 100644 index 0000000000..43a43e0abb Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png new file mode 100644 index 0000000000..4248ea6485 Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png new file mode 100644 index 0000000000..308cf42aad Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png new file mode 100644 index 0000000000..ec00e8f0d5 Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png new file mode 100644 index 0000000000..618cbdb21c Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png new file mode 100644 index 0000000000..eee63e20c7 Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png new file mode 100644 index 0000000000..c87abb445d Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png new file mode 100644 index 0000000000..3ada3943ea Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png new file mode 100644 index 0000000000..16b04ed173 Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png differ diff --git a/en/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png b/en/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png new file mode 100644 index 0000000000..1bea05c64d Binary files /dev/null and b/en/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png differ diff --git a/en/docs/chapter_sorting/quick_sort.md b/en/docs/chapter_sorting/quick_sort.md new file mode 100644 index 0000000000..9ca1df7b97 --- /dev/null +++ b/en/docs/chapter_sorting/quick_sort.md @@ -0,0 +1,101 @@ +# Quick sort + +Quick sort is a sorting algorithm based on the divide-and-conquer strategy, known for its efficiency and wide application. + +The core operation of quick sort is "pivot partitioning," which aims to select an element from the array as the "pivot" and move all elements less than the pivot to its left side, while moving all elements greater than the pivot to its right side. Specifically, the process of pivot partitioning is illustrated in the figure below. + +1. Select the leftmost element of the array as the pivot, and initialize two pointers `i` and `j` to point to the two ends of the array respectively. +2. Set up a loop where each round uses `i` (`j`) to search for the first element larger (smaller) than the pivot, then swap these two elements. +3. Repeat step `2.` until `i` and `j` meet, finally swap the pivot to the boundary between the two sub-arrays. + +=== "<1>" + ![Pivot division process](quick_sort.assets/pivot_division_step1.png) + +=== "<2>" + ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) + +=== "<3>" + ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) + +=== "<4>" + ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) + +=== "<5>" + ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) + +=== "<6>" + ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) + +=== "<7>" + ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) + +=== "<8>" + ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) + +=== "<9>" + ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) + +After the pivot partitioning, the original array is divided into three parts: left sub-array, pivot, and right sub-array, satisfying "any element in the left sub-array $\leq$ pivot $\leq$ any element in the right sub-array." Therefore, we then only need to sort these two sub-arrays. + +!!! note "Divide-and-conquer strategy for quick sort" + + The essence of pivot partitioning is to simplify the sorting problem of a longer array into two shorter arrays. + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{partition} +``` + +## Algorithm process + +The overall process of quick sort is shown in the figure below. + +1. First, perform a "pivot partitioning" on the original array to obtain the unsorted left and right sub-arrays. +2. Then, recursively perform "pivot partitioning" on the left and right sub-arrays separately. +3. Continue recursively until the length of sub-array is 1, thus completing the sorting of the entire array. + +![Quick sort process](quick_sort.assets/quick_sort_overview.png) + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} +``` + +## Algorithm features + +- **Time complexity of $O(n \log n)$, non-adaptive sorting**: In average cases, the recursive levels of pivot partitioning are $\log n$, and the total number of loops per level is $n$, using $O(n \log n)$ time overall. In the worst case, each round of pivot partitioning divides an array of length $n$ into two sub-arrays of lengths $0$ and $n - 1$, when the number of recursive levels reaches $n$, the number of loops in each level is $n$, and the total time used is $O(n^2)$. +- **Space complexity of $O(n)$, in-place sorting**: In the case where the input array is completely reversed, the worst recursive depth reaches $n$, using $O(n)$ stack frame space. The sorting operation is performed on the original array without the aid of additional arrays. +- **Non-stable sorting**: In the final step of pivot partitioning, the pivot may be swapped to the right of equal elements. + +## Why is quick sort fast + +As the name suggests, quick sort should have certain advantages in terms of efficiency. Although the average time complexity of quick sort is the same as that of "merge sort" and "heap sort," it is generally more efficient for the following reasons. + +- **Low probability of worst-case scenarios**: Although the worst time complexity of quick sort is $O(n^2)$, less stable than merge sort, in most cases, quick sort can operate under a time complexity of $O(n \log n)$. +- **High cache utilization**: During the pivot partitioning operation, the system can load the entire sub-array into the cache, thus accessing elements more efficiently. In contrast, algorithms like "heap sort" need to access elements in a jumping manner, lacking this feature. +- **Small constant coefficient of complexity**: Among the three algorithms mentioned above, quick sort has the least total number of operations such as comparisons, assignments, and swaps. This is similar to why "insertion sort" is faster than "bubble sort." + +## Pivot optimization + +**Quick sort's time efficiency may degrade under certain inputs**. For example, if the input array is completely reversed, since we select the leftmost element as the pivot, after the pivot partitioning, the pivot is swapped to the array's right end, causing the left sub-array length to be $n - 1$ and the right sub-array length to be $0$. Continuing this way, each round of pivot partitioning will have a sub-array length of $0$, and the divide-and-conquer strategy fails, degrading quick sort to a form similar to "bubble sort." + +To avoid this situation, **we can optimize the pivot selection strategy in the pivot partitioning**. For instance, we can randomly select an element as the pivot. However, if luck is not on our side, and we consistently select suboptimal pivots, the efficiency is still not satisfactory. + +It's important to note that programming languages usually generate "pseudo-random numbers". If we construct a specific test case for a pseudo-random number sequence, the efficiency of quick sort may still degrade. + +For further improvement, we can select three candidate elements (usually the first, last, and midpoint elements of the array), **and use the median of these three candidate elements as the pivot**. This way, the probability that the pivot is "neither too small nor too large" will be greatly increased. Of course, we can also select more candidate elements to further enhance robustness of the algorithm. With this method, the probability of the time complexity degrading to $O(n^2)$ is greatly reduced. + +Sample code is as follows: + +```src +[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} +``` + +## Tail recursion optimization + +**Under certain inputs, quick sort may occupy more space**. For example, consider a completely ordered input array. Let the length of the sub-array in the recursion be $m$. In each round of pivot partitioning, a left sub-array of length $0$ and a right sub-array of length $m - 1$ are produced. This means that the problem size is reduced by only one element per recursive call, resulting in a very small reduction at each level of recursion. +As a result, the height of the recursion tree can reach $n − 1$ , which requires $O(n)$ of stack frame space. + +To prevent the accumulation of stack frame space, we can compare the lengths of the two sub-arrays after each round of pivot sorting, **and only recursively sort the shorter sub-array**. Since the length of the shorter sub-array will not exceed $n / 2$, this method ensures that the recursion depth does not exceed $\log n$, thus optimizing the worst space complexity to $O(\log n)$. The code is as follows: + +```src +[file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} +``` diff --git a/en/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png b/en/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png new file mode 100644 index 0000000000..60e0ef9cc4 Binary files /dev/null and b/en/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png differ diff --git a/en/docs/chapter_sorting/radix_sort.md b/en/docs/chapter_sorting/radix_sort.md new file mode 100644 index 0000000000..08612d796a --- /dev/null +++ b/en/docs/chapter_sorting/radix_sort.md @@ -0,0 +1,41 @@ +# Radix sort + +The previous section introduced counting sort, which is suitable for scenarios where the data size $n$ is large but the data range $m$ is small. Suppose we need to sort $n = 10^6$ student IDs, where each ID is an $8$-digit number. This means the data range $m = 10^8$ is very large. Using counting sort in this case would require significant memory space. Radix sort can avoid this situation. + +Radix sort shares the same core concept as counting sort, which also sorts by counting the frequency of elements. Meanwhile, radix sort builds upon this by utilizing the progressive relationship between the digits of numbers. It processes and sorts the digits one at a time, achieving the final sorted order. + +## Algorithm process + +Taking the student ID data as an example, assume the least significant digit is the $1^{st}$ and the most significant is the $8^{th}$, the radix sort process is illustrated in the figure below. + +1. Initialize digit $k = 1$. +2. Perform "counting sort" on the $k^{th}$ digit of the student IDs. After completion, the data will be sorted from smallest to largest based on the $k^{th}$ digit. +3. Increment $k$ by $1$, then return to step `2.` and continue iterating until all digits have been sorted, at which point the process ends. + +![Radix sort algorithm process](radix_sort.assets/radix_sort_overview.png) + +Below we dissect the code implementation. For a number $x$ in base $d$, to obtain its $k^{th}$ digit $x_k$, the following calculation formula can be used: + +$$ +x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d +$$ + +Where $\lfloor a \rfloor$ denotes rounding down the floating point number $a$, and $\bmod \: d$ denotes taking the modulus of $d$. For student ID data, $d = 10$ and $k \in [1, 8]$. + +Additionally, we need to slightly modify the counting sort code to allow sorting based on the $k^{th}$ digit: + +```src +[file]{radix_sort}-[class]{}-[func]{radix_sort} +``` + +!!! question "Why start sorting from the least significant digit?" + + In consecutive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the result of the first round is $a < b$ and the second round is $a > b$, the second round's result will replace the first round's result. Since higher-order digits take precedence over lower-order digits, it makes sense to sort the lower digits before the higher digits. + +## Algorithm characteristics + +Compared to counting sort, radix sort is suitable for larger numerical ranges, **but it assumes that the data can be represented in a fixed number of digits, and the number of digits should not be too large**. For example, floating-point numbers are unsuitable for radix sort, as their digit count $k$ may be large, potentially leading to a time complexity $O(nk) \gg O(n^2)$. + +- **Time complexity is $O(nk)$, non-adaptive sorting**: Assuming the data size is $n$, the data is in base $d$, and the maximum number of digits is $k$, then sorting a single digit takes $O(n + d)$ time, and sorting all $k$ digits takes $O((n + d)k)$ time. Generally, both $d$ and $k$ are relatively small, leading to a time complexity approaching $O(n)$. +- **Space complexity is $O(n + d)$, non-in-place sorting**: Like counting sort, radix sort relies on arrays `res` and `counter` of lengths $n$ and $d$ respectively. +- **Stable sorting**: When counting sort is stable, radix sort is also stable; if counting sort is unstable, radix sort cannot ensure a correct sorting order. diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png new file mode 100644 index 0000000000..a510641c3c Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png new file mode 100644 index 0000000000..18d643639c Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png new file mode 100644 index 0000000000..37c8a83a78 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png new file mode 100644 index 0000000000..d47f924406 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png new file mode 100644 index 0000000000..11a33be969 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png new file mode 100644 index 0000000000..6c47435f4f Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png new file mode 100644 index 0000000000..3c0d4ac897 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png new file mode 100644 index 0000000000..7091771316 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png new file mode 100644 index 0000000000..1ec8c8c1b3 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png new file mode 100644 index 0000000000..e5c2ed8107 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png new file mode 100644 index 0000000000..be6b1285a4 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png differ diff --git a/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png new file mode 100644 index 0000000000..a689267801 Binary files /dev/null and b/en/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png differ diff --git a/en/docs/chapter_sorting/selection_sort.md b/en/docs/chapter_sorting/selection_sort.md new file mode 100644 index 0000000000..3b62a435be --- /dev/null +++ b/en/docs/chapter_sorting/selection_sort.md @@ -0,0 +1,58 @@ +# Selection sort + +Selection sort works on a very simple principle: it uses a loop where each iteration selects the smallest element from the unsorted interval and moves it to the end of the sorted section. + +Suppose the length of the array is $n$, the steps of selection sort is shown in the figure below. + +1. Initially, all elements are unsorted, i.e., the unsorted (index) interval is $[0, n-1]$. +2. Select the smallest element in the interval $[0, n-1]$ and swap it with the element at index $0$. After this, the first element of the array is sorted. +3. Select the smallest element in the interval $[1, n-1]$ and swap it with the element at index $1$. After this, the first two elements of the array are sorted. +4. Continue in this manner. After $n - 1$ rounds of selection and swapping, the first $n - 1$ elements are sorted. +5. The only remaining element is subsequently the largest element and does not need sorting, thus the array is sorted. + +=== "<1>" + ![Selection sort process](selection_sort.assets/selection_sort_step1.png) + +=== "<2>" + ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) + +=== "<3>" + ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) + +=== "<4>" + ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) + +=== "<5>" + ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) + +=== "<6>" + ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) + +=== "<7>" + ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) + +=== "<8>" + ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) + +=== "<9>" + ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) + +=== "<10>" + ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) + +=== "<11>" + ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) + +In the code, we use $k$ to record the smallest element within the unsorted interval: + +```src +[file]{selection_sort}-[class]{}-[func]{selection_sort} +``` + +## Algorithm characteristics + +- **Time complexity of $O(n^2)$, non-adaptive sort**: There are $n - 1$ iterations in the outer loop, with the length of the unsorted section starting at $n$ in the first iteration and decreasing to $2$ in the last iteration, i.e., each outer loop iterations contain $n$, $n - 1$, $\dots$, $3$, $2$ inner loop iterations respectively, summing up to $\frac{(n - 1)(n + 2)}{2}$. +- **Space complexity of $O(1)$, in-place sort**: Uses constant extra space with pointers $i$ and $j$. +- **Non-stable sort**: As shown in the figure below, an element `nums[i]` may be swapped to the right of an equal element, causing their relative order to change. + +![Selection sort instability example](selection_sort.assets/selection_sort_instability.png) diff --git a/en/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png b/en/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png new file mode 100644 index 0000000000..33e6786ddf Binary files /dev/null and b/en/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png differ diff --git a/en/docs/chapter_sorting/sorting_algorithm.md b/en/docs/chapter_sorting/sorting_algorithm.md new file mode 100644 index 0000000000..5980af21c3 --- /dev/null +++ b/en/docs/chapter_sorting/sorting_algorithm.md @@ -0,0 +1,46 @@ +# Sorting algorithms + +Sorting algorithms are used to arrange a set of data in a specific order. Sorting algorithms have a wide range of applications because ordered data can usually be searched, analyzed, and processed more efficiently. + +As shown in the figure below, the data types in sorting algorithms can be integers, floating point numbers, characters, or strings, etc. Sorting criterion can be set according to needs, such as numerical size, character ASCII order, or custom criterion. + +![Data types and comparator examples](sorting_algorithm.assets/sorting_examples.png) + +## Evaluation dimensions + +**Execution efficiency**: We expect the time complexity of sorting algorithms to be as low as possible, as well as a lower number of overall operations (lowering the constant term of time complexity). For large data volumes, execution efficiency is particularly important. + +**In-place property**: As the name implies, in-place sorting is achieved by directly manipulating the original array, without the need for additional helper arrays, thus saving memory. Generally, in-place sorting involves fewer data moving operations and is faster. + +**Stability**: Stable sorting ensures that the relative order of equal elements in the array does not change after sorting. + +Stable sorting is a necessary condition for multi-key sorting scenarios. Suppose we have a table storing student information, with the first and second columns being name and age, respectively. In this case, unstable sorting might lead to a loss of order in the input data: + +```shell +# Input data is sorted by name +# (name, age) + ('A', 19) + ('B', 18) + ('C', 21) + ('D', 19) + ('E', 23) + +# Assuming an unstable sorting algorithm is used to sort the list by age, +# the result changes the relative position of ('D', 19) and ('A', 19), +# and the property of the input data being sorted by name is lost + ('B', 18) + ('D', 19) + ('A', 19) + ('C', 21) + ('E', 23) +``` + +**Adaptability**: Adaptive sorting leverages existing order information within the input data to reduce computational effort, achieving more optimal time efficiency. The best-case time complexity of adaptive sorting algorithms is typically better than their average-case time complexity. + +**Comparison or non-comparison-based**: Comparison-based sorting relies on comparison operators ($<$, $=$, $>$) to determine the relative order of elements and thus sort the entire array, with the theoretical optimal time complexity being $O(n \log n)$. Meanwhile, non-comparison sorting does not use comparison operators and can achieve a time complexity of $O(n)$, but its versatility is relatively poor. + +## Ideal sorting algorithm + +**Fast execution, in-place, stable, adaptive, and versatile**. Clearly, no sorting algorithm that combines all these features has been found to date. Therefore, when selecting a sorting algorithm, it is necessary to decide based on the specific characteristics of the data and the requirements of the problem. + +Next, we will learn about various sorting algorithms together and analyze the advantages and disadvantages of each based on the above evaluation dimensions. diff --git a/en/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png b/en/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png new file mode 100644 index 0000000000..48b4c7c73c Binary files /dev/null and b/en/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png differ diff --git a/en/docs/chapter_sorting/summary.md b/en/docs/chapter_sorting/summary.md new file mode 100644 index 0000000000..e367d48efb --- /dev/null +++ b/en/docs/chapter_sorting/summary.md @@ -0,0 +1,47 @@ +# Summary + +### Key review + +- Bubble sort works by swapping adjacent elements. By adding a flag to enable early return, we can optimize the best-case time complexity of bubble sort to $O(n)$. +- Insertion sort sorts each round by inserting elements from the unsorted interval into the correct position in the sorted interval. Although the time complexity of insertion sort is $O(n^2)$, it is very popular in sorting small amounts of data due to relatively fewer operations per unit. +- Quick sort is based on sentinel partitioning operations. In sentinel partitioning, it's possible to always pick the worst pivot, leading to a time complexity degradation to $O(n^2)$. Introducing median or random pivots can reduce the probability of such degradation. Tail recursion effectively reduce the recursion depth, optimizing the space complexity to $O(\log n)$. +- Merge sort includes dividing and merging two phases, typically embodying the divide-and-conquer strategy. In merge sort, sorting an array requires creating auxiliary arrays, resulting in a space complexity of $O(n)$; however, the space complexity for sorting a list can be optimized to $O(1)$. +- Bucket sort consists of three steps: distributing data into buckets, sorting within each bucket, and merging results in bucket order. It also embodies the divide-and-conquer strategy, suitable for very large datasets. The key to bucket sort is the even distribution of data. +- Counting sort is a variant of bucket sort, which sorts by counting the occurrences of each data point. Counting sort is suitable for large datasets with a limited range of data and requires data conversion to positive integers. +- Radix sort processes data by sorting it digit by digit, requiring data to be represented as fixed-length numbers. +- Overall, we seek sorting algorithm that has high efficiency, stability, in-place operation, and adaptability. However, like other data structures and algorithms, no sorting algorithm can meet all these conditions simultaneously. In practical applications, we need to choose the appropriate sorting algorithm based on the characteristics of the data. +- The figure below compares mainstream sorting algorithms in terms of efficiency, stability, in-place nature, and adaptability. + +![Sorting Algorithm Comparison](summary.assets/sorting_algorithms_comparison.png) + +### Q & A + +**Q**: When is the stability of sorting algorithms necessary? + +In reality, we might sort based on one attribute of an object. For example, students have names and heights as attributes, and we aim to implement multi-level sorting: first by name to get `(A, 180) (B, 185) (C, 170) (D, 170)`; then by height. Because the sorting algorithm is unstable, we might end up with `(D, 170) (C, 170) (A, 180) (B, 185)`. + +It can be seen that the positions of students D and C have been swapped, disrupting the orderliness of the names, which is undesirable. + +**Q**: Can the order of "searching from right to left" and "searching from left to right" in sentinel partitioning be swapped? + +No, when using the leftmost element as the pivot, we must first "search from right to left" then "search from left to right". This conclusion is somewhat counterintuitive, so let's analyze the reason. + +The last step of the sentinel partition `partition()` is to swap `nums[left]` and `nums[i]`. After the swap, the elements to the left of the pivot are all `<=` the pivot, **which requires that `nums[left] >= nums[i]` must hold before the last swap**. Suppose we "search from left to right" first, and if no element larger than the pivot is found, **we will exit the loop when `i == j`, possibly with `nums[j] == nums[i] > nums[left]`**. In other words, the final swap operation will exchange an element larger than the pivot to the left end of the array, causing the sentinel partition to fail. + +For example, given the array `[0, 0, 0, 0, 1]`, if we first "search from left to right", the array after the sentinel partition is `[1, 0, 0, 0, 0]`, which is incorrect. + +Upon further consideration, if we choose `nums[right]` as the pivot, then exactly the opposite, we must first "search from left to right". + +**Q**: Regarding tail recursion optimization, why does choosing the shorter array ensure that the recursion depth does not exceed $\log n$? + +The recursion depth is the number of currently unreturned recursive methods. Each round of sentinel partition divides the original array into two subarrays. With tail recursion optimization, the length of the subarray to be recursively followed is at most half of the original array length. Assuming the worst case always halves the length, the final recursion depth will be $\log n$. + +Reviewing the original quicksort, we might continuously recursively process larger arrays, in the worst case from $n$, $n - 1$, ..., $2$, $1$, with a recursion depth of $n$. Tail recursion optimization can avoid this scenario. + +**Q**: When all elements in the array are equal, is the time complexity of quicksort $O(n^2)$? How should this degenerate case be handled? + +Yes. For this situation, consider using sentinel partitioning to divide the array into three parts: less than, equal to, and greater than the pivot. Only recursively proceed with the less than and greater than parts. In this method, an array where all input elements are equal can be sorted in just one round of sentinel partitioning. + +**Q**: Why is the worst-case time complexity of bucket sort $O(n^2)$? + +In the worst case, all elements are placed in the same bucket. If we use an $O(n^2)$ algorithm to sort these elements, the time complexity will be $O(n^2)$. diff --git a/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png new file mode 100644 index 0000000000..4825a7e358 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png new file mode 100644 index 0000000000..bff545e789 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png new file mode 100644 index 0000000000..f7fe476cf6 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png new file mode 100644 index 0000000000..88509cec5d Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png new file mode 100644 index 0000000000..6027679189 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/deque_operations.png b/en/docs/chapter_stack_and_queue/deque.assets/deque_operations.png new file mode 100644 index 0000000000..c1bb5252b1 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/deque_operations.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png new file mode 100644 index 0000000000..39d546d4ff Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png new file mode 100644 index 0000000000..6a1e8811aa Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png new file mode 100644 index 0000000000..11d7ae76d1 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png new file mode 100644 index 0000000000..ac4b073225 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png new file mode 100644 index 0000000000..1707646008 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png differ diff --git a/en/docs/chapter_stack_and_queue/deque.md b/en/docs/chapter_stack_and_queue/deque.md new file mode 100644 index 0000000000..b0d0e0ca76 --- /dev/null +++ b/en/docs/chapter_stack_and_queue/deque.md @@ -0,0 +1,405 @@ +# Double-ended queue + +In a queue, we can only delete elements from the head or add elements to the tail. As shown in the figure below, a double-ended queue (deque) offers more flexibility, allowing the addition or removal of elements at both the head and the tail. + +![Operations in double-ended queue](deque.assets/deque_operations.png) + +## Common operations in double-ended queue + +The common operations in a double-ended queue are listed below, and the names of specific methods depend on the programming language used. + +

Table   Efficiency of double-ended queue operations

+ +| Method Name | Description | Time Complexity | +| ------------- | -------------------------- | --------------- | +| `pushFirst()` | Add an element to the head | $O(1)$ | +| `pushLast()` | Add an element to the tail | $O(1)$ | +| `popFirst()` | Remove the first element | $O(1)$ | +| `popLast()` | Remove the last element | $O(1)$ | +| `peekFirst()` | Access the first element | $O(1)$ | +| `peekLast()` | Access the last element | $O(1)$ | + +Similarly, we can directly use the double-ended queue classes implemented in programming languages: + +=== "Python" + + ```python title="deque.py" + from collections import deque + + # Initialize the deque + deq: deque[int] = deque() + + # Enqueue elements + deq.append(2) # Add to the tail + deq.append(5) + deq.append(4) + deq.appendleft(3) # Add to the head + deq.appendleft(1) + + # Access elements + front: int = deq[0] # The first element + rear: int = deq[-1] # The last element + + # Dequeue elements + pop_front: int = deq.popleft() # The first element dequeued + pop_rear: int = deq.pop() # The last element dequeued + + # Get the length of the deque + size: int = len(deq) + + # Check if the deque is empty + is_empty: bool = len(deq) == 0 + ``` + +=== "C++" + + ```cpp title="deque.cpp" + /* Initialize the deque */ + deque deque; + + /* Enqueue elements */ + deque.push_back(2); // Add to the tail + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // Add to the head + deque.push_front(1); + + /* Access elements */ + int front = deque.front(); // The first element + int back = deque.back(); // The last element + + /* Dequeue elements */ + deque.pop_front(); // The first element dequeued + deque.pop_back(); // The last element dequeued + + /* Get the length of the deque */ + int size = deque.size(); + + /* Check if the deque is empty */ + bool empty = deque.empty(); + ``` + +=== "Java" + + ```java title="deque.java" + /* Initialize the deque */ + Deque deque = new LinkedList<>(); + + /* Enqueue elements */ + deque.offerLast(2); // Add to the tail + deque.offerLast(5); + deque.offerLast(4); + deque.offerFirst(3); // Add to the head + deque.offerFirst(1); + + /* Access elements */ + int peekFirst = deque.peekFirst(); // The first element + int peekLast = deque.peekLast(); // The last element + + /* Dequeue elements */ + int popFirst = deque.pollFirst(); // The first element dequeued + int popLast = deque.pollLast(); // The last element dequeued + + /* Get the length of the deque */ + int size = deque.size(); + + /* Check if the deque is empty */ + boolean isEmpty = deque.isEmpty(); + ``` + +=== "C#" + + ```csharp title="deque.cs" + /* Initialize the deque */ + // In C#, LinkedList is used as a deque + LinkedList deque = new(); + + /* Enqueue elements */ + deque.AddLast(2); // Add to the tail + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // Add to the head + deque.AddFirst(1); + + /* Access elements */ + int peekFirst = deque.First.Value; // The first element + int peekLast = deque.Last.Value; // The last element + + /* Dequeue elements */ + deque.RemoveFirst(); // The first element dequeued + deque.RemoveLast(); // The last element dequeued + + /* Get the length of the deque */ + int size = deque.Count; + + /* Check if the deque is empty */ + bool isEmpty = deque.Count == 0; + ``` + +=== "Go" + + ```go title="deque_test.go" + /* Initialize the deque */ + // In Go, use list as a deque + deque := list.New() + + /* Enqueue elements */ + deque.PushBack(2) // Add to the tail + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) // Add to the head + deque.PushFront(1) + + /* Access elements */ + front := deque.Front() // The first element + rear := deque.Back() // The last element + + /* Dequeue elements */ + deque.Remove(front) // The first element dequeued + deque.Remove(rear) // The last element dequeued + + /* Get the length of the deque */ + size := deque.Len() + + /* Check if the deque is empty */ + isEmpty := deque.Len() == 0 + ``` + +=== "Swift" + + ```swift title="deque.swift" + /* Initialize the deque */ + // Swift does not have a built-in deque class, so Array can be used as a deque + var deque: [Int] = [] + + /* Enqueue elements */ + deque.append(2) // Add to the tail + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) // Add to the head + deque.insert(1, at: 0) + + /* Access elements */ + let peekFirst = deque.first! // The first element + let peekLast = deque.last! // The last element + + /* Dequeue elements */ + // Using Array, popFirst has a complexity of O(n) + let popFirst = deque.removeFirst() // The first element dequeued + let popLast = deque.removeLast() // The last element dequeued + + /* Get the length of the deque */ + let size = deque.count + + /* Check if the deque is empty */ + let isEmpty = deque.isEmpty + ``` + +=== "JS" + + ```javascript title="deque.js" + /* Initialize the deque */ + // JavaScript does not have a built-in deque, so Array is used as a deque + const deque = []; + + /* Enqueue elements */ + deque.push(2); + deque.push(5); + deque.push(4); + // Note that unshift() has a time complexity of O(n) as it's an array + deque.unshift(3); + deque.unshift(1); + + /* Access elements */ + const peekFirst = deque[0]; // The first element + const peekLast = deque[deque.length - 1]; // The last element + + /* Dequeue elements */ + // Note that shift() has a time complexity of O(n) as it's an array + const popFront = deque.shift(); // The first element dequeued + const popBack = deque.pop(); // The last element dequeued + + /* Get the length of the deque */ + const size = deque.length; + + /* Check if the deque is empty */ + const isEmpty = size === 0; + ``` + +=== "TS" + + ```typescript title="deque.ts" + /* Initialize the deque */ + // TypeScript does not have a built-in deque, so Array is used as a deque + const deque: number[] = []; + + /* Enqueue elements */ + deque.push(2); + deque.push(5); + deque.push(4); + // Note that unshift() has a time complexity of O(n) as it's an array + deque.unshift(3); + deque.unshift(1); + + /* Access elements */ + const peekFirst: number = deque[0]; // The first element + const peekLast: number = deque[deque.length - 1]; // The last element + + /* Dequeue elements */ + // Note that shift() has a time complexity of O(n) as it's an array + const popFront: number = deque.shift() as number; // The first element dequeued + const popBack: number = deque.pop() as number; // The last element dequeued + + /* Get the length of the deque */ + const size: number = deque.length; + + /* Check if the deque is empty */ + const isEmpty: boolean = size === 0; + ``` + +=== "Dart" + + ```dart title="deque.dart" + /* Initialize the deque */ + // In Dart, Queue is defined as a deque + Queue deque = Queue(); + + /* Enqueue elements */ + deque.addLast(2); // Add to the tail + deque.addLast(5); + deque.addLast(4); + deque.addFirst(3); // Add to the head + deque.addFirst(1); + + /* Access elements */ + int peekFirst = deque.first; // The first element + int peekLast = deque.last; // The last element + + /* Dequeue elements */ + int popFirst = deque.removeFirst(); // The first element dequeued + int popLast = deque.removeLast(); // The last element dequeued + + /* Get the length of the deque */ + int size = deque.length; + + /* Check if the deque is empty */ + bool isEmpty = deque.isEmpty; + ``` + +=== "Rust" + + ```rust title="deque.rs" + /* Initialize the deque */ + let mut deque: VecDeque = VecDeque::new(); + + /* Enqueue elements */ + deque.push_back(2); // Add to the tail + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // Add to the head + deque.push_front(1); + + /* Access elements */ + if let Some(front) = deque.front() { // The first element + } + if let Some(rear) = deque.back() { // The last element + } + + /* Dequeue elements */ + if let Some(pop_front) = deque.pop_front() { // The first element dequeued + } + if let Some(pop_rear) = deque.pop_back() { // The last element dequeued + } + + /* Get the length of the deque */ + let size = deque.len(); + + /* Check if the deque is empty */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="deque.c" + // C does not provide a built-in deque + ``` + +=== "Kotlin" + + ```kotlin title="deque.kt" + + ``` + +=== "Zig" + + ```zig title="deque.zig" + + ``` + +??? pythontutor "Visualizing Code" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Implementing a double-ended queue * + +The implementation of a double-ended queue is similar to that of a regular queue, it can be based on either a linked list or an array as the underlying data structure. + +### Implementation based on doubly linked list + +Recall from the previous section that we used a regular singly linked list to implement a queue, as it conveniently allows for deleting from the head (corresponding to the dequeue operation) and adding new elements after the tail (corresponding to the enqueue operation). + +For a double-ended queue, both the head and the tail can perform enqueue and dequeue operations. In other words, a double-ended queue needs to implement operations in the opposite direction as well. For this, we use a "doubly linked list" as the underlying data structure of the double-ended queue. + +As shown in the figure below, we treat the head and tail nodes of the doubly linked list as the front and rear of the double-ended queue, respectively, and implement the functionality to add and remove nodes at both ends. + +=== "LinkedListDeque" + ![Implementing Double-Ended Queue with Doubly Linked List for Enqueue and Dequeue Operations](deque.assets/linkedlist_deque_step1.png) + +=== "pushLast()" + ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) + +=== "pushFirst()" + ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) + +=== "popLast()" + ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) + +=== "popFirst()" + ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) + +The implementation code is as follows: + +```src +[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} +``` + +### Implementation based on array + +As shown in the figure below, similar to implementing a queue with an array, we can also use a circular array to implement a double-ended queue. + +=== "ArrayDeque" + ![Implementing Double-Ended Queue with Array for Enqueue and Dequeue Operations](deque.assets/array_deque_step1.png) + +=== "pushLast()" + ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) + +=== "pushFirst()" + ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) + +=== "popLast()" + ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) + +=== "popFirst()" + ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) + +The implementation only needs to add methods for "front enqueue" and "rear dequeue": + +```src +[file]{array_deque}-[class]{array_deque}-[func]{} +``` + +## Applications of double-ended queue + +The double-ended queue combines the logic of both stacks and queues, **thus, it can implement all their respective use cases while offering greater flexibility**. + +We know that software's "undo" feature is typically implemented using a stack: the system `pushes` each change operation onto the stack and then `pops` to implement undoing. However, considering the limitations of system resources, software often restricts the number of undo steps (for example, only allowing the last 50 steps). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (the front of the queue). **But a regular stack cannot perform this function, where a double-ended queue becomes necessary**. Note that the core logic of "undo" still follows the Last-In-First-Out principle of a stack, but a double-ended queue can more flexibly implement some additional logic. diff --git a/en/docs/chapter_stack_and_queue/index.md b/en/docs/chapter_stack_and_queue/index.md new file mode 100644 index 0000000000..7d2d4ff558 --- /dev/null +++ b/en/docs/chapter_stack_and_queue/index.md @@ -0,0 +1,9 @@ +# Stack and queue + +![Stack and queue](../assets/covers/chapter_stack_and_queue.jpg) + +!!! abstract + + A stack is like cats placed on top of each other, while a queue is like cats lined up one by one. + + They represent the logical relationships of Last-In-First-Out (LIFO) and First-In-First-Out (FIFO), respectively. diff --git a/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png b/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png new file mode 100644 index 0000000000..5480fdcd27 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png differ diff --git a/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png b/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png new file mode 100644 index 0000000000..d012e6e81d Binary files /dev/null and b/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png differ diff --git a/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png b/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png new file mode 100644 index 0000000000..775db5ce21 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png differ diff --git a/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png b/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png new file mode 100644 index 0000000000..a91e20d85e Binary files /dev/null and b/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png differ diff --git a/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png b/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png new file mode 100644 index 0000000000..f818fb0e7c Binary files /dev/null and b/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png differ diff --git a/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png b/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png new file mode 100644 index 0000000000..d49c2fcc1e Binary files /dev/null and b/en/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png differ diff --git a/en/docs/chapter_stack_and_queue/queue.assets/queue_operations.png b/en/docs/chapter_stack_and_queue/queue.assets/queue_operations.png new file mode 100644 index 0000000000..9cd76f263e Binary files /dev/null and b/en/docs/chapter_stack_and_queue/queue.assets/queue_operations.png differ diff --git a/en/docs/chapter_stack_and_queue/queue.md b/en/docs/chapter_stack_and_queue/queue.md new file mode 100755 index 0000000000..186f932d77 --- /dev/null +++ b/en/docs/chapter_stack_and_queue/queue.md @@ -0,0 +1,381 @@ +# Queue + +A queue is a linear data structure that follows the First-In-First-Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers join the queue at the rear, and the person at the front leaves the queue first. + +As shown in the figure below, we call the front of the queue the "head" and the back the "tail." The operation of adding elements to the rear of the queue is termed "enqueue," and the operation of removing elements from the front is termed "dequeue." + +![Queue's first-in-first-out rule](queue.assets/queue_operations.png) + +## Common operations on queue + +The common operations on a queue are shown in the table below. Note that method names may vary across different programming languages. Here, we use the same naming convention as that used for stacks. + +

Table   Efficiency of queue operations

+ +| Method Name | Description | Time Complexity | +| ----------- | -------------------------------------- | --------------- | +| `push()` | Enqueue an element, add it to the tail | $O(1)$ | +| `pop()` | Dequeue the head element | $O(1)$ | +| `peek()` | Access the head element | $O(1)$ | + +We can directly use the ready-made queue classes in programming languages: + +=== "Python" + + ```python title="queue.py" + from collections import deque + + # Initialize the queue + # In Python, we generally use the deque class as a queue + # Although queue.Queue() is a pure queue class, it's not very user-friendly, so it's not recommended + que: deque[int] = deque() + + # Enqueue elements + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + + # Access the first element + front: int = que[0] + + # Dequeue an element + pop: int = que.popleft() + + # Get the length of the queue + size: int = len(que) + + # Check if the queue is empty + is_empty: bool = len(que) == 0 + ``` + +=== "C++" + + ```cpp title="queue.cpp" + /* Initialize the queue */ + queue queue; + + /* Enqueue elements */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* Access the first element*/ + int front = queue.front(); + + /* Dequeue an element */ + queue.pop(); + + /* Get the length of the queue */ + int size = queue.size(); + + /* Check if the queue is empty */ + bool empty = queue.empty(); + ``` + +=== "Java" + + ```java title="queue.java" + /* Initialize the queue */ + Queue queue = new LinkedList<>(); + + /* Enqueue elements */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + + /* Access the first element */ + int peek = queue.peek(); + + /* Dequeue an element */ + int pop = queue.poll(); + + /* Get the length of the queue */ + int size = queue.size(); + + /* Check if the queue is empty */ + boolean isEmpty = queue.isEmpty(); + ``` + +=== "C#" + + ```csharp title="queue.cs" + /* Initialize the queue */ + Queue queue = new(); + + /* Enqueue elements */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + + /* Access the first element */ + int peek = queue.Peek(); + + /* Dequeue an element */ + int pop = queue.Dequeue(); + + /* Get the length of the queue */ + int size = queue.Count; + + /* Check if the queue is empty */ + bool isEmpty = queue.Count == 0; + ``` + +=== "Go" + + ```go title="queue_test.go" + /* Initialize the queue */ + // In Go, use list as a queue + queue := list.New() + + /* Enqueue elements */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + + /* Access the first element */ + peek := queue.Front() + + /* Dequeue an element */ + pop := queue.Front() + queue.Remove(pop) + + /* Get the length of the queue */ + size := queue.Len() + + /* Check if the queue is empty */ + isEmpty := queue.Len() == 0 + ``` + +=== "Swift" + + ```swift title="queue.swift" + /* Initialize the queue */ + // Swift does not have a built-in queue class, so Array can be used as a queue + var queue: [Int] = [] + + /* Enqueue elements */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + + /* Access the first element */ + let peek = queue.first! + + /* Dequeue an element */ + // Since it's an array, removeFirst has a complexity of O(n) + let pool = queue.removeFirst() + + /* Get the length of the queue */ + let size = queue.count + + /* Check if the queue is empty */ + let isEmpty = queue.isEmpty + ``` + +=== "JS" + + ```javascript title="queue.js" + /* Initialize the queue */ + // JavaScript does not have a built-in queue, so Array can be used as a queue + const queue = []; + + /* Enqueue elements */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* Access the first element */ + const peek = queue[0]; + + /* Dequeue an element */ + // Since the underlying structure is an array, shift() method has a time complexity of O(n) + const pop = queue.shift(); + + /* Get the length of the queue */ + const size = queue.length; + + /* Check if the queue is empty */ + const empty = queue.length === 0; + ``` + +=== "TS" + + ```typescript title="queue.ts" + /* Initialize the queue */ + // TypeScript does not have a built-in queue, so Array can be used as a queue + const queue: number[] = []; + + /* Enqueue elements */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* Access the first element */ + const peek = queue[0]; + + /* Dequeue an element */ + // Since the underlying structure is an array, shift() method has a time complexity of O(n) + const pop = queue.shift(); + + /* Get the length of the queue */ + const size = queue.length; + + /* Check if the queue is empty */ + const empty = queue.length === 0; + ``` + +=== "Dart" + + ```dart title="queue.dart" + /* Initialize the queue */ + // In Dart, the Queue class is a double-ended queue but can be used as a queue + Queue queue = Queue(); + + /* Enqueue elements */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + + /* Access the first element */ + int peek = queue.first; + + /* Dequeue an element */ + int pop = queue.removeFirst(); + + /* Get the length of the queue */ + int size = queue.length; + + /* Check if the queue is empty */ + bool isEmpty = queue.isEmpty; + ``` + +=== "Rust" + + ```rust title="queue.rs" + /* Initialize the double-ended queue */ + // In Rust, use a double-ended queue as a regular queue + let mut deque: VecDeque = VecDeque::new(); + + /* Enqueue elements */ + deque.push_back(1); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + + /* Access the first element */ + if let Some(front) = deque.front() { + } + + /* Dequeue an element */ + if let Some(pop) = deque.pop_front() { + } + + /* Get the length of the queue */ + let size = deque.len(); + + /* Check if the queue is empty */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="queue.c" + // C does not provide a built-in queue + ``` + +=== "Kotlin" + + ```kotlin title="queue.kt" + + ``` + +=== "Zig" + + ```zig title="queue.zig" + + ``` + +??? pythontutor "Code Visualization" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Implementing a queue + +To implement a queue, we need a data structure that allows adding elements at one end and removing them at the other. Both linked lists and arrays meet this requirement. + +### Implementation based on a linked list + +As shown in the figure below, we can consider the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively. It is stipulated that nodes can only be added at the rear and removed at the front. + +=== "LinkedListQueue" + ![Implementing Queue with Linked List for Enqueue and Dequeue Operations](queue.assets/linkedlist_queue_step1.png) + +=== "push()" + ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) + +=== "pop()" + ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) + +Below is the code for implementing a queue using a linked list: + +```src +[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} +``` + +### Implementation based on an array + +Deleting the first element in an array has a time complexity of $O(n)$, which would make the dequeue operation inefficient. However, this problem can be cleverly avoided as follows. + +We use a variable `front` to indicate the index of the front element and maintain a variable `size` to record the queue's length. Define `rear = front + size`, which points to the position immediately following the tail element. + +With this design, **the effective interval of elements in the array is `[front, rear - 1]`**. The implementation methods for various operations are shown in the figure below. + +- Enqueue operation: Assign the input element to the `rear` index and increase `size` by 1. +- Dequeue operation: Simply increase `front` by 1 and decrease `size` by 1. + +Both enqueue and dequeue operations only require a single operation, each with a time complexity of $O(1)$. + +=== "ArrayQueue" + ![Implementing Queue with Array for Enqueue and Dequeue Operations](queue.assets/array_queue_step1.png) + +=== "push()" + ![array_queue_push](queue.assets/array_queue_step2_push.png) + +=== "pop()" + ![array_queue_pop](queue.assets/array_queue_step3_pop.png) + +You might notice a problem: as enqueue and dequeue operations are continuously performed, both `front` and `rear` move to the right and **will eventually reach the end of the array and can't move further**. To resolve this, we can treat the array as a "circular array" where connecting the end of the array back to its beginning. + +In a circular array, `front` or `rear` needs to loop back to the start of the array upon reaching the end. This cyclical pattern can be achieved with a "modulo operation" as shown in the code below: + +```src +[file]{array_queue}-[class]{array_queue}-[func]{} +``` + +The above implementation of the queue still has its limitations: its length is fixed. However, this issue is not difficult to resolve. We can replace the array with a dynamic array that can expand itself if needed. Interested readers can try to implement this themselves. + +The comparison of the two implementations is consistent with that of the stack and is not repeated here. + +## Typical applications of queue + +- **Amazon orders**: After shoppers place orders, these orders join a queue, and the system processes them in order. During events like Singles' Day, a massive number of orders are generated in a short time, making high concurrency a key challenge for engineers. +- **Various to-do lists**: Any scenario requiring a "first-come, first-served" functionality, such as a printer's task queue or a restaurant's food delivery queue, can effectively maintain the order of processing with a queue. diff --git a/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png b/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png new file mode 100644 index 0000000000..ca3205cf09 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png differ diff --git a/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png b/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png new file mode 100644 index 0000000000..d5dd08c7fc Binary files /dev/null and b/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png differ diff --git a/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png b/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png new file mode 100644 index 0000000000..1eb84b0509 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png differ diff --git a/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png b/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png new file mode 100644 index 0000000000..68d31f058b Binary files /dev/null and b/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png differ diff --git a/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png b/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png new file mode 100644 index 0000000000..de10323e9c Binary files /dev/null and b/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png differ diff --git a/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png b/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png new file mode 100644 index 0000000000..bee4facf03 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png differ diff --git a/en/docs/chapter_stack_and_queue/stack.assets/stack_operations.png b/en/docs/chapter_stack_and_queue/stack.assets/stack_operations.png new file mode 100644 index 0000000000..336ce12d89 Binary files /dev/null and b/en/docs/chapter_stack_and_queue/stack.assets/stack_operations.png differ diff --git a/en/docs/chapter_stack_and_queue/stack.md b/en/docs/chapter_stack_and_queue/stack.md new file mode 100755 index 0000000000..0548ec18eb --- /dev/null +++ b/en/docs/chapter_stack_and_queue/stack.md @@ -0,0 +1,389 @@ +# Stack + +A stack is a linear data structure that follows the principle of Last-In-First-Out (LIFO). + +We can compare a stack to a pile of plates on a table. To access the bottom plate, one must first remove the plates on top. By replacing the plates with various types of elements (such as integers, characters, objects, etc.), we obtain the data structure known as a stack. + +As shown in the figure below, we refer to the top of the pile of elements as the "top of the stack" and the bottom as the "bottom of the stack." The operation of adding elements to the top of the stack is called "push," and the operation of removing the top element is called "pop." + +![Stack's last-in-first-out rule](stack.assets/stack_operations.png) + +## Common operations on stack + +The common operations on a stack are shown in the table below. The specific method names depend on the programming language used. Here, we use `push()`, `pop()`, and `peek()` as examples. + +

Table   Efficiency of stack operations

+ +| Method | Description | Time Complexity | +| -------- | ----------------------------------------------- | --------------- | +| `push()` | Push an element onto the stack (add to the top) | $O(1)$ | +| `pop()` | Pop the top element from the stack | $O(1)$ | +| `peek()` | Access the top element of the stack | $O(1)$ | + +Typically, we can directly use the stack class built into the programming language. However, some languages may not specifically provide a stack class. In these cases, we can use the language's "array" or "linked list" as a stack and ignore operations that are not related to stack logic in the program. + +=== "Python" + + ```python title="stack.py" + # Initialize the stack + # Python does not have a built-in stack class, so a list can be used as a stack + stack: list[int] = [] + + # Push elements onto the stack + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + # Access the top element of the stack + peek: int = stack[-1] + + # Pop an element from the stack + pop: int = stack.pop() + + # Get the length of the stack + size: int = len(stack) + + # Check if the stack is empty + is_empty: bool = len(stack) == 0 + ``` + +=== "C++" + + ```cpp title="stack.cpp" + /* Initialize the stack */ + stack stack; + + /* Push elements onto the stack */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Access the top element of the stack */ + int top = stack.top(); + + /* Pop an element from the stack */ + stack.pop(); // No return value + + /* Get the length of the stack */ + int size = stack.size(); + + /* Check if the stack is empty */ + bool empty = stack.empty(); + ``` + +=== "Java" + + ```java title="stack.java" + /* Initialize the stack */ + Stack stack = new Stack<>(); + + /* Push elements onto the stack */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Access the top element of the stack */ + int peek = stack.peek(); + + /* Pop an element from the stack */ + int pop = stack.pop(); + + /* Get the length of the stack */ + int size = stack.size(); + + /* Check if the stack is empty */ + boolean isEmpty = stack.isEmpty(); + ``` + +=== "C#" + + ```csharp title="stack.cs" + /* Initialize the stack */ + Stack stack = new(); + + /* Push elements onto the stack */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + + /* Access the top element of the stack */ + int peek = stack.Peek(); + + /* Pop an element from the stack */ + int pop = stack.Pop(); + + /* Get the length of the stack */ + int size = stack.Count; + + /* Check if the stack is empty */ + bool isEmpty = stack.Count == 0; + ``` + +=== "Go" + + ```go title="stack_test.go" + /* Initialize the stack */ + // In Go, it is recommended to use a Slice as a stack + var stack []int + + /* Push elements onto the stack */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + + /* Access the top element of the stack */ + peek := stack[len(stack)-1] + + /* Pop an element from the stack */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + /* Get the length of the stack */ + size := len(stack) + + /* Check if the stack is empty */ + isEmpty := len(stack) == 0 + ``` + +=== "Swift" + + ```swift title="stack.swift" + /* Initialize the stack */ + // Swift does not have a built-in stack class, so Array can be used as a stack + var stack: [Int] = [] + + /* Push elements onto the stack */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + /* Access the top element of the stack */ + let peek = stack.last! + + /* Pop an element from the stack */ + let pop = stack.removeLast() + + /* Get the length of the stack */ + let size = stack.count + + /* Check if the stack is empty */ + let isEmpty = stack.isEmpty + ``` + +=== "JS" + + ```javascript title="stack.js" + /* Initialize the stack */ + // JavaScript does not have a built-in stack class, so Array can be used as a stack + const stack = []; + + /* Push elements onto the stack */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Access the top element of the stack */ + const peek = stack[stack.length-1]; + + /* Pop an element from the stack */ + const pop = stack.pop(); + + /* Get the length of the stack */ + const size = stack.length; + + /* Check if the stack is empty */ + const is_empty = stack.length === 0; + ``` + +=== "TS" + + ```typescript title="stack.ts" + /* Initialize the stack */ + // TypeScript does not have a built-in stack class, so Array can be used as a stack + const stack: number[] = []; + + /* Push elements onto the stack */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Access the top element of the stack */ + const peek = stack[stack.length - 1]; + + /* Pop an element from the stack */ + const pop = stack.pop(); + + /* Get the length of the stack */ + const size = stack.length; + + /* Check if the stack is empty */ + const is_empty = stack.length === 0; + ``` + +=== "Dart" + + ```dart title="stack.dart" + /* Initialize the stack */ + // Dart does not have a built-in stack class, so List can be used as a stack + List stack = []; + + /* Push elements onto the stack */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + + /* Access the top element of the stack */ + int peek = stack.last; + + /* Pop an element from the stack */ + int pop = stack.removeLast(); + + /* Get the length of the stack */ + int size = stack.length; + + /* Check if the stack is empty */ + bool isEmpty = stack.isEmpty; + ``` + +=== "Rust" + + ```rust title="stack.rs" + /* Initialize the stack */ + // Use Vec as a stack + let mut stack: Vec = Vec::new(); + + /* Push elements onto the stack */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Access the top element of the stack */ + let top = stack.last().unwrap(); + + /* Pop an element from the stack */ + let pop = stack.pop().unwrap(); + + /* Get the length of the stack */ + let size = stack.len(); + + /* Check if the stack is empty */ + let is_empty = stack.is_empty(); + ``` + +=== "C" + + ```c title="stack.c" + // C does not provide a built-in stack + ``` + +=== "Kotlin" + + ```kotlin title="stack.kt" + + ``` + +=== "Zig" + + ```zig title="stack.zig" + + ``` + +??? pythontutor "Code Visualization" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Implementing a stack + +To gain a deeper understanding of how a stack operates, let's try implementing a stack class ourselves. + +A stack follows the principle of Last-In-First-Out, which means we can only add or remove elements at the top of the stack. However, both arrays and linked lists allow adding and removing elements at any position, **therefore a stack can be seen as a restricted array or linked list**. In other words, we can "shield" certain irrelevant operations of an array or linked list, aligning their external behavior with the characteristics of a stack. + +### Implementation based on a linked list + +When implementing a stack using a linked list, we can consider the head node of the list as the top of the stack and the tail node as the bottom of the stack. + +As shown in the figure below, for the push operation, we simply insert elements at the head of the linked list. This method of node insertion is known as "head insertion." For the pop operation, we just need to remove the head node from the list. + +=== "LinkedListStack" + ![Implementing Stack with Linked List for Push and Pop Operations](stack.assets/linkedlist_stack_step1.png) + +=== "push()" + ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) + +=== "pop()" + ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) + +Below is an example code for implementing a stack based on a linked list: + +```src +[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} +``` + +### Implementation based on an array + +When implementing a stack using an array, we can consider the end of the array as the top of the stack. As shown in the figure below, push and pop operations correspond to adding and removing elements at the end of the array, respectively, both with a time complexity of $O(1)$. + +=== "ArrayStack" + ![Implementing Stack with Array for Push and Pop Operations](stack.assets/array_stack_step1.png) + +=== "push()" + ![array_stack_push](stack.assets/array_stack_step2_push.png) + +=== "pop()" + ![array_stack_pop](stack.assets/array_stack_step3_pop.png) + +Since the elements to be pushed onto the stack may continuously increase, we can use a dynamic array, thus avoiding the need to handle array expansion ourselves. Here is an example code: + +```src +[file]{array_stack}-[class]{array_stack}-[func]{} +``` + +## Comparison of the two implementations + +**Supported Operations** + +Both implementations support all the operations defined in a stack. The array implementation additionally supports random access, but this is beyond the scope of a stack definition and is generally not used. + +**Time Efficiency** + +In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and therefore higher efficiency. However, if the push operation exceeds the array capacity, it triggers a resizing mechanism, making the time complexity of that push operation $O(n)$. + +In the linked list implementation, list expansion is very flexible, and there is no efficiency decrease issue as in array expansion. However, the push operation requires initializing a node object and modifying pointers, so its efficiency is relatively lower. If the elements being pushed are already node objects, then the initialization step can be skipped, improving efficiency. + +Thus, when the elements for push and pop operations are basic data types like `int` or `double`, we can draw the following conclusions: + +- The array-based stack implementation's efficiency decreases during expansion, but since expansion is a low-frequency operation, its average efficiency is higher. +- The linked list-based stack implementation provides more stable efficiency performance. + +**Space Efficiency** + +When initializing a list, the system allocates an "initial capacity," which might exceed the actual need; moreover, the expansion mechanism usually increases capacity by a specific factor (like doubling), which may also exceed the actual need. Therefore, **the array-based stack might waste some space**. + +However, since linked list nodes require extra space for storing pointers, **the space occupied by linked list nodes is relatively larger**. + +In summary, we cannot simply determine which implementation is more memory-efficient. It requires analysis based on specific circumstances. + +## Typical applications of stack + +- **Back and forward in browsers, undo and redo in software**. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to go back to the previous page through the back operation, which is essentially a pop operation. To support both back and forward, two stacks are needed to work together. +- **Memory management in programs**. Each time a function is called, the system adds a stack frame at the top of the stack to record the function's context information. In recursive functions, the downward recursion phase keeps pushing onto the stack, while the upward backtracking phase keeps popping from the stack. diff --git a/en/docs/chapter_stack_and_queue/summary.md b/en/docs/chapter_stack_and_queue/summary.md new file mode 100644 index 0000000000..a76a814339 --- /dev/null +++ b/en/docs/chapter_stack_and_queue/summary.md @@ -0,0 +1,31 @@ +# Summary + +### Key review + +- Stack is a data structure that follows the Last-In-First-Out (LIFO) principle and can be implemented using arrays or linked lists. +- In terms of time efficiency, the array implementation of the stack has a higher average efficiency. However, during expansion, the time complexity for a single push operation can degrade to $O(n)$. In contrast, the linked list implementation of a stack offers more stable efficiency. +- Regarding space efficiency, the array implementation of the stack may lead to a certain degree of space wastage. However, it's important to note that the memory space occupied by nodes in a linked list is generally larger than that for elements in an array. +- A queue is a data structure that follows the First-In-First-Out (FIFO) principle, and it can also be implemented using arrays or linked lists. The conclusions regarding time and space efficiency for queues are similar to those for stacks. +- A double-ended queue (deque) is a more flexible type of queue that allows adding and removing elements at both ends. + +### Q & A + +**Q**: Is the browser's forward and backward functionality implemented with a doubly linked list? + +A browser's forward and backward navigation is essentially a manifestation of the "stack" concept. When a user visits a new page, the page is added to the top of the stack; when they click the back button, the page is popped from the top of the stack. A double-ended queue (deque) can conveniently implement some additional operations, as mentioned in the "Double-Ended Queue" section. + +**Q**: After popping from a stack, is it necessary to free the memory of the popped node? + +If the popped node will still be used later, it's not necessary to free its memory. In languages like Java and Python that have automatic garbage collection, manual memory release is not necessary; in C and C++, manual memory release is required. + +**Q**: A double-ended queue seems like two stacks joined together. What are its uses? + +A double-ended queue, which is a combination of a stack and a queue or two stacks joined together, exhibits both stack and queue logic. Thus, it can implement all applications of stacks and queues while offering more flexibility. + +**Q**: How exactly are undo and redo implemented? + +Undo and redo operations are implemented using two stacks: Stack `A` for undo and Stack `B` for redo. + +1. Each time a user performs an operation, it is pushed onto Stack `A`, and Stack `B` is cleared. +2. When the user executes an "undo", the most recent operation is popped from Stack `A` and pushed onto Stack `B`. +3. When the user executes a "redo", the most recent operation is popped from Stack `B` and pushed back onto Stack `A`. diff --git a/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png new file mode 100644 index 0000000000..0baa747745 Binary files /dev/null and b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png differ diff --git a/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png new file mode 100644 index 0000000000..b1059688f2 Binary files /dev/null and b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png differ diff --git a/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png new file mode 100644 index 0000000000..ca81c0449f Binary files /dev/null and b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png differ diff --git a/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png new file mode 100644 index 0000000000..3e280d8428 Binary files /dev/null and b/en/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png differ diff --git a/en/docs/chapter_tree/array_representation_of_tree.md b/en/docs/chapter_tree/array_representation_of_tree.md new file mode 100644 index 0000000000..a2e814a587 --- /dev/null +++ b/en/docs/chapter_tree/array_representation_of_tree.md @@ -0,0 +1,164 @@ +# Array representation of binary trees + +Under the linked list representation, the storage unit of a binary tree is a node `TreeNode`, with nodes connected by pointers. The basic operations of binary trees under the linked list representation were introduced in the previous section. + +So, can we use an array to represent a binary tree? The answer is yes. + +## Representing perfect binary trees + +Let's analyze a simple case first. Given a perfect binary tree, we store all nodes in an array according to the order of level-order traversal, where each node corresponds to a unique array index. + +Based on the characteristics of level-order traversal, we can deduce a "mapping formula" between the index of a parent node and its children: **If a node's index is $i$, then the index of its left child is $2i + 1$ and the right child is $2i + 2$**. The figure below shows the mapping relationship between the indices of various nodes. + +![Array representation of a perfect binary tree](array_representation_of_tree.assets/array_representation_binary_tree.png) + +**The mapping formula plays a role similar to the node references (pointers) in linked lists**. Given any node in the array, we can access its left (right) child node using the mapping formula. + +## Representing any binary tree + +Perfect binary trees are a special case; there are often many `None` values in the middle levels of a binary tree. Since the sequence of level-order traversal does not include these `None` values, we cannot solely rely on this sequence to deduce the number and distribution of `None` values. **This means that multiple binary tree structures can match the same level-order traversal sequence**. + +As shown in the figure below, given a non-perfect binary tree, the above method of array representation fails. + +![Level-order traversal sequence corresponds to multiple binary tree possibilities](array_representation_of_tree.assets/array_representation_without_empty.png) + +To solve this problem, **we can consider explicitly writing out all `None` values in the level-order traversal sequence**. As shown in the figure below, after this treatment, the level-order traversal sequence can uniquely represent a binary tree. Example code is as follows: + +=== "Python" + + ```python title="" + # Array representation of a binary tree + # Using None to represent empty slots + tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + ``` + +=== "C++" + + ```cpp title="" + /* Array representation of a binary tree */ + // Using the maximum integer value INT_MAX to mark empty slots + vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Java" + + ```java title="" + /* Array representation of a binary tree */ + // Using the Integer wrapper class allows for using null to mark empty slots + Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ``` + +=== "C#" + + ```csharp title="" + /* Array representation of a binary tree */ + // Using nullable int (int?) allows for using null to mark empty slots + int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Go" + + ```go title="" + /* Array representation of a binary tree */ + // Using an any type slice, allowing for nil to mark empty slots + tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + ``` + +=== "Swift" + + ```swift title="" + /* Array representation of a binary tree */ + // Using optional Int (Int?) allows for using nil to mark empty slots + let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "JS" + + ```javascript title="" + /* Array representation of a binary tree */ + // Using null to represent empty slots + let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "TS" + + ```typescript title="" + /* Array representation of a binary tree */ + // Using null to represent empty slots + let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Dart" + + ```dart title="" + /* Array representation of a binary tree */ + // Using nullable int (int?) allows for using null to mark empty slots + List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Rust" + + ```rust title="" + /* Array representation of a binary tree */ + // Using None to mark empty slots + let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; + ``` + +=== "C" + + ```c title="" + /* Array representation of a binary tree */ + // Using the maximum int value to mark empty slots, therefore, node values must not be INT_MAX + int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Array representation of a binary tree */ + // Using null to represent empty slots + val tree = mutableListOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +![Array representation of any type of binary tree](array_representation_of_tree.assets/array_representation_with_empty.png) + +It's worth noting that **complete binary trees are very suitable for array representation**. Recalling the definition of a complete binary tree, `None` appears only at the bottom level and towards the right, **meaning all `None` values definitely appear at the end of the level-order traversal sequence**. + +This means that when using an array to represent a complete binary tree, it's possible to omit storing all `None` values, which is very convenient. The figure below gives an example. + +![Array representation of a complete binary tree](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) + +The following code implements a binary tree based on array representation, including the following operations: + +- Given a node, obtain its value, left (right) child node, and parent node. +- Obtain the pre-order, in-order, post-order, and level-order traversal sequences. + +```src +[file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} +``` + +## Advantages and limitations + +The array representation of binary trees has the following advantages: + +- Arrays are stored in contiguous memory spaces, which is cache-friendly and allows for faster access and traversal. +- It does not require storing pointers, which saves space. +- It allows random access to nodes. + +However, the array representation also has some limitations: + +- Array storage requires contiguous memory space, so it is not suitable for storing trees with a large amount of data. +- Adding or deleting nodes requires array insertion and deletion operations, which are less efficient. +- When there are many `None` values in the binary tree, the proportion of node data contained in the array is low, leading to lower space utilization. diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png b/en/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png new file mode 100644 index 0000000000..ffc99032b6 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png b/en/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png new file mode 100644 index 0000000000..bbf8284a8d Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png b/en/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png new file mode 100644 index 0000000000..17a61030f8 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png b/en/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png new file mode 100644 index 0000000000..c64f74a1de Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png b/en/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png new file mode 100644 index 0000000000..61026646b6 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png b/en/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png new file mode 100644 index 0000000000..9290719e77 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png new file mode 100644 index 0000000000..456144d8f1 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png new file mode 100644 index 0000000000..8e4a5b4bb6 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png new file mode 100644 index 0000000000..b563da0da2 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png new file mode 100644 index 0000000000..c3afd8c1b7 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png new file mode 100644 index 0000000000..faed161d18 Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png differ diff --git a/en/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png b/en/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png new file mode 100644 index 0000000000..da4dfce6dc Binary files /dev/null and b/en/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png differ diff --git a/en/docs/chapter_tree/avl_tree.md b/en/docs/chapter_tree/avl_tree.md new file mode 100644 index 0000000000..68bd5e0888 --- /dev/null +++ b/en/docs/chapter_tree/avl_tree.md @@ -0,0 +1,353 @@ +# AVL tree * + +In the "Binary Search Tree" section, we mentioned that after multiple insertions and removals, a binary search tree might degrade to a linked list. In such cases, the time complexity of all operations degrades from $O(\log n)$ to $O(n)$. + +As shown in the figure below, after two node removal operations, this binary search tree will degrade into a linked list. + +![Degradation of an AVL tree after removing nodes](avl_tree.assets/avltree_degradation_from_removing_node.png) + +For example, in the perfect binary tree shown in the figure below, after inserting two nodes, the tree will lean heavily to the left, and the time complexity of search operations will also degrade. + +![Degradation of an AVL tree after inserting nodes](avl_tree.assets/avltree_degradation_from_inserting_node.png) + +In 1962, G. M. Adelson-Velsky and E. M. Landis proposed the AVL Tree in their paper "An algorithm for the organization of information". The paper detailed a series of operations to ensure that after continuously adding and removing nodes, the AVL tree would not degrade, thus maintaining the time complexity of various operations at $O(\log n)$ level. In other words, in scenarios where frequent additions, removals, searches, and modifications are needed, the AVL tree can always maintain efficient data operation performance, which has great application value. + +## Common terminology in AVL trees + +An AVL tree is both a binary search tree and a balanced binary tree, satisfying all properties of these two types of binary trees, hence it is a balanced binary search tree. + +### Node height + +Since the operations related to AVL trees require obtaining node heights, we need to add a `height` variable to the node class: + +=== "Python" + + ```python title="" + class TreeNode: + """AVL tree node""" + def __init__(self, val: int): + self.val: int = val # Node value + self.height: int = 0 # Node height + self.left: TreeNode | None = None # Left child reference + self.right: TreeNode | None = None # Right child reference + ``` + +=== "C++" + + ```cpp title="" + /* AVL tree node */ + struct TreeNode { + int val{}; // Node value + int height = 0; // Node height + TreeNode *left{}; // Left child + TreeNode *right{}; // Right child + TreeNode() = default; + explicit TreeNode(int x) : val(x){} + }; + ``` + +=== "Java" + + ```java title="" + /* AVL tree node */ + class TreeNode { + public int val; // Node value + public int height; // Node height + public TreeNode left; // Left child + public TreeNode right; // Right child + public TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* AVL tree node */ + class TreeNode(int? x) { + public int? val = x; // Node value + public int height; // Node height + public TreeNode? left; // Left child reference + public TreeNode? right; // Right child reference + } + ``` + +=== "Go" + + ```go title="" + /* AVL tree node */ + type TreeNode struct { + Val int // Node value + Height int // Node height + Left *TreeNode // Left child reference + Right *TreeNode // Right child reference + } + ``` + +=== "Swift" + + ```swift title="" + /* AVL tree node */ + class TreeNode { + var val: Int // Node value + var height: Int // Node height + var left: TreeNode? // Left child + var right: TreeNode? // Right child + + init(x: Int) { + val = x + height = 0 + } + } + ``` + +=== "JS" + + ```javascript title="" + /* AVL tree node */ + class TreeNode { + val; // Node value + height; // Node height + left; // Left child pointer + right; // Right child pointer + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* AVL tree node */ + class TreeNode { + val: number; // Node value + height: number; // Node height + left: TreeNode | null; // Left child pointer + right: TreeNode | null; // Right child pointer + constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "Dart" + + ```dart title="" + /* AVL tree node */ + class TreeNode { + int val; // Node value + int height; // Node height + TreeNode? left; // Left child + TreeNode? right; // Right child + TreeNode(this.val, [this.height = 0, this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* AVL tree node */ + struct TreeNode { + val: i32, // Node value + height: i32, // Node height + left: Option>>, // Left child + right: Option>>, // Right child + } + + impl TreeNode { + /* Constructor */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* AVL tree node */ + TreeNode struct TreeNode { + int val; + int height; + struct TreeNode *left; + struct TreeNode *right; + } TreeNode; + + /* Constructor */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* AVL tree node */ + class TreeNode(val _val: Int) { // Node value + val height: Int = 0 // Node height + val left: TreeNode? = null // Left child + val right: TreeNode? = null // Right child + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +The "node height" refers to the distance from that node to its farthest leaf node, i.e., the number of "edges" passed. It is important to note that the height of a leaf node is $0$, and the height of a null node is $-1$. We will create two utility functions for getting and updating the height of a node: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{update_height} +``` + +### Node balance factor + +The balance factor of a node is defined as the height of the node's left subtree minus the height of its right subtree, with the balance factor of a null node defined as $0$. We will also encapsulate the functionality of obtaining the node balance factor into a function for easy use later on: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} +``` + +!!! tip + + Let the balance factor be $f$, then the balance factor of any node in an AVL tree satisfies $-1 \le f \le 1$. + +## Rotations in AVL trees + +The characteristic feature of an AVL tree is the "rotation" operation, which can restore balance to an unbalanced node without affecting the in-order traversal sequence of the binary tree. In other words, **the rotation operation can maintain the property of a "binary search tree" while also turning the tree back into a "balanced binary tree"**. + +We call nodes with an absolute balance factor $> 1$ "unbalanced nodes". Depending on the type of imbalance, there are four kinds of rotations: right rotation, left rotation, right-left rotation, and left-right rotation. Below, we detail these rotation operations. + +### Right rotation + +As shown in the figure below, the first unbalanced node from the bottom up in the binary tree is "node 3". Focusing on the subtree with this unbalanced node as the root, denoted as `node`, and its left child as `child`, perform a "right rotation". After the right rotation, the subtree is balanced again while still maintaining the properties of a binary search tree. + +=== "<1>" + ![Steps of right rotation](avl_tree.assets/avltree_right_rotate_step1.png) + +=== "<2>" + ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) + +=== "<3>" + ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) + +=== "<4>" + ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) + +As shown in the figure below, when the `child` node has a right child (denoted as `grand_child`), a step needs to be added in the right rotation: set `grand_child` as the left child of `node`. + +![Right rotation with grand_child](avl_tree.assets/avltree_right_rotate_with_grandchild.png) + +"Right rotation" is a figurative term; in practice, it is achieved by modifying node pointers, as shown in the following code: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} +``` + +### Left rotation + +Correspondingly, if considering the "mirror" of the above unbalanced binary tree, the "left rotation" operation shown in the figure below needs to be performed. + +![Left rotation operation](avl_tree.assets/avltree_left_rotate.png) + +Similarly, as shown in the figure below, when the `child` node has a left child (denoted as `grand_child`), a step needs to be added in the left rotation: set `grand_child` as the right child of `node`. + +![Left rotation with grand_child](avl_tree.assets/avltree_left_rotate_with_grandchild.png) + +It can be observed that **the right and left rotation operations are logically symmetrical, and they solve two symmetrical types of imbalance**. Based on symmetry, by replacing all `left` with `right`, and all `right` with `left` in the implementation code of right rotation, we can get the implementation code for left rotation: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} +``` + +### Left-right rotation + +For the unbalanced node 3 shown in the figure below, using either left or right rotation alone cannot restore balance to the subtree. In this case, a "left rotation" needs to be performed on `child` first, followed by a "right rotation" on `node`. + +![Left-right rotation](avl_tree.assets/avltree_left_right_rotate.png) + +### Right-left rotation + +As shown in the figure below, for the mirror case of the above unbalanced binary tree, a "right rotation" needs to be performed on `child` first, followed by a "left rotation" on `node`. + +![Right-left rotation](avl_tree.assets/avltree_right_left_rotate.png) + +### Choice of rotation + +The four kinds of imbalances shown in the figure below correspond to the cases described above, respectively requiring right rotation, left-right rotation, right-left rotation, and left rotation. + +![The four rotation cases of AVL tree](avl_tree.assets/avltree_rotation_cases.png) + +As shown in the table below, we determine which of the above cases an unbalanced node belongs to by judging the sign of the balance factor of the unbalanced node and its higher-side child's balance factor. + +

Table   Conditions for Choosing Among the Four Rotation Cases

+ +| Balance factor of unbalanced node | Balance factor of child node | Rotation method to use | +| --------------------------------- | ---------------------------- | --------------------------------- | +| $> 1$ (Left-leaning tree) | $\geq 0$ | Right rotation | +| $> 1$ (Left-leaning tree) | $<0$ | Left rotation then right rotation | +| $< -1$ (Right-leaning tree) | $\leq 0$ | Left rotation | +| $< -1$ (Right-leaning tree) | $>0$ | Right rotation then left rotation | + +For convenience, we encapsulate the rotation operations into a function. **With this function, we can perform rotations on various kinds of imbalances, restoring balance to unbalanced nodes**. The code is as follows: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{rotate} +``` + +## Common operations in AVL trees + +### Node insertion + +The node insertion operation in AVL trees is similar to that in binary search trees. The only difference is that after inserting a node in an AVL tree, a series of unbalanced nodes may appear along the path from that node to the root node. Therefore, **we need to start from this node and perform rotation operations upwards to restore balance to all unbalanced nodes**. The code is as follows: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} +``` + +### Node removal + +Similarly, based on the method of removing nodes in binary search trees, rotation operations need to be performed from the bottom up to restore balance to all unbalanced nodes. The code is as follows: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} +``` + +### Node search + +The node search operation in AVL trees is consistent with that in binary search trees and will not be detailed here. + +## Typical applications of AVL trees + +- Organizing and storing large amounts of data, suitable for scenarios with high-frequency searches and low-frequency intertions and removals. +- Used to build index systems in databases. +- Red-black trees are also a common type of balanced binary search tree. Compared to AVL trees, red-black trees have more relaxed balancing conditions, require fewer rotations for node insertion and removal, and have a higher average efficiency for node addition and removal operations. diff --git a/en/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png b/en/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png new file mode 100644 index 0000000000..2c0c1e100a Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png new file mode 100644 index 0000000000..e56933ca58 Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png new file mode 100644 index 0000000000..c26c1082bf Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_insert.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_insert.png new file mode 100644 index 0000000000..a913e0063e Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_insert.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png new file mode 100644 index 0000000000..4942367251 Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png new file mode 100644 index 0000000000..979223610c Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png new file mode 100644 index 0000000000..46e03649df Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png new file mode 100644 index 0000000000..0efa18000d Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png new file mode 100644 index 0000000000..7f7bd2416f Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png new file mode 100644 index 0000000000..5acf8ffb81 Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png new file mode 100644 index 0000000000..c8bee4bab7 Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png new file mode 100644 index 0000000000..788173d765 Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png new file mode 100644 index 0000000000..6bfb17d541 Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png new file mode 100644 index 0000000000..23a22a0320 Binary files /dev/null and b/en/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png differ diff --git a/en/docs/chapter_tree/binary_search_tree.md b/en/docs/chapter_tree/binary_search_tree.md new file mode 100755 index 0000000000..881973801d --- /dev/null +++ b/en/docs/chapter_tree/binary_search_tree.md @@ -0,0 +1,129 @@ +# Binary search tree + +As shown in the figure below, a binary search tree satisfies the following conditions. + +1. For the root node, the value of all nodes in the left subtree $<$ the value of the root node $<$ the value of all nodes in the right subtree. +2. The left and right subtrees of any node are also binary search trees, i.e., they satisfy condition `1.` as well. + +![Binary search tree](binary_search_tree.assets/binary_search_tree.png) + +## Operations on a binary search tree + +We encapsulate the binary search tree as a class `BinarySearchTree` and declare a member variable `root` pointing to the tree's root node. + +### Searching for a node + +Given a target node value `num`, one can search according to the properties of the binary search tree. As shown in the figure below, we declare a node `cur`, start from the binary tree's root node `root`, and loop to compare the size between the node value `cur.val` and `num`. + +- If `cur.val < num`, it means the target node is in `cur`'s right subtree, thus execute `cur = cur.right`. +- If `cur.val > num`, it means the target node is in `cur`'s left subtree, thus execute `cur = cur.left`. +- If `cur.val = num`, it means the target node is found, exit the loop, and return the node. + +=== "<1>" + ![Example of searching for a node in a binary search tree](binary_search_tree.assets/bst_search_step1.png) + +=== "<2>" + ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) + +=== "<3>" + ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) + +=== "<4>" + ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) + +The search operation in a binary search tree works on the same principle as the binary search algorithm, eliminating half of the cases in each round. The number of loops is at most the height of the binary tree. When the binary tree is balanced, it uses $O(\log n)$ time. The example code is as follows: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} +``` + +### Inserting a node + +Given an element `num` to be inserted, to maintain the property of the binary search tree "left subtree < root node < right subtree," the insertion operation proceeds as shown in the figure below. + +1. **Finding insertion position**: Similar to the search operation, start from the root node, loop downwards according to the size relationship between the current node value and `num`, until the leaf node is passed (traversed to `None`), then exit the loop. +2. **Insert the node at this position**: Initialize the node `num` and place it where `None` was. + +![Inserting a node into a binary search tree](binary_search_tree.assets/bst_insert.png) + +In the code implementation, note the following two points. + +- The binary search tree does not allow duplicate nodes to exist; otherwise, its definition would be violated. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed, and the node returns directly. +- To perform the insertion operation, we need to use the node `pre` to save the node from the previous loop. This way, when traversing to `None`, we can get its parent node, thus completing the node insertion operation. + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} +``` + +Similar to searching for a node, inserting a node uses $O(\log n)$ time. + +### Removing a node + +First, find the target node in the binary tree, then remove it. Similar to inserting a node, we need to ensure that after the removal operation is completed, the property of the binary search tree "left subtree < root node < right subtree" is still satisfied. Therefore, based on the number of child nodes of the target node, we divide it into three cases: 0, 1, and 2, and perform the corresponding node removal operations. + +As shown in the figure below, when the degree of the node to be removed is $0$, it means the node is a leaf node and can be directly removed. + +![Removing a node in a binary search tree (degree 0)](binary_search_tree.assets/bst_remove_case1.png) + +As shown in the figure below, when the degree of the node to be removed is $1$, replacing the node to be removed with its child node is sufficient. + +![Removing a node in a binary search tree (degree 1)](binary_search_tree.assets/bst_remove_case2.png) + +When the degree of the node to be removed is $2$, we cannot remove it directly, but need to use a node to replace it. To maintain the property of the binary search tree "left subtree $<$ root node $<$ right subtree," **this node can be either the smallest node of the right subtree or the largest node of the left subtree**. + +Assuming we choose the smallest node of the right subtree (the next node in in-order traversal), then the removal operation proceeds as shown in the figure below. + +1. Find the next node in the "in-order traversal sequence" of the node to be removed, denoted as `tmp`. +2. Replace the value of the node to be removed with `tmp`'s value, and recursively remove the node `tmp` in the tree. + +=== "<1>" + ![Removing a node in a binary search tree (degree 2)](binary_search_tree.assets/bst_remove_case3_step1.png) + +=== "<2>" + ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) + +=== "<3>" + ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) + +=== "<4>" + ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) + +The operation of removing a node also uses $O(\log n)$ time, where finding the node to be removed requires $O(\log n)$ time, and obtaining the in-order traversal successor node requires $O(\log n)$ time. Example code is as follows: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} +``` + +### In-order traversal is ordered + +As shown in the figure below, the in-order traversal of a binary tree follows the traversal order of "left $\rightarrow$ root $\rightarrow$ right," and a binary search tree satisfies the size relationship of "left child node $<$ root node $<$ right child node." + +This means that when performing in-order traversal in a binary search tree, the next smallest node will always be traversed first, thus leading to an important property: **The sequence of in-order traversal in a binary search tree is ascending**. + +Using the ascending property of in-order traversal, obtaining ordered data in a binary search tree requires only $O(n)$ time, without the need for additional sorting operations, which is very efficient. + +![In-order traversal sequence of a binary search tree](binary_search_tree.assets/bst_inorder_traversal.png) + +## Efficiency of binary search trees + +Given a set of data, we consider using an array or a binary search tree for storage. Observing the table below, the operations on a binary search tree all have logarithmic time complexity, which is stable and efficient. Arrays are more efficient than binary search trees only in scenarios involving frequent additions and infrequent searches or removals. + +

Table   Efficiency comparison between arrays and search trees

+ +| | Unsorted array | Binary search tree | +| -------------- | -------------- | ------------------ | +| Search element | $O(n)$ | $O(\log n)$ | +| Insert element | $O(1)$ | $O(\log n)$ | +| Remove element | $O(n)$ | $O(\log n)$ | + +Ideally, the binary search tree is "balanced," allowing any node can be found within $\log n$ loops. + +However, if we continuously insert and remove nodes in a binary search tree, it may degenerate into a linked list as shown in the figure below, where the time complexity of various operations also degrades to $O(n)$. + +![Degradation of a binary search tree](binary_search_tree.assets/bst_degradation.png) + +## Common applications of binary search trees + +- Used as multi-level indexes in systems to implement efficient search, insertion, and removal operations. +- Serves as the underlying data structure for certain search algorithms. +- Used to store data streams to maintain their ordered state. diff --git a/en/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png b/en/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png new file mode 100644 index 0000000000..1102330e70 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png differ diff --git a/en/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/en/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png new file mode 100644 index 0000000000..a66ca9cdfd Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png differ diff --git a/en/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png b/en/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png new file mode 100644 index 0000000000..86c667ef02 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png differ diff --git a/en/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png b/en/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png new file mode 100644 index 0000000000..df51c6fd6d Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png differ diff --git a/en/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png b/en/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png new file mode 100644 index 0000000000..9f2563b7dc Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png differ diff --git a/en/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png b/en/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png new file mode 100644 index 0000000000..e8821b4f98 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png differ diff --git a/en/docs/chapter_tree/binary_tree.assets/full_binary_tree.png b/en/docs/chapter_tree/binary_tree.assets/full_binary_tree.png new file mode 100644 index 0000000000..4679adbc39 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/full_binary_tree.png differ diff --git a/en/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png b/en/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png new file mode 100644 index 0000000000..f12cdaff15 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png differ diff --git a/en/docs/chapter_tree/binary_tree.md b/en/docs/chapter_tree/binary_tree.md new file mode 100644 index 0000000000..b06570218a --- /dev/null +++ b/en/docs/chapter_tree/binary_tree.md @@ -0,0 +1,662 @@ +# Binary tree + +A binary tree is a non-linear data structure that represents the hierarchical relationship between ancestors and descendants and embodies the divide-and-conquer logic of "splitting into two". Similar to a linked list, the basic unit of a binary tree is a node, and each node contains a value, a reference to its left child node, and a reference to its right child node. + +=== "Python" + + ```python title="" + class TreeNode: + """Binary tree node""" + def __init__(self, val: int): + self.val: int = val # Node value + self.left: TreeNode | None = None # Reference to left child node + self.right: TreeNode | None = None # Reference to right child node + ``` + +=== "C++" + + ```cpp title="" + /* Binary tree node */ + struct TreeNode { + int val; // Node value + TreeNode *left; // Pointer to left child node + TreeNode *right; // Pointer to right child node + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + }; + ``` + +=== "Java" + + ```java title="" + /* Binary tree node */ + class TreeNode { + int val; // Node value + TreeNode left; // Reference to left child node + TreeNode right; // Reference to right child node + TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* Binary tree node */ + class TreeNode(int? x) { + public int? val = x; // Node value + public TreeNode? left; // Reference to left child node + public TreeNode? right; // Reference to right child node + } + ``` + +=== "Go" + + ```go title="" + /* Binary tree node */ + type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode + } + /* Constructor */ + func NewTreeNode(v int) *TreeNode { + return &TreeNode{ + Left: nil, // Pointer to left child node + Right: nil, // Pointer to right child node + Val: v, // Node value + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Binary tree node */ + class TreeNode { + var val: Int // Node value + var left: TreeNode? // Reference to left child node + var right: TreeNode? // Reference to right child node + + init(x: Int) { + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Binary tree node */ + class TreeNode { + val; // Node value + left; // Pointer to left child node + right; // Pointer to right child node + constructor(val, left, right) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Binary tree node */ + class TreeNode { + val: number; + left: TreeNode | null; + right: TreeNode | null; + + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; // Node value + this.left = left === undefined ? null : left; // Reference to left child node + this.right = right === undefined ? null : right; // Reference to right child node + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Binary tree node */ + class TreeNode { + int val; // Node value + TreeNode? left; // Reference to left child node + TreeNode? right; // Reference to right child node + TreeNode(this.val, [this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Binary tree node */ + struct TreeNode { + val: i32, // Node value + left: Option>>, // Reference to left child node + right: Option>>, // Reference to right child node + } + + impl TreeNode { + /* Constructor */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* Binary tree node */ + typedef struct TreeNode { + int val; // Node value + int height; // Node height + struct TreeNode *left; // Pointer to left child node + struct TreeNode *right; // Pointer to right child node + } TreeNode; + + /* Constructor */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Binary tree node */ + class TreeNode(val _val: Int) { // Node value + val left: TreeNode? = null // Reference to left child node + val right: TreeNode? = null // Reference to right child node + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +Each node has two references (pointers), pointing respectively to the left-child node and right-child node. This node is called the parent node of these two child nodes. When given a node of a binary tree, we call the tree formed by this node's left child and all nodes below it the left subtree of this node. Similarly, the right subtree can be defined. + +**In a binary tree, except leaf nodes, all other nodes contain child nodes and non-empty subtrees.** As shown in the figure below, if "Node 2" is regarded as a parent node, its left and right child nodes are "Node 4" and "Node 5" respectively. The left subtree is formed by "Node 4" and all nodes beneath it, while the right subtree is formed by "Node 5" and all nodes beneath it. + +![Parent Node, child Node, subtree](binary_tree.assets/binary_tree_definition.png) + +## Common terminology of binary trees + +The commonly used terminology of binary trees is shown in the figure below. + +- Root node: The node at the top level of a binary tree, which does not have a parent node. +- Leaf node: A node that does not have any child nodes, with both of its pointers pointing to `None`. +- Edge: A line segment that connects two nodes, representing a reference (pointer) between the nodes. +- The level of a node: It increases from top to bottom, with the root node being at level 1. +- The degree of a node: The number of child nodes that a node has. In a binary tree, the degree can be 0, 1, or 2. +- The height of a binary tree: The number of edges from the root node to the farthest leaf node. +- The depth of a node: The number of edges from the root node to the node. +- The height of a node: The number of edges from the farthest leaf node to the node. + +![Common Terminology of Binary Trees](binary_tree.assets/binary_tree_terminology.png) + +!!! tip + + Please note that we usually define "height" and "depth" as "the number of edges traversed", but some questions or textbooks may define them as "the number of nodes traversed". In this case, both height and depth need to be incremented by 1. + +## Basic operations of binary trees + +### Initializing a binary tree + +Similar to a linked list, the initialization of a binary tree involves first creating the nodes and then establishing the references (pointers) between them. + +=== "Python" + + ```python title="binary_tree.py" + # Initializing a binary tree + # Initializing nodes + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # Linking references (pointers) between nodes + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode* n1 = new TreeNode(1); + TreeNode* n2 = new TreeNode(2); + TreeNode* n3 = new TreeNode(3); + TreeNode* n4 = new TreeNode(4); + TreeNode* n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Java" + + ```java title="binary_tree.java" + // Initializing nodes + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* Initializing a binary tree */ + // Initializing nodes + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // Linking references (pointers) between nodes + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + // Initializing nodes + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // Linking references (pointers) between nodes + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* Initializing a binary tree */ + // Initializing nodes + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* Initializing a binary tree */ + // Initializing nodes + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Linking references (pointers) between nodes + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + // Initializing nodes + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // Linking references (pointers) between nodes + n1.borrow_mut().left = Some(n2.clone()); + n1.borrow_mut().right = Some(n3); + n2.borrow_mut().left = Some(n4); + n2.borrow_mut().right = Some(n5); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* Initializing a binary tree */ + // Initializing nodes + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // Linking references (pointers) between nodes + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + // Initializing nodes + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // Linking references (pointers) between nodes + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "Code visualization" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Inserting and removing nodes + +Similar to a linked list, inserting and removing nodes in a binary tree can be achieved by modifying pointers. The figure below provides an example. + +![Inserting and removing nodes in a binary tree](binary_tree.assets/binary_tree_add_remove.png) + +=== "Python" + + ```python title="binary_tree.py" + # Inserting and removing nodes + p = TreeNode(0) + # Inserting node P between n1 -> n2 + n1.left = p + p.left = n2 + # Removing node P + n1.left = n2 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* Inserting and removing nodes */ + TreeNode* P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1->left = P; + P->left = n2; + // Removing node P + n1->left = n2; + ``` + +=== "Java" + + ```java title="binary_tree.java" + TreeNode P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* Inserting and removing nodes */ + TreeNode P = new(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* Inserting and removing nodes */ + // Inserting node P between n1 and n2 + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + // Removing node P + n1.Left = n2 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + let P = TreeNode(x: 0) + // Inserting node P between n1 and n2 + n1.left = P + P.left = n2 + // Removing node P + n1.left = n2 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* Inserting and removing nodes */ + let P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* Inserting and removing nodes */ + const P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* Inserting and removing nodes */ + TreeNode P = new TreeNode(0); + // Inserting node P between n1 and n2 + n1.left = P; + P.left = n2; + // Removing node P + n1.left = n2; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + let p = TreeNode::new(0); + // Inserting node P between n1 and n2 + n1.borrow_mut().left = Some(p.clone()); + p.borrow_mut().left = Some(n2.clone()); + // Removing node P + n1.borrow_mut().left = Some(n2); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* Inserting and removing nodes */ + TreeNode *P = newTreeNode(0); + // Inserting node P between n1 and n2 + n1->left = P; + P->left = n2; + // Removing node P + n1->left = n2; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + val P = TreeNode(0) + // Inserting node P between n1 and n2 + n1.left = P + P.left = n2 + // Removing node P + n1.left = n2 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "Code visualization" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +!!! tip + + It should be noted that inserting nodes may change the original logical structure of the binary tree, while removing nodes typically involves removing the node and all its subtrees. Therefore, in a binary tree, insertion and removal are usually performed through a set of operations to achieve meaningful outcomes. + +## Common types of binary trees + +### Perfect binary tree + +As shown in the figure below, in a perfect binary tree, all levels are completely filled with nodes. In a perfect binary tree, leaf nodes have a degree of $0$, while all other nodes have a degree of $2$. The total number of nodes can be calculated as $2^{h+1} - 1$, where $h$ is the height of the tree. This exhibits a standard exponential relationship, reflecting the common phenomenon of cell division in nature. + +!!! tip + + Please note that in the Chinese community, a perfect binary tree is often referred to as a full binary tree. + +![Perfect binary tree](binary_tree.assets/perfect_binary_tree.png) + +### Complete binary tree + +As shown in the figure below, a complete binary tree is a binary tree where only the bottom level is possibly not completely filled, and nodes at the bottom level must be filled continuously from left to right. Note that a perfect binary tree is also a complete binary tree. + +![Complete binary tree](binary_tree.assets/complete_binary_tree.png) + +### Full binary tree + +As shown in the figure below, a full binary tree, except for the leaf nodes, has two child nodes for all other nodes. + +![Full binary tree](binary_tree.assets/full_binary_tree.png) + +### Balanced binary tree + +As shown in the figure below, in a balanced binary tree, the absolute difference between the height of the left and right subtrees of any node does not exceed 1. + +![Balanced binary tree](binary_tree.assets/balanced_binary_tree.png) + +## Degeneration of binary trees + +The figure below shows the ideal and degenerate structures of binary trees. A binary tree becomes a "perfect binary tree" when every level is filled; while it degenerates into a "linked list" when all nodes are biased toward one side. + +- A perfect binary tree is an ideal scenario where the "divide and conquer" advantage of a binary tree can be fully utilized. +- On the other hand, a linked list represents another extreme where all operations become linear, resulting in a time complexity of $O(n)$. + +![The Best and Worst Structures of Binary Trees](binary_tree.assets/binary_tree_best_worst_cases.png) + +As shown in the table below, in the best and worst structures, the binary tree achieves either maximum or minimum values for leaf node count, total number of nodes, and height. + +

Table   The Best and Worst Structures of Binary Trees

+ +| | Perfect binary tree | Linked list | +| ----------------------------------------------- | ------------------- | ----------- | +| Number of nodes at level $i$ | $2^{i-1}$ | $1$ | +| Number of leaf nodes in a tree with height $h$ | $2^h$ | $1$ | +| Total number of nodes in a tree with height $h$ | $2^{h+1} - 1$ | $h + 1$ | +| Height of a tree with $n$ total nodes | $\log_2 (n+1) - 1$ | $n - 1$ | diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png b/en/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png new file mode 100644 index 0000000000..0619301b01 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png b/en/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png new file mode 100644 index 0000000000..ee6233d113 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png new file mode 100644 index 0000000000..47f79cf3fa Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png new file mode 100644 index 0000000000..1ab77230cc Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png new file mode 100644 index 0000000000..8a5393fdad Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png new file mode 100644 index 0000000000..c32ec04e22 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png new file mode 100644 index 0000000000..bff7b8c6e4 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png new file mode 100644 index 0000000000..46af41d797 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png new file mode 100644 index 0000000000..8f68c9156e Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png new file mode 100644 index 0000000000..1d2d4881a3 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png new file mode 100644 index 0000000000..f875ea341f Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png new file mode 100644 index 0000000000..1dc55c56a9 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png new file mode 100644 index 0000000000..82852f0be6 Binary files /dev/null and b/en/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png differ diff --git a/en/docs/chapter_tree/binary_tree_traversal.md b/en/docs/chapter_tree/binary_tree_traversal.md new file mode 100755 index 0000000000..08c104605d --- /dev/null +++ b/en/docs/chapter_tree/binary_tree_traversal.md @@ -0,0 +1,89 @@ +# Binary tree traversal + +From a physical structure perspective, a tree is a data structure based on linked lists. Hence, its traversal method involves accessing nodes one by one through pointers. However, a tree is a non-linear data structure, which makes traversing a tree more complex than traversing a linked list, requiring the assistance of search algorithms. + +The common traversal methods for binary trees include level-order traversal, pre-order traversal, in-order traversal, and post-order traversal. + +## Level-order traversal + +As shown in the figure below, level-order traversal traverses the binary tree from top to bottom, layer by layer. Within each level, it visits nodes from left to right. + +Level-order traversal is essentially a type of breadth-first traversal, also known as breadth-first search (BFS), which embodies a "circumferentially outward expanding" layer-by-layer traversal method. + +![Level-order traversal of a binary tree](binary_tree_traversal.assets/binary_tree_bfs.png) + +### Code implementation + +Breadth-first traversal is usually implemented with the help of a "queue". The queue follows the "first in, first out" rule, while breadth-first traversal follows the "layer-by-layer progression" rule, the underlying ideas of the two are consistent. The implementation code is as follows: + +```src +[file]{binary_tree_bfs}-[class]{}-[func]{level_order} +``` + +### Complexity analysis + +- **Time complexity is $O(n)$**: All nodes are visited once, taking $O(n)$ time, where $n$ is the number of nodes. +- **Space complexity is $O(n)$**: In the worst case, i.e., a full binary tree, before traversing to the bottom level, the queue can contain at most $(n + 1) / 2$ nodes simultaneously, occupying $O(n)$ space. + +## Preorder, in-order, and post-order traversal + +Correspondingly, pre-order, in-order, and post-order traversal all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a "proceed to the end first, then backtrack and continue" traversal method. + +The figure below shows the working principle of performing a depth-first traversal on a binary tree. **Depth-first traversal is like "walking" around the entire binary tree**, encountering three positions at each node, corresponding to pre-order, in-order, and post-order traversal. + +![Preorder, in-order, and post-order traversal of a binary search tree](binary_tree_traversal.assets/binary_tree_dfs.png) + +### Code implementation + +Depth-first search is usually implemented based on recursion: + +```src +[file]{binary_tree_dfs}-[class]{}-[func]{post_order} +``` + +!!! tip + + Depth-first search can also be implemented based on iteration, interested readers can study this on their own. + +The figure below shows the recursive process of pre-order traversal of a binary tree, which can be divided into two opposite parts: "recursion" and "return". + +1. "Recursion" means starting a new method, the program accesses the next node in this process. +2. "Return" means the function returns, indicating the current node has been fully accessed. + +=== "<1>" + ![The recursive process of pre-order traversal](binary_tree_traversal.assets/preorder_step1.png) + +=== "<2>" + ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) + +=== "<3>" + ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) + +=== "<4>" + ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) + +=== "<5>" + ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) + +=== "<6>" + ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) + +=== "<7>" + ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) + +=== "<8>" + ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) + +=== "<9>" + ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) + +=== "<10>" + ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) + +=== "<11>" + ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) + +### Complexity analysis + +- **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time. +- **Space complexity is $O(n)$**: In the worst case, i.e., the tree degenerates into a linked list, the recursion depth reaches $n$, the system occupies $O(n)$ stack frame space. diff --git a/en/docs/chapter_tree/index.md b/en/docs/chapter_tree/index.md new file mode 100644 index 0000000000..f65885391c --- /dev/null +++ b/en/docs/chapter_tree/index.md @@ -0,0 +1,9 @@ +# Tree + +![Tree](../assets/covers/chapter_tree.jpg) + +!!! abstract + + The towering tree exudes a vibrant essence, boasting profound roots and abundant foliage, yet its branches are sparsely scattered, creating an ethereal aura. + + It shows us the vivid form of divide-and-conquer in data. \ No newline at end of file diff --git a/en/docs/chapter_tree/summary.md b/en/docs/chapter_tree/summary.md new file mode 100644 index 0000000000..93f64a4b98 --- /dev/null +++ b/en/docs/chapter_tree/summary.md @@ -0,0 +1,54 @@ +# Summary + +### Key review + +- A binary tree is a non-linear data structure that reflects the "divide and conquer" logic of splitting one into two. Each binary tree node contains a value and two pointers, which point to its left and right child nodes, respectively. +- For a node in a binary tree, its left (right) child node and the tree formed below it are collectively called the node's left (right) subtree. +- Terms related to binary trees include root node, leaf node, level, degree, edge, height, and depth. +- The operations of initializing a binary tree, inserting nodes, and removing nodes are similar to those of linked list operations. +- Common types of binary trees include perfect binary trees, complete binary trees, full binary trees, and balanced binary trees. The perfect binary tree represents the ideal state, while the linked list is the worst state after degradation. +- A binary tree can be represented using an array by arranging the node values and empty slots in a level-order traversal sequence and implementing pointers based on the index mapping relationship between parent nodes and child nodes. +- The level-order traversal of a binary tree is a breadth-first search method, which reflects a layer-by-layer traversal manner of "expanding circle by circle." It is usually implemented using a queue. +- Pre-order, in-order, and post-order traversals are all depth-first search methods, reflecting the traversal manner of "going to the end first, then backtracking to continue." They are usually implemented using recursion. +- A binary search tree is an efficient data structure for element searching, with the time complexity of search, insert, and remove operations all being $O(\log n)$. When a binary search tree degrades into a linked list, these time complexities deteriorate to $O(n)$. +- An AVL tree, also known as a balanced binary search tree, ensures that the tree remains balanced after continuous node insertions and removals through rotation operations. +- Rotation operations in an AVL tree include right rotation, left rotation, right-left rotation, and left-right rotation. After node insertion or removal, the AVL tree rebalances itself by performing these rotations in a bottom-up manner. + +### Q & A + +**Q**: For a binary tree with only one node, are both the height of the tree and the depth of the root node $0$? + +Yes, because height and depth are typically defined as "the number of edges passed." + +**Q**: The insertion and removal in a binary tree are generally accomplished by a set of operations. What does "a set of operations" refer to here? Does it imply releasing the resources of the child nodes? + +Taking the binary search tree as an example, the operation of removing a node needs to be handled in three different scenarios, each requiring multiple steps of node operations. + +**Q**: Why are there three sequences: pre-order, in-order, and post-order for DFS traversal of a binary tree, and what are their uses? + +Similar to sequential and reverse traversal of arrays, pre-order, in-order, and post-order traversals are three methods of traversing a binary tree, allowing us to obtain a traversal result in a specific order. For example, in a binary search tree, since the node sizes satisfy `left child node value < root node value < right child node value`, we can obtain an ordered node sequence by traversing the tree in the "left $\rightarrow$ root $\rightarrow$ right" priority. + +**Q**: In a right rotation operation that deals with the relationship between the imbalance nodes `node`, `child`, `grand_child`, isn't the connection between `node` and its parent node and the original link of `node` lost after the right rotation? + +We need to view this problem from a recursive perspective. The `right_rotate(root)` operation passes the root node of the subtree and eventually returns the root node of the rotated subtree with `return child`. The connection between the subtree's root node and its parent node is established after this function returns, which is outside the scope of the right rotation operation's maintenance. + +**Q**: In C++, functions are divided into `private` and `public` sections. What considerations are there for this? Why are the `height()` function and the `updateHeight()` function placed in `public` and `private`, respectively? + +It depends on the scope of the method's use. If a method is only used within the class, then it is designed to be `private`. For example, it makes no sense for users to call `updateHeight()` on their own, as it is just a step in the insertion or removal operations. However, `height()` is for accessing node height, similar to `vector.size()`, thus it is set to `public` for use. + +**Q**: How do you build a binary search tree from a set of input data? Is the choice of root node very important? + +Yes, the method for building the tree is provided in the `build_tree()` method in the binary search tree code. As for the choice of the root node, we usually sort the input data and then select the middle element as the root node, recursively building the left and right subtrees. This approach maximizes the balance of the tree. + +**Q**: In Java, do you always have to use the `equals()` method for string comparison? + +In Java, for primitive data types, `==` is used to compare whether the values of two variables are equal. For reference types, the working principles of the two symbols are different. + +- `==`: Used to compare whether two variables point to the same object, i.e., whether their positions in memory are the same. +- `equals()`: Used to compare whether the values of two objects are equal. + +Therefore, to compare values, we should use `equals()`. However, strings initialized with `String a = "hi"; String b = "hi";` are stored in the string constant pool and point to the same object, so `a == b` can also be used to compare the contents of two strings. + +**Q**: Before reaching the bottom level, is the number of nodes in the queue $2^h$ in breadth-first traversal? + +Yes, for example, a full binary tree with height $h = 2$ has a total of $n = 7$ nodes, then the bottom level has $4 = 2^h = (n + 1) / 2$ nodes. diff --git a/en/docs/index.assets/animation.gif b/en/docs/index.assets/animation.gif new file mode 100644 index 0000000000..641e677efb Binary files /dev/null and b/en/docs/index.assets/animation.gif differ diff --git a/en/docs/index.assets/animation_dark.gif b/en/docs/index.assets/animation_dark.gif new file mode 100644 index 0000000000..a05aedd70e Binary files /dev/null and b/en/docs/index.assets/animation_dark.gif differ diff --git a/en/docs/index.assets/btn_chinese_edition.svg b/en/docs/index.assets/btn_chinese_edition.svg new file mode 100644 index 0000000000..1d43e57384 --- /dev/null +++ b/en/docs/index.assets/btn_chinese_edition.svg @@ -0,0 +1 @@ +中文版 \ No newline at end of file diff --git a/en/docs/index.assets/btn_chinese_edition_dark.svg b/en/docs/index.assets/btn_chinese_edition_dark.svg new file mode 100644 index 0000000000..e622663ba6 --- /dev/null +++ b/en/docs/index.assets/btn_chinese_edition_dark.svg @@ -0,0 +1 @@ +中文版 \ No newline at end of file diff --git a/en/docs/index.assets/btn_download_pdf.svg b/en/docs/index.assets/btn_download_pdf.svg new file mode 100644 index 0000000000..ed236a0777 --- /dev/null +++ b/en/docs/index.assets/btn_download_pdf.svg @@ -0,0 +1 @@ +PDF \ No newline at end of file diff --git a/en/docs/index.assets/btn_download_pdf_dark.svg b/en/docs/index.assets/btn_download_pdf_dark.svg new file mode 100644 index 0000000000..308cfeda09 --- /dev/null +++ b/en/docs/index.assets/btn_download_pdf_dark.svg @@ -0,0 +1 @@ +PDF \ No newline at end of file diff --git a/en/docs/index.assets/btn_read_online.svg b/en/docs/index.assets/btn_read_online.svg new file mode 100644 index 0000000000..a2500e44f3 --- /dev/null +++ b/en/docs/index.assets/btn_read_online.svg @@ -0,0 +1 @@ +Web \ No newline at end of file diff --git a/en/docs/index.assets/btn_read_online_dark.svg b/en/docs/index.assets/btn_read_online_dark.svg new file mode 100644 index 0000000000..ddf3a6b2e4 --- /dev/null +++ b/en/docs/index.assets/btn_read_online_dark.svg @@ -0,0 +1 @@ +Web \ No newline at end of file diff --git a/en/docs/index.assets/comment.gif b/en/docs/index.assets/comment.gif new file mode 100644 index 0000000000..a6c5b84446 Binary files /dev/null and b/en/docs/index.assets/comment.gif differ diff --git a/en/docs/index.assets/hello_algo_header.png b/en/docs/index.assets/hello_algo_header.png new file mode 100644 index 0000000000..51e21c54e4 Binary files /dev/null and b/en/docs/index.assets/hello_algo_header.png differ diff --git a/en/docs/index.assets/hello_algo_mindmap_tp.png b/en/docs/index.assets/hello_algo_mindmap_tp.png new file mode 100644 index 0000000000..b3aea43a56 Binary files /dev/null and b/en/docs/index.assets/hello_algo_mindmap_tp.png differ diff --git a/en/docs/index.assets/running_code.gif b/en/docs/index.assets/running_code.gif new file mode 100644 index 0000000000..5c77187f7b Binary files /dev/null and b/en/docs/index.assets/running_code.gif differ diff --git a/en/docs/index.assets/running_code_dark.gif b/en/docs/index.assets/running_code_dark.gif new file mode 100644 index 0000000000..5d56a0184c Binary files /dev/null and b/en/docs/index.assets/running_code_dark.gif differ diff --git a/en/docs/index.html b/en/docs/index.html new file mode 100644 index 0000000000..dbedfcc7e0 --- /dev/null +++ b/en/docs/index.html @@ -0,0 +1,423 @@ + +
+ + + + + +
+ +
+

+ Data structures and algorithms crash course with animated illustrations and off-the-shelf code +

+ + + + + Dive in + + + + + + + GitHub + +
+ +
+ + + +
+
+
+ + +
+
+ Preview +
+ + + + + + + + + + + + + + +
+

500 animated illustrations, 14 programming languages, and 3000 community Q&As to help you dive into data structures and algorithms.

+
+
+ + +
+ +
+ + +
+
+

Endorsements

+
+
+

“An easy-to-understand book on data structures and algorithms, which guides readers to learn by minds-on and hands-on. Strongly recommended for algorithm beginners!”

+

—— Junhui Deng, Professor, Department of computer science and technology, Tsinghua University

+
+
+

“If I had 'Hello Algo' when I was learning data structures and algorithms, it would have been 10 times easier!”

+

—— Mu Li, Senior principal scientist, Amazon

+
+
+
+
+ + +
+
+
+
+
+
+ + + +

Animated illustrations

+
+

It’s crafted for a smooth learning experience.

+

"A picture is worth a thousand words."

+
+
+ Animation example +
+ +
+ Running code example +
+
+
+ + + +

Off-the-shelf code

+
+

One click to run code in multiple languages.

+

"Talk is cheap. Show me the code."

+
+
+
+ +
+
+
+
+ + + +

Learning together

+
+

Don’t hesitate to ask or share your thoughts.

+

"Learning by teaching."

+
+
+ Comments example +
+ +
+
+ + +
+
+ + + + + + + + + + +
+

Contributors

+

This book has been refined by the efforts of over 180 contributors. We sincerely thank them for their invaluable time and contributions!

+ + Contributors + +
+
+
\ No newline at end of file diff --git a/en/docs/index.md b/en/docs/index.md new file mode 100644 index 0000000000..59eb61d7a5 --- /dev/null +++ b/en/docs/index.md @@ -0,0 +1,5 @@ +# Hello Algo + +Data structures and algorithms crash course with animated illustrations and off-the-shelf code + +[Dive in](chapter_hello_algo/) diff --git a/en/mkdocs.yml b/en/mkdocs.yml new file mode 100644 index 0000000000..c98eeabb94 --- /dev/null +++ b/en/mkdocs.yml @@ -0,0 +1,180 @@ +# Config inheritance +INHERIT: ../mkdocs.yml + +# Project information +site_name: Hello Algo +site_url: https://www.hello-algo.com/en/ +site_description: "Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code" +docs_dir: ../build/en/docs +site_dir: ../site/en +# Repository +edit_uri: tree/main/en/docs +version: 1.0.0 + +# Configuration +theme: + custom_dir: ../build/overrides + language: en + font: + text: Roboto + code: Roboto Mono + palette: + - scheme: default + primary: white + accent: teal + toggle: + icon: material/theme-light-dark + name: Dark mode + - scheme: slate + primary: black + accent: teal + toggle: + icon: material/theme-light-dark + name: Light mode + +extra: + status: + new: Recently Added + +# Page tree +nav: + - Before starting: + - chapter_hello_algo/index.md + - Chapter 0. Preface: + # [icon: material/book-open-outline] + - chapter_preface/index.md + - 0.1 About this book: chapter_preface/about_the_book.md + - 0.2 How to read: chapter_preface/suggestions.md + - 0.3 Summary: chapter_preface/summary.md + - Chapter 1. Encounter with algorithms: + # [icon: material/calculator-variant-outline] + - chapter_introduction/index.md + - 1.1 Algorithms are everywhere: chapter_introduction/algorithms_are_everywhere.md + - 1.2 What is an algorithm: chapter_introduction/what_is_dsa.md + - 1.3 Summary: chapter_introduction/summary.md + - Chapter 2. Complexity analysis: + # [icon: material/timer-sand] + - chapter_computational_complexity/index.md + - 2.1 Algorithm efficiency assessment: chapter_computational_complexity/performance_evaluation.md + - 2.2 Iteration and recursion: chapter_computational_complexity/iteration_and_recursion.md + - 2.3 Time complexity: chapter_computational_complexity/time_complexity.md + - 2.4 Space complexity: chapter_computational_complexity/space_complexity.md + - 2.5 Summary: chapter_computational_complexity/summary.md + - Chapter 3. Data structures: + # [icon: material/shape-outline] + - chapter_data_structure/index.md + - 3.1 Classification of data structures: chapter_data_structure/classification_of_data_structure.md + - 3.2 Basic data types: chapter_data_structure/basic_data_types.md + - 3.3 Number encoding *: chapter_data_structure/number_encoding.md + - 3.4 Character encoding *: chapter_data_structure/character_encoding.md + - 3.5 Summary: chapter_data_structure/summary.md + - Chapter 4. Array and linked list: + # [icon: material/view-list-outline] + - chapter_array_and_linkedlist/index.md + - 4.1 Array: chapter_array_and_linkedlist/array.md + - 4.2 Linked list: chapter_array_and_linkedlist/linked_list.md + - 4.3 List: chapter_array_and_linkedlist/list.md + - 4.4 Memory and cache *: chapter_array_and_linkedlist/ram_and_cache.md + - 4.5 Summary: chapter_array_and_linkedlist/summary.md + - Chapter 5. Stack and queue: + # [icon: material/stack-overflow] + - chapter_stack_and_queue/index.md + - 5.1 Stack: chapter_stack_and_queue/stack.md + - 5.2 Queue: chapter_stack_and_queue/queue.md + - 5.3 Double-ended queue: chapter_stack_and_queue/deque.md + - 5.4 Summary: chapter_stack_and_queue/summary.md + - Chapter 6. Hash table: + # [icon: material/table-search] + - chapter_hashing/index.md + - 6.1 Hash table: chapter_hashing/hash_map.md + - 6.2 Hash collision: chapter_hashing/hash_collision.md + - 6.3 Hash algorithm: chapter_hashing/hash_algorithm.md + - 6.4 Summary: chapter_hashing/summary.md + - Chapter 7. Tree: + # [icon: material/graph-outline] + - chapter_tree/index.md + - 7.1 Binary tree: chapter_tree/binary_tree.md + - 7.2 Binary tree traversal: chapter_tree/binary_tree_traversal.md + - 7.3 Array Representation of tree: chapter_tree/array_representation_of_tree.md + - 7.4 Binary Search tree: chapter_tree/binary_search_tree.md + - 7.5 AVL tree *: chapter_tree/avl_tree.md + - 7.6 Summary: chapter_tree/summary.md + - Chapter 8. Heap: + # [icon: material/family-tree] + - chapter_heap/index.md + - 8.1 Heap: chapter_heap/heap.md + - 8.2 Building a heap: chapter_heap/build_heap.md + - 8.3 Top-k problem: chapter_heap/top_k.md + - 8.4 Summary: chapter_heap/summary.md + - Chapter 9. Graph: + # [icon: material/graphql] + - chapter_graph/index.md + - 9.1 Graph: chapter_graph/graph.md + - 9.2 Basic graph operations: chapter_graph/graph_operations.md + - 9.3 Graph traversal: chapter_graph/graph_traversal.md + - 9.4 Summary: chapter_graph/summary.md + - Chapter 10. Searching: + # [icon: material/text-search] + - chapter_searching/index.md + - 10.1 Binary search: chapter_searching/binary_search.md + - 10.2 Binary search insertion: chapter_searching/binary_search_insertion.md + - 10.3 Binary search boundaries: chapter_searching/binary_search_edge.md + - 10.4 Hashing optimization strategies: chapter_searching/replace_linear_by_hashing.md + - 10.5 Search algorithms revisited: chapter_searching/searching_algorithm_revisited.md + - 10.6 Summary: chapter_searching/summary.md + - Chapter 11. Sorting: + # [icon: material/sort-ascending] + - chapter_sorting/index.md + - 11.1 Sorting algorithms: chapter_sorting/sorting_algorithm.md + - 11.2 Selection sort: chapter_sorting/selection_sort.md + - 11.3 Bubble sort: chapter_sorting/bubble_sort.md + - 11.4 Insertion sort: chapter_sorting/insertion_sort.md + - 11.5 Quick sort: chapter_sorting/quick_sort.md + - 11.6 Merge sort: chapter_sorting/merge_sort.md + - 11.7 Heap sort: chapter_sorting/heap_sort.md + - 11.8 Bucket sort: chapter_sorting/bucket_sort.md + - 11.9 Counting sort: chapter_sorting/counting_sort.md + - 11.10 Radix sort: chapter_sorting/radix_sort.md + - 11.11 Summary: chapter_sorting/summary.md + - Chapter 12. Divide and conquer: + # [icon: material/set-split] + - chapter_divide_and_conquer/index.md + - 12.1 Divide and conquer algorithms: chapter_divide_and_conquer/divide_and_conquer.md + - 12.2 Divide and conquer search strategy: chapter_divide_and_conquer/binary_search_recur.md + - 12.3 Building binary tree problem: chapter_divide_and_conquer/build_binary_tree_problem.md + - 12.4 Tower of Hanoi Problem: chapter_divide_and_conquer/hanota_problem.md + - 12.5 Summary: chapter_divide_and_conquer/summary.md + - Chapter 13. Backtracking: + # [icon: material/map-marker-path] + - chapter_backtracking/index.md + - 13.1 Backtracking algorithms: chapter_backtracking/backtracking_algorithm.md + - 13.2 Permutation problem: chapter_backtracking/permutations_problem.md + - 13.3 Subset sum problem: chapter_backtracking/subset_sum_problem.md + - 13.4 n queens problem: chapter_backtracking/n_queens_problem.md + - 13.5 Summary: chapter_backtracking/summary.md + - Chapter 14. Dynamic programming: + # [icon: material/table-pivot] + - chapter_dynamic_programming/index.md + - 14.1 Introduction to dynamic programming: chapter_dynamic_programming/intro_to_dynamic_programming.md + - 14.2 Characteristics of DP problems: chapter_dynamic_programming/dp_problem_features.md + - 14.3 DP problem-solving approach¶: chapter_dynamic_programming/dp_solution_pipeline.md + - 14.4 0-1 Knapsack problem: chapter_dynamic_programming/knapsack_problem.md + - 14.5 Unbounded knapsack problem: chapter_dynamic_programming/unbounded_knapsack_problem.md + - 14.6 Edit distance problem: chapter_dynamic_programming/edit_distance_problem.md + - 14.7 Summary: chapter_dynamic_programming/summary.md + - Chapter 15. Greedy: + # [icon: material/head-heart-outline] + - chapter_greedy/index.md + - 15.1 Greedy algorithms: chapter_greedy/greedy_algorithm.md + - 15.2 Fractional knapsack problem: chapter_greedy/fractional_knapsack_problem.md + - 15.3 Maximum capacity problem: chapter_greedy/max_capacity_problem.md + - 15.4 Maximum product cutting problem: chapter_greedy/max_product_cutting_problem.md + - 15.5 Summary: chapter_greedy/summary.md + - Chapter 16. Appendix: + # [icon: material/help-circle-outline] + - chapter_appendix/index.md + - 16.1 Installation: chapter_appendix/installation.md + - 16.2 Contributing: chapter_appendix/contribution.md + - 16.3 Terminology: chapter_appendix/terminology.md + - References: + - chapter_reference/index.md diff --git a/giscus.json b/giscus.json new file mode 100644 index 0000000000..84ea074cd0 --- /dev/null +++ b/giscus.json @@ -0,0 +1,7 @@ +{ + "defaultCommentOrder": "newest", + "origins": [ + "https://www.hello-algo.com/", + "https://hello-algo.com/" + ] +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index ad836ae9ff..b5db4c422d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,20 +1,23 @@ # Project information site_name: Hello 算法 site_url: https://www.hello-algo.com/ -site_author: Krahets -site_description: 一本动画图解、能运行、可提问的数据结构与算法入门书 +site_author: krahets +site_description: 动画图解、一键运行的数据结构与算法教程 +docs_dir: build/docs +site_dir: site # Repository repo_name: krahets/hello-algo repo_url: https://github.com/krahets/hello-algo -edit_uri: https://github.com/krahets/hello-algo/tree/master/docs/ +edit_uri: tree/main/docs +version: 1.2.0 # Copyright -copyright: Copyright © 2022 Krahets +copyright: Copyright © 2025 krahets
The website content is licensed under CC BY-NC-SA 4.0 # Configuration theme: name: material - custom_dir: docs/overrides + custom_dir: build/overrides language: zh features: # - announce.dismiss @@ -29,7 +32,7 @@ theme: - navigation.indexes # - navigation.instant # - navigation.prune - - navigation.sections + # - navigation.sections # - navigation.tabs # - navigation.tabs.sticky - navigation.top @@ -43,39 +46,62 @@ theme: palette: - scheme: default primary: white - # accent: indigo + accent: teal toggle: - icon: material/weather-sunny - name: Switch to dark mode + icon: material/theme-light-dark + name: 深色模式 - scheme: slate - # primary: grey - # accent: indigo + primary: black + accent: teal toggle: - icon: material/weather-night - name: Switch to light mode + icon: material/theme-light-dark + name: 浅色模式 font: text: Noto Sans SC code: Fira Code favicon: assets/images/favicon.png - logo: assets/images/logo.png + logo: assets/images/logo.svg icon: logo: logo repo: fontawesome/brands/github - edit: material/file-edit-outline + edit: fontawesome/regular/pen-to-square extra: + alternate: + - name: 简体中文 + link: / + lang: zh + - name: 繁體中文 + link: /zh-hant/ + lang: zh-Hant + - name: English + link: /en/ + lang: en social: - - icon: fontawesome/brands/github + - icon: fontawesome/brands/github link: https://github.com/krahets - - icon: fontawesome/brands/twitter + - icon: fontawesome/brands/x-twitter link: https://twitter.com/krahets - - icon: fontawesome/solid/code + - icon: fontawesome/solid/code link: https://leetcode.cn/u/jyd/ generator: false + status: + new: 最近添加 # Plugins plugins: - search + - glightbox: + touchNavigation: true + loop: false + effect: zoom + slide_effect: none + width: 100% + height: auto + zoomable: true + draggable: false + auto_caption: false + caption_position: bottom # Extensions markdown_extensions: @@ -116,64 +142,156 @@ markdown_extensions: extra_javascript: - javascripts/mathjax.js - - https://polyfill.io/v3/polyfill.min.js?features=es6 - - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js + - https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js + # - javascripts/katex.js + # - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js + # - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js + extra_css: - stylesheets/extra.css + # - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css # Page tree nav: - - 写在前面: - - 关于本书: chapter_preface/about_the_book.md - - 如何使用本书: chapter_preface/suggestions.md - - 编程环境安装: chapter_preface/installation.md - - 一起参与创作: chapter_preface/contribution.md - - 引言: - - 算法无处不在: chapter_introduction/algorithms_are_everywhere.md - - 算法是什么: chapter_introduction/what_is_dsa.md - - 计算复杂度: - - 算法效率评估: chapter_computational_complexity/performance_evaluation.md - - 时间复杂度: chapter_computational_complexity/time_complexity.md - - 空间复杂度: chapter_computational_complexity/space_complexity.md - - 权衡时间与空间: chapter_computational_complexity/space_time_tradeoff.md - - 小结: chapter_computational_complexity/summary.md - - 数据结构简介: - - 数据与内存: chapter_data_structure/data_and_memory.md - - 数据结构分类: chapter_data_structure/classification_of_data_structure.md - - 小结: chapter_data_structure/summary.md - - 数组与链表: - - 数组(Array): chapter_array_and_linkedlist/array.md - - 链表(LinkedList): chapter_array_and_linkedlist/linked_list.md - - 列表(List): chapter_array_and_linkedlist/list.md - - 小结: chapter_array_and_linkedlist/summary.md - - 栈与队列: - - 栈(Stack): chapter_stack_and_queue/stack.md - - 队列(Queue): chapter_stack_and_queue/queue.md - - 双向队列(Deque): chapter_stack_and_queue/deque.md - - 小结: chapter_stack_and_queue/summary.md - - 散列表: - - 哈希表(HashMap): chapter_hashing/hash_map.md - - 哈希冲突处理: chapter_hashing/hash_collision.md - - 小结: chapter_hashing/summary.md - - 二叉树: - - 二叉树(Binary Tree): chapter_tree/binary_tree.md - - 二叉树遍历: chapter_tree/binary_tree_traversal.md - - 二叉搜索树: chapter_tree/binary_search_tree.md - - AVL 树 *: chapter_tree/avl_tree.md - - 小结: chapter_tree/summary.md - - 堆: - - 堆(Heap): chapter_heap/heap.md - - 查找算法: - - 线性查找: chapter_searching/linear_search.md - - 二分查找: chapter_searching/binary_search.md - - 哈希查找: chapter_searching/hashing_search.md - - 小结: chapter_searching/summary.md - - 排序算法: - - 排序简介: chapter_sorting/intro_to_sort.md - - 冒泡排序: chapter_sorting/bubble_sort.md - - 插入排序: chapter_sorting/insertion_sort.md - - 快速排序: chapter_sorting/quick_sort.md - - 归并排序: chapter_sorting/merge_sort.md - - 小结: chapter_sorting/summary.md + - 序: + - chapter_hello_algo/index.md + - 第 0 章   前言: + # [icon: material/book-open-outline] + - chapter_preface/index.md + - 0.1   关于本书: chapter_preface/about_the_book.md + - 0.2   如何使用本书: chapter_preface/suggestions.md + - 0.3   小结: chapter_preface/summary.md + - 第 1 章   初识算法: + # [icon: material/calculator-variant-outline] + - chapter_introduction/index.md + - 1.1   算法无处不在: chapter_introduction/algorithms_are_everywhere.md + - 1.2   算法是什么: chapter_introduction/what_is_dsa.md + - 1.3   小结: chapter_introduction/summary.md + - 第 2 章   复杂度分析: + # [icon: material/timer-sand] + - chapter_computational_complexity/index.md + - 2.1   算法效率评估: chapter_computational_complexity/performance_evaluation.md + - 2.2   迭代与递归: chapter_computational_complexity/iteration_and_recursion.md + - 2.3   时间复杂度: chapter_computational_complexity/time_complexity.md + - 2.4   空间复杂度: chapter_computational_complexity/space_complexity.md + - 2.5   小结: chapter_computational_complexity/summary.md + - 第 3 章   数据结构: + # [icon: material/shape-outline] + - chapter_data_structure/index.md + - 3.1   数据结构分类: chapter_data_structure/classification_of_data_structure.md + - 3.2   基本数据类型: chapter_data_structure/basic_data_types.md + - 3.3   数字编码 *: chapter_data_structure/number_encoding.md + - 3.4   字符编码 *: chapter_data_structure/character_encoding.md + - 3.5   小结: chapter_data_structure/summary.md + - 第 4 章   数组与链表: + # [icon: material/view-list-outline] + - chapter_array_and_linkedlist/index.md + - 4.1   数组: chapter_array_and_linkedlist/array.md + - 4.2   链表: chapter_array_and_linkedlist/linked_list.md + - 4.3   列表: chapter_array_and_linkedlist/list.md + - 4.4   内存与缓存 *: chapter_array_and_linkedlist/ram_and_cache.md + - 4.5   小结: chapter_array_and_linkedlist/summary.md + - 第 5 章   栈与队列: + # [icon: material/stack-overflow] + - chapter_stack_and_queue/index.md + - 5.1   栈: chapter_stack_and_queue/stack.md + - 5.2   队列: chapter_stack_and_queue/queue.md + - 5.3   双向队列: chapter_stack_and_queue/deque.md + - 5.4   小结: chapter_stack_and_queue/summary.md + - 第 6 章   哈希表: + # [icon: material/table-search] + - chapter_hashing/index.md + - 6.1   哈希表: chapter_hashing/hash_map.md + - 6.2   哈希冲突: chapter_hashing/hash_collision.md + - 6.3   哈希算法: chapter_hashing/hash_algorithm.md + - 6.4   小结: chapter_hashing/summary.md + - 第 7 章   树: + # [icon: material/graph-outline] + - chapter_tree/index.md + - 7.1   二叉树: chapter_tree/binary_tree.md + - 7.2   二叉树遍历: chapter_tree/binary_tree_traversal.md + - 7.3   二叉树数组表示: chapter_tree/array_representation_of_tree.md + - 7.4   二叉搜索树: chapter_tree/binary_search_tree.md + - 7.5   AVL 树 *: chapter_tree/avl_tree.md + - 7.6   小结: chapter_tree/summary.md + - 第 8 章   堆: + # [icon: material/family-tree] + - chapter_heap/index.md + - 8.1   堆: chapter_heap/heap.md + - 8.2   建堆操作: chapter_heap/build_heap.md + - 8.3   Top-k 问题: chapter_heap/top_k.md + - 8.4   小结: chapter_heap/summary.md + - 第 9 章   图: + # [icon: material/graphql] + - chapter_graph/index.md + - 9.1   图: chapter_graph/graph.md + - 9.2   图基础操作: chapter_graph/graph_operations.md + - 9.3   图的遍历: chapter_graph/graph_traversal.md + - 9.4   小结: chapter_graph/summary.md + - 第 10 章   搜索: + # [icon: material/text-search] + - chapter_searching/index.md + - 10.1   二分查找: chapter_searching/binary_search.md + - 10.2   二分查找插入点: chapter_searching/binary_search_insertion.md + - 10.3   二分查找边界: chapter_searching/binary_search_edge.md + - 10.4   哈希优化策略: chapter_searching/replace_linear_by_hashing.md + - 10.5   重识搜索算法: chapter_searching/searching_algorithm_revisited.md + - 10.6   小结: chapter_searching/summary.md + - 第 11 章   排序: + # [icon: material/sort-ascending] + - chapter_sorting/index.md + - 11.1   排序算法: chapter_sorting/sorting_algorithm.md + - 11.2   选择排序: chapter_sorting/selection_sort.md + - 11.3   冒泡排序: chapter_sorting/bubble_sort.md + - 11.4   插入排序: chapter_sorting/insertion_sort.md + - 11.5   快速排序: chapter_sorting/quick_sort.md + - 11.6   归并排序: chapter_sorting/merge_sort.md + - 11.7   堆排序: chapter_sorting/heap_sort.md + - 11.8   桶排序: chapter_sorting/bucket_sort.md + - 11.9   计数排序: chapter_sorting/counting_sort.md + - 11.10   基数排序: chapter_sorting/radix_sort.md + - 11.11   小结: chapter_sorting/summary.md + - 第 12 章   分治: + # [icon: material/set-split] + - chapter_divide_and_conquer/index.md + - 12.1   分治算法: chapter_divide_and_conquer/divide_and_conquer.md + - 12.2   分治搜索策略: chapter_divide_and_conquer/binary_search_recur.md + - 12.3   构建树问题: chapter_divide_and_conquer/build_binary_tree_problem.md + - 12.4   汉诺塔问题: chapter_divide_and_conquer/hanota_problem.md + - 12.5   小结: chapter_divide_and_conquer/summary.md + - 第 13 章   回溯: + # [icon: material/map-marker-path] + - chapter_backtracking/index.md + - 13.1   回溯算法: chapter_backtracking/backtracking_algorithm.md + - 13.2   全排列问题: chapter_backtracking/permutations_problem.md + - 13.3   子集和问题: chapter_backtracking/subset_sum_problem.md + - 13.4   N 皇后问题: chapter_backtracking/n_queens_problem.md + - 13.5   小结: chapter_backtracking/summary.md + - 第 14 章   动态规划: + # [icon: material/table-pivot] + - chapter_dynamic_programming/index.md + - 14.1   初探动态规划: chapter_dynamic_programming/intro_to_dynamic_programming.md + - 14.2   DP 问题特性: chapter_dynamic_programming/dp_problem_features.md + - 14.3   DP 解题思路: chapter_dynamic_programming/dp_solution_pipeline.md + - 14.4   0-1 背包问题: chapter_dynamic_programming/knapsack_problem.md + - 14.5   完全背包问题: chapter_dynamic_programming/unbounded_knapsack_problem.md + - 14.6   编辑距离问题: chapter_dynamic_programming/edit_distance_problem.md + - 14.7   小结: chapter_dynamic_programming/summary.md + - 第 15 章   贪心: + # [icon: material/head-heart-outline] + - chapter_greedy/index.md + - 15.1   贪心算法: chapter_greedy/greedy_algorithm.md + - 15.2   分数背包问题: chapter_greedy/fractional_knapsack_problem.md + - 15.3   最大容量问题: chapter_greedy/max_capacity_problem.md + - 15.4   最大切分乘积问题: chapter_greedy/max_product_cutting_problem.md + - 15.5   小结: chapter_greedy/summary.md + - 第 16 章   附录: + # [icon: material/help-circle-outline] + - chapter_appendix/index.md + - 16.1   编程环境安装: chapter_appendix/installation.md + - 16.2   一起参与创作: chapter_appendix/contribution.md + - 16.3   术语表: chapter_appendix/terminology.md - 参考文献: - chapter_reference/index.md + - 纸质书: + - chapter_paperbook/index.md diff --git a/overrides/assets/images/favicon.png b/overrides/assets/images/favicon.png new file mode 100644 index 0000000000..2322428317 Binary files /dev/null and b/overrides/assets/images/favicon.png differ diff --git a/overrides/assets/images/logo.png b/overrides/assets/images/logo.png new file mode 100644 index 0000000000..08b5970a98 Binary files /dev/null and b/overrides/assets/images/logo.png differ diff --git a/overrides/assets/images/logo.svg b/overrides/assets/images/logo.svg new file mode 100644 index 0000000000..17aeab49ee --- /dev/null +++ b/overrides/assets/images/logo.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/overrides/javascripts/katex.js b/overrides/javascripts/katex.js new file mode 100644 index 0000000000..0946ce0a89 --- /dev/null +++ b/overrides/javascripts/katex.js @@ -0,0 +1,10 @@ +document$.subscribe(({ body }) => { + renderMathInElement(body, { + delimiters: [ + { left: "$$", right: "$$", display: true }, + { left: "$", right: "$", display: false }, + { left: "\\(", right: "\\)", display: false }, + { left: "\\[", right: "\\]", display: true }, + ], + }); +}); diff --git a/overrides/javascripts/mathjax.js b/overrides/javascripts/mathjax.js new file mode 100644 index 0000000000..0631320c7c --- /dev/null +++ b/overrides/javascripts/mathjax.js @@ -0,0 +1,17 @@ +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true, + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex", + enableMenu: false, + }, +}; + +document$.subscribe(() => { + MathJax.typesetPromise(); +}); diff --git a/overrides/main.html b/overrides/main.html new file mode 100644 index 0000000000..27244e3917 --- /dev/null +++ b/overrides/main.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block announce %} +{% if config.theme.language == 'zh' %} + {% set announcements = '纸质书已发行,详情请见这里' %} +{% elif config.theme.language == 'zh-Hant' %} + {% set announcements = '紙質書(簡體中文版)已發行,詳情請見這裡' %} +{% elif config.theme.language == 'en' %} + {% set announcements = 'Welcome to contribute to Chinese-to-English translation! For more details, please refer to CONTRIBUTING.md.' %} +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/overrides/partials/.gitignore b/overrides/partials/.gitignore new file mode 100644 index 0000000000..3eebfb928f --- /dev/null +++ b/overrides/partials/.gitignore @@ -0,0 +1 @@ +comments.html \ No newline at end of file diff --git a/overrides/partials/LICENSE b/overrides/partials/LICENSE new file mode 100644 index 0000000000..98d7a71b5e --- /dev/null +++ b/overrides/partials/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2023 Martin Donath + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. \ No newline at end of file diff --git a/overrides/partials/actions.html b/overrides/partials/actions.html new file mode 100644 index 0000000000..86eace6eab --- /dev/null +++ b/overrides/partials/actions.html @@ -0,0 +1,32 @@ + +{% if page.edit_url %} + + + {% if "content.action.edit" in features and "edit" not in page.meta.hide %} + + {% set icon = config.theme.icon.edit or "material/file-edit-outline" %} + {% include ".icons/" ~ icon ~ ".svg" %} + + {% endif %} + + + {% if "content.action.view" in features %} + {% if "/blob/" in page.edit_url %} + {% set part = "blob" %} + {% else %} + {% set part = "edit" %} + {% endif %} + + {% set icon = config.theme.icon.view or "material/file-eye-outline" %} + {% include ".icons/" ~ icon ~ ".svg" %} + + {% endif %} +{% endif %} \ No newline at end of file diff --git a/overrides/partials/content.html b/overrides/partials/content.html new file mode 100644 index 0000000000..38323c9ae3 --- /dev/null +++ b/overrides/partials/content.html @@ -0,0 +1,80 @@ + +{% if "material/tags" in config.plugins and tags %} + {% include "partials/tags.html" %} +{% endif %} + + +{% include "partials/actions.html" %} + + +{{ page.content }} + + +{% if page.meta and ( + page.meta.git_revision_date_localized or + page.meta.revision_date +) %} + {% include "partials/source-file.html" %} +{% endif %} + + +{% include "partials/feedback.html" %} + + + + + +{% include "partials/comments.html" %} \ No newline at end of file diff --git a/overrides/stylesheets/extra.css b/overrides/stylesheets/extra.css new file mode 100644 index 0000000000..2d4af7f0f9 --- /dev/null +++ b/overrides/stylesheets/extra.css @@ -0,0 +1,554 @@ +/* Color Settings */ +/* https://github.com/squidfunk/mkdocs-material/blob/6b5035f5580f97532d664e3d1babf5f320e88ee9/src/assets/stylesheets/main/_colors.scss */ +/* https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#custom-colors */ +:root>* { + --md-primary-fg-color: #ffffff; + --md-primary-bg-color: #1d1d20; + + --md-default-fg-color: #1d1d20; + --md-default-bg-color: #ffffff; + + --md-body-bg-color: #22272e; + --md-header-bg-color: rgba(255, 255, 255, 0.6); + + --md-code-fg-color: #1d1d20; + --md-code-bg-color: #f5f5f5; + + --md-accent-fg-color: #999; + + --md-admonition-fg-color: #1d1d20; + + --md-typeset-color: #1d1d20; + --md-typeset-a-color: #349890; + + --md-typeset-btn-color: #55aea6; + --md-typeset-btn-hover-color: #52bbb1; + + --md-admonition-icon--pythontutor: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-pythontutor-color: #eee; +} + +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #22272e; + --md-primary-bg-color: #adbac7; + + --md-default-fg-color: #adbac7; + --md-default-bg-color: #22272e; + + --md-body-bg-color: #22272e; + --md-header-bg-color: rgba(34, 39, 46, 0.8); + + --md-code-fg-color: #adbac7; + --md-code-bg-color: #1d2126; + + --md-accent-fg-color: #aaa; + + --md-admonition-fg-color: #adbac7; + + --md-footer-fg-color: #adbac7; + + --md-typeset-color: #adbac7; + --md-typeset-a-color: #52bbb1; + + --md-typeset-btn-color: #52bbb1; + --md-typeset-btn-hover-color: #55aea6; + + --md-admonition-pythontutor-color: #30363f; +} + +[data-md-color-scheme="slate"][data-md-color-primary="black"], +[data-md-color-scheme="slate"][data-md-color-primary="white"] { + --md-typeset-a-color: #52bbb1; +} + +[data-md-color-primary="black"] .md-header { + background-color: var(--md-header-bg-color); +} + +.md-header--shadow, +.md-header--landing { + box-shadow: none; + transition: none; + backdrop-filter: saturate(180%) blur(20px); + /* Gaussian blur */ + -webkit-backdrop-filter: saturate(180%) blur(20px); + /* Safari */ + background-color: var(--md-header-bg-color); +} + +/* https://github.com/squidfunk/mkdocs-material/issues/4832#issuecomment-1374891676 */ +.md-nav__link[for] { + color: var(--md-default-fg-color) !important; +} + +/* Figure class */ +.animation-figure { + border-radius: 0.3rem; + display: block; + margin: 0 auto; + box-shadow: var(--md-shadow-z2); +} + +/* Cover image class */ +.cover-image { + width: 28rem; + height: auto; + border-radius: 0.3rem; + display: block; + margin: 0 auto; + box-shadow: var(--md-shadow-z2); +} + +/* Center Markdown Tables (requires md_in_html extension) */ +.center-table { + text-align: center; +} + +/* Reset alignment for table cells */ +.md-typeset .center-table :is(td, th):not([align]) { + text-align: initial; +} + +/* Font size */ +.md-typeset { + font-size: 0.75rem; + line-height: 1.5; +} + +.md-typeset pre { + font-size: 0.95em; +} + +/* Markdown Header */ +/* https://github.com/squidfunk/mkdocs-material/blob/dcab57dd1cced4b77875c1aa1b53467c62709d31/src/assets/stylesheets/main/_typeset.scss */ +.md-typeset h1 { + font-weight: 400; + color: var(--md-default-fg-color); +} + +.md-typeset h2 { + font-weight: 400; +} + +.md-typeset h3 { + font-weight: 500; +} + +.md-typeset h5 { + text-transform: none; +} + +.md-typeset a:hover { + color: var(--md-typeset-a-color); + text-decoration: underline; +} + +.md-typeset code { + border-radius: 0.2rem; +} + +.highlight span.filename { + font-weight: normal; +} + +/* font-family setting for Win10 */ +body { + --md-text-font-family: -apple-system, BlinkMacSystemFont, + var(--md-text-font, _), Helvetica, Arial, sans-serif; + --md-code-font-family: var(--md-code-font, _), SFMono-Regular, Consolas, Menlo, + -apple-system, BlinkMacSystemFont, var(--md-text-font, _), monospace; +} + +/* max height of code block */ +/* https://github.com/squidfunk/mkdocs-material/issues/3444 */ +.md-typeset pre>code { + max-height: 25rem; +} + +/* Make the picture not glare in dark theme */ +[data-md-color-scheme="slate"] .md-typeset img, +[data-md-color-scheme="slate"] .md-typeset svg, +[data-md-color-scheme="slate"] .md-typeset video { + filter: brightness(0.85) invert(0.05); +} + +/* landing page */ +.header-img-div { + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; + width: 100%; + /* Default to full width */ +} + +/* Admonition for python tutor */ +.md-typeset .admonition.pythontutor, +.md-typeset details.pythontutor { + border-color: var(--md-default-fg-color--lightest); + margin-top: 0; + margin-bottom: 1.5625em; +} + +.md-typeset .admonition:focus-within, +.md-typeset details:focus-within { + box-shadow: var(--md-shadow-z1); +} + +.md-typeset .pythontutor>.admonition-title, +.md-typeset .pythontutor>summary { + background-color: var(--md-code-bg-color); +} + +.md-typeset .pythontutor>.admonition-title::before, +.md-typeset .pythontutor>summary::before { + background-color: rgb(55, 118, 171); + -webkit-mask-image: var(--md-admonition-icon--pythontutor); + mask-image: var(--md-admonition-icon--pythontutor); +} + +.md-typeset .admonition-title:before, +.md-typeset summary:before { + width: 1.25em; +} + +/* code block tabs */ +.md-typeset .tabbed-labels>label { + font-size: 0.61rem; +} + +.md-typeset .tabbed-labels--linked>label>a { + padding: .78125em 1.0em .625em; +} + +/* header banner */ +.md-banner { + background-color: var(--md-code-bg-color); + color: var(--md-default-fg-color); + font-size: 0.75rem; +} + +.md-banner .banner-svg svg { + margin-right: 0.3rem; + height: 0.63rem; + fill: var(--md-default-fg-color); +} + +.pythontutor-iframe { + width: 125%; + height: 125%; + max-width: 125% !important; + max-height: 125% !important; + transform: scale(0.8); + transform-origin: top left; + border: none; +} + +/* landing page container */ +.home-div { + width: 100%; + height: auto; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); + font-size: 0.9rem; + padding: 3em 2em; + text-align: center; +} + +.section-content { + width: 100%; + height: auto; + max-width: 70vw; +} + +/* rounded button */ +.rounded-button { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 10em; + margin: 0 0.1em; + padding: 0.6em 1.3em; + border: none; + background-color: var(--md-typeset-btn-color); + color: var(--md-primary-fg-color) !important; + text-align: center; + text-decoration: none; + cursor: pointer; +} + +.rounded-button:hover { + background-color: var(--md-typeset-btn-hover-color); +} + +.rounded-button span { + margin: 0; + margin-bottom: 0.07em; + white-space: nowrap; +} + +.rounded-button svg { + fill: var(--md-primary-fg-color); + width: auto; + height: 1.2em; + margin-right: 0.5em; +} + +/* device image */ +.device-on-hover { + width: auto; + transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; +} + +a:hover .device-on-hover { + filter: drop-shadow(0 0 0.2rem rgba(0, 0, 0, 0.15)); + transform: scale(1.01); +} + +/* text button */ +.reading-media { + display: flex; + justify-content: center; + align-items: flex-end; + height: 32vw; +} + +.media-block { + height: 100%; + margin: 0 0.2em; +} + +.text-button { + width: auto; + color: var(--md-typeset-btn-color); + text-decoration: none; + text-align: center; + margin: 2.7em auto; +} + +.text-button span { + white-space: nowrap; +} + +.text-button svg { + display: inline-block; + fill: var(--md-typeset-btn-color); + width: auto; + height: 0.9em; + background-size: cover; + padding-top: 0.17em; + margin-left: 0.15em; +} + +a:hover .text-button span { + text-decoration: underline; +} + +/* hero image */ +.hero-div { + height: min(84vh, 75vw); + width: min(112vh, 100vw); + margin: 0 auto; + margin-top: -2.4rem; + padding: 0; + position: relative; + font-size: min(1.8vh, 2.5vw); + font-weight: normal; +} + +.hero-bg { + height: 100%; + width: 100%; + object-fit: cover; + position: absolute; +} + +/* hover on the planets */ +.hero-div>a>img { + width: auto; + position: absolute; + transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; +} + +.hero-div>a>span { + margin: 0; + position: absolute; + transform: translateX(-50%) translateY(-50%); + white-space: nowrap; + /* prevent line breaks */ + color: white; +} + +.hero-div>a:hover>img { + filter: brightness(1.15) saturate(1.1) drop-shadow(0 0 0.5rem rgba(255, 255, 255, 0.2)); + transform: scale(1.03); +} + +.hero-div>a:hover>span { + text-decoration: underline; + color: var(--md-typeset-btn-color); +} + +.heading-div { + width: 100%; + position: absolute; + transform: translateX(-50%); + left: 50%; + bottom: min(2vh, 3vw); + pointer-events: none; + color: #fff; +} + +/* code badge */ +.code-badge { + width: 100%; + height: auto; + margin: 1em auto; +} + +.code-badge img { + height: 1.07em; + width: auto; +} + +/* brief intro */ +.intro-container { + display: flex; + align-items: center; + margin: 2em auto; +} + +.intro-image { + flex-shrink: 0; + flex-grow: 0; + width: 50%; + border-radius: 0.5em; + box-shadow: var(--md-shadow-z2); +} + +.intro-text { + flex-grow: 1; + /* fill the space */ + display: flex; + flex-direction: column; + justify-content: center; + text-align: left; + align-items: flex-start; + width: fit-content; + margin: 2em; +} + +.intro-text>div { + align-self: flex-start; + width: auto; + margin: 0 auto; +} + +.endor-text { + width: 50%; +} + +.intro-quote { + color: var(--md-accent-fg-color); + font-weight: bold; +} + +/* contributors table */ +.profile-div { + display: flex; + flex-wrap: wrap; + justify-content: center; + max-width: 40em; + margin: 1em auto; +} + +.profile-cell { + flex: 1 1 15%; + margin: 1em 0; + text-align: center; +} + +.profile-img { + width: 5em; + border-radius: 50%; + margin-bottom: 0.5em; +} + +.giscus-container { + width: 40em; + max-width: 100%; + margin: 0 auto; +} + +/* Hide navigation */ +@media screen and (max-width: 76.25em) { + .section-content { + max-width: 95vw; + } + + .reading-media { + height: 33vw; + } + + .contrib-image { + width: 100%; + } +} + +/* mobile devices */ +@media screen and (max-width: 60em) { + .home-div { + font-size: 0.75rem; + } + + .hero-div { + margin-top: -4rem; + } + + .intro-container { + flex-direction: column; + } + + .intro-text { + width: auto; + order: 2; + margin: 0 auto; + } + + .endor-text { + width: auto; + margin: 0 auto; + } + + .intro-image { + width: 100%; + order: 1; + margin-bottom: 1em; + } + + .text-button { + margin: 0.7em auto; + } + + .profile-cell { + flex: 1 1 30%; + } +} + +.video-container { + position: relative; + padding-bottom: 56.25%; + /* 16:9 */ + height: 0; +} + +.video-container iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/zh-hant/README.md b/zh-hant/README.md new file mode 100644 index 0000000000..0e797a8385 --- /dev/null +++ b/zh-hant/README.md @@ -0,0 +1,92 @@ +

+ + +

+ +

+ hello-algo-typing-svg +
+ 動畫圖解、一鍵執行的資料結構與演算法教程 +

+ +

+ + + + +

+ +

+ + +

+ +

+ + + + + + + + + + + + + +

+ +

+ 简体中文 + | + 繁體中文 + | + English +

+ +## 關於本書 + +本專案旨在打造一本開源免費、新手友好的資料結構與演算法入門教程。 + +- 全書採用動畫圖解,內容清晰易懂、學習曲線平滑,引導初學者探索資料結構與演算法的知識地圖。 +- 源程式碼可一鍵執行,幫助讀者在練習中提升程式設計技能,瞭解演算法工作原理和資料結構底層實現。 +- 提倡讀者互助學習,歡迎大家在評論區提出問題與分享見解,在交流討論中共同進步。 + +若本書對您有所幫助,請在頁面右上角點個 Star :star: 支持一下,謝謝! + +## 推薦語 + +> “一本通俗易懂的資料結構與演算法入門書,引導讀者手腦並用地學習,強烈推薦演算法初學者閱讀。” +> +> **—— 鄧俊輝,清華大學計算機系教授** + +> “如果我當年學資料結構與演算法的時候有《Hello 演算法》,學起來應該會簡單 10 倍!” +> +> **—— 李沐,亞馬遜資深首席科學家** + +## 貢獻 + +> [!Important] +> +> 歡迎您於 [#1171](https://github.com/krahets/hello-algo/issues/1171) 為繁體中文版勘誤。 + +本開源書仍在持續更新之中,歡迎您參與本專案,一同為讀者提供更優質的學習內容。 + +- [內容修正](https://www.hello-algo.com/chapter_appendix/contribution/):請您協助修正或在評論區指出語法錯誤、內容缺失、文字歧義、無效連結或程式碼 bug 等問題。 +- [程式碼轉譯](https://github.com/krahets/hello-algo/issues/15):期待您貢獻各種語言程式碼,已支持 Python、Java、C++、Go、JavaScript 等 12 門程式語言。 +- [中譯英](https://github.com/krahets/hello-algo/issues/914):誠邀您加入我們的翻譯小組,成員主要來自計算機相關專業、英語專業和英文母語者。 + +歡迎您提出寶貴意見和建議,如有任何問題請提交 Issues 或微信聯繫 `krahets-jyd` 。 + +感謝本開源書的每一位撰稿人,是他們的無私奉獻讓這本書變得更好,他們是: + +

+ + + +

+ +## License + +The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/zh-hant/codes/Dockerfile b/zh-hant/codes/Dockerfile new file mode 100644 index 0000000000..97d37bbcff --- /dev/null +++ b/zh-hant/codes/Dockerfile @@ -0,0 +1,35 @@ +FROM ubuntu:latest + +# Use Ubuntu image from Aliyun +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 + +# Install languages environment +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 ;; \ + # More languages... + *) \ + echo "Warning: No installation workflow for $LANG" ;; \ + esac \ + done + +WORKDIR /codes +COPY ./ ./ + +CMD ["/bin/bash"] diff --git a/zh-hant/codes/c/.gitignore b/zh-hant/codes/c/.gitignore new file mode 100644 index 0000000000..698ee4e212 --- /dev/null +++ b/zh-hant/codes/c/.gitignore @@ -0,0 +1,9 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ +*.dSYM/ + +build/ diff --git a/zh-hant/codes/c/CMakeLists.txt b/zh-hant/codes/c/CMakeLists.txt new file mode 100644 index 0000000000..bb5f8f6a9a --- /dev/null +++ b/zh-hant/codes/c/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo C) + +set(CMAKE_C_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/CMakeLists.txt b/zh-hant/codes/c/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 0000000000..29677a0be6 --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(array array.c) +add_executable(linked_list linked_list.c) +add_executable(my_list my_list.c) \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/array.c b/zh-hant/codes/c/chapter_array_and_linkedlist/array.c new file mode 100644 index 0000000000..dc74b525c4 --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/array.c @@ -0,0 +1,114 @@ +/** + * 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]; + } + // 將 num 賦給 index 處的元素 + 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; +} + +/* Driver Code */ +int main() { + /* 初始化陣列 */ + int size = 5; + int arr[5]; + printf("陣列 arr = "); + printArray(arr, size); + + int nums[] = {1, 3, 2, 5, 4}; + printf("陣列 nums = "); + printArray(nums, size); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums, size); + printf("在 nums 中獲取隨機元素 %d", randomNum); + + /* 長度擴展 */ + int enlarge = 3; + int *res = extend(nums, size, enlarge); + size += enlarge; + printf("將陣列長度擴展至 8 ,得到 nums = "); + printArray(res, size); + + /* 插入元素 */ + insert(res, size, 6, 3); + printf("在索引 3 處插入數字 6 ,得到 nums = "); + printArray(res, size); + + /* 刪除元素 */ + removeItem(res, size, 2); + printf("刪除索引 2 處的元素,得到 nums = "); + printArray(res, size); + + /* 走訪陣列 */ + traverse(res, size); + + /* 查詢元素 */ + int index = find(res, size, 3); + printf("在 res 中查詢元素 3 ,得到索引 = %d\n", index); + + /* 釋放記憶體 */ + free(res); + return 0; +} diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/linked_list.c b/zh-hant/codes/c/chapter_array_and_linkedlist/linked_list.c new file mode 100644 index 0000000000..047d2832a3 --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/linked_list.c @@ -0,0 +1,89 @@ +/** + * File: linked_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +// 注意:stdio.h 佔用了 remove 關鍵詞 +void removeItem(ListNode *n0) { + if (!n0->next) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 釋放記憶體 + free(P); +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == NULL) + return NULL; + head = head->next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +int find(ListNode *head, int target) { + int index = 0; + while (head) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + ListNode *n0 = newListNode(1); + ListNode *n1 = newListNode(3); + ListNode *n2 = newListNode(2); + ListNode *n3 = newListNode(5); + ListNode *n4 = newListNode(4); + // 構建節點之間的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + printf("初始化的鏈結串列為\r\n"); + printLinkedList(n0); + + /* 插入節點 */ + insert(n0, newListNode(0)); + printf("插入節點後的鏈結串列為\r\n"); + printLinkedList(n0); + + /* 刪除節點 */ + removeItem(n0); + printf("刪除節點後的鏈結串列為\r\n"); + printLinkedList(n0); + + /* 訪問節點 */ + ListNode *node = access(n0, 3); + printf("鏈結串列中索引 3 處的節點的值 = %d\r\n", node->val); + + /* 查詢節點 */ + int index = find(n0, 2); + printf("鏈結串列中值為 2 的節點的索引 = %d\r\n", index); + + // 釋放記憶體 + freeMemoryLinkedList(n0); + return 0; +} diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/my_list.c b/zh-hant/codes/c/chapter_array_and_linkedlist/my_list.c new file mode 100644 index 0000000000..7b2742f0a1 --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/my_list.c @@ -0,0 +1,163 @@ +/** + * File: my_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 串列類別 */ +typedef struct { + int *arr; // 陣列(儲存串列元素) + int capacity; // 串列容量 + int size; // 串列大小 + int extendRatio; // 串列每次擴容的倍數 +} MyList; + +void extendCapacity(MyList *nums); + +/* 建構子 */ +MyList *newMyList() { + MyList *nums = malloc(sizeof(MyList)); + nums->capacity = 10; + nums->arr = malloc(sizeof(int) * nums->capacity); + nums->size = 0; + nums->extendRatio = 2; + return nums; +} + +/* 析構函式 */ +void delMyList(MyList *nums) { + free(nums->arr); + free(nums); +} + +/* 獲取串列長度 */ +int size(MyList *nums) { + return nums->size; +} + +/* 獲取串列容量 */ +int capacity(MyList *nums) { + return nums->capacity; +} + +/* 訪問元素 */ +int get(MyList *nums, int index) { + assert(index >= 0 && index < nums->size); + return nums->arr[index]; +} + +/* 更新元素 */ +void set(MyList *nums, int index, int num) { + assert(index >= 0 && index < nums->size); + nums->arr[index] = num; +} + +/* 在尾部新增元素 */ +void add(MyList *nums, int num) { + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 擴容 + } + nums->arr[size(nums)] = num; + nums->size++; +} + +/* 在中間插入元素 */ +void insert(MyList *nums, int index, int num) { + assert(index >= 0 && index < size(nums)); + // 元素數量超出容量時,觸發擴容機制 + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 擴容 + } + for (int i = size(nums); i > index; --i) { + nums->arr[i] = nums->arr[i - 1]; + } + nums->arr[index] = num; + nums->size++; +} + +/* 刪除元素 */ +// 注意:stdio.h 佔用了 remove 關鍵詞 +int removeItem(MyList *nums, int index) { + assert(index >= 0 && index < size(nums)); + int num = nums->arr[index]; + for (int i = index; i < size(nums) - 1; i++) { + nums->arr[i] = nums->arr[i + 1]; + } + nums->size--; + return num; +} + +/* 串列擴容 */ +void extendCapacity(MyList *nums) { + // 先分配空間 + int newCapacity = capacity(nums) * nums->extendRatio; + int *extend = (int *)malloc(sizeof(int) * newCapacity); + int *temp = nums->arr; + + // 複製舊資料到新資料 + for (int i = 0; i < size(nums); i++) + extend[i] = nums->arr[i]; + + // 釋放舊資料 + free(temp); + + // 更新新資料 + nums->arr = extend; + nums->capacity = newCapacity; +} + +/* 將串列轉換為 Array 用於列印 */ +int *toArray(MyList *nums) { + return nums->arr; +} + +/* Driver Code */ +int main() { + /* 初始化串列 */ + MyList *nums = newMyList(); + /* 在尾部新增元素 */ + add(nums, 1); + add(nums, 3); + add(nums, 2); + add(nums, 5); + add(nums, 4); + printf("串列 nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,長度 = %d\n", capacity(nums), size(nums)); + + /* 在中間插入元素 */ + insert(nums, 3, 6); + printf("在索引 3 處插入數字 6 ,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 刪除元素 */ + removeItem(nums, 3); + printf("刪除索引 3 處的元素,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 訪問元素 */ + int num = get(nums, 1); + printf("訪問索引 1 處的元素,得到 num = %d\n", num); + + /* 更新元素 */ + set(nums, 1, 0); + printf("將索引 1 處的元素更新為 0 ,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + add(nums, i); + } + + printf("擴容後的串列 nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,長度 = %d\n", capacity(nums), size(nums)); + + /* 釋放分配記憶體 */ + delMyList(nums); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/CMakeLists.txt b/zh-hant/codes/c/chapter_backtracking/CMakeLists.txt new file mode 100644 index 0000000000..70161b6dd8 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(permutations_i permutations_i.c) +add_executable(permutations_ii permutations_ii.c) +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) +add_executable(subset_sum_i_naive subset_sum_i_naive.c) +add_executable(subset_sum_i subset_sum_i.c) +add_executable(subset_sum_ii subset_sum_ii.c) +add_executable(n_queens n_queens.c) \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_backtracking/n_queens.c b/zh-hant/codes/c/chapter_backtracking/n_queens.c new file mode 100644 index 0000000000..f238194fa9 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/n_queens.c @@ -0,0 +1,95 @@ +/** + * File : n_queens.c + * Created Time: 2023-09-25 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* 回溯演算法:n 皇后 */ +void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], + bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { + // 當放置完所有行時,記錄解 + if (row == n) { + res[*resSize] = (char **)malloc(sizeof(char *) * n); + for (int i = 0; i < n; ++i) { + res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); + strcpy(res[*resSize][i], state[i]); + } + (*resSize)++; + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +char ***nQueens(int n, int *returnSize) { + char state[MAX_SIZE][MAX_SIZE]; + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + state[i][j] = '#'; + } + state[i][n] = '\0'; + } + bool cols[MAX_SIZE] = {false}; // 記錄列是否有皇后 + bool diags1[2 * MAX_SIZE - 1] = {false}; // 記錄主對角線上是否有皇后 + bool diags2[2 * MAX_SIZE - 1] = {false}; // 記錄次對角線上是否有皇后 + + char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); + *returnSize = 0; + backtrack(0, n, state, res, returnSize, cols, diags1, diags2); + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + int returnSize; + char ***res = nQueens(n, &returnSize); + + printf("輸入棋盤長寬為%d\n", n); + printf("皇后放置方案共有 %d 種\n", returnSize); + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + printf("["); + for (int k = 0; res[i][j][k] != '\0'; ++k) { + printf("%c", res[i][j][k]); + if (res[i][j][k + 1] != '\0') { + printf(", "); + } + } + printf("]\n"); + } + printf("---------------------\n"); + } + + // 釋放記憶體 + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + free(res[i][j]); + } + free(res[i]); + } + free(res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/permutations_i.c b/zh-hant/codes/c/chapter_backtracking/permutations_i.c new file mode 100644 index 0000000000..cc83abf323 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/permutations_i.c @@ -0,0 +1,79 @@ +/** + * File: permutations_i.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 假設最多有 1000 個排列 +#define MAX_SIZE 1000 + +/* 回溯演算法:全排列 I */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 當狀態長度等於元素數量時,記錄解 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // 走訪所有選擇 + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state[stateSize] = choice; + // 進行下一輪選擇 + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + } + } +} + +/* 全排列 I */ +int **permutationsI(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 2, 3}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsI(nums, numsSize, &returnSize); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("\n所有排列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // 釋放記憶體 + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/permutations_ii.c b/zh-hant/codes/c/chapter_backtracking/permutations_ii.c new file mode 100644 index 0000000000..da8dff71c3 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/permutations_ii.c @@ -0,0 +1,81 @@ +/** + * File: permutations_ii.c + * Created Time: 2023-10-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 假設最多有 1000 個排列,元素最大為 1000 +#define MAX_SIZE 1000 + +/* 回溯演算法:全排列 II */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 當狀態長度等於元素數量時,記錄解 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // 走訪所有選擇 + bool duplicated[MAX_SIZE] = {false}; + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated[choice]) { + // 嘗試:做出選擇,更新狀態 + duplicated[choice] = true; // 記錄選擇過的元素值 + selected[i] = true; + state[stateSize] = choice; + // 進行下一輪選擇 + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + } + } +} + +/* 全排列 II */ +int **permutationsII(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 1, 2}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsII(nums, numsSize, &returnSize); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("\n所有排列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // 釋放記憶體 + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_i_compact.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_i_compact.c new file mode 100644 index 0000000000..548e2d8b53 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_i_compact.c @@ -0,0 +1,49 @@ +/** + * File: preorder_traversal_i_compact.c + * Created Time: 2023-05-10 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設結果長度不超過 100 +#define MAX_SIZE 100 + +TreeNode *res[MAX_SIZE]; +int resSize = 0; + +/* 前序走訪:例題一 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + if (root->val == 7) { + // 記錄解 + res[resSize++] = root; + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二元樹\n"); + printTree(root); + + // 前序走訪 + preOrder(root); + + printf("\n輸出所有值為 7 的節點\n"); + int *vals = malloc(resSize * sizeof(int)); + for (int i = 0; i < resSize; i++) { + vals[i] = res[i]->val; + } + printArray(vals, resSize); + + // 釋放記憶體 + freeMemoryTree(root); + free(vals); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c new file mode 100644 index 0000000000..caac776380 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c @@ -0,0 +1,61 @@ +/** + * File: preorder_traversal_ii_compact.c + * Created Time: 2023-05-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設路徑和結果長度不超過 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前序走訪:例題二 */ +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--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二元樹\n"); + printTree(root); + + // 前序走訪 + preOrder(root); + + printf("\n輸出所有根節點到節點 7 的路徑\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 釋放記憶體 + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c new file mode 100644 index 0000000000..d38d05b0d9 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c @@ -0,0 +1,62 @@ +/** + * File: preorder_traversal_iii_compact.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設路徑和結果長度不超過 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前序走訪:例題三 */ +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--; +} + +/* Driver Code */ +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 的路徑,要求路徑中不包含值為 3 的節點\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 釋放記憶體 + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_template.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_template.c new file mode 100644 index 0000000000..1648d823d5 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_template.c @@ -0,0 +1,93 @@ +/** + * File: preorder_traversal_iii_template.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設路徑和結果長度不超過 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 判斷當前狀態是否為解 */ +bool isSolution(void) { + return pathSize > 0 && path[pathSize - 1]->val == 7; +} + +/* 記錄解 */ +void recordSolution(void) { + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +bool isValid(TreeNode *choice) { + return choice != NULL && choice->val != 3; +} + +/* 更新狀態 */ +void makeChoice(TreeNode *choice) { + path[pathSize++] = choice; +} + +/* 恢復狀態 */ +void undoChoice(void) { + pathSize--; +} + +/* 回溯演算法:例題三 */ +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(); + } + } +} + +/* Driver Code */ +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輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 釋放記憶體 + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/subset_sum_i.c b/zh-hant/codes/c/chapter_backtracking/subset_sum_i.c new file mode 100644 index 0000000000..661b608861 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/subset_sum_i.c @@ -0,0 +1,78 @@ +/** + * File: subset_sum_i.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 狀態(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果串列(子集串列) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯演算法:子集和 I */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + for (int i = 0; i < stateSize; ++i) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choicesSize; i++) { + // 剪枝一:若子集和超過 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); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumI(nums, numsSize, target); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等於 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/subset_sum_i_naive.c b/zh-hant/codes/c/chapter_backtracking/subset_sum_i_naive.c new file mode 100644 index 0000000000..3ca367fa6d --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/subset_sum_i_naive.c @@ -0,0 +1,69 @@ +/** + * File: subset_sum_i_naive.c + * Created Time: 2023-07-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 狀態(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果串列(子集串列) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯演算法:子集和 I */ +void backtrack(int target, int total, int *choices, int choicesSize) { + // 子集和等於 target 時,記錄解 + if (total == target) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 走訪所有選擇 + for (int i = 0; i < choicesSize; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state[stateSize++] = choices[i]; + // 進行下一輪選擇 + backtrack(target, total + choices[i], choices, choicesSize); + // 回退:撤銷選擇,恢復到之前的狀態 + stateSize--; + } +} + +/* 求解子集和 I(包含重複子集) */ +void subsetSumINaive(int *nums, int numsSize, int target) { + resSize = 0; // 初始化解的數量為0 + backtrack(target, 0, nums, numsSize); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumINaive(nums, numsSize, target); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等於 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; i++) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/subset_sum_ii.c b/zh-hant/codes/c/chapter_backtracking/subset_sum_ii.c new file mode 100644 index 0000000000..8064375c7a --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/subset_sum_ii.c @@ -0,0 +1,83 @@ +/** + * File: subset_sum_ii.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 狀態(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果串列(子集串列) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯演算法:子集和 II */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choicesSize; i++) { + // 剪枝一:若子集和超過 target ,則直接跳過 + if (target - choices[i] < 0) { + continue; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + 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); +} + +/* Driver Code */ +int main() { + int nums[] = {4, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumII(nums, numsSize, target); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等於 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/CMakeLists.txt b/zh-hant/codes/c/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 0000000000..dcfa063c3d --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +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) diff --git a/zh-hant/codes/c/chapter_computational_complexity/iteration.c b/zh-hant/codes/c/chapter_computational_complexity/iteration.c new file mode 100644 index 0000000000..1caf5068a2 --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/iteration.c @@ -0,0 +1,81 @@ +/** + * File: iteration.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) + */ + +#include "../utils/common.h" + +/* for 迴圈 */ +int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +char *nestedForLoop(int n) { + // n * n 為對應點數量,"(i, j), " 對應字串長最大為 6+10*2,加上最後一個空字元 \0 的額外空間 + int size = n * n * 26 + 1; + char *res = malloc(size * sizeof(char)); + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + char tmp[26]; + snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); + strncat(res, tmp, size - strlen(res) - 1); + } + } + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + printf("\nfor 迴圈的求和結果 res = %d\n", res); + + res = whileLoop(n); + printf("\nwhile 迴圈的求和結果 res = %d\n", res); + + res = whileLoopII(n); + printf("\nwhile 迴圈(兩次更新)求和結果 res = %d\n", res); + + char *resStr = nestedForLoop(n); + printf("\n雙層 for 迴圈的走訪結果 %s\r\n", resStr); + free(resStr); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/recursion.c b/zh-hant/codes/c/chapter_computational_complexity/recursion.c new file mode 100644 index 0000000000..6f845c679a --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/recursion.c @@ -0,0 +1,77 @@ +/** + * File: recursion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 遞迴 */ +int recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +int forLoopRecur(int n) { + int stack[1000]; // 藉助一個大陣列來模擬堆疊 + int top = -1; // 堆疊頂索引 + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack[1 + top++] = i; + } + // 迴:返回結果 + while (top >= 0) { + // 透過“出堆疊操作”模擬“迴” + res += stack[top--]; + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +int tailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +int fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + printf("\n遞迴函式的求和結果 res = %d\n", res); + + res = forLoopRecur(n); + printf("\n使用迭代模擬遞迴求和結果 res = %d\n", res); + + res = tailRecur(n, 0); + printf("\n尾遞迴函式的求和結果 res = %d\n", res); + + res = fib(n); + printf("\n費波那契數列的第 %d 項為 %d\n", n, res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/space_complexity.c b/zh-hant/codes/c/chapter_computational_complexity/space_complexity.c new file mode 100644 index 0000000000..b44c839a06 --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/space_complexity.c @@ -0,0 +1,141 @@ +/** + * File: space_complexity.c + * Created Time: 2023-04-15 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 函式 */ +int func() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + const int a = 0; + int b = 0; + int nums[1000]; + ListNode *node = newListNode(0); + free(node); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + func(); + } +} + +/* 雜湊表 */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // 基於 uthash.h 實現 +} HashTable; + +/* 線性階 */ +void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + int *nums = malloc(sizeof(int) * n); + free(nums); + + // 長度為 n 的串列佔用 O(n) 空間 + ListNode **nodes = malloc(sizeof(ListNode *) * n); + for (int i = 0; i < n; i++) { + nodes[i] = newListNode(i); + } + // 記憶體釋放 + for (int i = 0; i < n; i++) { + free(nodes[i]); + } + free(nodes); + + // 長度為 n 的雜湊表佔用 O(n) 空間 + HashTable *h = NULL; + for (int i = 0; i < n; i++) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = i; + tmp->val = i; + HASH_ADD_INT(h, key, tmp); + } + + // 記憶體釋放 + HashTable *curr, *tmp; + HASH_ITER(hh, h, curr, tmp) { + HASH_DEL(h, curr); + free(curr); + } +} + +/* 線性階(遞迴實現) */ +void linearRecur(int n) { + printf("遞迴 n = %d\r\n", n); + if (n == 1) + return; + linearRecur(n - 1); +} + +/* 平方階 */ +void quadratic(int n) { + // 二維串列佔用 O(n^2) 空間 + int **numMatrix = malloc(sizeof(int *) * n); + for (int i = 0; i < n; i++) { + int *tmp = malloc(sizeof(int) * n); + for (int j = 0; j < n; j++) { + tmp[j] = 0; + } + numMatrix[i] = tmp; + } + + // 記憶體釋放 + for (int i = 0; i < n; i++) { + free(numMatrix[i]); + } + free(numMatrix); +} + +/* 平方階(遞迴實現) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + int *nums = malloc(sizeof(int) * n); + printf("遞迴 n = %d 中的 nums 長度 = %d\r\n", n, n); + int res = quadraticRecur(n - 1); + free(nums); + return res; +} + +/* 指數階(建立滿二元樹) */ +TreeNode *buildTree(int n) { + if (n == 0) + return NULL; + TreeNode *root = newTreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode *root = buildTree(n); + printTree(root); + + // 釋放記憶體 + freeMemoryTree(root); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/time_complexity.c b/zh-hant/codes/c/chapter_computational_complexity/time_complexity.c new file mode 100644 index 0000000000..280eee9385 --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/time_complexity.c @@ -0,0 +1,179 @@ +/** + * 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; + int i = 0; + 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; +} + +/* Driver Code */ +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 ) + int *nums = (int *)malloc(n * sizeof(int)); + count = arrayTraversal(nums, n); + printf("線性階(走訪陣列)的操作數量 = %d\n", count); + + count = quadratic(n); + printf("平方階的操作數量 = %d\n", count); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums, n); + printf("平方階(泡沫排序)的操作數量 = %d\n", count); + + count = exponential(n); + printf("指數階(迴圈實現)的操作數量 = %d\n", count); + count = expRecur(n); + printf("指數階(遞迴實現)的操作數量 = %d\n", count); + + count = logarithmic(n); + printf("對數階(迴圈實現)的操作數量 = %d\n", count); + count = logRecur(n); + printf("對數階(遞迴實現)的操作數量 = %d\n", count); + + count = linearLogRecur(n); + printf("線性對數階(遞迴實現)的操作數量 = %d\n", count); + + count = factorialRecur(n); + printf("階乘階(遞迴實現)的操作數量 = %d\n", count); + + // 釋放堆積區記憶體 + if (nums != NULL) { + free(nums); + nums = NULL; + } + getchar(); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/zh-hant/codes/c/chapter_computational_complexity/worst_best_time_complexity.c new file mode 100644 index 0000000000..10435221aa --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/worst_best_time_complexity.c @@ -0,0 +1,57 @@ +/** + * File: worst_best_time_complexity.c + * Created Time: 2023-01-03 + * Author: codingonion (coderonion@gmail.com) + */ + +#include "../utils/common.h" + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +int *randomNumbers(int n) { + // 分配堆積區記憶體(建立一維可變長陣列:陣列中元素數量為 n ,元素型別為 int ) + 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; +} + +/* Driver Code */ +int main(int argc, char *argv[]) { + // 初始化隨機數種子 + srand((unsigned int)time(NULL)); + for (int i = 0; i < 10; i++) { + int n = 100; + int *nums = randomNumbers(n); + int index = findOne(nums, n); + printf("\n陣列 [ 1, 2, ..., n ] 被打亂後 = "); + printArray(nums, n); + printf("數字 1 的索引為 %d\n", index); + // 釋放堆積區記憶體 + if (nums != NULL) { + free(nums); + nums = NULL; + } + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/CMakeLists.txt b/zh-hant/codes/c/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 0000000000..e03b1c5881 --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.c) +add_executable(build_tree build_tree.c) +add_executable(hanota hanota.c) diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/binary_search_recur.c b/zh-hant/codes/c/chapter_divide_and_conquer/binary_search_recur.c new file mode 100644 index 0000000000..86582e12a4 --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/binary_search_recur.c @@ -0,0 +1,47 @@ +/** + * File: binary_search_recur.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋:問題 f(i, j) */ +int dfs(int nums[], int target, int i, int j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +int binarySearch(int nums[], int target, int numsSize) { + int n = numsSize; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target, numsSize); + printf("目標元素 6 的索引 = %d\n", index); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/build_tree.c b/zh-hant/codes/c/chapter_divide_and_conquer/build_tree.c new file mode 100644 index 0000000000..de71c8a94f --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/build_tree.c @@ -0,0 +1,61 @@ +/** + * File : build_tree.c + * Created Time: 2023-10-16 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// 假設所有元素都小於 1000 +#define MAX_SIZE 1000 + +/* 構建二元樹:分治 */ +TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { + // 子樹區間為空時終止 + if (r - l < 0) + return NULL; + // 初始化根節點 + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = preorder[i]; + root->left = NULL; + root->right = NULL; + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]; + // 子問題:構建左子樹 + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); + // 子問題:構建右子樹 + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); + for (int i = 0; i < inorderSize; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); + free(inorderMap); + return root; +} + +/* Driver Code */ +int main() { + int preorder[] = {3, 9, 2, 1, 7}; + int inorder[] = {9, 3, 1, 2, 7}; + int preorderSize = sizeof(preorder) / sizeof(preorder[0]); + int inorderSize = sizeof(inorder) / sizeof(inorder[0]); + printf("前序走訪 = "); + printArray(preorder, preorderSize); + printf("中序走訪 = "); + printArray(inorder, inorderSize); + + TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); + printf("構建的二元樹為:\n"); + printTree(root); + + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/hanota.c b/zh-hant/codes/c/chapter_divide_and_conquer/hanota.c new file mode 100644 index 0000000000..99a46dde68 --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/hanota.c @@ -0,0 +1,74 @@ +/** + * File: hanota.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// 假設最多有 1000 個排列 +#define MAX_SIZE 1000 + +/* 移動一個圓盤 */ +void move(int *src, int *srcSize, int *tar, int *tarSize) { + // 從 src 頂部拿出一個圓盤 + int pan = src[*srcSize - 1]; + src[*srcSize - 1] = 0; + (*srcSize)--; + // 將圓盤放入 tar 頂部 + tar[*tarSize] = pan; + (*tarSize)++; +} + +/* 求解河內塔問題 f(i) */ +void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i == 1) { + move(src, srcSize, tar, tarSize); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, srcSize, tar, tarSize); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); +} + +/* 求解河內塔問題 */ +void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(*ASize, A, ASize, B, BSize, C, CSize); +} + +/* Driver Code */ +int main() { + // 串列尾部是柱子頂部 + int a[] = {5, 4, 3, 2, 1}; + int b[MAX_SIZE] = {0}; + int c[MAX_SIZE] = {0}; + + int ASize = sizeof(a) / sizeof(a[0]); + int BSize = 0; + int CSize = 0; + + printf("\n初始狀態下:"); + printf("\nA = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + solveHanota(a, &ASize, b, &BSize, c, &CSize); + + printf("\n圓盤移動完成後:"); + printf("A = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/CMakeLists.txt b/zh-hant/codes/c/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 0000000000..dd769ebd59 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) +add_executable(min_path_sum min_path_sum.c) +add_executable(knapsack knapsack.c) +add_executable(unbounded_knapsack unbounded_knapsack.c) +add_executable(coin_change coin_change.c) +add_executable(coin_change_ii coin_change_ii.c) +add_executable(edit_distance edit_distance.c) diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c new file mode 100644 index 0000000000..28d606d701 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c @@ -0,0 +1,47 @@ +/** + * File: climbing_stairs_backtrack.c + * Created Time: 2023-09-22 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 回溯 */ +void backtrack(int *choices, int state, int n, int *res, int len) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res[0]++; + // 走訪所有選擇 + for (int i = 0; i < len; i++) { + int choice = choices[i]; + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res, len); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +int climbingStairsBacktrack(int n) { + int choices[2] = {1, 2}; // 可選擇向上爬 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; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c new file mode 100644 index 0000000000..151e36ccda --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_constraint_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 帶約束爬樓梯:動態規劃 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(3, sizeof(int)); + } + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + int res = dp[n][1] + dp[n][2]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c new file mode 100644 index 0000000000..eaddc6babe --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 搜尋 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c new file mode 100644 index 0000000000..fe19636de8 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_dfs_mem.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 記憶化搜尋 */ +int dfs(int i, int *mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +int climbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + int *mem = (int *)malloc((n + 1) * sizeof(int)); + for (int i = 0; i <= n; i++) { + mem[i] = -1; + } + int result = dfs(n, mem); + free(mem); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c new file mode 100644 index 0000000000..b4a97a5573 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c @@ -0,0 +1,51 @@ +/** + * File: climbing_stairs_dp.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 爬樓梯:動態規劃 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + int *dp = (int *)malloc((n + 1) * sizeof(int)); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + int result = dp[n]; + free(dp); + return result; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + res = climbingStairsDPComp(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/coin_change.c b/zh-hant/codes/c/chapter_dynamic_programming/coin_change.c new file mode 100644 index 0000000000..2c338ffd47 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/coin_change.c @@ -0,0 +1,92 @@ +/** + * File: coin_change.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 零錢兌換:動態規劃 */ +int coinChangeDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + int res = dp[n][amt] != MAX ? dp[n][amt] : -1; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +int coinChangeDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // 初始化 dp 表 + int *dp = malloc((amt + 1) * sizeof(int)); + for (int j = 1; j <= amt; j++) { + dp[j] = MAX; + } + dp[0] = 0; + + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + int res = dp[amt] != MAX ? dp[amt] : -1; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt, coinsSize); + printf("湊到目標金額所需的最少硬幣數量為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt, coinsSize); + printf("湊到目標金額所需的最少硬幣數量為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/coin_change_ii.c b/zh-hant/codes/c/chapter_dynamic_programming/coin_change_ii.c new file mode 100644 index 0000000000..5fbd240480 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/coin_change_ii.c @@ -0,0 +1,81 @@ +/** + * File: coin_change_ii.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 零錢兌換 II:動態規劃 */ +int coinChangeIIDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + int res = dp[n][amt]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // 初始化 dp 表 + int *dp = calloc(amt + 1, sizeof(int)); + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + int res = dp[amt]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt, coinsSize); + printf("湊出目標金額的硬幣組合數量為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt, coinsSize); + printf("湊出目標金額的硬幣組合數量為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/edit_distance.c b/zh-hant/codes/c/chapter_dynamic_programming/edit_distance.c new file mode 100644 index 0000000000..16b44e7e0f --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/edit_distance.c @@ -0,0 +1,159 @@ +/** + * File: edit_distance.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 編輯距離:暴力搜尋 */ +int editDistanceDFS(char *s, char *t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) + return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) + return i; + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return myMin(myMin(insert, del), replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) + return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) + return i; + // 若已有記錄,則直接返回之 + if (mem[i][j] != -1) + return mem[i][j]; + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = myMin(myMin(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +int editDistanceDP(char *s, char *t, int n, int m) { + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(m + 1, sizeof(int)); + } + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + int res = dp[n][m]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +int editDistanceDPComp(char *s, char *t, int n, int m) { + int *dp = calloc(m + 1, sizeof(int)); + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + int res = dp[m]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + char *s = "bag"; + char *t = "pack"; + int n = strlen(s), m = strlen(t); + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + + // 記憶化搜尋 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((m + 1) * sizeof(int)); + memset(mem[i], -1, (m + 1) * sizeof(int)); + } + res = editDistanceDFSMem(s, t, m + 1, mem, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 動態規劃 + res = editDistanceDP(s, t, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/knapsack.c b/zh-hant/codes/c/chapter_dynamic_programming/knapsack.c new file mode 100644 index 0000000000..c27631782b --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/knapsack.c @@ -0,0 +1,137 @@ +/** + * File: knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最大值 */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 0-1 背包:暴力搜尋 */ +int knapsackDFS(int wgt[], int val[], int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return myMax(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = myMax(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int *dp = calloc(cap + 1, sizeof(int)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int cap = 50; + int n = sizeof(wgt) / sizeof(wgt[0]); + int wgtSize = n; + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + // 記憶化搜尋 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((cap + 1) * sizeof(int)); + memset(mem[i], -1, (cap + 1) * sizeof(int)); + } + res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); + printf("不超過背包容量的最大物品價值為 %d\n", res); + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 動態規劃 + res = knapsackDP(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c b/zh-hant/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c new file mode 100644 index 0000000000..769f63e4c8 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c @@ -0,0 +1,62 @@ +/** + * File: min_cost_climbing_stairs_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 爬樓梯最小代價:動態規劃 */ +int minCostClimbingStairsDP(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + int *dp = calloc(n + 1, sizeof(int)); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; + } + int res = dp[n]; + // 釋放記憶體 + free(dp); + return res; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +int minCostClimbingStairsDPComp(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = myMin(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + int costSize = sizeof(cost) / sizeof(cost[0]); + printf("輸入樓梯的代價串列為:"); + printArray(cost, costSize); + + int res = minCostClimbingStairsDP(cost, costSize); + printf("爬完樓梯的最低代價為 %d\n", res); + + res = minCostClimbingStairsDPComp(cost, costSize); + printf("爬完樓梯的最低代價為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/min_path_sum.c b/zh-hant/codes/c/chapter_dynamic_programming/min_path_sum.c new file mode 100644 index 0000000000..b90a5b073e --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/min_path_sum.c @@ -0,0 +1,134 @@ +/** + * File: min_path_sum.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +// 假設矩陣最大行列數為 100 +#define MAX_SIZE 100 + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 最小路徑和:暴力搜尋 */ +int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小路徑和:記憶化搜尋 */ +int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // 初始化 dp 表 + int **dp = malloc(n * sizeof(int *)); + for (int i = 0; i < n; i++) { + dp[i] = calloc(m, sizeof(int)); + } + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + int res = dp[n - 1][m - 1]; + // 釋放記憶體 + for (int i = 0; i < n; i++) { + free(dp[i]); + } + return res; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // 初始化 dp 表 + int *dp = calloc(m, sizeof(int)); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; + } + } + int res = dp[m - 1]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = 4, m = 4; // 矩陣容量為 MAX_SIZE * MAX_SIZE ,有效行列數為 n * m + + // 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + // 記憶化搜尋 + int mem[MAX_SIZE][MAX_SIZE]; + memset(mem, -1, sizeof(mem)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + // 動態規劃 + res = minPathSumDP(grid, n, m); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid, n, m); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/unbounded_knapsack.c b/zh-hant/codes/c/chapter_dynamic_programming/unbounded_knapsack.c new file mode 100644 index 0000000000..3f0e7b91ca --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/unbounded_knapsack.c @@ -0,0 +1,81 @@ +/** + * File: unbounded_knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最大值 */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 完全背包:動態規劃 */ +int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int *dp = calloc(cap + 1, sizeof(int)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int wgt[] = {1, 2, 3}; + int val[] = {5, 11, 15}; + int wgtSize = sizeof(wgt) / sizeof(wgt[0]); + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/CMakeLists.txt b/zh-hant/codes/c/chapter_graph/CMakeLists.txt new file mode 100644 index 0000000000..28f8470f4b --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) +add_executable(graph_bfs graph_bfs.c) +add_executable(graph_dfs graph_dfs.c) diff --git a/zh-hant/codes/c/chapter_graph/graph_adjacency_list.c b/zh-hant/codes/c/chapter_graph/graph_adjacency_list.c new file mode 100644 index 0000000000..35236eaa16 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_adjacency_list.c @@ -0,0 +1,171 @@ +/** + * File: graph_adjacency_list.c + * Created Time: 2023-07-07 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// 假設節點最大數量為 100 +#define MAX_SIZE 100 + +/* 節點結構體 */ +typedef struct AdjListNode { + Vertex *vertex; // 頂點 + struct AdjListNode *next; // 後繼節點 +} AdjListNode; + +/* 基於鄰接表實現的無向圖類別 */ +typedef struct { + AdjListNode *heads[MAX_SIZE]; // 節點陣列 + int size; // 節點數量 +} GraphAdjList; + +/* 建構子 */ +GraphAdjList *newGraphAdjList() { + GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); + if (!graph) { + return NULL; + } + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + graph->heads[i] = NULL; + } + return graph; +} + +/* 析構函式 */ +void delGraphAdjList(GraphAdjList *graph) { + for (int i = 0; i < graph->size; i++) { + AdjListNode *cur = graph->heads[i]; + while (cur != NULL) { + AdjListNode *next = cur->next; + if (cur != graph->heads[i]) { + free(cur); + } + cur = next; + } + free(graph->heads[i]->vertex); + free(graph->heads[i]); + } + free(graph); +} + +/* 查詢頂點對應的節點 */ +AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { + for (int i = 0; i < graph->size; i++) { + if (graph->heads[i]->vertex == vet) { + return graph->heads[i]; + } + } + return NULL; +} + +/* 新增邊輔助函式 */ +void addEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); + node->vertex = vet; + // 頭插法 + node->next = head->next; + head->next = node; +} + +/* 新增邊 */ +void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL && head1 != head2); + // 新增邊 vet1 - vet2 + addEdgeHelper(head1, vet2); + addEdgeHelper(head2, vet1); +} + +/* 刪除邊輔助函式 */ +void removeEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *pre = head; + AdjListNode *cur = head->next; + // 在鏈結串列中搜索 vet 對應節點 + while (cur != NULL && cur->vertex != vet) { + pre = cur; + cur = cur->next; + } + if (cur == NULL) + return; + // 將 vet 對應節點從鏈結串列中刪除 + pre->next = cur->next; + // 釋放記憶體 + free(cur); +} + +/* 刪除邊 */ +void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL); + // 刪除邊 vet1 - vet2 + removeEdgeHelper(head1, head2->vertex); + removeEdgeHelper(head2, head1->vertex); +} + +/* 新增頂點 */ +void addVertex(GraphAdjList *graph, Vertex *vet) { + assert(graph != NULL && graph->size < MAX_SIZE); + AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); + head->vertex = vet; + head->next = NULL; + // 在鄰接表中新增一個新鏈結串列 + graph->heads[graph->size++] = head; +} + +/* 刪除頂點 */ +void removeVertex(GraphAdjList *graph, Vertex *vet) { + AdjListNode *node = findNode(graph, vet); + assert(node != NULL); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + AdjListNode *cur = node, *pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + free(pre); + } + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (int i = 0; i < graph->size; i++) { + cur = graph->heads[i]; + pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + if (cur && cur->vertex == vet) { + pre->next = cur->next; + free(cur); + break; + } + } + } + // 將該頂點之後的頂點向前移動,以填補空缺 + int i; + for (i = 0; i < graph->size; i++) { + if (graph->heads[i] == node) + break; + } + for (int j = i; j < graph->size - 1; j++) { + graph->heads[j] = graph->heads[j + 1]; + } + graph->size--; + free(vet); +} + +/* 列印鄰接表 */ +void printGraph(const GraphAdjList *graph) { + printf("鄰接表 =\n"); + for (int i = 0; i < graph->size; ++i) { + AdjListNode *node = graph->heads[i]; + printf("%d: [", node->vertex->val); + node = node->next; + while (node) { + printf("%d, ", node->vertex->val); + node = node->next; + } + printf("]\n"); + } +} diff --git a/zh-hant/codes/c/chapter_graph/graph_adjacency_list_test.c b/zh-hant/codes/c/chapter_graph/graph_adjacency_list_test.c new file mode 100644 index 0000000000..b3f539e20c --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_adjacency_list_test.c @@ -0,0 +1,55 @@ +/** + * File: graph_adjacency_list_test.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +/* Driver Code */ +int main() { + int vals[] = {1, 3, 2, 5, 4}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 新增所有頂點和邊 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraph(graph); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + addEdge(graph, v[0], v[2]); + printf("\n新增邊 1-2 後,圖為\n"); + printGraph(graph); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + removeEdge(graph, v[0], v[1]); + printf("\n刪除邊 1-3 後,圖為\n"); + printGraph(graph); + + /* 新增頂點 */ + Vertex *v5 = newVertex(6); + addVertex(graph, v5); + printf("\n新增頂點 6 後,圖為\n"); + printGraph(graph); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + removeVertex(graph, v[1]); + printf("\n刪除頂點 3 後,圖為:\n"); + printGraph(graph); + + // 釋放記憶體 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/graph_adjacency_matrix.c b/zh-hant/codes/c/chapter_graph/graph_adjacency_matrix.c new file mode 100644 index 0000000000..5848dee836 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_adjacency_matrix.c @@ -0,0 +1,150 @@ +/** + * File: graph_adjacency_matrix.c + * Created Time: 2023-07-06 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// 假設頂點數量最大為 100 +#define MAX_SIZE 100 + +/* 基於鄰接矩陣實現的無向圖結構體 */ +typedef struct { + int vertices[MAX_SIZE]; + int adjMat[MAX_SIZE][MAX_SIZE]; + int size; +} GraphAdjMat; + +/* 建構子 */ +GraphAdjMat *newGraphAdjMat() { + GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + for (int j = 0; j < MAX_SIZE; j++) { + graph->adjMat[i][j] = 0; + } + } + return graph; +} + +/* 析構函式 */ +void delGraphAdjMat(GraphAdjMat *graph) { + free(graph); +} + +/* 新增頂點 */ +void addVertex(GraphAdjMat *graph, int val) { + if (graph->size == MAX_SIZE) { + fprintf(stderr, "圖的頂點數量已達最大值\n"); + return; + } + // 新增第 n 個頂點,並將第 n 行和列置零 + 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); + } +} + +/* Driver Code */ +int main() { + // 初始化無向圖 + GraphAdjMat *graph = newGraphAdjMat(); + int vertices[] = {1, 3, 2, 5, 4}; + for (int i = 0; i < 5; i++) { + addVertex(graph, vertices[i]); + } + int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + for (int i = 0; i < 6; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraphAdjMat(graph); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + addEdge(graph, 0, 2); + printf("\n新增邊 1-2 後,圖為\n"); + printGraphAdjMat(graph); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + removeEdge(graph, 0, 1); + printf("\n刪除邊 1-3 後,圖為\n"); + printGraphAdjMat(graph); + + /* 新增頂點 */ + addVertex(graph, 6); + printf("\n新增頂點 6 後,圖為\n"); + printGraphAdjMat(graph); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + removeVertex(graph, 1); + printf("\n刪除頂點 3 後,圖為\n"); + printGraphAdjMat(graph); + + // 釋放記憶體 + delGraphAdjMat(graph); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/graph_bfs.c b/zh-hant/codes/c/chapter_graph/graph_bfs.c new file mode 100644 index 0000000000..2bc420c812 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_bfs.c @@ -0,0 +1,116 @@ +/** + * File: graph_bfs.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// 假設節點最大數量為 100 +#define MAX_SIZE 100 + +/* 節點佇列結構體 */ +typedef struct { + Vertex *vertices[MAX_SIZE]; + int front, rear, size; +} Queue; + +/* 建構子 */ +Queue *newQueue() { + Queue *q = (Queue *)malloc(sizeof(Queue)); + q->front = q->rear = q->size = 0; + return q; +} + +/* 判斷佇列是否為空 */ +int isEmpty(Queue *q) { + return q->size == 0; +} + +/* 入列操作 */ +void enqueue(Queue *q, Vertex *vet) { + q->vertices[q->rear] = vet; + q->rear = (q->rear + 1) % MAX_SIZE; + q->size++; +} + +/* 出列操作 */ +Vertex *dequeue(Queue *q) { + Vertex *vet = q->vertices[q->front]; + q->front = (q->front + 1) % MAX_SIZE; + q->size--; + return vet; +} + +/* 檢查頂點是否已被訪問 */ +int isVisited(Vertex **visited, int size, Vertex *vet) { + // 走訪查詢節點,使用 O(n) 時間 + for (int i = 0; i < size; i++) { + if (visited[i] == vet) + return 1; + } + return 0; +} + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { + // 佇列用於實現 BFS + Queue *queue = newQueue(); + enqueue(queue, startVet); + visited[(*visitedSize)++] = startVet; + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!isEmpty(queue)) { + Vertex *vet = dequeue(queue); // 佇列首頂點出隊 + res[(*resSize)++] = vet; // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳過已被訪問的頂點 + if (!isVisited(visited, *visitedSize, node->vertex)) { + enqueue(queue, node->vertex); // 只入列未訪問的頂點 + visited[(*visitedSize)++] = node->vertex; // 標記該頂點已被訪問 + } + node = node->next; + } + } + // 釋放記憶體 + free(queue); +} + +/* Driver Code */ +int main() { + // 初始化無向圖 + int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, + {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 新增所有頂點和邊 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraph(graph); + + // 廣度優先走訪 + // 頂點走訪序列 + Vertex *res[MAX_SIZE]; + int resSize = 0; + // 用於記錄已被訪問過的頂點 + Vertex *visited[MAX_SIZE]; + int visitedSize = 0; + graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); + printf("\n廣度優先走訪(BFS)頂點序列為\n"); + printArray(vetsToVals(res, resSize), resSize); + + // 釋放記憶體 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/graph_dfs.c b/zh-hant/codes/c/chapter_graph/graph_dfs.c new file mode 100644 index 0000000000..ca0658a08b --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_dfs.c @@ -0,0 +1,75 @@ +/** + * File: graph_dfs.c + * Created Time: 2023-07-13 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// 假設節點最大數量為 100 +#define MAX_SIZE 100 + +/* 檢查頂點是否已被訪問 */ +int isVisited(Vertex **res, int size, Vertex *vet) { + // 走訪查詢節點,使用 O(n) 時間 + for (int i = 0; i < size; i++) { + if (res[i] == vet) { + return 1; + } + } + return 0; +} + +/* 深度優先走訪輔助函式 */ +void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { + // 記錄訪問頂點 + res[(*resSize)++] = vet; + // 走訪該頂點的所有鄰接頂點 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳過已被訪問的頂點 + if (!isVisited(res, *resSize, node->vertex)) { + // 遞迴訪問鄰接頂點 + dfs(graph, res, resSize, node->vertex); + } + node = node->next; + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { + dfs(graph, res, resSize, startVet); +} + +/* Driver Code */ +int main() { + // 初始化無向圖 + int vals[] = {0, 1, 2, 3, 4, 5, 6}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 新增所有頂點和邊 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraph(graph); + + // 深度優先走訪 + Vertex *res[MAX_SIZE]; + int resSize = 0; + graphDFS(graph, v[0], res, &resSize); + printf("\n深度優先走訪(DFS)頂點序列為\n"); + printArray(vetsToVals(res, resSize), resSize); + + // 釋放記憶體 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/CMakeLists.txt b/zh-hant/codes/c/chapter_greedy/CMakeLists.txt new file mode 100644 index 0000000000..b8e6ca4250 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(coin_change_greedy coin_change_greedy.c) +add_executable(fractional_knapsack fractional_knapsack.c) +add_executable(max_capacity max_capacity.c) +add_executable(max_product_cutting max_product_cutting.c) + +if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_link_libraries(max_product_cutting m) +endif() diff --git a/zh-hant/codes/c/chapter_greedy/coin_change_greedy.c b/zh-hant/codes/c/chapter_greedy/coin_change_greedy.c new file mode 100644 index 0000000000..8df7078421 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/coin_change_greedy.c @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.c + * Created Time: 2023-09-07 + * Author: lwbaptx (lwbaptx@gmail.com) + */ + +#include "../utils/common.h" + +/* 零錢兌換:貪婪 */ +int coinChangeGreedy(int *coins, int size, int amt) { + // 假設 coins 串列有序 + int i = size - 1; + int count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // 貪婪:能夠保證找到全域性最優解 + int coins1[6] = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins1, 6, amt); + printf("\ncoins = "); + printArray(coins1, 6); + printf("amt = %d\n", amt); + printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); + + // 貪婪:無法保證找到全域性最優解 + int coins2[3] = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins2, 3, amt); + printf("\ncoins = "); + printArray(coins2, 3); + printf("amt = %d\n", amt); + printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); + printf("實際上需要的最少數量為 3 ,即 20 + 20 + 20\n"); + + // 貪婪:無法保證找到全域性最優解 + int coins3[3] = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins3, 3, amt); + printf("\ncoins = "); + printArray(coins3, 3); + printf("amt = %d\n", amt); + printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); + printf("實際上需要的最少數量為 2 ,即 49 + 49\n"); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/fractional_knapsack.c b/zh-hant/codes/c/chapter_greedy/fractional_knapsack.c new file mode 100644 index 0000000000..6475a4efe5 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/fractional_knapsack.c @@ -0,0 +1,60 @@ +/** + * File: fractional_knapsack.c + * Created Time: 2023-09-14 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 物品 */ +typedef struct { + int w; // 物品重量 + int v; // 物品價值 +} Item; + +/* 按照價值密度排序 */ +int sortByValueDensity(const void *a, const void *b) { + Item *t1 = (Item *)a; + Item *t2 = (Item *)b; + return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; +} + +/* 分數背包:貪婪 */ +float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + Item *items = malloc(sizeof(Item) * itemCount); + for (int i = 0; i < itemCount; i++) { + items[i] = (Item){.w = wgt[i], .v = val[i]}; + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); + // 迴圈貪婪選擇 + float res = 0.0; + for (int i = 0; i < itemCount; i++) { + if (items[i].w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += items[i].v; + cap -= items[i].w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (float)cap / items[i].w * items[i].v; + cap = 0; + break; + } + } + free(items); + return res; +} + +/* Driver Code */ +int main(void) { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int capacity = 50; + + // 貪婪演算法 + float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); + printf("不超過背包容量的最大物品價值為 %0.2f\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/max_capacity.c b/zh-hant/codes/c/chapter_greedy/max_capacity.c new file mode 100644 index 0000000000..04a68f2450 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/max_capacity.c @@ -0,0 +1,49 @@ +/** + * File: max_capacity.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} +/* 求最大值 */ +int myMax(int a, int b) { + return a < b ? a : b; +} + +/* 最大容量:貪婪 */ +int maxCapacity(int ht[], int htLength) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0; + int j = htLength - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int capacity = myMin(ht[i], ht[j]) * (j - i); + res = myMax(res, capacity); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main(void) { + int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 貪婪演算法 + int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); + printf("最大容量為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/max_product_cutting.c b/zh-hant/codes/c/chapter_greedy/max_product_cutting.c new file mode 100644 index 0000000000..84cc8dd102 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/max_product_cutting.c @@ -0,0 +1,38 @@ +/** + * File: max_product_cutting.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 最大切分乘積:貪婪 */ +int maxProductCutting(int n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return pow(3, a); +} + +/* Driver Code */ +int main(void) { + int n = 58; + // 貪婪演算法 + int res = maxProductCutting(n); + printf("最大切分乘積為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/CMakeLists.txt b/zh-hant/codes/c/chapter_hashing/CMakeLists.txt new file mode 100644 index 0000000000..9ac951ae43 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array_hash_map array_hash_map.c) +add_executable(hash_map_chaining hash_map_chaining.c) +add_executable(hash_map_open_addressing hash_map_open_addressing.c) +add_executable(simple_hash simple_hash.c) diff --git a/zh-hant/codes/c/chapter_hashing/array_hash_map.c b/zh-hant/codes/c/chapter_hashing/array_hash_map.c new file mode 100644 index 0000000000..58fb8a2002 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/array_hash_map.c @@ -0,0 +1,215 @@ +/** + * File: array_hash_map.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 雜湊表預設大小 */ +#define MAX_SIZE 100 + +/* 鍵值對 int->string */ +typedef struct { + int key; + char *val; +} Pair; + +/* 鍵值對的集合 */ +typedef struct { + void *set; + int len; +} MapSet; + +/* 基於陣列實現的雜湊表 */ +typedef struct { + Pair *buckets[MAX_SIZE]; +} ArrayHashMap; + +/* 建構子 */ +ArrayHashMap *newArrayHashMap() { + ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); + for (int i=0; i < MAX_SIZE; i++) { + hmap->buckets[i] = NULL; + } + return hmap; +} + +/* 析構函式 */ +void delArrayHashMap(ArrayHashMap *hmap) { + for (int i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + free(hmap->buckets[i]->val); + free(hmap->buckets[i]); + } + } + free(hmap); +} + +/* 雜湊函式 */ +int hashFunc(int key) { + int index = key % MAX_SIZE; + return index; +} + +/* 查詢操作 */ +const char *get(const ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + const Pair *Pair = hmap->buckets[index]; + if (Pair == NULL) + return NULL; + return Pair->val; +} + +/* 新增操作 */ +void put(ArrayHashMap *hmap, const int key, const char *val) { + Pair *Pair = malloc(sizeof(Pair)); + Pair->key = key; + Pair->val = malloc(strlen(val) + 1); + strcpy(Pair->val, val); + + int index = hashFunc(key); + hmap->buckets[index] = Pair; +} + +/* 刪除操作 */ +void removeItem(ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + free(hmap->buckets[index]->val); + free(hmap->buckets[index]); + hmap->buckets[index] = NULL; +} + +/* 獲取所有鍵值對 */ +void pairSet(ArrayHashMap *hmap, MapSet *set) { + Pair *entries; + int i = 0, index = 0; + int total = 0; + /* 統計有效鍵值對數量 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + entries = malloc(sizeof(Pair) * total); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + entries[index].key = hmap->buckets[i]->key; + entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); + strcpy(entries[index].val, hmap->buckets[i]->val); + index++; + } + } + set->set = entries; + set->len = total; +} + +/* 獲取所有鍵 */ +void keySet(ArrayHashMap *hmap, MapSet *set) { + int *keys; + int i = 0, index = 0; + int total = 0; + /* 統計有效鍵值對數量 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + keys = malloc(total * sizeof(int)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + keys[index] = hmap->buckets[i]->key; + index++; + } + } + set->set = keys; + set->len = total; +} + +/* 獲取所有值 */ +void valueSet(ArrayHashMap *hmap, MapSet *set) { + char **vals; + int i = 0, index = 0; + int total = 0; + /* 統計有效鍵值對數量 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + vals = malloc(total * sizeof(char *)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + vals[index] = hmap->buckets[i]->val; + index++; + } + } + set->set = vals; + set->len = total; +} + +/* 列印雜湊表 */ +void print(ArrayHashMap *hmap) { + int i; + MapSet set; + pairSet(hmap, &set); + Pair *entries = (Pair *)set.set; + for (i = 0; i < set.len; i++) { + printf("%d -> %s\n", entries[i].key, entries[i].val); + } + free(set.set); +} + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + ArrayHashMap *hmap = newArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + put(hmap, 12836, "小哈"); + put(hmap, 15937, "小囉"); + put(hmap, 16750, "小算"); + put(hmap, 13276, "小法"); + put(hmap, 10583, "小鴨"); + printf("\n新增完成後,雜湊表為\nKey -> Value\n"); + print(hmap); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + const char *name = get(hmap, 15937); + printf("\n輸入學號 15937 ,查詢到姓名 %s\n", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + removeItem(hmap, 10583); + printf("\n刪除 10583 後,雜湊表為\nKey -> Value\n"); + print(hmap); + + /* 走訪雜湊表 */ + int i; + + printf("\n走訪鍵值對 Key->Value\n"); + print(hmap); + + MapSet set; + + keySet(hmap, &set); + int *keys = (int *)set.set; + printf("\n單獨走訪鍵 Key\n"); + for (i = 0; i < set.len; i++) { + printf("%d\n", keys[i]); + } + free(set.set); + + valueSet(hmap, &set); + char **vals = (char **)set.set; + printf("\n單獨走訪鍵 Value\n"); + for (i = 0; i < set.len; i++) { + printf("%s\n", vals[i]); + } + free(set.set); + + delArrayHashMap(hmap); + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/hash_map_chaining.c b/zh-hant/codes/c/chapter_hashing/hash_map_chaining.c new file mode 100644 index 0000000000..94afbb86d0 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/hash_map_chaining.c @@ -0,0 +1,213 @@ +/** + * File: hash_map_chaining.c + * Created Time: 2023-10-13 + * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) + */ + +#include +#include +#include + +// 假設 val 最大長度為 100 +#define MAX_SIZE 100 + +/* 鍵值對 */ +typedef struct { + int key; + char val[MAX_SIZE]; +} Pair; + +/* 鏈結串列節點 */ +typedef struct Node { + Pair *pair; + struct Node *next; +} Node; + +/* 鏈式位址雜湊表 */ +typedef struct { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + Node **buckets; // 桶陣列 +} HashMapChaining; + +/* 建構子 */ +HashMapChaining *newHashMapChaining() { + HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + return hashMap; +} + +/* 析構函式 */ +void delHashMapChaining(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + while (cur) { + Node *tmp = cur; + cur = cur->next; + free(tmp->pair); + free(tmp); + } + } + free(hashMap->buckets); + free(hashMap); +} + +/* 雜湊函式 */ +int hashFunc(HashMapChaining *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 負載因子 */ +double loadFactor(HashMapChaining *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* 查詢操作 */ +char *get(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + // 走訪桶,若找到 key ,則返回對應 val + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + return cur->pair->val; + } + cur = cur->next; + } + return ""; // 若未找到 key ,則返回空字串 +} + +/* 新增操作 */ +void put(HashMapChaining *hashMap, int key, const char *val); + +/* 擴容雜湊表 */ +void extend(HashMapChaining *hashMap) { + // 暫存原雜湊表 + int oldCapacity = hashMap->capacity; + Node **oldBuckets = hashMap->buckets; + // 初始化擴容後的新雜湊表 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + hashMap->size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (int i = 0; i < oldCapacity; i++) { + Node *cur = oldBuckets[i]; + while (cur) { + put(hashMap, cur->pair->key, cur->pair->val); + Node *temp = cur; + cur = cur->next; + // 釋放記憶體 + free(temp->pair); + free(temp); + } + } + + free(oldBuckets); +} + +/* 新增操作 */ +void put(HashMapChaining *hashMap, int key, const char *val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + int index = hashFunc(hashMap, key); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + strcpy(cur->pair->val, val); // 若遇到指定 key ,則更新對應 val 並返回 + return; + } + cur = cur->next; + } + // 若無該 key ,則將鍵值對新增至鏈結串列頭部 + Pair *newPair = (Pair *)malloc(sizeof(Pair)); + newPair->key = key; + strcpy(newPair->val, val); + Node *newNode = (Node *)malloc(sizeof(Node)); + newNode->pair = newPair; + newNode->next = hashMap->buckets[index]; + hashMap->buckets[index] = newNode; + hashMap->size++; +} + +/* 刪除操作 */ +void removeItem(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + Node *cur = hashMap->buckets[index]; + Node *pre = NULL; + while (cur) { + if (cur->pair->key == key) { + // 從中刪除鍵值對 + if (pre) { + pre->next = cur->next; + } else { + hashMap->buckets[index] = cur->next; + } + // 釋放記憶體 + free(cur->pair); + free(cur); + hashMap->size--; + return; + } + pre = cur; + cur = cur->next; + } +} + +/* 列印雜湊表 */ +void print(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + printf("["); + while (cur) { + printf("%d -> %s, ", cur->pair->key, cur->pair->val); + cur = cur->next; + } + printf("]\n"); + } +} + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + HashMapChaining *hashMap = newHashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + put(hashMap, 12836, "小哈"); + put(hashMap, 15937, "小囉"); + put(hashMap, 16750, "小算"); + put(hashMap, 13276, "小法"); + put(hashMap, 10583, "小鴨"); + printf("\n新增完成後,雜湊表為\nKey -> Value\n"); + print(hashMap); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + char *name = get(hashMap, 13276); + printf("\n輸入學號 13276 ,查詢到姓名 %s\n", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + removeItem(hashMap, 12836); + printf("\n刪除學號 12836 後,雜湊表為\nKey -> Value\n"); + print(hashMap); + + /* 釋放雜湊表空間 */ + delHashMapChaining(hashMap); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/hash_map_open_addressing.c b/zh-hant/codes/c/chapter_hashing/hash_map_open_addressing.c new file mode 100644 index 0000000000..1df03417e0 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/hash_map_open_addressing.c @@ -0,0 +1,211 @@ +/** + * File: hash_map_open_addressing.c + * Created Time: 2023-10-6 + * Author: lclc6 (w1929522410@163.com) + */ + +#include "../utils/common.h" + +/* 開放定址雜湊表 */ +typedef struct { + int key; + char *val; +} Pair; + +/* 開放定址雜湊表 */ +typedef struct { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + Pair **buckets; // 桶陣列 + Pair *TOMBSTONE; // 刪除標記 +} HashMapOpenAddressing; + +// 函式宣告 +void extend(HashMapOpenAddressing *hashMap); + +/* 建構子 */ +HashMapOpenAddressing *newHashMapOpenAddressing() { + HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); + hashMap->TOMBSTONE->key = -1; + hashMap->TOMBSTONE->val = "-1"; + + return hashMap; +} + +/* 析構函式 */ +void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + free(pair->val); + free(pair); + } + } + free(hashMap->buckets); + free(hashMap->TOMBSTONE); + free(hashMap); +} + +/* 雜湊函式 */ +int hashFunc(HashMapOpenAddressing *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 負載因子 */ +double loadFactor(HashMapOpenAddressing *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* 搜尋 key 對應的桶索引 */ +int findBucket(HashMapOpenAddressing *hashMap, int key) { + int index = hashFunc(hashMap, key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (hashMap->buckets[index] != NULL) { + // 若遇到 key ,返回對應的桶索引 + if (hashMap->buckets[index]->key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + hashMap->buckets[firstTombstone] = hashMap->buckets[index]; + hashMap->buckets[index] = hashMap->TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % hashMap->capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; +} + +/* 查詢操作 */ +char *get(HashMapOpenAddressing *hashMap, int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(hashMap, key); + // 若找到鍵值對,則返回對應 val + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + return hashMap->buckets[index]->val; + } + // 若鍵值對不存在,則返回空字串 + return ""; +} + +/* 新增操作 */ +void put(HashMapOpenAddressing *hashMap, int key, char *val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(hashMap, key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + free(hashMap->buckets[index]->val); + hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(hashMap->buckets[index]->val, val); + hashMap->buckets[index]->val[strlen(val)] = '\0'; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + Pair *pair = (Pair *)malloc(sizeof(Pair)); + pair->key = key; + pair->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(pair->val, val); + pair->val[strlen(val)] = '\0'; + + hashMap->buckets[index] = pair; + hashMap->size++; +} + +/* 刪除操作 */ +void removeItem(HashMapOpenAddressing *hashMap, int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(hashMap, key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + Pair *pair = hashMap->buckets[index]; + free(pair->val); + free(pair); + hashMap->buckets[index] = hashMap->TOMBSTONE; + hashMap->size--; + } +} + +/* 擴容雜湊表 */ +void extend(HashMapOpenAddressing *hashMap) { + // 暫存原雜湊表 + Pair **bucketsTmp = hashMap->buckets; + int oldCapacity = hashMap->capacity; + // 初始化擴容後的新雜湊表 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (int i = 0; i < oldCapacity; i++) { + Pair *pair = bucketsTmp[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + put(hashMap, pair->key, pair->val); + free(pair->val); + free(pair); + } + } + free(bucketsTmp); +} + +/* 列印雜湊表 */ +void print(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair == NULL) { + printf("NULL\n"); + } else if (pair == hashMap->TOMBSTONE) { + printf("TOMBSTONE\n"); + } else { + printf("%d -> %s\n", pair->key, pair->val); + } + } +} + +/* Driver Code */ +int main() { + // 初始化雜湊表 + HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + put(hashmap, 12836, "小哈"); + put(hashmap, 15937, "小囉"); + put(hashmap, 16750, "小算"); + put(hashmap, 13276, "小法"); + put(hashmap, 10583, "小鴨"); + printf("\n新增完成後,雜湊表為\nKey -> Value\n"); + print(hashmap); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + char *name = get(hashmap, 13276); + printf("\n輸入學號 13276 ,查詢到姓名 %s\n", name); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + removeItem(hashmap, 16750); + printf("\n刪除 16750 後,雜湊表為\nKey -> Value\n"); + print(hashmap); + + // 銷燬雜湊表 + delHashMapOpenAddressing(hashmap); + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/simple_hash.c b/zh-hant/codes/c/chapter_hashing/simple_hash.c new file mode 100644 index 0000000000..4d2fdcf33c --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/simple_hash.c @@ -0,0 +1,68 @@ +/** + * File: simple_hash.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 加法雜湊 */ +int addHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* 乘法雜湊 */ +int mulHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (31 * hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* 互斥或雜湊 */ +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; +} + +/* Driver Code */ +int main() { + char *key = "Hello 演算法"; + + int hash = addHash(key); + printf("加法雜湊值為 %d\n", hash); + + hash = mulHash(key); + printf("乘法雜湊值為 %d\n", hash); + + hash = xorHash(key); + printf("互斥或雜湊值為 %d\n", hash); + + hash = rotHash(key); + printf("旋轉雜湊值為 %d\n", hash); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_heap/CMakeLists.txt b/zh-hant/codes/c/chapter_heap/CMakeLists.txt new file mode 100644 index 0000000000..357ec702c3 --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(my_heap_test my_heap_test.c) +add_executable(top_k top_k.c) diff --git a/zh-hant/codes/c/chapter_heap/my_heap.c b/zh-hant/codes/c/chapter_heap/my_heap.c new file mode 100644 index 0000000000..8b07243b4f --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/my_heap.c @@ -0,0 +1,152 @@ +/** + * File: my_heap.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 大頂堆積 */ +typedef struct { + // size 代表的是實際元素的個數 + int size; + // 使用預先分配記憶體的陣列,避免擴容 + int data[MAX_SIZE]; +} MaxHeap; + +// 函式宣告 +void siftDown(MaxHeap *maxHeap, int i); +void siftUp(MaxHeap *maxHeap, int i); +int parent(MaxHeap *maxHeap, int i); + +/* 建構子,根據切片建堆積 */ +MaxHeap *newMaxHeap(int nums[], int size) { + // 所有元素入堆積 + MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); + maxHeap->size = size; + memcpy(maxHeap->data, nums, size * sizeof(int)); + for (int i = parent(maxHeap, size - 1); i >= 0; i--) { + // 堆積化除葉節點以外的其他所有節點 + siftDown(maxHeap, i); + } + return maxHeap; +} + +/* 析構函式 */ +void delMaxHeap(MaxHeap *maxHeap) { + // 釋放記憶體 + free(maxHeap); +} + +/* 獲取左子節點的索引 */ +int left(MaxHeap *maxHeap, int i) { + return 2 * i + 1; +} + +/* 獲取右子節點的索引 */ +int right(MaxHeap *maxHeap, int i) { + return 2 * i + 2; +} + +/* 獲取父節點的索引 */ +int parent(MaxHeap *maxHeap, int i) { + return (i - 1) / 2; // 向下取整 +} + +/* 交換元素 */ +void swap(MaxHeap *maxHeap, int i, int j) { + int temp = maxHeap->data[i]; + maxHeap->data[i] = maxHeap->data[j]; + maxHeap->data[j] = temp; +} + +/* 獲取堆積大小 */ +int size(MaxHeap *maxHeap) { + return maxHeap->size; +} + +/* 判斷堆積是否為空 */ +int isEmpty(MaxHeap *maxHeap) { + return maxHeap->size == 0; +} + +/* 訪問堆積頂元素 */ +int peek(MaxHeap *maxHeap) { + return maxHeap->data[0]; +} + +/* 元素入堆積 */ +void push(MaxHeap *maxHeap, int val) { + // 預設情況下,不應該新增這麼多節點 + if (maxHeap->size == MAX_SIZE) { + printf("heap is full!"); + return; + } + // 新增節點 + maxHeap->data[maxHeap->size] = val; + maxHeap->size++; + + // 從底至頂堆積化 + siftUp(maxHeap, maxHeap->size - 1); +} + +/* 元素出堆積 */ +int pop(MaxHeap *maxHeap) { + // 判空處理 + if (isEmpty(maxHeap)) { + printf("heap is empty!"); + return INT_MAX; + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(maxHeap, 0, size(maxHeap) - 1); + // 刪除節點 + int val = maxHeap->data[maxHeap->size - 1]; + maxHeap->size--; + // 從頂至底堆積化 + siftDown(maxHeap, 0); + + // 返回堆積頂元素 + return val; +} + +/* 從節點 i 開始,從頂至底堆積化 */ +void siftDown(MaxHeap *maxHeap, int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 max + int l = left(maxHeap, i); + int r = right(maxHeap, i); + int max = i; + if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { + max = l; + } + if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { + max = r; + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (max == i) { + break; + } + // 交換兩節點 + swap(maxHeap, i, max); + // 迴圈向下堆積化 + i = max; + } +} + +/* 從節點 i 開始,從底至頂堆積化 */ +void siftUp(MaxHeap *maxHeap, int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = parent(maxHeap, i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { + break; + } + // 交換兩節點 + swap(maxHeap, i, p); + // 迴圈向上堆積化 + i = p; + } +} diff --git a/zh-hant/codes/c/chapter_heap/my_heap_test.c b/zh-hant/codes/c/chapter_heap/my_heap_test.c new file mode 100644 index 0000000000..673082a2cd --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/my_heap_test.c @@ -0,0 +1,41 @@ +/** + * File: my_heap_test.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "my_heap.c" + +/* Driver Code */ +int main() { + /* 初始化堆積 */ + // 初始化大頂堆積 + int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); + printf("輸入陣列並建堆積後\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* 獲取堆積頂元素 */ + printf("\n堆積頂元素為 %d\n", peek(maxHeap)); + + /* 元素入堆積 */ + push(maxHeap, 7); + printf("\n元素 7 入堆積後\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* 堆積頂元素出堆積 */ + int top = pop(maxHeap); + printf("\n堆積頂元素 %d 出堆積後\n", top); + printHeap(maxHeap->data, maxHeap->size); + + /* 獲取堆積大小 */ + printf("\n堆積元素數量為 %d\n", size(maxHeap)); + + /* 判斷堆積是否為空 */ + printf("\n堆積是否為空 %d\n", isEmpty(maxHeap)); + + // 釋放記憶體 + delMaxHeap(maxHeap); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_heap/top_k.c b/zh-hant/codes/c/chapter_heap/top_k.c new file mode 100644 index 0000000000..2c78f5979d --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/top_k.c @@ -0,0 +1,73 @@ +/** + * File: top_k.c + * Created Time: 2023-10-26 + * Author: krahets (krahets163.com) + */ + +#include "my_heap.c" + +/* 元素入堆積 */ +void pushMinHeap(MaxHeap *maxHeap, int val) { + // 元素取反 + push(maxHeap, -val); +} + +/* 元素出堆積 */ +int popMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -pop(maxHeap); +} + +/* 訪問堆積頂元素 */ +int peekMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -peek(maxHeap); +} + +/* 取出堆積中元素 */ +int *getMinHeap(MaxHeap *maxHeap) { + // 將堆積中所有元素取反並存入 res 陣列 + int *res = (int *)malloc(maxHeap->size * sizeof(int)); + for (int i = 0; i < maxHeap->size; i++) { + res[i] = -maxHeap->data[i]; + } + return res; +} + +// 基於堆積查詢陣列中最大的 k 個元素的函式 +int *topKHeap(int *nums, int sizeNums, int k) { + // 初始化小頂堆積 + // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 + int *empty = (int *)malloc(0); + MaxHeap *maxHeap = newMaxHeap(empty, 0); + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < sizeNums; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + int *res = getMinHeap(maxHeap); + // 釋放記憶體 + delMaxHeap(maxHeap); + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 7, 6, 3, 2}; + int k = 3; + int sizeNums = sizeof(nums) / sizeof(nums[0]); + + int *res = topKHeap(nums, sizeNums, k); + printf("最大的 %d 個元素為: ", k); + printArray(res, k); + + free(res); + return 0; +} diff --git a/zh-hant/codes/c/chapter_searching/CMakeLists.txt b/zh-hant/codes/c/chapter_searching/CMakeLists.txt new file mode 100644 index 0000000000..7b2a152d50 --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.c) +add_executable(two_sum two_sum.c) +add_executable(binary_search_edge binary_search_edge.c) +add_executable(binary_search_insertion binary_search_insertion.c) diff --git a/zh-hant/codes/c/chapter_searching/binary_search.c b/zh-hant/codes/c/chapter_searching/binary_search.c new file mode 100644 index 0000000000..16abe0dc94 --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/binary_search.c @@ -0,0 +1,59 @@ +/** + * File: binary_search.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋(雙閉區間) */ +int binarySearch(int *nums, int len, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = len - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 二分搜尋(左閉右開區間) */ +int binarySearchLCRO(int *nums, int len, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + 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; +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* 二分搜尋(雙閉區間) */ + int index = binarySearch(nums, 10, target); + printf("目標元素 6 的索引 = %d\n", index); + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, 10, target); + printf("目標元素 6 的索引 = %d\n", index); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_searching/binary_search_edge.c b/zh-hant/codes/c/chapter_searching/binary_search_edge.c new file mode 100644 index 0000000000..d1285fe9b9 --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/binary_search_edge.c @@ -0,0 +1,67 @@ +/** + * File: binary_search_edge.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* 二分搜尋最左一個 target */ +int binarySearchLeftEdge(int *nums, int numSize, int target) { + // 等價於查詢 target 的插入點 + int i = binarySearchInsertion(nums, numSize, target); + // 未找到 target ,返回 -1 + if (i == numSize || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +int binarySearchRightEdge(int *nums, int numSize, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binarySearchInsertion(nums, numSize, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +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; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_searching/binary_search_insertion.c b/zh-hant/codes/c/chapter_searching/binary_search_insertion.c new file mode 100644 index 0000000000..67ea0784e2 --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/binary_search_insertion.c @@ -0,0 +1,68 @@ +/** + * File: binary_search_insertion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋插入點(無重複元素) */ +int binarySearchInsertionSimple(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; +} + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* Driver Code */ +int main() { + // 無重複元素的陣列 + int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + printf("\n陣列 nums = "); + printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); + // 二分搜尋插入點 + int targets1[] = {6, 9}; + for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { + int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); + printf("元素 %d 的插入點的索引為 %d\n", targets1[i], index); + } + + // 包含重複元素的陣列 + int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\n陣列 nums = "); + printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); + // 二分搜尋插入點 + int targets2[] = {2, 6, 20}; + for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { + int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); + printf("元素 %d 的插入點的索引為 %d\n", targets2[i], index); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_searching/two_sum.c b/zh-hant/codes/c/chapter_searching/two_sum.c new file mode 100644 index 0000000000..b753b354a1 --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/two_sum.c @@ -0,0 +1,86 @@ +/** + * File: two_sum.c + * Created Time: 2023-01-19 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 方法一:暴力列舉 */ +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; + } +} + +/* 方法二:輔助雜湊表 */ +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; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + int nums[] = {2, 7, 11, 15}; + int target = 13; + // ====== Driver Code ====== + int returnSize; + int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); + // 方法一 + printf("方法一 res = "); + printArray(res, returnSize); + + // 方法二 + res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); + printf("方法二 res = "); + printArray(res, returnSize); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_sorting/CMakeLists.txt b/zh-hant/codes/c/chapter_sorting/CMakeLists.txt new file mode 100644 index 0000000000..88756b4c94 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(bubble_sort bubble_sort.c) +add_executable(insertion_sort insertion_sort.c) +add_executable(quick_sort quick_sort.c) +add_executable(counting_sort counting_sort.c) +add_executable(radix_sort radix_sort.c) +add_executable(merge_sort merge_sort.c) +add_executable(heap_sort heap_sort.c) +add_executable(bucket_sort bucket_sort.c) +add_executable(selection_sort selection_sort.c) diff --git a/zh-hant/codes/c/chapter_sorting/bubble_sort.c b/zh-hant/codes/c/chapter_sorting/bubble_sort.c new file mode 100644 index 0000000000..6c6ddff610 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/bubble_sort.c @@ -0,0 +1,61 @@ +/** + * File: bubble_sort.c + * Created Time: 2022-12-26 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* 泡沫排序 */ +void bubbleSort(int nums[], int size) { + // 外迴圈:未排序區間為 [0, i] + for (int i = size - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +void bubbleSortWithFlag(int nums[], int size) { + // 外迴圈:未排序區間為 [0, i] + for (int i = size - 1; i > 0; i--) { + bool flag = false; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + flag = true; + } + } + if (!flag) + break; + } +} + +/* Driver Code */ +int main() { + int nums[6] = {4, 1, 3, 1, 5, 2}; + printf("泡沫排序後: "); + bubbleSort(nums, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + + int nums1[6] = {4, 1, 3, 1, 5, 2}; + printf("\n最佳化版泡沫排序後: "); + bubbleSortWithFlag(nums1, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums1[i]); + } + printf("\n"); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/bucket_sort.c b/zh-hant/codes/c/chapter_sorting/bucket_sort.c new file mode 100644 index 0000000000..d96ec9115b --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/bucket_sort.c @@ -0,0 +1,57 @@ +/** + * File: bucket_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define SIZE 10 + +/* 用於 qsort 的比較函式 */ +int compare(const void *a, const void *b) { + float fa = *(const float *)a; + float fb = *(const float *)b; + return (fa > fb) - (fa < fb); +} + +/* 桶排序 */ +void bucketSort(float nums[], int n) { + int k = n / 2; // 初始化 k = n/2 個桶 + int *sizes = malloc(k * sizeof(int)); // 記錄每個桶的大小 + float **buckets = malloc(k * sizeof(float *)); // 動態陣列的陣列(桶) + // 為每個桶預分配足夠的空間 + for (int i = 0; i < k; ++i) { + buckets[i] = (float *)malloc(n * sizeof(float)); + sizes[i] = 0; + } + // 1. 將陣列元素分配到各個桶中 + for (int i = 0; i < n; ++i) { + int idx = (int)(nums[i] * k); + buckets[idx][sizes[idx]++] = nums[i]; + } + // 2. 對各個桶執行排序 + for (int i = 0; i < k; ++i) { + qsort(buckets[i], sizes[i], sizeof(float), compare); + } + // 3. 合併排序後的桶 + int idx = 0; + for (int i = 0; i < k; ++i) { + for (int j = 0; j < sizes[i]; ++j) { + nums[idx++] = buckets[i][j]; + } + // 釋放記憶體 + free(buckets[i]); + } +} + +/* Driver Code */ +int main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums, SIZE); + printf("桶排序完成後 nums = "); + printArrayFloat(nums, SIZE); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/counting_sort.c b/zh-hant/codes/c/chapter_sorting/counting_sort.c new file mode 100644 index 0000000000..ba1759fabb --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/counting_sort.c @@ -0,0 +1,87 @@ +/** + * File: counting_sort.c + * Created Time: 2023-03-20 + * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +void countingSortNaive(int nums[], int size) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int *counter = calloc(m + 1, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + // 4. 釋放記憶體 + free(counter); +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +void countingSort(int nums[], int size) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int *counter = calloc(m, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + 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); +} + +/* Driver Code */ +int main() { + int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size = sizeof(nums) / sizeof(int); + countingSortNaive(nums, size); + printf("計數排序(無法排序物件)完成後 nums = "); + printArray(nums, size); + + int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size1 = sizeof(nums1) / sizeof(int); + countingSort(nums1, size1); + printf("計數排序完成後 nums1 = "); + printArray(nums1, size1); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/heap_sort.c b/zh-hant/codes/c/chapter_sorting/heap_sort.c new file mode 100644 index 0000000000..17428e4048 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/heap_sort.c @@ -0,0 +1,60 @@ +/** + * File: heap_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +void siftDown(int nums[], int n, int i) { + while (1) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) { + break; + } + // 交換兩節點 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +void heapSort(int nums[], int n) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = n / 2 - 1; i >= 0; --i) { + siftDown(nums, n, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = n - 1; i > 0; --i) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + heapSort(nums, n); + printf("堆積排序完成後 nums = "); + printArray(nums, n); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_sorting/insertion_sort.c b/zh-hant/codes/c/chapter_sorting/insertion_sort.c new file mode 100644 index 0000000000..36a6838165 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/insertion_sort.c @@ -0,0 +1,36 @@ +/** + * File: insertion_sort.c + * Created Time: 2022-12-29 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* 插入排序 */ +void insertionSort(int nums[], int size) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < size; i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + // 將 nums[j] 向右移動一位 + nums[j + 1] = nums[j]; + j--; + } + // 將 base 賦值到正確位置 + nums[j + 1] = base; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + insertionSort(nums, 6); + printf("插入排序完成後 nums = "); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + printf("\n"); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/merge_sort.c b/zh-hant/codes/c/chapter_sorting/merge_sort.c new file mode 100644 index 0000000000..996148ea90 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/merge_sort.c @@ -0,0 +1,63 @@ +/** + * File: merge_sort.c + * Created Time: 2022-03-21 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 合併左子陣列和右子陣列 */ +void merge(int *nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + int tmpSize = right - left + 1; + int *tmp = (int *)malloc(tmpSize * sizeof(int)); + // 初始化左子陣列和右子陣列的起始索引 + int i = left, j = mid + 1, k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmpSize; ++k) { + nums[left + k] = tmp[k]; + } + // 釋放記憶體 + free(tmp); +} + +/* 合併排序 */ +void mergeSort(int *nums, int left, int right) { + // 終止條件 + if (left >= right) + return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + int mid = left + (right - left) / 2; // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* 合併排序 */ + int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; + int size = sizeof(nums) / sizeof(int); + mergeSort(nums, 0, size - 1); + printf("合併排序完成後 nums = "); + printArray(nums, size); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/quick_sort.c b/zh-hant/codes/c/chapter_sorting/quick_sort.c new file mode 100644 index 0000000000..206385b3b3 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/quick_sort.c @@ -0,0 +1,137 @@ +/** + * File: quick_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 元素交換 */ +void swap(int nums[], int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; +} + +/* 哨兵劃分 */ +int partition(int nums[], int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i++; // 從左向右找首個大於基準數的元素 + } + // 交換這兩個元素 + swap(nums, i, j); + } + // 將基準數交換至兩子陣列的分界線 + swap(nums, i, left); + // 返回基準數的索引 + return i; +} + +/* 快速排序 */ +void quickSort(int nums[], int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) { + return; + } + // 哨兵劃分 + int pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); +} + +// 以下為中位數最佳化的快速排序 + +/* 選取三個候選元素的中位數 */ +int medianThree(int nums[], int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之間 + return right; +} + +/* 哨兵劃分(三數取中值) */ +int partitionMedian(int nums[], int left, int right) { + // 選取三個候選元素的中位數 + int med = medianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + swap(nums, left, med); + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 +} + +/* 快速排序(三數取中值) */ +void quickSortMedian(int nums[], int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = partitionMedian(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSortMedian(nums, left, pivot - 1); + quickSortMedian(nums, pivot + 1, right); +} + +// 以下為尾遞迴最佳化的快速排序 + +/* 快速排序(尾遞迴最佳化) */ +void quickSortTailCall(int nums[], int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + // 遞迴排序左子陣列 + quickSortTailCall(nums, left, pivot - 1); + // 剩餘未排序區間為 [pivot + 1, right] + left = pivot + 1; + } else { + // 遞迴排序右子陣列 + quickSortTailCall(nums, pivot + 1, right); + // 剩餘未排序區間為 [left, pivot - 1] + right = pivot - 1; + } + } +} + +/* Driver Code */ +int main() { + /* 快速排序 */ + int nums[] = {2, 4, 1, 0, 3, 5}; + int size = sizeof(nums) / sizeof(int); + quickSort(nums, 0, size - 1); + printf("快速排序完成後 nums = "); + printArray(nums, size); + + /* 快速排序(中位基準數最佳化) */ + int nums1[] = {2, 4, 1, 0, 3, 5}; + quickSortMedian(nums1, 0, size - 1); + printf("快速排序(中位基準數最佳化)完成後 nums = "); + printArray(nums1, size); + + /* 快速排序(尾遞迴最佳化) */ + int nums2[] = {2, 4, 1, 0, 3, 5}; + quickSortTailCall(nums2, 0, size - 1); + printf("快速排序(尾遞迴最佳化)完成後 nums = "); + printArray(nums1, size); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/radix_sort.c b/zh-hant/codes/c/chapter_sorting/radix_sort.c new file mode 100644 index 0000000000..cdb7effb95 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/radix_sort.c @@ -0,0 +1,75 @@ +/** + * File: radix_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + 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); +} + +/* Driver Code */ +int main() { + // 基數排序 + int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + int size = sizeof(nums) / sizeof(int); + radixSort(nums, size); + printf("基數排序完成後 nums = "); + printArray(nums, size); +} diff --git a/zh-hant/codes/c/chapter_sorting/selection_sort.c b/zh-hant/codes/c/chapter_sorting/selection_sort.c new file mode 100644 index 0000000000..7ae3c1ffa6 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/selection_sort.c @@ -0,0 +1,37 @@ +/** + * File: selection_sort.c + * Created Time: 2023-05-31 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 選擇排序 */ +void selectionSort(int nums[], int n) { + // 外迴圈:未排序區間為 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + selectionSort(nums, n); + + printf("選擇排序完成後 nums = "); + printArray(nums, n); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/CMakeLists.txt b/zh-hant/codes/c/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 0000000000..ed3ba840c9 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(array_stack array_stack.c) +add_executable(linkedlist_stack linkedlist_stack.c) +add_executable(array_queue array_queue.c) +add_executable(linkedlist_queue linkedlist_queue.c) +add_executable(array_deque array_deque.c) +add_executable(linkedlist_deque linkedlist_deque.c) diff --git a/zh-hant/codes/c/chapter_stack_and_queue/array_deque.c b/zh-hant/codes/c/chapter_stack_and_queue/array_deque.c new file mode 100644 index 0000000000..234734a418 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/array_deque.c @@ -0,0 +1,172 @@ +/** + * File: array_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於環形陣列實現的雙向佇列 */ +typedef struct { + int *nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 尾指標,指向佇列尾 + 1 + int queCapacity; // 佇列容量 +} ArrayDeque; + +/* 建構子 */ +ArrayDeque *newArrayDeque(int capacity) { + ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); + // 初始化陣列 + deque->queCapacity = capacity; + deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); + deque->front = deque->queSize = 0; + return deque; +} + +/* 析構函式 */ +void delArrayDeque(ArrayDeque *deque) { + free(deque->nums); + free(deque); +} + +/* 獲取雙向佇列的容量 */ +int capacity(ArrayDeque *deque) { + return deque->queCapacity; +} + +/* 獲取雙向佇列的長度 */ +int size(ArrayDeque *deque) { + return deque->queSize; +} + +/* 判斷雙向佇列是否為空 */ +bool empty(ArrayDeque *deque) { + return deque->queSize == 0; +} + +/* 計算環形陣列索引 */ +int dequeIndex(ArrayDeque *deque, int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部時,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return ((i + capacity(deque)) % capacity(deque)); +} + +/* 佇列首入列 */ +void pushFirst(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("雙向佇列已滿\r\n"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部回到尾部 + deque->front = dequeIndex(deque, deque->front - 1); + // 將 num 新增到佇列首 + deque->nums[deque->front] = num; + deque->queSize++; +} + +/* 佇列尾入列 */ +void pushLast(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("雙向佇列已滿\r\n"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = dequeIndex(deque, deque->front + deque->queSize); + // 將 num 新增至佇列尾 + deque->nums[rear] = num; + deque->queSize++; +} + +/* 訪問佇列首元素 */ +int peekFirst(ArrayDeque *deque) { + // 訪問異常:雙向佇列為空 + assert(empty(deque) == 0); + return deque->nums[deque->front]; +} + +/* 訪問佇列尾元素 */ +int peekLast(ArrayDeque *deque) { + // 訪問異常:雙向佇列為空 + assert(empty(deque) == 0); + int last = dequeIndex(deque, deque->front + deque->queSize - 1); + return deque->nums[last]; +} + +/* 佇列首出列 */ +int popFirst(ArrayDeque *deque) { + int num = peekFirst(deque); + // 佇列首指標向後移動一位 + deque->front = dequeIndex(deque, deque->front + 1); + deque->queSize--; + return num; +} + +/* 佇列尾出列 */ +int popLast(ArrayDeque *deque) { + int num = peekLast(deque); + deque->queSize--; + return num; +} + +/* 返回陣列用於列印 */ +int *toArray(ArrayDeque *deque, int *queSize) { + *queSize = deque->queSize; + int *res = (int *)calloc(deque->queSize, sizeof(int)); + int j = deque->front; + for (int i = 0; i < deque->queSize; i++) { + res[i] = deque->nums[j % deque->queCapacity]; + j++; + } + return res; +} + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + int capacity = 10; + int queSize; + ArrayDeque *deque = newArrayDeque(capacity); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("雙向佇列 deque = "); + printArray(toArray(deque, &queSize), queSize); + + /* 訪問元素 */ + int peekFirstNum = peekFirst(deque); + printf("佇列首元素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("佇列尾元素 peekLast = %d\r\n", peekLastNum); + + /* 元素入列 */ + pushLast(deque, 4); + printf("元素 4 佇列尾入列後 deque = "); + printArray(toArray(deque, &queSize), queSize); + pushFirst(deque, 1); + printf("元素 1 佇列首入列後 deque = "); + printArray(toArray(deque, &queSize), queSize); + + /* 元素出列 */ + int popLastNum = popLast(deque); + printf("佇列尾出列元素 = %d ,佇列尾出列後 deque= ", popLastNum); + printArray(toArray(deque, &queSize), queSize); + int popFirstNum = popFirst(deque); + printf("佇列首出列元素 = %d ,佇列首出列後 deque= ", popFirstNum); + printArray(toArray(deque, &queSize), queSize); + + /* 獲取佇列的長度 */ + int dequeSize = size(deque); + printf("雙向佇列長度 size = %d\r\n", dequeSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(deque); + printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + // 釋放記憶體 + delArrayDeque(deque); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_stack_and_queue/array_queue.c b/zh-hant/codes/c/chapter_stack_and_queue/array_queue.c new file mode 100644 index 0000000000..bb49393f37 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/array_queue.c @@ -0,0 +1,134 @@ +/** + * File: array_queue.c + * Created Time: 2023-01-28 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於環形陣列實現的佇列 */ +typedef struct { + int *nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 尾指標,指向佇列尾 + 1 + 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; +} + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + int capacity = 10; + int queSize; + ArrayQueue *queue = newArrayQueue(capacity); + + /* 元素入列 */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("佇列 queue = "); + printArray(toArray(queue, &queSize), queSize); + + /* 訪問佇列首元素 */ + int peekNum = peek(queue); + printf("佇列首元素 peek = %d\r\n", peekNum); + + /* 元素出列 */ + peekNum = pop(queue); + printf("出列元素 pop = %d ,出列後 queue = ", peekNum); + printArray(toArray(queue, &queSize), queSize); + + /* 獲取佇列的長度 */ + int queueSize = size(queue); + printf("佇列長度 size = %d\r\n", queueSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(queue); + printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + push(queue, i); + pop(queue); + printf("第 %d 輪入列 + 出列後 queue = ", i); + printArray(toArray(queue, &queSize), queSize); + } + + // 釋放記憶體 + delArrayQueue(queue); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c b/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c new file mode 100644 index 0000000000..4593c6ba1c --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c @@ -0,0 +1,103 @@ +/** + * File: array_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 基於陣列實現的堆疊 */ +typedef struct { + int *data; + int size; +} ArrayStack; + +/* 建構子 */ +ArrayStack *newArrayStack() { + ArrayStack *stack = malloc(sizeof(ArrayStack)); + // 初始化一個大容量,避免擴容 + stack->data = malloc(sizeof(int) * MAX_SIZE); + stack->size = 0; + return stack; +} + +/* 析構函式 */ +void delArrayStack(ArrayStack *stack) { + free(stack->data); + free(stack); +} + +/* 獲取堆疊的長度 */ +int size(ArrayStack *stack) { + return stack->size; +} + +/* 判斷堆疊是否為空 */ +bool isEmpty(ArrayStack *stack) { + return stack->size == 0; +} + +/* 入堆疊 */ +void push(ArrayStack *stack, int num) { + if (stack->size == MAX_SIZE) { + printf("堆疊已滿\n"); + return; + } + stack->data[stack->size] = num; + stack->size++; +} + +/* 訪問堆疊頂元素 */ +int peek(ArrayStack *stack) { + if (stack->size == 0) { + printf("堆疊為空\n"); + return INT_MAX; + } + return stack->data[stack->size - 1]; +} + +/* 出堆疊 */ +int pop(ArrayStack *stack) { + int val = peek(stack); + stack->size--; + return val; +} + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + ArrayStack *stack = newArrayStack(); + + /* 元素入堆疊 */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + printf("堆疊 stack = "); + printArray(stack->data, stack->size); + + /* 訪問堆疊頂元素 */ + int val = peek(stack); + printf("堆疊頂元素 top = %d\n", val); + + /* 元素出堆疊 */ + val = pop(stack); + printf("出堆疊元素 pop = %d ,出堆疊後 stack = ", val); + printArray(stack->data, stack->size); + + /* 獲取堆疊的長度 */ + int size = stack->size; + printf("堆疊的長度 size = %d\n", size); + + /* 判斷是否為空 */ + bool empty = isEmpty(stack); + printf("堆疊是否為空 = %s\n", empty ? "true" : "false"); + + // 釋放記憶體 + delArrayStack(stack); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_deque.c b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_deque.c new file mode 100644 index 0000000000..ff3e52b9d8 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_deque.c @@ -0,0 +1,212 @@ +/** + * File: linkedlist_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 雙向鏈結串列節點 */ +typedef struct DoublyListNode { + int val; // 節點值 + struct DoublyListNode *next; // 後繼節點 + struct DoublyListNode *prev; // 前驅節點 +} DoublyListNode; + +/* 建構子 */ +DoublyListNode *newDoublyListNode(int num) { + DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); + new->val = num; + new->next = NULL; + new->prev = NULL; + return new; +} + +/* 析構函式 */ +void delDoublyListNode(DoublyListNode *node) { + free(node); +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +typedef struct { + DoublyListNode *front, *rear; // 頭節點 front ,尾節點 rear + int queSize; // 雙向佇列的長度 +} LinkedListDeque; + +/* 建構子 */ +LinkedListDeque *newLinkedListDeque() { + LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); + deque->front = NULL; + deque->rear = NULL; + deque->queSize = 0; + return deque; +} + +/* 析構函式 */ +void delLinkedListdeque(LinkedListDeque *deque) { + // 釋放所有節點 + for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { + DoublyListNode *tmp = deque->front; + deque->front = deque->front->next; + free(tmp); + } + // 釋放 deque 結構體 + free(deque); +} + +/* 獲取佇列的長度 */ +int size(LinkedListDeque *deque) { + return deque->queSize; +} + +/* 判斷佇列是否為空 */ +bool empty(LinkedListDeque *deque) { + return (size(deque) == 0); +} + +/* 入列 */ +void push(LinkedListDeque *deque, int num, bool isFront) { + DoublyListNode *node = newDoublyListNode(num); + // 若鏈結串列為空,則令 front 和 rear 都指向node + if (empty(deque)) { + deque->front = deque->rear = node; + } + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + deque->front->prev = node; + node->next = deque->front; + deque->front = node; // 更新頭節點 + } + // 佇列尾入列操作 + else { + // 將 node 新增至鏈結串列尾部 + deque->rear->next = node; + node->prev = deque->rear; + deque->rear = node; + } + deque->queSize++; // 更新佇列長度 +} + +/* 佇列首入列 */ +void pushFirst(LinkedListDeque *deque, int num) { + push(deque, num, true); +} + +/* 佇列尾入列 */ +void pushLast(LinkedListDeque *deque, int num) { + push(deque, num, false); +} + +/* 訪問佇列首元素 */ +int peekFirst(LinkedListDeque *deque) { + assert(size(deque) && deque->front); + return deque->front->val; +} + +/* 訪問佇列尾元素 */ +int peekLast(LinkedListDeque *deque) { + assert(size(deque) && deque->rear); + return deque->rear->val; +} + +/* 出列 */ +int pop(LinkedListDeque *deque, bool isFront) { + if (empty(deque)) + return -1; + int val; + // 佇列首出列操作 + if (isFront) { + val = peekFirst(deque); // 暫存頭節點值 + DoublyListNode *fNext = deque->front->next; + if (fNext) { + fNext->prev = NULL; + deque->front->next = NULL; + } + delDoublyListNode(deque->front); + deque->front = fNext; // 更新頭節點 + } + // 佇列尾出列操作 + else { + val = peekLast(deque); // 暫存尾節點值 + DoublyListNode *rPrev = deque->rear->prev; + if (rPrev) { + rPrev->next = NULL; + deque->rear->prev = NULL; + } + delDoublyListNode(deque->rear); + deque->rear = rPrev; // 更新尾節點 + } + deque->queSize--; // 更新佇列長度 + return val; +} + +/* 佇列首出列 */ +int popFirst(LinkedListDeque *deque) { + return pop(deque, true); +} + +/* 佇列尾出列 */ +int popLast(LinkedListDeque *deque) { + return pop(deque, false); +} + +/* 列印佇列 */ +void printLinkedListDeque(LinkedListDeque *deque) { + int *arr = malloc(sizeof(int) * deque->queSize); + // 複製鏈結串列中的資料到陣列 + int i; + DoublyListNode *node; + for (i = 0, node = deque->front; i < deque->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, deque->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + LinkedListDeque *deque = newLinkedListDeque(); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("雙向佇列 deque = "); + printLinkedListDeque(deque); + + /* 訪問元素 */ + int peekFirstNum = peekFirst(deque); + printf("佇列首元素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("佇列首元素 peekLast = %d\r\n", peekLastNum); + + /* 元素入列 */ + pushLast(deque, 4); + printf("元素 4 佇列尾入列後 deque ="); + printLinkedListDeque(deque); + pushFirst(deque, 1); + printf("元素 1 佇列首入列後 deque ="); + printLinkedListDeque(deque); + + /* 元素出列 */ + int popLastNum = popLast(deque); + printf("佇列尾出列元素 popLast = %d ,佇列尾出列後 deque = ", popLastNum); + printLinkedListDeque(deque); + int popFirstNum = popFirst(deque); + printf("佇列首出列元素 popFirst = %d ,佇列首出列後 deque = ", popFirstNum); + printLinkedListDeque(deque); + + /* 獲取佇列的長度 */ + int dequeSize = size(deque); + printf("雙向佇列長度 size = %d\r\n", dequeSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(deque); + printf("雙向佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + // 釋放記憶體 + delLinkedListdeque(deque); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_queue.c b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_queue.c new file mode 100644 index 0000000000..788c2dd98c --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_queue.c @@ -0,0 +1,128 @@ +/** + * File: linkedlist_queue.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於鏈結串列實現的佇列 */ +typedef struct { + ListNode *front, *rear; + int queSize; +} LinkedListQueue; + +/* 建構子 */ +LinkedListQueue *newLinkedListQueue() { + LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); + queue->front = NULL; + queue->rear = NULL; + queue->queSize = 0; + return queue; +} + +/* 析構函式 */ +void delLinkedListQueue(LinkedListQueue *queue) { + // 釋放所有節點 + while (queue->front != NULL) { + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + } + // 釋放 queue 結構體 + free(queue); +} + +/* 獲取佇列的長度 */ +int size(LinkedListQueue *queue) { + return queue->queSize; +} + +/* 判斷佇列是否為空 */ +bool empty(LinkedListQueue *queue) { + return (size(queue) == 0); +} + +/* 入列 */ +void push(LinkedListQueue *queue, int num) { + // 尾節點處新增 node + ListNode *node = newListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (queue->front == NULL) { + queue->front = node; + queue->rear = node; + } + // 如果佇列不為空,則將該節點新增到尾節點後 + else { + queue->rear->next = node; + queue->rear = node; + } + queue->queSize++; +} + +/* 訪問佇列首元素 */ +int peek(LinkedListQueue *queue) { + assert(size(queue) && queue->front); + return queue->front->val; +} + +/* 出列 */ +int pop(LinkedListQueue *queue) { + int num = peek(queue); + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + queue->queSize--; + return num; +} + +/* 列印佇列 */ +void printLinkedListQueue(LinkedListQueue *queue) { + int *arr = malloc(sizeof(int) * queue->queSize); + // 複製鏈結串列中的資料到陣列 + int i; + ListNode *node; + for (i = 0, node = queue->front; i < queue->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, queue->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + LinkedListQueue *queue = newLinkedListQueue(); + + /* 元素入列 */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("佇列 queue = "); + printLinkedListQueue(queue); + + /* 訪問佇列首元素 */ + int peekNum = peek(queue); + printf("佇列首元素 peek = %d\r\n", peekNum); + + /* 元素出列 */ + peekNum = pop(queue); + printf("出列元素 pop = %d ,出列後 queue = ", peekNum); + printLinkedListQueue(queue); + + /* 獲取佇列的長度 */ + int queueSize = size(queue); + printf("佇列長度 size = %d\r\n", queueSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(queue); + printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + // 釋放記憶體 + delLinkedListQueue(queue); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_stack.c b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_stack.c new file mode 100644 index 0000000000..9dd3a8d07f --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_stack.c @@ -0,0 +1,107 @@ +/** + * File: linkedlist_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於鏈結串列實現的堆疊 */ +typedef struct { + ListNode *top; // 將頭節點作為堆疊頂 + int size; // 堆疊的長度 +} LinkedListStack; + +/* 建構子 */ +LinkedListStack *newLinkedListStack() { + LinkedListStack *s = malloc(sizeof(LinkedListStack)); + s->top = NULL; + s->size = 0; + return s; +} + +/* 析構函式 */ +void delLinkedListStack(LinkedListStack *s) { + while (s->top) { + ListNode *n = s->top->next; + free(s->top); + s->top = n; + } + free(s); +} + +/* 獲取堆疊的長度 */ +int size(LinkedListStack *s) { + return s->size; +} + +/* 判斷堆疊是否為空 */ +bool isEmpty(LinkedListStack *s) { + return size(s) == 0; +} + +/* 入堆疊 */ +void push(LinkedListStack *s, int num) { + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->next = s->top; // 更新新加節點指標域 + node->val = num; // 更新新加節點資料域 + s->top = node; // 更新堆疊頂 + s->size++; // 更新堆疊大小 +} + +/* 訪問堆疊頂元素 */ +int peek(LinkedListStack *s) { + if (s->size == 0) { + printf("堆疊為空\n"); + return INT_MAX; + } + return s->top->val; +} + +/* 出堆疊 */ +int pop(LinkedListStack *s) { + int val = peek(s); + ListNode *tmp = s->top; + s->top = s->top->next; + // 釋放記憶體 + free(tmp); + s->size--; + return val; +} + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + LinkedListStack *stack = newLinkedListStack(); + + /* 元素入堆疊 */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + + printf("堆疊 stack = "); + printLinkedList(stack->top); + + /* 訪問堆疊頂元素 */ + int val = peek(stack); + printf("堆疊頂元素 top = %d\r\n", val); + + /* 元素出堆疊 */ + val = pop(stack); + printf("出堆疊元素 pop = %d, 出堆疊後 stack = ", val); + printLinkedList(stack->top); + + /* 獲取堆疊的長度 */ + printf("堆疊的長度 size = %d\n", size(stack)); + + /* 判斷是否為空 */ + bool empty = isEmpty(stack); + printf("堆疊是否為空 = %s\n", empty ? "true" : "false"); + + // 釋放記憶體 + delLinkedListStack(stack); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/CMakeLists.txt b/zh-hant/codes/c/chapter_tree/CMakeLists.txt new file mode 100644 index 0000000000..9b4e825ff1 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.c) +add_executable(binary_tree binary_tree.c) +add_executable(binary_tree_bfs binary_tree_bfs.c) +add_executable(binary_tree_dfs binary_tree_dfs.c) +add_executable(binary_search_tree binary_search_tree.c) +add_executable(array_binary_tree array_binary_tree.c) diff --git a/zh-hant/codes/c/chapter_tree/array_binary_tree.c b/zh-hant/codes/c/chapter_tree/array_binary_tree.c new file mode 100644 index 0000000000..645afe715f --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/array_binary_tree.c @@ -0,0 +1,166 @@ +/** + * File: array_binary_tree.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 陣列表示下的二元樹結構體 */ +typedef struct { + int *tree; + int size; +} ArrayBinaryTree; + +/* 建構子 */ +ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { + ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); + abt->tree = malloc(sizeof(int) * arrSize); + memcpy(abt->tree, arr, sizeof(int) * arrSize); + abt->size = arrSize; + return abt; +} + +/* 析構函式 */ +void delArrayBinaryTree(ArrayBinaryTree *abt) { + free(abt->tree); + free(abt); +} + +/* 串列容量 */ +int size(ArrayBinaryTree *abt) { + return abt->size; +} + +/* 獲取索引為 i 節點的值 */ +int val(ArrayBinaryTree *abt, int i) { + // 若索引越界,則返回 INT_MAX ,代表空位 + if (i < 0 || i >= size(abt)) + return INT_MAX; + return abt->tree[i]; +} + +/* 獲取索引為 i 節點的左子節點的索引 */ +int left(int i) { + return 2 * i + 1; +} + +/* 獲取索引為 i 節點的右子節點的索引 */ +int right(int i) { + return 2 * i + 2; +} + +/* 獲取索引為 i 節點的父節點的索引 */ +int parent(int i) { + return (i - 1) / 2; +} + +/* 層序走訪 */ +int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + // 直接走訪陣列 + for (int i = 0; i < size(abt); i++) { + if (val(abt, i) != INT_MAX) + res[index++] = val(abt, i); + } + *returnSize = index; + return res; +} + +/* 深度優先走訪 */ +void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { + // 若為空位,則返回 + if (val(abt, i) == INT_MAX) + return; + // 前序走訪 + if (strcmp(order, "pre") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, left(i), order, res, index); + // 中序走訪 + if (strcmp(order, "in") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, right(i), order, res, index); + // 後序走訪 + if (strcmp(order, "post") == 0) + res[(*index)++] = val(abt, i); +} + +/* 前序走訪 */ +int *preOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "pre", res, &index); + *returnSize = index; + return res; +} + +/* 中序走訪 */ +int *inOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "in", res, &index); + *returnSize = index; + return res; +} + +/* 後序走訪 */ +int *postOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "post", res, &index); + *returnSize = index; + return res; +} + +/* Driver Code */ +int main() { + // 初始化二元樹 + // 使用 INT_MAX 代表空位 NULL + int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + int arrSize = sizeof(arr) / sizeof(arr[0]); + TreeNode *root = arrayToTree(arr, arrSize); + printf("\n初始化二元樹\n"); + printf("二元樹的陣列表示:\n"); + printArray(arr, arrSize); + printf("二元樹的鏈結串列表示:\n"); + printTree(root); + + ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); + + // 訪問節點 + int i = 1; + int l = left(i), r = right(i), p = parent(i); + printf("\n當前節點的索引為 %d,值為 %d\n", i, val(abt, i)); + printf("其左子節點的索引為 %d,值為 %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); + printf("其右子節點的索引為 %d,值為 %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); + printf("其父節點的索引為 %d,值為 %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); + + // 走訪樹 + int returnSize; + int *res; + + res = levelOrder(abt, &returnSize); + printf("\n層序走訪為: "); + printArray(res, returnSize); + free(res); + + res = preOrder(abt, &returnSize); + printf("前序走訪為: "); + printArray(res, returnSize); + free(res); + + res = inOrder(abt, &returnSize); + printf("中序走訪為: "); + printArray(res, returnSize); + free(res); + + res = postOrder(abt, &returnSize); + printf("後序走訪為: "); + printArray(res, returnSize); + free(res); + + // 釋放記憶體 + delArrayBinaryTree(abt); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/avl_tree.c b/zh-hant/codes/c/chapter_tree/avl_tree.c new file mode 100644 index 0000000000..660d5df851 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/avl_tree.c @@ -0,0 +1,259 @@ +/** + * File: avl_tree.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* AVL 樹結構體 */ +typedef struct { + TreeNode *root; +} AVLTree; + +/* 建構子 */ +AVLTree *newAVLTree() { + AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); + tree->root = NULL; + return tree; +} + +/* 析構函式 */ +void delAVLTree(AVLTree *tree) { + freeMemoryTree(tree->root); + free(tree); +} + +/* 獲取節點高度 */ +int height(TreeNode *node) { + // 空節點高度為 -1 ,葉節點高度為 0 + if (node != NULL) { + return node->height; + } + return -1; +} + +/* 更新節點高度 */ +void updateHeight(TreeNode *node) { + int lh = height(node->left); + int rh = height(node->right); + // 節點高度等於最高子樹高度 + 1 + if (lh > rh) { + node->height = lh + 1; + } else { + node->height = rh + 1; + } +} + +/* 獲取平衡因子 */ +int balanceFactor(TreeNode *node) { + // 空節點平衡因子為 0 + if (node == NULL) { + return 0; + } + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node->left) - height(node->right); +} + +/* 右旋操作 */ +TreeNode *rightRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->left; + grandChild = child->right; + // 以 child 為原點,將 node 向右旋轉 + child->right = node; + node->left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; +} + +/* 左旋操作 */ +TreeNode *leftRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->right; + grandChild = child->left; + // 以 child 為原點,將 node 向左旋轉 + child->left = node; + node->right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; +} + +/* 執行旋轉操作,使該子樹重新恢復平衡 */ +TreeNode *rotate(TreeNode *node) { + // 獲取節點 node 的平衡因子 + int bf = balanceFactor(node); + // 左偏樹 + if (bf > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏樹 + if (bf < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; +} + +/* 遞迴插入節點(輔助函式) */ +TreeNode *insertHelper(TreeNode *node, int val) { + if (node == NULL) { + return newTreeNode(val); + } + /* 1. 查詢插入位置並插入節點 */ + if (val < node->val) { + node->left = insertHelper(node->left, val); + } else if (val > node->val) { + node->right = insertHelper(node->right, val); + } else { + // 重複節點不插入,直接返回 + return node; + } + // 更新節點高度 + updateHeight(node); + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; +} + +/* 插入節點 */ +void insert(AVLTree *tree, int val) { + tree->root = insertHelper(tree->root, val); +} + +/* 遞迴刪除節點(輔助函式) */ +TreeNode *removeHelper(TreeNode *node, int val) { + TreeNode *child, *grandChild; + if (node == NULL) { + return NULL; + } + /* 1. 查詢節點並刪除 */ + if (val < node->val) { + node->left = removeHelper(node->left, val); + } else if (val > node->val) { + node->right = removeHelper(node->right, val); + } else { + if (node->left == NULL || node->right == NULL) { + child = node->left; + if (node->right != NULL) { + child = node->right; + } + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == NULL) { + return NULL; + } else { + // 子節點數量 = 1 ,直接刪除 node + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode *temp = node->right; + while (temp->left != NULL) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + // 更新節點高度 + updateHeight(node); + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; +} + +/* 刪除節點 */ +// 由於引入了 stdio.h ,此處無法使用 remove 關鍵詞 +void removeItem(AVLTree *tree, int val) { + TreeNode *root = removeHelper(tree->root, val); +} + +/* 查詢節點 */ +TreeNode *search(AVLTree *tree, int val) { + TreeNode *cur = tree->root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + if (cur->val < val) { + // 目標節點在 cur 的右子樹中 + cur = cur->right; + } else if (cur->val > val) { + // 目標節點在 cur 的左子樹中 + cur = cur->left; + } else { + // 找到目標節點,跳出迴圈 + break; + } + } + // 找到目標節點,跳出迴圈 + return cur; +} + +void testInsert(AVLTree *tree, int val) { + insert(tree, val); + printf("\n插入節點 %d 後,AVL 樹為 \n", val); + printTree(tree->root); +} + +void testRemove(AVLTree *tree, int val) { + removeItem(tree, val); + printf("\n刪除節點 %d 後,AVL 樹為 \n", val); + printTree(tree->root); +} + +/* Driver Code */ +int main() { + /* 初始化空 AVL 樹 */ + AVLTree *tree = (AVLTree *)newAVLTree(); + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(tree, 1); + testInsert(tree, 2); + testInsert(tree, 3); + testInsert(tree, 4); + testInsert(tree, 5); + testInsert(tree, 8); + testInsert(tree, 7); + testInsert(tree, 9); + testInsert(tree, 10); + testInsert(tree, 6); + + /* 插入重複節點 */ + testInsert(tree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(tree, 8); // 刪除度為 0 的節點 + testRemove(tree, 5); // 刪除度為 1 的節點 + testRemove(tree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode *node = search(tree, 7); + printf("\n查詢到的節點物件節點值 = %d \n", node->val); + + // 釋放記憶體 + delAVLTree(tree); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_search_tree.c b/zh-hant/codes/c/chapter_tree/binary_search_tree.c new file mode 100644 index 0000000000..a20e5f4f03 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_search_tree.c @@ -0,0 +1,171 @@ +/** + * File: binary_search_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 二元搜尋樹結構體 */ +typedef struct { + TreeNode *root; +} BinarySearchTree; + +/* 建構子 */ +BinarySearchTree *newBinarySearchTree() { + // 初始化空樹 + BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); + bst->root = NULL; + return bst; +} + +/* 析構函式 */ +void delBinarySearchTree(BinarySearchTree *bst) { + freeMemoryTree(bst->root); + free(bst); +} + +/* 獲取二元樹根節點 */ +TreeNode *getRoot(BinarySearchTree *bst) { + return bst->root; +} + +/* 查詢節點 */ +TreeNode *search(BinarySearchTree *bst, int num) { + TreeNode *cur = bst->root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + if (cur->val < num) { + // 目標節點在 cur 的右子樹中 + cur = cur->right; + } else if (cur->val > num) { + // 目標節點在 cur 的左子樹中 + cur = cur->left; + } else { + // 找到目標節點,跳出迴圈 + break; + } + } + // 返回目標節點 + return cur; +} + +/* 插入節點 */ +void insert(BinarySearchTree *bst, int num) { + // 若樹為空,則初始化根節點 + if (bst->root == NULL) { + bst->root = newTreeNode(num); + return; + } + TreeNode *cur = bst->root, *pre = NULL; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + // 找到重複節點,直接返回 + if (cur->val == num) { + return; + } + pre = cur; + if (cur->val < num) { + // 插入位置在 cur 的右子樹中 + cur = cur->right; + } else { + // 插入位置在 cur 的左子樹中 + cur = cur->left; + } + } + // 插入節點 + TreeNode *node = newTreeNode(num); + if (pre->val < num) { + pre->right = node; + } else { + pre->left = node; + } +} + +/* 刪除節點 */ +// 由於引入了 stdio.h ,此處無法使用 remove 關鍵詞 +void removeItem(BinarySearchTree *bst, int num) { + // 若樹為空,直接提前返回 + if (bst->root == NULL) + return; + TreeNode *cur = bst->root, *pre = NULL; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + // 找到待刪除節點,跳出迴圈 + if (cur->val == num) + break; + pre = cur; + if (cur->val < num) { + // 待刪除節點在 root 的右子樹中 + cur = cur->right; + } else { + // 待刪除節點在 root 的左子樹中 + cur = cur->left; + } + } + // 若無待刪除節點,則直接返回 + if (cur == NULL) + return; + // 判斷待刪除節點是否存在子節點 + if (cur->left == NULL || cur->right == NULL) { + /* 子節點數量 = 0 or 1 */ + // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 + TreeNode *child = cur->left != NULL ? cur->left : cur->right; + // 刪除節點 cur + if (pre->left == cur) { + pre->left = child; + } else { + pre->right = child; + } + // 釋放記憶體 + free(cur); + } else { + /* 子節點數量 = 2 */ + // 獲取中序走訪中 cur 的下一個節點 + TreeNode *tmp = cur->right; + while (tmp->left != NULL) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 遞迴刪除節點 tmp + removeItem(bst, tmp->val); + // 用 tmp 覆蓋 cur + cur->val = tmpVal; + } +} + +/* Driver Code */ +int main() { + /* 初始化二元搜尋樹 */ + int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + BinarySearchTree *bst = newBinarySearchTree(); + for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { + insert(bst, nums[i]); + } + printf("初始化的二元樹為\n"); + printTree(getRoot(bst)); + + /* 查詢節點 */ + TreeNode *node = search(bst, 7); + printf("查詢到的節點物件的節點值 = %d\n", node->val); + + /* 插入節點 */ + insert(bst, 16); + printf("插入節點 16 後,二元樹為\n"); + printTree(getRoot(bst)); + + /* 刪除節點 */ + removeItem(bst, 1); + printf("刪除節點 1 後,二元樹為\n"); + printTree(getRoot(bst)); + removeItem(bst, 2); + printf("刪除節點 2 後,二元樹為\n"); + printTree(getRoot(bst)); + removeItem(bst, 4); + printf("刪除節點 4 後,二元樹為\n"); + printTree(getRoot(bst)); + + // 釋放記憶體 + delBinarySearchTree(bst); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_tree.c b/zh-hant/codes/c/chapter_tree/binary_tree.c new file mode 100644 index 0000000000..4dd1b76bb9 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_tree.c @@ -0,0 +1,43 @@ +/** + * File: binary_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + printf("初始化二元樹\n"); + printTree(n1); + + /* 插入與刪除節點 */ + TreeNode *P = newTreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + printf("插入節點 P 後\n"); + printTree(n1); + + // 刪除節點 P + n1->left = n2; + // 釋放記憶體 + free(P); + printf("刪除節點 P 後\n"); + printTree(n1); + + freeMemoryTree(n1); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_tree_bfs.c b/zh-hant/codes/c/chapter_tree/binary_tree_bfs.c new file mode 100644 index 0000000000..16fcaa61d2 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_tree_bfs.c @@ -0,0 +1,73 @@ +/** + * File: binary_tree_bfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* 層序走訪 */ +int *levelOrder(TreeNode *root, int *size) { + /* 輔助佇列 */ + int front, rear; + int index, *arr; + TreeNode *node; + TreeNode **queue; + + /* 輔助佇列 */ + queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); + // 佇列指標 + front = 0, rear = 0; + // 加入根節點 + queue[rear++] = root; + // 初始化一個串列,用於儲存走訪序列 + /* 輔助陣列 */ + arr = (int *)malloc(sizeof(int) * MAX_SIZE); + // 陣列指標 + index = 0; + while (front < rear) { + // 隊列出隊 + node = queue[front++]; + // 儲存節點值 + arr[index++] = node->val; + if (node->left != NULL) { + // 左子節點入列 + queue[rear++] = node->left; + } + if (node->right != NULL) { + // 右子節點入列 + queue[rear++] = node->right; + } + } + // 更新陣列長度的值 + *size = index; + arr = realloc(arr, sizeof(int) * (*size)); + + // 釋放輔助陣列空間 + free(queue); + return arr; +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("初始化二元樹\n"); + printTree(root); + + /* 層序走訪 */ + // 需要傳入陣列的長度 + int *arr = levelOrder(root, &size); + printf("層序走訪的節點列印序列 = "); + printArray(arr, size); + + // 釋放記憶體 + freeMemoryTree(root); + free(arr); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_tree_dfs.c b/zh-hant/codes/c/chapter_tree/binary_tree_dfs.c new file mode 100644 index 0000000000..d0f89008af --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_tree_dfs.c @@ -0,0 +1,75 @@ +/** + * File: binary_tree_dfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +// 輔助陣列,用於儲存走訪序列 +int arr[MAX_SIZE]; + +/* 前序走訪 */ +void preOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + arr[(*size)++] = root->val; + preOrder(root->left, size); + preOrder(root->right, size); +} + +/* 中序走訪 */ +void inOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root->left, size); + arr[(*size)++] = root->val; + inOrder(root->right, size); +} + +/* 後序走訪 */ +void postOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root->left, size); + postOrder(root->right, size); + arr[(*size)++] = root->val; +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("初始化二元樹\n"); + printTree(root); + + /* 前序走訪 */ + // 初始化輔助陣列 + size = 0; + preOrder(root, &size); + printf("前序走訪的節點列印序列 = "); + printArray(arr, size); + + /* 中序走訪 */ + size = 0; + inOrder(root, &size); + printf("中序走訪的節點列印序列 = "); + printArray(arr, size); + + /* 後序走訪 */ + size = 0; + postOrder(root, &size); + printf("後序走訪的節點列印序列 = "); + printArray(arr, size); + + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/utils/CMakeLists.txt b/zh-hant/codes/c/utils/CMakeLists.txt new file mode 100644 index 0000000000..c1ece2e38b --- /dev/null +++ b/zh-hant/codes/c/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(utils + common_test.c + common.h print_util.h + list_node.h tree_node.h + uthash.h) \ No newline at end of file diff --git a/zh-hant/codes/c/utils/common.h b/zh-hant/codes/c/utils/common.h new file mode 100644 index 0000000000..8b9adeff7a --- /dev/null +++ b/zh-hant/codes/c/utils/common.h @@ -0,0 +1,36 @@ +/** + * File: common.h + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.h" +#include "print_util.h" +#include "tree_node.h" +#include "vertex.h" + +// hash table lib +#include "uthash.h" + +#include "vector.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_H diff --git a/zh-hant/codes/c/utils/common_test.c b/zh-hant/codes/c/utils/common_test.c new file mode 100644 index 0000000000..a889b423b8 --- /dev/null +++ b/zh-hant/codes/c/utils/common_test.c @@ -0,0 +1,35 @@ +/** + * File: include_test.c + * Created Time: 2023-01-10 + * Author: Reanon (793584285@qq.com) + */ + +#include "common.h" + +void testListNode() { + int nums[] = {2, 3, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + ListNode *head = arrToLinkedList(nums, size); + printLinkedList(head); +} + +void testTreeNode() { + int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + + // print tree + printTree(root); + + // tree to arr + int *arr = treeToArray(root, &size); + printArray(arr, size); +} + +int main(int argc, char *argv[]) { + printf("==testListNode==\n"); + testListNode(); + printf("==testTreeNode==\n"); + testTreeNode(); + return 0; +} diff --git a/zh-hant/codes/c/utils/list_node.h b/zh-hant/codes/c/utils/list_node.h new file mode 100644 index 0000000000..73567a1bcf --- /dev/null +++ b/zh-hant/codes/c/utils/list_node.h @@ -0,0 +1,59 @@ +/** + * File: list_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef LIST_NODE_H +#define LIST_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 鏈結串列節點結構體 */ +typedef struct ListNode { + int val; // 節點值 + struct ListNode *next; // 指向下一節點的引用 +} ListNode; + +/* 建構子,初始化一個新節點 */ +ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *)malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; +} + +/* 將陣列反序列化為鏈結串列 */ +ListNode *arrToLinkedList(const int *arr, size_t size) { + if (size <= 0) { + return NULL; + } + + ListNode *dummy = newListNode(0); + ListNode *node = dummy; + for (int i = 0; i < size; i++) { + node->next = newListNode(arr[i]); + node = node->next; + } + return dummy->next; +} + +/* 釋放分配給鏈結串列的記憶體空間 */ +void freeMemoryLinkedList(ListNode *cur) { + // 釋放記憶體 + ListNode *pre; + while (cur != NULL) { + pre = cur; + cur = cur->next; + free(pre); + } +} + +#ifdef __cplusplus +} +#endif + +#endif // LIST_NODE_H diff --git a/zh-hant/codes/c/utils/print_util.h b/zh-hant/codes/c/utils/print_util.h new file mode 100644 index 0000000000..aab7f140ab --- /dev/null +++ b/zh-hant/codes/c/utils/print_util.h @@ -0,0 +1,131 @@ +/** + * File: print_util.h + * Created Time: 2022-12-21 + * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) + */ + +#ifndef PRINT_UTIL_H +#define PRINT_UTIL_H + +#include +#include +#include + +#include "list_node.h" +#include "tree_node.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 列印陣列 */ +void printArray(int arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%d, ", arr[i]); + } + printf("%d]\n", arr[size - 1]); +} + +/* 列印陣列 */ +void printArrayFloat(float arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%.2f, ", arr[i]); + } + printf("%.2f]\n", arr[size - 1]); +} + +/* 列印鏈結串列 */ +void printLinkedList(ListNode *node) { + if (node == NULL) { + return; + } + while (node->next != NULL) { + printf("%d -> ", node->val); + node = node->next; + } + printf("%d\n", node->val); +} + +typedef struct Trunk { + struct Trunk *prev; + char *str; +} Trunk; + +Trunk *newTrunk(Trunk *prev, char *str) { + Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); + trunk->prev = prev; + trunk->str = (char *)malloc(sizeof(char) * 10); + strcpy(trunk->str, str); + return trunk; +} + +void showTrunks(Trunk *trunk) { + if (trunk == NULL) { + return; + } + showTrunks(trunk->prev); + printf("%s", trunk->str); +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { + if (node == NULL) { + return; + } + char *prev_str = " "; + Trunk *trunk = newTrunk(prev, prev_str); + printTreeHelper(node->right, trunk, true); + if (prev == NULL) { + trunk->str = "———"; + } else if (isRight) { + trunk->str = "/———"; + prev_str = " |"; + } else { + trunk->str = "\\———"; + prev->str = prev_str; + } + showTrunks(trunk); + printf("%d\n", node->val); + + if (prev != NULL) { + prev->str = prev_str; + } + trunk->str = " |"; + + printTreeHelper(node->left, trunk, false); +} + +/* 列印二元樹 */ +void printTree(TreeNode *root) { + printTreeHelper(root, NULL, false); +} + +/* 列印堆積 */ +void printHeap(int arr[], int size) { + TreeNode *root; + printf("堆積的陣列表示:"); + printArray(arr, size); + printf("堆積的樹狀表示:\n"); + root = arrayToTree(arr, size); + printTree(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // PRINT_UTIL_H diff --git a/zh-hant/codes/c/utils/tree_node.h b/zh-hant/codes/c/utils/tree_node.h new file mode 100644 index 0000000000..8d47a07f86 --- /dev/null +++ b/zh-hant/codes/c/utils/tree_node.h @@ -0,0 +1,107 @@ +/** + * File: tree_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef TREE_NODE_H +#define TREE_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define MAX_NODE_SIZE 5000 + +/* 二元樹節點結構體 */ +typedef struct TreeNode { + int val; // 節點值 + int height; // 節點高度 + struct TreeNode *left; // 左子節點指標 + struct TreeNode *right; // 右子節點指標 +} TreeNode; + +/* 建構子 */ +TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; +} + +// 序列化編碼規則請參考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二元樹的陣列表示: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二元樹的鏈結串列表示: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* 將串列反序列化為二元樹:遞迴 */ +TreeNode *arrayToTreeDFS(int *arr, int size, int i) { + if (i < 0 || i >= size || arr[i] == INT_MAX) { + return NULL; + } + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = arr[i]; + root->left = arrayToTreeDFS(arr, size, 2 * i + 1); + root->right = arrayToTreeDFS(arr, size, 2 * i + 2); + return root; +} + +/* 將串列反序列化為二元樹 */ +TreeNode *arrayToTree(int *arr, int size) { + return arrayToTreeDFS(arr, size, 0); +} + +/* 將二元樹序列化為串列:遞迴 */ +void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { + if (root == NULL) { + return; + } + while (i >= *size) { + res = realloc(res, (*size + 1) * sizeof(int)); + res[*size] = INT_MAX; + (*size)++; + } + res[i] = root->val; + treeToArrayDFS(root->left, 2 * i + 1, res, size); + treeToArrayDFS(root->right, 2 * i + 2, res, size); +} + +/* 將二元樹序列化為串列 */ +int *treeToArray(TreeNode *root, int *size) { + *size = 0; + int *res = NULL; + treeToArrayDFS(root, 0, res, size); + return res; +} + +/* 釋放二元樹記憶體 */ +void freeMemoryTree(TreeNode *root) { + if (root == NULL) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + free(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_NODE_H diff --git a/zh-hant/codes/c/utils/uthash.h b/zh-hant/codes/c/utils/uthash.h new file mode 100644 index 0000000000..68693bf396 --- /dev/null +++ b/zh-hant/codes/c/utils/uthash.h @@ -0,0 +1,1140 @@ +/* +Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/zh-hant/codes/c/utils/vector.h b/zh-hant/codes/c/utils/vector.h new file mode 100644 index 0000000000..c6eccbd2f3 --- /dev/null +++ b/zh-hant/codes/c/utils/vector.h @@ -0,0 +1,259 @@ +/** + * File: vector.h + * Created Time: 2023-07-13 + * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 定義向量型別 */ +typedef struct vector { + int size; // 當前向量的大小 + int capacity; // 當前向量的容量 + int depth; // 當前向量的深度 + void **data; // 指向資料的指標陣列 +} vector; + +/* 構造向量 */ +vector *newVector() { + vector *v = malloc(sizeof(vector)); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + return v; +} + +/* 構造向量,指定大小、元素預設值 */ +vector *_newVector(int size, void *elem, int elemSize) { + vector *v = malloc(sizeof(vector)); + v->size = size; + v->capacity = size; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + for (int i = 0; i < size; i++) { + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[i] = tmp; + } + return v; +} + +/* 析構向量 */ +void delVector(vector *v) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + for (int i = 0; i < v->size; i++) { + free(v->data[i]); + } + free(v); + } else { + for (int i = 0; i < v->size; i++) { + delVector(v->data[i]); + } + v->depth--; + } + } +} + +/* 新增元素(複製方式)到向量尾部 */ +void vectorPushback(vector *v, void *elem, int elemSize) { + if (v->size == v->capacity) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[v->size++] = tmp; +} + +/* 從向量尾部彈出元素 */ +void vectorPopback(vector *v) { + if (v->size != 0) { + free(v->data[v->size - 1]); + v->size--; + } +} + +/* 清空向量 */ +void vectorClear(vector *v) { + delVector(v); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); +} + +/* 獲取向量的大小 */ +int vectorSize(vector *v) { + return v->size; +} + +/* 獲取向量的尾元素 */ +void *vectorBack(vector *v) { + int n = v->size; + return n > 0 ? v->data[n - 1] : NULL; +} + +/* 獲取向量的頭元素 */ +void *vectorFront(vector *v) { + return v->size > 0 ? v->data[0] : NULL; +} + +/* 獲取向量下標 pos 的元素 */ +void *vectorAt(vector *v, int pos) { + if (pos < 0 || pos >= v->size) { + printf("vectorAt: out of range\n"); + return NULL; + } + return v->data[pos]; +} + +/* 設定向量下標 pos 的元素 */ +void vectorSet(vector *v, int pos, void *elem, int elemSize) { + if (pos < 0 || pos >= v->size) { + printf("vectorSet: out of range\n"); + return; + } + free(v->data[pos]); + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; +} + +/* 向量擴容 */ +void vectorExpand(vector *v) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* 向量縮容 */ +void vectorShrink(vector *v) { + v->capacity /= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* 在向量下標 pos 處插入元素 */ +void vectorInsert(vector *v, int pos, void *elem, int elemSize) { + if (v->size == v->capacity) { + vectorExpand(v); + } + for (int j = v->size; j > pos; j--) { + v->data[j] = v->data[j - 1]; + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; + v->size++; +} + +/* 刪除向量下標 pos 處的元素 */ +void vectorErase(vector *v, int pos) { + if (v->size != 0) { + free(v->data[pos]); + for (int j = pos; j < v->size - 1; j++) { + v->data[j] = v->data[j + 1]; + } + v->size--; + } +} + +/* 向量交換元素 */ +void vectorSwap(vector *v, int i, int j) { + void *tmp = v->data[i]; + v->data[i] = v->data[j]; + v->data[j] = tmp; +} + +/* 向量是否為空 */ +bool vectorEmpty(vector *v) { + return v->size == 0; +} + +/* 向量是否已滿 */ +bool vectorFull(vector *v) { + return v->size == v->capacity; +} + +/* 向量是否相等 */ +bool vectorEqual(vector *v1, vector *v2) { + if (v1->size != v2->size) { + printf("size not equal\n"); + return false; + } + for (int i = 0; i < v1->size; i++) { + void *a = v1->data[i]; + void *b = v2->data[i]; + if (memcmp(a, b, sizeof(a)) != 0) { + printf("data %d not equal\n", i); + return false; + } + } + return true; +} + +/* 對向量內部進行排序 */ +void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { + qsort(v->data, v->size, sizeof(void *), cmp); +} + +/* 列印函式, 需傳遞一個列印變數的函式進來 */ +/* 當前僅支持列印深度為 1 的 vector */ +void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + if(v->size == 0) { + printf("\n"); + return; + } + for (int i = 0; i < v->size; i++) { + if (i == 0) { + printf("["); + } else if (i == v->size - 1) { + printFunc(v, v->data[i]); + printf("]\r\n"); + break; + } + printFunc(v, v->data[i]); + printf(","); + } + } else { + for (int i = 0; i < v->size; i++) { + printVector(v->data[i], printFunc); + } + v->depth--; + } + } +} + +/* 當前僅支持列印深度為 2 的 vector */ +void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { + printf("[\n"); + for (int i = 0; i < vv->size; i++) { + vector *v = (vector *)vv->data[i]; + printf(" ["); + for (int j = 0; j < v->size; j++) { + printFunc(v, v->data[j]); + if (j != v->size - 1) + printf(","); + } + printf("],"); + printf("\n"); + } + printf("]\n"); +} + +#ifdef __cplusplus +} +#endif + +#endif // VECTOR_H diff --git a/zh-hant/codes/c/utils/vertex.h b/zh-hant/codes/c/utils/vertex.h new file mode 100644 index 0000000000..c277df66de --- /dev/null +++ b/zh-hant/codes/c/utils/vertex.h @@ -0,0 +1,49 @@ +/** + * File: vertex.h + * Created Time: 2023-10-28 + * Author: krahets (krahets@163.com) + */ + +#ifndef VERTEX_H +#define VERTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 頂點結構體 */ +typedef struct { + int val; +} Vertex; + +/* 建構子,初始化一個新節點 */ +Vertex *newVertex(int val) { + Vertex *vet; + vet = (Vertex *)malloc(sizeof(Vertex)); + vet->val = val; + return vet; +} + +/* 將值陣列轉換為頂點陣列 */ +Vertex **valsToVets(int *vals, int size) { + Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); + for (int i = 0; i < size; ++i) { + vertices[i] = newVertex(vals[i]); + } + return vertices; +} + +/* 將頂點陣列轉換為值陣列 */ +int *vetsToVals(Vertex **vertices, int size) { + int *vals = (int *)malloc(size * sizeof(int)); + for (int i = 0; i < size; ++i) { + vals[i] = vertices[i]->val; + } + return vals; +} + +#ifdef __cplusplus +} +#endif + +#endif // VERTEX_H diff --git a/zh-hant/codes/cpp/.gitignore b/zh-hant/codes/cpp/.gitignore new file mode 100644 index 0000000000..dc1ffacf49 --- /dev/null +++ b/zh-hant/codes/cpp/.gitignore @@ -0,0 +1,10 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ + +*.dSYM/ + +build/ diff --git a/zh-hant/codes/cpp/CMakeLists.txt b/zh-hant/codes/cpp/CMakeLists.txt new file mode 100644 index 0000000000..1e80bc4d78 --- /dev/null +++ b/zh-hant/codes/cpp/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt b/zh-hant/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 0000000000..2e933e0163 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array array.cpp) +add_executable(linked_list linked_list.cpp) +add_executable(list list.cpp) +add_executable(my_list my_list.cpp) diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/array.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/array.cpp new file mode 100644 index 0000000000..89d3f20b5f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -0,0 +1,113 @@ +/** + * File: array.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 隨機訪問元素 */ +int randomAccess(int *nums, int size) { + // 在區間 [0, size) 中隨機抽取一個數字 + int randomIndex = rand() % size; + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 擴展陣列長度 */ +int *extend(int *nums, int size, int enlarge) { + // 初始化一個擴展長度後的陣列 + int *res = new int[size + enlarge]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // 釋放記憶體 + delete[] nums; + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 num */ +void insert(int *nums, int size, int num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +void remove(int *nums, int size, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +void traverse(int *nums, int size) { + int count = 0; + // 透過索引走訪陣列 + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* 在陣列中查詢指定元素 */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化陣列 */ + int size = 5; + int *arr = new int[size]; + cout << "陣列 arr = "; + printArray(arr, size); + + int *nums = new int[size]{1, 3, 2, 5, 4}; + cout << "陣列 nums = "; + printArray(nums, size); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums, size); + cout << "在 nums 中獲取隨機元素 " << randomNum << endl; + + /* 長度擴展 */ + int enlarge = 3; + nums = extend(nums, size, enlarge); + size += enlarge; + cout << "將陣列長度擴展至 8 ,得到 nums = "; + printArray(nums, size); + + /* 插入元素 */ + insert(nums, size, 6, 3); + cout << "在索引 3 處插入數字 6 ,得到 nums = "; + printArray(nums, size); + + /* 刪除元素 */ + remove(nums, size, 2); + cout << "刪除索引 2 處的元素,得到 nums = "; + printArray(nums, size); + + /* 走訪陣列 */ + traverse(nums, size); + + /* 查詢元素 */ + int index = find(nums, size, 3); + cout << "在 nums 中查詢元素 3 ,得到索引 = " << index << endl; + + // 釋放記憶體 + delete[] arr; + delete[] nums; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp new file mode 100644 index 0000000000..ede559c189 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -0,0 +1,89 @@ +/** + * File: linked_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +void remove(ListNode *n0) { + if (n0->next == nullptr) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 釋放記憶體 + delete P; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == nullptr) + return nullptr; + head = head->next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +int find(ListNode *head, int target) { + int index = 0; + while (head != nullptr) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + ListNode *n0 = new ListNode(1); + ListNode *n1 = new ListNode(3); + ListNode *n2 = new ListNode(2); + ListNode *n3 = new ListNode(5); + ListNode *n4 = new ListNode(4); + // 構建節點之間的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + cout << "初始化的鏈結串列為" << endl; + printLinkedList(n0); + + /* 插入節點 */ + insert(n0, new ListNode(0)); + cout << "插入節點後的鏈結串列為" << endl; + printLinkedList(n0); + + /* 刪除節點 */ + remove(n0); + cout << "刪除節點後的鏈結串列為" << endl; + printLinkedList(n0); + + /* 訪問節點 */ + ListNode *node = access(n0, 3); + cout << "鏈結串列中索引 3 處的節點的值 = " << node->val << endl; + + /* 查詢節點 */ + int index = find(n0, 2); + cout << "鏈結串列中值為 2 的節點的索引 = " << index << endl; + + // 釋放記憶體 + freeMemoryLinkedList(n0); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/list.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/list.cpp new file mode 100644 index 0000000000..efd5d0444c --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -0,0 +1,72 @@ +/** + * File: list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化串列 */ + vector nums = {1, 3, 2, 5, 4}; + cout << "串列 nums = "; + printVector(nums); + + /* 訪問元素 */ + int num = nums[1]; + cout << "訪問索引 1 處的元素,得到 num = " << num << endl; + + /* 更新元素 */ + nums[1] = 0; + cout << "將索引 1 處的元素更新為 0 ,得到 nums = "; + printVector(nums); + + /* 清空串列 */ + nums.clear(); + cout << "清空串列後 nums = "; + printVector(nums); + + /* 在尾部新增元素 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + cout << "新增元素後 nums = "; + printVector(nums); + + /* 在中間插入元素 */ + nums.insert(nums.begin() + 3, 6); + cout << "在索引 3 處插入數字 6 ,得到 nums = "; + printVector(nums); + + /* 刪除元素 */ + nums.erase(nums.begin() + 3); + cout << "刪除索引 3 處的元素,得到 nums = "; + printVector(nums); + + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + /* 直接走訪串列元素 */ + count = 0; + for (int x : nums) { + count += x; + } + + /* 拼接兩個串列 */ + vector nums1 = {6, 8, 7, 10, 9}; + nums.insert(nums.end(), nums1.begin(), nums1.end()); + cout << "將串列 nums1 拼接到 nums 之後,得到 nums = "; + printVector(nums); + + /* 排序串列 */ + sort(nums.begin(), nums.end()); + cout << "排序串列後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/my_list.cpp new file mode 100644 index 0000000000..6459f0129b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -0,0 +1,171 @@ +/** + * File: my_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 串列類別 */ +class MyList { + private: + int *arr; // 陣列(儲存串列元素) + int arrCapacity = 10; // 串列容量 + int arrSize = 0; // 串列長度(當前元素數量) + int extendRatio = 2; // 每次串列擴容的倍數 + + public: + /* 建構子 */ + MyList() { + arr = new int[arrCapacity]; + } + + /* 析構方法 */ + ~MyList() { + delete[] arr; + } + + /* 獲取串列長度(當前元素數量)*/ + int size() { + return arrSize; + } + + /* 獲取串列容量 */ + int capacity() { + return arrCapacity; + } + + /* 訪問元素 */ + int get(int index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + return arr[index]; + } + + /* 更新元素 */ + void set(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + arr[index] = num; + } + + /* 在尾部新增元素 */ + void add(int num) { + // 元素數量超出容量時,觸發擴容機制 + if (size() == capacity()) + extendCapacity(); + arr[size()] = num; + // 更新元素數量 + arrSize++; + } + + /* 在中間插入元素 */ + void insert(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (size() == capacity()) + extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (int j = size() - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 更新元素數量 + arrSize++; + } + + /* 刪除元素 */ + int remove(int index) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + int num = arr[index]; + // 將索引 index 之後的元素都向前移動一位 + for (int j = index; j < size() - 1; j++) { + arr[j] = arr[j + 1]; + } + // 更新元素數量 + arrSize--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + void extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列 + int newCapacity = capacity() * extendRatio; + int *tmp = arr; + arr = new int[newCapacity]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < size(); i++) { + arr[i] = tmp[i]; + } + // 釋放記憶體 + delete[] tmp; + arrCapacity = newCapacity; + } + + /* 將串列轉換為 Vector 用於列印 */ + vector toVector() { + // 僅轉換有效長度範圍內的串列元素 + vector vec(size()); + for (int i = 0; i < size(); i++) { + vec[i] = arr[i]; + } + return vec; + } +}; + +/* Driver Code */ +int main() { + /* 初始化串列 */ + MyList *nums = new MyList(); + /* 在尾部新增元素 */ + nums->add(1); + nums->add(3); + nums->add(2); + nums->add(5); + nums->add(4); + cout << "串列 nums = "; + vector vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,長度 = " << nums->size() << endl; + + /* 在中間插入元素 */ + nums->insert(3, 6); + cout << "在索引 3 處插入數字 6 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 刪除元素 */ + nums->remove(3); + cout << "刪除索引 3 處的元素,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 訪問元素 */ + int num = nums->get(1); + cout << "訪問索引 1 處的元素,得到 num = " << num << endl; + + /* 更新元素 */ + nums->set(1, 0); + cout << "將索引 1 處的元素更新為 0 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums->add(i); + } + cout << "擴容後的串列 nums = "; + vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,長度 = " << nums->size() << endl; + + // 釋放記憶體 + delete nums; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/CMakeLists.txt b/zh-hant/codes/cpp/chapter_backtracking/CMakeLists.txt new file mode 100644 index 0000000000..6c271e330b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) +add_executable(permutations_i permutations_i.cpp) +add_executable(permutations_ii permutations_ii.cpp) +add_executable(n_queens n_queens.cpp) +add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) +add_executable(subset_sum_i subset_sum_i.cpp) +add_executable(subset_sum_ii subset_sum_ii.cpp) diff --git a/zh-hant/codes/cpp/chapter_backtracking/n_queens.cpp b/zh-hant/codes/cpp/chapter_backtracking/n_queens.cpp new file mode 100644 index 0000000000..7e151599b4 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/n_queens.cpp @@ -0,0 +1,65 @@ +/** + * File: n_queens.cpp + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:n 皇后 */ +void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, + vector &diags1, vector &diags2) { + // 當放置完所有行時,記錄解 + if (row == n) { + res.push_back(state); + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +vector>> nQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + vector> state(n, vector(n, "#")); + vector cols(n, false); // 記錄列是否有皇后 + vector diags1(2 * n - 1, false); // 記錄主對角線上是否有皇后 + vector diags2(2 * n - 1, false); // 記錄次對角線上是否有皇后 + vector>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + vector>> res = nQueens(n); + + cout << "輸入棋盤長寬為 " << n << endl; + cout << "皇后放置方案共有 " << res.size() << " 種" << endl; + for (const vector> &state : res) { + cout << "--------------------" << endl; + for (const vector &row : state) { + printVector(row); + } + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/permutations_i.cpp b/zh-hant/codes/cpp/chapter_backtracking/permutations_i.cpp new file mode 100644 index 0000000000..36642f37f9 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/permutations_i.cpp @@ -0,0 +1,54 @@ +/** + * File: permutations_i.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:全排列 I */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push_back(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 I */ +vector> permutationsI(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 2, 3}; + + vector> res = permutationsI(nums); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/permutations_ii.cpp b/zh-hant/codes/cpp/chapter_backtracking/permutations_ii.cpp new file mode 100644 index 0000000000..d033f98b30 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/permutations_ii.cpp @@ -0,0 +1,56 @@ +/** + * File: permutations_ii.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:全排列 II */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 走訪所有選擇 + unordered_set duplicated; + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && duplicated.find(choice) == duplicated.end()) { + // 嘗試:做出選擇,更新狀態 + duplicated.emplace(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push_back(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 II */ +vector> permutationsII(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 1, 2}; + + vector> res = permutationsII(nums); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp new file mode 100644 index 0000000000..6b50a2672f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -0,0 +1,39 @@ +/** + * File: preorder_traversal_i_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector res; + +/* 前序走訪:例題一 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // 記錄解 + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 前序走訪 + preOrder(root); + + cout << "\n輸出所有值為 7 的節點" << endl; + vector vals; + for (TreeNode *node : res) { + vals.push_back(node->val); + } + printVector(vals); +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp new file mode 100644 index 0000000000..7121b5df87 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -0,0 +1,46 @@ +/** + * File: preorder_traversal_ii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序走訪:例題二 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // 嘗試 + path.push_back(root); + if (root->val == 7) { + // 記錄解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 前序走訪 + preOrder(root); + + cout << "\n輸出所有根節點到節點 7 的路徑" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp new file mode 100644 index 0000000000..0a0a535bd5 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序走訪:例題三 */ +void preOrder(TreeNode *root) { + // 剪枝 + if (root == nullptr || root->val == 3) { + return; + } + // 嘗試 + path.push_back(root); + if (root->val == 7) { + // 記錄解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 前序走訪 + preOrder(root); + + cout << "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp new file mode 100644 index 0000000000..23af8bee76 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 判斷當前狀態是否為解 */ +bool isSolution(vector &state) { + return !state.empty() && state.back()->val == 7; +} + +/* 記錄解 */ +void recordSolution(vector &state, vector> &res) { + res.push_back(state); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +bool isValid(vector &state, TreeNode *choice) { + return choice != nullptr && choice->val != 3; +} + +/* 更新狀態 */ +void makeChoice(vector &state, TreeNode *choice) { + state.push_back(choice); +} + +/* 恢復狀態 */ +void undoChoice(vector &state, TreeNode *choice) { + state.pop_back(); +} + +/* 回溯演算法:例題三 */ +void backtrack(vector &state, vector &choices, vector> &res) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (TreeNode *choice : choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + vector nextChoices{choice->left, choice->right}; + backtrack(state, nextChoices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 回溯演算法 + vector state; + vector choices = {root}; + vector> res; + backtrack(state, choices, res); + + cout << "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i.cpp b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i.cpp new file mode 100644 index 0000000000..f705801f7e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i.cpp @@ -0,0 +1,57 @@ +/** + * File: subset_sum_i.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:子集和 I */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.push_back(state); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push_back(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop_back(); + } +} + +/* 求解子集和 I */ +vector> subsetSumI(vector &nums, int target) { + vector state; // 狀態(子集) + sort(nums.begin(), nums.end()); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + vector> res; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumI(nums, target); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等於 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp new file mode 100644 index 0000000000..b7ddf663ea --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:子集和 I */ +void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.push_back(state); + return; + } + // 走訪所有選擇 + for (size_t i = 0; i < choices.size(); i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push_back(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop_back(); + } +} + +/* 求解子集和 I(包含重複子集) */ +vector> subsetSumINaive(vector &nums, int target) { + vector state; // 狀態(子集) + int total = 0; // 子集和 + vector> res; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumINaive(nums, target); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等於 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/subset_sum_ii.cpp b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_ii.cpp new file mode 100644 index 0000000000..36041c2ccd --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_ii.cpp @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:子集和 II */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.push_back(state); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push_back(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop_back(); + } +} + +/* 求解子集和 II */ +vector> subsetSumII(vector &nums, int target) { + vector state; // 狀態(子集) + sort(nums.begin(), nums.end()); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + vector> res; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {4, 4, 5}; + int target = 9; + + vector> res = subsetSumII(nums, target); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等於 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/CMakeLists.txt b/zh-hant/codes/cpp/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 0000000000..ea2845b751 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.cpp) +add_executable(recursion recursion.cpp) +add_executable(space_complexity space_complexity.cpp) +add_executable(time_complexity time_complexity.cpp) +add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/iteration.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/iteration.cpp new file mode 100644 index 0000000000..f9a5992fdf --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/iteration.cpp @@ -0,0 +1,76 @@ +/** + * File: iteration.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* for 迴圈 */ +int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + res += i; + } + return res; +} + +/* while 迴圈 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +string nestedForLoop(int n) { + ostringstream res; + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; ++j) { + res << "(" << i << ", " << j << "), "; + } + } + return res.str(); +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + cout << "\nfor 迴圈的求和結果 res = " << res << endl; + + res = whileLoop(n); + cout << "\nwhile 迴圈的求和結果 res = " << res << endl; + + res = whileLoopII(n); + cout << "\nwhile 迴圈(兩次更新)求和結果 res = " << res << endl; + + string resStr = nestedForLoop(n); + cout << "\n雙層 for 迴圈的走訪結果 " << resStr << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/recursion.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/recursion.cpp new file mode 100644 index 0000000000..b03cf85775 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/recursion.cpp @@ -0,0 +1,78 @@ +/** + * File: recursion.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 遞迴 */ +int recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +int forLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + stack stack; + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (!stack.empty()) { + // 透過“出堆疊操作”模擬“迴” + res += stack.top(); + stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +int tailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +int fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + cout << "\n遞迴函式的求和結果 res = " << res << endl; + + res = forLoopRecur(n); + cout << "\n使用迭代模擬遞迴求和結果 res = " << res << endl; + + res = tailRecur(n, 0); + cout << "\n尾遞迴函式的求和結果 res = " << res << endl; + + res = fib(n); + cout << "\n費波那契數列的第 " << n << " 項為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/space_complexity.cpp new file mode 100644 index 0000000000..9720b5f8b5 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -0,0 +1,107 @@ +/** + * File: space_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 函式 */ +int func() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + const int a = 0; + int b = 0; + vector nums(10000); + ListNode node(0); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + func(); + } +} + +/* 線性階 */ +void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + vector nums(n); + // 長度為 n 的串列佔用 O(n) 空間 + vector nodes; + for (int i = 0; i < n; i++) { + nodes.push_back(ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + unordered_map map; + for (int i = 0; i < n; i++) { + map[i] = to_string(i); + } +} + +/* 線性階(遞迴實現) */ +void linearRecur(int n) { + cout << "遞迴 n = " << n << endl; + if (n == 1) + return; + linearRecur(n - 1); +} + +/* 平方階 */ +void quadratic(int n) { + // 二維串列佔用 O(n^2) 空間 + vector> numMatrix; + for (int i = 0; i < n; i++) { + vector tmp; + for (int j = 0; j < n; j++) { + tmp.push_back(0); + } + numMatrix.push_back(tmp); + } +} + +/* 平方階(遞迴實現) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + vector nums(n); + cout << "遞迴 n = " << n << " 中的 nums 長度 = " << nums.size() << endl; + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +TreeNode *buildTree(int n) { + if (n == 0) + return nullptr; + TreeNode *root = new TreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode *root = buildTree(n); + printTree(root); + + // 釋放記憶體 + freeMemoryTree(root); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/time_complexity.cpp new file mode 100644 index 0000000000..c8bf484c1a --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -0,0 +1,168 @@ +/** + * File: time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 常數階 */ +int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; +} + +/* 線性階 */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; +} + +/* 線性階(走訪陣列) */ +int arrayTraversal(vector &nums) { + int count = 0; + // 迴圈次數與陣列長度成正比 + for (int num : nums) { + count++; + } + return count; +} + +/* 平方階 */ +int quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +int bubbleSort(vector &nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +int exponential(int n) { + int count = 0, base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; +} + +/* 線性對數階 */ +int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // 從 1 個分裂出 n 個 + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + cout << "輸入資料大小 n = " << n << endl; + + int count = constant(n); + cout << "常數階的操作數量 = " << count << endl; + + count = linear(n); + cout << "線性階的操作數量 = " << count << endl; + vector arr(n); + count = arrayTraversal(arr); + cout << "線性階(走訪陣列)的操作數量 = " << count << endl; + + count = quadratic(n); + cout << "平方階的操作數量 = " << count << endl; + vector nums(n); + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + cout << "平方階(泡沫排序)的操作數量 = " << count << endl; + + count = exponential(n); + cout << "指數階(迴圈實現)的操作數量 = " << count << endl; + count = expRecur(n); + cout << "指數階(遞迴實現)的操作數量 = " << count << endl; + + count = logarithmic(n); + cout << "對數階(迴圈實現)的操作數量 = " << count << endl; + count = logRecur(n); + cout << "對數階(遞迴實現)的操作數量 = " << count << endl; + + count = linearLogRecur(n); + cout << "線性對數階(遞迴實現)的操作數量 = " << count << endl; + + count = factorialRecur(n); + cout << "階乘階(遞迴實現)的操作數量 = " << count << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp new file mode 100644 index 0000000000..0d1571b9eb --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +vector randomNumbers(int n) { + vector nums(n); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 使用系統時間生成隨機種子 + unsigned seed = chrono::system_clock::now().time_since_epoch().count(); + // 隨機打亂陣列元素 + shuffle(nums.begin(), nums.end(), default_random_engine(seed)); + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +int findOne(vector &nums) { + for (int i = 0; i < nums.size(); i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + for (int i = 0; i < 1000; i++) { + int n = 100; + vector nums = randomNumbers(n); + int index = findOne(nums); + cout << "\n陣列 [ 1, 2, ..., n ] 被打亂後 = "; + printVector(nums); + cout << "數字 1 的索引為 " << index << endl; + } + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt b/zh-hant/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 0000000000..38dfff7107 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.cpp) +add_executable(build_tree build_tree.cpp) +add_executable(hanota hanota.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp b/zh-hant/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp new file mode 100644 index 0000000000..5611a38155 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp @@ -0,0 +1,46 @@ +/** + * File: binary_search_recur.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋:問題 f(i, j) */ +int dfs(vector &nums, int target, int i, int j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +int binarySearch(vector &nums, int target) { + int n = nums.size(); + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target); + cout << "目標元素 6 的索引 = " << index << endl; + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/build_tree.cpp b/zh-hant/codes/cpp/chapter_divide_and_conquer/build_tree.cpp new file mode 100644 index 0000000000..5398c86415 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/build_tree.cpp @@ -0,0 +1,51 @@ +/** + * File: build_tree.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 構建二元樹:分治 */ +TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { + // 子樹區間為空時終止 + if (r - l < 0) + return NULL; + // 初始化根節點 + TreeNode *root = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]; + // 子問題:構建左子樹 + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +TreeNode *buildTree(vector &preorder, vector &inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + unordered_map inorderMap; + for (int i = 0; i < inorder.size(); i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1); + return root; +} + +/* Driver Code */ +int main() { + vector preorder = {3, 9, 2, 1, 7}; + vector inorder = {9, 3, 1, 2, 7}; + cout << "前序走訪 = "; + printVector(preorder); + cout << "中序走訪 = "; + printVector(inorder); + + TreeNode *root = buildTree(preorder, inorder); + cout << "構建的二元樹為:\n"; + printTree(root); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/hanota.cpp b/zh-hant/codes/cpp/chapter_divide_and_conquer/hanota.cpp new file mode 100644 index 0000000000..258b3d3a38 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/hanota.cpp @@ -0,0 +1,66 @@ +/** + * File: hanota.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 移動一個圓盤 */ +void move(vector &src, vector &tar) { + // 從 src 頂部拿出一個圓盤 + int pan = src.back(); + src.pop_back(); + // 將圓盤放入 tar 頂部 + tar.push_back(pan); +} + +/* 求解河內塔問題 f(i) */ +void dfs(int i, vector &src, vector &buf, vector &tar) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解河內塔問題 */ +void solveHanota(vector &A, vector &B, vector &C) { + int n = A.size(); + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +int main() { + // 串列尾部是柱子頂部 + vector A = {5, 4, 3, 2, 1}; + vector B = {}; + vector C = {}; + + cout << "初始狀態下:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + solveHanota(A, B, C); + + cout << "圓盤移動完成後:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/CMakeLists.txt b/zh-hant/codes/cpp/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 0000000000..ed185458a4 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) +add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) +add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) +add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) +add_executable(min_path_sum min_path_sum.cpp) +add_executable(unbounded_knapsack unbounded_knapsack.cpp) +add_executable(coin_change coin_change.cpp) +add_executable(coin_change_ii coin_change_ii.cpp) +add_executable(edit_distance edit_distance.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp new file mode 100644 index 0000000000..8eb6e0921c --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -0,0 +1,43 @@ + +/** + * File: climbing_stairs_backtrack.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯 */ +void backtrack(vector &choices, int state, int n, vector &res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res[0]++; + // 走訪所有選擇 + for (auto &choice : choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +int climbingStairsBacktrack(int n) { + vector choices = {1, 2}; // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + vector res = {0}; // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp new file mode 100644 index 0000000000..2ec90a71d1 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -0,0 +1,37 @@ +/** + * File: climbing_stairs_constraint_dp.cpp + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 帶約束爬樓梯:動態規劃 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + vector> dp(n + 1, vector(3, 0)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp new file mode 100644 index 0000000000..a4b1a85b69 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 搜尋 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp new file mode 100644 index 0000000000..7779e39b34 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_dfs_mem.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 記憶化搜尋 */ +int dfs(int i, vector &mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +int climbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + vector mem(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp new file mode 100644 index 0000000000..95d46510af --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬樓梯:動態規劃 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + vector dp(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + res = climbingStairsDPComp(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change.cpp new file mode 100644 index 0000000000..75d39c8142 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change.cpp @@ -0,0 +1,70 @@ +/** + * File: coin_change.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零錢兌換:動態規劃 */ +int coinChangeDP(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +int coinChangeDPComp(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector dp(amt + 1, MAX); + dp[0] = 0; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt); + cout << "湊到目標金額所需的最少硬幣數量為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt); + cout << "湊到目標金額所需的最少硬幣數量為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp new file mode 100644 index 0000000000..7fc199efec --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp @@ -0,0 +1,68 @@ +/** + * File: coin_change_ii.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零錢兌換 II:動態規劃 */ +int coinChangeIIDP(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +int coinChangeIIDPComp(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector dp(amt + 1, 0); + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt); + cout << "湊出目標金額的硬幣組合數量為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt); + cout << "湊出目標金額的硬幣組合數量為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/edit_distance.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/edit_distance.cpp new file mode 100644 index 0000000000..e6dab8e134 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/edit_distance.cpp @@ -0,0 +1,136 @@ +/** + * File: edit_distance.cpp + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 編輯距離:暴力搜尋 */ +int editDistanceDFS(string s, string t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) + return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) + return i; + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return min(min(insert, del), replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) + return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) + return i; + // 若已有記錄,則直接返回之 + if (mem[i][j] != -1) + return mem[i][j]; + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = min(min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +int editDistanceDP(string s, string t) { + int n = s.length(), m = t.length(); + vector> dp(n + 1, vector(m + 1, 0)); + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +int editDistanceDPComp(string s, string t) { + int n = s.length(), m = t.length(); + vector dp(m + 1, 0); + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +int main() { + string s = "bag"; + string t = "pack"; + int n = s.length(), m = t.length(); + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + // 記憶化搜尋 + vector> mem(n + 1, vector(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + // 動態規劃 + res = editDistanceDP(s, t); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/knapsack.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/knapsack.cpp new file mode 100644 index 0000000000..6a2fa99436 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/knapsack.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +using namespace std; + +/* 0-1 背包:暴力搜尋 */ +int knapsackDFS(vector &wgt, vector &val, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +int knapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +int knapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + int n = wgt.size(); + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 記憶化搜尋 + vector> mem(n + 1, vector(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 動態規劃 + res = knapsackDP(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp new file mode 100644 index 0000000000..8b0edf2cac --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬樓梯最小代價:動態規劃 */ +int minCostClimbingStairsDP(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + vector dp(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +int minCostClimbingStairsDPComp(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + cout << "輸入樓梯的代價串列為 "; + printVector(cost); + + int res = minCostClimbingStairsDP(cost); + cout << "爬完樓梯的最低代價為 " << res << endl; + + res = minCostClimbingStairsDPComp(cost); + cout << "爬完樓梯的最低代價為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp new file mode 100644 index 0000000000..28b8074d6d --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp @@ -0,0 +1,116 @@ +/** + * File: min_path_sum.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最小路徑和:暴力搜尋 */ +int minPathSumDFS(vector> &grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小路徑和:記憶化搜尋 */ +int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +int minPathSumDP(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector> dp(n, vector(m)); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +int minPathSumDPComp(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector dp(m); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +int main() { + vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = grid.size(), m = grid[0].size(); + + // 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + // 記憶化搜尋 + vector> mem(n, vector(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + // 動態規劃 + res = minPathSumDP(grid); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp new file mode 100644 index 0000000000..fae6d7d67b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -0,0 +1,64 @@ +/** + * File: unbounded_knapsack.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 完全背包:動態規劃 */ +int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver code */ +int main() { + vector wgt = {1, 2, 3}; + vector val = {5, 11, 15}; + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/CMakeLists.txt b/zh-hant/codes/cpp/chapter_graph/CMakeLists.txt new file mode 100644 index 0000000000..4a56ce35ba --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(graph_bfs graph_bfs.cpp) +add_executable(graph_dfs graph_dfs.cpp) +# add_executable(graph_adjacency_list graph_adjacency_list.cpp) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) +add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) diff --git a/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list.cpp new file mode 100644 index 0000000000..2148b4a6e8 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -0,0 +1,90 @@ +/** + * File: graph_adjacency_list.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + public: + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + unordered_map> adjList; + + /* 在 vector 中刪除指定節點 */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* 建構子 */ + GraphAdjList(const vector> &edges) { + // 新增所有頂點和邊 + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int size() { + return adjList.size(); + } + + /* 新增邊 */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在頂點"); + // 新增邊 vet1 - vet2 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* 刪除邊 */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在頂點"); + // 刪除邊 vet1 - vet2 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* 新增頂點 */ + void addVertex(Vertex *vet) { + if (adjList.count(vet)) + return; + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = vector(); + } + + /* 刪除頂點 */ + void removeVertex(Vertex *vet) { + if (!adjList.count(vet)) + throw invalid_argument("不存在頂點"); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.erase(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* 列印鄰接表 */ + void print() { + cout << "鄰接表 =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } +}; + +// 測試樣例請見 graph_adjacency_list_test.cpp diff --git a/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 0000000000..8e3bec669a --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + vector v = valsToVets(vector{1, 3, 2, 5, 4}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + GraphAdjList graph(edges); + cout << "\n初始化後,圖為" << endl; + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + cout << "\n新增邊 1-2 後,圖為" << endl; + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + cout << "\n刪除邊 1-3 後,圖為" << endl; + graph.print(); + + /* 新增頂點 */ + Vertex *v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\n新增頂點 6 後,圖為" << endl; + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]); + cout << "\n刪除頂點 3 後,圖為" << endl; + graph.print(); + + // 釋放記憶體 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp new file mode 100644 index 0000000000..2da942a6ea --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -0,0 +1,127 @@ +/** + * File: graph_adjacency_matrix.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + vector vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + vector> adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + public: + /* 建構子 */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // 新增頂點 + for (int val : vertices) { + addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int size() const { + return vertices.size(); + } + + /* 新增頂點 */ + void addVertex(int val) { + int n = size(); + // 向頂點串列中新增新頂點的值 + vertices.push_back(val); + // 在鄰接矩陣中新增一行 + adjMat.emplace_back(vector(n, 0)); + // 在鄰接矩陣中新增一列 + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* 刪除頂點 */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("頂點不存在"); + } + // 在頂點串列中移除索引 index 的頂點 + vertices.erase(vertices.begin() + index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.erase(adjMat.begin() + index); + // 在鄰接矩陣中刪除索引 index 的列 + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("頂點不存在"); + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("頂點不存在"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + void print() { + cout << "頂點串列 = "; + printVector(vertices); + cout << "鄰接矩陣 =" << endl; + printVectorMatrix(adjMat); + } +}; + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + vector vertices = {1, 3, 2, 5, 4}; + vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + GraphAdjMat graph(vertices, edges); + cout << "\n初始化後,圖為" << endl; + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2); + cout << "\n新增邊 1-2 後,圖為" << endl; + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1); + cout << "\n刪除邊 1-3 後,圖為" << endl; + graph.print(); + + /* 新增頂點 */ + graph.addVertex(6); + cout << "\n新增頂點 6 後,圖為" << endl; + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1); + cout << "\n刪除頂點 3 後,圖為" << endl; + graph.print(); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/graph_bfs.cpp b/zh-hant/codes/cpp/chapter_graph/graph_bfs.cpp new file mode 100644 index 0000000000..9e4b4c485f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // 頂點走訪序列 + vector res; + // 雜湊集合,用於記錄已被訪問過的頂點 + unordered_set visited = {startVet}; + // 佇列用於實現 BFS + queue que; + que.push(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // 佇列首頂點出隊 + res.push_back(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳過已被訪問的頂點 + que.push(adjVet); // 只入列未訪問的頂點 + visited.emplace(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + vector> edges = {{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]}}; + GraphAdjList graph(edges); + cout << "\n初始化後,圖為\\n"; + graph.print(); + + /* 廣度優先走訪 */ + vector res = graphBFS(graph, v[0]); + cout << "\n廣度優先走訪(BFS)頂點序列為" << endl; + printVector(vetsToVals(res)); + + // 釋放記憶體 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/graph_dfs.cpp b/zh-hant/codes/cpp/chapter_graph/graph_dfs.cpp new file mode 100644 index 0000000000..ea78733b11 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 深度優先走訪輔助函式 */ +void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { + res.push_back(vet); // 記錄訪問頂點 + visited.emplace(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳過已被訪問的頂點 + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // 頂點走訪序列 + vector res; + // 雜湊集合,用於記錄已被訪問過的頂點 + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + GraphAdjList graph(edges); + cout << "\n初始化後,圖為" << endl; + graph.print(); + + /* 深度優先走訪 */ + vector res = graphDFS(graph, v[0]); + cout << "\n深度優先走訪(DFS)頂點序列為" << endl; + printVector(vetsToVals(res)); + + // 釋放記憶體 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/CMakeLists.txt b/zh-hant/codes/cpp/chapter_greedy/CMakeLists.txt new file mode 100644 index 0000000000..91788668d4 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(coin_change_greedy coin_change_greedy.cpp) +add_executable(fractional_knapsack fractional_knapsack.cpp) +add_executable(max_capacity max_capacity.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_greedy/coin_change_greedy.cpp b/zh-hant/codes/cpp/chapter_greedy/coin_change_greedy.cpp new file mode 100644 index 0000000000..78cc54ac62 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/coin_change_greedy.cpp @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零錢兌換:貪婪 */ +int coinChangeGreedy(vector &coins, int amt) { + // 假設 coins 串列有序 + int i = coins.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; +} + +/* Driver Code */ +int main() { + // 貪婪:能夠保證找到全域性最優解 + vector coins = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "湊到 " << amt << " 所需的最少硬幣數量為 " << res << endl; + + // 貪婪:無法保證找到全域性最優解 + coins = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "湊到 " << amt << " 所需的最少硬幣數量為 " << res << endl; + cout << "實際上需要的最少數量為 3 ,即 20 + 20 + 20" << endl; + + // 貪婪:無法保證找到全域性最優解 + coins = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "湊到 " << amt << " 所需的最少硬幣數量為 " << res << endl; + cout << "實際上需要的最少數量為 2 ,即 49 + 49" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/fractional_knapsack.cpp b/zh-hant/codes/cpp/chapter_greedy/fractional_knapsack.cpp new file mode 100644 index 0000000000..579a6b4a21 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/fractional_knapsack.cpp @@ -0,0 +1,56 @@ +/** + * File: fractional_knapsack.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 物品 */ +class Item { + public: + int w; // 物品重量 + int v; // 物品價值 + + Item(int w, int v) : w(w), v(v) { + } +}; + +/* 分數背包:貪婪 */ +double fractionalKnapsack(vector &wgt, vector &val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + vector items; + for (int i = 0; i < wgt.size(); i++) { + items.push_back(Item(wgt[i], val[i])); + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); + // 迴圈貪婪選擇 + double res = 0; + for (auto &item : items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (double)item.v / item.w * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + + // 貪婪演算法 + double res = fractionalKnapsack(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/max_capacity.cpp b/zh-hant/codes/cpp/chapter_greedy/max_capacity.cpp new file mode 100644 index 0000000000..05102fa22a --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/max_capacity.cpp @@ -0,0 +1,39 @@ +/** + * File: max_capacity.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大容量:貪婪 */ +int maxCapacity(vector &ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.size() - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 貪婪演算法 + int res = maxCapacity(ht); + cout << "最大容量為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/max_product_cutting.cpp b/zh-hant/codes/cpp/chapter_greedy/max_product_cutting.cpp new file mode 100644 index 0000000000..65352c7776 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/max_product_cutting.cpp @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大切分乘積:貪婪 */ +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 (int)pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return (int)pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return (int)pow(3, a); +} + +/* Driver Code */ +int main() { + int n = 58; + + // 貪婪演算法 + int res = maxProductCutting(n); + cout << "最大切分乘積為" << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/CMakeLists.txt b/zh-hant/codes/cpp/chapter_hashing/CMakeLists.txt new file mode 100644 index 0000000000..6b583ef556 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(hash_map hash_map.cpp) +add_executable(array_hash_map_test array_hash_map_test.cpp) +add_executable(hash_map_chaining hash_map_chaining.cpp) +add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) +add_executable(simple_hash simple_hash.cpp) +add_executable(built_in_hash built_in_hash.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_hashing/array_hash_map.cpp b/zh-hant/codes/cpp/chapter_hashing/array_hash_map.cpp new file mode 100644 index 0000000000..60c1f9b66e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* 鍵值對 */ +struct Pair { + public: + int key; + string val; + Pair(int key, string val) { + this->key = key; + this->val = val; + } +}; + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private: + vector buckets; + + public: + ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + buckets = vector(100); + } + + ~ArrayHashMap() { + // 釋放記憶體 + for (const auto &bucket : buckets) { + delete bucket; + } + buckets.clear(); + } + + /* 雜湊函式 */ + int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查詢操作 */ + string get(int key) { + int index = hashFunc(key); + Pair *pair = buckets[index]; + if (pair == nullptr) + return ""; + return pair->val; + } + + /* 新增操作 */ + void put(int key, string val) { + Pair *pair = new Pair(key, val); + int index = hashFunc(key); + buckets[index] = pair; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + // 釋放記憶體並置為 nullptr + delete buckets[index]; + buckets[index] = nullptr; + } + + /* 獲取所有鍵值對 */ + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + pairSet.push_back(pair); + } + } + return pairSet; + } + + /* 獲取所有鍵 */ + vector keySet() { + vector keySet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + keySet.push_back(pair->key); + } + } + return keySet; + } + + /* 獲取所有值 */ + vector valueSet() { + vector valueSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + valueSet.push_back(pair->val); + } + } + return valueSet; + } + + /* 列印雜湊表 */ + void print() { + for (Pair *kv : pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + } +}; + +// 測試樣例請見 array_hash_map_test.cpp diff --git a/zh-hant/codes/cpp/chapter_hashing/array_hash_map_test.cpp b/zh-hant/codes/cpp/chapter_hashing/array_hash_map_test.cpp new file mode 100644 index 0000000000..341fa40ac0 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/array_hash_map_test.cpp @@ -0,0 +1,52 @@ +/** + * File: array_hash_map_test.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "./array_hash_map.cpp" + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + ArrayHashMap map = ArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map.get(15937); + cout << "\n輸入學號 15937 ,查詢到姓名 " << name << endl; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + cout << "\n刪除 10583 後,雜湊表為\nKey -> Value" << endl; + map.print(); + + /* 走訪雜湊表 */ + cout << "\n走訪鍵值對 Key->Value" << endl; + for (auto kv : map.pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + + cout << "\n單獨走訪鍵 Key" << endl; + for (auto key : map.keySet()) { + cout << key << endl; + } + + cout << "\n單獨走訪值 Value" << endl; + for (auto val : map.valueSet()) { + cout << val << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/built_in_hash.cpp b/zh-hant/codes/cpp/chapter_hashing/built_in_hash.cpp new file mode 100644 index 0000000000..6616f5c6ef --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/built_in_hash.cpp @@ -0,0 +1,29 @@ +/** + * File: built_in_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + int num = 3; + size_t hashNum = hash()(num); + cout << "整數 " << num << " 的雜湊值為 " << hashNum << "\n"; + + bool bol = true; + size_t hashBol = hash()(bol); + cout << "布林量 " << bol << " 的雜湊值為 " << hashBol << "\n"; + + double dec = 3.14159; + size_t hashDec = hash()(dec); + cout << "小數 " << dec << " 的雜湊值為 " << hashDec << "\n"; + + string str = "Hello 演算法"; + size_t hashStr = hash()(str); + cout << "字串 " << str << " 的雜湊值為 " << hashStr << "\n"; + + // 在 C++ 中,內建 std:hash() 僅提供基本資料型別的雜湊值計算 + // 陣列、物件的雜湊值計算需要自行實現 +} diff --git a/zh-hant/codes/cpp/chapter_hashing/hash_map.cpp b/zh-hant/codes/cpp/chapter_hashing/hash_map.cpp new file mode 100644 index 0000000000..dfd262abf2 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/hash_map.cpp @@ -0,0 +1,46 @@ +/** + * File: hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + unordered_map map; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + printHashMap(map); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + cout << "\n輸入學號 15937 ,查詢到姓名 " << name << endl; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.erase(10583); + cout << "\n刪除 10583 後,雜湊表為\nKey -> Value" << endl; + printHashMap(map); + + /* 走訪雜湊表 */ + cout << "\n走訪鍵值對 Key->Value" << endl; + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << endl; + } + cout << "\n使用迭代器走訪 Key->Value" << endl; + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/hash_map_chaining.cpp b/zh-hant/codes/cpp/chapter_hashing/hash_map_chaining.cpp new file mode 100644 index 0000000000..02f4c54005 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/hash_map_chaining.cpp @@ -0,0 +1,150 @@ +/** + * File: hash_map_chaining.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + private: + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + vector> buckets; // 桶陣列 + + public: + /* 建構子 */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { + buckets.resize(capacity); + } + + /* 析構方法 */ + ~HashMapChaining() { + for (auto &bucket : buckets) { + for (Pair *pair : bucket) { + // 釋放記憶體 + delete pair; + } + } + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* 查詢操作 */ + string get(int key) { + int index = hashFunc(key); + // 走訪桶,若找到 key ,則返回對應 val + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // 若未找到 key ,則返回空字串 + return ""; + } + + /* 新增操作 */ + void put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + auto &bucket = buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (int i = 0; i < bucket.size(); i++) { + if (bucket[i]->key == key) { + Pair *tmp = bucket[i]; + bucket.erase(bucket.begin() + i); // 從中刪除鍵值對 + delete tmp; // 釋放記憶體 + size--; + return; + } + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + vector> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets.clear(); + buckets.resize(capacity); + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (auto &bucket : bucketsTmp) { + for (Pair *pair : bucket) { + put(pair->key, pair->val); + // 釋放記憶體 + delete pair; + } + } + } + + /* 列印雜湊表 */ + void print() { + for (auto &bucket : buckets) { + cout << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } +}; + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + HashMapChaining map = HashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map.get(13276); + cout << "\n輸入學號 13276 ,查詢到姓名 " << name << endl; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + cout << "\n刪除 12836 後,雜湊表為\nKey -> Value" << endl; + map.print(); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp b/zh-hant/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp new file mode 100644 index 0000000000..4d4625b009 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -0,0 +1,171 @@ +/** + * File: hash_map_open_addressing.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private: + int size; // 鍵值對數量 + int capacity = 4; // 雜湊表容量 + const double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + const int extendRatio = 2; // 擴容倍數 + vector buckets; // 桶陣列 + Pair *TOMBSTONE = new Pair(-1, "-1"); // 刪除標記 + + public: + /* 建構子 */ + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* 析構方法 */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return (double)size / capacity; + } + + /* 搜尋 key 對應的桶索引 */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != nullptr) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index]->key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + string get(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則返回對應 val + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + return buckets[index]->val; + } + // 若鍵值對不存在,則返回空字串 + return ""; + } + + /* 新增操作 */ + void put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + buckets[index]->val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = new Pair(key, val); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + vector bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = vector(capacity, nullptr); + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (Pair *pair : bucketsTmp) { + if (pair != nullptr && pair != TOMBSTONE) { + put(pair->key, pair->val); + delete pair; + } + } + } + + /* 列印雜湊表 */ + void print() { + for (Pair *pair : buckets) { + if (pair == nullptr) { + cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; + } + } + } +}; + +/* Driver Code */ +int main() { + // 初始化雜湊表 + HashMapOpenAddressing hashmap; + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小囉"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鴨"); + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + hashmap.print(); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + string name = hashmap.get(13276); + cout << "\n輸入學號 13276 ,查詢到姓名 " << name << endl; + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750); + cout << "\n刪除 16750 後,雜湊表為\nKey -> Value" << endl; + hashmap.print(); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp b/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp new file mode 100644 index 0000000000..b8a3173a68 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp @@ -0,0 +1,66 @@ +/** + * File: simple_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 加法雜湊 */ +int addHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 乘法雜湊 */ +int mulHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (31 * hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 互斥或雜湊 */ +int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash ^= (int)c; + } + return hash & MODULUS; +} + +/* 旋轉雜湊 */ +int rotHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS; + } + return (int)hash; +} + +/* Driver Code */ +int main() { + string key = "Hello 演算法"; + + int hash = addHash(key); + cout << "加法雜湊值為 " << hash << endl; + + hash = mulHash(key); + cout << "乘法雜湊值為 " << hash << endl; + + hash = xorHash(key); + cout << "互斥或雜湊值為 " << hash << endl; + + hash = rotHash(key); + cout << "旋轉雜湊值為 " << hash << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_heap/CMakeLists.txt b/zh-hant/codes/cpp/chapter_heap/CMakeLists.txt new file mode 100644 index 0000000000..1ac33a44fe --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(heap heap.cpp) +add_executable(my_heap my_heap.cpp) +add_executable(top_k top_k.cpp) diff --git a/zh-hant/codes/cpp/chapter_heap/heap.cpp b/zh-hant/codes/cpp/chapter_heap/heap.cpp new file mode 100644 index 0000000000..c02463ffeb --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/heap.cpp @@ -0,0 +1,66 @@ +/** + * File: heap.cpp + * Created Time: 2023-01-19 + * Author: LoneRanger(836253168@qq.com) + */ + +#include "../utils/common.hpp" + +void testPush(priority_queue &heap, int val) { + heap.push(val); // 元素入堆積 + cout << "\n元素 " << val << " 入堆積後" << endl; + printHeap(heap); +} + +void testPop(priority_queue &heap) { + int val = heap.top(); + heap.pop(); + cout << "\n堆積頂元素 " << val << " 出堆積後" << endl; + printHeap(heap); +} + +/* Driver Code */ +int main() { + /* 初始化堆積 */ + // 初始化小頂堆積 + // priority_queue, greater> minHeap; + // 初始化大頂堆積 + priority_queue, less> maxHeap; + + cout << "\n以下測試樣例為大頂堆積" << endl; + + /* 元素入堆積 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.top(); + cout << "\n堆積頂元素為 " << peek << endl; + + /* 堆積頂元素出堆積 */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + cout << "\n堆積元素數量為 " << size << endl; + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.empty(); + cout << "\n堆積是否為空 " << isEmpty << endl; + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + cout << "輸入串列並建立小頂堆積後" << endl; + printHeap(minHeap); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_heap/my_heap.cpp b/zh-hant/codes/cpp/chapter_heap/my_heap.cpp new file mode 100644 index 0000000000..ad36d5656f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/my_heap.cpp @@ -0,0 +1,155 @@ +/** + * File: my_heap.cpp + * Created Time: 2023-02-04 + * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 大頂堆積 */ +class MaxHeap { + private: + // 使用動態陣列,這樣無須考慮擴容問題 + vector maxHeap; + + /* 獲取左子節點的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交換兩節點 + swap(maxHeap[i], maxHeap[p]); + // 迴圈向上堆積化 + i = p; + } + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) + break; + swap(maxHeap[i], maxHeap[ma]); + // 迴圈向下堆積化 + i = ma; + } + } + + public: + /* 建構子,根據輸入串列建堆積 */ + MaxHeap(vector nums) { + // 將串列元素原封不動新增進堆積 + maxHeap = nums; + // 堆積化除葉節點以外的其他所有節點 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 獲取堆積大小 */ + int size() { + return maxHeap.size(); + } + + /* 判斷堆積是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + int peek() { + return maxHeap[0]; + } + + /* 元素入堆積 */ + void push(int val) { + // 新增節點 + maxHeap.push_back(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 元素出堆積 */ + void pop() { + // 判空處理 + if (isEmpty()) { + throw out_of_range("堆積為空"); + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(maxHeap[0], maxHeap[size() - 1]); + // 刪除節點 + maxHeap.pop_back(); + // 從頂至底堆積化 + siftDown(0); + } + + /* 列印堆積(二元樹)*/ + void print() { + cout << "堆積的陣列表示:"; + printVector(maxHeap); + cout << "堆積的樹狀表示:" << endl; + TreeNode *root = vectorToTree(maxHeap); + printTree(root); + freeMemoryTree(root); + } +}; + +/* Driver Code */ +int main() { + /* 初始化大頂堆積 */ + vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap maxHeap(vec); + cout << "\n輸入串列並建堆積後" << endl; + maxHeap.print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + cout << "\n堆積頂元素為 " << peek << endl; + + /* 元素入堆積 */ + int val = 7; + maxHeap.push(val); + cout << "\n元素 " << val << " 入堆積後" << endl; + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.peek(); + maxHeap.pop(); + cout << "\n堆積頂元素 " << peek << " 出堆積後" << endl; + maxHeap.print(); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + cout << "\n堆積元素數量為 " << size << endl; + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.isEmpty(); + cout << "\n堆積是否為空 " << isEmpty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_heap/top_k.cpp b/zh-hant/codes/cpp/chapter_heap/top_k.cpp new file mode 100644 index 0000000000..96ff263c1f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/top_k.cpp @@ -0,0 +1,38 @@ +/** + * File: top_k.cpp + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +priority_queue, greater> topKHeap(vector &nums, int k) { + // 初始化小頂堆積 + priority_queue, greater> heap; + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.size(); i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +// Driver Code +int main() { + vector nums = {1, 7, 6, 3, 2}; + int k = 3; + + priority_queue, greater> res = topKHeap(nums, k); + cout << "最大的 " << k << " 個元素為: "; + printHeap(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/CMakeLists.txt b/zh-hant/codes/cpp/chapter_searching/CMakeLists.txt new file mode 100644 index 0000000000..60a223d839 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.cpp) +add_executable(binary_search_insertion binary_search_insertion.cpp) +add_executable(binary_search_edge binary_search_edge.cpp) +add_executable(two_sum two_sum.cpp) diff --git a/zh-hant/codes/cpp/chapter_searching/binary_search.cpp b/zh-hant/codes/cpp/chapter_searching/binary_search.cpp new file mode 100644 index 0000000000..0c22020369 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/binary_search.cpp @@ -0,0 +1,59 @@ +/** + * File: binary_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋(雙閉區間) */ +int binarySearch(vector &nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.size() - 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(vector &nums, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.size(); + // 迴圈,當搜尋區間為空時跳出(當 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; +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* 二分搜尋(雙閉區間) */ + int index = binarySearch(nums, target); + cout << "目標元素 6 的索引 = " << index << endl; + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target); + cout << "目標元素 6 的索引 = " << index << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/binary_search_edge.cpp b/zh-hant/codes/cpp/chapter_searching/binary_search_edge.cpp new file mode 100644 index 0000000000..e39607abe3 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/binary_search_edge.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_edge.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(const vector &nums, int target) { + int i = 0, j = nums.size() - 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(vector &nums, int target) { + // 等價於查詢 target 的插入點 + int i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.size() || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +int binarySearchRightEdge(vector &nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +int main() { + // 包含重複元素的陣列 + vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n陣列 nums = "; + printVector(nums); + + // 二分搜尋左邊界和右邊界 + for (int target : {6, 7}) { + int index = binarySearchLeftEdge(nums, target); + cout << "最左一個元素 " << target << " 的索引為 " << index << endl; + index = binarySearchRightEdge(nums, target); + cout << "最右一個元素 " << target << " 的索引為 " << index << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/binary_search_insertion.cpp b/zh-hant/codes/cpp/chapter_searching/binary_search_insertion.cpp new file mode 100644 index 0000000000..e0526125da --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/binary_search_insertion.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_insertion.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋插入點(無重複元素) */ +int binarySearchInsertionSimple(vector &nums, int target) { + int i = 0, j = nums.size() - 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(vector &nums, int target) { + int i = 0, j = nums.size() - 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; +} + +/* Driver Code */ +int main() { + // 無重複元素的陣列 + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + cout << "\n陣列 nums = "; + printVector(nums); + // 二分搜尋插入點 + for (int target : {6, 9}) { + int index = binarySearchInsertionSimple(nums, target); + cout << "元素 " << target << " 的插入點的索引為 " << index << endl; + } + + // 包含重複元素的陣列 + nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n陣列 nums = "; + printVector(nums); + // 二分搜尋插入點 + for (int target : {2, 6, 20}) { + int index = binarySearchInsertion(nums, target); + cout << "元素 " << target << " 的插入點的索引為 " << index << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/hashing_search.cpp b/zh-hant/codes/cpp/chapter_searching/hashing_search.cpp new file mode 100644 index 0000000000..0e2b0a8ed9 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/hashing_search.cpp @@ -0,0 +1,53 @@ +/** + * File: hashing_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 雜湊查詢(陣列) */ +int hashingSearchArray(unordered_map map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if (map.find(target) == map.end()) + return -1; + return map[target]; +} + +/* 雜湊查詢(鏈結串列) */ +ListNode *hashingSearchLinkedList(unordered_map map, int target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 nullptr + if (map.find(target) == map.end()) + return nullptr; + return map[target]; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* 雜湊查詢(陣列) */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + // 初始化雜湊表 + unordered_map map; + for (int i = 0; i < nums.size(); i++) { + map[nums[i]] = i; // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + cout << "目標元素 3 的索引 = " << index << endl; + + /* 雜湊查詢(鏈結串列) */ + ListNode *head = vecToLinkedList(nums); + // 初始化雜湊表 + unordered_map map1; + while (head != nullptr) { + map1[head->val] = head; // key: 節點值,value: 節點 + head = head->next; + } + ListNode *node = hashingSearchLinkedList(map1, target); + cout << "目標節點值 3 的對應節點物件為 " << node << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/linear_search.cpp b/zh-hant/codes/cpp/chapter_searching/linear_search.cpp new file mode 100644 index 0000000000..c740ad6f75 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/linear_search.cpp @@ -0,0 +1,49 @@ +/** + * File: linear_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 線性查詢(陣列) */ +int linearSearchArray(vector &nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.size(); i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列) */ +ListNode *linearSearchLinkedList(ListNode *head, int target) { + // 走訪鏈結串列 + while (head != nullptr) { + // 找到目標節點,返回之 + if (head->val == target) + return head; + head = head->next; + } + // 未找到目標節點,返回 nullptr + return nullptr; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* 在陣列中執行線性查詢 */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + int index = linearSearchArray(nums, target); + cout << "目標元素 3 的索引 = " << index << endl; + + /* 在鏈結串列中執行線性查詢 */ + ListNode *head = vecToLinkedList(nums); + ListNode *node = linearSearchLinkedList(head, target); + cout << "目標節點值 3 的對應節點物件為 " << node << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/two_sum.cpp b/zh-hant/codes/cpp/chapter_searching/two_sum.cpp new file mode 100644 index 0000000000..a20f03c04f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/two_sum.cpp @@ -0,0 +1,54 @@ +/** + * File: two_sum.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 方法一:暴力列舉 */ +vector twoSumBruteForce(vector &nums, int target) { + int size = nums.size(); + // 兩層迴圈,時間複雜度為 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return {i, j}; + } + } + return {}; +} + +/* 方法二:輔助雜湊表 */ +vector twoSumHashTable(vector &nums, int target) { + int size = nums.size(); + // 輔助雜湊表,空間複雜度為 O(n) + unordered_map dic; + // 單層迴圈,時間複雜度為 O(n) + for (int i = 0; i < size; i++) { + if (dic.find(target - nums[i]) != dic.end()) { + return {dic[target - nums[i]], i}; + } + dic.emplace(nums[i], i); + } + return {}; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + vector nums = {2, 7, 11, 15}; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + vector res = twoSumBruteForce(nums, target); + cout << "方法一 res = "; + printVector(res); + // 方法二 + res = twoSumHashTable(nums, target); + cout << "方法二 res = "; + printVector(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/CMakeLists.txt b/zh-hant/codes/cpp/chapter_sorting/CMakeLists.txt new file mode 100644 index 0000000000..e6347cf9f3 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(selection_sort selection_sort.cpp) +add_executable(bubble_sort bubble_sort.cpp) +add_executable(insertion_sort insertion_sort.cpp) +add_executable(merge_sort merge_sort.cpp) +add_executable(quick_sort quick_sort.cpp) +add_executable(heap_sort heap_sort.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_sorting/bubble_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/bubble_sort.cpp new file mode 100644 index 0000000000..d29ed46610 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -0,0 +1,56 @@ +/** + * File: bubble_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 泡沫排序 */ +void bubbleSort(vector &nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + // 這裡使用了 std::swap() 函式 + swap(nums[j], nums[j + 1]); + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +void bubbleSortWithFlag(vector &nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + bool flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + // 這裡使用了 std::swap() 函式 + swap(nums[j], nums[j + 1]); + flag = true; // 記錄交換元素 + } + } + if (!flag) + break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + bubbleSort(nums); + cout << "泡沫排序完成後 nums = "; + printVector(nums); + + vector nums1 = {4, 1, 3, 1, 5, 2}; + bubbleSortWithFlag(nums1); + cout << "泡沫排序完成後 nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/bucket_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/bucket_sort.cpp new file mode 100644 index 0000000000..4595aba3df --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/bucket_sort.cpp @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.cpp + * Created Time: 2023-03-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 桶排序 */ +void bucketSort(vector &nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.size() / 2; + vector> buckets(k); + // 1. 將陣列元素分配到各個桶中 + for (float num : nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + int i = num * k; + // 將 num 新增進桶 bucket_idx + buckets[i].push_back(num); + } + // 2. 對各個桶執行排序 + for (vector &bucket : buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + sort(bucket.begin(), bucket.end()); + } + // 3. 走訪桶合併結果 + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +int main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + vector nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums); + cout << "桶排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/counting_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/counting_sort.cpp new file mode 100644 index 0000000000..b2ba9ce01b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/counting_sort.cpp @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cpp + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +void countingSortNaive(vector &nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 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; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +void countingSort(vector &nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.size(); + vector res(n); + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + nums = res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSortNaive(nums); + cout << "計數排序(無法排序物件)完成後 nums = "; + printVector(nums); + + vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSort(nums1); + cout << "計數排序完成後 nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/heap_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/heap_sort.cpp new file mode 100644 index 0000000000..9f10ccfc8c --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/heap_sort.cpp @@ -0,0 +1,54 @@ +/** + * File: heap_sort.cpp + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +void siftDown(vector &nums, int n, int i) { + while (true) { + // 判斷節點 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; + } + // 交換兩節點 + swap(nums[i], nums[ma]); + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +void heapSort(vector &nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.size() / 2 - 1; i >= 0; --i) { + siftDown(nums, nums.size(), i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.size() - 1; i > 0; --i) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(nums[0], nums[i]); + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + heapSort(nums); + cout << "堆積排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/insertion_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/insertion_sort.cpp new file mode 100644 index 0000000000..f63706e37e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 插入排序 */ +void insertionSort(vector &nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.size(); i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + insertionSort(nums); + cout << "插入排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/merge_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/merge_sort.cpp new file mode 100644 index 0000000000..5d32b42124 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/merge_sort.cpp @@ -0,0 +1,58 @@ +/** + * File: merge_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 合併左子陣列和右子陣列 */ +void merge(vector &nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + vector tmp(right - left + 1); + // 初始化左子陣列和右子陣列的起始索引 + 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 < tmp.size(); k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +void mergeSort(vector &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); +} + +/* Driver Code */ +int main() { + /* 合併排序 */ + vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; + mergeSort(nums, 0, nums.size() - 1); + cout << "合併排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/quick_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/quick_sort.cpp new file mode 100644 index 0000000000..99ae48d56c --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/quick_sort.cpp @@ -0,0 +1,145 @@ +/** + * File: quick_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 快速排序類別 */ +class QuickSort { + private: + /* 哨兵劃分 */ + static int partition(vector &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], nums[j]); // 交換這兩個元素 + } + swap(nums[i], nums[left]); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + public: + /* 快速排序 */ + static void quickSort(vector &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); + } +}; + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + private: + /* 選取三個候選元素的中位數 */ + static int medianThree(vector &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; + } + + /* 哨兵劃分(三數取中值) */ + static int partition(vector &nums, int left, int right) { + // 選取三個候選元素的中位數 + int med = medianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + swap(nums[left], nums[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], nums[j]); // 交換這兩個元素 + } + swap(nums[i], nums[left]); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + public: + /* 快速排序 */ + static void quickSort(vector &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); + } +}; + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + private: + /* 哨兵劃分 */ + static int partition(vector &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], nums[j]); // 交換這兩個元素 + } + swap(nums[i], nums[left]); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + public: + /* 快速排序(尾遞迴最佳化) */ + static void quickSort(vector &nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +}; + +/* Driver Code */ +int main() { + /* 快速排序 */ + vector nums{2, 4, 1, 0, 3, 5}; + QuickSort::quickSort(nums, 0, nums.size() - 1); + cout << "快速排序完成後 nums = "; + printVector(nums); + + /* 快速排序(中位基準數最佳化) */ + vector nums1 = {2, 4, 1, 0, 3, 5}; + QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); + cout << "快速排序(中位基準數最佳化)完成後 nums = "; + printVector(nums1); + + /* 快速排序(尾遞迴最佳化) */ + vector nums2 = {2, 4, 1, 0, 3, 5}; + QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); + cout << "快速排序(尾遞迴最佳化)完成後 nums = "; + printVector(nums2); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/radix_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/radix_sort.cpp new file mode 100644 index 0000000000..28a41da040 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/radix_sort.cpp @@ -0,0 +1,65 @@ +/** + * File: radix_sort.cpp + * Created Time: 2023-03-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +void countingSortDigit(vector &nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + vector counter(10, 0); + int n = nums.size(); + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + vector res(n, 0); + for (int i = n - 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 < n; i++) + nums[i] = res[i]; +} + +/* 基數排序 */ +void radixSort(vector &nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + int m = *max_element(nums.begin(), nums.end()); + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +int main() { + // 基數排序 + vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + radixSort(nums); + cout << "基數排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/selection_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/selection_sort.cpp new file mode 100644 index 0000000000..846eeaf0ad --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/selection_sort.cpp @@ -0,0 +1,34 @@ +/** + * File: selection_sort.cpp + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 選擇排序 */ +void selectionSort(vector &nums) { + int n = nums.size(); + // 外迴圈:未排序區間為 [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; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + swap(nums[i], nums[k]); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + selectionSort(nums); + + cout << "選擇排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/CMakeLists.txt b/zh-hant/codes/cpp/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 0000000000..b55878a174 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(array_deque array_deque.cpp) +add_executable(array_queue array_queue.cpp) +add_executable(array_stack array_stack.cpp) +add_executable(deque deque.cpp) +add_executable(linkedlist_deque linkedlist_deque.cpp) +add_executable(linkedlist_queue linkedlist_queue.cpp) +add_executable(linkedlist_stack linkedlist_stack.cpp) +add_executable(queue queue.cpp) +add_executable(stack stack.cpp) diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/array_deque.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/array_deque.cpp new file mode 100644 index 0000000000..061506afe8 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/array_deque.cpp @@ -0,0 +1,156 @@ +/** + * File: array_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private: + vector nums; // 用於儲存雙向佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 雙向佇列長度 + + public: + /* 建構子 */ + ArrayDeque(int capacity) { + nums.resize(capacity); + front = queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + int capacity() { + return nums.size(); + } + + /* 獲取雙向佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 計算環形陣列索引 */ + int index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 佇列首入列 */ + void pushFirst(int num) { + if (queSize == capacity()) { + cout << "雙向佇列已滿" << endl; + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = index(front - 1); + // 將 num 新增至佇列首 + nums[front] = num; + queSize++; + } + + /* 佇列尾入列 */ + void pushLast(int num) { + if (queSize == capacity()) { + cout << "雙向佇列已滿" << endl; + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = index(front + queSize); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 佇列首出列 */ + int popFirst() { + int num = peekFirst(); + // 佇列首指標向後移動一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 佇列尾出列 */ + int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + return nums[front]; + } + + /* 訪問佇列尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + // 計算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回陣列用於列印 */ + vector toVector() { + // 僅轉換有效長度範圍內的串列元素 + vector res(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + ArrayDeque *deque = new ArrayDeque(10); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "雙向佇列 deque = "; + printVector(deque->toVector()); + + /* 訪問元素 */ + int peekFirst = deque->peekFirst(); + cout << "佇列首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "佇列尾元素 peekLast = " << peekLast << endl; + + /* 元素入列 */ + deque->pushLast(4); + cout << "元素 4 佇列尾入列後 deque = "; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 佇列首入列後 deque = "; + printVector(deque->toVector()); + + /* 元素出列 */ + int popLast = deque->popLast(); + cout << "佇列尾出列元素 = " << popLast << ",佇列尾出列後 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "佇列首出列元素 = " << popFirst << ",佇列首出列後 deque = "; + printVector(deque->toVector()); + + /* 獲取雙向佇列的長度 */ + int size = deque->size(); + cout << "雙向佇列長度 size = " << size << endl; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque->isEmpty(); + cout << "雙向佇列是否為空 = " << boolalpha << isEmpty << endl; + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/array_queue.cpp new file mode 100644 index 0000000000..0a229c7b98 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -0,0 +1,129 @@ +/** + * File: array_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private: + int *nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 佇列長度 + int queCapacity; // 佇列容量 + + public: + ArrayQueue(int capacity) { + // 初始化陣列 + nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; + } + + /* 獲取佇列的容量 */ + int capacity() { + return queCapacity; + } + + /* 獲取佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入列 */ + void push(int num) { + if (queSize == queCapacity) { + cout << "佇列已滿" << endl; + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (front + queSize) % queCapacity; + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 出列 */ + int pop() { + int num = peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % queCapacity; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (isEmpty()) + throw out_of_range("佇列為空"); + return nums[front]; + } + + /* 將陣列轉化為 Vector 並返回 */ + vector toVector() { + // 僅轉換有效長度範圍內的串列元素 + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; + } + return arr; + } +}; + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + int capacity = 10; + ArrayQueue *queue = new ArrayQueue(capacity); + + /* 元素入列 */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "佇列 queue = "; + printVector(queue->toVector()); + + /* 訪問佇列首元素 */ + int peek = queue->peek(); + cout << "佇列首元素 peek = " << peek << endl; + + /* 元素出列 */ + peek = queue->pop(); + cout << "出列元素 pop = " << peek << ",出列後 queue = "; + printVector(queue->toVector()); + + /* 獲取佇列的長度 */ + int size = queue->size(); + cout << "佇列長度 size = " << size << endl; + + /* 判斷佇列是否為空 */ + bool empty = queue->isEmpty(); + cout << "佇列是否為空 = " << empty << endl; + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue->push(i); + queue->pop(); + cout << "第 " << i << " 輪入列 + 出列後 queue = "; + printVector(queue->toVector()); + } + + // 釋放記憶體 + delete queue; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/array_stack.cpp new file mode 100644 index 0000000000..423bf70115 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -0,0 +1,85 @@ +/** + * File: array_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private: + vector stack; + + public: + /* 獲取堆疊的長度 */ + int size() { + return stack.size(); + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return stack.size() == 0; + } + + /* 入堆疊 */ + void push(int num) { + stack.push_back(num); + } + + /* 出堆疊 */ + int pop() { + int num = top(); + stack.pop_back(); + return num; + } + + /* 訪問堆疊頂元素 */ + int top() { + if (isEmpty()) + throw out_of_range("堆疊為空"); + return stack.back(); + } + + /* 返回 Vector */ + vector toVector() { + return stack; + } +}; + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + ArrayStack *stack = new ArrayStack(); + + /* 元素入堆疊 */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "堆疊 stack = "; + printVector(stack->toVector()); + + /* 訪問堆疊頂元素 */ + int top = stack->top(); + cout << "堆疊頂元素 top = " << top << endl; + + /* 元素出堆疊 */ + top = stack->pop(); + cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; + printVector(stack->toVector()); + + /* 獲取堆疊的長度 */ + int size = stack->size(); + cout << "堆疊的長度 size = " << size << endl; + + /* 判斷是否為空 */ + bool empty = stack->isEmpty(); + cout << "堆疊是否為空 = " << empty << endl; + + // 釋放記憶體 + delete stack; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/deque.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/deque.cpp new file mode 100644 index 0000000000..dab6cdc9fb --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -0,0 +1,46 @@ +/** + * File: deque.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + deque deque; + + /* 元素入列 */ + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); + deque.push_front(1); + cout << "雙向佇列 deque = "; + printDeque(deque); + + /* 訪問元素 */ + int front = deque.front(); + cout << "佇列首元素 front = " << front << endl; + int back = deque.back(); + cout << "佇列尾元素 back = " << back << endl; + + /* 元素出列 */ + deque.pop_front(); + cout << "佇列首出列元素 popFront = " << front << ",佇列首出列後 deque = "; + printDeque(deque); + deque.pop_back(); + cout << "佇列尾出列元素 popLast = " << back << ",佇列尾出列後 deque = "; + printDeque(deque); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + cout << "雙向佇列長度 size = " << size << endl; + + /* 判斷雙向佇列是否為空 */ + bool empty = deque.empty(); + cout << "雙向佇列是否為空 = " << empty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp new file mode 100644 index 0000000000..1767609eb7 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -0,0 +1,194 @@ +/** + * File: linkedlist_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 雙向鏈結串列節點 */ +struct DoublyListNode { + int val; // 節點值 + DoublyListNode *next; // 後繼節點指標 + DoublyListNode *prev; // 前驅節點指標 + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } +}; + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private: + DoublyListNode *front, *rear; // 頭節點 front ,尾節點 rear + int queSize = 0; // 雙向佇列的長度 + + public: + /* 建構子 */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* 析構方法 */ + ~LinkedListDeque() { + // 走訪鏈結串列刪除節點,釋放記憶體 + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* 獲取雙向佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入列操作 */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front->prev = node; + node->next = front; + front = node; // 更新頭節點 + // 佇列尾入列操作 + } else { + // 將 node 新增至鏈結串列尾部 + rear->next = node; + node->prev = rear; + rear = node; // 更新尾節點 + } + queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + void pushFirst(int num) { + push(num, true); + } + + /* 佇列尾入列 */ + void pushLast(int num) { + push(num, false); + } + + /* 出列操作 */ + int pop(bool isFront) { + if (isEmpty()) + throw out_of_range("佇列為空"); + int val; + // 佇列首出列操作 + if (isFront) { + val = front->val; // 暫存頭節點值 + // 刪除頭節點 + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + } + delete front; + front = fNext; // 更新頭節點 + // 佇列尾出列操作 + } else { + val = rear->val; // 暫存尾節點值 + // 刪除尾節點 + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + } + delete rear; + rear = rPrev; // 更新尾節點 + } + queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + int popFirst() { + return pop(true); + } + + /* 佇列尾出列 */ + int popLast() { + return pop(false); + } + + /* 訪問佇列首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + return front->val; + } + + /* 訪問佇列尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + return rear->val; + } + + /* 返回陣列用於列印 */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + LinkedListDeque *deque = new LinkedListDeque(); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "雙向佇列 deque = "; + printVector(deque->toVector()); + + /* 訪問元素 */ + int peekFirst = deque->peekFirst(); + cout << "佇列首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "佇列尾元素 peekLast = " << peekLast << endl; + + /* 元素入列 */ + deque->pushLast(4); + cout << "元素 4 佇列尾入列後 deque ="; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 佇列首入列後 deque = "; + printVector(deque->toVector()); + + /* 元素出列 */ + int popLast = deque->popLast(); + cout << "佇列尾出列元素 = " << popLast << ",佇列尾出列後 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "佇列首出列元素 = " << popFirst << ",佇列首出列後 deque = "; + printVector(deque->toVector()); + + /* 獲取雙向佇列的長度 */ + int size = deque->size(); + cout << "雙向佇列長度 size = " << size << endl; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque->isEmpty(); + cout << "雙向佇列是否為空 = " << boolalpha << isEmpty << endl; + + // 釋放記憶體 + delete deque; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp new file mode 100644 index 0000000000..152620d663 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -0,0 +1,120 @@ +/** + * File: linkedlist_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private: + ListNode *front, *rear; // 頭節點 front ,尾節點 rear + int queSize; + + public: + LinkedListQueue() { + front = nullptr; + rear = nullptr; + queSize = 0; + } + + ~LinkedListQueue() { + // 走訪鏈結串列刪除節點,釋放記憶體 + freeMemoryLinkedList(front); + } + + /* 獲取佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 入列 */ + void push(int num) { + // 在尾節點後新增 num + ListNode *node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == nullptr) { + front = node; + rear = node; + } + // 如果佇列不為空,則將該節點新增到尾節點後 + else { + rear->next = node; + rear = node; + } + queSize++; + } + + /* 出列 */ + int pop() { + int num = peek(); + // 刪除頭節點 + ListNode *tmp = front; + front = front->next; + // 釋放記憶體 + delete tmp; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (size() == 0) + throw out_of_range("佇列為空"); + return front->val; + } + + /* 將鏈結串列轉化為 Vector 並返回 */ + vector toVector() { + ListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + LinkedListQueue *queue = new LinkedListQueue(); + + /* 元素入列 */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "佇列 queue = "; + printVector(queue->toVector()); + + /* 訪問佇列首元素 */ + int peek = queue->peek(); + cout << "佇列首元素 peek = " << peek << endl; + + /* 元素出列 */ + peek = queue->pop(); + cout << "出列元素 pop = " << peek << ",出列後 queue = "; + printVector(queue->toVector()); + + /* 獲取佇列的長度 */ + int size = queue->size(); + cout << "佇列長度 size = " << size << endl; + + /* 判斷佇列是否為空 */ + bool empty = queue->isEmpty(); + cout << "佇列是否為空 = " << empty << endl; + + // 釋放記憶體 + delete queue; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp new file mode 100644 index 0000000000..3bf1707c06 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -0,0 +1,109 @@ +/** + * File: linkedlist_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private: + ListNode *stackTop; // 將頭節點作為堆疊頂 + int stkSize; // 堆疊的長度 + + public: + LinkedListStack() { + stackTop = nullptr; + stkSize = 0; + } + + ~LinkedListStack() { + // 走訪鏈結串列刪除節點,釋放記憶體 + freeMemoryLinkedList(stackTop); + } + + /* 獲取堆疊的長度 */ + int size() { + return stkSize; + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入堆疊 */ + void push(int num) { + ListNode *node = new ListNode(num); + node->next = stackTop; + stackTop = node; + stkSize++; + } + + /* 出堆疊 */ + int pop() { + int num = top(); + ListNode *tmp = stackTop; + stackTop = stackTop->next; + // 釋放記憶體 + delete tmp; + stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + int top() { + if (isEmpty()) + throw out_of_range("堆疊為空"); + return stackTop->val; + } + + /* 將 List 轉化為 Array 並返回 */ + vector toVector() { + ListNode *node = stackTop; + vector res(size()); + for (int i = res.size() - 1; i >= 0; i--) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + LinkedListStack *stack = new LinkedListStack(); + + /* 元素入堆疊 */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "堆疊 stack = "; + printVector(stack->toVector()); + + /* 訪問堆疊頂元素 */ + int top = stack->top(); + cout << "堆疊頂元素 top = " << top << endl; + + /* 元素出堆疊 */ + top = stack->pop(); + cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; + printVector(stack->toVector()); + + /* 獲取堆疊的長度 */ + int size = stack->size(); + cout << "堆疊的長度 size = " << size << endl; + + /* 判斷是否為空 */ + bool empty = stack->isEmpty(); + cout << "堆疊是否為空 = " << empty << endl; + + // 釋放記憶體 + delete stack; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/queue.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/queue.cpp new file mode 100644 index 0000000000..78b306d5ea --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -0,0 +1,41 @@ +/** + * File: queue.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + queue queue; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + cout << "佇列 queue = "; + printQueue(queue); + + /* 訪問佇列首元素 */ + int front = queue.front(); + cout << "佇列首元素 front = " << front << endl; + + /* 元素出列 */ + queue.pop(); + cout << "出列元素 front = " << front << ",出列後 queue = "; + printQueue(queue); + + /* 獲取佇列的長度 */ + int size = queue.size(); + cout << "佇列長度 size = " << size << endl; + + /* 判斷佇列是否為空 */ + bool empty = queue.empty(); + cout << "佇列是否為空 = " << empty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/stack.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/stack.cpp new file mode 100644 index 0000000000..e046d5c9b8 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -0,0 +1,41 @@ +/** + * File: stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + stack stack; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + cout << "堆疊 stack = "; + printStack(stack); + + /* 訪問堆疊頂元素 */ + int top = stack.top(); + cout << "堆疊頂元素 top = " << top << endl; + + /* 元素出堆疊 */ + stack.pop(); // 無返回值 + cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; + printStack(stack); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + cout << "堆疊的長度 size = " << size << endl; + + /* 判斷是否為空 */ + bool empty = stack.empty(); + cout << "堆疊是否為空 = " << empty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/CMakeLists.txt b/zh-hant/codes/cpp/chapter_tree/CMakeLists.txt new file mode 100644 index 0000000000..fa7009bcb3 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.cpp) +add_executable(binary_search_tree binary_search_tree.cpp) +add_executable(binary_tree binary_tree.cpp) +add_executable(binary_tree_bfs binary_tree_bfs.cpp) +add_executable(binary_tree_dfs binary_tree_dfs.cpp) +add_executable(array_binary_tree array_binary_tree.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_tree/array_binary_tree.cpp b/zh-hant/codes/cpp/chapter_tree/array_binary_tree.cpp new file mode 100644 index 0000000000..0c8f3287f1 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/array_binary_tree.cpp @@ -0,0 +1,137 @@ +/** + * File: array_binary_tree.cpp + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + public: + /* 建構子 */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* 串列容量 */ + int size() { + return tree.size(); + } + + /* 獲取索引為 i 節點的值 */ + int val(int i) { + // 若索引越界,則返回 INT_MAX ,代表空位 + if (i < 0 || i >= size()) + return INT_MAX; + return tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + int parent(int i) { + return (i - 1) / 2; + } + + /* 層序走訪 */ + vector levelOrder() { + vector res; + // 直接走訪陣列 + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* 前序走訪 */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* 中序走訪 */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* 後序走訪 */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + private: + vector tree; + + /* 深度優先走訪 */ + void dfs(int i, string order, vector &res) { + // 若為空位,則返回 + if (val(i) == INT_MAX) + return; + // 前序走訪 + if (order == "pre") + res.push_back(val(i)); + dfs(left(i), order, res); + // 中序走訪 + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // 後序走訪 + if (order == "post") + res.push_back(val(i)); + } +}; + +/* Driver Code */ +int main() { + // 初始化二元樹 + // 使用 INT_MAX 代表空位 nullptr + vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + TreeNode *root = vectorToTree(arr); + cout << "\n初始化二元樹\n"; + cout << "二元樹的陣列表示:\n"; + printVector(arr); + cout << "二元樹的鏈結串列表示:\n"; + printTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt(arr); + + // 訪問節點 + int i = 1; + int l = abt.left(i), r = abt.right(i), p = abt.parent(i); + cout << "\n當前節點的索引為 " << i << ",值為 " << abt.val(i) << "\n"; + cout << "其左子節點的索引為 " << l << ",值為 " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; + cout << "其右子節點的索引為 " << r << ",值為 " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; + cout << "其父節點的索引為 " << p << ",值為 " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; + + // 走訪樹 + vector res = abt.levelOrder(); + cout << "\n層序走訪為: "; + printVector(res); + res = abt.preOrder(); + cout << "前序走訪為: "; + printVector(res); + res = abt.inOrder(); + cout << "中序走訪為: "; + printVector(res); + res = abt.postOrder(); + cout << "後序走訪為: "; + printVector(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/avl_tree.cpp b/zh-hant/codes/cpp/chapter_tree/avl_tree.cpp new file mode 100644 index 0000000000..b24fac7edb --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/avl_tree.cpp @@ -0,0 +1,233 @@ +/** + * File: avl_tree.cpp + * Created Time: 2023-02-03 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* AVL 樹 */ +class AVLTree { + private: + /* 更新節點高度 */ + void updateHeight(TreeNode *node) { + // 節點高度等於最高子樹高度 + 1 + node->height = max(height(node->left), height(node->right)) + 1; + } + + /* 右旋操作 */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child = node->left; + TreeNode *grandChild = child->right; + // 以 child 為原點,將 node 向右旋轉 + child->right = node; + node->left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child = node->right; + TreeNode *grandChild = child->left; + // 以 child 為原點,將 node 向左旋轉 + child->left = node; + node->right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + TreeNode *rotate(TreeNode *node) { + // 獲取節點 node 的平衡因子 + int _balanceFactor = balanceFactor(node); + // 左偏樹 + if (_balanceFactor > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏樹 + if (_balanceFactor < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 遞迴插入節點(輔助方法) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == nullptr) + return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node->val) + node->left = insertHelper(node->left, val); + else if (val > node->val) + node->right = insertHelper(node->right, val); + else + return node; // 重複節點不插入,直接返回 + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 遞迴刪除節點(輔助方法) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 1. 查詢節點並刪除 */ + if (val < node->val) + node->left = removeHelper(node->left, val); + else if (val > node->val) + node->right = removeHelper(node->right, val); + else { + if (node->left == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == nullptr) { + delete node; + return nullptr; + } + // 子節點數量 = 1 ,直接刪除 node + else { + delete node; + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode *temp = node->right; + while (temp->left != nullptr) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + public: + TreeNode *root; // 根節點 + + /* 獲取節點高度 */ + int height(TreeNode *node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == nullptr ? -1 : node->height; + } + + /* 獲取平衡因子 */ + int balanceFactor(TreeNode *node) { + // 空節點平衡因子為 0 + if (node == nullptr) + return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node->left) - height(node->right); + } + + /* 插入節點 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 刪除節點 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 查詢節點 */ + TreeNode *search(int val) { + TreeNode *cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 目標節點在 cur 的右子樹中 + if (cur->val < val) + cur = cur->right; + // 目標節點在 cur 的左子樹中 + else if (cur->val > val) + cur = cur->left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /*建構子*/ + AVLTree() : root(nullptr) { + } + + /*析構方法*/ + ~AVLTree() { + freeMemoryTree(root); + } +}; + +void testInsert(AVLTree &tree, int val) { + tree.insert(val); + cout << "\n插入節點 " << val << " 後,AVL 樹為" << endl; + printTree(tree.root); +} + +void testRemove(AVLTree &tree, int val) { + tree.remove(val); + cout << "\n刪除節點 " << val << " 後,AVL 樹為" << endl; + printTree(tree.root); +} + +/* Driver Code */ +int main() { + /* 初始化空 AVL 樹 */ + AVLTree avlTree; + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重複節點 */ + testInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8); // 刪除度為 0 的節點 + testRemove(avlTree, 5); // 刪除度為 1 的節點 + testRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode *node = avlTree.search(7); + cout << "\n查詢到的節點物件為 " << node << ",節點值 = " << node->val << endl; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_search_tree.cpp b/zh-hant/codes/cpp/chapter_tree/binary_search_tree.cpp new file mode 100644 index 0000000000..a86e78a44e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -0,0 +1,170 @@ +/** + * File: binary_search_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二元搜尋樹 */ +class BinarySearchTree { + private: + TreeNode *root; + + public: + /* 建構子 */ + BinarySearchTree() { + // 初始化空樹 + root = nullptr; + } + + /* 析構方法 */ + ~BinarySearchTree() { + freeMemoryTree(root); + } + + /* 獲取二元樹根節點 */ + TreeNode *getRoot() { + return root; + } + + /* 查詢節點 */ + TreeNode *search(int num) { + TreeNode *cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 目標節點在 cur 的右子樹中 + if (cur->val < num) + cur = cur->right; + // 目標節點在 cur 的左子樹中 + else if (cur->val > num) + cur = cur->left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + void insert(int num) { + // 若樹為空,則初始化根節點 + if (root == nullptr) { + root = new TreeNode(num); + return; + } + TreeNode *cur = root, *pre = nullptr; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 找到重複節點,直接返回 + if (cur->val == num) + return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur->val < num) + cur = cur->right; + // 插入位置在 cur 的左子樹中 + else + cur = cur->left; + } + // 插入節點 + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; + } + + /* 刪除節點 */ + void remove(int num) { + // 若樹為空,直接提前返回 + if (root == nullptr) + return; + TreeNode *cur = root, *pre = nullptr; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 找到待刪除節點,跳出迴圈 + if (cur->val == num) + break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur->val < num) + cur = cur->right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur->left; + } + // 若無待刪除節點,則直接返回 + if (cur == nullptr) + return; + // 子節點數量 = 0 or 1 + if (cur->left == nullptr || cur->right == nullptr) { + // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // 刪除節點 cur + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child; + } + // 釋放記憶體 + delete cur; + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 遞迴刪除節點 tmp + remove(tmp->val); + // 用 tmp 覆蓋 cur + cur->val = tmpVal; + } + } +}; + +/* Driver Code */ +int main() { + /* 初始化二元搜尋樹 */ + BinarySearchTree *bst = new BinarySearchTree(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + for (int num : nums) { + bst->insert(num); + } + cout << endl << "初始化的二元樹為\n" << endl; + printTree(bst->getRoot()); + + /* 查詢節點 */ + TreeNode *node = bst->search(7); + cout << endl << "查詢到的節點物件為 " << node << ",節點值 = " << node->val << endl; + + /* 插入節點 */ + bst->insert(16); + cout << endl << "插入節點 16 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + + /* 刪除節點 */ + bst->remove(1); + cout << endl << "刪除節點 1 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + bst->remove(2); + cout << endl << "刪除節點 2 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + bst->remove(4); + cout << endl << "刪除節點 4 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + + // 釋放記憶體 + delete bst; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_tree.cpp b/zh-hant/codes/cpp/chapter_tree/binary_tree.cpp new file mode 100644 index 0000000000..56b7c8ec6b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_tree.cpp @@ -0,0 +1,43 @@ +/** + * File: binary_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode *n1 = new TreeNode(1); + TreeNode *n2 = new TreeNode(2); + TreeNode *n3 = new TreeNode(3); + TreeNode *n4 = new TreeNode(4); + TreeNode *n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + cout << endl << "初始化二元樹\n" << endl; + printTree(n1); + + /* 插入與刪除節點 */ + TreeNode *P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + cout << endl << "插入節點 P 後\n" << endl; + printTree(n1); + // 刪除節點 P + n1->left = n2; + delete P; // 釋放記憶體 + cout << endl << "刪除節點 P 後\n" << endl; + printTree(n1); + + // 釋放記憶體 + freeMemoryTree(n1); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/zh-hant/codes/cpp/chapter_tree/binary_tree_bfs.cpp new file mode 100644 index 0000000000..a22509114e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 層序走訪 */ +vector levelOrder(TreeNode *root) { + // 初始化佇列,加入根節點 + queue queue; + queue.push(root); + // 初始化一個串列,用於儲存走訪序列 + vector vec; + while (!queue.empty()) { + TreeNode *node = queue.front(); + queue.pop(); // 隊列出隊 + vec.push_back(node->val); // 儲存節點值 + if (node->left != nullptr) + queue.push(node->left); // 左子節點入列 + if (node->right != nullptr) + queue.push(node->right); // 右子節點入列 + } + return vec; +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "初始化二元樹\n" << endl; + printTree(root); + + /* 層序走訪 */ + vector vec = levelOrder(root); + cout << endl << "層序走訪的節點列印序列 = "; + printVector(vec); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/zh-hant/codes/cpp/chapter_tree/binary_tree_dfs.cpp new file mode 100644 index 0000000000..e4ebf7a446 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +// 初始化串列,用於儲存走訪序列 +vector vec; + +/* 前序走訪 */ +void preOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + vec.push_back(root->val); + preOrder(root->left); + preOrder(root->right); +} + +/* 中序走訪 */ +void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root->left); + vec.push_back(root->val); + inOrder(root->right); +} + +/* 後序走訪 */ +void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root->left); + postOrder(root->right); + vec.push_back(root->val); +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "初始化二元樹\n" << endl; + printTree(root); + + /* 前序走訪 */ + vec.clear(); + preOrder(root); + cout << endl << "前序走訪的節點列印序列 = "; + printVector(vec); + + /* 中序走訪 */ + vec.clear(); + inOrder(root); + cout << endl << "中序走訪的節點列印序列 = "; + printVector(vec); + + /* 後序走訪 */ + vec.clear(); + postOrder(root); + cout << endl << "後序走訪的節點列印序列 = "; + printVector(vec); + + return 0; +} diff --git a/zh-hant/codes/cpp/utils/CMakeLists.txt b/zh-hant/codes/cpp/utils/CMakeLists.txt new file mode 100644 index 0000000000..775a558690 --- /dev/null +++ b/zh-hant/codes/cpp/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(utils + common.hpp print_utils.hpp + list_node.hpp tree_node.hpp + vertex.hpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/utils/common.hpp b/zh-hant/codes/cpp/utils/common.hpp new file mode 100644 index 0000000000..c72dabd882 --- /dev/null +++ b/zh-hant/codes/cpp/utils/common.hpp @@ -0,0 +1,28 @@ +/** + * File: common.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.hpp" +#include "print_utils.hpp" +#include "tree_node.hpp" +#include "vertex.hpp" + +using namespace std; diff --git a/zh-hant/codes/cpp/utils/list_node.hpp b/zh-hant/codes/cpp/utils/list_node.hpp new file mode 100644 index 0000000000..e3b7e23899 --- /dev/null +++ b/zh-hant/codes/cpp/utils/list_node.hpp @@ -0,0 +1,42 @@ +/** + * File: list_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 鏈結串列節點 */ +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(nullptr) { + } +}; + +/* 將串列反序列化為鏈結串列 */ +ListNode *vecToLinkedList(vector list) { + ListNode *dum = new ListNode(0); + ListNode *head = dum; + for (int val : list) { + head->next = new ListNode(val); + head = head->next; + } + return dum->next; +} + +/* 釋放分配給鏈結串列的記憶體空間 */ +void freeMemoryLinkedList(ListNode *cur) { + // 釋放記憶體 + ListNode *pre; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } +} diff --git a/zh-hant/codes/cpp/utils/print_utils.hpp b/zh-hant/codes/cpp/utils/print_utils.hpp new file mode 100644 index 0000000000..a3bc4a6b37 --- /dev/null +++ b/zh-hant/codes/cpp/utils/print_utils.hpp @@ -0,0 +1,228 @@ +/** + * File: print_utils.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com) + */ + +#pragma once + +#include "list_node.hpp" +#include "tree_node.hpp" +#include +#include +#include +#include + +/* Find an element in a vector */ +template int vecFind(const vector &vec, T ele) { + int j = INT_MAX; + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == ele) { + j = i; + } + } + return j; +} + +/* Concatenate a vector with a delim */ +template string strJoin(const string &delim, const T &vec) { + ostringstream s; + for (const auto &i : vec) { + if (&i != &vec[0]) { + s << delim; + } + s << i; + } + return s.str(); +} + +/* Repeat a string for n times */ +string strRepeat(string str, int n) { + ostringstream os; + for (int i = 0; i < n; i++) + os << str; + return os.str(); +} + +/* 列印陣列 */ +template void printArray(T *arr, int n) { + cout << "["; + for (int i = 0; i < n - 1; i++) { + cout << arr[i] << ", "; + } + if (n >= 1) + cout << arr[n - 1] << "]" << endl; + else + cout << "]" << endl; +} + +/* Get the Vector String object */ +template string getVectorString(vector &list) { + return "[" + strJoin(", ", list) + "]"; +} + +/* 列印串列 */ +template void printVector(vector list) { + cout << getVectorString(list) << '\n'; +} + +/* 列印矩陣 */ +template void printVectorMatrix(vector> &matrix) { + cout << "[" << '\n'; + for (vector &list : matrix) + cout << " " + getVectorString(list) + "," << '\n'; + cout << "]" << '\n'; +} + +/* 列印鏈結串列 */ +void printLinkedList(ListNode *head) { + vector list; + while (head != nullptr) { + list.push_back(head->val); + head = head->next; + } + + cout << strJoin(" -> ", list) << '\n'; +} + +struct Trunk { + Trunk *prev; + string str; + Trunk(Trunk *prev, string str) { + this->prev = prev; + this->str = str; + } +}; + +void showTrunks(Trunk *p) { + if (p == nullptr) { + return; + } + + showTrunks(p->prev); + cout << p->str; +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode *root, Trunk *prev, bool isRight) { + if (root == nullptr) { + return; + } + + string prev_str = " "; + Trunk trunk(prev, prev_str); + + printTree(root->right, &trunk, true); + + if (!prev) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev->str = prev_str; + } + + showTrunks(&trunk); + cout << " " << root->val << endl; + + if (prev) { + prev->str = prev_str; + } + trunk.str = " |"; + + printTree(root->left, &trunk, false); +} + +/* 列印二元樹 */ +void printTree(TreeNode *root) { + printTree(root, nullptr, false); +} + +/* 列印堆疊 */ +template void printStack(stack stk) { + // Reverse the input stack + stack tmp; + while (!stk.empty()) { + tmp.push(stk.top()); + stk.pop(); + } + // Generate the string to print + ostringstream s; + bool flag = true; + while (!tmp.empty()) { + if (flag) { + s << tmp.top(); + flag = false; + } else + s << ", " << tmp.top(); + tmp.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 列印佇列 */ +template void printQueue(queue queue) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!queue.empty()) { + if (flag) { + s << queue.front(); + flag = false; + } else + s << ", " << queue.front(); + queue.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 列印雙向佇列 */ +template void printDeque(deque deque) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!deque.empty()) { + if (flag) { + s << deque.front(); + flag = false; + } else + s << ", " << deque.front(); + deque.pop_front(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 列印雜湊表 */ +// 定義模板參數 TKey 和 TValue ,用於指定鍵值對的型別 +template void printHashMap(unordered_map map) { + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << '\n'; + } +} + +/* Expose the underlying storage of the priority_queue container */ +template S &Container(priority_queue &pq) { + struct HackedQueue : private priority_queue { + static S &Container(priority_queue &pq) { + return pq.*&HackedQueue::c; + } + }; + return HackedQueue::Container(pq); +} + +/* 列印堆積(優先佇列) */ +template void printHeap(priority_queue &heap) { + vector vec = Container(heap); + cout << "堆積的陣列表示:"; + printVector(vec); + cout << "堆積的樹狀表示:" << endl; + TreeNode *root = vectorToTree(vec); + printTree(root); + freeMemoryTree(root); +} diff --git a/zh-hant/codes/cpp/utils/tree_node.hpp b/zh-hant/codes/cpp/utils/tree_node.hpp new file mode 100644 index 0000000000..266c5a1833 --- /dev/null +++ b/zh-hant/codes/cpp/utils/tree_node.hpp @@ -0,0 +1,84 @@ +/** + * File: tree_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 二元樹節點結構體 */ +struct TreeNode { + int val{}; + int height = 0; + TreeNode *parent{}; + TreeNode *left{}; + TreeNode *right{}; + TreeNode() = default; + explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) { + } +}; + +// 序列化編碼規則請參考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二元樹的陣列表示: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二元樹的鏈結串列表示: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* 將串列反序列化為二元樹:遞迴 */ +TreeNode *vectorToTreeDFS(vector &arr, int i) { + if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) { + return nullptr; + } + TreeNode *root = new TreeNode(arr[i]); + root->left = vectorToTreeDFS(arr, 2 * i + 1); + root->right = vectorToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* 將串列反序列化為二元樹 */ +TreeNode *vectorToTree(vector arr) { + return vectorToTreeDFS(arr, 0); +} + +/* 將二元樹序列化為串列:遞迴 */ +void treeToVecorDFS(TreeNode *root, int i, vector &res) { + if (root == nullptr) + return; + while (i >= res.size()) { + res.push_back(INT_MAX); + } + res[i] = root->val; + treeToVecorDFS(root->left, 2 * i + 1, res); + treeToVecorDFS(root->right, 2 * i + 2, res); +} + +/* 將二元樹序列化為串列 */ +vector treeToVecor(TreeNode *root) { + vector res; + treeToVecorDFS(root, 0, res); + return res; +} + +/* 釋放二元樹記憶體 */ +void freeMemoryTree(TreeNode *root) { + if (root == nullptr) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + delete root; +} diff --git a/zh-hant/codes/cpp/utils/vertex.hpp b/zh-hant/codes/cpp/utils/vertex.hpp new file mode 100644 index 0000000000..9b78144d90 --- /dev/null +++ b/zh-hant/codes/cpp/utils/vertex.hpp @@ -0,0 +1,36 @@ +/** + * File: vertex.hpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include + +using namespace std; + +/* 頂點類別 */ +struct Vertex { + int val; + Vertex(int x) : val(x) { + } +}; + +/* 輸入值串列 vals ,返回頂點串列 vets */ +vector valsToVets(vector vals) { + vector vets; + for (int val : vals) { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* 輸入頂點串列 vets ,返回值串列 vals */ +vector vetsToVals(vector vets) { + vector vals; + for (Vertex *vet : vets) { + vals.push_back(vet->val); + } + return vals; +} diff --git a/zh-hant/codes/csharp/.editorconfig b/zh-hant/codes/csharp/.editorconfig new file mode 100644 index 0000000000..0a2c1df585 --- /dev/null +++ b/zh-hant/codes/csharp/.editorconfig @@ -0,0 +1,88 @@ +# CSharp formatting rules +[*.cs] +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent + +# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. +dotnet_diagnostic.CS8981.severity = silent + +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = silent + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = silent + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = silent + +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = silent diff --git a/zh-hant/codes/csharp/.gitignore b/zh-hant/codes/csharp/.gitignore new file mode 100644 index 0000000000..a4b66a94af --- /dev/null +++ b/zh-hant/codes/csharp/.gitignore @@ -0,0 +1,5 @@ +.idea/ +.vs/ +obj/ +.Debug +bin/ diff --git a/zh-hant/codes/csharp/GlobalUsing.cs b/zh-hant/codes/csharp/GlobalUsing.cs new file mode 100644 index 0000000000..402066ff43 --- /dev/null +++ b/zh-hant/codes/csharp/GlobalUsing.cs @@ -0,0 +1,3 @@ +global using NUnit.Framework; +global using hello_algo.utils; +global using System.Text; \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/array.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/array.cs new file mode 100644 index 0000000000..328aa58c93 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/array.cs @@ -0,0 +1,107 @@ +// File: array.cs +// Created Time: 2022-12-14 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class array { + /* 隨機訪問元素 */ + int RandomAccess(int[] nums) { + Random random = new(); + // 在區間 [0, nums.Length) 中隨機抽取一個數字 + int randomIndex = random.Next(nums.Length); + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* 擴展陣列長度 */ + int[] Extend(int[] nums, int enlarge) { + // 初始化一個擴展長度後的陣列 + int[] res = new int[nums.Length + enlarge]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < nums.Length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; + } + + /* 在陣列的索引 index 處插入元素 num */ + void Insert(int[] nums, int num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (int i = nums.Length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; + } + + /* 刪除索引 index 處的元素 */ + void Remove(int[] nums, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (int i = index; i < nums.Length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* 走訪陣列 */ + void Traverse(int[] nums) { + int count = 0; + // 透過索引走訪陣列 + for (int i = 0; i < nums.Length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + foreach (int num in nums) { + count += num; + } + } + + /* 在陣列中查詢指定元素 */ + int Find(int[] nums, int target) { + for (int i = 0; i < nums.Length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* 輔助函式,陣列轉字串 */ + string ToString(int[] nums) { + return string.Join(",", nums); + } + + + [Test] + public void Test() { + // 初始化陣列 + int[] arr = new int[5]; + Console.WriteLine("陣列 arr = " + ToString(arr)); + int[] nums = [1, 3, 2, 5, 4]; + Console.WriteLine("陣列 nums = " + ToString(nums)); + + // 隨機訪問 + int randomNum = RandomAccess(nums); + Console.WriteLine("在 nums 中獲取隨機元素 " + randomNum); + + // 長度擴展 + nums = Extend(nums, 3); + Console.WriteLine("將陣列長度擴展至 8 ,得到 nums = " + ToString(nums)); + + // 插入元素 + Insert(nums, 6, 3); + Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + ToString(nums)); + + // 刪除元素 + Remove(nums, 2); + Console.WriteLine("刪除索引 2 處的元素,得到 nums = " + ToString(nums)); + + // 走訪陣列 + Traverse(nums); + + // 查詢元素 + int index = Find(nums, 3); + Console.WriteLine("在 nums 中查詢元素 3 ,得到索引 = " + index); + } +} diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/linked_list.cs new file mode 100644 index 0000000000..f4fb0965ce --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -0,0 +1,80 @@ +// File: linked_list.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class linked_list { + /* 在鏈結串列的節點 n0 之後插入節點 P */ + void Insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* 刪除鏈結串列的節點 n0 之後的首個節點 */ + void Remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode? n1 = P.next; + n0.next = n1; + } + + /* 訪問鏈結串列中索引為 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 != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + + [Test] + public void Test() { + // 初始化鏈結串列 + // 初始化各個節點 + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + Console.WriteLine($"初始化的鏈結串列為{n0}"); + + // 插入節點 + Insert(n0, new ListNode(0)); + Console.WriteLine($"插入節點後的鏈結串列為{n0}"); + + // 刪除節點 + Remove(n0); + Console.WriteLine($"刪除節點後的鏈結串列為{n0}"); + + // 訪問節點 + ListNode? node = Access(n0, 3); + Console.WriteLine($"鏈結串列中索引 3 處的節點的值 = {node?.val}"); + + // 查詢節點 + int index = Find(n0, 2); + Console.WriteLine($"鏈結串列中值為 2 的節點的索引 = {index}"); + } +} diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/list.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/list.cs new file mode 100644 index 0000000000..71bbc2050d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/list.cs @@ -0,0 +1,66 @@ +/** + * File: list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +public class list { + [Test] + public void Test() { + + /* 初始化串列 */ + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + Console.WriteLine("串列 nums = " + string.Join(",", nums)); + + /* 訪問元素 */ + int num = nums[1]; + Console.WriteLine("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums[1] = 0; + Console.WriteLine("將索引 1 處的元素更新為 0 ,得到 nums = " + string.Join(",", nums)); + + /* 清空串列 */ + nums.Clear(); + Console.WriteLine("清空串列後 nums = " + string.Join(",", nums)); + + /* 在尾部新增元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("新增元素後 nums = " + string.Join(",", nums)); + + /* 在中間插入元素 */ + nums.Insert(3, 6); + Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + string.Join(",", nums)); + + /* 刪除元素 */ + nums.RemoveAt(3); + Console.WriteLine("刪除索引 3 處的元素,得到 nums = " + string.Join(",", nums)); + + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + /* 直接走訪串列元素 */ + count = 0; + foreach (int x in nums) { + count += x; + } + + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); + Console.WriteLine("將串列 nums1 拼接到 nums 之後,得到 nums = " + string.Join(",", nums)); + + /* 排序串列 */ + nums.Sort(); // 排序後,串列元素從小到大排列 + Console.WriteLine("排序串列後 nums = " + string.Join(",", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/my_list.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/my_list.cs new file mode 100644 index 0000000000..55cb27ccb9 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/my_list.cs @@ -0,0 +1,144 @@ +/** + * File: my_list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +/* 串列類別 */ +class MyList { + private int[] arr; // 陣列(儲存串列元素) + private int arrCapacity = 10; // 串列容量 + private int arrSize = 0; // 串列長度(當前元素數量) + private readonly int extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + public MyList() { + arr = new int[arrCapacity]; + } + + /* 獲取串列長度(當前元素數量)*/ + public int Size() { + return arrSize; + } + + /* 獲取串列容量 */ + public int Capacity() { + return arrCapacity; + } + + /* 訪問元素 */ + public int Get(int index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + return arr[index]; + } + + /* 更新元素 */ + public void Set(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + arr[index] = num; + } + + /* 在尾部新增元素 */ + public void Add(int num) { + // 元素數量超出容量時,觸發擴容機制 + if (arrSize == arrCapacity) + ExtendCapacity(); + arr[arrSize] = num; + // 更新元素數量 + arrSize++; + } + + /* 在中間插入元素 */ + public void Insert(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (arrSize == arrCapacity) + ExtendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (int j = arrSize - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 更新元素數量 + arrSize++; + } + + /* 刪除元素 */ + public int Remove(int index) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + int num = arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (int j = index; j < arrSize - 1; j++) { + arr[j] = arr[j + 1]; + } + // 更新元素數量 + arrSize--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + public void ExtendCapacity() { + // 新建一個長度為 arrCapacity * extendRatio 的陣列,並將原陣列複製到新陣列 + Array.Resize(ref arr, arrCapacity * extendRatio); + // 更新串列容量 + arrCapacity = arr.Length; + } + + /* 將串列轉換為陣列 */ + public int[] ToArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] arr = new int[arrSize]; + for (int i = 0; i < arrSize; i++) { + arr[i] = Get(i); + } + return arr; + } +} + +public class my_list { + [Test] + public void Test() { + /* 初始化串列 */ + MyList nums = new(); + /* 在尾部新增元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("串列 nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,長度 = " + nums.Size()); + + /* 在中間插入元素 */ + nums.Insert(3, 6); + Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 刪除元素 */ + nums.Remove(3); + Console.WriteLine("刪除索引 3 處的元素,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 訪問元素 */ + int num = nums.Get(1); + Console.WriteLine("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums.Set(1, 0); + Console.WriteLine("將索引 1 處的元素更新為 0 ,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.Add(i); + } + Console.WriteLine("擴容後的串列 nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,長度 = " + nums.Size()); + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/n_queens.cs b/zh-hant/codes/csharp/chapter_backtracking/n_queens.cs new file mode 100644 index 0000000000..6112035885 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/n_queens.cs @@ -0,0 +1,76 @@ +/** + * File: n_queens.cs + * Created Time: 2023-05-04 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class n_queens { + /* 回溯演算法:n 皇后 */ + void Backtrack(int row, int n, List> state, List>> res, + bool[] cols, bool[] diags1, bool[] diags2) { + // 當放置完所有行時,記錄解 + if (row == n) { + List> copyState = []; + foreach (List sRow in state) { + copyState.Add(new List(sRow)); + } + res.Add(copyState); + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + Backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* 求解 n 皇后 */ + List>> NQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + List> state = []; + for (int i = 0; i < n; i++) { + List row = []; + for (int j = 0; j < n; j++) { + row.Add("#"); + } + state.Add(row); + } + bool[] cols = new bool[n]; // 記錄列是否有皇后 + bool[] diags1 = new bool[2 * n - 1]; // 記錄主對角線上是否有皇后 + bool[] diags2 = new bool[2 * n - 1]; // 記錄次對角線上是否有皇后 + List>> res = []; + + Backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + [Test] + public void Test() { + int n = 4; + List>> res = NQueens(n); + + Console.WriteLine("輸入棋盤長寬為 " + n); + Console.WriteLine("皇后放置方案共有 " + res.Count + " 種"); + foreach (List> state in res) { + Console.WriteLine("--------------------"); + foreach (List row in state) { + PrintUtil.PrintList(row); + } + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/permutations_i.cs b/zh-hant/codes/csharp/chapter_backtracking/permutations_i.cs new file mode 100644 index 0000000000..b61fc0c957 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/permutations_i.cs @@ -0,0 +1,53 @@ +/** + * File: permutations_i.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_i { + /* 回溯演算法:全排列 I */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.Add(choice); + // 進行下一輪選擇 + Backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 I */ + List> PermutationsI(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 3]; + + List> res = PermutationsI(nums); + + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums)); + Console.WriteLine("所有排列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/permutations_ii.cs b/zh-hant/codes/csharp/chapter_backtracking/permutations_ii.cs new file mode 100644 index 0000000000..dd286058f0 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/permutations_ii.cs @@ -0,0 +1,55 @@ +/** + * File: permutations_ii.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_ii { + /* 回溯演算法:全排列 II */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + HashSet duplicated = []; + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.Contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.Add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.Add(choice); + // 進行下一輪選擇 + Backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 II */ + List> PermutationsII(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 2]; + + List> res = PermutationsII(nums); + + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums)); + Console.WriteLine("所有排列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs new file mode 100644 index 0000000000..938a00b0dd --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs @@ -0,0 +1,37 @@ +/** + * File: preorder_traversal_i_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_i_compact { + List res = []; + + /* 前序走訪:例題一 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 記錄解 + res.Add(root); + } + PreOrder(root.left); + PreOrder(root.right); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 前序走訪 + PreOrder(root); + + Console.WriteLine("\n輸出所有值為 7 的節點"); + PrintUtil.PrintList(res.Select(p => p.val).ToList()); + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs new file mode 100644 index 0000000000..145593f228 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_ii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_ii_compact { + List path = []; + List> res = []; + + /* 前序走訪:例題二 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + // 嘗試 + path.Add(root); + if (root.val == 7) { + // 記錄解 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 前序走訪 + PreOrder(root); + + Console.WriteLine("\n輸出所有根節點到節點 7 的路徑"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs new file mode 100644 index 0000000000..7aa13837d2 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs @@ -0,0 +1,45 @@ +/** + * File: preorder_traversal_iii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_compact { + List path = []; + List> res = []; + + /* 前序走訪:例題三 */ + void PreOrder(TreeNode? root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 嘗試 + path.Add(root); + if (root.val == 7) { + // 記錄解 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 前序走訪 + PreOrder(root); + + Console.WriteLine("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs new file mode 100644 index 0000000000..3d8c1a57b4 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs @@ -0,0 +1,72 @@ +/** + * File: preorder_traversal_iii_template.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_template { + /* 判斷當前狀態是否為解 */ + bool IsSolution(List state) { + return state.Count != 0 && state[^1].val == 7; + } + + /* 記錄解 */ + void RecordSolution(List state, List> res) { + res.Add(new List(state)); + } + + /* 判斷在當前狀態下,該選擇是否合法 */ + bool IsValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 更新狀態 */ + void MakeChoice(List state, TreeNode choice) { + state.Add(choice); + } + + /* 恢復狀態 */ + void UndoChoice(List state, TreeNode choice) { + state.RemoveAt(state.Count - 1); + } + + /* 回溯演算法:例題三 */ + void Backtrack(List state, List choices, List> res) { + // 檢查是否為解 + if (IsSolution(state)) { + // 記錄解 + RecordSolution(state, res); + } + // 走訪所有選擇 + foreach (TreeNode choice in choices) { + // 剪枝:檢查選擇是否合法 + if (IsValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + MakeChoice(state, choice); + // 進行下一輪選擇 + Backtrack(state, [choice.left!, choice.right!], res); + // 回退:撤銷選擇,恢復到之前的狀態 + UndoChoice(state, choice); + } + } + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 回溯演算法 + List> res = []; + List choices = [root!]; + Backtrack([], choices, res); + + Console.WriteLine("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i.cs b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i.cs new file mode 100644 index 0000000000..8179946623 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i.cs @@ -0,0 +1,55 @@ +/** +* File: subset_sum_i.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i { + /* 回溯演算法:子集和 I */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.Add(choices[i]); + // 進行下一輪選擇 + Backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I */ + List> SubsetSumI(int[] nums, int target) { + List state = []; // 狀態(子集) + Array.Sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumI(nums, target); + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等於 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs new file mode 100644 index 0000000000..cf93538dbb --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs @@ -0,0 +1,53 @@ +/** +* File: subset_sum_i_naive.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i_naive { + /* 回溯演算法:子集和 I */ + void Backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.Length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.Add(choices[i]); + // 進行下一輪選擇 + Backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I(包含重複子集) */ + List> SubsetSumINaive(int[] nums, int target) { + List state = []; // 狀態(子集) + int total = 0; // 子集和 + List> res = []; // 結果串列(子集串列) + Backtrack(state, target, total, nums, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumINaive(nums, target); + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等於 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + Console.WriteLine("請注意,該方法輸出的結果包含重複集合"); + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/subset_sum_ii.cs b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_ii.cs new file mode 100644 index 0000000000..bd442b66b8 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_ii.cs @@ -0,0 +1,60 @@ +/** +* File: subset_sum_ii.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_ii { + /* 回溯演算法:子集和 II */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.Add(choices[i]); + // 進行下一輪選擇 + Backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 II */ + List> SubsetSumII(int[] nums, int target) { + List state = []; // 狀態(子集) + Array.Sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [4, 4, 5]; + int target = 9; + List> res = SubsetSumII(nums, target); + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等於 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/iteration.cs b/zh-hant/codes/csharp/chapter_computational_complexity/iteration.cs new file mode 100644 index 0000000000..6065cd2662 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/iteration.cs @@ -0,0 +1,77 @@ +/** +* File: iteration.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class iteration { + /* 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 += 1; // 更新條件變數 + } + return res; + } + + /* while 迴圈(兩次更新) */ + int WhileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i += 1; + i *= 2; + } + return res; + } + + /* 雙層 for 迴圈 */ + string NestedForLoop(int n) { + StringBuilder res = new(); + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.Append($"({i}, {j}), "); + } + } + return res.ToString(); + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = ForLoop(n); + Console.WriteLine("\nfor 迴圈的求和結果 res = " + res); + + res = WhileLoop(n); + Console.WriteLine("\nwhile 迴圈的求和結果 res = " + res); + + res = WhileLoopII(n); + Console.WriteLine("\nwhile 迴圈(兩次更新)求和結果 res = " + res); + + string resStr = NestedForLoop(n); + Console.WriteLine("\n雙層 for 迴圈的走訪結果 " + resStr); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/recursion.cs b/zh-hant/codes/csharp/chapter_computational_complexity/recursion.cs new file mode 100644 index 0000000000..159b7aae83 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/recursion.cs @@ -0,0 +1,78 @@ +/** +* File: recursion.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class recursion { + /* 遞迴 */ + int Recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = Recur(n - 1); + // 迴:返回結果 + return n + res; + } + + /* 使用迭代模擬遞迴 */ + int ForLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + Stack stack = new(); + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.Push(i); + } + // 迴:返回結果 + while (stack.Count > 0) { + // 透過“出堆疊操作”模擬“迴” + res += stack.Pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* 尾遞迴 */ + int TailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return TailRecur(n - 1, res + n); + } + + /* 費波那契數列:遞迴 */ + int Fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = Fib(n - 1) + Fib(n - 2); + // 返回結果 f(n) + return res; + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = Recur(n); + Console.WriteLine("\n遞迴函式的求和結果 res = " + res); + + res = ForLoopRecur(n); + Console.WriteLine("\n使用迭代模擬遞迴求和結果 res = " + res); + + res = TailRecur(n, 0); + Console.WriteLine("\n尾遞迴函式的求和結果 res = " + res); + + res = Fib(n); + Console.WriteLine("\n費波那契數列的第 " + n + " 項為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/space_complexity.cs b/zh-hant/codes/csharp/chapter_computational_complexity/space_complexity.cs new file mode 100644 index 0000000000..e9b065ff80 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/space_complexity.cs @@ -0,0 +1,104 @@ +/** + * File: space_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class space_complexity { + /* 函式 */ + int Function() { + // 執行某些操作 + return 0; + } + + /* 常數階 */ + void Constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new(0); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + Function(); + } + } + + /* 線性階 */ + void Linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + int[] nums = new int[n]; + // 長度為 n 的串列佔用 O(n) 空間 + List nodes = []; + for (int i = 0; i < n; i++) { + nodes.Add(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + Dictionary map = []; + for (int i = 0; i < n; i++) { + map.Add(i, i.ToString()); + } + } + + /* 線性階(遞迴實現) */ + void LinearRecur(int n) { + Console.WriteLine("遞迴 n = " + n); + if (n == 1) return; + LinearRecur(n - 1); + } + + /* 平方階 */ + void Quadratic(int n) { + // 矩陣佔用 O(n^2) 空間 + int[,] numMatrix = new int[n, n]; + // 二維串列佔用 O(n^2) 空間 + List> numList = []; + for (int i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.Add(0); + } + numList.Add(tmp); + } + } + + /* 平方階(遞迴實現) */ + int QuadraticRecur(int n) { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("遞迴 n = " + n + " 中的 nums 長度 = " + nums.Length); + return QuadraticRecur(n - 1); + } + + /* 指數階(建立滿二元樹) */ + TreeNode? BuildTree(int n) { + if (n == 0) return null; + TreeNode root = new(0) { + left = BuildTree(n - 1), + right = BuildTree(n - 1) + }; + return root; + } + + [Test] + public void Test() { + int n = 5; + // 常數階 + Constant(n); + // 線性階 + Linear(n); + LinearRecur(n); + // 平方階 + Quadratic(n); + QuadraticRecur(n); + // 指數階 + TreeNode? root = BuildTree(n); + PrintUtil.PrintTree(root); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/time_complexity.cs b/zh-hant/codes/csharp/chapter_computational_complexity/time_complexity.cs new file mode 100644 index 0000000000..5fd3021e05 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/time_complexity.cs @@ -0,0 +1,195 @@ +/** + * File: time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class time_complexity { + void Algorithm(int n) { + int a = 1; // +0(技巧 1) + a += n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + + // 演算法 A 時間複雜度:常數階 + void AlgorithmA(int n) { + Console.WriteLine(0); + } + + // 演算法 B 時間複雜度:線性階 + void AlgorithmB(int n) { + for (int i = 0; i < n; i++) { + Console.WriteLine(0); + } + } + + // 演算法 C 時間複雜度:常數階 + void AlgorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + Console.WriteLine(0); + } + } + + /* 常數階 */ + 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 count = 0; + // 迴圈次數與陣列長度成正比 + foreach (int num in nums) { + count++; + } + return count; + } + + /* 平方階 */ + int Quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* 平方階(泡沫排序) */ + int BubbleSort(int[] nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; + } + + /* 指數階(迴圈實現) */ + int Exponential(int n) { + int count = 0, 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 /= 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; + // 從 1 個分裂出 n 個 + for (int i = 0; i < n; i++) { + count += FactorialRecur(n - 1); + } + return count; + } + + [Test] + public void Test() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + Console.WriteLine("輸入資料大小 n = " + n); + + int count = Constant(n); + Console.WriteLine("常數階的操作數量 = " + count); + + count = Linear(n); + Console.WriteLine("線性階的操作數量 = " + count); + count = ArrayTraversal(new int[n]); + Console.WriteLine("線性階(走訪陣列)的操作數量 = " + count); + + count = Quadratic(n); + Console.WriteLine("平方階的操作數量 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = BubbleSort(nums); + Console.WriteLine("平方階(泡沫排序)的操作數量 = " + count); + + count = Exponential(n); + Console.WriteLine("指數階(迴圈實現)的操作數量 = " + count); + count = ExpRecur(n); + Console.WriteLine("指數階(遞迴實現)的操作數量 = " + count); + + count = Logarithmic(n); + Console.WriteLine("對數階(迴圈實現)的操作數量 = " + count); + count = LogRecur(n); + Console.WriteLine("對數階(遞迴實現)的操作數量 = " + count); + + count = LinearLogRecur(n); + Console.WriteLine("線性對數階(遞迴實現)的操作數量 = " + count); + + count = FactorialRecur(n); + Console.WriteLine("階乘階(遞迴實現)的操作數量 = " + count); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs b/zh-hant/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs new file mode 100644 index 0000000000..5359ae3f81 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs @@ -0,0 +1,49 @@ +/** + * File: worst_best_time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class worst_best_time_complexity { + /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ + int[] RandomNumbers(int n) { + int[] nums = new int[n]; + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + + // 隨機打亂陣列元素 + for (int i = 0; i < nums.Length; i++) { + int index = new Random().Next(i, nums.Length); + (nums[i], nums[index]) = (nums[index], nums[i]); + } + return nums; + } + + /* 查詢陣列 nums 中數字 1 所在索引 */ + int FindOne(int[] nums) { + for (int i = 0; i < nums.Length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i; + } + return -1; + } + + + /* Driver Code */ + [Test] + public void Test() { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = RandomNumbers(n); + int index = FindOne(nums); + Console.WriteLine("\n陣列 [ 1, 2, ..., n ] 被打亂後 = " + string.Join(",", nums)); + Console.WriteLine("數字 1 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs b/zh-hant/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs new file mode 100644 index 0000000000..5832ee668d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs @@ -0,0 +1,46 @@ +/** +* File: binary_search_recur.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class binary_search_recur { + /* 二分搜尋:問題 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 n = nums.Length; + // 求解問題 f(0, n-1) + return DFS(nums, target, 0, n - 1); + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + int index = BinarySearch(nums, target); + Console.WriteLine("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/csharp/chapter_divide_and_conquer/build_tree.cs b/zh-hant/codes/csharp/chapter_divide_and_conquer/build_tree.cs new file mode 100644 index 0000000000..8c07ec787a --- /dev/null +++ b/zh-hant/codes/csharp/chapter_divide_and_conquer/build_tree.cs @@ -0,0 +1,49 @@ +/** +* File: build_tree.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class build_tree { + /* 構建二元樹:分治 */ + TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { + // 子樹區間為空時終止 + if (r - l < 0) + return null; + // 初始化根節點 + TreeNode root = new(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]; + // 子問題:構建左子樹 + root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; + } + + /* 構建二元樹 */ + TreeNode? BuildTree(int[] preorder, int[] inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + Dictionary inorderMap = []; + for (int i = 0; i < inorder.Length; i++) { + inorderMap.TryAdd(inorder[i], i); + } + TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); + return root; + } + + [Test] + public void Test() { + int[] preorder = [3, 9, 2, 1, 7]; + int[] inorder = [9, 3, 1, 2, 7]; + Console.WriteLine("前序走訪 = " + string.Join(", ", preorder)); + Console.WriteLine("中序走訪 = " + string.Join(", ", inorder)); + + TreeNode? root = BuildTree(preorder, inorder); + Console.WriteLine("構建的二元樹為:"); + PrintUtil.PrintTree(root); + } +} diff --git a/zh-hant/codes/csharp/chapter_divide_and_conquer/hanota.cs b/zh-hant/codes/csharp/chapter_divide_and_conquer/hanota.cs new file mode 100644 index 0000000000..404f7dde0c --- /dev/null +++ b/zh-hant/codes/csharp/chapter_divide_and_conquer/hanota.cs @@ -0,0 +1,59 @@ +/** +* File: hanota.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class hanota { + /* 移動一個圓盤 */ + void Move(List src, List tar) { + // 從 src 頂部拿出一個圓盤 + int pan = src[^1]; + src.RemoveAt(src.Count - 1); + // 將圓盤放入 tar 頂部 + tar.Add(pan); + } + + /* 求解河內塔問題 f(i) */ + void DFS(int i, List src, List buf, List tar) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i == 1) { + Move(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + DFS(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + Move(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + DFS(i - 1, buf, src, tar); + } + + /* 求解河內塔問題 */ + void SolveHanota(List A, List B, List C) { + int n = A.Count; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + DFS(n, A, B, C); + } + + [Test] + public void Test() { + // 串列尾部是柱子頂部 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + Console.WriteLine("初始狀態下:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + + SolveHanota(A, B, C); + + Console.WriteLine("圓盤移動完成後:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs new file mode 100644 index 0000000000..bb7f384dd3 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs @@ -0,0 +1,41 @@ +/** +* File: climbing_stairs_backtrack.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_backtrack { + /* 回溯 */ + void Backtrack(List choices, int state, int n, List res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res[0]++; + // 走訪所有選擇 + foreach (int choice in choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + Backtrack(choices, state + choice, n, res); + // 回退 + } + } + + /* 爬樓梯:回溯 */ + int ClimbingStairsBacktrack(int n) { + List choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + List res = [0]; // 使用 res[0] 記錄方案數量 + Backtrack(choices, state, n, res); + return res[0]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsBacktrack(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs new file mode 100644 index 0000000000..170735b82b --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs @@ -0,0 +1,36 @@ +/** +* File: climbing_stairs_constraint_dp.cs +* Created Time: 2023-07-03 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 帶約束爬樓梯:動態規劃 */ + int ClimbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + int[,] dp = new int[n + 1, 3]; + // 初始狀態:預設最小子問題的解 + dp[1, 1] = 1; + dp[1, 2] = 0; + dp[2, 1] = 0; + dp[2, 2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i, 1] = dp[i - 1, 2]; + dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; + } + return dp[n, 1] + dp[n, 2]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsConstraintDP(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs new file mode 100644 index 0000000000..cdd0e8d07d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs @@ -0,0 +1,31 @@ +/** +* File: climbing_stairs_dfs.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 搜尋 */ + 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); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFS(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs new file mode 100644 index 0000000000..28d7864a37 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs @@ -0,0 +1,39 @@ +/** +* File: climbing_stairs_dfs_mem.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs_mem { + /* 記憶化搜尋 */ + 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 = new int[n + 1]; + Array.Fill(mem, -1); + return DFS(n, mem); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFSMem(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs new file mode 100644 index 0000000000..84b62668e8 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs @@ -0,0 +1,49 @@ +/** +* File: climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 爬樓梯:動態規劃 */ + int ClimbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* 爬樓梯:空間最佳化後的動態規劃 */ + int ClimbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int n = 9; + + int res = ClimbingStairsDP(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + + res = ClimbingStairsDPComp(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change.cs new file mode 100644 index 0000000000..724ac57b25 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change.cs @@ -0,0 +1,71 @@ +/** +* File: coin_change.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change { + /* 零錢兌換:動態規劃 */ + int CoinChangeDP(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 狀態轉移:首行首列 + 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] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); + } + } + } + return dp[n, amt] != MAX ? dp[n, amt] : -1; + } + + /* 零錢兌換:空間最佳化後的動態規劃 */ + int CoinChangeDPComp(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Array.Fill(dp, 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] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 4; + + // 動態規劃 + int res = CoinChangeDP(coins, amt); + Console.WriteLine("湊到目標金額所需的最少硬幣數量為 " + res); + + // 空間最佳化後的動態規劃 + res = CoinChangeDPComp(coins, amt); + Console.WriteLine("湊到目標金額所需的最少硬幣數量為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs new file mode 100644 index 0000000000..e25453fb0a --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs @@ -0,0 +1,68 @@ +/** +* File: coin_change_ii.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change_ii { + /* 零錢兌換 II:動態規劃 */ + int CoinChangeIIDP(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i, 0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i, a] = dp[i - 1, a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; + } + } + } + return dp[n, amt]; + } + + /* 零錢兌換 II:空間最佳化後的動態規劃 */ + int CoinChangeIIDPComp(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 5; + + // 動態規劃 + int res = CoinChangeIIDP(coins, amt); + Console.WriteLine("湊出目標金額的硬幣組合數量為 " + res); + + // 空間最佳化後的動態規劃 + res = CoinChangeIIDPComp(coins, amt); + Console.WriteLine("湊出目標金額的硬幣組合數量為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/edit_distance.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/edit_distance.cs new file mode 100644 index 0000000000..59f4bd05e9 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/edit_distance.cs @@ -0,0 +1,141 @@ +/** +* File: edit_distance.cs +* Created Time: 2023-07-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class edit_distance { + /* 編輯距離:暴力搜尋 */ + int EditDistanceDFS(string s, string t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) + return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) + return i; + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) + return EditDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = EditDistanceDFS(s, t, i, j - 1); + int delete = EditDistanceDFS(s, t, i - 1, j); + int replace = EditDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.Min(Math.Min(insert, delete), replace) + 1; + } + + /* 編輯距離:記憶化搜尋 */ + int EditDistanceDFSMem(string s, string t, 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, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); + int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); + int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* 編輯距離:動態規劃 */ + int EditDistanceDP(string s, string t) { + int n = s.Length, m = t.Length; + int[,] dp = new int[n + 1, m + 1]; + // 狀態轉移:首行首列 + 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] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; + } + } + } + return dp[n, m]; + } + + /* 編輯距離:空間最佳化後的動態規劃 */ + int EditDistanceDPComp(string s, string t) { + int n = s.Length, m = t.Length; + int[] dp = new int[m + 1]; + // 狀態轉移:首行 + 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] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; + } + + [Test] + public void Test() { + string s = "bag"; + string t = "pack"; + int n = s.Length, m = t.Length; + + // 暴力搜尋 + int res = EditDistanceDFS(s, t, n, m); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[m + 1]; + Array.Fill(mem[i], -1); + } + + res = EditDistanceDFSMem(s, t, mem, n, m); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 動態規劃 + res = EditDistanceDP(s, t); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 空間最佳化後的動態規劃 + res = EditDistanceDPComp(s, t); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/knapsack.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/knapsack.cs new file mode 100644 index 0000000000..c3cf2d7101 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/knapsack.cs @@ -0,0 +1,118 @@ +/** +* File: knapsack.cs +* Created Time: 2023-07-07 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class knapsack { + /* 0-1 背包:暴力搜尋 */ + int KnapsackDFS(int[] weight, int[] val, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (weight[i - 1] > c) { + return KnapsackDFS(weight, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = KnapsackDFS(weight, val, i - 1, c); + int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return Math.Max(no, yes); + } + + /* 0-1 背包:記憶化搜尋 */ + int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (weight[i - 1] > c) { + return KnapsackDFSMem(weight, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = KnapsackDFSMem(weight, val, mem, i - 1, c); + int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.Max(no, yes); + return mem[i][c]; + } + + /* 0-1 背包:動態規劃 */ + int KnapsackDP(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (weight[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i, c] = dp[i - 1, c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); + } + } + } + return dp[n, cap]; + } + + /* 0-1 背包:空間最佳化後的動態規劃 */ + int KnapsackDPComp(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c > 0; c--) { + if (weight[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] weight = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = weight.Length; + + // 暴力搜尋 + int res = KnapsackDFS(weight, val, n, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[cap + 1]; + Array.Fill(mem[i], -1); + } + res = KnapsackDFSMem(weight, val, mem, n, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 動態規劃 + res = KnapsackDP(weight, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = KnapsackDPComp(weight, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs new file mode 100644 index 0000000000..d43d965638 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs @@ -0,0 +1,53 @@ +/** +* File: min_cost_climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_cost_climbing_stairs_dp { + /* 爬樓梯最小代價:動態規劃 */ + int MinCostClimbingStairsDP(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ + int MinCostClimbingStairsDPComp(int[] cost) { + int n = cost.Length - 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 = Math.Min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + Console.WriteLine("輸入樓梯的代價串列為"); + PrintUtil.PrintList(cost); + + int res = MinCostClimbingStairsDP(cost); + Console.WriteLine($"爬完樓梯的最低代價為 {res}"); + + res = MinCostClimbingStairsDPComp(cost); + Console.WriteLine($"爬完樓梯的最低代價為 {res}"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/min_path_sum.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/min_path_sum.cs new file mode 100644 index 0000000000..f3f77e09ca --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/min_path_sum.cs @@ -0,0 +1,127 @@ +/** +* File: min_path_sum.cs +* Created Time: 2023-07-10 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_path_sum { + /* 最小路徑和:暴力搜尋 */ + int MinPathSumDFS(int[][] grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = MinPathSumDFS(grid, i - 1, j); + int left = MinPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.Min(left, up) + grid[i][j]; + } + + /* 最小路徑和:記憶化搜尋 */ + int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 若已有記錄,則直接返回 + 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] = Math.Min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* 最小路徑和:動態規劃 */ + int MinPathSumDP(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[,] dp = new int[n, m]; + dp[0, 0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0, j] = dp[0, j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i, 0] = dp[i - 1, 0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; + } + } + return dp[n - 1, m - 1]; + } + + /* 最小路徑和:空間最佳化後的動態規劃 */ + int MinPathSumDPComp(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[] dp = new int[m]; + dp[0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + [Test] + public void Test() { + int[][] grid = + [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2] + ]; + + int n = grid.Length, m = grid[0].Length; + + // 暴力搜尋 + int res = MinPathSumDFS(grid, n - 1, m - 1); + Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n][]; + for (int i = 0; i < n; i++) { + mem[i] = new int[m]; + Array.Fill(mem[i], -1); + } + res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); + Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); + + // 動態規劃 + res = MinPathSumDP(grid); + Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); + + // 空間最佳化後的動態規劃 + res = MinPathSumDPComp(grid); + Console.WriteLine("從左上角到右下角的最小路徑和為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs new file mode 100644 index 0000000000..c917b941a4 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs @@ -0,0 +1,64 @@ +/** +* File: unbounded_knapsack.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全背包:動態規劃 */ + int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 狀態轉移 + 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] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n, cap]; + } + + /* 完全背包:空間最佳化後的動態規劃 */ + int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + 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] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] wgt = [1, 2, 3]; + int[] val = [5, 11, 15]; + int cap = 4; + + // 動態規劃 + int res = UnboundedKnapsackDP(wgt, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = UnboundedKnapsackDPComp(wgt, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_adjacency_list.cs b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_list.cs new file mode 100644 index 0000000000..7ad6e84ec5 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_list.cs @@ -0,0 +1,122 @@ +/** + * File: graph_adjacency_list.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 基於鄰接表實現的無向圖類別 */ +public class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + public Dictionary> adjList; + + /* 建構子 */ + public GraphAdjList(Vertex[][] edges) { + adjList = []; + // 新增所有頂點和邊 + foreach (Vertex[] edge in edges) { + AddVertex(edge[0]); + AddVertex(edge[1]); + AddEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int Size() { + return adjList.Count; + } + + /* 新增邊 */ + public void AddEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 新增邊 vet1 - vet2 + adjList[vet1].Add(vet2); + adjList[vet2].Add(vet1); + } + + /* 刪除邊 */ + public void RemoveEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 刪除邊 vet1 - vet2 + adjList[vet1].Remove(vet2); + adjList[vet2].Remove(vet1); + } + + /* 新增頂點 */ + public void AddVertex(Vertex vet) { + if (adjList.ContainsKey(vet)) + return; + // 在鄰接表中新增一個新鏈結串列 + adjList.Add(vet, []); + } + + /* 刪除頂點 */ + public void RemoveVertex(Vertex vet) { + if (!adjList.ContainsKey(vet)) + throw new InvalidOperationException(); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.Remove(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + foreach (List list in adjList.Values) { + list.Remove(vet); + } + } + + /* 列印鄰接表 */ + public void Print() { + Console.WriteLine("鄰接表 ="); + foreach (KeyValuePair> pair in adjList) { + List tmp = []; + foreach (Vertex vertex in pair.Value) + tmp.Add(vertex.val); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); + } + } +} + +public class graph_adjacency_list { + [Test] + public void Test() { + /* 初始化無向圖 */ + Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); + Vertex[][] edges = + [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]] + ]; + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.AddEdge(v[0], v[2]); + Console.WriteLine("\n新增邊 1-2 後,圖為"); + graph.Print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.RemoveEdge(v[0], v[1]); + Console.WriteLine("\n刪除邊 1-3 後,圖為"); + graph.Print(); + + /* 新增頂點 */ + Vertex v5 = new(6); + graph.AddVertex(v5); + Console.WriteLine("\n新增頂點 6 後,圖為"); + graph.Print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.RemoveVertex(v[1]); + Console.WriteLine("\n刪除頂點 3 後,圖為"); + graph.Print(); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_adjacency_matrix.cs b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_matrix.cs new file mode 100644 index 0000000000..6e900e1786 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_matrix.cs @@ -0,0 +1,137 @@ +/** + * File: graph_adjacency_matrix.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + List vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + List> adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + foreach (int val in vertices) { + AddVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + foreach (int[] e in edges) { + AddEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + int Size() { + return vertices.Count; + } + + /* 新增頂點 */ + public void AddVertex(int val) { + int n = Size(); + // 向頂點串列中新增新頂點的值 + vertices.Add(val); + // 在鄰接矩陣中新增一行 + List newRow = new(n); + for (int j = 0; j < n; j++) { + newRow.Add(0); + } + adjMat.Add(newRow); + // 在鄰接矩陣中新增一列 + foreach (List row in adjMat) { + row.Add(0); + } + } + + /* 刪除頂點 */ + public void RemoveVertex(int index) { + if (index >= Size()) + throw new IndexOutOfRangeException(); + // 在頂點串列中移除索引 index 的頂點 + vertices.RemoveAt(index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.RemoveAt(index); + // 在鄰接矩陣中刪除索引 index 的列 + foreach (List row in adjMat) { + row.RemoveAt(index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void AddEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void RemoveEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + public void Print() { + Console.Write("頂點串列 = "); + PrintUtil.PrintList(vertices); + Console.WriteLine("鄰接矩陣 ="); + PrintUtil.PrintMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + [Test] + public void Test() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + int[] vertices = [1, 3, 2, 5, 4]; + int[][] edges = + [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4] + ]; + GraphAdjMat graph = new(vertices, edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.AddEdge(0, 2); + Console.WriteLine("\n新增邊 1-2 後,圖為"); + graph.Print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.RemoveEdge(0, 1); + Console.WriteLine("\n刪除邊 1-3 後,圖為"); + graph.Print(); + + /* 新增頂點 */ + graph.AddVertex(6); + Console.WriteLine("\n新增頂點 6 後,圖為"); + graph.Print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.RemoveVertex(1); + Console.WriteLine("\n刪除頂點 3 後,圖為"); + graph.Print(); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_bfs.cs b/zh-hant/codes/csharp/chapter_graph/graph_bfs.cs new file mode 100644 index 0000000000..f3a82e5243 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_bfs.cs @@ -0,0 +1,58 @@ +/** + * File: graph_bfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_bfs { + /* 廣度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + List GraphBFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + HashSet visited = [startVet]; + // 佇列用於實現 BFS + Queue que = new(); + que.Enqueue(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.Count > 0) { + Vertex vet = que.Dequeue(); // 佇列首頂點出隊 + res.Add(vet); // 記錄訪問頂點 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.Enqueue(adjVet); // 只入列未訪問的頂點 + visited.Add(adjVet); // 標記該頂點已被訪問 + } + } + + // 返回頂點走訪序列 + return res; + } + + [Test] + public void Test() { + /* 初始化無向圖 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + Vertex[][] edges = + [ + [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]] + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 廣度優先走訪 */ + List res = GraphBFS(graph, v[0]); + Console.WriteLine("\n廣度優先走訪(BFS)頂點序列為"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_dfs.cs b/zh-hant/codes/csharp/chapter_graph/graph_dfs.cs new file mode 100644 index 0000000000..a310403663 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_dfs.cs @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_dfs { + /* 深度優先走訪輔助函式 */ + void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { + res.Add(vet); // 記錄訪問頂點 + visited.Add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + DFS(graph, visited, res, adjVet); + } + } + + /* 深度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + List GraphDFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + HashSet visited = []; + DFS(graph, visited, res, startVet); + return res; + } + + [Test] + public void Test() { + /* 初始化無向圖 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); + Vertex[][] edges = + [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 深度優先走訪 */ + List res = GraphDFS(graph, v[0]); + Console.WriteLine("\n深度優先走訪(DFS)頂點序列為"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/zh-hant/codes/csharp/chapter_greedy/coin_change_greedy.cs b/zh-hant/codes/csharp/chapter_greedy/coin_change_greedy.cs new file mode 100644 index 0000000000..85a37a0a79 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/coin_change_greedy.cs @@ -0,0 +1,54 @@ +/** +* File: coin_change_greedy.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class coin_change_greedy { + /* 零錢兌換:貪婪 */ + int CoinChangeGreedy(int[] coins, int amt) { + // 假設 coins 串列有序 + int i = coins.Length - 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; + } + + [Test] + public void Test() { + // 貪婪:能夠保證找到全域性最優解 + int[] coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50]; + amt = 60; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + Console.WriteLine("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50]; + amt = 98; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + Console.WriteLine("實際上需要的最少數量為 2 ,即 49 + 49"); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_greedy/fractional_knapsack.cs b/zh-hant/codes/csharp/chapter_greedy/fractional_knapsack.cs new file mode 100644 index 0000000000..d88bb3e81f --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/fractional_knapsack.cs @@ -0,0 +1,52 @@ +/** +* File: fractional_knapsack.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +/* 物品 */ +class Item(int w, int v) { + public int w = w; // 物品重量 + public int v = v; // 物品價值 +} + +public class fractional_knapsack { + /* 分數背包:貪婪 */ + double FractionalKnapsack(int[] wgt, int[] val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + Item[] items = new Item[wgt.Length]; + for (int i = 0; i < wgt.Length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); + // 迴圈貪婪選擇 + double res = 0; + foreach (Item item in items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (double)item.v / item.w * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; + } + + [Test] + public void Test() { + int[] wgt = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 貪婪演算法 + double res = FractionalKnapsack(wgt, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_greedy/max_capacity.cs b/zh-hant/codes/csharp/chapter_greedy/max_capacity.cs new file mode 100644 index 0000000000..da2f8c588b --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/max_capacity.cs @@ -0,0 +1,39 @@ +/** +* File: max_capacity.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_capacity { + /* 最大容量:貪婪 */ + int MaxCapacity(int[] ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.Length - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.Min(ht[i], ht[j]) * (j - i); + res = Math.Max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + [Test] + public void Test() { + int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪婪演算法 + int res = MaxCapacity(ht); + Console.WriteLine("最大容量為 " + res); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_greedy/max_product_cutting.cs b/zh-hant/codes/csharp/chapter_greedy/max_product_cutting.cs new file mode 100644 index 0000000000..e631bde785 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/max_product_cutting.cs @@ -0,0 +1,39 @@ +/** +* File: max_product_cutting.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_product_cutting { + /* 最大切分乘積:貪婪 */ + 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 (int)Math.Pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return (int)Math.Pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return (int)Math.Pow(3, a); + } + + [Test] + public void Test() { + int n = 58; + + // 貪婪演算法 + int res = MaxProductCutting(n); + Console.WriteLine("最大切分乘積為" + res); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_hashing/array_hash_map.cs b/zh-hant/codes/csharp/chapter_hashing/array_hash_map.cs new file mode 100644 index 0000000000..d7b83279c5 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/array_hash_map.cs @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +/* 鍵值對 int->string */ +class Pair(int key, string val) { + public int key = key; + public string val = val; +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + List buckets; + public ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + buckets = []; + for (int i = 0; i < 100; i++) { + buckets.Add(null); + } + } + + /* 雜湊函式 */ + int HashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查詢操作 */ + public string? Get(int key) { + int index = HashFunc(key); + Pair? pair = buckets[index]; + if (pair == null) return null; + return pair.val; + } + + /* 新增操作 */ + public void Put(int key, string val) { + Pair pair = new(key, val); + int index = HashFunc(key); + buckets[index] = pair; + } + + /* 刪除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // 置為 null ,代表刪除 + buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + public List PairSet() { + List pairSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + pairSet.Add(pair); + } + return pairSet; + } + + /* 獲取所有鍵 */ + public List KeySet() { + List keySet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + keySet.Add(pair.key); + } + return keySet; + } + + /* 獲取所有值 */ + public List ValueSet() { + List valueSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + valueSet.Add(pair.val); + } + return valueSet; + } + + /* 列印雜湊表 */ + public void Print() { + foreach (Pair kv in PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + } +} + + +public class array_hash_map { + [Test] + public void Test() { + /* 初始化雜湊表 */ + ArrayHashMap map = new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小囉"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鴨"); + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string? name = map.Get(15937); + Console.WriteLine("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(10583); + Console.WriteLine("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 走訪雜湊表 */ + Console.WriteLine("\n走訪鍵值對 Key->Value"); + foreach (Pair kv in map.PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + Console.WriteLine("\n單獨走訪鍵 Key"); + foreach (int key in map.KeySet()) { + Console.WriteLine(key); + } + Console.WriteLine("\n單獨走訪值 Value"); + foreach (string val in map.ValueSet()) { + Console.WriteLine(val); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/built_in_hash.cs b/zh-hant/codes/csharp/chapter_hashing/built_in_hash.cs new file mode 100644 index 0000000000..73b7e188ef --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/built_in_hash.cs @@ -0,0 +1,36 @@ +/** +* File: built_in_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class built_in_hash { + [Test] + public void Test() { + int num = 3; + int hashNum = num.GetHashCode(); + Console.WriteLine("整數 " + num + " 的雜湊值為 " + hashNum); + + bool bol = true; + int hashBol = bol.GetHashCode(); + Console.WriteLine("布林量 " + bol + " 的雜湊值為 " + hashBol); + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + Console.WriteLine("小數 " + dec + " 的雜湊值為 " + hashDec); + + string str = "Hello 演算法"; + int hashStr = str.GetHashCode(); + Console.WriteLine("字串 " + str + " 的雜湊值為 " + hashStr); + + object[] arr = [12836, "小哈"]; + int hashTup = arr.GetHashCode(); + Console.WriteLine("陣列 [" + string.Join(", ", arr) + "] 的雜湊值為 " + hashTup); + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + Console.WriteLine("節點物件 " + obj + " 的雜湊值為 " + hashObj); + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/hash_map.cs b/zh-hant/codes/csharp/chapter_hashing/hash_map.cs new file mode 100644 index 0000000000..7266bd103a --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/hash_map.cs @@ -0,0 +1,51 @@ + +/** + * File: hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +public class hash_map { + [Test] + public void Test() { + /* 初始化雜湊表 */ + Dictionary map = new() { + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + { 12836, "小哈" }, + { 15937, "小囉" }, + { 16750, "小算" }, + { 13276, "小法" }, + { 10583, "小鴨" } + }; + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + Console.WriteLine("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(10583); + Console.WriteLine("\n刪除 10583 後,雜湊表為\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* 走訪雜湊表 */ + Console.WriteLine("\n走訪鍵值對 Key->Value"); + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + Console.WriteLine("\n單獨走訪鍵 Key"); + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + Console.WriteLine("\n單獨走訪值 Value"); + foreach (string val in map.Values) { + Console.WriteLine(val); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/hash_map_chaining.cs b/zh-hant/codes/csharp/chapter_hashing/hash_map_chaining.cs new file mode 100644 index 0000000000..0e9fc6dd0c --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/hash_map_chaining.cs @@ -0,0 +1,144 @@ +/** +* File: hash_map_chaining.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + List> buckets; // 桶陣列 + + /* 建構子 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + } + + /* 雜湊函式 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* 查詢操作 */ + public string? Get(int key) { + int index = HashFunc(key); + // 走訪桶,若找到 key ,則返回對應 val + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + public void Put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (LoadFactor() > loadThres) { + Extend(); + } + int index = HashFunc(key); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + buckets[index].Add(new Pair(key, val)); + size++; + } + + /* 刪除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // 走訪桶,從中刪除鍵值對 + foreach (Pair pair in buckets[index].ToList()) { + if (pair.key == key) { + buckets[index].Remove(pair); + size--; + break; + } + } + } + + /* 擴容雜湊表 */ + void Extend() { + // 暫存原雜湊表 + List> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + foreach (List bucket in bucketsTmp) { + foreach (Pair pair in bucket) { + Put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + public void Print() { + foreach (List bucket in buckets) { + List res = []; + foreach (Pair pair in bucket) { + res.Add(pair.key + " -> " + pair.val); + } + foreach (string kv in res) { + Console.WriteLine(kv); + } + } + } +} + +public class hash_map_chaining { + [Test] + public void Test() { + /* 初始化雜湊表 */ + HashMapChaining map = new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小囉"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鴨"); + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string? name = map.Get(13276); + Console.WriteLine("\n輸入學號 13276 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(12836); + Console.WriteLine("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.Print(); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_hashing/hash_map_open_addressing.cs b/zh-hant/codes/csharp/chapter_hashing/hash_map_open_addressing.cs new file mode 100644 index 0000000000..3e393266c8 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/hash_map_open_addressing.cs @@ -0,0 +1,159 @@ +/** +* File: hash_map_open_addressing.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + int size; // 鍵值對數量 + int capacity = 4; // 雜湊表容量 + double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + int extendRatio = 2; // 擴容倍數 + Pair[] buckets; // 桶陣列 + Pair TOMBSTONE = new(-1, "-1"); // 刪除標記 + + /* 建構子 */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* 雜湊函式 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* 搜尋 key 對應的桶索引 */ + int FindBucket(int key) { + int index = HashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index].key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + public string? Get(int key) { + // 搜尋 key 對應的桶索引 + int index = FindBucket(key); + // 若找到鍵值對,則返回對應 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + public void Put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (LoadFactor() > loadThres) { + Extend(); + } + // 搜尋 key 對應的桶索引 + int index = FindBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = new Pair(key, val); + size++; + } + + /* 刪除操作 */ + public void Remove(int key) { + // 搜尋 key 對應的桶索引 + int index = FindBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 擴容雜湊表 */ + void Extend() { + // 暫存原雜湊表 + Pair[] bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + foreach (Pair pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + Put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + public void Print() { + foreach (Pair pair in buckets) { + if (pair == null) { + Console.WriteLine("null"); + } else if (pair == TOMBSTONE) { + Console.WriteLine("TOMBSTONE"); + } else { + Console.WriteLine(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + [Test] + public void Test() { + /* 初始化雜湊表 */ + HashMapOpenAddressing map = new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小囉"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鴨"); + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string? name = map.Get(13276); + Console.WriteLine("\n輸入學號 13276 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(16750); + Console.WriteLine("\n刪除 16750 後,雜湊表為\nKey -> Value"); + map.Print(); + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/simple_hash.cs b/zh-hant/codes/csharp/chapter_hashing/simple_hash.cs new file mode 100644 index 0000000000..9ab74faf26 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/simple_hash.cs @@ -0,0 +1,66 @@ +/** +* File: simple_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class simple_hash { + /* 加法雜湊 */ + int AddHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (hash + c) % MODULUS; + } + return (int)hash; + } + + /* 乘法雜湊 */ + int MulHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (31 * hash + c) % MODULUS; + } + return (int)hash; + } + + /* 互斥或雜湊 */ + int XorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash ^= c; + } + return hash & MODULUS; + } + + /* 旋轉雜湊 */ + int RotHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; + } + return (int)hash; + } + + [Test] + public void Test() { + string key = "Hello 演算法"; + + int hash = AddHash(key); + Console.WriteLine("加法雜湊值為 " + hash); + + hash = MulHash(key); + Console.WriteLine("乘法雜湊值為 " + hash); + + hash = XorHash(key); + Console.WriteLine("互斥或雜湊值為 " + hash); + + hash = RotHash(key); + Console.WriteLine("旋轉雜湊值為 " + hash); + } +} diff --git a/zh-hant/codes/csharp/chapter_heap/heap.cs b/zh-hant/codes/csharp/chapter_heap/heap.cs new file mode 100644 index 0000000000..71289029e1 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_heap/heap.cs @@ -0,0 +1,64 @@ +/** + * File: heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +public class heap { + void TestPush(PriorityQueue heap, int val) { + heap.Enqueue(val, val); // 元素入堆積 + Console.WriteLine($"\n元素 {val} 入堆積後\n"); + PrintUtil.PrintHeap(heap); + } + + void TestPop(PriorityQueue heap) { + int val = heap.Dequeue(); // 堆積頂元素出堆積 + Console.WriteLine($"\n堆積頂元素 {val} 出堆積後\n"); + PrintUtil.PrintHeap(heap); + } + + [Test] + public void Test() { + /* 初始化堆積 */ + // 初始化小頂堆積 + PriorityQueue minHeap = new(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparer 即可) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); + Console.WriteLine("以下測試樣例為大頂堆積"); + + /* 元素入堆積 */ + TestPush(maxHeap, 1); + TestPush(maxHeap, 3); + TestPush(maxHeap, 2); + TestPush(maxHeap, 5); + TestPush(maxHeap, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"堆積頂元素為 {peek}"); + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + + /* 獲取堆積大小 */ + int size = maxHeap.Count; + Console.WriteLine($"堆積元素數量為 {size}"); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.Count == 0; + Console.WriteLine($"堆積是否為空 {isEmpty}"); + + /* 輸入串列並建堆積 */ + var list = new int[] { 1, 3, 2, 5, 4 }; + minHeap = new PriorityQueue(list.Select(x => (x, x))); + Console.WriteLine("輸入串列並建立小頂堆積後"); + PrintUtil.PrintHeap(minHeap); + } +} diff --git a/zh-hant/codes/csharp/chapter_heap/my_heap.cs b/zh-hant/codes/csharp/chapter_heap/my_heap.cs new file mode 100644 index 0000000000..ccfb4d1f2c --- /dev/null +++ b/zh-hant/codes/csharp/chapter_heap/my_heap.cs @@ -0,0 +1,160 @@ +/** + * File: my_heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +/* 大頂堆積 */ +class MaxHeap { + // 使用串列而非陣列,這樣無須考慮擴容問題 + List maxHeap; + + /* 建構子,建立空堆積 */ + public MaxHeap() { + maxHeap = []; + } + + /* 建構子,根據輸入串列建堆積 */ + public MaxHeap(IEnumerable nums) { + // 將串列元素原封不動新增進堆積 + maxHeap = new List(nums); + // 堆積化除葉節點以外的其他所有節點 + var size = Parent(this.Size() - 1); + for (int i = size; i >= 0; i--) { + SiftDown(i); + } + } + + /* 獲取左子節點的索引 */ + int Left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int Right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int Parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 訪問堆積頂元素 */ + public int Peek() { + return maxHeap[0]; + } + + /* 元素入堆積 */ + public void Push(int val) { + // 新增節點 + maxHeap.Add(val); + // 從底至頂堆積化 + SiftUp(Size() - 1); + } + + /* 獲取堆積大小 */ + public int Size() { + return maxHeap.Count; + } + + /* 判斷堆積是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void SiftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = Parent(i); + // 若“越過根節點”或“節點無須修復”,則結束堆積化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交換兩節點 + Swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + public int Pop() { + // 判空處理 + if (IsEmpty()) + throw new IndexOutOfRangeException(); + // 交換根節點與最右葉節點(交換首元素與尾元素) + Swap(0, Size() - 1); + // 刪除節點 + int val = maxHeap.Last(); + maxHeap.RemoveAt(Size() - 1); + // 從頂至底堆積化 + SiftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void SiftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = Left(i), r = Right(i), ma = i; + if (l < Size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < Size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若“節點 i 最大”或“越過葉節點”,則結束堆積化 + if (ma == i) break; + // 交換兩節點 + Swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 交換元素 */ + void Swap(int i, int p) { + (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); + } + + /* 列印堆積(二元樹) */ + public void Print() { + var queue = new Queue(maxHeap); + PrintUtil.PrintHeap(queue); + } +} + +public class my_heap { + [Test] + public void Test() { + /* 初始化大頂堆積 */ + MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + Console.WriteLine("\n輸入串列並建堆積後"); + maxHeap.Print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"堆積頂元素為 {peek}"); + + /* 元素入堆積 */ + int val = 7; + maxHeap.Push(val); + Console.WriteLine($"元素 {val} 入堆積後"); + maxHeap.Print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.Pop(); + Console.WriteLine($"堆積頂元素 {peek} 出堆積後"); + maxHeap.Print(); + + /* 獲取堆積大小 */ + int size = maxHeap.Size(); + Console.WriteLine($"堆積元素數量為 {size}"); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.IsEmpty(); + Console.WriteLine($"堆積是否為空 {isEmpty}"); + } +} diff --git a/zh-hant/codes/csharp/chapter_heap/top_k.cs b/zh-hant/codes/csharp/chapter_heap/top_k.cs new file mode 100644 index 0000000000..3e0a2e2826 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_heap/top_k.cs @@ -0,0 +1,37 @@ +/** +* File: top_k.cs +* Created Time: 2023-06-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_heap; + +public class top_k { + /* 基於堆積查詢陣列中最大的 k 個元素 */ + PriorityQueue TopKHeap(int[] nums, int k) { + // 初始化小頂堆積 + PriorityQueue heap = new(); + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + heap.Enqueue(nums[i], nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.Length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.Peek()) { + heap.Dequeue(); + heap.Enqueue(nums[i], nums[i]); + } + } + return heap; + } + + [Test] + public void Test() { + int[] nums = [1, 7, 6, 3, 2]; + int k = 3; + PriorityQueue res = TopKHeap(nums, k); + Console.WriteLine("最大的 " + k + " 個元素為"); + PrintUtil.PrintHeap(res); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/binary_search.cs b/zh-hant/codes/csharp/chapter_searching/binary_search.cs new file mode 100644 index 0000000000..f330e1fedf --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/binary_search.cs @@ -0,0 +1,59 @@ +/** + * File: binary_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class binary_search { + /* 二分搜尋(雙閉區間) */ + int BinarySearch(int[] nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.Length - 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 target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.Length; + // 迴圈,當搜尋區間為空時跳出(當 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; + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* 二分搜尋(雙閉區間) */ + int index = BinarySearch(nums, target); + Console.WriteLine("目標元素 6 的索引 = " + index); + + /* 二分搜尋(左閉右開區間) */ + index = BinarySearchLCRO(nums, target); + Console.WriteLine("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/binary_search_edge.cs b/zh-hant/codes/csharp/chapter_searching/binary_search_edge.cs new file mode 100644 index 0000000000..7dd0e88486 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/binary_search_edge.cs @@ -0,0 +1,50 @@ +/** +* File: binary_search_edge.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_edge { + /* 二分搜尋最左一個 target */ + int BinarySearchLeftEdge(int[] nums, int target) { + // 等價於查詢 target 的插入點 + int i = binary_search_insertion.BinarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.Length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } + + /* 二分搜尋最右一個 target */ + int BinarySearchRightEdge(int[] nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } + + [Test] + public void Test() { + // 包含重複元素的陣列 + int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n陣列 nums = " + nums.PrintList()); + + // 二分搜尋左邊界和右邊界 + foreach (int target in new int[] { 6, 7 }) { + int index = BinarySearchLeftEdge(nums, target); + Console.WriteLine("最左一個元素 " + target + " 的索引為 " + index); + index = BinarySearchRightEdge(nums, target); + Console.WriteLine("最右一個元素 " + target + " 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/binary_search_insertion.cs b/zh-hant/codes/csharp/chapter_searching/binary_search_insertion.cs new file mode 100644 index 0000000000..8b4659ac7d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/binary_search_insertion.cs @@ -0,0 +1,64 @@ +/** +* File: binary_search_insertion.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_insertion { + /* 二分搜尋插入點(無重複元素) */ + public static int BinarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.Length - 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; + } + + /* 二分搜尋插入點(存在重複元素) */ + public static int BinarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.Length - 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; + } + + [Test] + public void Test() { + // 無重複元素的陣列 + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + Console.WriteLine("\n陣列 nums = " + nums.PrintList()); + // 二分搜尋插入點 + foreach (int target in new int[] { 6, 9 }) { + int index = BinarySearchInsertionSimple(nums, target); + Console.WriteLine("元素 " + target + " 的插入點的索引為 " + index); + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n陣列 nums = " + nums.PrintList()); + // 二分搜尋插入點 + foreach (int target in new int[] { 2, 6, 20 }) { + int index = BinarySearchInsertion(nums, target); + Console.WriteLine("元素 " + target + " 的插入點的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/hashing_search.cs b/zh-hant/codes/csharp/chapter_searching/hashing_search.cs new file mode 100644 index 0000000000..28dd9af0d5 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/hashing_search.cs @@ -0,0 +1,50 @@ +/** + * File: hashing_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class hashing_search { + /* 雜湊查詢(陣列) */ + int HashingSearchArray(Dictionary map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.GetValueOrDefault(target, -1); + } + + /* 雜湊查詢(鏈結串列) */ + ListNode? HashingSearchLinkedList(Dictionary map, int target) { + + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.GetValueOrDefault(target); + } + + [Test] + public void Test() { + int target = 3; + + /* 雜湊查詢(陣列) */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化雜湊表 + Dictionary map = []; + for (int i = 0; i < nums.Length; i++) { + map[nums[i]] = i; // key: 元素,value: 索引 + } + int index = HashingSearchArray(map, target); + Console.WriteLine("目標元素 3 的索引 = " + index); + + /* 雜湊查詢(鏈結串列) */ + ListNode? head = ListNode.ArrToLinkedList(nums); + // 初始化雜湊表 + Dictionary map1 = []; + while (head != null) { + map1[head.val] = head; // key: 節點值,value: 節點 + head = head.next; + } + ListNode? node = HashingSearchLinkedList(map1, target); + Console.WriteLine("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/linear_search.cs b/zh-hant/codes/csharp/chapter_searching/linear_search.cs new file mode 100644 index 0000000000..dc0ca9e7e9 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/linear_search.cs @@ -0,0 +1,49 @@ +/** + * File: linear_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class linear_search { + /* 線性查詢(陣列) */ + int LinearSearchArray(int[] nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.Length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目標元素,返回 -1 + return -1; + } + + /* 線性查詢(鏈結串列) */ + ListNode? LinearSearchLinkedList(ListNode? head, int target) { + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.val == target) + return head; + head = head.next; + } + // 未找到目標節點,返回 null + return null; + } + + [Test] + public void Test() { + int target = 3; + + /* 在陣列中執行線性查詢 */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = LinearSearchArray(nums, target); + Console.WriteLine("目標元素 3 的索引 = " + index); + + /* 在鏈結串列中執行線性查詢 */ + ListNode? head = ListNode.ArrToLinkedList(nums); + ListNode? node = LinearSearchLinkedList(head, target); + Console.WriteLine("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/two_sum.cs b/zh-hant/codes/csharp/chapter_searching/two_sum.cs new file mode 100644 index 0000000000..2c60ce7e49 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/two_sum.cs @@ -0,0 +1,52 @@ +/** + * File: two_sum.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class two_sum { + /* 方法一:暴力列舉 */ + int[] TwoSumBruteForce(int[] nums, int target) { + int size = nums.Length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return [i, j]; + } + } + return []; + } + + /* 方法二:輔助雜湊表 */ + int[] TwoSumHashTable(int[] nums, int target) { + int size = nums.Length; + // 輔助雜湊表,空間複雜度為 O(n) + Dictionary dic = []; + // 單層迴圈,時間複雜度為 O(n) + for (int i = 0; i < size; i++) { + if (dic.ContainsKey(target - nums[i])) { + return [dic[target - nums[i]], i]; + } + dic.Add(nums[i], i); + } + return []; + } + + [Test] + public void Test() { + // ======= Test Case ======= + int[] nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + int[] res = TwoSumBruteForce(nums, target); + Console.WriteLine("方法一 res = " + string.Join(",", res)); + // 方法二 + res = TwoSumHashTable(nums, target); + Console.WriteLine("方法二 res = " + string.Join(",", res)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/bubble_sort.cs b/zh-hant/codes/csharp/chapter_sorting/bubble_sort.cs new file mode 100644 index 0000000000..511f867d76 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/bubble_sort.cs @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bubble_sort { + /* 泡沫排序 */ + void BubbleSort(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + } + } + } + } + + /* 泡沫排序(標誌最佳化)*/ + void BubbleSortWithFlag(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + bool flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + BubbleSort(nums); + Console.WriteLine("泡沫排序完成後 nums = " + string.Join(",", nums)); + + int[] nums1 = [4, 1, 3, 1, 5, 2]; + BubbleSortWithFlag(nums1); + Console.WriteLine("泡沫排序完成後 nums1 = " + string.Join(",", nums1)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/bucket_sort.cs b/zh-hant/codes/csharp/chapter_sorting/bucket_sort.cs new file mode 100644 index 0000000000..684c4a3c1e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/bucket_sort.cs @@ -0,0 +1,46 @@ +/** + * File: bucket_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bucket_sort { + /* 桶排序 */ + void BucketSort(float[] nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.Length / 2; + List> buckets = []; + for (int i = 0; i < k; i++) { + buckets.Add([]); + } + // 1. 將陣列元素分配到各個桶中 + foreach (float num in nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + int i = (int)(num * k); + // 將 num 新增進桶 i + buckets[i].Add(num); + } + // 2. 對各個桶執行排序 + foreach (List bucket in buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.Sort(); + } + // 3. 走訪桶合併結果 + int j = 0; + foreach (List bucket in buckets) { + foreach (float num in bucket) { + nums[j++] = num; + } + } + } + + [Test] + public void Test() { + // 設輸入資料為浮點數,範圍為 [0, 1) + float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; + BucketSort(nums); + Console.WriteLine("桶排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/counting_sort.cs b/zh-hant/codes/csharp/chapter_sorting/counting_sort.cs new file mode 100644 index 0000000000..b86ba72d44 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/counting_sort.cs @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class counting_sort { + /* 計數排序 */ + // 簡單實現,無法用於排序物件 + void CountingSortNaive(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 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; + } + } + } + + /* 計數排序 */ + // 完整實現,可排序物件,並且是穩定排序 + void CountingSort(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.Length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + [Test] + public void Test() { + int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSortNaive(nums); + Console.WriteLine("計數排序(無法排序物件)完成後 nums = " + string.Join(" ", nums)); + + int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSort(nums1); + Console.WriteLine("計數排序完成後 nums1 = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/heap_sort.cs b/zh-hant/codes/csharp/chapter_sorting/heap_sort.cs new file mode 100644 index 0000000000..e365cfaf26 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/heap_sort.cs @@ -0,0 +1,52 @@ +/** +* File: heap_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class heap_sort { + /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ + void SiftDown(int[] nums, int n, int i) { + while (true) { + // 判斷節點 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; + // 交換兩節點 + (nums[ma], nums[i]) = (nums[i], nums[ma]); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 堆積排序 */ + void HeapSort(int[] nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.Length / 2 - 1; i >= 0; i--) { + SiftDown(nums, nums.Length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.Length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + (nums[i], nums[0]) = (nums[0], nums[i]); + // 以根節點為起點,從頂至底進行堆積化 + SiftDown(nums, i, 0); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + HeapSort(nums); + Console.WriteLine("堆積排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/insertion_sort.cs b/zh-hant/codes/csharp/chapter_sorting/insertion_sort.cs new file mode 100644 index 0000000000..c4953bae5a --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/insertion_sort.cs @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class insertion_sort { + /* 插入排序 */ + void InsertionSort(int[] nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.Length; i++) { + int bas = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > bas) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = bas; // 將 base 賦值到正確位置 + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + InsertionSort(nums); + Console.WriteLine("插入排序完成後 nums = " + string.Join(",", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/merge_sort.cs b/zh-hant/codes/csharp/chapter_sorting/merge_sort.cs new file mode 100644 index 0000000000..22e1bf0144 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/merge_sort.cs @@ -0,0 +1,56 @@ +/** + * File: merge_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class merge_sort { + /* 合併左子陣列和右子陣列 */ + void Merge(int[] nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + int[] tmp = new int[right - left + 1]; + // 初始化左子陣列和右子陣列的起始索引 + 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 < tmp.Length; ++k) { + nums[left + k] = tmp[k]; + } + } + + /* 合併排序 */ + 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); + } + + [Test] + public void Test() { + /* 合併排序 */ + int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; + MergeSort(nums, 0, nums.Length - 1); + Console.WriteLine("合併排序完成後 nums = " + string.Join(",", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/quick_sort.cs b/zh-hant/codes/csharp/chapter_sorting/quick_sort.cs new file mode 100644 index 0000000000..90fa517f9e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/quick_sort.cs @@ -0,0 +1,150 @@ +/** + * File: quick_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +class quickSort { + /* 元素交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 哨兵劃分 */ + static 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; // 返回基準數的索引 + } + + /* 快速排序 */ + public static 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); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 選取三個候選元素的中位數 */ + static 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; + } + + /* 哨兵劃分(三數取中值) */ + static int Partition(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; // 返回基準數的索引 + } + + /* 快速排序 */ + public static 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); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 哨兵劃分 */ + static 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; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + public static void QuickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = Partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + QuickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + QuickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +public class quick_sort { + [Test] + public void Test() { + /* 快速排序 */ + int[] nums = [2, 4, 1, 0, 3, 5]; + quickSort.QuickSort(nums, 0, nums.Length - 1); + Console.WriteLine("快速排序完成後 nums = " + string.Join(",", nums)); + + /* 快速排序(中位基準數最佳化) */ + int[] nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); + Console.WriteLine("快速排序(中位基準數最佳化)完成後 nums1 = " + string.Join(",", nums1)); + + /* 快速排序(尾遞迴最佳化) */ + int[] nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); + Console.WriteLine("快速排序(尾遞迴最佳化)完成後 nums2 = " + string.Join(",", nums2)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/radix_sort.cs b/zh-hant/codes/csharp/chapter_sorting/radix_sort.cs new file mode 100644 index 0000000000..2cc67ec58d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/radix_sort.cs @@ -0,0 +1,69 @@ +/** + * File: radix_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class radix_sort { + /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + int Digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10; + } + + /* 計數排序(根據 nums 第 k 位排序) */ + void CountingSortDigit(int[] nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + int[] counter = new int[10]; + int n = nums.Length; + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = Digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + int[] res = new int[n]; + for (int i = n - 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 < n; i++) { + nums[i] = res[i]; + } + } + + /* 基數排序 */ + void RadixSort(int[] nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + int m = int.MinValue; + foreach (int num in nums) { + if (num > m) m = num; + } + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + CountingSortDigit(nums, exp); + } + } + + [Test] + public void Test() { + // 基數排序 + int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 ]; + RadixSort(nums); + Console.WriteLine("基數排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/selection_sort.cs b/zh-hant/codes/csharp/chapter_sorting/selection_sort.cs new file mode 100644 index 0000000000..81b2b3a13c --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/selection_sort.cs @@ -0,0 +1,32 @@ +/** +* File: selection_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class selection_sort { + /* 選擇排序 */ + void SelectionSort(int[] nums) { + int n = nums.Length; + // 外迴圈:未排序區間為 [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; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + (nums[k], nums[i]) = (nums[i], nums[k]); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + SelectionSort(nums); + Console.WriteLine("選擇排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/array_deque.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/array_deque.cs new file mode 100644 index 0000000000..6e8a1657e2 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/array_deque.cs @@ -0,0 +1,152 @@ +/** + * File: array_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於環形陣列實現的雙向佇列 */ +public class ArrayDeque { + int[] nums; // 用於儲存雙向佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 雙向佇列長度 + + /* 建構子 */ + public ArrayDeque(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + int Capacity() { + return nums.Length; + } + + /* 獲取雙向佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* 計算環形陣列索引 */ + int Index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + Capacity()) % Capacity(); + } + + /* 佇列首入列 */ + public void PushFirst(int num) { + if (queSize == Capacity()) { + Console.WriteLine("雙向佇列已滿"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = Index(front - 1); + // 將 num 新增至佇列首 + nums[front] = num; + queSize++; + } + + /* 佇列尾入列 */ + public void PushLast(int num) { + if (queSize == Capacity()) { + Console.WriteLine("雙向佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = Index(front + queSize); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 佇列首出列 */ + public int PopFirst() { + int num = PeekFirst(); + // 佇列首指標向後移動一位 + front = Index(front + 1); + queSize--; + return num; + } + + /* 佇列尾出列 */ + public int PopLast() { + int num = PeekLast(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int PeekFirst() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + return nums[front]; + } + + /* 訪問佇列尾元素 */ + public int PeekLast() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + // 計算尾元素索引 + int last = Index(front + queSize - 1); + return nums[last]; + } + + /* 返回陣列用於列印 */ + public int[] ToArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[Index(j)]; + } + return res; + } +} + +public class array_deque { + [Test] + public void Test() { + /* 初始化雙向佇列 */ + ArrayDeque deque = new(10); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("雙向佇列 deque = " + string.Join(" ", deque.ToArray())); + + /* 訪問元素 */ + int peekFirst = deque.PeekFirst(); + Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.PeekLast(); + Console.WriteLine("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.PushLast(4); + Console.WriteLine("元素 4 佇列尾入列後 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("元素 1 佇列首入列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 元素出列 */ + int popLast = deque.PopLast(); + Console.WriteLine("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + string.Join(" ", deque.ToArray())); + int popFirst = deque.PopFirst(); + Console.WriteLine("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.Size(); + Console.WriteLine("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/array_queue.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/array_queue.cs new file mode 100644 index 0000000000..7e1118820d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/array_queue.cs @@ -0,0 +1,114 @@ +/** + * File: array_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + int[] nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 佇列長度 + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取佇列的容量 */ + int Capacity() { + return nums.Length; + } + + /* 獲取佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* 入列 */ + public void Push(int num) { + if (queSize == Capacity()) { + Console.WriteLine("佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (front + queSize) % Capacity(); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 出列 */ + public int Pop() { + int num = Peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % Capacity(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return nums[front]; + } + + /* 返回陣列 */ + public int[] ToArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % this.Capacity()]; + } + return res; + } +} + +public class array_queue { + [Test] + public void Test() { + /* 初始化佇列 */ + int capacity = 10; + ArrayQueue queue = new(capacity); + + /* 元素入列 */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("佇列 queue = " + string.Join(",", queue.ToArray())); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + Console.WriteLine("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.Pop(); + Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue.ToArray())); + + /* 獲取佇列的長度 */ + int size = queue.Size(); + Console.WriteLine("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("佇列是否為空 = " + isEmpty); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue.Push(i); + queue.Pop(); + Console.WriteLine("第 " + i + " 輪入列 + 出列後 queue = " + string.Join(",", queue.ToArray())); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/array_stack.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/array_stack.cs new file mode 100644 index 0000000000..6543115503 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/array_stack.cs @@ -0,0 +1,84 @@ +/** + * File: array_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + List stack; + public ArrayStack() { + // 初始化串列(動態陣列) + stack = []; + } + + /* 獲取堆疊的長度 */ + public int Size() { + return stack.Count; + } + + /* 判斷堆疊是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入堆疊 */ + public void Push(int num) { + stack.Add(num); + } + + /* 出堆疊 */ + public int Pop() { + if (IsEmpty()) + throw new Exception(); + var val = Peek(); + stack.RemoveAt(Size() - 1); + return val; + } + + /* 訪問堆疊頂元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stack[Size() - 1]; + } + + /* 將 List 轉化為 Array 並返回 */ + public int[] ToArray() { + return [.. stack]; + } +} + +public class array_stack { + [Test] + public void Test() { + /* 初始化堆疊 */ + ArrayStack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("堆疊 stack = " + string.Join(",", stack.ToArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + Console.WriteLine("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack.ToArray())); + + /* 獲取堆疊的長度 */ + int size = stack.Size(); + Console.WriteLine("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/deque.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/deque.cs new file mode 100644 index 0000000000..b893684614 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/deque.cs @@ -0,0 +1,44 @@ +/** + * File: deque.cs + * Created Time: 2022-12-30 + * Author: moonache (microin1301@outlook.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class deque { + [Test] + public void Test() { + /* 初始化雙向佇列 */ + // 在 C# 中,將鏈結串列 LinkedList 看作雙向佇列來使用 + LinkedList deque = new(); + + /* 元素入列 */ + deque.AddLast(2); // 新增至佇列尾 + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 新增至佇列首 + deque.AddFirst(1); + Console.WriteLine("雙向佇列 deque = " + string.Join(",", deque)); + + /* 訪問元素 */ + int? peekFirst = deque.First?.Value; // 佇列首元素 + Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); + int? peekLast = deque.Last?.Value; // 佇列尾元素 + Console.WriteLine("佇列尾元素 peekLast = " + peekLast); + + /* 元素出列 */ + deque.RemoveFirst(); // 佇列首元素出列 + Console.WriteLine("佇列首元素出列後 deque = " + string.Join(",", deque)); + deque.RemoveLast(); // 佇列尾元素出列 + Console.WriteLine("佇列尾元素出列後 deque = " + string.Join(",", deque)); + + /* 獲取雙向佇列的長度 */ + int size = deque.Count; + Console.WriteLine("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.Count == 0; + Console.WriteLine("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs new file mode 100644 index 0000000000..1379a41829 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs @@ -0,0 +1,177 @@ +/** + * File: linkedlist_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 雙向鏈結串列節點 */ +public class ListNode(int val) { + public int val = val; // 節點值 + public ListNode? next = null; // 後繼節點引用 + public ListNode? prev = null; // 前驅節點引用 +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +public class LinkedListDeque { + ListNode? front, rear; // 頭節點 front, 尾節點 rear + int queSize = 0; // 雙向佇列的長度 + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* 獲取雙向佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入列操作 */ + void Push(int num, bool isFront) { + ListNode node = new(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (IsEmpty()) { + front = node; + rear = node; + } + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front!.prev = node; + node.next = front; + front = node; // 更新頭節點 + } + // 佇列尾入列操作 + else { + // 將 node 新增至鏈結串列尾部 + rear!.next = node; + node.prev = rear; + rear = node; // 更新尾節點 + } + + queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + public void PushFirst(int num) { + Push(num, true); + } + + /* 佇列尾入列 */ + public void PushLast(int num) { + Push(num, false); + } + + /* 出列操作 */ + int? Pop(bool isFront) { + if (IsEmpty()) + throw new Exception(); + int? val; + // 佇列首出列操作 + if (isFront) { + val = front?.val; // 暫存頭節點值 + // 刪除頭節點 + ListNode? fNext = front?.next; + if (fNext != null) { + fNext.prev = null; + front!.next = null; + } + front = fNext; // 更新頭節點 + } + // 佇列尾出列操作 + else { + val = rear?.val; // 暫存尾節點值 + // 刪除尾節點 + ListNode? rPrev = rear?.prev; + if (rPrev != null) { + rPrev.next = null; + rear!.prev = null; + } + rear = rPrev; // 更新尾節點 + } + + queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + public int? PopFirst() { + return Pop(true); + } + + /* 佇列尾出列 */ + public int? PopLast() { + return Pop(false); + } + + /* 訪問佇列首元素 */ + public int? PeekFirst() { + if (IsEmpty()) + throw new Exception(); + return front?.val; + } + + /* 訪問佇列尾元素 */ + public int? PeekLast() { + if (IsEmpty()) + throw new Exception(); + return rear?.val; + } + + /* 返回陣列用於列印 */ + public int?[] ToArray() { + ListNode? node = front; + int?[] res = new int?[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node?.val; + node = node?.next; + } + + return res; + } +} + +public class linkedlist_deque { + [Test] + public void Test() { + /* 初始化雙向佇列 */ + LinkedListDeque deque = new(); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("雙向佇列 deque = " + string.Join(" ", deque.ToArray())); + + /* 訪問元素 */ + int? peekFirst = deque.PeekFirst(); + Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); + int? peekLast = deque.PeekLast(); + Console.WriteLine("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.PushLast(4); + Console.WriteLine("元素 4 佇列尾入列後 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("元素 1 佇列首入列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 元素出列 */ + int? popLast = deque.PopLast(); + Console.WriteLine("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + string.Join(" ", deque.ToArray())); + int? popFirst = deque.PopFirst(); + Console.WriteLine("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.Size(); + Console.WriteLine("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs new file mode 100644 index 0000000000..17515d33e6 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs @@ -0,0 +1,106 @@ +/** + * File: linkedlist_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + ListNode? front, rear; // 頭節點 front ,尾節點 rear + int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* 獲取佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入列 */ + public void Push(int num) { + // 在尾節點後新增 num + ListNode node = new(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == null) { + front = node; + rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else if (rear != null) { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出列 */ + public int Pop() { + int num = Peek(); + // 刪除頭節點 + front = front?.next; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return front!.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + public int[] ToArray() { + if (front == null) + return []; + + ListNode? node = front; + int[] res = new int[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + [Test] + public void Test() { + /* 初始化佇列 */ + LinkedListQueue queue = new(); + + /* 元素入列 */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("佇列 queue = " + string.Join(",", queue.ToArray())); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + Console.WriteLine("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.Pop(); + Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue.ToArray())); + + /* 獲取佇列的長度 */ + int size = queue.Size(); + Console.WriteLine("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs new file mode 100644 index 0000000000..0749a21f67 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs @@ -0,0 +1,97 @@ +/** + * File: linkedlist_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + ListNode? stackPeek; // 將頭節點作為堆疊頂 + int stkSize = 0; // 堆疊的長度 + + public LinkedListStack() { + stackPeek = null; + } + + /* 獲取堆疊的長度 */ + public int Size() { + return stkSize; + } + + /* 判斷堆疊是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入堆疊 */ + public void Push(int num) { + ListNode node = new(num) { + next = stackPeek + }; + stackPeek = node; + stkSize++; + } + + /* 出堆疊 */ + public int Pop() { + int num = Peek(); + stackPeek = stackPeek!.next; + stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stackPeek!.val; + } + + /* 將 List 轉化為 Array 並返回 */ + public int[] ToArray() { + if (stackPeek == null) + return []; + + ListNode? node = stackPeek; + int[] res = new int[Size()]; + for (int i = res.Length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + [Test] + public void Test() { + /* 初始化堆疊 */ + LinkedListStack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("堆疊 stack = " + string.Join(",", stack.ToArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + Console.WriteLine("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack.ToArray())); + + /* 獲取堆疊的長度 */ + int size = stack.Size(); + Console.WriteLine("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/queue.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/queue.cs new file mode 100644 index 0000000000..bca7cec1d1 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/queue.cs @@ -0,0 +1,39 @@ +/** + * File: queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class queue { + [Test] + public void Test() { + /* 初始化佇列 */ + Queue queue = new(); + + /* 元素入列 */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + Console.WriteLine("佇列 queue = " + string.Join(",", queue)); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + Console.WriteLine("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.Dequeue(); + Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue)); + + /* 獲取佇列的長度 */ + int size = queue.Count; + Console.WriteLine("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.Count == 0; + Console.WriteLine("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/stack.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/stack.cs new file mode 100644 index 0000000000..f30d4532aa --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/stack.cs @@ -0,0 +1,40 @@ +/** + * File: stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class stack { + [Test] + public void Test() { + /* 初始化堆疊 */ + Stack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + // 請注意,stack.ToArray() 得到的是倒序序列,即索引 0 為堆疊頂 + Console.WriteLine("堆疊 stack = " + string.Join(",", stack)); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + Console.WriteLine("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack)); + + /* 獲取堆疊的長度 */ + int size = stack.Count; + Console.WriteLine("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + bool isEmpty = stack.Count == 0; + Console.WriteLine("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/array_binary_tree.cs b/zh-hant/codes/csharp/chapter_tree/array_binary_tree.cs new file mode 100644 index 0000000000..a2e949499d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/array_binary_tree.cs @@ -0,0 +1,129 @@ +/** +* File: array_binary_tree.cs +* Created Time: 2023-07-20 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_tree; + +/* 陣列表示下的二元樹類別 */ +public class ArrayBinaryTree(List arr) { + List tree = new(arr); + + /* 串列容量 */ + public int Size() { + return tree.Count; + } + + /* 獲取索引為 i 節點的值 */ + public int? Val(int i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= Size()) + return null; + return tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + public int Left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + public int Right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + public int Parent(int i) { + return (i - 1) / 2; + } + + /* 層序走訪 */ + public List LevelOrder() { + List res = []; + // 直接走訪陣列 + for (int i = 0; i < Size(); i++) { + if (Val(i).HasValue) + res.Add(Val(i)!.Value); + } + return res; + } + + /* 深度優先走訪 */ + void DFS(int i, string order, List res) { + // 若為空位,則返回 + if (!Val(i).HasValue) + return; + // 前序走訪 + if (order == "pre") + res.Add(Val(i)!.Value); + DFS(Left(i), order, res); + // 中序走訪 + if (order == "in") + res.Add(Val(i)!.Value); + DFS(Right(i), order, res); + // 後序走訪 + if (order == "post") + res.Add(Val(i)!.Value); + } + + /* 前序走訪 */ + public List PreOrder() { + List res = []; + DFS(0, "pre", res); + return res; + } + + /* 中序走訪 */ + public List InOrder() { + List res = []; + DFS(0, "in", res); + return res; + } + + /* 後序走訪 */ + public List PostOrder() { + List res = []; + DFS(0, "post", res); + return res; + } +} + +public class array_binary_tree { + [Test] + public void Test() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + + TreeNode? root = TreeNode.ListToTree(arr); + Console.WriteLine("\n初始化二元樹\n"); + Console.WriteLine("二元樹的陣列表示:"); + Console.WriteLine(arr.PrintList()); + Console.WriteLine("二元樹的鏈結串列表示:"); + PrintUtil.PrintTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt = new(arr); + + // 訪問節點 + int i = 1; + int l = abt.Left(i); + int r = abt.Right(i); + int p = abt.Parent(i); + Console.WriteLine("\n當前節點的索引為 " + i + " ,值為 " + abt.Val(i)); + Console.WriteLine("其左子節點的索引為 " + l + " ,值為 " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); + Console.WriteLine("其右子節點的索引為 " + r + " ,值為 " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); + Console.WriteLine("其父節點的索引為 " + p + " ,值為 " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); + + // 走訪樹 + List res = abt.LevelOrder(); + Console.WriteLine("\n層序走訪為:" + res.PrintList()); + res = abt.PreOrder(); + Console.WriteLine("前序走訪為:" + res.PrintList()); + res = abt.InOrder(); + Console.WriteLine("中序走訪為:" + res.PrintList()); + res = abt.PostOrder(); + Console.WriteLine("後序走訪為:" + res.PrintList()); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_tree/avl_tree.cs b/zh-hant/codes/csharp/chapter_tree/avl_tree.cs new file mode 100644 index 0000000000..1e1f52dadc --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/avl_tree.cs @@ -0,0 +1,216 @@ +/** + * File: avl_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +/* AVL 樹 */ +class AVLTree { + public TreeNode? root; // 根節點 + + /* 獲取節點高度 */ + int Height(TreeNode? node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == null ? -1 : node.height; + } + + /* 更新節點高度 */ + void UpdateHeight(TreeNode node) { + // 節點高度等於最高子樹高度 + 1 + node.height = Math.Max(Height(node.left), Height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + public int BalanceFactor(TreeNode? node) { + // 空節點平衡因子為 0 + if (node == null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return Height(node.left) - Height(node.right); + } + + /* 右旋操作 */ + TreeNode? RightRotate(TreeNode? node) { + TreeNode? child = node?.left; + TreeNode? grandChild = child?.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + TreeNode? LeftRotate(TreeNode? node) { + TreeNode? child = node?.right; + TreeNode? grandChild = child?.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + TreeNode? Rotate(TreeNode? node) { + // 獲取節點 node 的平衡因子 + int balanceFactorInt = BalanceFactor(node); + // 左偏樹 + if (balanceFactorInt > 1) { + if (BalanceFactor(node?.left) >= 0) { + // 右旋 + return RightRotate(node); + } else { + // 先左旋後右旋 + node!.left = LeftRotate(node!.left); + return RightRotate(node); + } + } + // 右偏樹 + if (balanceFactorInt < -1) { + if (BalanceFactor(node?.right) <= 0) { + // 左旋 + return LeftRotate(node); + } else { + // 先右旋後左旋 + node!.right = RightRotate(node!.right); + return LeftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + public void Insert(int val) { + root = InsertHelper(root, val); + } + + /* 遞迴插入節點(輔助方法) */ + TreeNode? InsertHelper(TreeNode? node, int val) { + if (node == null) return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) + node.left = InsertHelper(node.left, val); + else if (val > node.val) + node.right = InsertHelper(node.right, val); + else + return node; // 重複節點不插入,直接返回 + UpdateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + public void Remove(int val) { + root = RemoveHelper(root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + TreeNode? RemoveHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) + node.left = RemoveHelper(node.left, val); + else if (val > node.val) + node.right = RemoveHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) + return null; + // 子節點數量 = 1 ,直接刪除 node + else + node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode? temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = RemoveHelper(node.right, temp.val!.Value); + node.val = temp.val; + } + } + UpdateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + public TreeNode? Search(int val) { + TreeNode? cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < val) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > val) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } +} + +public class avl_tree { + static void TestInsert(AVLTree tree, int val) { + tree.Insert(val); + Console.WriteLine("\n插入節點 " + val + " 後,AVL 樹為"); + PrintUtil.PrintTree(tree.root); + } + + static void TestRemove(AVLTree tree, int val) { + tree.Remove(val); + Console.WriteLine("\n刪除節點 " + val + " 後,AVL 樹為"); + PrintUtil.PrintTree(tree.root); + } + + [Test] + public void Test() { + /* 初始化空 AVL 樹 */ + AVLTree avlTree = new(); + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + TestInsert(avlTree, 1); + TestInsert(avlTree, 2); + TestInsert(avlTree, 3); + TestInsert(avlTree, 4); + TestInsert(avlTree, 5); + TestInsert(avlTree, 8); + TestInsert(avlTree, 7); + TestInsert(avlTree, 9); + TestInsert(avlTree, 10); + TestInsert(avlTree, 6); + + /* 插入重複節點 */ + TestInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + TestRemove(avlTree, 8); // 刪除度為 0 的節點 + TestRemove(avlTree, 5); // 刪除度為 1 的節點 + TestRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode? node = avlTree.Search(7); + Console.WriteLine("\n查詢到的節點物件為 " + node + ",節點值 = " + node?.val); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_search_tree.cs b/zh-hant/codes/csharp/chapter_tree/binary_search_tree.cs new file mode 100644 index 0000000000..ca0c1ef7f2 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_search_tree.cs @@ -0,0 +1,160 @@ +/** + * File: binary_search_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +class BinarySearchTree { + TreeNode? root; + + public BinarySearchTree() { + // 初始化空樹 + root = null; + } + + /* 獲取二元樹根節點 */ + public TreeNode? GetRoot() { + return root; + } + + /* 查詢節點 */ + public TreeNode? Search(int num) { + TreeNode? cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) cur = + cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + public void Insert(int num) { + // 若樹為空,則初始化根節點 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode? cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子樹中 + else + cur = cur.left; + } + + // 插入節點 + TreeNode node = new(num); + if (pre != null) { + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + } + + + /* 刪除節點 */ + public void Remove(int num) { + // 若樹為空,直接提前返回 + if (root == null) + return; + TreeNode? cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val == num) + break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur == null) + return; + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + TreeNode? child = cur.left ?? cur.right; + // 刪除節點 cur + if (cur != root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + TreeNode? tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + Remove(tmp.val!.Value); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + [Test] + public void Test() { + /* 初始化二元搜尋樹 */ + BinarySearchTree bst = new(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + foreach (int num in nums) { + bst.Insert(num); + } + + Console.WriteLine("\n初始化的二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* 查詢節點 */ + TreeNode? node = bst.Search(7); + Console.WriteLine("\n查詢到的節點物件為 " + node + ",節點值 = " + node?.val); + + /* 插入節點 */ + bst.Insert(16); + Console.WriteLine("\n插入節點 16 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* 刪除節點 */ + bst.Remove(1); + Console.WriteLine("\n刪除節點 1 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(2); + Console.WriteLine("\n刪除節點 2 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(4); + Console.WriteLine("\n刪除節點 4 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_tree.cs b/zh-hant/codes/csharp/chapter_tree/binary_tree.cs new file mode 100644 index 0000000000..8e16c4a50f --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_tree.cs @@ -0,0 +1,39 @@ +/** + * File: binary_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree { + [Test] + public void Test() { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + Console.WriteLine("\n初始化二元樹\n"); + PrintUtil.PrintTree(n1); + + /* 插入與刪除節點 */ + TreeNode P = new(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + Console.WriteLine("\n插入節點 P 後\n"); + PrintUtil.PrintTree(n1); + // 刪除節點 P + n1.left = n2; + Console.WriteLine("\n刪除節點 P 後\n"); + PrintUtil.PrintTree(n1); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_tree_bfs.cs b/zh-hant/codes/csharp/chapter_tree/binary_tree_bfs.cs new file mode 100644 index 0000000000..b4e730ff1d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -0,0 +1,40 @@ +/** + * File: binary_tree_bfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_bfs { + + /* 層序走訪 */ + List LevelOrder(TreeNode root) { + // 初始化佇列,加入根節點 + Queue queue = new(); + queue.Enqueue(root); + // 初始化一個串列,用於儲存走訪序列 + List list = []; + while (queue.Count != 0) { + TreeNode node = queue.Dequeue(); // 隊列出隊 + list.Add(node.val!.Value); // 儲存節點值 + if (node.left != null) + queue.Enqueue(node.left); // 左子節點入列 + if (node.right != null) + queue.Enqueue(node.right); // 右子節點入列 + } + return list; + } + + [Test] + public void Test() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹\n"); + PrintUtil.PrintTree(root); + + List list = LevelOrder(root!); + Console.WriteLine("\n層序走訪的節點列印序列 = " + string.Join(",", list)); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_tree_dfs.cs b/zh-hant/codes/csharp/chapter_tree/binary_tree_dfs.cs new file mode 100644 index 0000000000..e65d1da8df --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -0,0 +1,59 @@ +/** + * File: binary_tree_dfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_dfs { + List list = []; + + /* 前序走訪 */ + void PreOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.Add(root.val!.Value); + PreOrder(root.left); + PreOrder(root.right); + } + + /* 中序走訪 */ + void InOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + InOrder(root.left); + list.Add(root.val!.Value); + InOrder(root.right); + } + + /* 後序走訪 */ + void PostOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + PostOrder(root.left); + PostOrder(root.right); + list.Add(root.val!.Value); + } + + [Test] + public void Test() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹\n"); + PrintUtil.PrintTree(root); + + list.Clear(); + PreOrder(root); + Console.WriteLine("\n前序走訪的節點列印序列 = " + string.Join(",", list)); + + list.Clear(); + InOrder(root); + Console.WriteLine("\n中序走訪的節點列印序列 = " + string.Join(",", list)); + + list.Clear(); + PostOrder(root); + Console.WriteLine("\n後序走訪的節點列印序列 = " + string.Join(",", list)); + } +} diff --git a/zh-hant/codes/csharp/csharp.sln b/zh-hant/codes/csharp/csharp.sln new file mode 100644 index 0000000000..5df88ec833 --- /dev/null +++ b/zh-hant/codes/csharp/csharp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} + EndGlobalSection +EndGlobal diff --git a/zh-hant/codes/csharp/hello-algo.csproj b/zh-hant/codes/csharp/hello-algo.csproj new file mode 100644 index 0000000000..43817cc382 --- /dev/null +++ b/zh-hant/codes/csharp/hello-algo.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + hello_algo + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/zh-hant/codes/csharp/utils/ListNode.cs b/zh-hant/codes/csharp/utils/ListNode.cs new file mode 100644 index 0000000000..30047c4872 --- /dev/null +++ b/zh-hant/codes/csharp/utils/ListNode.cs @@ -0,0 +1,32 @@ +// File: ListNode.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.utils; + +/* 鏈結串列節點 */ +public class ListNode(int x) { + public int val = x; + public ListNode? next; + + /* 將陣列反序列化為鏈結串列 */ + public static ListNode? ArrToLinkedList(int[] arr) { + ListNode dum = new(0); + ListNode head = dum; + foreach (int val in arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } + + public override string? ToString() { + List list = []; + var head = this; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + return string.Join("->", list); + } +} diff --git a/zh-hant/codes/csharp/utils/PrintUtil.cs b/zh-hant/codes/csharp/utils/PrintUtil.cs new file mode 100644 index 0000000000..538866991e --- /dev/null +++ b/zh-hant/codes/csharp/utils/PrintUtil.cs @@ -0,0 +1,132 @@ +/** +* File: PrintUtil.cs +* Created Time: 2022-12-23 +* Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) +*/ + +namespace hello_algo.utils; + +public class Trunk(Trunk? prev, string str) { + public Trunk? prev = prev; + public string str = str; +}; + +public static class PrintUtil { + /* 列印串列 */ + public static void PrintList(IList list) { + Console.WriteLine("[" + string.Join(", ", list) + "]"); + } + + public static string PrintList(this IEnumerable list) { + return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; + } + + /* 列印矩陣 (Array) */ + public static void PrintMatrix(T[][] matrix) { + Console.WriteLine("["); + foreach (T[] row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 列印矩陣 (List) */ + public static void PrintMatrix(List> matrix) { + Console.WriteLine("["); + foreach (List row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 列印鏈結串列 */ + public static void PrintLinkedList(ListNode? head) { + List list = []; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + Console.Write(string.Join(" -> ", list)); + } + + /** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void PrintTree(TreeNode? root) { + PrintTree(root, null, false); + } + + /* 列印二元樹 */ + public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { + if (root == null) { + return; + } + + string prev_str = " "; + Trunk trunk = new(prev, prev_str); + + PrintTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + ShowTrunks(trunk); + Console.WriteLine(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + PrintTree(root.left, trunk, false); + } + + public static void ShowTrunks(Trunk? p) { + if (p == null) { + return; + } + + ShowTrunks(p.prev); + Console.Write(p.str); + } + + /* 列印雜湊表 */ + public static void PrintHashMap(Dictionary map) where K : notnull { + foreach (var kv in map.Keys) { + Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); + } + } + + /* 列印堆積 */ + public static void PrintHeap(Queue queue) { + Console.Write("堆積的陣列表示:"); + List list = [.. queue]; + Console.WriteLine(string.Join(',', list)); + Console.WriteLine("堆積的樹狀表示:"); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } + + /* 列印優先佇列 */ + public static void PrintHeap(PriorityQueue queue) { + var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); + Console.Write("堆積的陣列表示:"); + List list = []; + while (newQueue.TryDequeue(out int element, out _)) { + list.Add(element); + } + Console.WriteLine("堆積的樹狀表示:"); + Console.WriteLine(string.Join(',', list.ToList())); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/utils/TreeNode.cs b/zh-hant/codes/csharp/utils/TreeNode.cs new file mode 100644 index 0000000000..af0f771ec9 --- /dev/null +++ b/zh-hant/codes/csharp/utils/TreeNode.cs @@ -0,0 +1,67 @@ +/** + * File: TreeNode.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.utils; + +/* 二元樹節點類別 */ +public class TreeNode(int? x) { + public int? val = x; // 節點值 + public int height; // 節點高度 + public TreeNode? left; // 左子節點引用 + public TreeNode? right; // 右子節點引用 + + // 序列化編碼規則請參考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二元樹的陣列表示: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二元樹的鏈結串列表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 將串列反序列化為二元樹:遞迴 */ + static TreeNode? ListToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.Count || !arr[i].HasValue) { + return null; + } + TreeNode root = new(arr[i]) { + left = ListToTreeDFS(arr, 2 * i + 1), + right = ListToTreeDFS(arr, 2 * i + 2) + }; + return root; + } + + /* 將串列反序列化為二元樹 */ + public static TreeNode? ListToTree(List arr) { + return ListToTreeDFS(arr, 0); + } + + /* 將二元樹序列化為串列:遞迴 */ + static void TreeToListDFS(TreeNode? root, int i, List res) { + if (root == null) + return; + while (i >= res.Count) { + res.Add(null); + } + res[i] = root.val; + TreeToListDFS(root.left, 2 * i + 1, res); + TreeToListDFS(root.right, 2 * i + 2, res); + } + + /* 將二元樹序列化為串列 */ + public static List TreeToList(TreeNode root) { + List res = []; + TreeToListDFS(root, 0, res); + return res; + } +} diff --git a/zh-hant/codes/csharp/utils/Vertex.cs b/zh-hant/codes/csharp/utils/Vertex.cs new file mode 100644 index 0000000000..e1dc7d1395 --- /dev/null +++ b/zh-hant/codes/csharp/utils/Vertex.cs @@ -0,0 +1,30 @@ +/** + * File: Vertex.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) + */ + +namespace hello_algo.utils; + +/* 頂點類別 */ +public class Vertex(int val) { + public int val = val; + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static Vertex[] ValsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.Length]; + for (int i = 0; i < vals.Length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static List VetsToVals(List vets) { + List vals = []; + foreach (Vertex vet in vets) { + vals.Add(vet.val); + } + return vals; + } +} diff --git a/zh-hant/codes/dart/build.dart b/zh-hant/codes/dart/build.dart new file mode 100644 index 0000000000..7bd5b51a14 --- /dev/null +++ b/zh-hant/codes/dart/build.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +void main() { + Directory foldPath = Directory('codes/dart/'); + List files = foldPath.listSync(); + int totalCount = 0; + int errorCount = 0; + for (var file in files) { + if (file.path.endsWith('build.dart')) continue; + if (file is File && file.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [file.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } else if (file is Directory) { + List subFiles = file.listSync(); + for (var subFile in subFiles) { + if (subFile is File && subFile.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [subFile.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } + } + } + } + + print('===== Build Complete ====='); + print('Total: $totalCount'); + print('Error: $errorCount'); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/array.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/array.dart new file mode 100644 index 0000000000..4d1c488434 --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/array.dart @@ -0,0 +1,105 @@ +/** + * File: array.dart + * Created Time: 2023-01-20 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:math'; + +/* 隨機訪問元素 */ +int randomAccess(List nums) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + int randomIndex = Random().nextInt(nums.length); + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 擴展陣列長度 */ +List extend(List nums, int enlarge) { + // 初始化一個擴展長度後的陣列 + List res = List.filled(nums.length + enlarge, 0); + // 將原陣列中的所有元素複製到新陣列 + for (var i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 _num */ +void insert(List nums, int _num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (var i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 _num 賦給 index 處元素 + nums[index] = _num; +} + +/* 刪除索引 index 處的元素 */ +void remove(List nums, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (var i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列元素 */ +void traverse(List nums) { + int count = 0; + // 透過索引走訪陣列 + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (int _num in nums) { + count += _num; + } + // 透過 forEach 方法走訪陣列 + nums.forEach((_num) { + count += _num; + }); +} + +/* 在陣列中查詢指定元素 */ +int find(List nums, int target) { + for (var i = 0; i < nums.length; i++) { + if (nums[i] == target) return i; + } + return -1; +} + +/* Driver Code */ +void main() { + /* 初始化陣列 */ + var arr = List.filled(5, 0); + print('陣列 arr = $arr'); + List nums = [1, 3, 2, 5, 4]; + print('陣列 nums = $nums'); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums); + print('在 nums 中獲取隨機元素 $randomNum'); + + /* 長度擴展 */ + nums = extend(nums, 3); + print('將陣列長度擴展至 8 ,得到 nums = $nums'); + + /* 插入元素 */ + insert(nums, 6, 3); + print("在索引 3 處插入數字 6 ,得到 nums = $nums"); + + /* 刪除元素 */ + remove(nums, 2); + print("刪除索引 2 處的元素,得到 nums = $nums"); + + /* 走訪陣列 */ + traverse(nums); + + /* 查詢元素 */ + int index = find(nums, 3); + print("在 nums 中查詢元素 3 ,得到索引 = $index"); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/linked_list.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/linked_list.dart new file mode 100644 index 0000000000..415576a325 --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/linked_list.dart @@ -0,0 +1,83 @@ +/** + * File: linked_list.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +void insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +void remove(ListNode n0) { + if (n0.next == null) return; + // n0 -> P -> n1 + ListNode P = n0.next!; + ListNode? n1 = P.next; + n0.next = n1; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +ListNode? access(ListNode? head, int index) { + for (var 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 != null) { + if (head.val == target) { + return index; + } + head = head.next; + index++; + } + return -1; +} + +/* Driver Code */ +void main() { + // 初始化鏈結串列 + // 初始化各個節點 + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + + print('初始化的鏈結串列為'); + printLinkedList(n0); + + /* 插入節點 */ + insert(n0, ListNode(0)); + print('插入節點後的鏈結串列為'); + printLinkedList(n0); + + /* 刪除節點 */ + remove(n0); + print('刪除節點後的鏈結串列為'); + printLinkedList(n0); + + /* 訪問節點 */ + ListNode? node = access(n0, 3); + print('鏈結串列中索引 3 處的節點的值 = ${node!.val}'); + + /* 查詢節點 */ + int index = find(n0, 2); + print('鏈結串列中值為 2 的節點的索引 = $index'); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/list.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/list.dart new file mode 100644 index 0000000000..9967b4833d --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/list.dart @@ -0,0 +1,62 @@ +/** + * File: list.dart + * Created Time: 2023-01-24 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* Driver Code */ +void main() { + /* 初始化串列 */ + List nums = [1, 3, 2, 5, 4]; + print('串列 nums = $nums'); + + /* 訪問元素 */ + int _num = nums[1]; + print('訪問索引 1 處的元素,得到 _num = $_num'); + + /* 更新元素 */ + nums[1] = 0; + print('將索引 1 處的元素更新為 0 ,得到 nums = $nums'); + + /* 清空串列 */ + nums.clear(); + print('清空串列後 nums = $nums'); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print('新增元素後 nums = $nums'); + + /* 在中間插入元素 */ + nums.insert(3, 6); + print('在索引 3 處插入數字 6 ,得到 nums = $nums'); + + /* 刪除元素 */ + nums.removeAt(3); + print('刪除索引 3 處的元素,得到 nums = $nums'); + + /* 透過索引走訪串列 */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + /* 直接走訪串列元素 */ + count = 0; + for (var x in nums) { + count += x; + } + + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); + print('將串列 nums1 拼接到 nums 之後,得到 nums = $nums'); + + /* 排序串列 */ + nums.sort(); + print('排序串列後 nums = $nums'); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/my_list.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/my_list.dart new file mode 100644 index 0000000000..b60d9a6058 --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/my_list.dart @@ -0,0 +1,132 @@ +/** + * File: my_list.dart + * Created Time: 2023-02-05 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 串列類別 */ +class MyList { + late List _arr; // 陣列(儲存串列元素) + int _capacity = 10; // 串列容量 + int _size = 0; // 串列長度(當前元素數量) + int _extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + MyList() { + _arr = List.filled(_capacity, 0); + } + + /* 獲取串列長度(當前元素數量)*/ + int size() => _size; + + /* 獲取串列容量 */ + int capacity() => _capacity; + + /* 訪問元素 */ + int get(int index) { + if (index >= _size) throw RangeError('索引越界'); + return _arr[index]; + } + + /* 更新元素 */ + void set(int index, int _num) { + if (index >= _size) throw RangeError('索引越界'); + _arr[index] = _num; + } + + /* 在尾部新增元素 */ + void add(int _num) { + // 元素數量超出容量時,觸發擴容機制 + if (_size == _capacity) extendCapacity(); + _arr[_size] = _num; + // 更新元素數量 + _size++; + } + + /* 在中間插入元素 */ + void insert(int index, int _num) { + if (index >= _size) throw RangeError('索引越界'); + // 元素數量超出容量時,觸發擴容機制 + if (_size == _capacity) extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (var j = _size - 1; j >= index; j--) { + _arr[j + 1] = _arr[j]; + } + _arr[index] = _num; + // 更新元素數量 + _size++; + } + + /* 刪除元素 */ + int remove(int index) { + if (index >= _size) throw RangeError('索引越界'); + int _num = _arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (var j = index; j < _size - 1; j++) { + _arr[j] = _arr[j + 1]; + } + // 更新元素數量 + _size--; + // 返回被刪除的元素 + return _num; + } + + /* 串列擴容 */ + void extendCapacity() { + // 新建一個長度為原陣列 _extendRatio 倍的新陣列 + final _newNums = List.filled(_capacity * _extendRatio, 0); + // 將原陣列複製到新陣列 + List.copyRange(_newNums, 0, _arr); + // 更新 _arr 的引用 + _arr = _newNums; + // 更新串列容量 + _capacity = _arr.length; + } + + /* 將串列轉換為陣列 */ + List toArray() { + List arr = []; + for (var i = 0; i < _size; i++) { + arr.add(get(i)); + } + return arr; + } +} + +/* Driver Code */ +void main() { + /* 初始化串列 */ + MyList nums = MyList(); + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print( + '串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}'); + + /* 在中間插入元素 */ + nums.insert(3, 6); + print('在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}'); + + /* 刪除元素 */ + nums.remove(3); + print('刪除索引 3 處的元素,得到 nums = ${nums.toArray()}'); + + /* 訪問元素 */ + int _num = nums.get(1); + print('訪問索引 1 處的元素,得到 _num = $_num'); + + /* 更新元素 */ + nums.set(1, 0); + print('將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}'); + + /* 測試擴容機制 */ + for (var i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); + } + print( + '擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}'); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/n_queens.dart b/zh-hant/codes/dart/chapter_backtracking/n_queens.dart new file mode 100644 index 0000000000..9c2a93fd23 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/n_queens.dart @@ -0,0 +1,75 @@ +/** + * File: n_queens.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +void backtrack( + int row, + int n, + List> state, + List>> res, + List cols, + List diags1, + List diags2, +) { + // 當放置完所有行時,記錄解 + if (row == n) { + List> copyState = []; + for (List sRow in state) { + copyState.add(List.from(sRow)); + } + res.add(copyState); + 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] = true; + diags1[diag1] = true; + diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = "#"; + cols[col] = false; + diags1[diag1] = false; + diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +List>> nQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + List> state = List.generate(n, (index) => List.filled(n, "#")); + List cols = List.filled(n, false); // 記錄列是否有皇后 + List diags1 = List.filled(2 * n - 1, false); // 記錄主對角線上是否有皇后 + List diags2 = List.filled(2 * n - 1, false); // 記錄次對角線上是否有皇后 + List>> res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +void main() { + int n = 4; + List>> res = nQueens(n); + print("輸入棋盤長寬為 $n"); + print("皇后放置方案共有 ${res.length} 種"); + for (List> state in res) { + print("--------------------"); + for (List row in state) { + print(row); + } + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/permutations_i.dart b/zh-hant/codes/dart/chapter_backtracking/permutations_i.dart new file mode 100644 index 0000000000..56fde7b916 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/permutations_i.dart @@ -0,0 +1,51 @@ +/** + * File: permutations_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全排列 I */ +List> permutationsI(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 3]; + + List> res = permutationsI(nums); + + print("輸入陣列 nums = $nums"); + print("所有排列 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/permutations_ii.dart b/zh-hant/codes/dart/chapter_backtracking/permutations_ii.dart new file mode 100644 index 0000000000..1eea3a1f5c --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/permutations_ii.dart @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:全排列 II */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + Set duplicated = {}; + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全排列 II */ +List> permutationsII(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 2]; + + List> res = permutationsII(nums); + + print("輸入陣列 nums = $nums"); + print("所有排列 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart new file mode 100644 index 0000000000..fe9c1ce787 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart @@ -0,0 +1,35 @@ +/** + * File: preorder_traversal_i_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序走訪:例題一 */ +void preOrder(TreeNode? root, List res) { + if (root == null) { + return; + } + if (root.val == 7) { + // 記錄解 + res.add(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 前序走訪 + List res = []; + preOrder(root, res); + + print("\n輸出所有值為 7 的節點"); + print(List.generate(res.length, (i) => res[i].val)); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart new file mode 100644 index 0000000000..0cb2ee0ad3 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序走訪:例題二 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null) { + return; + } + + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 前序走訪 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\n輸出所有根節點到節點 7 的路徑"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart new file mode 100644 index 0000000000..1cdf34c023 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序走訪:例題三 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null || root.val == 3) { + return; + } + + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 前序走訪 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\n輸出所有根節點到節點 7 的路徑"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart new file mode 100644 index 0000000000..e12cd13288 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart @@ -0,0 +1,73 @@ +/** + * File: preorder_traversal_iii_template.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 判斷當前狀態是否為解 */ +bool isSolution(List state) { + return state.isNotEmpty && state.last.val == 7; +} + +/* 記錄解 */ +void recordSolution(List state, List> res) { + res.add(List.from(state)); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +bool isValid(List state, TreeNode? choice) { + return choice != null && choice.val != 3; +} + +/* 更新狀態 */ +void makeChoice(List state, TreeNode? choice) { + state.add(choice!); +} + +/* 恢復狀態 */ +void undoChoice(List state, TreeNode? choice) { + state.removeLast(); +} + +/* 回溯演算法:例題三 */ +void backtrack( + List state, + List choices, + List> res, +) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (TreeNode? choice in choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, [choice!.left, choice.right], res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 回溯演算法 + List> res = []; + backtrack([], [root!], res); + print("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + for (List path in res) { + print(List.from(path.map((e) => e.val))); + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/subset_sum_i.dart b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i.dart new file mode 100644 index 0000000000..135e057711 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i.dart @@ -0,0 +1,56 @@ +/** + * File: subset_sum_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast(); + } +} + +/* 求解子集和 I */ +List> subsetSumI(List nums, int target) { + List state = []; // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumI(nums, target); + + print("輸入陣列 nums = $nums, target = $target"); + print("所有和等於 $target 的子集 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/subset_sum_i_naive.dart b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i_naive.dart new file mode 100644 index 0000000000..71b2c5e37f --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i_naive.dart @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +void backtrack( + List state, + int target, + int total, + List choices, + List> res, +) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast(); + } +} + +/* 求解子集和 I(包含重複子集) */ +List> subsetSumINaive(List nums, int target) { + List state = []; // 狀態(子集) + int total = 0; // 元素和 + List> res = []; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + print("輸入陣列 nums = $nums, target = $target"); + print("所有和等於 $target 的子集 res = $res"); + print("請注意,該方法輸出的結果包含重複集合"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/subset_sum_ii.dart b/zh-hant/codes/dart/chapter_backtracking/subset_sum_ii.dart new file mode 100644 index 0000000000..51969c1c31 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/subset_sum_ii.dart @@ -0,0 +1,61 @@ +/** + * File: subset_sum_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast(); + } +} + +/* 求解子集和 II */ +List> subsetSumII(List nums, int target) { + List state = []; // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [4, 4, 5]; + int target = 9; + + List> res = subsetSumII(nums, target); + + print("輸入陣列 nums = $nums, target = $target"); + print("所有和等於 $target 的子集 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/iteration.dart b/zh-hant/codes/dart/chapter_computational_complexity/iteration.dart new file mode 100644 index 0000000000..1e7812e597 --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/iteration.dart @@ -0,0 +1,72 @@ +/** + * File: iteration.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* for 迴圈 */ +int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +String nestedForLoop(int n) { + String res = ""; + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res += "($i, $j), "; + } + } + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = forLoop(n); + print("\nfor 迴圈的求和結果 res = $res"); + + res = whileLoop(n); + print("\nwhile 迴圈的求和結果 res = $res"); + + res = whileLoopII(n); + print("\nwhile 迴圈(兩次更新)的求和結果 res = $res"); + + String resStr = nestedForLoop(n); + print("\n雙層 for 迴圈的結果 $resStr"); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/recursion.dart b/zh-hant/codes/dart/chapter_computational_complexity/recursion.dart new file mode 100644 index 0000000000..6f7df2f159 --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/recursion.dart @@ -0,0 +1,70 @@ +/** + * File: recursion.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 遞迴 */ +int recur(int n) { + // 終止條件 + if (n == 1) return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +int forLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + List stack = []; + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.add(i); + } + // 迴:返回結果 + while (!stack.isEmpty) { + // 透過“出堆疊操作”模擬“迴” + res += stack.removeLast(); + } + // 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; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = recur(n); + print("\n遞迴函式的求和結果 res = $res"); + + res = tailRecur(n, 0); + print("\n尾遞迴函式的求和結果 res = $res"); + + res = forLoopRecur(n); + print("\n使用迭代模擬遞迴求和結果 res = $res"); + + res = fib(n); + print("\n費波那契數列的第 $n 項為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/space_complexity.dart b/zh-hant/codes/dart/chapter_computational_complexity/space_complexity.dart new file mode 100644 index 0000000000..057b3dc5b7 --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/space_complexity.dart @@ -0,0 +1,106 @@ +/** + * File: space_complexity.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:collection'; +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 函式 */ +int function() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + final int a = 0; + int b = 0; + List nums = List.filled(10000, 0); + ListNode node = ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (var i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (var i = 0; i < n; i++) { + function(); + } +} + +/* 線性階 */ +void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + List nums = List.filled(n, 0); + // 長度為 n 的串列佔用 O(n) 空間 + List nodes = []; + for (var i = 0; i < n; i++) { + nodes.add(ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + Map map = HashMap(); + for (var i = 0; i < n; i++) { + map.putIfAbsent(i, () => i.toString()); + } +} + +/* 線性階(遞迴實現) */ +void linearRecur(int n) { + print('遞迴 n = $n'); + if (n == 1) return; + linearRecur(n - 1); +} + +/* 平方階 */ +void quadratic(int n) { + // 矩陣佔用 O(n^2) 空間 + List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); + // 二維串列佔用 O(n^2) 空間 + List> numList = []; + for (var i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } +} + +/* 平方階(遞迴實現) */ +int quadraticRecur(int n) { + if (n <= 0) return 0; + List nums = List.filled(n, 0); + print('遞迴 n = $n 中的 nums 長度 = ${nums.length}'); + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +TreeNode? buildTree(int n) { + if (n == 0) return null; + TreeNode root = TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +void main() { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode? root = buildTree(n); + printTree(root); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/time_complexity.dart b/zh-hant/codes/dart/chapter_computational_complexity/time_complexity.dart new file mode 100644 index 0000000000..49b9b2a2b5 --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/time_complexity.dart @@ -0,0 +1,165 @@ +/** + * File: time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* 常數階 */ +int constant(int n) { + int count = 0; + int size = 100000; + for (var i = 0; i < size; i++) { + count++; + } + return count; +} + +/* 線性階 */ +int linear(int n) { + int count = 0; + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 線性階(走訪陣列) */ +int arrayTraversal(List nums) { + int count = 0; + // 迴圈次數與陣列長度成正比 + for (var _num in nums) { + count++; + } + return count; +} + +/* 平方階 */ +int quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +int bubbleSort(List nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (var i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (var j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +int exponential(int n) { + int count = 0, base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (var i = 0; i < n; i++) { + for (var j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +int expRecur(int n) { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n ~/ 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +int logRecur(int n) { + if (n <= 1) return 0; + return logRecur(n ~/ 2) + 1; +} + +/* 線性對數階 */ +int linearLogRecur(int n) { + if (n <= 1) return 1; + int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +int factorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // 從 1 個分裂出 n 個 + for (var i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +void main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + print('輸入資料大小 n = $n'); + + int count = constant(n); + print('常數階的操作數量 = $count'); + + count = linear(n); + print('線性階的操作數量 = $count'); + + count = arrayTraversal(List.filled(n, 0)); + print('線性階(走訪陣列)的操作數量 = $count'); + + count = quadratic(n); + print('平方階的操作數量 = $count'); + final nums = List.filled(n, 0); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums); + print('平方階(泡沫排序)的操作數量 = $count'); + + count = exponential(n); + print('指數階(迴圈實現)的操作數量 = $count'); + count = expRecur(n); + print('指數階(遞迴實現)的操作數量 = $count'); + + count = logarithmic(n); + print('對數階(迴圈實現)的操作數量 = $count'); + count = logRecur(n); + print('對數階(遞迴實現)的操作數量 = $count'); + + count = linearLogRecur(n); + print('線性對數階(遞迴實現)的操作數量 = $count'); + + count = factorialRecur(n); + print('階乘階(遞迴實現)的操作數量 = $count'); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart b/zh-hant/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart new file mode 100644 index 0000000000..7e2593f4f6 --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +List randomNumbers(int n) { + final nums = List.filled(n, 0); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (var i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + nums.shuffle(); + + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +int findOne(List nums) { + for (var i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) return i; + } + + return -1; +} + +/* Driver Code */ +void main() { + for (var i = 0; i < 10; i++) { + int n = 100; + final nums = randomNumbers(n); + int index = findOne(nums); + print('\n陣列 [ 1, 2, ..., n ] 被打亂後 = $nums'); + print('數字 1 的索引為 + $index'); + } +} diff --git a/zh-hant/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart b/zh-hant/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart new file mode 100644 index 0000000000..4d5b17ab4e --- /dev/null +++ b/zh-hant/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart @@ -0,0 +1,42 @@ +/** + * File: binary_search_recur.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +int dfs(List 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(List nums, int target) { + int n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +void main() { + int target = 6; + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target); + print("目標元素 6 的索引 = $index"); +} diff --git a/zh-hant/codes/dart/chapter_divide_and_conquer/build_tree.dart b/zh-hant/codes/dart/chapter_divide_and_conquer/build_tree.dart new file mode 100644 index 0000000000..1ce99f9cab --- /dev/null +++ b/zh-hant/codes/dart/chapter_divide_and_conquer/build_tree.dart @@ -0,0 +1,55 @@ +/** + * File: build_tree.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 構建二元樹:分治 */ +TreeNode? dfs( + List preorder, + Map inorderMap, + int i, + int l, + int r, +) { + // 子樹區間為空時終止 + if (r - l < 0) { + return null; + } + // 初始化根節點 + TreeNode? root = TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]!; + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +TreeNode? buildTree(List preorder, List inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + Map inorderMap = {}; + for (int i = 0; i < inorder.length; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +void main() { + List preorder = [3, 9, 2, 1, 7]; + List inorder = [9, 3, 1, 2, 7]; + print("前序走訪 = $preorder"); + print("中序走訪 = $inorder"); + + TreeNode? root = buildTree(preorder, inorder); + print("構建的二元樹為:"); + printTree(root!); +} diff --git a/zh-hant/codes/dart/chapter_divide_and_conquer/hanota.dart b/zh-hant/codes/dart/chapter_divide_and_conquer/hanota.dart new file mode 100644 index 0000000000..e6d383ef41 --- /dev/null +++ b/zh-hant/codes/dart/chapter_divide_and_conquer/hanota.dart @@ -0,0 +1,54 @@ +/** + * File: hanota.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 移動一個圓盤 */ +void move(List src, List tar) { + // 從 src 頂部拿出一個圓盤 + int pan = src.removeLast(); + // 將圓盤放入 tar 頂部 + tar.add(pan); +} + +/* 求解河內塔問題 f(i) */ +void dfs(int i, List src, List buf, List tar) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解河內塔問題 */ +void solveHanota(List A, List B, List C) { + int n = A.length; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +void main() { + // 串列尾部是柱子頂部 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + print("初始狀態下:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); + + solveHanota(A, B, C); + + print("圓盤移動完成後:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart new file mode 100644 index 0000000000..d9216ec8f9 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_backtrack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯 */ +void backtrack(List choices, int state, int n, List res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) { + res[0]++; + } + // 走訪所有選擇 + for (int choice in choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +int climbingStairsBacktrack(int n) { + List choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + List res = []; + res.add(0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart new file mode 100644 index 0000000000..84e9990a1f --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_constraint_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart new file mode 100644 index 0000000000..32dff0872c --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart @@ -0,0 +1,27 @@ +/** + * File: climbing_stairs_dfs.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 搜尋 */ +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); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFS(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart new file mode 100644 index 0000000000..3e04a87c4c --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_dfs_mem.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 記憶化搜尋 */ +int dfs(int i, List 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 代表無記錄 + List mem = List.filled(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart new file mode 100644 index 0000000000..c5f7fd0f1b --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart @@ -0,0 +1,43 @@ +/** + * File: climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) return n; + // 初始化 dp 表,用於儲存子問題的解 + List dp = List.filled(n + 1, 0); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDP(n); + print("爬 $n 階樓梯共有 $res 種方案"); + + res = climbingStairsDPComp(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/coin_change.dart b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change.dart new file mode 100644 index 0000000000..8c975e23b4 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change.dart @@ -0,0 +1,68 @@ +/** + * File: coin_change.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 零錢兌換:動態規劃 */ +int coinChangeDP(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +int coinChangeDPComp(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + List dp = List.filled(amt + 1, MAX); + dp[0] = 0; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt); + print("湊到目標金額所需的最少硬幣數量為 $res"); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt); + print("湊到目標金額所需的最少硬幣數量為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/coin_change_ii.dart b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change_ii.dart new file mode 100644 index 0000000000..2bf1dc3553 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change_ii.dart @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 零錢兌換 II:動態規劃 */ +int coinChangeIIDP(List coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +int coinChangeIIDPComp(List coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + List dp = List.filled(amt + 1, 0); + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt); + print("湊出目標金額的硬幣組合數量為 $res"); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt); + print("湊出目標金額的硬幣組合數量為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/edit_distance.dart b/zh-hant/codes/dart/chapter_dynamic_programming/edit_distance.dart new file mode 100644 index 0000000000..b8542f5ef4 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/edit_distance.dart @@ -0,0 +1,125 @@ +/** + * File: edit_distance.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 編輯距離:暴力搜尋 */ +int editDistanceDFS(String s, String t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) return i; + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return min(min(insert, delete), replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) return i; + // 若已有記錄,則直接返回之 + if (mem[i][j] != -1) return mem[i][j]; + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = min(min(insert, delete), replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +int editDistanceDP(String s, String t) { + int n = s.length, m = t.length; + List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +int editDistanceDPComp(String s, String t) { + int n = s.length, m = t.length; + List dp = List.filled(m + 1, 0); + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +void main() { + String s = "bag"; + String t = "pack"; + int n = s.length, m = t.length; + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); + + // 記憶化搜尋 + List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); + + // 動態規劃 + res = editDistanceDP(s, t); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/knapsack.dart b/zh-hant/codes/dart/chapter_dynamic_programming/knapsack.dart new file mode 100644 index 0000000000..f38ee8cfd2 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/knapsack.dart @@ -0,0 +1,116 @@ +/** + * File: knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 0-1 背包:暴力搜尋 */ +int knapsackDFS(List wgt, List val, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +int knapsackDFSMem( + List wgt, + List val, + List> mem, + int i, + int c, +) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +int knapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +int knapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List dp = List.filled(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = wgt.length; + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 記憶化搜尋 + List> mem = + List.generate(n + 1, (index) => List.filled(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 動態規劃 + res = knapsackDP(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart b/zh-hant/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart new file mode 100644 index 0000000000..99a4363089 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart @@ -0,0 +1,48 @@ +/** + * File: min_cost_climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 爬樓梯最小代價:動態規劃 */ +int minCostClimbingStairsDP(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + List dp = List.filled(n + 1, 0); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +int minCostClimbingStairsDPComp(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + print("輸入樓梯的代價串列為 $cost"); + + int res = minCostClimbingStairsDP(cost); + print("爬完樓梯的最低代價為 $res"); + + res = minCostClimbingStairsDPComp(cost); + print("爬完樓梯的最低代價為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/min_path_sum.dart b/zh-hant/codes/dart/chapter_dynamic_programming/min_path_sum.dart new file mode 100644 index 0000000000..6e897cb682 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/min_path_sum.dart @@ -0,0 +1,120 @@ +/** + * File: min_path_sum.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最小路徑和:暴力搜尋 */ +int minPathSumDFS(List> grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + // 在 Dart 中,int 型別是固定範圍的整數,不存在表示“無窮大”的值 + return BigInt.from(2).pow(31).toInt(); + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) + grid[i][j]; +} + +/* 最小路徑和:記憶化搜尋 */ +int minPathSumDFSMem(List> grid, List> mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + // 在 Dart 中,int 型別是固定範圍的整數,不存在表示“無窮大”的值 + return BigInt.from(2).pow(31).toInt(); + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +int minPathSumDP(List> grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + List> dp = List.generate(n, (i) => List.filled(m, 0)); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +int minPathSumDPComp(List> grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + List dp = List.filled(m, 0); + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +void main() { + List> grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ]; + int n = grid.length, m = grid[0].length; + +// 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + print("從左上角到右下角的最小路徑和為 $res"); + +// 記憶化搜尋 + List> mem = List.generate(n, (i) => List.filled(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + print("從左上角到右下角的最小路徑和為 $res"); + +// 動態規劃 + res = minPathSumDP(grid); + print("從左上角到右下角的最小路徑和為 $res"); + +// 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid); + print("從左上角到右下角的最小路徑和為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart b/zh-hant/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart new file mode 100644 index 0000000000..3b8c7b88de --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart @@ -0,0 +1,62 @@ +/** + * File: unbounded_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 完全背包:動態規劃 */ +int unboundedKnapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +int unboundedKnapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List dp = List.filled(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [1, 2, 3]; + List val = [5, 11, 15]; + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 空間最佳化後的動態規劃 + int resComp = unboundedKnapsackDPComp(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $resComp"); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_adjacency_list.dart b/zh-hant/codes/dart/chapter_graph/graph_adjacency_list.dart new file mode 100644 index 0000000000..01ec2db368 --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_adjacency_list.dart @@ -0,0 +1,124 @@ +/** + * File: graph_adjacency_list.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + Map> adjList = {}; + + /* 建構子 */ + GraphAdjList(List> edges) { + for (List edge in edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int size() { + return adjList.length; + } + + /* 新增邊 */ + void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 新增邊 vet1 - vet2 + adjList[vet1]!.add(vet2); + adjList[vet2]!.add(vet1); + } + + /* 刪除邊 */ + void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 刪除邊 vet1 - vet2 + adjList[vet1]!.remove(vet2); + adjList[vet2]!.remove(vet1); + } + + /* 新增頂點 */ + void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) return; + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = []; + } + + /* 刪除頂點 */ + void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) { + throw ArgumentError; + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.remove(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + adjList.forEach((key, value) { + value.remove(vet); + }); + } + + /* 列印鄰接表 */ + void printAdjList() { + print("鄰接表 ="); + adjList.forEach((key, value) { + List tmp = []; + for (Vertex vertex in value) { + tmp.add(vertex.val); + } + print("${key.val}: $tmp,"); + }); + } +} + +/* Driver Code */ +void main() { + /* 初始化無向圖 */ + List v = Vertex.valsToVets([1, 3, 2, 5, 4]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化後,圖為"); + graph.printAdjList(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + print("\n新增邊 1-2 後,圖為"); + graph.printAdjList(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + print("\n刪除邊 1-3 後,圖為"); + graph.printAdjList(); + + /* 新增頂點 */ + Vertex v5 = Vertex(6); + graph.addVertex(v5); + print("\n新增頂點 6 後,圖為"); + graph.printAdjList(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]); + print("\n刪除頂點 3 後,圖為"); + graph.printAdjList(); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_adjacency_matrix.dart b/zh-hant/codes/dart/chapter_graph/graph_adjacency_matrix.dart new file mode 100644 index 0000000000..c96c50d3c7 --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_adjacency_matrix.dart @@ -0,0 +1,133 @@ +/** + * File: graph_adjacency_matrix.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + List vertices = []; // 頂點元素,元素代表“頂點值”,索引代表“頂點索引” + List> adjMat = []; //鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + GraphAdjMat(List vertices, List> edges) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + for (int val in vertices) { + addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (List e in edges) { + addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + int size() { + return vertices.length; + } + + /* 新增頂點 */ + void addVertex(int val) { + int n = size(); + // 向頂點串列中新增新頂點的值 + vertices.add(val); + // 在鄰接矩陣中新增一行 + List newRow = List.filled(n, 0, growable: true); + adjMat.add(newRow); + // 在鄰接矩陣中新增一列 + for (List row in adjMat) { + row.add(0); + } + } + + /* 刪除頂點 */ + void removeVertex(int index) { + if (index >= size()) { + throw IndexError; + } + // 在頂點串列中移除索引 index 的頂點 + vertices.removeAt(index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.removeAt(index); + // 在鄰接矩陣中刪除索引 index 的列 + for (List row in adjMat) { + row.removeAt(index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + void printAdjMat() { + print("頂點串列 = $vertices"); + print("鄰接矩陣 = "); + printMatrix(adjMat); + } +} + +/* Driver Code */ +void main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + List vertices = [1, 3, 2, 5, 4]; + List> edges = [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4], + ]; + GraphAdjMat graph = GraphAdjMat(vertices, edges); + print("\n初始化後,圖為"); + graph.printAdjMat(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2); + print("\n新增邊 1-2 後,圖為"); + graph.printAdjMat(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1); + print("\n刪除邊 1-3 後,圖為"); + graph.printAdjMat(); + + /* 新增頂點 */ + graph.addVertex(6); + print("\n新增頂點 6 後,圖為"); + graph.printAdjMat(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1); + print("\n刪除頂點 3 後,圖為"); + graph.printAdjMat(); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_bfs.dart b/zh-hant/codes/dart/chapter_graph/graph_bfs.dart new file mode 100644 index 0000000000..26757114b5 --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_bfs.dart @@ -0,0 +1,66 @@ +/** + * File: graph_bfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 廣度優先走訪 */ +List graphBFS(GraphAdjList graph, Vertex startVet) { + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + // 頂點走訪序列 + List res = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + Set visited = {}; + visited.add(startVet); + // 佇列用於實現 BFS + Queue que = Queue(); + que.add(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.isNotEmpty) { + Vertex vet = que.removeFirst(); // 佇列首頂點出隊 + res.add(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.add(adjVet); // 只入列未訪問的頂點 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Dirver Code */ +void main() { + /* 初始化無向圖 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + List> edges = [ + [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]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化後,圖為"); + graph.printAdjList(); + + /* 廣度優先走訪 */ + List res = graphBFS(graph, v[0]); + print("\n廣度優先走訪(BFS)頂點序列為"); + print(Vertex.vetsToVals(res)); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_dfs.dart b/zh-hant/codes/dart/chapter_graph/graph_dfs.dart new file mode 100644 index 0000000000..82b3907645 --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_dfs.dart @@ -0,0 +1,59 @@ +/** + * File: graph_dfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 深度優先走訪輔助函式 */ +void dfs( + GraphAdjList graph, + Set visited, + List res, + Vertex vet, +) { + res.add(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +List graphDFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + Set visited = {}; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +void main() { + /* 初始化無向圖 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化後,圖為"); + graph.printAdjList(); + + /* 深度優先走訪 */ + List res = graphDFS(graph, v[0]); + print("\n深度優先走訪(DFS)頂點序列為"); + print(Vertex.vetsToVals(res)); +} diff --git a/zh-hant/codes/dart/chapter_greedy/coin_change_greedy.dart b/zh-hant/codes/dart/chapter_greedy/coin_change_greedy.dart new file mode 100644 index 0000000000..96e64010c5 --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/coin_change_greedy.dart @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 零錢兌換:貪婪 */ +int coinChangeGreedy(List coins, int amt) { + // 假設 coins 串列有序 + int i = coins.length - 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; +} + +/* Driver Code */ +void main() { + // 貪婪:能夠保證找到全域性最優解 + List coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("湊到 $amt 所需的最少硬幣數量為 $res"); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50]; + amt = 60; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("湊到 $amt 所需的最少硬幣數量為 $res"); + print("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50]; + amt = 98; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("湊到 $amt 所需的最少硬幣數量為 $res"); + print("實際上需要的最少數量為 2 ,即 49 + 49"); +} diff --git a/zh-hant/codes/dart/chapter_greedy/fractional_knapsack.dart b/zh-hant/codes/dart/chapter_greedy/fractional_knapsack.dart new file mode 100644 index 0000000000..0e6091ae71 --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/fractional_knapsack.dart @@ -0,0 +1,47 @@ +/** + * File: fractional_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 物品 */ +class Item { + int w; // 物品重量 + int v; // 物品價值 + + Item(this.w, this.v); +} + +/* 分數背包:貪婪 */ +double fractionalKnapsack(List wgt, List val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); + // 迴圈貪婪選擇 + double res = 0; + for (Item item in items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += item.v / item.w * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 貪婪演算法 + double res = fractionalKnapsack(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_greedy/max_capacity.dart b/zh-hant/codes/dart/chapter_greedy/max_capacity.dart new file mode 100644 index 0000000000..804ee9fb7f --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/max_capacity.dart @@ -0,0 +1,37 @@ +/** + * File: max_capacity.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大容量:貪婪 */ +int maxCapacity(List ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.length - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +void main() { + List ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪婪演算法 + int res = maxCapacity(ht); + print("最大容量為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_greedy/max_product_cutting.dart b/zh-hant/codes/dart/chapter_greedy/max_product_cutting.dart new file mode 100644 index 0000000000..65b08f63bf --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/max_product_cutting.dart @@ -0,0 +1,37 @@ +/** + * File: max_product_cutting.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大切分乘積:貪婪 */ +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).toInt(); + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return (pow(3, a) * 2).toInt(); + } + // 當餘數為 0 時,不做處理 + return pow(3, a).toInt(); +} + +/* Driver Code */ +void main() { + int n = 58; + + // 貪婪演算法 + int res = maxProductCutting(n); + print("最大切分乘積為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_hashing/array_hash_map.dart b/zh-hant/codes/dart/chapter_hashing/array_hash_map.dart new file mode 100644 index 0000000000..9c558247e6 --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/array_hash_map.dart @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 鍵值對 */ +class Pair { + int key; + String val; + Pair(this.key, this.val); +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + late List _buckets; + + ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + _buckets = List.filled(100, null); + } + + /* 雜湊函式 */ + int _hashFunc(int key) { + final int index = key % 100; + return index; + } + + /* 查詢操作 */ + String? get(int key) { + final int index = _hashFunc(key); + final Pair? pair = _buckets[index]; + if (pair == null) { + return null; + } + return pair.val; + } + + /* 新增操作 */ + void put(int key, String val) { + final Pair pair = Pair(key, val); + final int index = _hashFunc(key); + _buckets[index] = pair; + } + + /* 刪除操作 */ + void remove(int key) { + final int index = _hashFunc(key); + _buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + List pairSet() { + List pairSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + pairSet.add(pair); + } + } + return pairSet; + } + + /* 獲取所有鍵 */ + List keySet() { + List keySet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + keySet.add(pair.key); + } + } + return keySet; + } + + /* 獲取所有值 */ + List values() { + List valueSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + valueSet.add(pair.val); + } + } + return valueSet; + } + + /* 列印雜湊表 */ + void printHashMap() { + for (final Pair kv in pairSet()) { + print("${kv.key} -> ${kv.val}"); + } + } +} + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + final ArrayHashMap map = ArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String? name = map.get(15937); + print("\n輸入學號 15937 ,查詢到姓名 $name"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + print("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value"); + map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); + print("\n單獨走訪鍵 Key"); + map.keySet().forEach((key) => print("$key")); + print("\n單獨走訪值 Value"); + map.values().forEach((val) => print("$val")); +} diff --git a/zh-hant/codes/dart/chapter_hashing/built_in_hash.dart b/zh-hant/codes/dart/chapter_hashing/built_in_hash.dart new file mode 100644 index 0000000000..6e5dad53c0 --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/built_in_hash.dart @@ -0,0 +1,34 @@ +/** + * File: built_in_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../chapter_stack_and_queue/linkedlist_deque.dart'; + +/* Driver Code */ +void main() { + int _num = 3; + int hashNum = _num.hashCode; + print("整數 $_num 的雜湊值為 $hashNum"); + + bool bol = true; + int hashBol = bol.hashCode; + print("布林值 $bol 的雜湊值為 $hashBol"); + + double dec = 3.14159; + int hashDec = dec.hashCode; + print("小數 $dec 的雜湊值為 $hashDec"); + + String str = "Hello 演算法"; + int hashStr = str.hashCode; + print("字串 $str 的雜湊值為 $hashStr"); + + List arr = [12836, "小哈"]; + int hashArr = arr.hashCode; + print("陣列 $arr 的雜湊值為 $hashArr"); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + print("節點物件 $obj 的雜湊值為 $hashObj"); +} diff --git a/zh-hant/codes/dart/chapter_hashing/hash_map.dart b/zh-hant/codes/dart/chapter_hashing/hash_map.dart new file mode 100644 index 0000000000..9bcec5da5e --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/hash_map.dart @@ -0,0 +1,41 @@ +/** + * File: hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + final Map map = {}; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + final String? name = map[15937]; + print("\n輸入學號 15937 ,查詢到姓名 $name"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + print("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value"); + map.forEach((key, value) => print("$key -> $value")); + print("\n單獨走訪鍵 Key"); + map.keys.forEach((key) => print(key)); + print("\n單獨走訪值 Value"); + map.forEach((key, value) => print("$value")); + map.values.forEach((value) => print(value)); +} diff --git a/zh-hant/codes/dart/chapter_hashing/hash_map_chaining.dart b/zh-hant/codes/dart/chapter_hashing/hash_map_chaining.dart new file mode 100644 index 0000000000..b59b157c69 --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/hash_map_chaining.dart @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.dart + * Created Time: 2023-06-24 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + late int size; // 鍵值對數量 + late int capacity; // 雜湊表容量 + late double loadThres; // 觸發擴容的負載因子閾值 + late int extendRatio; // 擴容倍數 + late List> buckets; // 桶陣列 + + /* 建構子 */ + HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = List.generate(capacity, (_) => []); + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return size / capacity; + } + + /* 查詢操作 */ + String? get(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // 走訪桶,若找到 key ,則返回對應 val + for (Pair pair in bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets[index]; + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (Pair pair in bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + Pair pair = Pair(key, val); + bucket.add(pair); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (Pair pair in bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + List> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = List.generate(capacity, (_) => []); + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (List bucket in bucketsTmp) { + for (Pair pair in bucket) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + void printHashMap() { + for (List bucket in buckets) { + List res = []; + for (Pair pair in bucket) { + res.add("${pair.key} -> ${pair.val}"); + } + print(res); + } + } +} + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + HashMapChaining map = HashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String? name = map.get(13276); + print("\n輸入學號 13276 ,查詢到姓名 ${name}"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + print("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.printHashMap(); +} diff --git a/zh-hant/codes/dart/chapter_hashing/hash_map_open_addressing.dart b/zh-hant/codes/dart/chapter_hashing/hash_map_open_addressing.dart new file mode 100644 index 0000000000..24e2ed151a --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/hash_map_open_addressing.dart @@ -0,0 +1,157 @@ +/** + * File: hash_map_open_addressing.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + late int _size; // 鍵值對數量 + int _capacity = 4; // 雜湊表容量 + double _loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + int _extendRatio = 2; // 擴容倍數 + late List _buckets; // 桶陣列 + Pair _TOMBSTONE = Pair(-1, "-1"); // 刪除標記 + + /* 建構子 */ + HashMapOpenAddressing() { + _size = 0; + _buckets = List.generate(_capacity, (index) => null); + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % _capacity; + } + + /* 負載因子 */ + double loadFactor() { + return _size / _capacity; + } + + /* 搜尋 key 對應的桶索引 */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (_buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (_buckets[index]!.key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + _buckets[firstTombstone] = _buckets[index]; + _buckets[index] = _TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % _capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + String? get(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則返回對應 val + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + return _buckets[index]!.val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > _loadThres) { + extend(); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index]!.val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + _buckets[index] = new Pair(key, val); + _size++; + } + + /* 刪除操作 */ + void remove(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index] = _TOMBSTONE; + _size--; + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + List bucketsTmp = _buckets; + // 初始化擴容後的新雜湊表 + _capacity *= _extendRatio; + _buckets = List.generate(_capacity, (index) => null); + _size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (Pair? pair in bucketsTmp) { + if (pair != null && pair != _TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + void printHashMap() { + for (Pair? pair in _buckets) { + if (pair == null) { + print("null"); + } else if (pair == _TOMBSTONE) { + print("TOMBSTONE"); + } else { + print("${pair.key} -> ${pair.val}"); + } + } + } +} + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + HashMapOpenAddressing map = HashMapOpenAddressing(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String? name = map.get(13276); + print("\n輸入學號 13276 ,查詢到姓名 $name"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(16750); + print("\n刪除 16750 後,雜湊表為\nKey -> Value"); + map.printHashMap(); +} diff --git a/zh-hant/codes/dart/chapter_hashing/simple_hash.dart b/zh-hant/codes/dart/chapter_hashing/simple_hash.dart new file mode 100644 index 0000000000..30e07023e3 --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/simple_hash.dart @@ -0,0 +1,62 @@ +/** + * File: simple_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 加法雜湊 */ +int addHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* 乘法雜湊 */ +int mulHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* 互斥或雜湊 */ +int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash ^= key.codeUnitAt(i); + } + return hash & MODULUS; +} + +/* 旋轉雜湊 */ +int rotHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* Dirver Code */ +void main() { + String key = "Hello 演算法"; + + int hash = addHash(key); + print("加法雜湊值為 $hash"); + + hash = mulHash(key); + print("乘法雜湊值為 $hash"); + + hash = xorHash(key); + print("互斥或雜湊值為 $hash"); + + hash = rotHash(key); + print("旋轉雜湊值為 $hash"); +} diff --git a/zh-hant/codes/dart/chapter_heap/my_heap.dart b/zh-hant/codes/dart/chapter_heap/my_heap.dart new file mode 100644 index 0000000000..ea3c94274d --- /dev/null +++ b/zh-hant/codes/dart/chapter_heap/my_heap.dart @@ -0,0 +1,151 @@ +/** + * File: my_heap.dart + * Created Time: 2023-04-09 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 大頂堆積 */ +class MaxHeap { + late List _maxHeap; + + /* 建構子,根據輸入串列建堆積 */ + MaxHeap(List nums) { + // 將串列元素原封不動新增進堆積 + _maxHeap = nums; + // 堆積化除葉節點以外的其他所有節點 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 向下整除 + } + + /* 交換元素 */ + void _swap(int i, int j) { + int tmp = _maxHeap[i]; + _maxHeap[i] = _maxHeap[j]; + _maxHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + int size() { + return _maxHeap.length; + } + + /* 判斷堆積是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + int peek() { + return _maxHeap[0]; + } + + /* 元素入堆積 */ + void push(int val) { + // 新增節點 + _maxHeap.add(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = _parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { + break; + } + // 交換兩節點 + _swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + int pop() { + // 判空處理 + if (isEmpty()) throw Exception('堆積為空'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + _swap(0, size() - 1); + // 刪除節點 + int val = _maxHeap.removeLast(); + // 從頂至底堆積化 + siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = _left(i); + int r = _right(i); + int ma = i; + if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; + if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break; + // 交換兩節點 + _swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + void print() { + printHeap(_maxHeap); + } +} + +/* Driver Code */ +void main() { + /* 初始化大頂堆積 */ + MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + print("\n輸入串列並建堆積後"); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + print("\n堆積頂元素為 $peek"); + + /* 元素入堆積 */ + int val = 7; + maxHeap.push(val); + print("\n元素 $val 入堆積後"); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + print("\n堆積頂元素 $peek 出堆積後"); + maxHeap.print(); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + print("\n堆積元素數量為 $size"); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.isEmpty(); + print("\n堆積是否為空 $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_heap/top_k.dart b/zh-hant/codes/dart/chapter_heap/top_k.dart new file mode 100644 index 0000000000..f91a8a82cb --- /dev/null +++ b/zh-hant/codes/dart/chapter_heap/top_k.dart @@ -0,0 +1,150 @@ +/** + * File: top_k.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +MinHeap topKHeap(List nums, int k) { + // 初始化小頂堆積,將陣列的前 k 個元素入堆積 + MinHeap heap = MinHeap(nums.sublist(0, k)); + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.peek()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +/* Driver Code */ +void main() { + List nums = [1, 7, 6, 3, 2]; + int k = 3; + + MinHeap res = topKHeap(nums, k); + print("最大的 $k 個元素為"); + res.print(); +} + +/* 小頂堆積 */ +class MinHeap { + late List _minHeap; + + /* 建構子,根據輸入串列建堆積 */ + MinHeap(List nums) { + // 將串列元素原封不動新增進堆積 + _minHeap = nums; + // 堆積化除葉節點以外的其他所有節點 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 返回堆積中的元素 */ + List getHeap() { + return _minHeap; + } + + /* 獲取左子節點的索引 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 向下整除 + } + + /* 交換元素 */ + void _swap(int i, int j) { + int tmp = _minHeap[i]; + _minHeap[i] = _minHeap[j]; + _minHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + int size() { + return _minHeap.length; + } + + /* 判斷堆積是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + int peek() { + return _minHeap[0]; + } + + /* 元素入堆積 */ + void push(int val) { + // 新增節點 + _minHeap.add(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = _parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || _minHeap[i] >= _minHeap[p]) { + break; + } + // 交換兩節點 + _swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + int pop() { + // 判空處理 + if (isEmpty()) throw Exception('堆積為空'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + _swap(0, size() - 1); + // 刪除節點 + int val = _minHeap.removeLast(); + // 從頂至底堆積化 + siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = _left(i); + int r = _right(i); + int mi = i; + if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; + if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (mi == i) break; + // 交換兩節點 + _swap(i, mi); + // 迴圈向下堆積化 + i = mi; + } + } + + /* 列印堆積(二元樹) */ + void print() { + printHeap(_minHeap); + } +} diff --git a/zh-hant/codes/dart/chapter_searching/binary_search.dart b/zh-hant/codes/dart/chapter_searching/binary_search.dart new file mode 100644 index 0000000000..449de8accf --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/binary_search.dart @@ -0,0 +1,63 @@ +/** + * File: binary_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二分搜尋(雙閉區間) */ +int binarySearch(List nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.length - 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(List nums, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 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; +} + +/* Driver Code*/ +void main() { + int target = 6; + final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* 二分搜尋 (雙閉區間) */ + int index = binarySearch(nums, target); + print('目標元素 6 的索引 = $index'); + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target); + print('目標元素 6 的索引 = $index'); +} diff --git a/zh-hant/codes/dart/chapter_searching/binary_search_edge.dart b/zh-hant/codes/dart/chapter_searching/binary_search_edge.dart new file mode 100644 index 0000000000..fc20768e93 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/binary_search_edge.dart @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'binary_search_insertion.dart'; + +/* 二分搜尋最左一個 target */ +int binarySearchLeftEdge(List nums, int target) { + // 等價於查詢 target 的插入點 + int i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +int binarySearchRightEdge(List nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +void main() { + // 包含重複元素的陣列 + List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n陣列 nums = $nums"); + + // 二分搜尋左邊界和右邊界 + for (int target in [6, 7]) { + int index = binarySearchLeftEdge(nums, target); + print("最左一個元素 $target 的索引為 $index"); + index = binarySearchRightEdge(nums, target); + print("最右一個元素 $target 的索引為 $index"); + } +} diff --git a/zh-hant/codes/dart/chapter_searching/binary_search_insertion.dart b/zh-hant/codes/dart/chapter_searching/binary_search_insertion.dart new file mode 100644 index 0000000000..4104243a4a --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/binary_search_insertion.dart @@ -0,0 +1,60 @@ +/** + * File: binary_search_insertion.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +int binarySearchInsertionSimple(List nums, int target) { + int i = 0, j = nums.length - 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(List nums, int target) { + int i = 0, j = nums.length - 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; +} + +/* Driver Code */ +void main() { + // 無重複元素的陣列 + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + print("\n陣列 nums = $nums"); + // 二分搜尋插入點 + for (int target in [6, 9]) { + int index = binarySearchInsertionSimple(nums, target); + print("元素 $target 的插入點的索引為 $index"); + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n陣列 nums = $nums"); + // 二分搜尋插入點 + for (int target in [2, 6, 20]) { + int index = binarySearchInsertion(nums, target); + print("元素 $target 的插入點的索引為 $index"); + } +} diff --git a/zh-hant/codes/dart/chapter_searching/hashing_search.dart b/zh-hant/codes/dart/chapter_searching/hashing_search.dart new file mode 100644 index 0000000000..20a6f0c41a --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/hashing_search.dart @@ -0,0 +1,54 @@ +/** + * File: hashing_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; +import '../utils/list_node.dart'; + +/* 雜湊查詢(陣列) */ +int hashingSearchArray(Map map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if (!map.containsKey(target)) { + return -1; + } + return map[target]!; +} + +/* 雜湊查詢(鏈結串列) */ +ListNode? hashingSearchLinkedList(Map map, int target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + if (!map.containsKey(target)) { + return null; + } + return map[target]!; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* 雜湊查詢(陣列) */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化雜湊表 + Map map = HashMap(); + for (int i = 0; i < nums.length; i++) { + map.putIfAbsent(nums[i], () => i); // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + print('目標元素 3 的索引 = $index'); + + /* 雜湊查詢(鏈結串列) */ + ListNode? head = listToLinkedList(nums); + // 初始化雜湊表 + Map map1 = HashMap(); + while (head != null) { + map1.putIfAbsent(head.val, () => head!); // key: 節點值,value: 節點 + head = head.next; + } + ListNode? node = hashingSearchLinkedList(map1, target); + print('目標節點值 3 的對應節點物件為 $node'); +} diff --git a/zh-hant/codes/dart/chapter_searching/linear_search.dart b/zh-hant/codes/dart/chapter_searching/linear_search.dart new file mode 100644 index 0000000000..46adbb0e35 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/linear_search.dart @@ -0,0 +1,47 @@ +/** + * File: linear_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 線性查詢(陣列) */ +int linearSearchArray(List nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) { + return i; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列) */ +ListNode? linearSearchList(ListNode? head, int target) { + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.val == target) return head; + head = head.next; + } + // 未找到目標元素,返回 null + return null; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* 在陣列中執行線性查詢 */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = linearSearchArray(nums, target); + print('目標元素 3 的索引 = $index'); + + /* 在鏈結串列中執行線性查詢 */ + ListNode? head = listToLinkedList(nums); + ListNode? node = linearSearchList(head, target); + print('目標節點值 3 的對應節點物件為 $node'); +} diff --git a/zh-hant/codes/dart/chapter_searching/two_sum.dart b/zh-hant/codes/dart/chapter_searching/two_sum.dart new file mode 100644 index 0000000000..0a1a4ad189 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/two_sum.dart @@ -0,0 +1,49 @@ +/** + * File: two_sum.dart + * Created Time: 2023-2-11 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; + +/* 方法一: 暴力列舉 */ +List twoSumBruteForce(List nums, int target) { + int size = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (var i = 0; i < size - 1; i++) { + for (var j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) return [i, j]; + } + } + return [0]; +} + +/* 方法二: 輔助雜湊表 */ +List twoSumHashTable(List nums, int target) { + int size = nums.length; + // 輔助雜湊表,空間複雜度為 O(n) + Map dic = HashMap(); + // 單層迴圈,時間複雜度為 O(n) + for (var i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return [dic[target - nums[i]]!, i]; + } + dic.putIfAbsent(nums[i], () => i); + } + return [0]; +} + +/* Driver Code */ +void main() { + // ======= Test Case ======= + List nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + List res = twoSumBruteForce(nums, target); + print('方法一 res = $res'); + // 方法二 + res = twoSumHashTable(nums, target); + print('方法二 res = $res'); +} diff --git a/zh-hant/codes/dart/chapter_sorting/bubble_sort.dart b/zh-hant/codes/dart/chapter_sorting/bubble_sort.dart new file mode 100644 index 0000000000..9e3b518e67 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/bubble_sort.dart @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 泡沫排序 */ +void bubbleSort(List nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 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; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +void bubbleSortWithFlag(List nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + bool flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [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; + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + bubbleSort(nums); + print("泡沫排序完成後 nums = $nums"); + + List nums1 = [4, 1, 3, 1, 5, 2]; + bubbleSortWithFlag(nums1); + print("泡沫排序完成後 nums1 = $nums1"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/bucket_sort.dart b/zh-hant/codes/dart/chapter_sorting/bucket_sort.dart new file mode 100644 index 0000000000..87feba9d74 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/bucket_sort.dart @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 桶排序 */ +void bucketSort(List nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.length ~/ 2; + List> buckets = List.generate(k, (index) => []); + + // 1. 將陣列元素分配到各個桶中 + for (double _num in nums) { + // 輸入資料範圍為 [0, 1),使用 _num * k 對映到索引範圍 [0, k-1] + int i = (_num * k).toInt(); + // 將 _num 新增進桶 bucket_idx + buckets[i].add(_num); + } + // 2. 對各個桶執行排序 + for (List bucket in buckets) { + bucket.sort(); + } + // 3. 走訪桶合併結果 + int i = 0; + for (List bucket in buckets) { + for (double _num in bucket) { + nums[i++] = _num; + } + } +} + +/* Driver Code*/ +void main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucketSort(nums); + print('桶排序完成後 nums = $nums'); +} diff --git a/zh-hant/codes/dart/chapter_sorting/counting_sort.dart b/zh-hant/codes/dart/chapter_sorting/counting_sort.dart new file mode 100644 index 0000000000..09c38bd16e --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/counting_sort.dart @@ -0,0 +1,72 @@ +/** + * File: counting_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ +import 'dart:math'; + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +void countingSortNaive(List nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 統計各數字的出現次數 + // counter[_num] 代表 _num 的出現次數 + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 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; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +void countingSort(List nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 統計各數字的出現次數 + // counter[_num] 代表 _num 的出現次數 + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[_num]-1 是 _num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.length; + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int _num = nums[i]; + res[counter[_num] - 1] = _num; // 將 _num 放置到對應索引處 + counter[_num]--; // 令前綴和自減 1 ,得到下次放置 _num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + nums.setAll(0, res); +} + +/* Driver Code*/ +void main() { + final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSortNaive(nums); + print('計數排序(無法排序物件)完成後 nums = $nums'); + + final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSort(nums1); + print('計數排序完成後 nums1 = $nums1'); +} diff --git a/zh-hant/codes/dart/chapter_sorting/heap_sort.dart b/zh-hant/codes/dart/chapter_sorting/heap_sort.dart new file mode 100644 index 0000000000..67119a4035 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/heap_sort.dart @@ -0,0 +1,49 @@ +/** + * File: heap_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +void siftDown(List nums, int n, int i) { + while (true) { + // 判斷節點 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(List nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + heapSort(nums); + print("堆積排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/insertion_sort.dart b/zh-hant/codes/dart/chapter_sorting/insertion_sort.dart new file mode 100644 index 0000000000..68be93791a --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/insertion_sort.dart @@ -0,0 +1,26 @@ +/** + * File: insertion_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 插入排序 */ +void insertionSort(List nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + insertionSort(nums); + print("插入排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/merge_sort.dart b/zh-hant/codes/dart/chapter_sorting/merge_sort.dart new file mode 100644 index 0000000000..6b953ca342 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/merge_sort.dart @@ -0,0 +1,52 @@ +/** + * File: merge_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 合併左子陣列和右子陣列 */ +void merge(List nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + List tmp = List.filled(right - left + 1, 0); + // 初始化左子陣列和右子陣列的起始索引 + 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 < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +void mergeSort(List 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); +} + +/* Driver Code */ +void main() { + /* 合併排序 */ + List nums = [7, 3, 2, 6, 0, 1, 5, 4]; + mergeSort(nums, 0, nums.length - 1); + print("合併排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/quick_sort.dart b/zh-hant/codes/dart/chapter_sorting/quick_sort.dart new file mode 100644 index 0000000000..369910845c --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/quick_sort.dart @@ -0,0 +1,145 @@ +/** + * File: quick_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int _partition(List 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; // 返回基準數的索引 + } + + /* 快速排序 */ + static void quickSort(List 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); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + static int _medianThree(List 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; + } + + /* 哨兵劃分(三數取中值) */ + static int _partition(List 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; // 返回基準數的索引 + } + + /* 快速排序 */ + static void quickSort(List 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); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int _partition(List 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; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + static void quickSort(List nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = _partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +void main() { + /* 快速排序 */ + List nums = [2, 4, 1, 0, 3, 5]; + QuickSort.quickSort(nums, 0, nums.length - 1); + print("快速排序完成後 nums = $nums"); + + /* 快速排序(中位基準數最佳化) */ + List nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + print("快速排序(中位基準數最佳化)完成後 nums1 = $nums1"); + + /* 快速排序(尾遞迴最佳化) */ + List nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + print("快速排序(尾遞迴最佳化)完成後 nums2 = $nums2"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/radix_sort.dart b/zh-hant/codes/dart/chapter_sorting/radix_sort.dart new file mode 100644 index 0000000000..b479f9f52f --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/radix_sort.dart @@ -0,0 +1,71 @@ +/** + * File: radix_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 獲取元素 _num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int _num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (_num ~/ exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +void countingSortDigit(List nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + List counter = List.filled(10, 0); + int n = nums.length; + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + List res = List.filled(n, 0); + for (int i = n - 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 < n; i++) nums[i] = res[i]; +} + +/* 基數排序 */ +void radixSort(List nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + // dart 中 int 的長度是 64 位的 + int m = -1 << 63; + for (int _num in nums) if (_num > m) m = _num; + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +void main() { + // 基數排序 + List nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996 + ]; + radixSort(nums); + print("基數排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/selection_sort.dart b/zh-hant/codes/dart/chapter_sorting/selection_sort.dart new file mode 100644 index 0000000000..6637c040ad --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/selection_sort.dart @@ -0,0 +1,29 @@ +/** + * File: selection_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 選擇排序 */ +void selectionSort(List nums) { + int n = nums.length; + // 外迴圈:未排序區間為 [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; + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + selectionSort(nums); + print("選擇排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/array_deque.dart b/zh-hant/codes/dart/chapter_stack_and_queue/array_deque.dart new file mode 100644 index 0000000000..8c1041b0dd --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/array_deque.dart @@ -0,0 +1,146 @@ +/** + * File: array_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + late List _nums; // 用於儲存雙向佇列元素的陣列 + late int _front; // 佇列首指標,指向佇列首元素 + late int _queSize; // 雙向佇列長度 + + /* 建構子 */ + ArrayDeque(int capacity) { + this._nums = List.filled(capacity, 0); + this._front = this._queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + int capacity() { + return _nums.length; + } + + /* 獲取雙向佇列的長度 */ + int size() { + return _queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 計算環形陣列索引 */ + int index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 佇列首入列 */ + void pushFirst(int _num) { + if (_queSize == capacity()) { + throw Exception("雙向佇列已滿"); + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 _front 越過陣列頭部後回到尾部 + _front = index(_front - 1); + // 將 _num 新增至佇列首 + _nums[_front] = _num; + _queSize++; + } + + /* 佇列尾入列 */ + void pushLast(int _num) { + if (_queSize == capacity()) { + throw Exception("雙向佇列已滿"); + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = index(_front + _queSize); + // 將 _num 新增至佇列尾 + _nums[rear] = _num; + _queSize++; + } + + /* 佇列首出列 */ + int popFirst() { + int _num = peekFirst(); + // 佇列首指標向右移動一位 + _front = index(_front + 1); + _queSize--; + return _num; + } + + /* 佇列尾出列 */ + int popLast() { + int _num = peekLast(); + _queSize--; + return _num; + } + + /* 訪問佇列首元素 */ + int peekFirst() { + if (isEmpty()) { + throw Exception("雙向佇列為空"); + } + return _nums[_front]; + } + + /* 訪問佇列尾元素 */ + int peekLast() { + if (isEmpty()) { + throw Exception("雙向佇列為空"); + } + // 計算尾元素索引 + int last = index(_front + _queSize - 1); + return _nums[last]; + } + + /* 返回陣列用於列印 */ + List toArray() { + // 僅轉換有效長度範圍內的串列元素 + List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[index(j)]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化雙向佇列 */ + final ArrayDeque deque = ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("雙向佇列 deque = ${deque.toArray()}"); + + /* 訪問元素 */ + final int peekFirst = deque.peekFirst(); + print("佇列首元素 peekFirst = $peekFirst"); + final int peekLast = deque.peekLast(); + print("佇列尾元素 peekLast = $peekLast"); + + /* 元素入列 */ + deque.pushLast(4); + print("元素 4 佇列尾入列後 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("元素 1 佇列首入列後 deque = ${deque.toArray()}"); + + /* 元素出列 */ + final int popLast = deque.popLast(); + print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = ${deque.toArray()}"); + final int popFirst = deque.popFirst(); + print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = ${deque.toArray()}"); + + /* 獲取雙向佇列的長度 */ + final int size = deque.size(); + print("雙向佇列長度 size = $size"); + + /* 判斷雙向佇列是否為空 */ + final bool isEmpty = deque.isEmpty(); + print("雙向佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/array_queue.dart b/zh-hant/codes/dart/chapter_stack_and_queue/array_queue.dart new file mode 100644 index 0000000000..85e5fa690f --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/array_queue.dart @@ -0,0 +1,110 @@ +/** + * File: array_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + late List _nums; // 用於儲存佇列元素的陣列 + late int _front; // 佇列首指標,指向佇列首元素 + late int _queSize; // 佇列長度 + + ArrayQueue(int capacity) { + _nums = List.filled(capacity, 0); + _front = _queSize = 0; + } + + /* 獲取佇列的容量 */ + int capaCity() { + return _nums.length; + } + + /* 獲取佇列的長度 */ + int size() { + return _queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入列 */ + void push(int _num) { + if (_queSize == capaCity()) { + throw Exception("佇列已滿"); + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (_front + _queSize) % capaCity(); + // 將 _num 新增至佇列尾 + _nums[rear] = _num; + _queSize++; + } + + /* 出列 */ + int pop() { + int _num = peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + _front = (_front + 1) % capaCity(); + _queSize--; + return _num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (isEmpty()) { + throw Exception("佇列為空"); + } + return _nums[_front]; + } + + /* 返回 Array */ + List toArray() { + // 僅轉換有效長度範圍內的串列元素 + final List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[j % capaCity()]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化佇列 */ + final int capacity = 10; + final ArrayQueue queue = ArrayQueue(capacity); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("佇列 queue = ${queue.toArray()}"); + + /* 訪問佇列首元素 */ + final int peek = queue.peek(); + print("佇列首元素 peek = $peek"); + + /* 元素出列 */ + final int pop = queue.pop(); + print("出列元素 pop = $pop ,出列後 queue = ${queue.toArray()}"); + + /* 獲取佇列長度 */ + final int size = queue.size(); + print("佇列長度 size = $size"); + + /* 判斷佇列是否為空 */ + final bool isEmpty = queue.isEmpty(); + print("佇列是否為空 = $isEmpty"); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + print("第 $i 輪入列 + 出列後 queue = ${queue.toArray()}"); + } +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/array_stack.dart b/zh-hant/codes/dart/chapter_stack_and_queue/array_stack.dart new file mode 100644 index 0000000000..bd72a070f8 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/array_stack.dart @@ -0,0 +1,77 @@ +/** + * File: array_stack.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + late List _stack; + ArrayStack() { + _stack = []; + } + + /* 獲取堆疊的長度 */ + int size() { + return _stack.length; + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return _stack.isEmpty; + } + + /* 入堆疊 */ + void push(int _num) { + _stack.add(_num); + } + + /* 出堆疊 */ + int pop() { + if (isEmpty()) { + throw Exception("堆疊為空"); + } + return _stack.removeLast(); + } + + /* 訪問堆疊頂元素 */ + int peek() { + if (isEmpty()) { + throw Exception("堆疊為空"); + } + return _stack.last; + } + + /* 將堆疊轉化為 Array 並返回 */ + List toArray() => _stack; +} + +/* Driver Code */ +void main() { + /* 初始化堆疊 */ + final ArrayStack stack = ArrayStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("堆疊 stack = ${stack.toArray()}"); + + /* 訪問堆疊頂元素 */ + final int peek = stack.peek(); + print("堆疊頂元素 peek = $peek"); + + /* 元素出堆疊 */ + final int pop = stack.pop(); + print("出堆疊元素 pop = $pop ,出堆疊後 stack = ${stack.toArray()}"); + + /* 獲取堆疊的長度 */ + final int size = stack.size(); + print("堆疊的長度 size = $size"); + + /* 判斷是否為空 */ + final bool isEmpty = stack.isEmpty(); + print("堆疊是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/deque.dart b/zh-hant/codes/dart/chapter_stack_and_queue/deque.dart new file mode 100644 index 0000000000..ba14256bac --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/deque.dart @@ -0,0 +1,42 @@ +/** + * File: deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* 初始化雙向佇列 */ + final Queue deque = Queue(); + deque.addFirst(3); + deque.addLast(2); + deque.addLast(5); + print("雙向佇列 deque = $deque"); + + /* 訪問元素 */ + final int peekFirst = deque.first; + print("佇列首元素 peekFirst = $peekFirst"); + final int peekLast = deque.last; + print("佇列尾元素 peekLast = $peekLast"); + + /* 元素入列 */ + deque.addLast(4); + print("元素 4 佇列尾入列後 deque = $deque"); + deque.addFirst(1); + print("元素 1 佇列首入列後 deque = $deque"); + + /* 元素出列 */ + final int popLast = deque.removeLast(); + print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = $deque"); + final int popFirst = deque.removeFirst(); + print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = $deque"); + + /* 獲取雙向佇列的長度 */ + final int size = deque.length; + print("雙向佇列長度 size = $size"); + + /* 判斷雙向佇列是否為空 */ + final bool isEmpty = deque.isEmpty; + print("雙向佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart new file mode 100644 index 0000000000..547cee251b --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + int val; // 節點值 + ListNode? next; // 後繼節點引用 + ListNode? prev; // 前驅節點引用 + + ListNode(this.val, {this.next, this.prev}); +} + +/* 基於雙向鏈結串列實現的雙向對列 */ +class LinkedListDeque { + late ListNode? _front; // 頭節點 _front + late ListNode? _rear; // 尾節點 _rear + int _queSize = 0; // 雙向佇列的長度 + + LinkedListDeque() { + this._front = null; + this._rear = null; + } + + /* 獲取雙向佇列長度 */ + int size() { + return this._queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入列操作 */ + void push(int _num, bool isFront) { + final ListNode node = ListNode(_num); + if (isEmpty()) { + // 若鏈結串列為空,則令 _front 和 _rear 都指向 node + _front = _rear = node; + } else if (isFront) { + // 佇列首入列操作 + // 將 node 新增至鏈結串列頭部 + _front!.prev = node; + node.next = _front; + _front = node; // 更新頭節點 + } else { + // 佇列尾入列操作 + // 將 node 新增至鏈結串列尾部 + _rear!.next = node; + node.prev = _rear; + _rear = node; // 更新尾節點 + } + _queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + void pushFirst(int _num) { + push(_num, true); + } + + /* 佇列尾入列 */ + void pushLast(int _num) { + push(_num, false); + } + + /* 出列操作 */ + int? pop(bool isFront) { + // 若佇列為空,直接返回 null + if (isEmpty()) { + return null; + } + final int val; + if (isFront) { + // 佇列首出列操作 + val = _front!.val; // 暫存頭節點值 + // 刪除頭節點 + ListNode? fNext = _front!.next; + if (fNext != null) { + fNext.prev = null; + _front!.next = null; + } + _front = fNext; // 更新頭節點 + } else { + // 佇列尾出列操作 + val = _rear!.val; // 暫存尾節點值 + // 刪除尾節點 + ListNode? rPrev = _rear!.prev; + if (rPrev != null) { + rPrev.next = null; + _rear!.prev = null; + } + _rear = rPrev; // 更新尾節點 + } + _queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + int? popFirst() { + return pop(true); + } + + /* 佇列尾出列 */ + int? popLast() { + return pop(false); + } + + /* 訪問佇列首元素 */ + int? peekFirst() { + return _front?.val; + } + + /* 訪問佇列尾元素 */ + int? peekLast() { + return _rear?.val; + } + + /* 返回陣列用於列印 */ + List toArray() { + ListNode? node = _front; + final List res = []; + for (int i = 0; i < _queSize; i++) { + res.add(node!.val); + node = node.next; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化雙向佇列 */ + final LinkedListDeque deque = LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("雙向佇列 deque = ${deque.toArray()}"); + + /* 訪問元素 */ + int? peekFirst = deque.peekFirst(); + print("佇列首元素 peekFirst = $peekFirst"); + int? peekLast = deque.peekLast(); + print("佇列尾元素 peekLast = $peekLast"); + + /* 元素入列 */ + deque.pushLast(4); + print("元素 4 佇列尾入列後 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("元素 1 佇列首入列後 deque = ${deque.toArray()}"); + + /* 元素出列 */ + int? popLast = deque.popLast(); + print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = ${deque.toArray()}"); + int? popFirst = deque.popFirst(); + print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = ${deque.toArray()}"); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + print("雙向佇列長度 size = $size"); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.isEmpty(); + print("雙向佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart new file mode 100644 index 0000000000..64d8ac7424 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart @@ -0,0 +1,103 @@ +/** + * File: linkedlist_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + ListNode? _front; // 頭節點 _front + ListNode? _rear; // 尾節點 _rear + int _queSize = 0; // 佇列長度 + + LinkedListQueue() { + _front = null; + _rear = null; + } + + /* 獲取佇列的長度 */ + int size() { + return _queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入列 */ + void push(int _num) { + // 在尾節點後新增 _num + final node = ListNode(_num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (_front == null) { + _front = node; + _rear = node; + } else { + // 如果佇列不為空,則將該節點新增到尾節點後 + _rear!.next = node; + _rear = node; + } + _queSize++; + } + + /* 出列 */ + int pop() { + final int _num = peek(); + // 刪除頭節點 + _front = _front!.next; + _queSize--; + return _num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (_queSize == 0) { + throw Exception('佇列為空'); + } + return _front!.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + List toArray() { + ListNode? node = _front; + final List queue = []; + while (node != null) { + queue.add(node.val); + node = node.next; + } + return queue; + } +} + +/* Driver Code */ +void main() { + /* 初始化佇列 */ + final queue = LinkedListQueue(); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("佇列 queue = ${queue.toArray()}"); + + /* 訪問佇列首元素 */ + final int peek = queue.peek(); + print("佇列首元素 peek = $peek"); + + /* 元素出列 */ + final int pop = queue.pop(); + print("出列元素 pop = $pop ,出列後 queue = ${queue.toArray()}"); + + /* 獲取佇列的長度 */ + final int size = queue.size(); + print("佇列長度 size = $size"); + + /* 判斷佇列是否為空 */ + final bool isEmpty = queue.isEmpty(); + print("佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart new file mode 100644 index 0000000000..39a458a076 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart @@ -0,0 +1,93 @@ +/** + * File: linkedlist_stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 基於鏈結串列類別實現的堆疊 */ +class LinkedListStack { + ListNode? _stackPeek; // 將頭節點作為堆疊頂 + int _stkSize = 0; // 堆疊的長度 + + LinkedListStack() { + _stackPeek = null; + } + + /* 獲取堆疊的長度 */ + int size() { + return _stkSize; + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return _stkSize == 0; + } + + /* 入堆疊 */ + void push(int _num) { + final ListNode node = ListNode(_num); + node.next = _stackPeek; + _stackPeek = node; + _stkSize++; + } + + /* 出堆疊 */ + int pop() { + final int _num = peek(); + _stackPeek = _stackPeek!.next; + _stkSize--; + return _num; + } + + /* 訪問堆疊頂元素 */ + int peek() { + if (_stackPeek == null) { + throw Exception("堆疊為空"); + } + return _stackPeek!.val; + } + + /* 將鏈結串列轉化為 List 並返回 */ + List toList() { + ListNode? node = _stackPeek; + List list = []; + while (node != null) { + list.add(node.val); + node = node.next; + } + list = list.reversed.toList(); + return list; + } +} + +/* Driver Code */ +void main() { + /* 初始化堆疊 */ + final LinkedListStack stack = LinkedListStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("堆疊 stack = ${stack.toList()}"); + + /* 訪問堆疊頂元素 */ + final int peek = stack.peek(); + print("堆疊頂元素 peek = $peek"); + + /* 元素出堆疊 */ + final int pop = stack.pop(); + print("出堆疊元素 pop = $pop ,出堆疊後 stack = ${stack.toList()}"); + + /* 獲取堆疊的長度 */ + final int size = stack.size(); + print("堆疊的長度 size = $size"); + + /* 判斷是否為空 */ + final bool isEmpty = stack.isEmpty(); + print("堆疊是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/queue.dart b/zh-hant/codes/dart/chapter_stack_and_queue/queue.dart new file mode 100644 index 0000000000..269c39066d --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/queue.dart @@ -0,0 +1,37 @@ +/** + * File: queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* 初始化佇列 */ + // 在 Dart 中,一般將雙向佇列類別 Queue 看作佇列使用 + final Queue queue = Queue(); + + /* 元素入列 */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + print("佇列 queue = $queue"); + + /* 訪問佇列首元素 */ + final int peek = queue.first; + print("佇列首元素 peek = $peek"); + + /* 元素出列 */ + final int pop = queue.removeFirst(); + print("出列元素 pop = $pop ,出列後 queue = $queue"); + + /* 獲取佇列長度 */ + final int size = queue.length; + print("佇列長度 size = $size"); + + /* 判斷佇列是否為空 */ + final bool isEmpty = queue.isEmpty; + print("佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/stack.dart b/zh-hant/codes/dart/chapter_stack_and_queue/stack.dart new file mode 100644 index 0000000000..eb492f7d18 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/stack.dart @@ -0,0 +1,35 @@ +/** + * File: stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +void main() { + /* 初始化堆疊 */ + // Dart 沒有內建的堆疊類別,可以把 List 當作堆疊來使用 + final List stack = []; + + /* 元素入堆疊 */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + print("堆疊 stack = $stack"); + + /* 訪問堆疊頂元素 */ + final int peek = stack.last; + print("堆疊頂元素 peek = $peek"); + + /* 元素出堆疊 */ + final int pop = stack.removeLast(); + print("出堆疊元素 pop = $pop ,出堆疊後 stack = $stack"); + + /* 獲取堆疊的長度 */ + final int size = stack.length; + print("堆疊的長度 size = $size"); + + /* 判斷是否為空 */ + final bool isEmpty = stack.isEmpty; + print("堆疊是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_tree/array_binary_tree.dart b/zh-hant/codes/dart/chapter_tree/array_binary_tree.dart new file mode 100644 index 0000000000..30bc18b82c --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/array_binary_tree.dart @@ -0,0 +1,152 @@ +/** + * File: array_binary_tree.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + late List _tree; + + /* 建構子 */ + ArrayBinaryTree(this._tree); + + /* 串列容量 */ + int size() { + return _tree.length; + } + + /* 獲取索引為 i 節點的值 */ + int? val(int i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= size()) { + return null; + } + return _tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + int? left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + int? right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + int? parent(int i) { + return (i - 1) ~/ 2; + } + + /* 層序走訪 */ + List levelOrder() { + List res = []; + for (int i = 0; i < size(); i++) { + if (val(i) != null) { + res.add(val(i)!); + } + } + return res; + } + + /* 深度優先走訪 */ + void dfs(int i, String order, List res) { + // 若為空位,則返回 + if (val(i) == null) { + return; + } + // 前序走訪 + if (order == 'pre') { + res.add(val(i)); + } + dfs(left(i)!, order, res); + // 中序走訪 + if (order == 'in') { + res.add(val(i)); + } + dfs(right(i)!, order, res); + // 後序走訪 + if (order == 'post') { + res.add(val(i)); + } + } + + /* 前序走訪 */ + List preOrder() { + List res = []; + dfs(0, 'pre', res); + return res; + } + + /* 中序走訪 */ + List inOrder() { + List res = []; + dfs(0, 'in', res); + return res; + } + + /* 後序走訪 */ + List postOrder() { + List res = []; + dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +void main() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + List arr = [ + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 + ]; + + TreeNode? root = listToTree(arr); + print("\n初始化二元樹\n"); + print("二元樹的陣列表示:"); + print(arr); + print("二元樹的鏈結串列表示:"); + printTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt = ArrayBinaryTree(arr); + + // 訪問節點 + int i = 1; + int? l = abt.left(i); + int? r = abt.right(i); + int? p = abt.parent(i); + print("\n當前節點的索引為 $i ,值為 ${abt.val(i)}"); + print("其左子節點的索引為 $l ,值為 ${(l == null ? "null" : abt.val(l))}"); + print("其右子節點的索引為 $r ,值為 ${(r == null ? "null" : abt.val(r))}"); + print("其父節點的索引為 $p ,值為 ${(p == null ? "null" : abt.val(p))}"); + + // 走訪樹 + List res = abt.levelOrder(); + print("\n層序走訪為:$res"); + res = abt.preOrder(); + print("前序走訪為 $res"); + res = abt.inOrder(); + print("中序走訪為 $res"); + res = abt.postOrder(); + print("後序走訪為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_tree/avl_tree.dart b/zh-hant/codes/dart/chapter_tree/avl_tree.dart new file mode 100644 index 0000000000..c5c6d4485c --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/avl_tree.dart @@ -0,0 +1,218 @@ +/** + * File: avl_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +class AVLTree { + TreeNode? root; + + /* 建構子 */ + AVLTree() { + root = null; + } + + /* 獲取節點高度 */ + int height(TreeNode? node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == null ? -1 : node.height; + } + + /* 更新節點高度 */ + void updateHeight(TreeNode? node) { + // 節點高度等於最高子樹高度 + 1 + node!.height = max(height(node.left), height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + int balanceFactor(TreeNode? node) { + // 空節點平衡因子為 0 + if (node == null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node.left) - height(node.right); + } + + /* 右旋操作 */ + TreeNode? rightRotate(TreeNode? node) { + TreeNode? child = node!.left; + TreeNode? grandChild = child!.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + TreeNode? leftRotate(TreeNode? node) { + TreeNode? child = node!.right; + TreeNode? grandChild = child!.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + TreeNode? rotate(TreeNode? node) { + // 獲取節點 node 的平衡因子 + int factor = balanceFactor(node); + // 左偏樹 + if (factor > 1) { + if (balanceFactor(node!.left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右偏樹 + if (factor < -1) { + if (balanceFactor(node!.right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 遞迴插入節點(輔助方法) */ + TreeNode? insertHelper(TreeNode? node, int val) { + if (node == null) return TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重複節點不插入,直接返回 + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + TreeNode? removeHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) + return null; + // 子節點數量 = 1 ,直接刪除 node + else + node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode? temp = node.right; + while (temp!.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + TreeNode? search(int val) { + TreeNode? cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (val < cur.val) + cur = cur.left; + // 目標節點在 cur 的左子樹中 + else if (val > cur.val) + cur = cur.right; + // 目標節點與當前節點相等 + else + break; + } + return cur; + } +} + +void testInsert(AVLTree tree, int val) { + tree.insert(val); + print("\n插入節點 $val 後,AVL 樹為"); + printTree(tree.root); +} + +void testRemove(AVLTree tree, int val) { + tree.remove(val); + print("\n刪除節點 $val 後,AVL 樹為"); + printTree(tree.root); +} + +/* Driver Code */ +void main() { + /* 初始化空 AVL 樹 */ + AVLTree avlTree = AVLTree(); + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重複節點 */ + testInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8); // 刪除度為 0 的節點 + testRemove(avlTree, 5); // 刪除度為 1 的節點 + testRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode? node = avlTree.search(7); + print("\n查詢到的節點物件為 $node ,節點值 = ${node!.val}"); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_search_tree.dart b/zh-hant/codes/dart/chapter_tree/binary_search_tree.dart new file mode 100644 index 0000000000..d27a8dae29 --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_search_tree.dart @@ -0,0 +1,153 @@ +/** + * File: binary_search_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 二元搜尋樹 */ +class BinarySearchTree { + late TreeNode? _root; + + /* 建構子 */ + BinarySearchTree() { + // 初始化空樹 + _root = null; + } + + /* 獲取二元樹的根節點 */ + TreeNode? getRoot() { + return _root; + } + + /* 查詢節點 */ + TreeNode? search(int _num) { + TreeNode? cur = _root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < _num) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > _num) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + void insert(int _num) { + // 若樹為空,則初始化根節點 + if (_root == null) { + _root = TreeNode(_num); + return; + } + TreeNode? cur = _root; + TreeNode? pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.val == _num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < _num) + cur = cur.right; + // 插入位置在 cur 的左子樹中 + else + cur = cur.left; + } + // 插入節點 + TreeNode? node = TreeNode(_num); + if (pre!.val < _num) + pre.right = node; + else + pre.left = node; + } + + /* 刪除節點 */ + void remove(int _num) { + // 若樹為空,直接提前返回 + if (_root == null) return; + TreeNode? cur = _root; + TreeNode? pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val == _num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < _num) + cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur.left; + } + // 若無待刪除節點,直接返回 + if (cur == null) return; + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + TreeNode? child = cur.left ?? cur.right; + // 刪除節點 cur + if (cur != _root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + _root = child; + } + } else { + // 子節點數量 = 2 + // 獲取中序走訪中 cur 的下一個節點 + TreeNode? tmp = cur.right; + while (tmp!.left != null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + remove(tmp.val); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +void main() { + /* 初始化二元搜尋樹 */ + BinarySearchTree bst = BinarySearchTree(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for (int _num in nums) { + bst.insert(_num); + } + print("\n初始化的二元樹為\n"); + printTree(bst.getRoot()); + + /* 查詢節點 */ + TreeNode? node = bst.search(7); + print("\n查詢到的節點物件為 $node ,節點值 = ${node?.val}"); + + /* 插入節點 */ + bst.insert(16); + print("\n插入節點 16 後,二元樹為\n"); + printTree(bst.getRoot()); + + /* 刪除節點 */ + bst.remove(1); + print("\n刪除節點 1 後,二元樹為\n"); + printTree(bst.getRoot()); + bst.remove(2); + print("\n刪除節點 2 後,二元樹為\n"); + printTree(bst.getRoot()); + bst.remove(4); + print("\n刪除節點 4 後,二元樹為\n"); + printTree(bst.getRoot()); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_tree.dart b/zh-hant/codes/dart/chapter_tree/binary_tree.dart new file mode 100644 index 0000000000..4cdbfaacbe --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_tree.dart @@ -0,0 +1,37 @@ +/** + * File: binary_tree.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +void main() { + /* 初始化二元樹 */ + // 舒適化節點 + TreeNode n1 = TreeNode(1); + TreeNode n2 = TreeNode(2); + TreeNode n3 = TreeNode(3); + TreeNode n4 = TreeNode(4); + TreeNode n5 = TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + print("\n初始化二元樹\n"); + printTree(n1); + + /* 插入與刪除節點 */ + TreeNode p = TreeNode(0); + // 在 n1 -> n2 中間插入節點 p + n1.left = p; + p.left = n2; + print("\n插入節點 P 後\n"); + printTree(n1); + // 刪除節點 P + n1.left = n2; + print("\n刪除節點 P 後\n"); + printTree(n1); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_tree_bfs.dart b/zh-hant/codes/dart/chapter_tree/binary_tree_bfs.dart new file mode 100644 index 0000000000..69bf549aaa --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_tree_bfs.dart @@ -0,0 +1,38 @@ +/** + * File: binary_tree_bfs.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmai.com) + */ + +import 'dart:collection'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 層序走訪 */ +List levelOrder(TreeNode? root) { + // 初始化佇列,加入根節點 + Queue queue = Queue(); + queue.add(root); + // 初始化一個串列,用於儲存走訪序列 + List res = []; + while (queue.isNotEmpty) { + TreeNode? node = queue.removeFirst(); // 隊列出隊 + res.add(node!.val); // 儲存節點值 + if (node.left != null) queue.add(node.left); // 左子節點入列 + if (node.right != null) queue.add(node.right); // 右子節點入列 + } + return res; +} + +/* Driver Code */ +void main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n初始化二元樹\n"); + printTree(root); + + // 層序走訪 + List res = levelOrder(root); + print("\n層序走訪的節點列印序列 = $res"); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_tree_dfs.dart b/zh-hant/codes/dart/chapter_tree/binary_tree_dfs.dart new file mode 100644 index 0000000000..27c3732354 --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_tree_dfs.dart @@ -0,0 +1,62 @@ +/** + * File: binary_tree_dfs.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +// 初始化串列,用於儲存走訪序列 +List list = []; + +/* 前序走訪 */ +void preOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.add(node.val); + preOrder(node.left); + preOrder(node.right); +} + +/* 中序走訪 */ +void inOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(node.left); + list.add(node.val); + inOrder(node.right); +} + +/* 後序走訪 */ +void postOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(node.left); + postOrder(node.right); + list.add(node.val); +} + +/* Driver Code */ +void main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n初始化二元樹\n"); + printTree(root); + + /* 前序走訪 */ + list.clear(); + preOrder(root); + print("\n前序走訪的節點列印序列 = $list"); + + /* 中序走訪 */ + list.clear(); + inOrder(root); + print("\n中序走訪的節點列印序列 = $list"); + + /* 後序走訪 */ + list.clear(); + postOrder(root); + print("\n後序走訪的節點列印序列 = $list"); +} diff --git a/zh-hant/codes/dart/utils/list_node.dart b/zh-hant/codes/dart/utils/list_node.dart new file mode 100644 index 0000000000..b240a0c168 --- /dev/null +++ b/zh-hant/codes/dart/utils/list_node.dart @@ -0,0 +1,24 @@ +/** + * File: list_node.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 鏈結串列節點 */ +class ListNode { + int val; + ListNode? next; + + ListNode(this.val, [this.next]); +} + +/* 將串列反序列化為鏈結串列 */ +ListNode? listToLinkedList(List list) { + ListNode dum = ListNode(0); + ListNode? head = dum; + for (int val in list) { + head?.next = ListNode(val); + head = head?.next; + } + return dum.next; +} diff --git a/zh-hant/codes/dart/utils/print_util.dart b/zh-hant/codes/dart/utils/print_util.dart new file mode 100644 index 0000000000..4426bd01ef --- /dev/null +++ b/zh-hant/codes/dart/utils/print_util.dart @@ -0,0 +1,90 @@ +/** + * File: print_util.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:io'; + +import 'list_node.dart'; +import 'tree_node.dart'; + +class Trunk { + Trunk? prev; + String str; + + Trunk(this.prev, this.str); +} + +/* 列印矩陣 (Array) */ +void printMatrix(List> matrix) { + print("["); + for (List row in matrix) { + print(" $row,"); + } + print("]"); +} + +/* 列印鏈結串列 */ +void printLinkedList(ListNode? head) { + List list = []; + + while (head != null) { + list.add('${head.val}'); + head = head.next; + } + + print(list.join(' -> ')); +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { + if (root == null) { + return; + } + + String prev_str = ' '; + Trunk trunk = Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + showTrunks(trunk); + print(' ${root.val}'); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +void showTrunks(Trunk? p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + stdout.write(p.str); +} + +/* 列印堆積 */ +void printHeap(List heap) { + print("堆積的陣列表示:$heap"); + print("堆積的樹狀表示:"); + TreeNode? root = listToTree(heap); + printTree(root); +} diff --git a/zh-hant/codes/dart/utils/tree_node.dart b/zh-hant/codes/dart/utils/tree_node.dart new file mode 100644 index 0000000000..704f66d41e --- /dev/null +++ b/zh-hant/codes/dart/utils/tree_node.dart @@ -0,0 +1,50 @@ +/** + * File: tree_node.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二元樹節點類別 */ +class TreeNode { + int val; // 節點值 + int height; // 節點高度 + TreeNode? left; // 左子節點引用 + TreeNode? right; // 右子節點引用 + + /* 建構子 */ + TreeNode(this.val, [this.height = 0, this.left, this.right]); +} + +/* 將串列反序列化為二元樹:遞迴 */ +TreeNode? listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.length || arr[i] == null) { + return null; + } + TreeNode? root = TreeNode(arr[i]!); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* 將串列反序列化為二元樹 */ +TreeNode? listToTree(List arr) { + return listToTreeDFS(arr, 0); +} + +/* 將二元樹序列化為串列:遞迴 */ +void treeToListDFS(TreeNode? root, int i, List res) { + if (root == null) return; + while (i >= res.length) { + res.add(null); + } + res[i] = root.val; + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); +} + +/* 將二元樹序列化為串列 */ +List treeToList(TreeNode? root) { + List res = []; + treeToListDFS(root, 0, res); + return res; +} diff --git a/zh-hant/codes/dart/utils/vertex.dart b/zh-hant/codes/dart/utils/vertex.dart new file mode 100644 index 0000000000..57c2b8ea68 --- /dev/null +++ b/zh-hant/codes/dart/utils/vertex.dart @@ -0,0 +1,29 @@ +/** + * File: Vertex.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 頂點類別 */ +class Vertex { + int val; + Vertex(this.val); + + /* 輸入值串列 vals ,返回頂點串列 vets */ + static List valsToVets(List vals) { + List vets = []; + for (int i in vals) { + vets.add(Vertex(i)); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + static List vetsToVals(List vets) { + List vals = []; + for (Vertex vet in vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/zh-hant/codes/docker-compose.yml b/zh-hant/codes/docker-compose.yml new file mode 100644 index 0000000000..ea5bd9e8d7 --- /dev/null +++ b/zh-hant/codes/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' +services: + hello-algo-code: + build: + context: . + args: + # 設定需要安裝的語言,使用空格隔開 + # Set the languages to be installed, separated by spaces + LANGS: "python cpp java csharp" + image: hello-algo-code + container_name: hello-algo-code + stdin_open: true + tty: true diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/array.go b/zh-hant/codes/go/chapter_array_and_linkedlist/array.go new file mode 100644 index 0000000000..cbe62d0cfb --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/array.go @@ -0,0 +1,79 @@ +// File: array.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "math/rand" +) + +/* 隨機訪問元素 */ +func randomAccess(nums []int) (randomNum int) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + randomIndex := rand.Intn(len(nums)) + // 獲取並返回隨機元素 + randomNum = nums[randomIndex] + return +} + +/* 擴展陣列長度 */ +func extend(nums []int, enlarge int) []int { + // 初始化一個擴展長度後的陣列 + res := make([]int, len(nums)+enlarge) + // 將原陣列中的所有元素複製到新陣列 + for i, num := range nums { + res[i] = num + } + // 返回擴展後的新陣列 + return res +} + +/* 在陣列的索引 index 處插入元素 num */ +func insert(nums []int, num int, index int) { + // 把索引 index 以及之後的所有元素向後移動一位 + for i := len(nums) - 1; i > index; i-- { + nums[i] = nums[i-1] + } + // 將 num 賦給 index 處的元素 + nums[index] = num +} + +/* 刪除索引 index 處的元素 */ +func remove(nums []int, index int) { + // 把索引 index 之後的所有元素向前移動一位 + for i := index; i < len(nums)-1; i++ { + nums[i] = nums[i+1] + } +} + +/* 走訪陣列 */ +func traverse(nums []int) { + count := 0 + // 透過索引走訪陣列 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + count = 0 + // 直接走訪陣列元素 + for _, num := range nums { + count += num + } + // 同時走訪資料索引和元素 + for i, num := range nums { + count += nums[i] + count += num + } +} + +/* 在陣列中查詢指定元素 */ +func find(nums []int, target int) (index int) { + index = -1 + for i := 0; i < len(nums); i++ { + if nums[i] == target { + index = i + break + } + } + return +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/array_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/array_test.go new file mode 100644 index 0000000000..b2d9910b9e --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/array_test.go @@ -0,0 +1,50 @@ +// File: array_test.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +/** +我們將 Go 中的 Slice 切片看作 Array 陣列。因為這樣可以 +降低理解成本,利於我們將關注點放在資料結構與演算法上。 +*/ + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestArray(t *testing.T) { + /* 初始化陣列 */ + var arr [5]int + fmt.Println("陣列 arr =", arr) + // 在 Go 中,指定長度時([5]int)為陣列,不指定長度時([]int)為切片 + // 由於 Go 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 + // 為了方便實現擴容 extend() 函式,以下將切片(Slice)看作陣列(Array) + nums := []int{1, 3, 2, 5, 4} + fmt.Println("陣列 nums =", nums) + + /* 隨機訪問 */ + randomNum := randomAccess(nums) + fmt.Println("在 nums 中獲取隨機元素", randomNum) + + /* 長度擴展 */ + nums = extend(nums, 3) + fmt.Println("將陣列長度擴展至 8 ,得到 nums =", nums) + + /* 插入元素 */ + insert(nums, 6, 3) + fmt.Println("在索引 3 處插入數字 6 ,得到 nums =", nums) + + /* 刪除元素 */ + remove(nums, 2) + fmt.Println("刪除索引 2 處的元素,得到 nums =", nums) + + /* 走訪陣列 */ + traverse(nums) + + /* 查詢元素 */ + index := find(nums, 3) + fmt.Println("在 nums 中查詢元素 3 ,得到索引 =", index) +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list.go b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list.go new file mode 100644 index 0000000000..df7e4ce6d6 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list.go @@ -0,0 +1,51 @@ +// File: linked_list.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +func insertNode(n0 *ListNode, P *ListNode) { + n1 := n0.Next + P.Next = n1 + n0.Next = P +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +func removeItem(n0 *ListNode) { + if n0.Next == nil { + return + } + // n0 -> P -> n1 + P := n0.Next + n1 := P.Next + n0.Next = n1 +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +func access(head *ListNode, index int) *ListNode { + for i := 0; i < index; i++ { + if head == nil { + return nil + } + head = head.Next + } + return head +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +func findNode(head *ListNode, target int) int { + index := 0 + for head != nil { + if head.Val == target { + return index + } + head = head.Next + index++ + } + return -1 +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list_test.go new file mode 100644 index 0000000000..940592d8a5 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list_test.go @@ -0,0 +1,48 @@ +// File: linked_list_test.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinkedList(t *testing.T) { + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + + // 構建節點之間的引用 + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + fmt.Println("初始化的鏈結串列為") + PrintLinkedList(n0) + + /* 插入節點 */ + insertNode(n0, NewListNode(0)) + fmt.Println("插入節點後的鏈結串列為") + PrintLinkedList(n0) + + /* 刪除節點 */ + removeItem(n0) + fmt.Println("刪除節點後的鏈結串列為") + PrintLinkedList(n0) + + /* 訪問節點 */ + node := access(n0, 3) + fmt.Println("鏈結串列中索引 3 處的節點的值 =", node) + + /* 查詢節點 */ + index := findNode(n0, 2) + fmt.Println("鏈結串列中值為 2 的節點的索引 =", index) +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/list_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/list_test.go new file mode 100644 index 0000000000..fdc7cf2489 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/list_test.go @@ -0,0 +1,66 @@ +// File: list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "sort" + "testing" +) + +/* Driver Code */ +func TestList(t *testing.T) { + /* 初始化串列 */ + nums := []int{1, 3, 2, 5, 4} + fmt.Println("串列 nums =", nums) + + /* 訪問元素 */ + num := nums[1] // 訪問索引 1 處的元素 + fmt.Println("訪問索引 1 處的元素,得到 num =", num) + + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + fmt.Println("將索引 1 處的元素更新為 0 ,得到 nums =", nums) + + /* 清空串列 */ + nums = nil + fmt.Println("清空串列後 nums =", nums) + + /* 在尾部新增元素 */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + fmt.Println("新增元素後 nums =", nums) + + /* 在中間插入元素 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 處插入數字 6 + fmt.Println("在索引 3 處插入數字 6 ,得到 nums =", nums) + + /* 刪除元素 */ + nums = append(nums[:3], nums[4:]...) // 刪除索引 3 處的元素 + fmt.Println("刪除索引 3 處的元素,得到 nums =", nums) + + /* 透過索引走訪串列 */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + /* 直接走訪串列元素 */ + count = 0 + for _, x := range nums { + count += x + } + + /* 拼接兩個串列 */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // 將串列 nums1 拼接到 nums 之後 + fmt.Println("將串列 nums1 拼接到 nums 之後,得到 nums =", nums) + + /* 排序串列 */ + sort.Ints(nums) // 排序後,串列元素從小到大排列 + fmt.Println("排序串列後 nums =", nums) +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/my_list.go b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list.go new file mode 100644 index 0000000000..ac06cb6202 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list.go @@ -0,0 +1,109 @@ +// File: my_list.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +/* 串列類別 */ +type myList struct { + arrCapacity int + arr []int + arrSize int + extendRatio int +} + +/* 建構子 */ +func newMyList() *myList { + return &myList{ + arrCapacity: 10, // 串列容量 + arr: make([]int, 10), // 陣列(儲存串列元素) + arrSize: 0, // 串列長度(當前元素數量) + extendRatio: 2, // 每次串列擴容的倍數 + } +} + +/* 獲取串列長度(當前元素數量) */ +func (l *myList) size() int { + return l.arrSize +} + +/* 獲取串列容量 */ +func (l *myList) capacity() int { + return l.arrCapacity +} + +/* 訪問元素 */ +func (l *myList) get(index int) int { + // 索引如果越界,則丟擲異常,下同 + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + return l.arr[index] +} + +/* 更新元素 */ +func (l *myList) set(num, index int) { + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + l.arr[index] = num +} + +/* 在尾部新增元素 */ +func (l *myList) add(num int) { + // 元素數量超出容量時,觸發擴容機制 + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + l.arr[l.arrSize] = num + // 更新元素數量 + l.arrSize++ +} + +/* 在中間插入元素 */ +func (l *myList) insert(num, index int) { + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + // 元素數量超出容量時,觸發擴容機制 + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + // 將索引 index 以及之後的元素都向後移動一位 + for j := l.arrSize - 1; j >= index; j-- { + l.arr[j+1] = l.arr[j] + } + l.arr[index] = num + // 更新元素數量 + l.arrSize++ +} + +/* 刪除元素 */ +func (l *myList) remove(index int) int { + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + num := l.arr[index] + // 將索引 index 之後的元素都向前移動一位 + for j := index; j < l.arrSize-1; j++ { + l.arr[j] = l.arr[j+1] + } + // 更新元素數量 + l.arrSize-- + // 返回被刪除的元素 + return num +} + +/* 串列擴容 */ +func (l *myList) extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) + // 更新串列容量 + l.arrCapacity = len(l.arr) +} + +/* 返回有效長度的串列 */ +func (l *myList) toArray() []int { + // 僅轉換有效長度範圍內的串列元素 + return l.arr[:l.arrSize] +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/my_list_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list_test.go new file mode 100644 index 0000000000..f094c227a6 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list_test.go @@ -0,0 +1,46 @@ +// File: my_list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestMyList(t *testing.T) { + /* 初始化串列 */ + nums := newMyList() + /* 在尾部新增元素 */ + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + fmt.Printf("串列 nums = %v ,容量 = %v ,長度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) + + /* 在中間插入元素 */ + nums.insert(6, 3) + fmt.Printf("在索引 3 處插入數字 6 ,得到 nums = %v\n", nums.toArray()) + + /* 刪除元素 */ + nums.remove(3) + fmt.Printf("刪除索引 3 處的元素,得到 nums = %v\n", nums.toArray()) + + /* 訪問元素 */ + num := nums.get(1) + fmt.Printf("訪問索引 1 處的元素,得到 num = %v\n", num) + + /* 更新元素 */ + nums.set(0, 1) + fmt.Printf("將索引 1 處的元素更新為 0 ,得到 nums = %v\n", nums.toArray()) + + /* 測試擴容機制 */ + for i := 0; i < 10; i++ { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i) + } + fmt.Printf("擴容後的串列 nums = %v ,容量 = %v ,長度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) +} diff --git a/zh-hant/codes/go/chapter_backtracking/n_queens.go b/zh-hant/codes/go/chapter_backtracking/n_queens.go new file mode 100644 index 0000000000..516af5eecc --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/n_queens.go @@ -0,0 +1,57 @@ +// File: n_queens.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:n 皇后 */ +func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { + // 當放置完所有行時,記錄解 + if row == n { + newState := make([][]string, len(*state)) + for i, _ := range newState { + newState[i] = make([]string, len((*state)[0])) + copy(newState[i], (*state)[i]) + + } + *res = append(*res, newState) + return + } + // 走訪所有列 + for col := 0; col < n; col++ { + // 計算該格子對應的主對角線和次對角線 + diag1 := row - col + n - 1 + diag2 := row + col + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { + // 嘗試:將皇后放置在該格子 + (*state)[row][col] = "Q" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true + // 放置下一行 + backtrack(row+1, n, state, res, cols, diags1, diags2) + // 回退:將該格子恢復為空位 + (*state)[row][col] = "#" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false + } + } +} + +/* 求解 n 皇后 */ +func nQueens(n int) [][][]string { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + state := make([][]string, n) + for i := 0; i < n; i++ { + row := make([]string, n) + for i := 0; i < n; i++ { + row[i] = "#" + } + state[i] = row + } + // 記錄列是否有皇后 + cols := make([]bool, n) + diags1 := make([]bool, 2*n-1) + diags2 := make([]bool, 2*n-1) + res := make([][][]string, 0) + backtrack(0, n, &state, &res, &cols, &diags1, &diags2) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/n_queens_test.go b/zh-hant/codes/go/chapter_backtracking/n_queens_test.go new file mode 100644 index 0000000000..08bd2057b8 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/n_queens_test.go @@ -0,0 +1,24 @@ +// File: n_queens_test.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" +) + +func TestNQueens(t *testing.T) { + n := 4 + res := nQueens(n) + + fmt.Println("輸入棋盤長寬為 ", n) + fmt.Println("皇后放置方案共有 ", len(res), " 種") + for _, state := range res { + fmt.Println("--------------------") + for _, row := range state { + fmt.Println(row) + } + } +} diff --git a/zh-hant/codes/go/chapter_backtracking/permutation_test.go b/zh-hant/codes/go/chapter_backtracking/permutation_test.go new file mode 100644 index 0000000000..183095eefb --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/permutation_test.go @@ -0,0 +1,33 @@ +// File: permutation_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPermutationI(t *testing.T) { + /* 全排列 I */ + nums := []int{1, 2, 3} + fmt.Printf("輸入陣列 nums = ") + PrintSlice(nums) + + res := permutationsI(nums) + fmt.Printf("所有排列 res = ") + fmt.Println(res) +} + +func TestPermutationII(t *testing.T) { + nums := []int{1, 2, 2} + fmt.Printf("輸入陣列 nums = ") + PrintSlice(nums) + + res := permutationsII(nums) + fmt.Printf("所有排列 res = ") + fmt.Println(res) +} diff --git a/zh-hant/codes/go/chapter_backtracking/permutations_i.go b/zh-hant/codes/go/chapter_backtracking/permutations_i.go new file mode 100644 index 0000000000..a068c12bfe --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/permutations_i.go @@ -0,0 +1,38 @@ +// File: permutations_i.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:全排列 I */ +func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 當狀態長度等於元素數量時,記錄解 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // 走訪所有選擇 + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 剪枝:不允許重複選擇元素 + if !(*selected)[i] { + // 嘗試:做出選擇,更新狀態 + (*selected)[i] = true + *state = append(*state, choice) + // 進行下一輪選擇 + backtrackI(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全排列 I */ +func permutationsI(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackI(&state, &nums, &selected, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/permutations_ii.go b/zh-hant/codes/go/chapter_backtracking/permutations_ii.go new file mode 100644 index 0000000000..15065625e2 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/permutations_ii.go @@ -0,0 +1,41 @@ +// File: permutations_ii.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:全排列 II */ +func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 當狀態長度等於元素數量時,記錄解 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // 走訪所有選擇 + duplicated := make(map[int]struct{}, 0) + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if _, ok := duplicated[choice]; !ok && !(*selected)[i] { + // 嘗試:做出選擇,更新狀態 + // 記錄選擇過的元素值 + duplicated[choice] = struct{}{} + (*selected)[i] = true + *state = append(*state, choice) + // 進行下一輪選擇 + backtrackII(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全排列 II */ +func permutationsII(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackII(&state, &nums, &selected, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_i_compact.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_i_compact.go new file mode 100644 index 0000000000..1c84ab8669 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_i_compact.go @@ -0,0 +1,22 @@ +// File: preorder_traversal_i_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序走訪:例題一 */ +func preOrderI(root *TreeNode, res *[]*TreeNode) { + if root == nil { + return + } + if (root.Val).(int) == 7 { + // 記錄解 + *res = append(*res, root) + } + preOrderI(root.Left, res) + preOrderI(root.Right, res) +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go new file mode 100644 index 0000000000..b1b78acaf7 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go @@ -0,0 +1,26 @@ +// File: preorder_traversal_ii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序走訪:例題二 */ +func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + if root == nil { + return + } + // 嘗試 + *path = append(*path, root) + if root.Val.(int) == 7 { + // 記錄解 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderII(root.Left, res, path) + preOrderII(root.Right, res, path) + // 回退 + *path = (*path)[:len(*path)-1] +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go new file mode 100644 index 0000000000..c539925deb --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go @@ -0,0 +1,27 @@ +// File: preorder_traversal_iii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序走訪:例題三 */ +func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + // 剪枝 + if root == nil || root.Val == 3 { + return + } + // 嘗試 + *path = append(*path, root) + if root.Val.(int) == 7 { + // 記錄解 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderIII(root.Left, res, path) + preOrderIII(root.Right, res, path) + // 回退 + *path = (*path)[:len(*path)-1] +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_template.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_template.go new file mode 100644 index 0000000000..63c24f1964 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_template.go @@ -0,0 +1,57 @@ +// File: preorder_traversal_iii_template.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 判斷當前狀態是否為解 */ +func isSolution(state *[]*TreeNode) bool { + return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 +} + +/* 記錄解 */ +func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { + *res = append(*res, append([]*TreeNode{}, *state...)) +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +func isValid(state *[]*TreeNode, choice *TreeNode) bool { + return choice != nil && choice.Val != 3 +} + +/* 更新狀態 */ +func makeChoice(state *[]*TreeNode, choice *TreeNode) { + *state = append(*state, choice) +} + +/* 恢復狀態 */ +func undoChoice(state *[]*TreeNode, choice *TreeNode) { + *state = (*state)[:len(*state)-1] +} + +/* 回溯演算法:例題三 */ +func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { + // 檢查是否為解 + if isSolution(state) { + // 記錄解 + recordSolution(state, res) + } + // 走訪所有選擇 + for _, choice := range *choices { + // 剪枝:檢查選擇是否合法 + if isValid(state, choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + // 進行下一輪選擇 + temp := make([]*TreeNode, 0) + temp = append(temp, choice.Left, choice.Right) + backtrackIII(state, &temp, res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_test.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_test.go new file mode 100644 index 0000000000..1dfd1fd79d --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_test.go @@ -0,0 +1,91 @@ +// File: preorder_traversal_i_compact_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreorderTraversalICompact(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 前序走訪 + res := make([]*TreeNode, 0) + preOrderI(root, &res) + + fmt.Println("\n輸出所有值為 7 的節點") + for _, node := range res { + fmt.Printf("%v ", node.Val) + } + fmt.Println() +} + +func TestPreorderTraversalIICompact(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 前序走訪 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderII(root, &res, &path) + + fmt.Println("\n輸出所有根節點到節點 7 的路徑") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIICompact(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 前序走訪 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderIII(root, &res, &path) + + fmt.Println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIITemplate(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 回溯演算法 + res := make([][]*TreeNode, 0) + state := make([]*TreeNode, 0) + choices := make([]*TreeNode, 0) + choices = append(choices, root) + backtrackIII(&state, &choices, &res) + + fmt.Println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_i.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_i.go new file mode 100644 index 0000000000..01ff3fbd63 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_i.go @@ -0,0 +1,42 @@ +// File: subset_sum_i.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* 回溯演算法:子集和 I */ +func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { + // 子集和等於 target 時,記錄解 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for i := start; i < len(*choices); i++ { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target-(*choices)[i] < 0 { + break + } + // 嘗試:做出選擇,更新 target, start + *state = append(*state, (*choices)[i]) + // 進行下一輪選擇 + backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 I */ +func subsetSumI(nums []int, target int) [][]int { + state := make([]int, 0) // 狀態(子集) + sort.Ints(nums) // 對 nums 進行排序 + start := 0 // 走訪起始點 + res := make([][]int, 0) // 結果串列(子集串列) + backtrackSubsetSumI(start, target, &state, &nums, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_i_naive.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_i_naive.go new file mode 100644 index 0000000000..c8ee156995 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_i_naive.go @@ -0,0 +1,37 @@ +// File: subset_sum_i_naive.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:子集和 I */ +func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { + // 子集和等於 target 時,記錄解 + if target == total { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 走訪所有選擇 + for i := 0; i < len(*choices); i++ { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if total+(*choices)[i] > target { + continue + } + // 嘗試:做出選擇,更新元素和 total + *state = append(*state, (*choices)[i]) + // 進行下一輪選擇 + backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 I(包含重複子集) */ +func subsetSumINaive(nums []int, target int) [][]int { + state := make([]int, 0) // 狀態(子集) + total := 0 // 子集和 + res := make([][]int, 0) // 結果串列(子集串列) + backtrackSubsetSumINaive(total, target, &state, &nums, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_ii.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_ii.go new file mode 100644 index 0000000000..3e03790c33 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_ii.go @@ -0,0 +1,47 @@ +// File: subset_sum_ii.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* 回溯演算法:子集和 II */ +func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { + // 子集和等於 target 時,記錄解 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i := start; i < len(*choices); i++ { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target-(*choices)[i] < 0 { + break + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if i > start && (*choices)[i] == (*choices)[i-1] { + continue + } + // 嘗試:做出選擇,更新 target, start + *state = append(*state, (*choices)[i]) + // 進行下一輪選擇 + backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 II */ +func subsetSumII(nums []int, target int) [][]int { + state := make([]int, 0) // 狀態(子集) + sort.Ints(nums) // 對 nums 進行排序 + start := 0 // 走訪起始點 + res := make([][]int, 0) // 結果串列(子集串列) + backtrackSubsetSumII(start, target, &state, &nums, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_test.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_test.go new file mode 100644 index 0000000000..5920be25df --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_test.go @@ -0,0 +1,56 @@ +// File: subset_sum_test.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSubsetSumINaive(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumINaive(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } + fmt.Println("請注意,該方法輸出的結果包含重複集合") +} + +func TestSubsetSumI(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumI(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } +} + +func TestSubsetSumII(t *testing.T) { + nums := []int{4, 4, 5} + target := 9 + res := subsetSumII(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/iteration.go b/zh-hant/codes/go/chapter_computational_complexity/iteration.go new file mode 100644 index 0000000000..a65acb8040 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/iteration.go @@ -0,0 +1,59 @@ +// File: iteration.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "fmt" + +/* for 迴圈 */ +func forLoop(n int) int { + res := 0 + // 迴圈求和 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + res += i + } + return res +} + +/* while 迴圈 */ +func whileLoop(n int) int { + res := 0 + // 初始化條件變數 + i := 1 + // 迴圈求和 1, 2, ..., n-1, n + for i <= n { + res += i + // 更新條件變數 + i++ + } + return res +} + +/* while 迴圈(兩次更新) */ +func whileLoopII(n int) int { + res := 0 + // 初始化條件變數 + i := 1 + // 迴圈求和 1, 4, 10, ... + for i <= n { + res += i + // 更新條件變數 + i++ + i *= 2 + } + return res +} + +/* 雙層 for 迴圈 */ +func nestedForLoop(n int) string { + res := "" + // 迴圈 i = 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + for j := 1; j <= n; j++ { + // 迴圈 j = 1, 2, ..., n-1, n + res += fmt.Sprintf("(%d, %d), ", i, j) + } + } + return res +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/iteration_test.go b/zh-hant/codes/go/chapter_computational_complexity/iteration_test.go new file mode 100644 index 0000000000..d7e893c35a --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/iteration_test.go @@ -0,0 +1,26 @@ +// File: iteration_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestIteration(t *testing.T) { + n := 5 + res := forLoop(n) + fmt.Println("\nfor 迴圈的求和結果 res = ", res) + + res = whileLoop(n) + fmt.Println("\nwhile 迴圈的求和結果 res = ", res) + + res = whileLoopII(n) + fmt.Println("\nwhile 迴圈(兩次更新)求和結果 res = ", res) + + resStr := nestedForLoop(n) + fmt.Println("\n雙層 for 迴圈的走訪結果 ", resStr) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/recursion.go b/zh-hant/codes/go/chapter_computational_complexity/recursion.go new file mode 100644 index 0000000000..a0c4d66988 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/recursion.go @@ -0,0 +1,61 @@ +// File: recursion.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "container/list" + +/* 遞迴 */ +func recur(n int) int { + // 終止條件 + if n == 1 { + return 1 + } + // 遞:遞迴呼叫 + res := recur(n - 1) + // 迴:返回結果 + return n + res +} + +/* 使用迭代模擬遞迴 */ +func forLoopRecur(n int) int { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + stack := list.New() + res := 0 + // 遞:遞迴呼叫 + for i := n; i > 0; i-- { + // 透過“入堆疊操作”模擬“遞” + stack.PushBack(i) + } + // 迴:返回結果 + for stack.Len() != 0 { + // 透過“出堆疊操作”模擬“迴” + res += stack.Back().Value.(int) + stack.Remove(stack.Back()) + } + // res = 1+2+3+...+n + return res +} + +/* 尾遞迴 */ +func tailRecur(n int, res int) int { + // 終止條件 + if n == 0 { + return res + } + // 尾遞迴呼叫 + return tailRecur(n-1, res+n) +} + +/* 費波那契數列:遞迴 */ +func fib(n int) int { + // 終止條件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + res := fib(n-1) + fib(n-2) + // 返回結果 f(n) + return res +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/recursion_test.go b/zh-hant/codes/go/chapter_computational_complexity/recursion_test.go new file mode 100644 index 0000000000..4798877db0 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/recursion_test.go @@ -0,0 +1,26 @@ +// File: recursion_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestRecursion(t *testing.T) { + n := 5 + res := recur(n) + fmt.Println("\n遞迴函式的求和結果 res = ", res) + + res = forLoopRecur(n) + fmt.Println("\n使用迭代模擬遞迴求和結果 res = ", res) + + res = tailRecur(n, 0) + fmt.Println("\n尾遞迴函式的求和結果 res = ", res) + + res = fib(n) + fmt.Println("\n費波那契數列的第", n, "項為", res) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/space_complexity.go b/zh-hant/codes/go/chapter_computational_complexity/space_complexity.go new file mode 100644 index 0000000000..8e5f141569 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/space_complexity.go @@ -0,0 +1,106 @@ +// File: space_complexity.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "strconv" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 結構體 */ +type node struct { + val int + next *node +} + +/* 建立 node 結構體 */ +func newNode(val int) *node { + return &node{val: val} +} + +/* 函式 */ +func function() int { + // 執行某些操作... + return 0 +} + +/* 常數階 */ +func spaceConstant(n int) { + // 常數、變數、物件佔用 O(1) 空間 + const a = 0 + b := 0 + nums := make([]int, 10000) + node := newNode(0) + // 迴圈中的變數佔用 O(1) 空間 + var c int + for i := 0; i < n; i++ { + c = 0 + } + // 迴圈中的函式佔用 O(1) 空間 + for i := 0; i < n; i++ { + function() + } + b += 0 + c += 0 + nums[0] = 0 + node.val = 0 +} + +/* 線性階 */ +func spaceLinear(n int) { + // 長度為 n 的陣列佔用 O(n) 空間 + _ = make([]int, n) + // 長度為 n 的串列佔用 O(n) 空間 + var nodes []*node + for i := 0; i < n; i++ { + nodes = append(nodes, newNode(i)) + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + m := make(map[int]string, n) + for i := 0; i < n; i++ { + m[i] = strconv.Itoa(i) + } +} + +/* 線性階(遞迴實現) */ +func spaceLinearRecur(n int) { + fmt.Println("遞迴 n =", n) + if n == 1 { + return + } + spaceLinearRecur(n - 1) +} + +/* 平方階 */ +func spaceQuadratic(n int) { + // 矩陣佔用 O(n^2) 空間 + numMatrix := make([][]int, n) + for i := 0; i < n; i++ { + numMatrix[i] = make([]int, n) + } +} + +/* 平方階(遞迴實現) */ +func spaceQuadraticRecur(n int) int { + if n <= 0 { + return 0 + } + nums := make([]int, n) + fmt.Printf("遞迴 n = %d 中的 nums 長度 = %d \n", n, len(nums)) + return spaceQuadraticRecur(n - 1) +} + +/* 指數階(建立滿二元樹) */ +func buildTree(n int) *TreeNode { + if n == 0 { + return nil + } + root := NewTreeNode(0) + root.Left = buildTree(n - 1) + root.Right = buildTree(n - 1) + return root +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/space_complexity_test.go b/zh-hant/codes/go/chapter_computational_complexity/space_complexity_test.go new file mode 100644 index 0000000000..f40b796229 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/space_complexity_test.go @@ -0,0 +1,26 @@ +// File: space_complexity_test.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSpaceComplexity(t *testing.T) { + n := 5 + // 常數階 + spaceConstant(n) + // 線性階 + spaceLinear(n) + spaceLinearRecur(n) + // 平方階 + spaceQuadratic(n) + spaceQuadraticRecur(n) + // 指數階 + root := buildTree(n) + PrintTree(root) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/time_complexity.go b/zh-hant/codes/go/chapter_computational_complexity/time_complexity.go new file mode 100644 index 0000000000..5592eadaf6 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/time_complexity.go @@ -0,0 +1,130 @@ +// File: time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +/* 常數階 */ +func constant(n int) int { + count := 0 + size := 100000 + for i := 0; i < size; i++ { + count++ + } + return count +} + +/* 線性階 */ +func linear(n int) int { + count := 0 + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* 線性階(走訪陣列) */ +func arrayTraversal(nums []int) int { + count := 0 + // 迴圈次數與陣列長度成正比 + for range nums { + count++ + } + return count +} + +/* 平方階 */ +func quadratic(n int) int { + count := 0 + // 迴圈次數與資料大小 n 成平方關係 + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + count++ + } + } + return count +} + +/* 平方階(泡沫排序) */ +func bubbleSort(nums []int) int { + count := 0 // 計數器 + // 外迴圈:未排序區間為 [0, i] + for i := len(nums) - 1; i > 0; i-- { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // 交換 nums[j] 與 nums[j + 1] + tmp := nums[j] + nums[j] = nums[j+1] + nums[j+1] = tmp + count += 3 // 元素交換包含 3 個單元操作 + } + } + } + return count +} + +/* 指數階(迴圈實現)*/ +func exponential(n int) int { + count, base := 0, 1 + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for i := 0; i < n; i++ { + for j := 0; j < base; j++ { + count++ + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* 指數階(遞迴實現)*/ +func expRecur(n int) int { + if n == 1 { + return 1 + } + return expRecur(n-1) + expRecur(n-1) + 1 +} + +/* 對數階(迴圈實現)*/ +func logarithmic(n int) int { + count := 0 + for n > 1 { + n = n / 2 + count++ + } + return count +} + +/* 對數階(遞迴實現)*/ +func logRecur(n int) int { + if n <= 1 { + return 0 + } + return logRecur(n/2) + 1 +} + +/* 線性對數階 */ +func linearLogRecur(n int) int { + if n <= 1 { + return 1 + } + count := linearLogRecur(n/2) + linearLogRecur(n/2) + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* 階乘階(遞迴實現) */ +func factorialRecur(n int) int { + if n == 0 { + return 1 + } + count := 0 + // 從 1 個分裂出 n 個 + for i := 0; i < n; i++ { + count += factorialRecur(n - 1) + } + return count +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/time_complexity_test.go b/zh-hant/codes/go/chapter_computational_complexity/time_complexity_test.go new file mode 100644 index 0000000000..86b121d7cd --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/time_complexity_test.go @@ -0,0 +1,48 @@ +// File: time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestTimeComplexity(t *testing.T) { + n := 8 + fmt.Println("輸入資料大小 n =", n) + + count := constant(n) + fmt.Println("常數階的操作數量 =", count) + + count = linear(n) + fmt.Println("線性階的操作數量 =", count) + count = arrayTraversal(make([]int, n)) + fmt.Println("線性階(走訪陣列)的操作數量 =", count) + + count = quadratic(n) + fmt.Println("平方階的操作數量 =", count) + nums := make([]int, n) + for i := 0; i < n; i++ { + nums[i] = n - i + } + count = bubbleSort(nums) + fmt.Println("平方階(泡沫排序)的操作數量 =", count) + + count = exponential(n) + fmt.Println("指數階(迴圈實現)的操作數量 =", count) + count = expRecur(n) + fmt.Println("指數階(遞迴實現)的操作數量 =", count) + + count = logarithmic(n) + fmt.Println("對數階(迴圈實現)的操作數量 =", count) + count = logRecur(n) + fmt.Println("對數階(遞迴實現)的操作數量 =", count) + + count = linearLogRecur(n) + fmt.Println("線性對數階(遞迴實現)的操作數量 =", count) + + count = factorialRecur(n) + fmt.Println("階乘階(遞迴實現)的操作數量 =", count) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity.go b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity.go new file mode 100644 index 0000000000..e64bfce533 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity.go @@ -0,0 +1,35 @@ +// File: worst_best_time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "math/rand" +) + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +func randomNumbers(n int) []int { + nums := make([]int, n) + // 生成陣列 nums = { 1, 2, 3, ..., n } + for i := 0; i < n; i++ { + nums[i] = i + 1 + } + // 隨機打亂陣列元素 + rand.Shuffle(len(nums), func(i, j int) { + nums[i], nums[j] = nums[j], nums[i] + }) + return nums +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +func findOne(nums []int) int { + for i := 0; i < len(nums); i++ { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if nums[i] == 1 { + return i + } + } + return -1 +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go new file mode 100644 index 0000000000..af6e04124b --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go @@ -0,0 +1,20 @@ +// File: worst_best_time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestWorstBestTimeComplexity(t *testing.T) { + for i := 0; i < 10; i++ { + n := 100 + nums := randomNumbers(n) + index := findOne(nums) + fmt.Println("\n陣列 [ 1, 2, ..., n ] 被打亂後 =", nums) + fmt.Println("數字 1 的索引為", index) + } +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur.go b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur.go new file mode 100644 index 0000000000..25ac0e4672 --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur.go @@ -0,0 +1,34 @@ +// File: binary_search_recur.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +/* 二分搜尋:問題 f(i, j) */ +func dfs(nums []int, target, i, j int) int { + // 如果區間為空,代表沒有目標元素,則返回 -1 + if i > j { + return -1 + } + // 計算索引中點 + m := i + ((j - i) >> 1) + //判斷中點與目標元素大小 + 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 + } +} + +/* 二分搜尋 */ +func binarySearch(nums []int, target int) int { + n := len(nums) + return dfs(nums, target, 0, n-1) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go new file mode 100644 index 0000000000..afd4f105a5 --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go @@ -0,0 +1,20 @@ +// File: binary_search_recur_test.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + target := 6 + noTarget := 99 + targetIndex := binarySearch(nums, target) + fmt.Println("目標元素 6 的索引 = ", targetIndex) + noTargetIndex := binarySearch(nums, noTarget) + fmt.Println("不存在目標元素的索引 = ", noTargetIndex) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/build_tree.go b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree.go new file mode 100644 index 0000000000..1bc127cdfa --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree.go @@ -0,0 +1,37 @@ +// File: build_tree.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import . "github.com/krahets/hello-algo/pkg" + +/* 構建二元樹:分治 */ +func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { + // 子樹區間為空時終止 + if r-l < 0 { + return nil + } + // 初始化根節點 + root := NewTreeNode(preorder[i]) + // 查詢 m ,從而劃分左右子樹 + m := inorderMap[preorder[i]] + // 子問題:構建左子樹 + root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) + // 子問題:構建右子樹 + root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) + // 返回根節點 + return root +} + +/* 構建二元樹 */ +func buildTree(preorder, inorder []int) *TreeNode { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + inorderMap := make(map[int]int, len(inorder)) + for i := 0; i < len(inorder); i++ { + inorderMap[inorder[i]] = i + } + + root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) + return root +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/build_tree_test.go b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree_test.go new file mode 100644 index 0000000000..9f3d1c2601 --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree_test.go @@ -0,0 +1,25 @@ +// File: build_tree_test.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBuildTree(t *testing.T) { + preorder := []int{3, 9, 2, 1, 7} + inorder := []int{9, 3, 1, 2, 7} + fmt.Print("前序走訪 = ") + PrintSlice(preorder) + fmt.Print("中序走訪 = ") + PrintSlice(inorder) + + root := buildTree(preorder, inorder) + fmt.Println("構建的二元樹為:") + PrintTree(root) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/hanota.go b/zh-hant/codes/go/chapter_divide_and_conquer/hanota.go new file mode 100644 index 0000000000..414b55ba3c --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/hanota.go @@ -0,0 +1,39 @@ +// File: hanota.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import "container/list" + +/* 移動一個圓盤 */ +func move(src, tar *list.List) { + // 從 src 頂部拿出一個圓盤 + pan := src.Back() + // 將圓盤放入 tar 頂部 + tar.PushBack(pan.Value) + // 移除 src 頂部圓盤 + src.Remove(pan) +} + +/* 求解河內塔問題 f(i) */ +func dfsHanota(i int, src, buf, tar *list.List) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 { + move(src, tar) + return + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfsHanota(i-1, src, tar, buf) + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar) + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfsHanota(i-1, buf, src, tar) +} + +/* 求解河內塔問題 */ +func solveHanota(A, B, C *list.List) { + n := A.Len() + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfsHanota(n, A, B, C) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/hanota_test.go b/zh-hant/codes/go/chapter_divide_and_conquer/hanota_test.go new file mode 100644 index 0000000000..a7550d3475 --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/hanota_test.go @@ -0,0 +1,40 @@ +// File: hanota_test.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHanota(t *testing.T) { + // 串列尾部是柱子頂部 + A := list.New() + for i := 5; i > 0; i-- { + A.PushBack(i) + } + B := list.New() + C := list.New() + fmt.Println("初始狀態下:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) + + solveHanota(A, B, C) + + fmt.Println("圓盤移動完成後:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go new file mode 100644 index 0000000000..e76833fff4 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go @@ -0,0 +1,36 @@ +// File: climbing_stairs_backtrack.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 回溯 */ +func backtrack(choices []int, state, n int, res []int) { + // 當爬到第 n 階時,方案數量加 1 + if state == n { + res[0] = res[0] + 1 + } + // 走訪所有選擇 + for _, choice := range choices { + // 剪枝:不允許越過第 n 階 + if state+choice > n { + continue + } + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state+choice, n, res) + // 回退 + } +} + +/* 爬樓梯:回溯 */ +func climbingStairsBacktrack(n int) int { + // 可選擇向上爬 1 階或 2 階 + choices := []int{1, 2} + // 從第 0 階開始爬 + state := 0 + res := make([]int, 1) + // 使用 res[0] 記錄方案數量 + res[0] = 0 + backtrack(choices, state, n, res) + return res[0] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go new file mode 100644 index 0000000000..ec96f8af6e --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go @@ -0,0 +1,25 @@ +// File: climbing_stairs_constraint_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 帶約束爬樓梯:動態規劃 */ +func climbingStairsConstraintDP(n int) int { + if n == 1 || n == 2 { + return 1 + } + // 初始化 dp 表,用於儲存子問題的解 + dp := make([][3]int, n+1) + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + dp[i][1] = dp[i-1][2] + dp[i][2] = dp[i-2][1] + dp[i-2][2] + } + return dp[n][1] + dp[n][2] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go new file mode 100644 index 0000000000..6fb2d6f3f1 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go @@ -0,0 +1,21 @@ +// File: climbing_stairs_dfs.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 搜尋 */ +func dfs(i int) int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfs(i-1) + dfs(i-2) + return count +} + +/* 爬樓梯:搜尋 */ +func climbingStairsDFS(n int) int { + return dfs(n) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go new file mode 100644 index 0000000000..549522379a --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go @@ -0,0 +1,32 @@ +// File: climbing_stairs_dfs_mem.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 記憶化搜尋 */ +func dfsMem(i int, mem []int) int { + // 已知 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] + count := dfsMem(i-1, mem) + dfsMem(i-2, mem) + // 記錄 dp[i] + mem[i] = count + return count +} + +/* 爬樓梯:記憶化搜尋 */ +func climbingStairsDFSMem(n int) int { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + mem := make([]int, n+1) + for i := range mem { + mem[i] = -1 + } + return dfsMem(n, mem) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go new file mode 100644 index 0000000000..22af573e3c --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go @@ -0,0 +1,35 @@ +// File: climbing_stairs_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 爬樓梯:動態規劃 */ +func climbingStairsDP(n int) int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用於儲存子問題的解 + dp := make([]int, n+1) + // 初始狀態:預設最小子問題的解 + dp[1] = 1 + dp[2] = 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + return dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +func climbingStairsDPComp(n int) int { + if n == 1 || n == 2 { + return n + } + a, b := 1, 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + a, b = b, a+b + } + return b +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_test.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_test.go new file mode 100644 index 0000000000..eeaa4c5ad4 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_test.go @@ -0,0 +1,57 @@ +// File: climbing_stairs_test.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestClimbingStairsBacktrack(t *testing.T) { + n := 9 + res := climbingStairsBacktrack(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDFS(t *testing.T) { + n := 9 + res := climbingStairsDFS(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDFSMem(t *testing.T) { + n := 9 + res := climbingStairsDFSMem(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDP(t *testing.T) { + n := 9 + res := climbingStairsDP(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDPComp(t *testing.T) { + n := 9 + res := climbingStairsDPComp(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsConstraintDP(t *testing.T) { + n := 9 + res := climbingStairsConstraintDP(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestMinCostClimbingStairsDPComp(t *testing.T) { + cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} + fmt.Printf("輸入樓梯的代價串列為 %v\n", cost) + + res := minCostClimbingStairsDP(cost) + fmt.Printf("爬完樓梯的最低代價為 %d\n", res) + + res = minCostClimbingStairsDPComp(cost) + fmt.Printf("爬完樓梯的最低代價為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/coin_change.go b/zh-hant/codes/go/chapter_dynamic_programming/coin_change.go new file mode 100644 index 0000000000..ea08ab319f --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/coin_change.go @@ -0,0 +1,66 @@ +// File: coin_change.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 零錢兌換:動態規劃 */ +func coinChangeDP(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 狀態轉移:首行首列 + for a := 1; a <= amt; a++ { + dp[0][a] = max + } + // 狀態轉移:其餘行和列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i-1][a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) + } + } + } + if dp[n][amt] != max { + return dp[n][amt] + } + return -1 +} + +/* 零錢兌換:動態規劃 */ +func coinChangeDPComp(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([]int, amt+1) + for i := 1; i <= amt; i++ { + dp[i] = max + } + // 狀態轉移 + for i := 1; i <= n; i++ { + // 正序走訪 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) + } + } + } + if dp[amt] != max { + return dp[amt] + } + return -1 +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/coin_change_ii.go b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_ii.go new file mode 100644 index 0000000000..7cc4ab72df --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_ii.go @@ -0,0 +1,54 @@ +// File: coin_change_ii.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 零錢兌換 II:動態規劃 */ +func coinChangeIIDP(coins []int, amt int) int { + n := len(coins) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 初始化首列 + for i := 0; i <= n; i++ { + dp[i][0] = 1 + } + // 狀態轉移:其餘行和列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i-1][a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] + } + } + } + return dp[n][amt] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +func coinChangeIIDPComp(coins []int, amt int) int { + n := len(coins) + // 初始化 dp 表 + dp := make([]int, amt+1) + dp[0] = 1 + // 狀態轉移 + for i := 1; i <= n; i++ { + // 正序走訪 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a-coins[i-1]] + } + } + } + return dp[amt] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/coin_change_test.go b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_test.go new file mode 100644 index 0000000000..97bea53dab --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_test.go @@ -0,0 +1,23 @@ +// File: coin_change_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestCoinChange(t *testing.T) { + coins := []int{1, 2, 5} + amt := 4 + + // 動態規劃 + res := coinChangeDP(coins, amt) + fmt.Printf("湊到目標金額所需的最少硬幣數量為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt) + fmt.Printf("湊到目標金額所需的最少硬幣數量為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/edit_distance.go b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance.go new file mode 100644 index 0000000000..90ae241ea6 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance.go @@ -0,0 +1,129 @@ +// File: edit_distance.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 編輯距離:暴力搜尋 */ +func editDistanceDFS(s string, t string, i int, j int) int { + // 若 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 + insert := editDistanceDFS(s, t, i, j-1) + deleted := editDistanceDFS(s, t, i-1, j) + replace := editDistanceDFS(s, t, i-1, j-1) + // 返回最少編輯步數 + return MinInt(MinInt(insert, deleted), replace) + 1 +} + +/* 編輯距離:記憶化搜尋 */ +func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { + // 若 s 和 t 都為空,則返回 0 + if i == 0 && j == 0 { + return 0 + } + // 若 s 為空,則返回 t 長度 + if i == 0 { + return j + } + // 若 t 為空,則返回 s 長度 + if j == 0 { + return i + } + // 若已有記錄,則直接返回之 + if mem[i][j] != -1 { + return mem[i][j] + } + // 若兩字元相等,則直接跳過此兩字元 + if s[i-1] == t[j-1] { + return editDistanceDFSMem(s, t, mem, i-1, j-1) + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert := editDistanceDFSMem(s, t, mem, i, j-1) + deleted := editDistanceDFSMem(s, t, mem, i-1, j) + replace := editDistanceDFSMem(s, t, mem, i-1, j-1) + // 記錄並返回最少編輯步數 + mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 + return mem[i][j] +} + +/* 編輯距離:動態規劃 */ +func editDistanceDP(s string, t string) int { + n := len(s) + m := len(t) + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, m+1) + } + // 狀態轉移:首行首列 + for i := 1; i <= n; i++ { + dp[i][0] = i + } + for j := 1; j <= m; j++ { + dp[0][j] = j + } + // 狀態轉移:其餘行和列 + for i := 1; i <= n; i++ { + for 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] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +func editDistanceDPComp(s string, t string) int { + n := len(s) + m := len(t) + dp := make([]int, m+1) + // 狀態轉移:首行 + for j := 1; j <= m; j++ { + dp[j] = j + } + // 狀態轉移:其餘行 + for i := 1; i <= n; i++ { + // 狀態轉移:首列 + leftUp := dp[0] // 暫存 dp[i-1, j-1] + dp[0] = i + // 狀態轉移:其餘列 + for j := 1; j <= m; j++ { + temp := dp[j] + if s[i-1] == t[j-1] { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftUp + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 + } + leftUp = temp // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m] +} + +func MinInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/edit_distance_test.go b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance_test.go new file mode 100644 index 0000000000..3973397ee3 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance_test.go @@ -0,0 +1,40 @@ +// File: edit_distance_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestEditDistanceDFS(test *testing.T) { + s := "bag" + t := "pack" + n := len(s) + m := len(t) + + // 暴力搜尋 + res := editDistanceDFS(s, t, n, m) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) + + // 記憶化搜尋 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, m+1) + for j := 0; j <= m; j++ { + mem[i][j] = -1 + } + } + res = editDistanceDFSMem(s, t, mem, n, m) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) + + // 動態規劃 + res = editDistanceDP(s, t) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/knapsack.go b/zh-hant/codes/go/chapter_dynamic_programming/knapsack.go new file mode 100644 index 0000000000..029569cf52 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/knapsack.go @@ -0,0 +1,87 @@ +// File: knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 0-1 背包:暴力搜尋 */ +func knapsackDFS(wgt, val []int, i, c int) int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i-1] > c { + return knapsackDFS(wgt, val, i-1, c) + } + // 計算不放入和放入物品 i 的最大價值 + no := knapsackDFS(wgt, val, i-1, c) + yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] + // 返回兩種方案中價值更大的那一個 + return int(math.Max(float64(no), float64(yes))) +} + +/* 0-1 背包:記憶化搜尋 */ +func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0 + } + // 若已有記錄,則直接返回 + if mem[i][c] != -1 { + return mem[i][c] + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i-1] > c { + return knapsackDFSMem(wgt, val, mem, i-1, c) + } + // 計算不放入和放入物品 i 的最大價值 + no := knapsackDFSMem(wgt, val, mem, i-1, c) + yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] + // 返回兩種方案中價值更大的那一個 + mem[i][c] = int(math.Max(float64(no), float64(yes))) + return mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +func knapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 狀態轉移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +func knapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 狀態轉移 + for i := 1; i <= n; i++ { + // 倒序走訪 + for c := cap; c >= 1; c-- { + if wgt[i-1] <= c { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/knapsack_test.go b/zh-hant/codes/go/chapter_dynamic_programming/knapsack_test.go new file mode 100644 index 0000000000..8816bfb039 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/knapsack_test.go @@ -0,0 +1,54 @@ +// File: knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + c := 50 + n := len(wgt) + + // 暴力搜尋 + res := knapsackDFS(wgt, val, n, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 記憶化搜尋 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, c+1) + for j := 0; j <= c; j++ { + mem[i][j] = -1 + } + } + res = knapsackDFSMem(wgt, val, mem, n, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 動態規劃 + res = knapsackDP(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) +} + +func TestUnboundedKnapsack(t *testing.T) { + wgt := []int{1, 2, 3} + val := []int{5, 11, 15} + c := 4 + + // 動態規劃 + res := unboundedKnapsackDP(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go b/zh-hant/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go new file mode 100644 index 0000000000..1b00880cbd --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go @@ -0,0 +1,52 @@ +// File: min_cost_climbing_stairs_dp.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 爬樓梯最小代價:動態規劃 */ +func minCostClimbingStairsDP(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 初始化 dp 表,用於儲存子問題的解 + dp := make([]int, n+1) + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + dp[i] = min(dp[i-1], dp[i-2]) + cost[i] + } + return dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +func minCostClimbingStairsDPComp(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 初始狀態:預設最小子問題的解 + a, b := cost[1], cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + tmp := b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum.go b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum.go new file mode 100644 index 0000000000..ef0c0365bf --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum.go @@ -0,0 +1,94 @@ +// File: min_path_sum.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 最小路徑和:暴力搜尋 */ +func minPathSumDFS(grid [][]int, i, j int) int { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return math.MaxInt + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + up := minPathSumDFS(grid, i-1, j) + left := minPathSumDFS(grid, i, j-1) + // 返回從左上角到 (i, j) 的最小路徑代價 + return int(math.Min(float64(left), float64(up))) + grid[i][j] +} + +/* 最小路徑和:記憶化搜尋 */ +func minPathSumDFSMem(grid, mem [][]int, i, j int) int { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return math.MaxInt + } + // 若已有記錄,則直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左邊和上邊單元格的最小路徑代價 + up := minPathSumDFSMem(grid, mem, i-1, j) + left := minPathSumDFSMem(grid, mem, i, j-1) + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] + return mem[i][j] +} + +/* 最小路徑和:動態規劃 */ +func minPathSumDP(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([][]int, n) + for i := 0; i < n; i++ { + dp[i] = make([]int, m) + } + dp[0][0] = grid[0][0] + // 狀態轉移:首行 + for j := 1; j < m; j++ { + dp[0][j] = dp[0][j-1] + grid[0][j] + } + // 狀態轉移:首列 + for i := 1; i < n; i++ { + dp[i][0] = dp[i-1][0] + grid[i][0] + } + // 狀態轉移:其餘行和列 + for i := 1; i < n; i++ { + for j := 1; j < m; j++ { + dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] + } + } + return dp[n-1][m-1] +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +func minPathSumDPComp(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([]int, m) + // 狀態轉移:首行 + dp[0] = grid[0][0] + for j := 1; j < m; j++ { + dp[j] = dp[j-1] + grid[0][j] + } + // 狀態轉移:其餘行和列 + for i := 1; i < n; i++ { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0] + // 狀態轉移:其餘列 + for j := 1; j < m; j++ { + dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] + } + } + return dp[m-1] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum_test.go b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum_test.go new file mode 100644 index 0000000000..e8e8ce51a3 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum_test.go @@ -0,0 +1,43 @@ +// File: min_path_sum_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestMinPathSum(t *testing.T) { + grid := [][]int{ + {1, 3, 1, 5}, + {2, 2, 4, 2}, + {5, 3, 2, 1}, + {4, 3, 5, 2}, + } + n, m := len(grid), len(grid[0]) + + // 暴力搜尋 + res := minPathSumDFS(grid, n-1, m-1) + fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) + + // 記憶化搜尋 + mem := make([][]int, n) + for i := 0; i < n; i++ { + mem[i] = make([]int, m) + for j := 0; j < m; j++ { + mem[i][j] = -1 + } + } + res = minPathSumDFSMem(grid, mem, n-1, m-1) + fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) + + // 動態規劃 + res = minPathSumDP(grid) + fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid) + fmt.Printf("從左上角到右下角的最小路徑和為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/unbounded_knapsack.go b/zh-hant/codes/go/chapter_dynamic_programming/unbounded_knapsack.go new file mode 100644 index 0000000000..4868aeb00b --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/unbounded_knapsack.go @@ -0,0 +1,50 @@ +// File: unbounded_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 完全背包:動態規劃 */ +func unboundedKnapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 狀態轉移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 完全背包:空間最佳化後的動態規劃 */ +func unboundedKnapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 狀態轉移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_list.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_list.go new file mode 100644 index 0000000000..839d555f9c --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_list.go @@ -0,0 +1,100 @@ +// File: graph_adjacency_list.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "strconv" + "strings" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 基於鄰接表實現的無向圖類別 */ +type graphAdjList struct { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + adjList map[Vertex][]Vertex +} + +/* 建構子 */ +func newGraphAdjList(edges [][]Vertex) *graphAdjList { + g := &graphAdjList{ + adjList: make(map[Vertex][]Vertex), + } + // 新增所有頂點和邊 + for _, edge := range edges { + g.addVertex(edge[0]) + g.addVertex(edge[1]) + g.addEdge(edge[0], edge[1]) + } + return g +} + +/* 獲取頂點數量 */ +func (g *graphAdjList) size() int { + return len(g.adjList) +} + +/* 新增邊 */ +func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 新增邊 vet1 - vet2, 新增匿名 struct{}, + g.adjList[vet1] = append(g.adjList[vet1], vet2) + g.adjList[vet2] = append(g.adjList[vet2], vet1) +} + +/* 刪除邊 */ +func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 刪除邊 vet1 - vet2 + g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) + g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) +} + +/* 新增頂點 */ +func (g *graphAdjList) addVertex(vet Vertex) { + _, ok := g.adjList[vet] + if ok { + return + } + // 在鄰接表中新增一個新鏈結串列 + g.adjList[vet] = make([]Vertex, 0) +} + +/* 刪除頂點 */ +func (g *graphAdjList) removeVertex(vet Vertex) { + _, ok := g.adjList[vet] + if !ok { + panic("error") + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + delete(g.adjList, vet) + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for v, list := range g.adjList { + g.adjList[v] = DeleteSliceElms(list, vet) + } +} + +/* 列印鄰接表 */ +func (g *graphAdjList) print() { + var builder strings.Builder + fmt.Printf("鄰接表 = \n") + for k, v := range g.adjList { + builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") + for _, vet := range v { + builder.WriteString(strconv.Itoa(vet.Val) + " ") + } + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_list_test.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_list_test.go new file mode 100644 index 0000000000..59e254b3fc --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_list_test.go @@ -0,0 +1,45 @@ +// File: graph_adjacency_list_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphAdjList(t *testing.T) { + /* 初始化無向圖 */ + v := ValsToVets([]int{1, 3, 2, 5, 4}) + edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]) + fmt.Println("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]) + fmt.Println("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + v5 := NewVertex(6) + graph.addVertex(v5) + fmt.Println("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]) + fmt.Println("\n刪除頂點 3 後,圖為") + graph.print() +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix.go new file mode 100644 index 0000000000..54e6c108b9 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix.go @@ -0,0 +1,102 @@ +// File: graph_adjacency_matrix.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import "fmt" + +/* 基於鄰接矩陣實現的無向圖類別 */ +type graphAdjMat struct { + // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + vertices []int + // 鄰接矩陣,行列索引對應“頂點索引” + adjMat [][]int +} + +/* 建構子 */ +func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { + // 新增頂點 + n := len(vertices) + adjMat := make([][]int, n) + for i := range adjMat { + adjMat[i] = make([]int, n) + } + // 初始化圖 + g := &graphAdjMat{ + vertices: vertices, + adjMat: adjMat, + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for i := range edges { + g.addEdge(edges[i][0], edges[i][1]) + } + return g +} + +/* 獲取頂點數量 */ +func (g *graphAdjMat) size() int { + return len(g.vertices) +} + +/* 新增頂點 */ +func (g *graphAdjMat) addVertex(val int) { + n := g.size() + // 向頂點串列中新增新頂點的值 + g.vertices = append(g.vertices, val) + // 在鄰接矩陣中新增一行 + newRow := make([]int, n) + g.adjMat = append(g.adjMat, newRow) + // 在鄰接矩陣中新增一列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i], 0) + } +} + +/* 刪除頂點 */ +func (g *graphAdjMat) removeVertex(index int) { + if index >= g.size() { + return + } + // 在頂點串列中移除索引 index 的頂點 + g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) + // 在鄰接矩陣中刪除索引 index 的行 + g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) + // 在鄰接矩陣中刪除索引 index 的列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) + } +} + +/* 新增邊 */ +// 參數 i, j 對應 vertices 元素索引 +func (g *graphAdjMat) addEdge(i, j int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + g.adjMat[i][j] = 1 + g.adjMat[j][i] = 1 +} + +/* 刪除邊 */ +// 參數 i, j 對應 vertices 元素索引 +func (g *graphAdjMat) removeEdge(i, j int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + g.adjMat[i][j] = 0 + g.adjMat[j][i] = 0 +} + +/* 列印鄰接矩陣 */ +func (g *graphAdjMat) print() { + fmt.Printf("\t頂點串列 = %v\n", g.vertices) + fmt.Printf("\t鄰接矩陣 = \n") + for i := range g.adjMat { + fmt.Printf("\t\t\t%v\n", g.adjMat[i]) + } +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix_test.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix_test.go new file mode 100644 index 0000000000..f332eb0369 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix_test.go @@ -0,0 +1,43 @@ +// File: graph_adjacency_matrix_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" +) + +func TestGraphAdjMat(t *testing.T) { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + vertices := []int{1, 3, 2, 5, 4} + edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} + graph := newGraphAdjMat(vertices, edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2) + fmt.Println("新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1) + fmt.Println("刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + graph.addVertex(6) + fmt.Println("新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1) + fmt.Println("刪除頂點 3 後,圖為") + graph.print() +} diff --git a/zh-hant/codes/go/chapter_graph/graph_bfs.go b/zh-hant/codes/go/chapter_graph/graph_bfs.go new file mode 100644 index 0000000000..692365d006 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_bfs.go @@ -0,0 +1,41 @@ +// File: graph_bfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { + // 頂點走訪序列 + res := make([]Vertex, 0) + // 雜湊集合,用於記錄已被訪問過的頂點 + visited := make(map[Vertex]struct{}) + visited[startVet] = struct{}{} + // 佇列用於實現 BFS, 使用切片模擬佇列 + queue := make([]Vertex, 0) + queue = append(queue, startVet) + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + for len(queue) > 0 { + // 佇列首頂點出隊 + vet := queue[0] + queue = queue[1:] + // 記錄訪問頂點 + res = append(res, vet) + // 走訪該頂點的所有鄰接頂點 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 只入列未訪問的頂點 + if !isExist { + queue = append(queue, adjVet) + visited[adjVet] = struct{}{} + } + } + } + // 返回頂點走訪序列 + return res +} diff --git a/zh-hant/codes/go/chapter_graph/graph_bfs_test.go b/zh-hant/codes/go/chapter_graph/graph_bfs_test.go new file mode 100644 index 0000000000..c1023d7667 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_bfs_test.go @@ -0,0 +1,29 @@ +// File: graph_bfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphBFS(t *testing.T) { + /* 初始化無向圖 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, + {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, + {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 廣度優先走訪 */ + res := graphBFS(graph, vets[0]) + fmt.Println("廣度優先走訪(BFS)頂點序列為:") + PrintSlice(VetsToVals(res)) +} diff --git a/zh-hant/codes/go/chapter_graph/graph_dfs.go b/zh-hant/codes/go/chapter_graph/graph_dfs.go new file mode 100644 index 0000000000..90aa26f1cc --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_dfs.go @@ -0,0 +1,36 @@ +// File: graph_dfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 深度優先走訪輔助函式 */ +func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { + // append 操作會返回新的的引用,必須讓原引用重新賦值為新slice的引用 + *res = append(*res, vet) + visited[vet] = struct{}{} + // 走訪該頂點的所有鄰接頂點 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 遞迴訪問鄰接頂點 + if !isExist { + dfs(g, visited, res, adjVet) + } + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { + // 頂點走訪序列 + res := make([]Vertex, 0) + // 雜湊集合,用於記錄已被訪問過的頂點 + visited := make(map[Vertex]struct{}) + dfs(g, visited, &res, startVet) + // 返回頂點走訪序列 + return res +} diff --git a/zh-hant/codes/go/chapter_graph/graph_dfs_test.go b/zh-hant/codes/go/chapter_graph/graph_dfs_test.go new file mode 100644 index 0000000000..0778baabb4 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_dfs_test.go @@ -0,0 +1,28 @@ +// File: graph_dfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphDFS(t *testing.T) { + /* 初始化無向圖 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, + {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 深度優先走訪 */ + res := graphDFS(graph, vets[0]) + fmt.Println("深度優先走訪(DFS)頂點序列為:") + PrintSlice(VetsToVals(res)) +} diff --git a/zh-hant/codes/go/chapter_greedy/coin_change_greedy.go b/zh-hant/codes/go/chapter_greedy/coin_change_greedy.go new file mode 100644 index 0000000000..ea068b652a --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/coin_change_greedy.go @@ -0,0 +1,27 @@ +// File: coin_change_greedy.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +/* 零錢兌換:貪婪 */ +func coinChangeGreedy(coins []int, amt int) int { + // 假設 coins 串列有序 + i := len(coins) - 1 + count := 0 + // 迴圈進行貪婪選擇,直到無剩餘金額 + for amt > 0 { + // 找到小於且最接近剩餘金額的硬幣 + for i > 0 && coins[i] > amt { + i-- + } + // 選擇 coins[i] + amt -= coins[i] + count++ + } + // 若未找到可行方案,則返回 -1 + if amt != 0 { + return -1 + } + return count +} diff --git a/zh-hant/codes/go/chapter_greedy/coin_change_greedy_test.go b/zh-hant/codes/go/chapter_greedy/coin_change_greedy_test.go new file mode 100644 index 0000000000..15cc386758 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/coin_change_greedy_test.go @@ -0,0 +1,35 @@ +// File: coin_change_greedy_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestCoinChangeGreedy(t *testing.T) { + // 貪婪:能夠保證找到全域性最優解 + coins := []int{1, 5, 10, 20, 50, 100} + amt := 186 + res := coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) + + // 貪婪:無法保證找到全域性最優解 + coins = []int{1, 20, 50} + amt = 60 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) + fmt.Println("實際上需要的最少數量為 3 ,即 20 + 20 + 20") + + // 貪婪:無法保證找到全域性最優解 + coins = []int{1, 49, 50} + amt = 98 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) + fmt.Println("實際上需要的最少數量為 2 ,即 49 + 49") +} diff --git a/zh-hant/codes/go/chapter_greedy/fractional_knapsack.go b/zh-hant/codes/go/chapter_greedy/fractional_knapsack.go new file mode 100644 index 0000000000..bfccb9d953 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/fractional_knapsack.go @@ -0,0 +1,41 @@ +// File: fractional_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "sort" + +/* 物品 */ +type Item struct { + w int // 物品重量 + v int // 物品價值 +} + +/* 分數背包:貪婪 */ +func fractionalKnapsack(wgt []int, val []int, cap int) float64 { + // 建立物品串列,包含兩個屬性:重量、價值 + items := make([]Item, len(wgt)) + for i := 0; i < len(wgt); i++ { + items[i] = Item{wgt[i], val[i]} + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + sort.Slice(items, func(i, j int) bool { + return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) + }) + // 迴圈貪婪選擇 + res := 0.0 + for _, item := range items { + if item.w <= cap { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += float64(item.v) + cap -= item.w + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += float64(item.v) / float64(item.w) * float64(cap) + // 已無剩餘容量,因此跳出迴圈 + break + } + } + return res +} diff --git a/zh-hant/codes/go/chapter_greedy/fractional_knapsack_test.go b/zh-hant/codes/go/chapter_greedy/fractional_knapsack_test.go new file mode 100644 index 0000000000..9de0a9384f --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/fractional_knapsack_test.go @@ -0,0 +1,20 @@ +// File: fractional_knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestFractionalKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + capacity := 50 + + // 貪婪演算法 + res := fractionalKnapsack(wgt, val, capacity) + fmt.Println("不超過背包容量的最大物品價值為", res) +} diff --git a/zh-hant/codes/go/chapter_greedy/max_capacity.go b/zh-hant/codes/go/chapter_greedy/max_capacity.go new file mode 100644 index 0000000000..ca768df4c6 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_capacity.go @@ -0,0 +1,28 @@ +// File: max_capacity.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大容量:貪婪 */ +func maxCapacity(ht []int) int { + // 初始化 i, j,使其分列陣列兩端 + i, j := 0, len(ht)-1 + // 初始最大容量為 0 + res := 0 + // 迴圈貪婪選擇,直至兩板相遇 + for i < j { + // 更新最大容量 + capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) + res = int(math.Max(float64(res), float64(capacity))) + // 向內移動短板 + if ht[i] < ht[j] { + i++ + } else { + j-- + } + } + return res +} diff --git a/zh-hant/codes/go/chapter_greedy/max_capacity_test.go b/zh-hant/codes/go/chapter_greedy/max_capacity_test.go new file mode 100644 index 0000000000..fe1724c780 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_capacity_test.go @@ -0,0 +1,18 @@ +// File: max_capacity_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxCapacity(t *testing.T) { + ht := []int{3, 8, 5, 2, 7, 7, 3, 4} + + // 貪婪演算法 + res := maxCapacity(ht) + fmt.Println("最大容量為", res) +} diff --git a/zh-hant/codes/go/chapter_greedy/max_product_cutting.go b/zh-hant/codes/go/chapter_greedy/max_product_cutting.go new file mode 100644 index 0000000000..6509696150 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_product_cutting.go @@ -0,0 +1,28 @@ +// File: max_product_cutting.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大切分乘積:貪婪 */ +func maxProductCutting(n int) int { + // 當 n <= 3 時,必須切分出一個 1 + if n <= 3 { + return 1 * (n - 1) + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + a := n / 3 + b := n % 3 + if b == 1 { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return int(math.Pow(3, float64(a-1))) * 2 * 2 + } + if b == 2 { + // 當餘數為 2 時,不做處理 + return int(math.Pow(3, float64(a))) * 2 + } + // 當餘數為 0 時,不做處理 + return int(math.Pow(3, float64(a))) +} diff --git a/zh-hant/codes/go/chapter_greedy/max_product_cutting_test.go b/zh-hant/codes/go/chapter_greedy/max_product_cutting_test.go new file mode 100644 index 0000000000..0f246bdb91 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_product_cutting_test.go @@ -0,0 +1,17 @@ +// File: max_product_cutting_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxProductCutting(t *testing.T) { + n := 58 + // 貪婪演算法 + res := maxProductCutting(n) + fmt.Println("最大切分乘積為", res) +} diff --git a/zh-hant/codes/go/chapter_hashing/array_hash_map.go b/zh-hant/codes/go/chapter_hashing/array_hash_map.go new file mode 100644 index 0000000000..d1fac07149 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/array_hash_map.go @@ -0,0 +1,97 @@ +// File: array_hash_map.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import "fmt" + +/* 鍵值對 */ +type pair struct { + key int + val string +} + +/* 基於陣列實現的雜湊表 */ +type arrayHashMap struct { + buckets []*pair +} + +/* 初始化雜湊表 */ +func newArrayHashMap() *arrayHashMap { + // 初始化陣列,包含 100 個桶 + buckets := make([]*pair, 100) + return &arrayHashMap{buckets: buckets} +} + +/* 雜湊函式 */ +func (a *arrayHashMap) hashFunc(key int) int { + index := key % 100 + return index +} + +/* 查詢操作 */ +func (a *arrayHashMap) get(key int) string { + index := a.hashFunc(key) + pair := a.buckets[index] + if pair == nil { + return "Not Found" + } + return pair.val +} + +/* 新增操作 */ +func (a *arrayHashMap) put(key int, val string) { + pair := &pair{key: key, val: val} + index := a.hashFunc(key) + a.buckets[index] = pair +} + +/* 刪除操作 */ +func (a *arrayHashMap) remove(key int) { + index := a.hashFunc(key) + // 置為 nil ,代表刪除 + a.buckets[index] = nil +} + +/* 獲取所有鍵對 */ +func (a *arrayHashMap) pairSet() []*pair { + var pairs []*pair + for _, pair := range a.buckets { + if pair != nil { + pairs = append(pairs, pair) + } + } + return pairs +} + +/* 獲取所有鍵 */ +func (a *arrayHashMap) keySet() []int { + var keys []int + for _, pair := range a.buckets { + if pair != nil { + keys = append(keys, pair.key) + } + } + return keys +} + +/* 獲取所有值 */ +func (a *arrayHashMap) valueSet() []string { + var values []string + for _, pair := range a.buckets { + if pair != nil { + values = append(values, pair.val) + } + } + return values +} + +/* 列印雜湊表 */ +func (a *arrayHashMap) print() { + for _, pair := range a.buckets { + if pair != nil { + fmt.Println(pair.key, "->", pair.val) + } + } +} diff --git a/zh-hant/codes/go/chapter_hashing/array_hash_map_test.go b/zh-hant/codes/go/chapter_hashing/array_hash_map_test.go new file mode 100644 index 0000000000..e2e62db8da --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/array_hash_map_test.go @@ -0,0 +1,52 @@ +// File: array_hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestArrayHashMap(t *testing.T) { + /* 初始化雜湊表 */ + hmap := newArrayHashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + hmap.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap.get(15937) + fmt.Println("\n輸入學號 15937 ,查詢到姓名 " + name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + hmap.remove(10583) + fmt.Println("\n刪除 10583 後,雜湊表為\nKey -> Value") + hmap.print() + + /* 走訪雜湊表 */ + fmt.Println("\n走訪鍵值對 Key->Value") + for _, kv := range hmap.pairSet() { + fmt.Println(kv.key, " -> ", kv.val) + } + + fmt.Println("\n單獨走訪鍵 Key") + for _, key := range hmap.keySet() { + fmt.Println(key) + } + + fmt.Println("\n單獨走訪值 Value") + for _, val := range hmap.valueSet() { + fmt.Println(val) + } +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_collision_test.go b/zh-hant/codes/go/chapter_hashing/hash_collision_test.go new file mode 100644 index 0000000000..67184ad297 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_collision_test.go @@ -0,0 +1,62 @@ +// File: hash_collision_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestHashMapChaining(t *testing.T) { + /* 初始化雜湊表 */ + hmap := newHashMapChaining() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + hmap.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap.get(15937) + fmt.Println("\n輸入學號 15937 ,查詢到姓名", name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + hmap.remove(12836) + fmt.Println("\n刪除 12836 後,雜湊表為\nKey -> Value") + hmap.print() +} + +func TestHashMapOpenAddressing(t *testing.T) { + /* 初始化雜湊表 */ + hmap := newHashMapOpenAddressing() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + hmap.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap.get(13276) + fmt.Println("\n輸入學號 13276 ,查詢到姓名 ", name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + hmap.remove(16750) + fmt.Println("\n刪除 16750 後,雜湊表為\nKey -> Value") + hmap.print() +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_map_chaining.go b/zh-hant/codes/go/chapter_hashing/hash_map_chaining.go new file mode 100644 index 0000000000..acdb8805e4 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_map_chaining.go @@ -0,0 +1,134 @@ +// File: hash_map_chaining.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "strings" +) + +/* 鏈式位址雜湊表 */ +type hashMapChaining struct { + size int // 鍵值對數量 + capacity int // 雜湊表容量 + loadThres float64 // 觸發擴容的負載因子閾值 + extendRatio int // 擴容倍數 + buckets [][]pair // 桶陣列 +} + +/* 建構子 */ +func newHashMapChaining() *hashMapChaining { + buckets := make([][]pair, 4) + for i := 0; i < 4; i++ { + buckets[i] = make([]pair, 0) + } + return &hashMapChaining{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: buckets, + } +} + +/* 雜湊函式 */ +func (m *hashMapChaining) hashFunc(key int) int { + return key % m.capacity +} + +/* 負載因子 */ +func (m *hashMapChaining) loadFactor() float64 { + return float64(m.size) / float64(m.capacity) +} + +/* 查詢操作 */ +func (m *hashMapChaining) get(key int) string { + idx := m.hashFunc(key) + bucket := m.buckets[idx] + // 走訪桶,若找到 key ,則返回對應 val + for _, p := range bucket { + if p.key == key { + return p.val + } + } + // 若未找到 key ,則返回空字串 + return "" +} + +/* 新增操作 */ +func (m *hashMapChaining) put(key int, val string) { + // 當負載因子超過閾值時,執行擴容 + if m.loadFactor() > m.loadThres { + m.extend() + } + idx := m.hashFunc(key) + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for i := range m.buckets[idx] { + if m.buckets[idx][i].key == key { + m.buckets[idx][i].val = val + return + } + } + // 若無該 key ,則將鍵值對新增至尾部 + p := pair{ + key: key, + val: val, + } + m.buckets[idx] = append(m.buckets[idx], p) + m.size += 1 +} + +/* 刪除操作 */ +func (m *hashMapChaining) remove(key int) { + idx := m.hashFunc(key) + // 走訪桶,從中刪除鍵值對 + for i, p := range m.buckets[idx] { + if p.key == key { + // 切片刪除 + m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) + m.size -= 1 + break + } + } +} + +/* 擴容雜湊表 */ +func (m *hashMapChaining) extend() { + // 暫存原雜湊表 + tmpBuckets := make([][]pair, len(m.buckets)) + for i := 0; i < len(m.buckets); i++ { + tmpBuckets[i] = make([]pair, len(m.buckets[i])) + copy(tmpBuckets[i], m.buckets[i]) + } + // 初始化擴容後的新雜湊表 + m.capacity *= m.extendRatio + m.buckets = make([][]pair, m.capacity) + for i := 0; i < m.capacity; i++ { + m.buckets[i] = make([]pair, 0) + } + m.size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for _, bucket := range tmpBuckets { + for _, p := range bucket { + m.put(p.key, p.val) + } + } +} + +/* 列印雜湊表 */ +func (m *hashMapChaining) print() { + var builder strings.Builder + + for _, bucket := range m.buckets { + builder.WriteString("[") + for _, p := range bucket { + builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") + } + builder.WriteString("]") + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_map_open_addressing.go b/zh-hant/codes/go/chapter_hashing/hash_map_open_addressing.go new file mode 100644 index 0000000000..943f216912 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_map_open_addressing.go @@ -0,0 +1,126 @@ +// File: hash_map_open_addressing.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" +) + +/* 開放定址雜湊表 */ +type hashMapOpenAddressing struct { + size int // 鍵值對數量 + capacity int // 雜湊表容量 + loadThres float64 // 觸發擴容的負載因子閾值 + extendRatio int // 擴容倍數 + buckets []*pair // 桶陣列 + TOMBSTONE *pair // 刪除標記 +} + +/* 建構子 */ +func newHashMapOpenAddressing() *hashMapOpenAddressing { + return &hashMapOpenAddressing{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: make([]*pair, 4), + TOMBSTONE: &pair{-1, "-1"}, + } +} + +/* 雜湊函式 */ +func (h *hashMapOpenAddressing) hashFunc(key int) int { + return key % h.capacity // 根據鍵計算雜湊值 +} + +/* 負載因子 */ +func (h *hashMapOpenAddressing) loadFactor() float64 { + return float64(h.size) / float64(h.capacity) // 計算當前負載因子 +} + +/* 搜尋 key 對應的桶索引 */ +func (h *hashMapOpenAddressing) findBucket(key int) int { + index := h.hashFunc(key) // 獲取初始索引 + firstTombstone := -1 // 記錄遇到的第一個TOMBSTONE的位置 + for h.buckets[index] != nil { + if h.buckets[index].key == key { + if firstTombstone != -1 { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + h.buckets[firstTombstone] = h.buckets[index] + h.buckets[index] = h.TOMBSTONE + return firstTombstone // 返回移動後的桶索引 + } + return index // 返回找到的索引 + } + if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { + firstTombstone = index // 記錄遇到的首個刪除標記的位置 + } + index = (index + 1) % h.capacity // 線性探查,越過尾部則返回頭部 + } + // 若 key 不存在,則返回新增點的索引 + if firstTombstone != -1 { + return firstTombstone + } + return index +} + +/* 查詢操作 */ +func (h *hashMapOpenAddressing) get(key int) string { + index := h.findBucket(key) // 搜尋 key 對應的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + return h.buckets[index].val // 若找到鍵值對,則返回對應 val + } + return "" // 若鍵值對不存在,則返回 "" +} + +/* 新增操作 */ +func (h *hashMapOpenAddressing) put(key int, val string) { + if h.loadFactor() > h.loadThres { + h.extend() // 當負載因子超過閾值時,執行擴容 + } + index := h.findBucket(key) // 搜尋 key 對應的桶索引 + if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { + h.buckets[index] = &pair{key, val} // 若鍵值對不存在,則新增該鍵值對 + h.size++ + } else { + h.buckets[index].val = val // 若找到鍵值對,則覆蓋 val + } +} + +/* 刪除操作 */ +func (h *hashMapOpenAddressing) remove(key int) { + index := h.findBucket(key) // 搜尋 key 對應的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + h.buckets[index] = h.TOMBSTONE // 若找到鍵值對,則用刪除標記覆蓋它 + h.size-- + } +} + +/* 擴容雜湊表 */ +func (h *hashMapOpenAddressing) extend() { + oldBuckets := h.buckets // 暫存原雜湊表 + h.capacity *= h.extendRatio // 更新容量 + h.buckets = make([]*pair, h.capacity) // 初始化擴容後的新雜湊表 + h.size = 0 // 重置大小 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for _, pair := range oldBuckets { + if pair != nil && pair != h.TOMBSTONE { + h.put(pair.key, pair.val) + } + } +} + +/* 列印雜湊表 */ +func (h *hashMapOpenAddressing) print() { + for _, pair := range h.buckets { + if pair == nil { + fmt.Println("nil") + } else if pair == h.TOMBSTONE { + fmt.Println("TOMBSTONE") + } else { + fmt.Printf("%d -> %s\n", pair.key, pair.val) + } + } +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_map_test.go b/zh-hant/codes/go/chapter_hashing/hash_map_test.go new file mode 100644 index 0000000000..bcf64ee911 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_map_test.go @@ -0,0 +1,74 @@ +// File: hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashMap(t *testing.T) { + /* 初始化雜湊表 */ + hmap := make(map[int]string) + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + PrintMap(hmap) + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap[15937] + fmt.Println("\n輸入學號 15937 ,查詢到姓名 ", name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + delete(hmap, 10583) + fmt.Println("\n刪除 10583 後,雜湊表為\nKey -> Value") + PrintMap(hmap) + + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + fmt.Println("\n走訪鍵值對 Key->Value") + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // 單獨走訪鍵 key + fmt.Println("\n單獨走訪鍵 Key") + for key := range hmap { + fmt.Println(key) + } + // 單獨走訪值 value + fmt.Println("\n單獨走訪值 Value") + for _, value := range hmap { + fmt.Println(value) + } +} + +func TestSimpleHash(t *testing.T) { + var hash int + + key := "Hello 演算法" + + hash = addHash(key) + fmt.Println("加法雜湊值為 " + strconv.Itoa(hash)) + + hash = mulHash(key) + fmt.Println("乘法雜湊值為 " + strconv.Itoa(hash)) + + hash = xorHash(key) + fmt.Println("互斥或雜湊值為 " + strconv.Itoa(hash)) + + hash = rotHash(key) + fmt.Println("旋轉雜湊值為 " + strconv.Itoa(hash)) +} diff --git a/zh-hant/codes/go/chapter_hashing/simple_hash.go b/zh-hant/codes/go/chapter_hashing/simple_hash.go new file mode 100644 index 0000000000..db2e4c5bdd --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/simple_hash.go @@ -0,0 +1,55 @@ +// File: simple_hash.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import "fmt" + +/* 加法雜湊 */ +func addHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (hash + int64(b)) % modulus + } + return int(hash) +} + +/* 乘法雜湊 */ +func mulHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (31*hash + int64(b)) % modulus + } + return int(hash) +} + +/* 互斥或雜湊 */ +func xorHash(key string) int { + hash := 0 + modulus := 1000000007 + for _, b := range []byte(key) { + fmt.Println(int(b)) + hash ^= int(b) + hash = (31*hash + int(b)) % modulus + } + return hash & modulus +} + +/* 旋轉雜湊 */ +func rotHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus + } + return int(hash) +} diff --git a/zh-hant/codes/go/chapter_heap/heap.go b/zh-hant/codes/go/chapter_heap/heap.go new file mode 100644 index 0000000000..765556a49a --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/heap.go @@ -0,0 +1,45 @@ +// File: heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +// Go 語言中可以透過實現 heap.Interface 來構建整數大頂堆積 +// 實現 heap.Interface 需要同時實現 sort.Interface +type intHeap []any + +// Push heap.Interface 的函式,實現推入元素到堆積 +func (h *intHeap) Push(x any) { + // Push 和 Pop 使用 pointer receiver 作為參數 + // 因為它們不僅會對切片的內容進行調整,還會修改切片的長度。 + *h = append(*h, x.(int)) +} + +// Pop heap.Interface 的函式,實現彈出堆積頂元素 +func (h *intHeap) Pop() any { + // 待出堆積元素存放在最後 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Len sort.Interface 的函式 +func (h *intHeap) Len() int { + return len(*h) +} + +// Less sort.Interface 的函式 +func (h *intHeap) Less(i, j int) bool { + // 如果實現小頂堆積,則需要調整為小於號 + return (*h)[i].(int) > (*h)[j].(int) +} + +// Swap sort.Interface 的函式 +func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] +} + +// Top 獲取堆積頂元素 +func (h *intHeap) Top() any { + return (*h)[0] +} diff --git a/zh-hant/codes/go/chapter_heap/heap_test.go b/zh-hant/codes/go/chapter_heap/heap_test.go new file mode 100644 index 0000000000..3fb6e1ae48 --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/heap_test.go @@ -0,0 +1,101 @@ +// File: heap_test.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "container/heap" + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func testPush(h *intHeap, val int) { + // 呼叫 heap.Interface 的函式,來新增元素 + heap.Push(h, val) + fmt.Printf("\n元素 %d 入堆積後 \n", val) + PrintHeap(*h) +} + +func testPop(h *intHeap) { + // 呼叫 heap.Interface 的函式,來移除元素 + val := heap.Pop(h) + fmt.Printf("\n堆積頂元素 %d 出堆積後 \n", val) + PrintHeap(*h) +} + +func TestHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 元素入堆積 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* 獲取堆積頂元素 */ + top := maxHeap.Top() + fmt.Printf("堆積頂元素為 %d\n", top) + + /* 堆積頂元素出堆積 */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* 獲取堆積大小 */ + size := len(*maxHeap) + fmt.Printf("堆積元素數量為 %d\n", size) + + /* 判斷堆積是否為空 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("堆積是否為空 %t\n", isEmpty) +} + +func TestMyHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) + fmt.Printf("輸入陣列並建堆積後\n") + maxHeap.print() + + /* 獲取堆積頂元素 */ + peek := maxHeap.peek() + fmt.Printf("\n堆積頂元素為 %d\n", peek) + + /* 元素入堆積 */ + val := 7 + maxHeap.push(val) + fmt.Printf("\n元素 %d 入堆積後\n", val) + maxHeap.print() + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop() + fmt.Printf("\n堆積頂元素 %d 出堆積後\n", peek) + maxHeap.print() + + /* 獲取堆積大小 */ + size := maxHeap.size() + fmt.Printf("\n堆積元素數量為 %d\n", size) + + /* 判斷堆積是否為空 */ + isEmpty := maxHeap.isEmpty() + fmt.Printf("\n堆積是否為空 %t\n", isEmpty) +} + +func TestTopKHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + nums := []int{1, 7, 6, 3, 2} + k := 3 + res := topKHeap(nums, k) + fmt.Printf("最大的 " + strconv.Itoa(k) + " 個元素為") + PrintHeap(*res) +} diff --git a/zh-hant/codes/go/chapter_heap/my_heap.go b/zh-hant/codes/go/chapter_heap/my_heap.go new file mode 100644 index 0000000000..e25189f216 --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/my_heap.go @@ -0,0 +1,140 @@ +// File: my_heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "fmt" + + . "github.com/krahets/hello-algo/pkg" +) + +type maxHeap struct { + // 使用切片而非陣列,這樣無須考慮擴容問題 + data []any +} + +/* 建構子,建立空堆積 */ +func newHeap() *maxHeap { + return &maxHeap{ + data: make([]any, 0), + } +} + +/* 建構子,根據切片建堆積 */ +func newMaxHeap(nums []any) *maxHeap { + // 將串列元素原封不動新增進堆積 + h := &maxHeap{data: nums} + for i := h.parent(len(h.data) - 1); i >= 0; i-- { + // 堆積化除葉節點以外的其他所有節點 + h.siftDown(i) + } + return h +} + +/* 獲取左子節點的索引 */ +func (h *maxHeap) left(i int) int { + return 2*i + 1 +} + +/* 獲取右子節點的索引 */ +func (h *maxHeap) right(i int) int { + return 2*i + 2 +} + +/* 獲取父節點的索引 */ +func (h *maxHeap) parent(i int) int { + // 向下整除 + return (i - 1) / 2 +} + +/* 交換元素 */ +func (h *maxHeap) swap(i, j int) { + h.data[i], h.data[j] = h.data[j], h.data[i] +} + +/* 獲取堆積大小 */ +func (h *maxHeap) size() int { + return len(h.data) +} + +/* 判斷堆積是否為空 */ +func (h *maxHeap) isEmpty() bool { + return len(h.data) == 0 +} + +/* 訪問堆積頂元素 */ +func (h *maxHeap) peek() any { + return h.data[0] +} + +/* 元素入堆積 */ +func (h *maxHeap) push(val any) { + // 新增節點 + h.data = append(h.data, val) + // 從底至頂堆積化 + h.siftUp(len(h.data) - 1) +} + +/* 從節點 i 開始,從底至頂堆積化 */ +func (h *maxHeap) siftUp(i int) { + for true { + // 獲取節點 i 的父節點 + p := h.parent(i) + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if p < 0 || h.data[i].(int) <= h.data[p].(int) { + break + } + // 交換兩節點 + h.swap(i, p) + // 迴圈向上堆積化 + i = p + } +} + +/* 元素出堆積 */ +func (h *maxHeap) pop() any { + // 判空處理 + if h.isEmpty() { + fmt.Println("error") + return nil + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + h.swap(0, h.size()-1) + // 刪除節點 + val := h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + // 從頂至底堆積化 + h.siftDown(0) + + // 返回堆積頂元素 + return val +} + +/* 從節點 i 開始,從頂至底堆積化 */ +func (h *maxHeap) siftDown(i int) { + for true { + // 判斷節點 i, l, r 中值最大的節點,記為 max + l, r, max := h.left(i), h.right(i), i + if l < h.size() && h.data[l].(int) > h.data[max].(int) { + max = l + } + if r < h.size() && h.data[r].(int) > h.data[max].(int) { + max = r + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if max == i { + break + } + // 交換兩節點 + h.swap(i, max) + // 迴圈向下堆積化 + i = max + } +} + +/* 列印堆積(二元樹) */ +func (h *maxHeap) print() { + PrintHeap(h.data) +} diff --git a/zh-hant/codes/go/chapter_heap/top_k.go b/zh-hant/codes/go/chapter_heap/top_k.go new file mode 100644 index 0000000000..21a4bb7acd --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/top_k.go @@ -0,0 +1,51 @@ +// File: top_k.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import "container/heap" + +type minHeap []any + +func (h *minHeap) Len() int { return len(*h) } +func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } +func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } + +// Push heap.Interface 的方法,實現推入元素到堆積 +func (h *minHeap) Push(x any) { + *h = append(*h, x.(int)) +} + +// Pop heap.Interface 的方法,實現彈出堆積頂元素 +func (h *minHeap) Pop() any { + // 待出堆積元素存放在最後 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Top 獲取堆積頂元素 +func (h *minHeap) Top() any { + return (*h)[0] +} + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +func topKHeap(nums []int, k int) *minHeap { + // 初始化小頂堆積 + h := &minHeap{} + heap.Init(h) + // 將陣列的前 k 個元素入堆積 + for i := 0; i < k; i++ { + heap.Push(h, nums[i]) + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for i := k; i < len(nums); i++ { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if nums[i] > h.Top().(int) { + heap.Pop(h) + heap.Push(h, nums[i]) + } + } + return h +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search.go b/zh-hant/codes/go/chapter_searching/binary_search.go new file mode 100644 index 0000000000..b78f615d6c --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search.go @@ -0,0 +1,43 @@ +// File: binary_search.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +/* 二分搜尋(雙閉區間) */ +func binarySearch(nums []int, target int) int { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + i, j := 0, len(nums)-1 + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + for i <= j { + 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 +} + +/* 二分搜尋(左閉右開區間) */ +func binarySearchLCRO(nums []int, target int) int { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + i, j := 0, len(nums) + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + for i < j { + 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 +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search_edge.go b/zh-hant/codes/go/chapter_searching/binary_search_edge.go new file mode 100644 index 0000000000..017744d82f --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search_edge.go @@ -0,0 +1,31 @@ +// File: binary_search_edge.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 二分搜尋最左一個 target */ +func binarySearchLeftEdge(nums []int, target int) int { + // 等價於查詢 target 的插入點 + i := binarySearchInsertion(nums, target) + // 未找到 target ,返回 -1 + if i == len(nums) || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分搜尋最右一個 target */ +func binarySearchRightEdge(nums []int, target int) int { + // 轉化為查詢最左一個 target + 1 + i := binarySearchInsertion(nums, target+1) + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + j := i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search_insertion.go b/zh-hant/codes/go/chapter_searching/binary_search_insertion.go new file mode 100644 index 0000000000..1aef060843 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search_insertion.go @@ -0,0 +1,49 @@ +// File: binary_search_insertion.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 二分搜尋插入點(無重複元素) */ +func binarySearchInsertionSimple(nums []int, target int) int { + // 初始化雙閉區間 [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // 計算中點索引 m + m := i + (j-i)/2 + if nums[m] < target { + // target 在區間 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { + // target 在區間 [i, m-1] 中 + j = m - 1 + } else { + // 找到 target ,返回插入點 m + return m + } + } + // 未找到 target ,返回插入點 i + return i +} + +/* 二分搜尋插入點(存在重複元素) */ +func binarySearchInsertion(nums []int, target int) int { + // 初始化雙閉區間 [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // 計算中點索引 m + m := i + (j-i)/2 + if nums[m] < target { + // target 在區間 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { + // target 在區間 [i, m-1] 中 + j = m - 1 + } else { + // 首個小於 target 的元素在區間 [i, m-1] 中 + j = m - 1 + } + } + // 返回插入點 i + return i +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search_test.go b/zh-hant/codes/go/chapter_searching/binary_search_test.go new file mode 100644 index 0000000000..ea650b20dd --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search_test.go @@ -0,0 +1,61 @@ +// File: binary_search_test.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + var ( + target = 6 + nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + expected = 2 + ) + // 在陣列中執行二分搜尋 + actual := binarySearch(nums, target) + fmt.Println("目標元素 6 的索引 =", actual) + if actual != expected { + t.Errorf("目標元素 6 的索引 = %d, 應該為 %d", actual, expected) + } +} + +func TestBinarySearchEdge(t *testing.T) { + // 包含重複元素的陣列 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("\n陣列 nums = ", nums) + + // 二分搜尋左邊界和右邊界 + for _, target := range []int{6, 7} { + index := binarySearchLeftEdge(nums, target) + fmt.Println("最左一個元素", target, "的索引為", index) + + index = binarySearchRightEdge(nums, target) + fmt.Println("最右一個元素", target, "的索引為", index) + } +} + +func TestBinarySearchInsertion(t *testing.T) { + // 無重複元素的陣列 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("陣列 nums =", nums) + + // 二分搜尋插入點 + for _, target := range []int{6, 9} { + index := binarySearchInsertionSimple(nums, target) + fmt.Println("元素", target, "的插入點的索引為", index) + } + + // 包含重複元素的陣列 + nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} + fmt.Println("\n陣列 nums =", nums) + + // 二分搜尋插入點 + for _, target := range []int{2, 6, 20} { + index := binarySearchInsertion(nums, target) + fmt.Println("元素", target, "的插入點的索引為", index) + } +} diff --git a/zh-hant/codes/go/chapter_searching/hashing_search.go b/zh-hant/codes/go/chapter_searching/hashing_search.go new file mode 100644 index 0000000000..46852e1c98 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/hashing_search.go @@ -0,0 +1,29 @@ +// File: hashing_search.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import . "github.com/krahets/hello-algo/pkg" + +/* 雜湊查詢(陣列) */ +func hashingSearchArray(m map[int]int, target int) int { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if index, ok := m[target]; ok { + return index + } else { + return -1 + } +} + +/* 雜湊查詢(鏈結串列) */ +func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 nil + if node, ok := m[target]; ok { + return node + } else { + return nil + } +} diff --git a/zh-hant/codes/go/chapter_searching/hashing_search_test.go b/zh-hant/codes/go/chapter_searching/hashing_search_test.go new file mode 100644 index 0000000000..52488fb80a --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/hashing_search_test.go @@ -0,0 +1,36 @@ +// File: hashing_search_test.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashingSearch(t *testing.T) { + target := 3 + /* 雜湊查詢(陣列) */ + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + // 初始化雜湊表 + m := make(map[int]int) + for i := 0; i < len(nums); i++ { + m[nums[i]] = i + } + index := hashingSearchArray(m, target) + fmt.Println("目標元素 3 的索引 = ", index) + + /* 雜湊查詢(鏈結串列) */ + head := ArrayToLinkedList(nums) + // 初始化雜湊表 + m1 := make(map[int]*ListNode) + for head != nil { + m1[head.Val] = head + head = head.Next + } + node := hashingSearchLinkedList(m1, target) + fmt.Println("目標節點值 3 的對應節點物件為 ", node) +} diff --git a/zh-hant/codes/go/chapter_searching/linear_search.go b/zh-hant/codes/go/chapter_searching/linear_search.go new file mode 100644 index 0000000000..68a87ee440 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/linear_search.go @@ -0,0 +1,36 @@ +// File: linear_search.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 線性查詢(陣列) */ +func linearSearchArray(nums []int, target int) int { + // 走訪陣列 + for i := 0; i < len(nums); i++ { + // 找到目標元素,返回其索引 + if nums[i] == target { + return i + } + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 線性查詢(鏈結串列) */ +func linearSearchLinkedList(node *ListNode, target int) *ListNode { + // 走訪鏈結串列 + for node != nil { + // 找到目標節點,返回之 + if node.Val == target { + return node + } + node = node.Next + } + // 未找到目標元素,返回 nil + return nil +} diff --git a/zh-hant/codes/go/chapter_searching/linear_search_test.go b/zh-hant/codes/go/chapter_searching/linear_search_test.go new file mode 100644 index 0000000000..263e2e8a61 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/linear_search_test.go @@ -0,0 +1,26 @@ +// File: linear_search_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinearSearch(t *testing.T) { + target := 3 + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + + // 在陣列中執行線性查詢 + index := linearSearchArray(nums, target) + fmt.Println("目標元素 3 的索引 =", index) + + // 在鏈結串列中執行線性查詢 + head := ArrayToLinkedList(nums) + node := linearSearchLinkedList(head, target) + fmt.Println("目標節點值 3 的對應節點物件為", node) +} diff --git a/zh-hant/codes/go/chapter_searching/two_sum.go b/zh-hant/codes/go/chapter_searching/two_sum.go new file mode 100644 index 0000000000..314ea6b9dd --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/two_sum.go @@ -0,0 +1,33 @@ +// File: two_sum.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +/* 方法一:暴力列舉 */ +func twoSumBruteForce(nums []int, target int) []int { + size := len(nums) + // 兩層迴圈,時間複雜度為 O(n^2) + for i := 0; i < size-1; i++ { + for j := i + 1; j < size; j++ { + if nums[i]+nums[j] == target { + return []int{i, j} + } + } + } + return nil +} + +/* 方法二:輔助雜湊表 */ +func twoSumHashTable(nums []int, target int) []int { + // 輔助雜湊表,空間複雜度為 O(n) + hashTable := map[int]int{} + // 單層迴圈,時間複雜度為 O(n) + for idx, val := range nums { + if preIdx, ok := hashTable[target-val]; ok { + return []int{preIdx, idx} + } + hashTable[val] = idx + } + return nil +} diff --git a/zh-hant/codes/go/chapter_searching/two_sum_test.go b/zh-hant/codes/go/chapter_searching/two_sum_test.go new file mode 100644 index 0000000000..b577d3c092 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/two_sum_test.go @@ -0,0 +1,24 @@ +// File: two_sum_test.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestTwoSum(t *testing.T) { + // ======= Test Case ======= + nums := []int{2, 7, 11, 15} + target := 13 + + // ====== Driver Code ====== + // 方法一:暴力解法 + res := twoSumBruteForce(nums, target) + fmt.Println("方法一 res =", res) + // 方法二:雜湊表 + res = twoSumHashTable(nums, target) + fmt.Println("方法二 res =", res) +} diff --git a/zh-hant/codes/go/chapter_sorting/bubble_sort.go b/zh-hant/codes/go/chapter_sorting/bubble_sort.go new file mode 100644 index 0000000000..7c5b652855 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bubble_sort.go @@ -0,0 +1,38 @@ +// File: bubble_sort.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +/* 泡沫排序 */ +func bubbleSort(nums []int) { + // 外迴圈:未排序區間為 [0, i] + for i := len(nums) - 1; i > 0; i-- { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j+1] = nums[j+1], nums[j] + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +func bubbleSortWithFlag(nums []int) { + // 外迴圈:未排序區間為 [0, i] + for i := len(nums) - 1; i > 0; i-- { + flag := false // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j+1] = nums[j+1], nums[j] + flag = true // 記錄交換元素 + } + } + if flag == false { // 此輪“冒泡”未交換任何元素,直接跳出 + break + } + } +} diff --git a/zh-hant/codes/go/chapter_sorting/bubble_sort_test.go b/zh-hant/codes/go/chapter_sorting/bubble_sort_test.go new file mode 100644 index 0000000000..283e7c9b58 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bubble_sort_test.go @@ -0,0 +1,20 @@ +// File: bubble_sort_test.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBubbleSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + bubbleSort(nums) + fmt.Println("泡沫排序完成後 nums = ", nums) + + nums1 := []int{4, 1, 3, 1, 5, 2} + bubbleSortWithFlag(nums1) + fmt.Println("泡沫排序完成後 nums1 = ", nums1) +} diff --git a/zh-hant/codes/go/chapter_sorting/bucket_sort.go b/zh-hant/codes/go/chapter_sorting/bucket_sort.go new file mode 100644 index 0000000000..e06363d03e --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bucket_sort.go @@ -0,0 +1,37 @@ +// File: bucket_sort.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "sort" + +/* 桶排序 */ +func bucketSort(nums []float64) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + k := len(nums) / 2 + buckets := make([][]float64, k) + for i := 0; i < k; i++ { + buckets[i] = make([]float64, 0) + } + // 1. 將陣列元素分配到各個桶中 + for _, num := range nums { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + i := int(num * float64(k)) + // 將 num 新增進桶 i + buckets[i] = append(buckets[i], num) + } + // 2. 對各個桶執行排序 + for i := 0; i < k; i++ { + // 使用內建切片排序函式,也可以替換成其他排序演算法 + sort.Float64s(buckets[i]) + } + // 3. 走訪桶合併結果 + i := 0 + for _, bucket := range buckets { + for _, num := range bucket { + nums[i] = num + i++ + } + } +} diff --git a/zh-hant/codes/go/chapter_sorting/bucket_sort_test.go b/zh-hant/codes/go/chapter_sorting/bucket_sort_test.go new file mode 100644 index 0000000000..c6690b6001 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bucket_sort_test.go @@ -0,0 +1,17 @@ +// File: bucket_sort_test.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBucketSort(t *testing.T) { + // 設輸入資料為浮點數,範圍為 [0, 1) + nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} + bucketSort(nums) + fmt.Println("桶排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/counting_sort.go b/zh-hant/codes/go/chapter_sorting/counting_sort.go new file mode 100644 index 0000000000..6f6dd8d427 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/counting_sort.go @@ -0,0 +1,68 @@ +// File: counting_sort.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +type CountingSort struct{} + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +func countingSortNaive(nums []int) { + // 1. 統計陣列最大元素 m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + for i, num := 0, 0; num < m+1; num++ { + for j := 0; j < counter[num]; j++ { + nums[i] = num + i++ + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +func countingSort(nums []int) { + // 1. 統計陣列最大元素 m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for i := 0; i < m; i++ { + counter[i+1] += counter[i] + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + n := len(nums) + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + num := nums[i] + // 將 num 放置到對應索引處 + res[counter[num]-1] = num + // 令前綴和自減 1 ,得到下次放置 num 的索引 + counter[num]-- + } + // 使用結果陣列 res 覆蓋原陣列 nums + copy(nums, res) +} diff --git a/zh-hant/codes/go/chapter_sorting/counting_sort_test.go b/zh-hant/codes/go/chapter_sorting/counting_sort_test.go new file mode 100644 index 0000000000..d04920eb63 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/counting_sort_test.go @@ -0,0 +1,20 @@ +// File: counting_sort_test.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestCountingSort(t *testing.T) { + nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSortNaive(nums) + fmt.Println("計數排序(無法排序物件)完成後 nums = ", nums) + + nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSort(nums1) + fmt.Println("計數排序完成後 nums1 = ", nums1) +} diff --git a/zh-hant/codes/go/chapter_sorting/heap_sort.go b/zh-hant/codes/go/chapter_sorting/heap_sort.go new file mode 100644 index 0000000000..9502a70fd8 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/heap_sort.go @@ -0,0 +1,44 @@ +// File: heap_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +func siftDown(nums *[]int, n, i int) { + for true { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + l := 2*i + 1 + r := 2*i + 2 + 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 + } + // 交換兩節點 + (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] + // 迴圈向下堆積化 + i = ma + } +} + +/* 堆積排序 */ +func heapSort(nums *[]int) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for i := len(*nums)/2 - 1; i >= 0; i-- { + siftDown(nums, len(*nums), i) + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for i := len(*nums) - 1; i > 0; i-- { + // 交換根節點與最右葉節點(交換首元素與尾元素) + (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0) + } +} diff --git a/zh-hant/codes/go/chapter_sorting/heap_sort_test.go b/zh-hant/codes/go/chapter_sorting/heap_sort_test.go new file mode 100644 index 0000000000..de4aa8b25d --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/heap_sort_test.go @@ -0,0 +1,16 @@ +// File: heap_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestHeapSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + heapSort(&nums) + fmt.Println("堆積排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/insertion_sort.go b/zh-hant/codes/go/chapter_sorting/insertion_sort.go new file mode 100644 index 0000000000..16ba2cdc18 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/insertion_sort.go @@ -0,0 +1,20 @@ +// File: insertion_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* 插入排序 */ +func insertionSort(nums []int) { + // 外迴圈:已排序區間為 [0, i-1] + for i := 1; i < len(nums); i++ { + base := nums[i] + j := i - 1 + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + for j >= 0 && nums[j] > base { + nums[j+1] = nums[j] // 將 nums[j] 向右移動一位 + j-- + } + nums[j+1] = base // 將 base 賦值到正確位置 + } +} diff --git a/zh-hant/codes/go/chapter_sorting/insertion_sort_test.go b/zh-hant/codes/go/chapter_sorting/insertion_sort_test.go new file mode 100644 index 0000000000..d983379708 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/insertion_sort_test.go @@ -0,0 +1,16 @@ +// File: insertion_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestInsertionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + insertionSort(nums) + fmt.Println("插入排序完成後 nums =", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/merge_sort.go b/zh-hant/codes/go/chapter_sorting/merge_sort.go new file mode 100644 index 0000000000..49e880ef3d --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/merge_sort.go @@ -0,0 +1,54 @@ +// File: merge_sort.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* 合併左子陣列和右子陣列 */ +func merge(nums []int, left, mid, right int) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + tmp := make([]int, right-left+1) + // 初始化左子陣列和右子陣列的起始索引 + i, j, k := left, mid+1, 0 + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + for i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i++ + } else { + tmp[k] = nums[j] + j++ + } + k++ + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + for i <= mid { + tmp[k] = nums[i] + i++ + k++ + } + for j <= right { + tmp[k] = nums[j] + j++ + k++ + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for k := 0; k < len(tmp); k++ { + nums[left+k] = tmp[k] + } +} + +/* 合併排序 */ +func mergeSort(nums []int, left, right int) { + // 終止條件 + if left >= right { + return + } + // 劃分階段 + mid := left + (right - left) / 2 + mergeSort(nums, left, mid) + mergeSort(nums, mid+1, right) + // 合併階段 + merge(nums, left, mid, right) +} diff --git a/zh-hant/codes/go/chapter_sorting/merge_sort_test.go b/zh-hant/codes/go/chapter_sorting/merge_sort_test.go new file mode 100644 index 0000000000..c0ff396838 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/merge_sort_test.go @@ -0,0 +1,16 @@ +// File: merge_sort_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestMergeSort(t *testing.T) { + nums := []int{7, 3, 2, 6, 0, 1, 5, 4} + mergeSort(nums, 0, len(nums)-1) + fmt.Println("合併排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/quick_sort.go b/zh-hant/codes/go/chapter_sorting/quick_sort.go new file mode 100644 index 0000000000..e8b94da2f4 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/quick_sort.go @@ -0,0 +1,130 @@ +// File: quick_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +// 快速排序 +type quickSort struct{} + +// 快速排序(中位基準數最佳化) +type quickSortMedian struct{} + +// 快速排序(尾遞迴最佳化) +type quickSortTailCall struct{} + +/* 哨兵劃分 */ +func (q *quickSort) partition(nums []int, left, right int) int { + // 以 nums[left] 為基準數 + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // 從右向左找首個小於基準數的元素 + } + for i < j && nums[i] <= nums[left] { + i++ // 從左向右找首個大於基準數的元素 + } + // 元素交換 + nums[i], nums[j] = nums[j], nums[i] + } + // 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i // 返回基準數的索引 +} + +/* 快速排序 */ +func (q *quickSort) quickSort(nums []int, left, right int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + pivot := q.partition(nums, left, right) + // 遞迴左子陣列、右子陣列 + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* 選取三個候選元素的中位數 */ +func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { + l, m, r := nums[left], nums[mid], 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 +} + +/* 哨兵劃分(三數取中值)*/ +func (q *quickSortMedian) partition(nums []int, left, right int) int { + // 以 nums[left] 為基準數 + med := q.medianThree(nums, left, (left+right)/2, right) + // 將中位數交換至陣列最左端 + nums[left], nums[med] = nums[med], nums[left] + // 以 nums[left] 為基準數 + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- //從右向左找首個小於基準數的元素 + } + for i < j && nums[i] <= nums[left] { + i++ //從左向右找首個大於基準數的元素 + } + //元素交換 + nums[i], nums[j] = nums[j], nums[i] + } + //將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i //返回基準數的索引 +} + +/* 快速排序 */ +func (q *quickSortMedian) quickSort(nums []int, left, right int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + pivot := q.partition(nums, left, right) + // 遞迴左子陣列、右子陣列 + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* 哨兵劃分 */ +func (q *quickSortTailCall) partition(nums []int, left, right int) int { + // 以 nums[left] 為基準數 + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // 從右向左找首個小於基準數的元素 + } + for i < j && nums[i] <= nums[left] { + i++ // 從左向右找首個大於基準數的元素 + } + // 元素交換 + nums[i], nums[j] = nums[j], nums[i] + } + // 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i // 返回基準數的索引 +} + +/* 快速排序(尾遞迴最佳化)*/ +func (q *quickSortTailCall) quickSort(nums []int, left, right int) { + // 子陣列長度為 1 時終止 + for left < right { + // 哨兵劃分操作 + pivot := q.partition(nums, left, right) + // 對兩個子陣列中較短的那個執行快速排序 + if pivot-left < right-pivot { + q.quickSort(nums, left, pivot-1) // 遞迴排序左子陣列 + left = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] + } else { + q.quickSort(nums, pivot+1, right) // 遞迴排序右子陣列 + right = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] + } + } +} diff --git a/zh-hant/codes/go/chapter_sorting/quick_sort_test.go b/zh-hant/codes/go/chapter_sorting/quick_sort_test.go new file mode 100644 index 0000000000..503cb8b9f5 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/quick_sort_test.go @@ -0,0 +1,34 @@ +// File: quick_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +// 快速排序 +func TestQuickSort(t *testing.T) { + q := quickSort{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("快速排序完成後 nums = ", nums) +} + +// 快速排序(中位基準數最佳化) +func TestQuickSortMedian(t *testing.T) { + q := quickSortMedian{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("快速排序(中位基準數最佳化)完成後 nums = ", nums) +} + +// 快速排序(尾遞迴最佳化) +func TestQuickSortTailCall(t *testing.T) { + q := quickSortTailCall{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("快速排序(尾遞迴最佳化)完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/radix_sort.go b/zh-hant/codes/go/chapter_sorting/radix_sort.go new file mode 100644 index 0000000000..5edd9596b4 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/radix_sort.go @@ -0,0 +1,60 @@ +// File: radix_sort.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "math" + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +func digit(num, exp int) int { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10 +} + +/* 計數排序(根據 nums 第 k 位排序) */ +func countingSortDigit(nums []int, exp int) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + counter := make([]int, 10) + n := len(nums) + // 統計 0~9 各數字的出現次數 + for i := 0; i < n; i++ { + d := digit(nums[i], exp) // 獲取 nums[i] 第 k 位,記為 d + counter[d]++ // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for i := 1; i < 10; i++ { + counter[i] += counter[i-1] + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + d := digit(nums[i], exp) + j := counter[d] - 1 // 獲取 d 在陣列中的索引 j + res[j] = nums[i] // 將當前元素填入索引 j + counter[d]-- // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for i := 0; i < n; i++ { + nums[i] = res[i] + } +} + +/* 基數排序 */ +func radixSort(nums []int) { + // 獲取陣列的最大元素,用於判斷最大位數 + max := math.MinInt + for _, num := range nums { + if num > max { + max = num + } + } + // 按照從低位到高位的順序走訪 + for exp := 1; max >= exp; exp *= 10 { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp) + } +} diff --git a/zh-hant/codes/go/chapter_sorting/radix_sort_test.go b/zh-hant/codes/go/chapter_sorting/radix_sort_test.go new file mode 100644 index 0000000000..c20bb94eb7 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/radix_sort_test.go @@ -0,0 +1,18 @@ +// File: radix_sort_test.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestRadixSort(t *testing.T) { + /* 基數排序 */ + nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996} + radixSort(nums) + fmt.Println("基數排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/selection_sort.go b/zh-hant/codes/go/chapter_sorting/selection_sort.go new file mode 100644 index 0000000000..7fcb57d707 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/selection_sort.go @@ -0,0 +1,24 @@ +// File: selection_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* 選擇排序 */ +func selectionSort(nums []int) { + n := len(nums) + // 外迴圈:未排序區間為 [i, n-1] + for i := 0; i < n-1; i++ { + // 內迴圈:找到未排序區間內的最小元素 + k := i + for j := i + 1; j < n; j++ { + if nums[j] < nums[k] { + // 記錄最小元素的索引 + k = j + } + } + // 將該最小元素與未排序區間的首個元素交換 + nums[i], nums[k] = nums[k], nums[i] + + } +} diff --git a/zh-hant/codes/go/chapter_sorting/selection_sort_test.go b/zh-hant/codes/go/chapter_sorting/selection_sort_test.go new file mode 100644 index 0000000000..509ec3672a --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/selection_sort_test.go @@ -0,0 +1,16 @@ +// File: selection_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestSelectionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + selectionSort(nums) + fmt.Println("選擇排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/array_deque.go b/zh-hant/codes/go/chapter_stack_and_queue/array_deque.go new file mode 100644 index 0000000000..4cf6632bd7 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/array_deque.go @@ -0,0 +1,121 @@ +// File: array_deque.go +// Created Time: 2023-03-13 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import "fmt" + +/* 基於環形陣列實現的雙向佇列 */ +type arrayDeque struct { + nums []int // 用於儲存雙向佇列元素的陣列 + front int // 佇列首指標,指向佇列首元素 + queSize int // 雙向佇列長度 + queCapacity int // 佇列容量(即最大容納元素數量) +} + +/* 初始化佇列 */ +func newArrayDeque(queCapacity int) *arrayDeque { + return &arrayDeque{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* 獲取雙向佇列的長度 */ +func (q *arrayDeque) size() int { + return q.queSize +} + +/* 判斷雙向佇列是否為空 */ +func (q *arrayDeque) isEmpty() bool { + return q.queSize == 0 +} + +/* 計算環形陣列索引 */ +func (q *arrayDeque) index(i int) int { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + q.queCapacity) % q.queCapacity +} + +/* 佇列首入列 */ +func (q *arrayDeque) pushFirst(num int) { + if q.queSize == q.queCapacity { + fmt.Println("雙向佇列已滿") + return + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + q.front = q.index(q.front - 1) + // 將 num 新增至佇列首 + q.nums[q.front] = num + q.queSize++ +} + +/* 佇列尾入列 */ +func (q *arrayDeque) pushLast(num int) { + if q.queSize == q.queCapacity { + fmt.Println("雙向佇列已滿") + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + rear := q.index(q.front + q.queSize) + // 將 num 新增至佇列尾 + q.nums[rear] = num + q.queSize++ +} + +/* 佇列首出列 */ +func (q *arrayDeque) popFirst() any { + num := q.peekFirst() + if num == nil { + return nil + } + // 佇列首指標向後移動一位 + q.front = q.index(q.front + 1) + q.queSize-- + return num +} + +/* 佇列尾出列 */ +func (q *arrayDeque) popLast() any { + num := q.peekLast() + if num == nil { + return nil + } + q.queSize-- + return num +} + +/* 訪問佇列首元素 */ +func (q *arrayDeque) peekFirst() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* 訪問佇列尾元素 */ +func (q *arrayDeque) peekLast() any { + if q.isEmpty() { + return nil + } + // 計算尾元素索引 + last := q.index(q.front + q.queSize - 1) + return q.nums[last] +} + +/* 獲取 Slice 用於列印 */ +func (q *arrayDeque) toSlice() []int { + // 僅轉換有效長度範圍內的串列元素 + res := make([]int, q.queSize) + for i, j := 0, q.front; i < q.queSize; i++ { + res[i] = q.nums[q.index(j)] + j++ + } + return res +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/array_queue.go b/zh-hant/codes/go/chapter_stack_and_queue/array_queue.go new file mode 100644 index 0000000000..699fe4d795 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/array_queue.go @@ -0,0 +1,78 @@ +// File: array_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* 基於環形陣列實現的佇列 */ +type arrayQueue struct { + nums []int // 用於儲存佇列元素的陣列 + front int // 佇列首指標,指向佇列首元素 + queSize int // 佇列長度 + queCapacity int // 佇列容量(即最大容納元素數量) +} + +/* 初始化佇列 */ +func newArrayQueue(queCapacity int) *arrayQueue { + return &arrayQueue{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* 獲取佇列的長度 */ +func (q *arrayQueue) size() int { + return q.queSize +} + +/* 判斷佇列是否為空 */ +func (q *arrayQueue) isEmpty() bool { + return q.queSize == 0 +} + +/* 入列 */ +func (q *arrayQueue) push(num int) { + // 當 rear == queCapacity 表示佇列已滿 + if q.queSize == q.queCapacity { + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + rear := (q.front + q.queSize) % q.queCapacity + // 將 num 新增至佇列尾 + q.nums[rear] = num + q.queSize++ +} + +/* 出列 */ +func (q *arrayQueue) pop() any { + num := q.peek() + if num == nil { + return nil + } + + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + q.front = (q.front + 1) % q.queCapacity + q.queSize-- + return num +} + +/* 訪問佇列首元素 */ +func (q *arrayQueue) peek() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* 獲取 Slice 用於列印 */ +func (q *arrayQueue) toSlice() []int { + rear := (q.front + q.queSize) + if rear >= q.queCapacity { + rear %= q.queCapacity + return append(q.nums[q.front:], q.nums[:rear]...) + } + return q.nums[q.front:rear] +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/array_stack.go b/zh-hant/codes/go/chapter_stack_and_queue/array_stack.go new file mode 100644 index 0000000000..cf882bf04c --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/array_stack.go @@ -0,0 +1,55 @@ +// File: array_stack.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* 基於陣列實現的堆疊 */ +type arrayStack struct { + data []int // 資料 +} + +/* 初始化堆疊 */ +func newArrayStack() *arrayStack { + return &arrayStack{ + // 設定堆疊的長度為 0,容量為 16 + data: make([]int, 0, 16), + } +} + +/* 堆疊的長度 */ +func (s *arrayStack) size() int { + return len(s.data) +} + +/* 堆疊是否為空 */ +func (s *arrayStack) isEmpty() bool { + return s.size() == 0 +} + +/* 入堆疊 */ +func (s *arrayStack) push(v int) { + // 切片會自動擴容 + s.data = append(s.data, v) +} + +/* 出堆疊 */ +func (s *arrayStack) pop() any { + val := s.peek() + s.data = s.data[:len(s.data)-1] + return val +} + +/* 獲取堆疊頂元素 */ +func (s *arrayStack) peek() any { + if s.isEmpty() { + return nil + } + val := s.data[len(s.data)-1] + return val +} + +/* 獲取 Slice 用於列印 */ +func (s *arrayStack) toSlice() []int { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/deque_test.go b/zh-hant/codes/go/chapter_stack_and_queue/deque_test.go new file mode 100644 index 0000000000..37e60e80e3 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/deque_test.go @@ -0,0 +1,141 @@ +// File: deque_test.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestDeque(t *testing.T) { + /* 初始化雙向佇列 */ + // 在 Go 中,將 list 作為雙向佇列使用 + deque := list.New() + + /* 元素入列 */ + deque.PushBack(2) + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) + deque.PushFront(1) + fmt.Print("雙向佇列 deque = ") + PrintList(deque) + + /* 訪問元素 */ + front := deque.Front() + fmt.Println("佇列首元素 front =", front.Value) + rear := deque.Back() + fmt.Println("佇列尾元素 rear =", rear.Value) + + /* 元素出列 */ + deque.Remove(front) + fmt.Print("佇列首出列元素 front = ", front.Value, ",佇列首出列後 deque = ") + PrintList(deque) + deque.Remove(rear) + fmt.Print("佇列尾出列元素 rear = ", rear.Value, ",佇列尾出列後 deque = ") + PrintList(deque) + + /* 獲取雙向佇列的長度 */ + size := deque.Len() + fmt.Println("雙向佇列長度 size =", size) + + /* 判斷雙向佇列是否為空 */ + isEmpty := deque.Len() == 0 + fmt.Println("雙向佇列是否為空 =", isEmpty) +} + +func TestArrayDeque(t *testing.T) { + /* 初始化雙向佇列 */ + // 在 Go 中,將 list 作為雙向佇列使用 + deque := newArrayDeque(16) + + /* 元素入列 */ + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + fmt.Print("雙向佇列 deque = ") + PrintSlice(deque.toSlice()) + + /* 訪問元素 */ + peekFirst := deque.peekFirst() + fmt.Println("佇列首元素 peekFirst =", peekFirst) + peekLast := deque.peekLast() + fmt.Println("佇列尾元素 peekLast =", peekLast) + + /* 元素入列 */ + deque.pushLast(4) + fmt.Print("元素 4 佇列尾入列後 deque = ") + PrintSlice(deque.toSlice()) + deque.pushFirst(1) + fmt.Print("元素 1 佇列首入列後 deque = ") + PrintSlice(deque.toSlice()) + + /* 元素出列 */ + popFirst := deque.popFirst() + fmt.Print("佇列首出列元素 popFirst = ", popFirst, ",佇列首出列後 deque = ") + PrintSlice(deque.toSlice()) + popLast := deque.popLast() + fmt.Print("佇列尾出列元素 popLast = ", popLast, ",佇列尾出列後 deque = ") + PrintSlice(deque.toSlice()) + + /* 獲取雙向佇列的長度 */ + size := deque.size() + fmt.Println("雙向佇列長度 size =", size) + + /* 判斷雙向佇列是否為空 */ + isEmpty := deque.isEmpty() + fmt.Println("雙向佇列是否為空 =", isEmpty) +} + +func TestLinkedListDeque(t *testing.T) { + // 初始化佇列 + deque := newLinkedListDeque() + + // 元素入列 + deque.pushLast(2) + deque.pushLast(5) + deque.pushLast(4) + deque.pushFirst(3) + deque.pushFirst(1) + fmt.Print("佇列 deque = ") + PrintList(deque.toList()) + + // 訪問佇列首元素 + front := deque.peekFirst() + fmt.Println("佇列首元素 front =", front) + rear := deque.peekLast() + fmt.Println("佇列尾元素 rear =", rear) + + // 元素出列 + popFirst := deque.popFirst() + fmt.Print("佇列首出列元素 popFirst = ", popFirst, ",佇列首出列後 deque = ") + PrintList(deque.toList()) + popLast := deque.popLast() + fmt.Print("佇列尾出列元素 popLast = ", popLast, ",佇列尾出列後 deque = ") + PrintList(deque.toList()) + + // 獲取隊的長度 + size := deque.size() + fmt.Println("隊的長度 size =", size) + + // 判斷是否為空 + isEmpty := deque.isEmpty() + fmt.Println("隊是否為空 =", isEmpty) +} + +// BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro +func BenchmarkLinkedListDeque(b *testing.B) { + deque := newLinkedListDeque() + // use b.N for looping + for i := 0; i < b.N; i++ { + deque.pushLast(777) + } + for i := 0; i < b.N; i++ { + deque.popFirst() + } +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_deque.go b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_deque.go new file mode 100644 index 0000000000..e47dc54594 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_deque.go @@ -0,0 +1,85 @@ +// File: linkedlist_deque.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 基於雙向鏈結串列實現的雙向佇列 */ +type linkedListDeque struct { + // 使用內建包 list + data *list.List +} + +/* 初始化雙端佇列 */ +func newLinkedListDeque() *linkedListDeque { + return &linkedListDeque{ + data: list.New(), + } +} + +/* 佇列首元素入列 */ +func (s *linkedListDeque) pushFirst(value any) { + s.data.PushFront(value) +} + +/* 佇列尾元素入列 */ +func (s *linkedListDeque) pushLast(value any) { + s.data.PushBack(value) +} + +/* 佇列首元素出列 */ +func (s *linkedListDeque) popFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* 佇列尾元素出列 */ +func (s *linkedListDeque) popLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* 訪問佇列首元素 */ +func (s *linkedListDeque) peekFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* 訪問佇列尾元素 */ +func (s *linkedListDeque) peekLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* 獲取佇列的長度 */ +func (s *linkedListDeque) size() int { + return s.data.Len() +} + +/* 判斷佇列是否為空 */ +func (s *linkedListDeque) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 獲取 List 用於列印 */ +func (s *linkedListDeque) toList() *list.List { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_queue.go b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_queue.go new file mode 100644 index 0000000000..fca7332f61 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_queue.go @@ -0,0 +1,61 @@ +// File: linkedlist_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 基於鏈結串列實現的佇列 */ +type linkedListQueue struct { + // 使用內建包 list 來實現佇列 + data *list.List +} + +/* 初始化佇列 */ +func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ + data: list.New(), + } +} + +/* 入列 */ +func (s *linkedListQueue) push(value any) { + s.data.PushBack(value) +} + +/* 出列 */ +func (s *linkedListQueue) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* 訪問佇列首元素 */ +func (s *linkedListQueue) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* 獲取佇列的長度 */ +func (s *linkedListQueue) size() int { + return s.data.Len() +} + +/* 判斷佇列是否為空 */ +func (s *linkedListQueue) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 獲取 List 用於列印 */ +func (s *linkedListQueue) toList() *list.List { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_stack.go b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_stack.go new file mode 100644 index 0000000000..4bcac1cfa3 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_stack.go @@ -0,0 +1,61 @@ +// File: linkedlist_stack.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 基於鏈結串列實現的堆疊 */ +type linkedListStack struct { + // 使用內建包 list 來實現堆疊 + data *list.List +} + +/* 初始化堆疊 */ +func newLinkedListStack() *linkedListStack { + return &linkedListStack{ + data: list.New(), + } +} + +/* 入堆疊 */ +func (s *linkedListStack) push(value int) { + s.data.PushBack(value) +} + +/* 出堆疊 */ +func (s *linkedListStack) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* 訪問堆疊頂元素 */ +func (s *linkedListStack) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* 獲取堆疊的長度 */ +func (s *linkedListStack) size() int { + return s.data.Len() +} + +/* 判斷堆疊是否為空 */ +func (s *linkedListStack) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 獲取 List 用於列印 */ +func (s *linkedListStack) toList() *list.List { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/queue_test.go b/zh-hant/codes/go/chapter_stack_and_queue/queue_test.go new file mode 100644 index 0000000000..8eca50e4b3 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/queue_test.go @@ -0,0 +1,146 @@ +// File: queue_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestQueue(t *testing.T) { + /* 初始化佇列 */ + // 在 Go 中,將 list 作為佇列來使用 + queue := list.New() + + /* 元素入列 */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + fmt.Print("佇列 queue = ") + PrintList(queue) + + /* 訪問佇列首元素 */ + peek := queue.Front() + fmt.Println("佇列首元素 peek =", peek.Value) + + /* 元素出列 */ + pop := queue.Front() + queue.Remove(pop) + fmt.Print("出列元素 pop = ", pop.Value, ",出列後 queue = ") + PrintList(queue) + + /* 獲取佇列的長度 */ + size := queue.Len() + fmt.Println("佇列長度 size =", size) + + /* 判斷佇列是否為空 */ + isEmpty := queue.Len() == 0 + fmt.Println("佇列是否為空 =", isEmpty) +} + +func TestArrayQueue(t *testing.T) { + + // 初始化佇列,使用佇列的通用介面 + capacity := 10 + queue := newArrayQueue(capacity) + if queue.pop() != nil { + t.Errorf("want:%v,got:%v", nil, queue.pop()) + } + + // 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("佇列 queue = ") + PrintSlice(queue.toSlice()) + + // 訪問佇列首元素 + peek := queue.peek() + fmt.Println("佇列首元素 peek =", peek) + + // 元素出列 + pop := queue.pop() + fmt.Print("出列元素 pop = ", pop, ", 出列後 queue = ") + PrintSlice(queue.toSlice()) + + // 獲取隊的長度 + size := queue.size() + fmt.Println("隊的長度 size =", size) + + // 判斷是否為空 + isEmpty := queue.isEmpty() + fmt.Println("隊是否為空 =", isEmpty) + + /* 測試環形陣列 */ + for i := 0; i < 10; i++ { + queue.push(i) + queue.pop() + fmt.Print("第", i, "輪入列 + 出列後 queue =") + PrintSlice(queue.toSlice()) + } +} + +func TestLinkedListQueue(t *testing.T) { + // 初始化隊 + queue := newLinkedListQueue() + + // 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("佇列 queue = ") + PrintList(queue.toList()) + + // 訪問佇列首元素 + peek := queue.peek() + fmt.Println("佇列首元素 peek =", peek) + + // 元素出列 + pop := queue.pop() + fmt.Print("出列元素 pop = ", pop, ", 出列後 queue = ") + PrintList(queue.toList()) + + // 獲取隊的長度 + size := queue.size() + fmt.Println("隊的長度 size =", size) + + // 判斷是否為空 + isEmpty := queue.isEmpty() + fmt.Println("隊是否為空 =", isEmpty) +} + +// BenchmarkArrayQueue 8 ns/op in Mac M1 Pro +func BenchmarkArrayQueue(b *testing.B) { + capacity := 1000 + queue := newArrayQueue(capacity) + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} + +// BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro +func BenchmarkLinkedQueue(b *testing.B) { + queue := newLinkedListQueue() + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/stack_test.go b/zh-hant/codes/go/chapter_stack_and_queue/stack_test.go new file mode 100644 index 0000000000..3a5a9e0b0b --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/stack_test.go @@ -0,0 +1,130 @@ +// File: stack_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestStack(t *testing.T) { + /* 初始化堆疊 */ + // 在 Go 中,推薦將 Slice 當作堆疊來使用 + var stack []int + + /* 元素入堆疊 */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + fmt.Print("堆疊 stack = ") + PrintSlice(stack) + + /* 訪問堆疊頂元素 */ + peek := stack[len(stack)-1] + fmt.Println("堆疊頂元素 peek =", peek) + + /* 元素出堆疊 */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + fmt.Print("出堆疊元素 pop = ", pop, ",出堆疊後 stack = ") + PrintSlice(stack) + + /* 獲取堆疊的長度 */ + size := len(stack) + fmt.Println("堆疊的長度 size =", size) + + /* 判斷是否為空 */ + isEmpty := len(stack) == 0 + fmt.Println("堆疊是否為空 =", isEmpty) +} + +func TestArrayStack(t *testing.T) { + // 初始化堆疊, 使用介面承接 + stack := newArrayStack() + + // 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("堆疊 stack = ") + PrintSlice(stack.toSlice()) + + // 訪問堆疊頂元素 + peek := stack.peek() + fmt.Println("堆疊頂元素 peek =", peek) + + // 元素出堆疊 + pop := stack.pop() + fmt.Print("出堆疊元素 pop = ", pop, ", 出堆疊後 stack = ") + PrintSlice(stack.toSlice()) + + // 獲取堆疊的長度 + size := stack.size() + fmt.Println("堆疊的長度 size =", size) + + // 判斷是否為空 + isEmpty := stack.isEmpty() + fmt.Println("堆疊是否為空 =", isEmpty) +} + +func TestLinkedListStack(t *testing.T) { + // 初始化堆疊 + stack := newLinkedListStack() + // 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("堆疊 stack = ") + PrintList(stack.toList()) + + // 訪問堆疊頂元素 + peek := stack.peek() + fmt.Println("堆疊頂元素 peek =", peek) + + // 元素出堆疊 + pop := stack.pop() + fmt.Print("出堆疊元素 pop = ", pop, ", 出堆疊後 stack = ") + PrintList(stack.toList()) + + // 獲取堆疊的長度 + size := stack.size() + fmt.Println("堆疊的長度 size =", size) + + // 判斷是否為空 + isEmpty := stack.isEmpty() + fmt.Println("堆疊是否為空 =", isEmpty) +} + +// BenchmarkArrayStack 8 ns/op in Mac M1 Pro +func BenchmarkArrayStack(b *testing.B) { + stack := newArrayStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} + +// BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro +func BenchmarkLinkedListStack(b *testing.B) { + stack := newLinkedListStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} diff --git a/zh-hant/codes/go/chapter_tree/array_binary_tree.go b/zh-hant/codes/go/chapter_tree/array_binary_tree.go new file mode 100644 index 0000000000..6f5802c872 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/array_binary_tree.go @@ -0,0 +1,101 @@ +// File: array_binary_tree.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +/* 陣列表示下的二元樹類別 */ +type arrayBinaryTree struct { + tree []any +} + +/* 建構子 */ +func newArrayBinaryTree(arr []any) *arrayBinaryTree { + return &arrayBinaryTree{ + tree: arr, + } +} + +/* 串列容量 */ +func (abt *arrayBinaryTree) size() int { + return len(abt.tree) +} + +/* 獲取索引為 i 節點的值 */ +func (abt *arrayBinaryTree) val(i int) any { + // 若索引越界,則返回 null ,代表空位 + if i < 0 || i >= abt.size() { + return nil + } + return abt.tree[i] +} + +/* 獲取索引為 i 節點的左子節點的索引 */ +func (abt *arrayBinaryTree) left(i int) int { + return 2*i + 1 +} + +/* 獲取索引為 i 節點的右子節點的索引 */ +func (abt *arrayBinaryTree) right(i int) int { + return 2*i + 2 +} + +/* 獲取索引為 i 節點的父節點的索引 */ +func (abt *arrayBinaryTree) parent(i int) int { + return (i - 1) / 2 +} + +/* 層序走訪 */ +func (abt *arrayBinaryTree) levelOrder() []any { + var res []any + // 直接走訪陣列 + for i := 0; i < abt.size(); i++ { + if abt.val(i) != nil { + res = append(res, abt.val(i)) + } + } + return res +} + +/* 深度優先走訪 */ +func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { + // 若為空位,則返回 + if abt.val(i) == nil { + return + } + // 前序走訪 + if order == "pre" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.left(i), order, res) + // 中序走訪 + if order == "in" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.right(i), order, res) + // 後序走訪 + if order == "post" { + *res = append(*res, abt.val(i)) + } +} + +/* 前序走訪 */ +func (abt *arrayBinaryTree) preOrder() []any { + var res []any + abt.dfs(0, "pre", &res) + return res +} + +/* 中序走訪 */ +func (abt *arrayBinaryTree) inOrder() []any { + var res []any + abt.dfs(0, "in", &res) + return res +} + +/* 後序走訪 */ +func (abt *arrayBinaryTree) postOrder() []any { + var res []any + abt.dfs(0, "post", &res) + return res +} diff --git a/zh-hant/codes/go/chapter_tree/array_binary_tree_test.go b/zh-hant/codes/go/chapter_tree/array_binary_tree_test.go new file mode 100644 index 0000000000..ea64c553b5 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/array_binary_tree_test.go @@ -0,0 +1,47 @@ +// File: array_binary_tree_test.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestArrayBinaryTree(t *testing.T) { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + root := SliceToTree(arr) + fmt.Println("\n初始化二元樹") + fmt.Println("二元樹的陣列表示:") + fmt.Println(arr) + fmt.Println("二元樹的鏈結串列表示:") + PrintTree(root) + + // 陣列表示下的二元樹類別 + abt := newArrayBinaryTree(arr) + + // 訪問節點 + i := 1 + l := abt.left(i) + r := abt.right(i) + p := abt.parent(i) + fmt.Println("\n當前節點的索引為", i, ",值為", abt.val(i)) + fmt.Println("其左子節點的索引為", l, ",值為", abt.val(l)) + fmt.Println("其右子節點的索引為", r, ",值為", abt.val(r)) + fmt.Println("其父節點的索引為", p, ",值為", abt.val(p)) + + // 走訪樹 + res := abt.levelOrder() + fmt.Println("\n層序走訪為:", res) + res = abt.preOrder() + fmt.Println("前序走訪為:", res) + res = abt.inOrder() + fmt.Println("中序走訪為:", res) + res = abt.postOrder() + fmt.Println("後序走訪為:", res) +} diff --git a/zh-hant/codes/go/chapter_tree/avl_tree.go b/zh-hant/codes/go/chapter_tree/avl_tree.go new file mode 100644 index 0000000000..0ab038ac5a --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/avl_tree.go @@ -0,0 +1,200 @@ +// File: avl_tree.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import . "github.com/krahets/hello-algo/pkg" + +/* AVL 樹 */ +type aVLTree struct { + // 根節點 + root *TreeNode +} + +func newAVLTree() *aVLTree { + return &aVLTree{root: nil} +} + +/* 獲取節點高度 */ +func (t *aVLTree) height(node *TreeNode) int { + // 空節點高度為 -1 ,葉節點高度為 0 + if node != nil { + return node.Height + } + return -1 +} + +/* 更新節點高度 */ +func (t *aVLTree) updateHeight(node *TreeNode) { + lh := t.height(node.Left) + rh := t.height(node.Right) + // 節點高度等於最高子樹高度 + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } +} + +/* 獲取平衡因子 */ +func (t *aVLTree) balanceFactor(node *TreeNode) int { + // 空節點平衡因子為 0 + if node == nil { + return 0 + } + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return t.height(node.Left) - t.height(node.Right) +} + +/* 右旋操作 */ +func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // 以 child 為原點,將 node 向右旋轉 + child.Right = node + node.Left = grandChild + // 更新節點高度 + t.updateHeight(node) + t.updateHeight(child) + // 返回旋轉後子樹的根節點 + return child +} + +/* 左旋操作 */ +func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // 以 child 為原點,將 node 向左旋轉 + child.Left = node + node.Right = grandChild + // 更新節點高度 + t.updateHeight(node) + t.updateHeight(child) + // 返回旋轉後子樹的根節點 + return child +} + +/* 執行旋轉操作,使該子樹重新恢復平衡 */ +func (t *aVLTree) rotate(node *TreeNode) *TreeNode { + // 獲取節點 node 的平衡因子 + // Go 推薦短變數,這裡 bf 指代 t.balanceFactor + bf := t.balanceFactor(node) + // 左偏樹 + if bf > 1 { + if t.balanceFactor(node.Left) >= 0 { + // 右旋 + return t.rightRotate(node) + } else { + // 先左旋後右旋 + node.Left = t.leftRotate(node.Left) + return t.rightRotate(node) + } + } + // 右偏樹 + if bf < -1 { + if t.balanceFactor(node.Right) <= 0 { + // 左旋 + return t.leftRotate(node) + } else { + // 先右旋後左旋 + node.Right = t.rightRotate(node.Right) + return t.leftRotate(node) + } + } + // 平衡樹,無須旋轉,直接返回 + return node +} + +/* 插入節點 */ +func (t *aVLTree) insert(val int) { + t.root = t.insertHelper(t.root, val) +} + +/* 遞迴插入節點(輔助函式) */ +func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return NewTreeNode(val) + } + /* 1. 查詢插入位置並插入節點 */ + if val < node.Val.(int) { + node.Left = t.insertHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.insertHelper(node.Right, val) + } else { + // 重複節點不插入,直接返回 + return node + } + // 更新節點高度 + t.updateHeight(node) + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = t.rotate(node) + // 返回子樹的根節點 + return node +} + +/* 刪除節點 */ +func (t *aVLTree) remove(val int) { + t.root = t.removeHelper(t.root, val) +} + +/* 遞迴刪除節點(輔助函式) */ +func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 1. 查詢節點並刪除 */ + if val < node.Val.(int) { + node.Left = t.removeHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.removeHelper(node.Right, val) + } else { + if node.Left == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + if child == nil { + // 子節點數量 = 0 ,直接刪除 node 並返回 + return nil + } else { + // 子節點數量 = 1 ,直接刪除 node + node = child + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + temp := node.Right + for temp.Left != nil { + temp = temp.Left + } + node.Right = t.removeHelper(node.Right, temp.Val.(int)) + node.Val = temp.Val + } + } + // 更新節點高度 + t.updateHeight(node) + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = t.rotate(node) + // 返回子樹的根節點 + return node +} + +/* 查詢節點 */ +func (t *aVLTree) search(val int) *TreeNode { + cur := t.root + // 迴圈查詢,越過葉節點後跳出 + for cur != nil { + if cur.Val.(int) < val { + // 目標節點在 cur 的右子樹中 + cur = cur.Right + } else if cur.Val.(int) > val { + // 目標節點在 cur 的左子樹中 + cur = cur.Left + } else { + // 找到目標節點,跳出迴圈 + break + } + } + // 返回目標節點 + return cur +} diff --git a/zh-hant/codes/go/chapter_tree/avl_tree_test.go b/zh-hant/codes/go/chapter_tree/avl_tree_test.go new file mode 100644 index 0000000000..184f235076 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/avl_tree_test.go @@ -0,0 +1,54 @@ +// File: avl_tree_test.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestAVLTree(t *testing.T) { + /* 初始化空 AVL 樹 */ + tree := newAVLTree() + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(tree, 1) + testInsert(tree, 2) + testInsert(tree, 3) + testInsert(tree, 4) + testInsert(tree, 5) + testInsert(tree, 8) + testInsert(tree, 7) + testInsert(tree, 9) + testInsert(tree, 10) + testInsert(tree, 6) + + /* 插入重複節點 */ + testInsert(tree, 7) + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(tree, 8) // 刪除度為 0 的節點 + testRemove(tree, 5) // 刪除度為 1 的節點 + testRemove(tree, 4) // 刪除度為 2 的節點 + + /* 查詢節點 */ + node := tree.search(7) + fmt.Printf("\n查詢到的節點物件為 %#v ,節點值 = %d \n", node, node.Val) +} + +func testInsert(tree *aVLTree, val int) { + tree.insert(val) + fmt.Printf("\n插入節點 %d 後,AVL 樹為 \n", val) + PrintTree(tree.root) +} + +func testRemove(tree *aVLTree, val int) { + tree.remove(val) + fmt.Printf("\n刪除節點 %d 後,AVL 樹為 \n", val) + PrintTree(tree.root) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_search_tree.go b/zh-hant/codes/go/chapter_tree/binary_search_tree.go new file mode 100644 index 0000000000..f6f6e899a0 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_search_tree.go @@ -0,0 +1,142 @@ +// File: binary_search_tree.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +type binarySearchTree struct { + root *TreeNode +} + +func newBinarySearchTree() *binarySearchTree { + bst := &binarySearchTree{} + // 初始化空樹 + bst.root = nil + return bst +} + +/* 獲取根節點 */ +func (bst *binarySearchTree) getRoot() *TreeNode { + return bst.root +} + +/* 查詢節點 */ +func (bst *binarySearchTree) search(num int) *TreeNode { + node := bst.root + // 迴圈查詢,越過葉節點後跳出 + for node != nil { + if node.Val.(int) < num { + // 目標節點在 cur 的右子樹中 + node = node.Right + } else if node.Val.(int) > num { + // 目標節點在 cur 的左子樹中 + node = node.Left + } else { + // 找到目標節點,跳出迴圈 + break + } + } + // 返回目標節點 + return node +} + +/* 插入節點 */ +func (bst *binarySearchTree) insert(num int) { + cur := bst.root + // 若樹為空,則初始化根節點 + if cur == nil { + bst.root = NewTreeNode(num) + return + } + // 待插入節點之前的節點位置 + var pre *TreeNode = nil + // 迴圈查詢,越過葉節點後跳出 + for cur != nil { + if cur.Val == num { + return + } + pre = cur + if cur.Val.(int) < num { + cur = cur.Right + } else { + cur = cur.Left + } + } + // 插入節點 + node := NewTreeNode(num) + if pre.Val.(int) < num { + pre.Right = node + } else { + pre.Left = node + } +} + +/* 刪除節點 */ +func (bst *binarySearchTree) remove(num int) { + cur := bst.root + // 若樹為空,直接提前返回 + if cur == nil { + return + } + // 待刪除節點之前的節點位置 + var pre *TreeNode = nil + // 迴圈查詢,越過葉節點後跳出 + for cur != nil { + if cur.Val == num { + break + } + pre = cur + if cur.Val.(int) < num { + // 待刪除節點在右子樹中 + cur = cur.Right + } else { + // 待刪除節點在左子樹中 + cur = cur.Left + } + } + // 若無待刪除節點,則直接返回 + if cur == nil { + return + } + // 子節點數為 0 或 1 + if cur.Left == nil || cur.Right == nil { + var child *TreeNode = nil + // 取出待刪除節點的子節點 + if cur.Left != nil { + child = cur.Left + } else { + child = cur.Right + } + // 刪除節點 cur + if cur != bst.root { + if pre.Left == cur { + pre.Left = child + } else { + pre.Right = child + } + } else { + // 若刪除節點為根節點,則重新指定根節點 + bst.root = child + } + // 子節點數為 2 + } else { + // 獲取中序走訪中待刪除節點 cur 的下一個節點 + tmp := cur.Right + for tmp.Left != nil { + tmp = tmp.Left + } + // 遞迴刪除節點 tmp + bst.remove(tmp.Val.(int)) + // 用 tmp 覆蓋 cur + cur.Val = tmp.Val + } +} + +/* 列印二元搜尋樹 */ +func (bst *binarySearchTree) print() { + PrintTree(bst.root) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_search_tree_test.go b/zh-hant/codes/go/chapter_tree/binary_search_tree_test.go new file mode 100644 index 0000000000..2b3f45f31c --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_search_tree_test.go @@ -0,0 +1,45 @@ +// File: binary_search_tree_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" +) + +func TestBinarySearchTree(t *testing.T) { + bst := newBinarySearchTree() + nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + for _, num := range nums { + bst.insert(num) + } + fmt.Println("\n初始化的二元樹為:") + bst.print() + + // 獲取根節點 + node := bst.getRoot() + fmt.Println("\n二元樹的根節點為:", node.Val) + + // 查詢節點 + node = bst.search(7) + fmt.Println("查詢到的節點物件為", node, ",節點值 =", node.Val) + + // 插入節點 + bst.insert(16) + fmt.Println("\n插入節點後 16 的二元樹為:") + bst.print() + + // 刪除節點 + bst.remove(1) + fmt.Println("\n刪除節點 1 後的二元樹為:") + bst.print() + bst.remove(2) + fmt.Println("\n刪除節點 2 後的二元樹為:") + bst.print() + bst.remove(4) + fmt.Println("\n刪除節點 4 後的二元樹為:") + bst.print() +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_bfs.go b/zh-hant/codes/go/chapter_tree/binary_tree_bfs.go new file mode 100644 index 0000000000..821c08d290 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_bfs.go @@ -0,0 +1,35 @@ +// File: binary_tree_bfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "container/list" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 層序走訪 */ +func levelOrder(root *TreeNode) []any { + // 初始化佇列,加入根節點 + queue := list.New() + queue.PushBack(root) + // 初始化一個切片,用於儲存走訪序列 + nums := make([]any, 0) + for queue.Len() > 0 { + // 隊列出隊 + node := queue.Remove(queue.Front()).(*TreeNode) + // 儲存節點值 + nums = append(nums, node.Val) + if node.Left != nil { + // 左子節點入列 + queue.PushBack(node.Left) + } + if node.Right != nil { + // 右子節點入列 + queue.PushBack(node.Right) + } + } + return nums +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_bfs_test.go b/zh-hant/codes/go/chapter_tree/binary_tree_bfs_test.go new file mode 100644 index 0000000000..fd33ce5aec --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -0,0 +1,24 @@ +// File: binary_tree_bfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLevelOrder(t *testing.T) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹: ") + PrintTree(root) + + // 層序走訪 + nums := levelOrder(root) + fmt.Println("\n層序走訪的節點列印序列 =", nums) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_dfs.go b/zh-hant/codes/go/chapter_tree/binary_tree_dfs.go new file mode 100644 index 0000000000..874bc93ec3 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_dfs.go @@ -0,0 +1,44 @@ +// File: binary_tree_dfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +var nums []any + +/* 前序走訪 */ +func preOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + nums = append(nums, node.Val) + preOrder(node.Left) + preOrder(node.Right) +} + +/* 中序走訪 */ +func inOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(node.Left) + nums = append(nums, node.Val) + inOrder(node.Right) +} + +/* 後序走訪 */ +func postOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(node.Left) + postOrder(node.Right) + nums = append(nums, node.Val) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_dfs_test.go b/zh-hant/codes/go/chapter_tree/binary_tree_dfs_test.go new file mode 100644 index 0000000000..08c635af92 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -0,0 +1,35 @@ +// File: binary_tree_dfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreInPostOrderTraversal(t *testing.T) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹: ") + PrintTree(root) + + // 前序走訪 + nums = nil + preOrder(root) + fmt.Println("\n前序走訪的節點列印序列 =", nums) + + // 中序走訪 + nums = nil + inOrder(root) + fmt.Println("\n中序走訪的節點列印序列 =", nums) + + // 後序走訪 + nums = nil + postOrder(root) + fmt.Println("\n後序走訪的節點列印序列 =", nums) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_test.go b/zh-hant/codes/go/chapter_tree/binary_tree_test.go new file mode 100644 index 0000000000..f54b5db950 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_test.go @@ -0,0 +1,41 @@ +// File: binary_tree_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBinaryTree(t *testing.T) { + /* 初始化二元樹 */ + // 初始化節點 + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // 構建節點之間的引用(指標) + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + fmt.Println("初始化二元樹") + PrintTree(n1) + + /* 插入與刪除節點 */ + // 插入節點 + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + fmt.Println("插入節點 P 後") + PrintTree(n1) + // 刪除節點 + n1.Left = n2 + fmt.Println("刪除節點 P 後") + PrintTree(n1) +} diff --git a/zh-hant/codes/go/go.mod b/zh-hant/codes/go/go.mod new file mode 100644 index 0000000000..34f5dac200 --- /dev/null +++ b/zh-hant/codes/go/go.mod @@ -0,0 +1,3 @@ +module github.com/krahets/hello-algo + +go 1.19 diff --git a/zh-hant/codes/go/pkg/list_node.go b/zh-hant/codes/go/pkg/list_node.go new file mode 100644 index 0000000000..c88bbf4930 --- /dev/null +++ b/zh-hant/codes/go/pkg/list_node.go @@ -0,0 +1,31 @@ +// File: list_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// ListNode 鏈結串列節點 +type ListNode struct { + Next *ListNode + Val int +} + +// NewListNode 鏈結串列節點建構子 +func NewListNode(v int) *ListNode { + return &ListNode{ + Next: nil, + Val: v, + } +} + +// ArrayToLinkedList 將陣列反序列化為鏈結串列 +func ArrayToLinkedList(arr []int) *ListNode { + // dummy header of linked list + dummy := NewListNode(0) + node := dummy + for _, val := range arr { + node.Next = NewListNode(val) + node = node.Next + } + return dummy.Next +} diff --git a/zh-hant/codes/go/pkg/list_node_test.go b/zh-hant/codes/go/pkg/list_node_test.go new file mode 100644 index 0000000000..e61d8d5bff --- /dev/null +++ b/zh-hant/codes/go/pkg/list_node_test.go @@ -0,0 +1,16 @@ +// File: list_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "testing" +) + +func TestListNode(t *testing.T) { + arr := []int{2, 3, 5, 6, 7} + head := ArrayToLinkedList(arr) + + PrintLinkedList(head) +} diff --git a/zh-hant/codes/go/pkg/print_utils.go b/zh-hant/codes/go/pkg/print_utils.go new file mode 100644 index 0000000000..e443d318ce --- /dev/null +++ b/zh-hant/codes/go/pkg/print_utils.go @@ -0,0 +1,118 @@ +// File: print_utils.go +// Created Time: 2022-12-03 +// Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) + +package pkg + +import ( + "container/list" + "fmt" + "strconv" + "strings" +) + +// PrintSlice 列印切片 +func PrintSlice[T any](nums []T) { + fmt.Printf("%v", nums) + fmt.Println() +} + +// PrintList 列印串列 +func PrintList(list *list.List) { + if list.Len() == 0 { + fmt.Print("[]\n") + return + } + e := list.Front() + // 強轉為 string, 會影響效率 + fmt.Print("[") + for e.Next() != nil { + fmt.Print(e.Value, " ") + e = e.Next() + } + fmt.Print(e.Value, "]\n") +} + +// PrintMap 列印雜湊表 +func PrintMap[K comparable, V any](m map[K]V) { + for key, value := range m { + fmt.Println(key, "->", value) + } +} + +// PrintHeap 列印堆積 +func PrintHeap(h []any) { + fmt.Printf("堆積的陣列表示:") + fmt.Printf("%v", h) + fmt.Printf("\n堆積的樹狀表示:\n") + root := SliceToTree(h) + PrintTree(root) +} + +// PrintLinkedList 列印鏈結串列 +func PrintLinkedList(node *ListNode) { + if node == nil { + return + } + var builder strings.Builder + for node.Next != nil { + builder.WriteString(strconv.Itoa(node.Val) + " -> ") + node = node.Next + } + builder.WriteString(strconv.Itoa(node.Val)) + fmt.Println(builder.String()) +} + +// PrintTree 列印二元樹 +func PrintTree(root *TreeNode) { + printTreeHelper(root, nil, false) +} + +// printTreeHelper 列印二元樹 +// This tree printer is borrowed from TECHIE DELIGHT +// https://www.techiedelight.com/c-program-print-binary-tree/ +func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { + if root == nil { + return + } + prevStr := " " + trunk := newTrunk(prev, prevStr) + printTreeHelper(root.Right, trunk, true) + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + showTrunk(trunk) + fmt.Println(root.Val) + if prev != nil { + prev.str = prevStr + } + trunk.str = " |" + printTreeHelper(root.Left, trunk, false) +} + +type trunk struct { + prev *trunk + str string +} + +func newTrunk(prev *trunk, str string) *trunk { + return &trunk{ + prev: prev, + str: str, + } +} + +func showTrunk(t *trunk) { + if t == nil { + return + } + + showTrunk(t.prev) + fmt.Print(t.str) +} diff --git a/zh-hant/codes/go/pkg/tree_node.go b/zh-hant/codes/go/pkg/tree_node.go new file mode 100644 index 0000000000..1943b44c9a --- /dev/null +++ b/zh-hant/codes/go/pkg/tree_node.go @@ -0,0 +1,78 @@ +// File: tree_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// TreeNode 二元樹節點 +type TreeNode struct { + Val any // 節點值 + Height int // 節點高度 + Left *TreeNode // 左子節點引用 + Right *TreeNode // 右子節點引用 +} + +// NewTreeNode 二元樹節點建構子 +func NewTreeNode(v any) *TreeNode { + return &TreeNode{ + Val: v, + Height: 0, + Left: nil, + Right: nil, + } +} + +// 序列化編碼規則請參考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二元樹的陣列表示: +// [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] +// 二元樹的鏈結串列表示: +// +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// +// ——— 1 +// +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +// SliceToTreeDFS 將串列反序列化為二元樹:遞迴 +func SliceToTreeDFS(arr []any, i int) *TreeNode { + if i < 0 || i >= len(arr) || arr[i] == nil { + return nil + } + root := NewTreeNode(arr[i]) + root.Left = SliceToTreeDFS(arr, 2*i+1) + root.Right = SliceToTreeDFS(arr, 2*i+2) + return root +} + +// SliceToTree 將切片反序列化為二元樹 +func SliceToTree(arr []any) *TreeNode { + return SliceToTreeDFS(arr, 0) +} + +// TreeToSliceDFS 將二元樹序列化為切片:遞迴 +func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { + if root == nil { + return + } + for i >= len(*res) { + *res = append(*res, nil) + } + (*res)[i] = root.Val + TreeToSliceDFS(root.Left, 2*i+1, res) + TreeToSliceDFS(root.Right, 2*i+2, res) +} + +// TreeToSlice 將二元樹序列化為切片 +func TreeToSlice(root *TreeNode) []any { + var res []any + TreeToSliceDFS(root, 0, &res) + return res +} diff --git a/zh-hant/codes/go/pkg/tree_node_test.go b/zh-hant/codes/go/pkg/tree_node_test.go new file mode 100644 index 0000000000..043aab099a --- /dev/null +++ b/zh-hant/codes/go/pkg/tree_node_test.go @@ -0,0 +1,21 @@ +// File: tree_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "fmt" + "testing" +) + +func TestTreeNode(t *testing.T) { + arr := []any{1, 2, 3, nil, 5, 6, nil} + node := SliceToTree(arr) + + // print tree + PrintTree(node) + + // tree to arr + fmt.Println(TreeToSlice(node)) +} diff --git a/zh-hant/codes/go/pkg/vertex.go b/zh-hant/codes/go/pkg/vertex.go new file mode 100644 index 0000000000..3031187060 --- /dev/null +++ b/zh-hant/codes/go/pkg/vertex.go @@ -0,0 +1,55 @@ +// File: vertex.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package pkg + +// Vertex 頂點類別 +type Vertex struct { + Val int +} + +// NewVertex 頂點建構子 +func NewVertex(val int) Vertex { + return Vertex{ + Val: val, + } +} + +// ValsToVets 將值串列反序列化為頂點串列 +func ValsToVets(vals []int) []Vertex { + vets := make([]Vertex, len(vals)) + for i := 0; i < len(vals); i++ { + vets[i] = NewVertex(vals[i]) + } + return vets +} + +// VetsToVals 將頂點串列序列化為值串列 +func VetsToVals(vets []Vertex) []int { + vals := make([]int, len(vets)) + for i := range vets { + vals[i] = vets[i].Val + } + return vals +} + +// DeleteSliceElms 刪除切片指定元素 +func DeleteSliceElms[T any](a []T, elms ...T) []T { + if len(a) == 0 || len(elms) == 0 { + return a + } + // 先將元素轉為 set + m := make(map[any]struct{}) + for _, v := range elms { + m[v] = struct{}{} + } + // 過濾掉指定元素 + res := make([]T, 0, len(a)) + for _, v := range a { + if _, ok := m[v]; !ok { + res = append(res, v) + } + } + return res +} diff --git a/zh-hant/codes/java/.gitignore b/zh-hant/codes/java/.gitignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/zh-hant/codes/java/.gitignore @@ -0,0 +1 @@ +build diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/array.java b/zh-hant/codes/java/chapter_array_and_linkedlist/array.java new file mode 100644 index 0000000000..bdc6321f04 --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/array.java @@ -0,0 +1,105 @@ +/** + * File: array.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class array { + /* 隨機訪問元素 */ + static int randomAccess(int[] nums) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* 擴展陣列長度 */ + static int[] extend(int[] nums, int enlarge) { + // 初始化一個擴展長度後的陣列 + int[] res = new int[nums.length + enlarge]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; + } + + /* 在陣列的索引 index 處插入元素 num */ + static void insert(int[] nums, int num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (int i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; + } + + /* 刪除索引 index 處的元素 */ + static void remove(int[] nums, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (int i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* 走訪陣列 */ + static void traverse(int[] nums) { + int count = 0; + // 透過索引走訪陣列 + for (int i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (int num : nums) { + count += num; + } + } + + /* 在陣列中查詢指定元素 */ + static int find(int[] nums, int target) { + for (int i = 0; i < nums.length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* 初始化陣列 */ + int[] arr = new int[5]; + System.out.println("陣列 arr = " + Arrays.toString(arr)); + int[] nums = { 1, 3, 2, 5, 4 }; + System.out.println("陣列 nums = " + Arrays.toString(nums)); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums); + System.out.println("在 nums 中獲取隨機元素 " + randomNum); + + /* 長度擴展 */ + nums = extend(nums, 3); + System.out.println("將陣列長度擴展至 8 ,得到 nums = " + Arrays.toString(nums)); + + /* 插入元素 */ + insert(nums, 6, 3); + System.out.println("在索引 3 處插入數字 6 ,得到 nums = " + Arrays.toString(nums)); + + /* 刪除元素 */ + remove(nums, 2); + System.out.println("刪除索引 2 處的元素,得到 nums = " + Arrays.toString(nums)); + + /* 走訪陣列 */ + traverse(nums); + + /* 查詢元素 */ + int index = find(nums, 3); + System.out.println("在 nums 中查詢元素 3 ,得到索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/linked_list.java b/zh-hant/codes/java/chapter_array_and_linkedlist/linked_list.java new file mode 100644 index 0000000000..b3ab5a9413 --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -0,0 +1,86 @@ +/** + * File: linked_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import utils.*; + +public class linked_list { + /* 在鏈結串列的節點 n0 之後插入節點 P */ + static void insert(ListNode n0, ListNode P) { + ListNode n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* 刪除鏈結串列的節點 n0 之後的首個節點 */ + static void remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode n1 = P.next; + n0.next = n1; + } + + /* 訪問鏈結串列中索引為 index 的節點 */ + static ListNode access(ListNode head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* 在鏈結串列中查詢值為 target 的首個節點 */ + static int find(ListNode head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* 初始化鏈結串列 */ + // 初始化各個節點 + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + System.out.println("初始化的鏈結串列為"); + PrintUtil.printLinkedList(n0); + + /* 插入節點 */ + insert(n0, new ListNode(0)); + System.out.println("插入節點後的鏈結串列為"); + PrintUtil.printLinkedList(n0); + + /* 刪除節點 */ + remove(n0); + System.out.println("刪除節點後的鏈結串列為"); + PrintUtil.printLinkedList(n0); + + /* 訪問節點 */ + ListNode node = access(n0, 3); + System.out.println("鏈結串列中索引 3 處的節點的值 = " + node.val); + + /* 查詢節點 */ + int index = find(n0, 2); + System.out.println("鏈結串列中值為 2 的節點的索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/list.java b/zh-hant/codes/java/chapter_array_and_linkedlist/list.java new file mode 100644 index 0000000000..13862233b6 --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/list.java @@ -0,0 +1,66 @@ +/** + * File: list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +public class list { + public static void main(String[] args) { + /* 初始化串列 */ + // 注意陣列的元素型別是 int[] 的包裝類別 Integer[] + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + System.out.println("串列 nums = " + nums); + + /* 訪問元素 */ + int num = nums.get(1); + System.out.println("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums.set(1, 0); + System.out.println("將索引 1 處的元素更新為 0 ,得到 nums = " + nums); + + /* 清空串列 */ + nums.clear(); + System.out.println("清空串列後 nums = " + nums); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("新增元素後 nums = " + nums); + + /* 在中間插入元素 */ + nums.add(3, 6); + System.out.println("在索引 3 處插入數字 6 ,得到 nums = " + nums); + + /* 刪除元素 */ + nums.remove(3); + System.out.println("刪除索引 3 處的元素,得到 nums = " + nums); + + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + /* 直接走訪串列元素 */ + for (int x : nums) { + count += x; + } + + /* 拼接兩個串列 */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); + System.out.println("將串列 nums1 拼接到 nums 之後,得到 nums = " + nums); + + /* 排序串列 */ + Collections.sort(nums); + System.out.println("排序串列後 nums = " + nums); + } +} diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/my_list.java b/zh-hant/codes/java/chapter_array_and_linkedlist/my_list.java new file mode 100644 index 0000000000..64265e31cf --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/my_list.java @@ -0,0 +1,147 @@ +/** + * File: my_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +/* 串列類別 */ +class MyList { + private int[] arr; // 陣列(儲存串列元素) + private int capacity = 10; // 串列容量 + private int size = 0; // 串列長度(當前元素數量) + private int extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + public MyList() { + arr = new int[capacity]; + } + + /* 獲取串列長度(當前元素數量) */ + public int size() { + return size; + } + + /* 獲取串列容量 */ + public int capacity() { + return capacity; + } + + /* 訪問元素 */ + public int get(int index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + return arr[index]; + } + + /* 更新元素 */ + public void set(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + arr[index] = num; + } + + /* 在尾部新增元素 */ + public void add(int num) { + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity(); + arr[size] = num; + // 更新元素數量 + size++; + } + + /* 在中間插入元素 */ + public void insert(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (int j = size - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 更新元素數量 + size++; + } + + /* 刪除元素 */ + public int remove(int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + int num = arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (int j = index; j < size - 1; j++) { + arr[j] = arr[j + 1]; + } + // 更新元素數量 + size--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + public void extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + arr = Arrays.copyOf(arr, capacity() * extendRatio); + // 更新串列容量 + capacity = arr.length; + } + + /* 將串列轉換為陣列 */ + public int[] toArray() { + int size = size(); + // 僅轉換有效長度範圍內的串列元素 + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = get(i); + } + return arr; + } +} + +public class my_list { + /* Driver Code */ + public static void main(String[] args) { + /* 初始化串列 */ + MyList nums = new MyList(); + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("串列 nums = " + Arrays.toString(nums.toArray()) + + " ,容量 = " + nums.capacity() + " ,長度 = " + nums.size()); + + /* 在中間插入元素 */ + nums.insert(3, 6); + System.out.println("在索引 3 處插入數字 6 ,得到 nums = " + Arrays.toString(nums.toArray())); + + /* 刪除元素 */ + nums.remove(3); + System.out.println("刪除索引 3 處的元素,得到 nums = " + Arrays.toString(nums.toArray())); + + /* 訪問元素 */ + int num = nums.get(1); + System.out.println("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums.set(1, 0); + System.out.println("將索引 1 處的元素更新為 0 ,得到 nums = " + Arrays.toString(nums.toArray())); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); + } + System.out.println("擴容後的串列 nums = " + Arrays.toString(nums.toArray()) + + " ,容量 = " + nums.capacity() + " ,長度 = " + nums.size()); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/n_queens.java b/zh-hant/codes/java/chapter_backtracking/n_queens.java new file mode 100644 index 0000000000..e726a5ad87 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/n_queens.java @@ -0,0 +1,77 @@ +/** + * File: n_queens.java + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class n_queens { + /* 回溯演算法:n 皇后 */ + public static void backtrack(int row, int n, List> state, List>> res, + boolean[] cols, boolean[] diags1, boolean[] diags2) { + // 當放置完所有行時,記錄解 + if (row == n) { + List> copyState = new ArrayList<>(); + for (List sRow : state) { + copyState.add(new ArrayList<>(sRow)); + } + res.add(copyState); + 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.get(row).set(col, "Q"); + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state.get(row).set(col, "#"); + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* 求解 n 皇后 */ + public static List>> nQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + List> state = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List row = new ArrayList<>(); + for (int j = 0; j < n; j++) { + row.add("#"); + } + state.add(row); + } + boolean[] cols = new boolean[n]; // 記錄列是否有皇后 + boolean[] diags1 = new boolean[2 * n - 1]; // 記錄主對角線上是否有皇后 + boolean[] diags2 = new boolean[2 * n - 1]; // 記錄次對角線上是否有皇后 + List>> res = new ArrayList<>(); + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + public static void main(String[] args) { + int n = 4; + List>> res = nQueens(n); + + System.out.println("輸入棋盤長寬為 " + n); + System.out.println("皇后放置方案共有 " + res.size() + " 種"); + for (List> state : res) { + System.out.println("--------------------"); + for (List row : state) { + System.out.println(row); + } + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/permutations_i.java b/zh-hant/codes/java/chapter_backtracking/permutations_i.java new file mode 100644 index 0000000000..e7beb496aa --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/permutations_i.java @@ -0,0 +1,51 @@ +/** + * File: permutations_i.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_i { + /* 回溯演算法:全排列 I */ + public static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* 全排列 I */ + static List> permutationsI(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3 }; + + List> res = permutationsI(nums); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums)); + System.out.println("所有排列 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/permutations_ii.java b/zh-hant/codes/java/chapter_backtracking/permutations_ii.java new file mode 100644 index 0000000000..58c1227e6c --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/permutations_ii.java @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_ii { + /* 回溯演算法:全排列 II */ + static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 走訪所有選擇 + Set duplicated = new HashSet(); + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* 全排列 II */ + static List> permutationsII(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 2 }; + + List> res = permutationsII(nums); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums)); + System.out.println("所有排列 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_i_compact.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_i_compact.java new file mode 100644 index 0000000000..e1e47695f6 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_i_compact.java @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_i_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_i_compact { + static List res; + + /* 前序走訪:例題一 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 記錄解 + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 前序走訪 + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n輸出所有值為 7 的節點"); + List vals = new ArrayList<>(); + for (TreeNode node : res) { + vals.add(node.val); + } + System.out.println(vals); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java new file mode 100644 index 0000000000..472aa47996 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_ii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_ii_compact { + static List path; + static List> res; + + /* 前序走訪:例題二 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 前序走訪 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n輸出所有根節點到節點 7 的路徑"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java new file mode 100644 index 0000000000..36a4e1a1f4 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -0,0 +1,53 @@ +/** + * File: preorder_traversal_iii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_compact { + static List path; + static List> res; + + /* 前序走訪:例題三 */ + static void preOrder(TreeNode root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 前序走訪 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_template.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_template.java new file mode 100644 index 0000000000..4a5088acf0 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -0,0 +1,77 @@ +/** + * File: preorder_traversal_iii_template.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_template { + /* 判斷當前狀態是否為解 */ + static boolean isSolution(List state) { + return !state.isEmpty() && state.get(state.size() - 1).val == 7; + } + + /* 記錄解 */ + static void recordSolution(List state, List> res) { + res.add(new ArrayList<>(state)); + } + + /* 判斷在當前狀態下,該選擇是否合法 */ + static boolean isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 更新狀態 */ + static void makeChoice(List state, TreeNode choice) { + state.add(choice); + } + + /* 恢復狀態 */ + static void undoChoice(List state, TreeNode choice) { + state.remove(state.size() - 1); + } + + /* 回溯演算法:例題三 */ + static void backtrack(List state, List choices, List> res) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (TreeNode choice : choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, Arrays.asList(choice.left, choice.right), res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 回溯演算法 + List> res = new ArrayList<>(); + backtrack(new ArrayList<>(), Arrays.asList(root), res); + + System.out.println("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/subset_sum_i.java b/zh-hant/codes/java/chapter_backtracking/subset_sum_i.java new file mode 100644 index 0000000000..03aca353de --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/subset_sum_i.java @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i { + /* 回溯演算法:子集和 I */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 I */ + static List> subsetSumI(int[] nums, int target) { + List state = new ArrayList<>(); // 狀態(子集) + Arrays.sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = new ArrayList<>(); // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumI(nums, target); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等於 " + target + " 的子集 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/subset_sum_i_naive.java b/zh-hant/codes/java/chapter_backtracking/subset_sum_i_naive.java new file mode 100644 index 0000000000..be2147b1e5 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/subset_sum_i_naive.java @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i_naive.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i_naive { + /* 回溯演算法:子集和 I */ + static void backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.add(new ArrayList<>(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 I(包含重複子集) */ + static List> subsetSumINaive(int[] nums, int target) { + List state = new ArrayList<>(); // 狀態(子集) + int total = 0; // 子集和 + List> res = new ArrayList<>(); // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等於 " + target + " 的子集 res = " + res); + System.out.println("請注意,該方法輸出的結果包含重複集合"); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/subset_sum_ii.java b/zh-hant/codes/java/chapter_backtracking/subset_sum_ii.java new file mode 100644 index 0000000000..38dbf4c2cf --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/subset_sum_ii.java @@ -0,0 +1,60 @@ +/** + * File: subset_sum_ii.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_ii { + /* 回溯演算法:子集和 II */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 II */ + static List> subsetSumII(int[] nums, int target) { + List state = new ArrayList<>(); // 狀態(子集) + Arrays.sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = new ArrayList<>(); // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 4, 4, 5 }; + int target = 9; + + List> res = subsetSumII(nums, target); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等於 " + target + " 的子集 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/iteration.java b/zh-hant/codes/java/chapter_computational_complexity/iteration.java new file mode 100644 index 0000000000..2da250a077 --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/iteration.java @@ -0,0 +1,76 @@ +/** + * File: iteration.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class iteration { + /* for 迴圈 */ + static int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* while 迴圈 */ + static int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; + } + + /* while 迴圈(兩次更新) */ + static int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; + } + + /* 雙層 for 迴圈 */ + static String nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.append("(" + i + ", " + j + "), "); + } + } + return res.toString(); + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = forLoop(n); + System.out.println("\nfor 迴圈的求和結果 res = " + res); + + res = whileLoop(n); + System.out.println("\nwhile 迴圈的求和結果 res = " + res); + + res = whileLoopII(n); + System.out.println("\nwhile 迴圈(兩次更新)求和結果 res = " + res); + + String resStr = nestedForLoop(n); + System.out.println("\n雙層 for 迴圈的走訪結果 " + resStr); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/recursion.java b/zh-hant/codes/java/chapter_computational_complexity/recursion.java new file mode 100644 index 0000000000..e72854e69e --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/recursion.java @@ -0,0 +1,79 @@ +/** + * File: recursion.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.Stack; + +public class recursion { + /* 遞迴 */ + static int recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; + } + + /* 使用迭代模擬遞迴 */ + static int forLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + Stack stack = new Stack<>(); + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (!stack.isEmpty()) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* 尾遞迴 */ + static int tailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); + } + + /* 費波那契數列:遞迴 */ + static 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; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = recur(n); + System.out.println("\n遞迴函式的求和結果 res = " + res); + + res = forLoopRecur(n); + System.out.println("\n使用迭代模擬遞迴求和結果 res = " + res); + + res = tailRecur(n, 0); + System.out.println("\n尾遞迴函式的求和結果 res = " + res); + + res = fib(n); + System.out.println("\n費波那契數列的第 " + n + " 項為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/space_complexity.java b/zh-hant/codes/java/chapter_computational_complexity/space_complexity.java new file mode 100644 index 0000000000..4aa7c3b65c --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/space_complexity.java @@ -0,0 +1,110 @@ +/** + * File: space_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import utils.*; +import java.util.*; + +public class space_complexity { + /* 函式 */ + static int function() { + // 執行某些操作 + return 0; + } + + /* 常數階 */ + static void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + final int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + function(); + } + } + + /* 線性階 */ + static void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + int[] nums = new int[n]; + // 長度為 n 的串列佔用 O(n) 空間 + List nodes = new ArrayList<>(); + for (int i = 0; i < n; i++) { + nodes.add(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + Map map = new HashMap<>(); + for (int i = 0; i < n; i++) { + map.put(i, String.valueOf(i)); + } + } + + /* 線性階(遞迴實現) */ + static void linearRecur(int n) { + System.out.println("遞迴 n = " + n); + if (n == 1) + return; + linearRecur(n - 1); + } + + /* 平方階 */ + static void quadratic(int n) { + // 矩陣佔用 O(n^2) 空間 + int[][] numMatrix = new int[n][n]; + // 二維串列佔用 O(n^2) 空間 + List> numList = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List tmp = new ArrayList<>(); + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } + } + + /* 平方階(遞迴實現) */ + static int quadraticRecur(int n) { + if (n <= 0) + return 0; + // 陣列 nums 長度為 n, n-1, ..., 2, 1 + int[] nums = new int[n]; + System.out.println("遞迴 n = " + n + " 中的 nums 長度 = " + nums.length); + return quadraticRecur(n - 1); + } + + /* 指數階(建立滿二元樹) */ + static TreeNode buildTree(int n) { + if (n == 0) + return null; + TreeNode root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode root = buildTree(n); + PrintUtil.printTree(root); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/time_complexity.java b/zh-hant/codes/java/chapter_computational_complexity/time_complexity.java new file mode 100644 index 0000000000..1fedefa05e --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/time_complexity.java @@ -0,0 +1,167 @@ +/** + * File: time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class time_complexity { + /* 常數階 */ + static int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* 線性階 */ + static int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* 線性階(走訪陣列) */ + static int arrayTraversal(int[] nums) { + int count = 0; + // 迴圈次數與陣列長度成正比 + for (int num : nums) { + count++; + } + return count; + } + + /* 平方階 */ + static 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; + } + + /* 平方階(泡沫排序) */ + static int bubbleSort(int[] nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 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; + } + + /* 指數階(迴圈實現) */ + static int exponential(int n) { + int count = 0, base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* 指數階(遞迴實現) */ + static int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } + + /* 對數階(迴圈實現) */ + static int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; + } + + /* 對數階(遞迴實現) */ + static int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; + } + + /* 線性對數階 */ + static 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; + } + + /* 階乘階(遞迴實現) */ + static int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // 從 1 個分裂出 n 個 + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; + } + + /* Driver Code */ + public static void main(String[] args) { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + System.out.println("輸入資料大小 n = " + n); + + int count = constant(n); + System.out.println("常數階的操作數量 = " + count); + + count = linear(n); + System.out.println("線性階的操作數量 = " + count); + count = arrayTraversal(new int[n]); + System.out.println("線性階(走訪陣列)的操作數量 = " + count); + + count = quadratic(n); + System.out.println("平方階的操作數量 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + System.out.println("平方階(泡沫排序)的操作數量 = " + count); + + count = exponential(n); + System.out.println("指數階(迴圈實現)的操作數量 = " + count); + count = expRecur(n); + System.out.println("指數階(遞迴實現)的操作數量 = " + count); + + count = logarithmic(n); + System.out.println("對數階(迴圈實現)的操作數量 = " + count); + count = logRecur(n); + System.out.println("對數階(遞迴實現)的操作數量 = " + count); + + count = linearLogRecur(n); + System.out.println("線性對數階(遞迴實現)的操作數量 = " + count); + + count = factorialRecur(n); + System.out.println("階乘階(遞迴實現)的操作數量 = " + count); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/worst_best_time_complexity.java b/zh-hant/codes/java/chapter_computational_complexity/worst_best_time_complexity.java new file mode 100644 index 0000000000..b098c74f37 --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -0,0 +1,50 @@ +/** + * File: worst_best_time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.*; + +public class worst_best_time_complexity { + /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ + static int[] randomNumbers(int n) { + Integer[] nums = new Integer[n]; + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + Collections.shuffle(Arrays.asList(nums)); + // Integer[] -> int[] + int[] res = new int[n]; + for (int i = 0; i < n; i++) { + res[i] = nums[i]; + } + return res; + } + + /* 查詢陣列 nums 中數字 1 所在索引 */ + static int findOne(int[] nums) { + for (int i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = randomNumbers(n); + int index = findOne(nums); + System.out.println("\n陣列 [ 1, 2, ..., n ] 被打亂後 = " + Arrays.toString(nums)); + System.out.println("數字 1 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/java/chapter_divide_and_conquer/binary_search_recur.java b/zh-hant/codes/java/chapter_divide_and_conquer/binary_search_recur.java new file mode 100644 index 0000000000..7c3b65990a --- /dev/null +++ b/zh-hant/codes/java/chapter_divide_and_conquer/binary_search_recur.java @@ -0,0 +1,45 @@ +/** + * File: binary_search_recur.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +public class binary_search_recur { + /* 二分搜尋:問題 f(i, j) */ + static 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; + } + } + + /* 二分搜尋 */ + static int binarySearch(int[] nums, int target) { + int n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target); + System.out.println("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_divide_and_conquer/build_tree.java b/zh-hant/codes/java/chapter_divide_and_conquer/build_tree.java new file mode 100644 index 0000000000..77895cfb9a --- /dev/null +++ b/zh-hant/codes/java/chapter_divide_and_conquer/build_tree.java @@ -0,0 +1,51 @@ +/** + * File: build_tree.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import utils.*; +import java.util.*; + +public class build_tree { + /* 構建二元樹:分治 */ + static TreeNode dfs(int[] preorder, Map inorderMap, int i, int l, int r) { + // 子樹區間為空時終止 + if (r - l < 0) + return null; + // 初始化根節點 + TreeNode root = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap.get(preorder[i]); + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; + } + + /* 構建二元樹 */ + static TreeNode buildTree(int[] preorder, int[] inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + Map inorderMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderMap.put(inorder[i], i); + } + TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; + } + + public static void main(String[] args) { + int[] preorder = { 3, 9, 2, 1, 7 }; + int[] inorder = { 9, 3, 1, 2, 7 }; + System.out.println("前序走訪 = " + Arrays.toString(preorder)); + System.out.println("中序走訪 = " + Arrays.toString(inorder)); + + TreeNode root = buildTree(preorder, inorder); + System.out.println("構建的二元樹為:"); + PrintUtil.printTree(root); + } +} diff --git a/zh-hant/codes/java/chapter_divide_and_conquer/hanota.java b/zh-hant/codes/java/chapter_divide_and_conquer/hanota.java new file mode 100644 index 0000000000..e9bb96df3d --- /dev/null +++ b/zh-hant/codes/java/chapter_divide_and_conquer/hanota.java @@ -0,0 +1,59 @@ +/** + * File: hanota.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import java.util.*; + +public class hanota { + /* 移動一個圓盤 */ + static void move(List src, List tar) { + // 從 src 頂部拿出一個圓盤 + Integer pan = src.remove(src.size() - 1); + // 將圓盤放入 tar 頂部 + tar.add(pan); + } + + /* 求解河內塔問題 f(i) */ + static void dfs(int i, List src, List buf, List tar) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar); + } + + /* 求解河內塔問題 */ + static void solveHanota(List A, List B, List C) { + int n = A.size(); + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); + } + + public static void main(String[] args) { + // 串列尾部是柱子頂部 + List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); + List B = new ArrayList<>(); + List C = new ArrayList<>(); + System.out.println("初始狀態下:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + + solveHanota(A, B, C); + + System.out.println("圓盤移動完成後:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java new file mode 100644 index 0000000000..94b6959cc1 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.*; + +public class climbing_stairs_backtrack { + /* 回溯 */ + public static void backtrack(List choices, int state, int n, List res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res.set(0, res.get(0) + 1); + // 走訪所有選擇 + for (Integer choice : choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } + } + + /* 爬樓梯:回溯 */ + public static int climbingStairsBacktrack(int n) { + List choices = Arrays.asList(1, 2); // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + List res = new ArrayList<>(); + res.add(0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res.get(0); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsBacktrack(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java new file mode 100644 index 0000000000..7552c7a0fc --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.java + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 帶約束爬樓梯:動態規劃 */ + static int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + int[][] dp = new int[n + 1][3]; + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsConstraintDP(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java new file mode 100644 index 0000000000..f51c8b3c2c --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java @@ -0,0 +1,31 @@ +/** + * File: climbing_stairs_dfs.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 搜尋 */ + public static 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; + } + + /* 爬樓梯:搜尋 */ + public static int climbingStairsDFS(int n) { + return dfs(n); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFS(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java new file mode 100644 index 0000000000..9966606bce --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_dfs_mem.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class climbing_stairs_dfs_mem { + /* 記憶化搜尋 */ + public static 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; + } + + /* 爬樓梯:記憶化搜尋 */ + public static int climbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + int[] mem = new int[n + 1]; + Arrays.fill(mem, -1); + return dfs(n, mem); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFSMem(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} \ No newline at end of file diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java new file mode 100644 index 0000000000..a74fc02add --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java @@ -0,0 +1,48 @@ +/** + * File: climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 爬樓梯:動態規劃 */ + public static int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* 爬樓梯:空間最佳化後的動態規劃 */ + public static 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; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDP(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + + res = climbingStairsDPComp(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/coin_change.java b/zh-hant/codes/java/chapter_dynamic_programming/coin_change.java new file mode 100644 index 0000000000..bab37eb448 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/coin_change.java @@ -0,0 +1,72 @@ +/** + * File: coin_change.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class coin_change { + /* 零錢兌換:動態規劃 */ + static int coinChangeDP(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 狀態轉移:首行首列 + 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] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; + } + + /* 零錢兌換:空間最佳化後的動態規劃 */ + static int coinChangeDPComp(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Arrays.fill(dp, 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] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt); + System.out.println("湊到目標金額所需的最少硬幣數量為 " + res); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt); + System.out.println("湊到目標金額所需的最少硬幣數量為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/coin_change_ii.java b/zh-hant/codes/java/chapter_dynamic_programming/coin_change_ii.java new file mode 100644 index 0000000000..13e675fd5e --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/coin_change_ii.java @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class coin_change_ii { + /* 零錢兌換 II:動態規劃 */ + static int coinChangeIIDP(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; + } + + /* 零錢兌換 II:空間最佳化後的動態規劃 */ + static int coinChangeIIDPComp(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt); + System.out.println("湊出目標金額的硬幣組合數量為 " + res); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt); + System.out.println("湊出目標金額的硬幣組合數量為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/edit_distance.java b/zh-hant/codes/java/chapter_dynamic_programming/edit_distance.java new file mode 100644 index 0000000000..3f50239a4a --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/edit_distance.java @@ -0,0 +1,139 @@ +/** + * File: edit_distance.java + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class edit_distance { + /* 編輯距離:暴力搜尋 */ + static int editDistanceDFS(String s, String t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) + return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) + return i; + // 若兩字元相等,則直接跳過此兩字元 + if (s.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.min(Math.min(insert, delete), replace) + 1; + } + + /* 編輯距離:記憶化搜尋 */ + static int editDistanceDFSMem(String s, String t, 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.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.min(Math.min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* 編輯距離:動態規劃 */ + static int editDistanceDP(String s, String t) { + int n = s.length(), m = t.length(); + int[][] dp = new int[n + 1][m + 1]; + // 狀態轉移:首行首列 + 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.charAt(i - 1) == t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; + } + + /* 編輯距離:空間最佳化後的動態規劃 */ + static int editDistanceDPComp(String s, String t) { + int n = s.length(), m = t.length(); + int[] dp = new int[m + 1]; + // 狀態轉移:首行 + 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.charAt(i - 1) == t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; + } + + public static void main(String[] args) { + String s = "bag"; + String t = "pack"; + int n = s.length(), m = t.length(); + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][m + 1]; + for (int[] row : mem) + Arrays.fill(row, -1); + res = editDistanceDFSMem(s, t, mem, n, m); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 動態規劃 + res = editDistanceDP(s, t); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/knapsack.java b/zh-hant/codes/java/chapter_dynamic_programming/knapsack.java new file mode 100644 index 0000000000..a8500450d0 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/knapsack.java @@ -0,0 +1,116 @@ +/** + * File: knapsack.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class knapsack { + + /* 0-1 背包:暴力搜尋 */ + static 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 Math.max(no, yes); + } + + /* 0-1 背包:記憶化搜尋 */ + static int knapsackDFSMem(int[] wgt, int[] val, 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, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; + } + + /* 0-1 背包:動態規劃 */ + static int knapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 狀態轉移 + 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] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 0-1 背包:空間最佳化後的動態規劃 */ + static int knapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + int n = wgt.length; + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][cap + 1]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = knapsackDFSMem(wgt, val, mem, n, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 動態規劃 + res = knapsackDP(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java b/zh-hant/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java new file mode 100644 index 0000000000..ffd91ed01b --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_cost_climbing_stairs_dp { + /* 爬樓梯最小代價:動態規劃 */ + public static int minCostClimbingStairsDP(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ + public static int minCostClimbingStairsDPComp(int[] cost) { + int n = cost.length - 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 = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + public static void main(String[] args) { + int[] cost = { 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + System.out.println(String.format("輸入樓梯的代價串列為 %s", Arrays.toString(cost))); + + int res = minCostClimbingStairsDP(cost); + System.out.println(String.format("爬完樓梯的最低代價為 %d", res)); + + res = minCostClimbingStairsDPComp(cost); + System.out.println(String.format("爬完樓梯的最低代價為 %d", res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/min_path_sum.java b/zh-hant/codes/java/chapter_dynamic_programming/min_path_sum.java new file mode 100644 index 0000000000..eaecdd77e8 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -0,0 +1,125 @@ +/** + * File: min_path_sum.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_path_sum { + /* 最小路徑和:暴力搜尋 */ + static int minPathSumDFS(int[][] grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.min(left, up) + grid[i][j]; + } + + /* 最小路徑和:記憶化搜尋 */ + static int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 若已有記錄,則直接返回 + 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] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* 最小路徑和:動態規劃 */ + static int minPathSumDP(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[][] dp = new int[n][m]; + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; + } + + /* 最小路徑和:空間最佳化後的動態規劃 */ + static int minPathSumDPComp(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[] dp = new int[m]; + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + public static void main(String[] args) { + int[][] grid = { + { 1, 3, 1, 5 }, + { 2, 2, 4, 2 }, + { 5, 3, 2, 1 }, + { 4, 3, 5, 2 } + }; + int n = grid.length, m = grid[0].length; + + // 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n][m]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + + // 動態規劃 + res = minPathSumDP(grid); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/unbounded_knapsack.java b/zh-hant/codes/java/chapter_dynamic_programming/unbounded_knapsack.java new file mode 100644 index 0000000000..944b371ef3 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/unbounded_knapsack.java @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全背包:動態規劃 */ + static int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 狀態轉移 + 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] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 完全背包:空間最佳化後的動態規劃 */ + static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + 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] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 1, 2, 3 }; + int[] val = { 5, 11, 15 }; + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_adjacency_list.java b/zh-hant/codes/java/chapter_graph/graph_adjacency_list.java new file mode 100644 index 0000000000..8e21158111 --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_adjacency_list.java @@ -0,0 +1,117 @@ +/** + * File: graph_adjacency_list.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + Map> adjList; + + /* 建構子 */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // 新增所有頂點和邊 + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + public int size() { + return adjList.size(); + } + + /* 新增邊 */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 新增邊 vet1 - vet2 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* 刪除邊 */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 刪除邊 vet1 - vet2 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* 新增頂點 */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // 在鄰接表中新增一個新鏈結串列 + adjList.put(vet, new ArrayList<>()); + } + + /* 刪除頂點 */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.remove(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (List list : adjList.values()) { + list.remove(vet); + } + } + + /* 列印鄰接表 */ + public void print() { + System.out.println("鄰接表 ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } +} + +public class graph_adjacency_list { + public static void main(String[] args) { + /* 初始化無向圖 */ + Vertex[] v = Vertex.valsToVets(new int[] { 1, 3, 2, 5, 4 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + System.out.println("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + System.out.println("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + Vertex v5 = new Vertex(6); + graph.addVertex(v5); + System.out.println("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]); + System.out.println("\n刪除頂點 3 後,圖為"); + graph.print(); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_adjacency_matrix.java b/zh-hant/codes/java/chapter_graph/graph_adjacency_matrix.java new file mode 100644 index 0000000000..2ca0ca6250 --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_adjacency_matrix.java @@ -0,0 +1,131 @@ +/** + * File: graph_adjacency_matrix.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import utils.*; +import java.util.*; + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + List vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + List> adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // 新增頂點 + for (int val : vertices) { + addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + public int size() { + return vertices.size(); + } + + /* 新增頂點 */ + public void addVertex(int val) { + int n = size(); + // 向頂點串列中新增新頂點的值 + vertices.add(val); + // 在鄰接矩陣中新增一行 + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // 在鄰接矩陣中新增一列 + for (List row : adjMat) { + row.add(0); + } + } + + /* 刪除頂點 */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // 在頂點串列中移除索引 index 的頂點 + vertices.remove(index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.remove(index); + // 在鄰接矩陣中刪除索引 index 的列 + for (List row : adjMat) { + row.remove(index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void addEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat.get(i).set(j, 1); + adjMat.get(j).set(i, 1); + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void removeEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + adjMat.get(i).set(j, 0); + adjMat.get(j).set(i, 0); + } + + /* 列印鄰接矩陣 */ + public void print() { + System.out.print("頂點串列 = "); + System.out.println(vertices); + System.out.println("鄰接矩陣 ="); + PrintUtil.printMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + public static void main(String[] args) { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + int[] vertices = { 1, 3, 2, 5, 4 }; + int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; + GraphAdjMat graph = new GraphAdjMat(vertices, edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2); + System.out.println("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1); + System.out.println("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + graph.addVertex(6); + System.out.println("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1); + System.out.println("\n刪除頂點 3 後,圖為"); + graph.print(); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_bfs.java b/zh-hant/codes/java/chapter_graph/graph_bfs.java new file mode 100644 index 0000000000..b3eddc77e8 --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_bfs.java @@ -0,0 +1,55 @@ +/** + * File: graph_bfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_bfs { + /* 廣度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + static List graphBFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = new ArrayList<>(); + // 雜湊集合,用於記錄已被訪問過的頂點 + Set visited = new HashSet<>(); + visited.add(startVet); + // 佇列用於實現 BFS + Queue que = new LinkedList<>(); + que.offer(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!que.isEmpty()) { + Vertex vet = que.poll(); // 佇列首頂點出隊 + res.add(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳過已被訪問的頂點 + que.offer(adjVet); // 只入列未訪問的頂點 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; + } + + public static void main(String[] args) { + /* 初始化無向圖 */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + Vertex[][] edges = { { 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] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 廣度優先走訪 */ + List res = graphBFS(graph, v[0]); + System.out.println("\n廣度優先走訪(BFS)頂點序列為"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_dfs.java b/zh-hant/codes/java/chapter_graph/graph_dfs.java new file mode 100644 index 0000000000..266bf913a9 --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_dfs.java @@ -0,0 +1,51 @@ +/** + * File: graph_dfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_dfs { + /* 深度優先走訪輔助函式 */ + static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳過已被訪問的頂點 + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + static List graphDFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = new ArrayList<>(); + // 雜湊集合,用於記錄已被訪問過的頂點 + Set visited = new HashSet<>(); + dfs(graph, visited, res, startVet); + return res; + } + + public static void main(String[] args) { + /* 初始化無向圖 */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[5] }, { v[4], v[5] }, { v[5], v[6] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 深度優先走訪 */ + List res = graphDFS(graph, v[0]); + System.out.println("\n深度優先走訪(DFS)頂點序列為"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/coin_change_greedy.java b/zh-hant/codes/java/chapter_greedy/coin_change_greedy.java new file mode 100644 index 0000000000..671ff95b87 --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/coin_change_greedy.java @@ -0,0 +1,55 @@ +/** + * File: coin_change_greedy.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; + +public class coin_change_greedy { + /* 零錢兌換:貪婪 */ + static int coinChangeGreedy(int[] coins, int amt) { + // 假設 coins 串列有序 + int i = coins.length - 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; + } + + public static void main(String[] args) { + // 貪婪:能夠保證找到全域性最優解 + int[] coins = { 1, 5, 10, 20, 50, 100 }; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + + // 貪婪:無法保證找到全域性最優解 + coins = new int[] { 1, 20, 50 }; + amt = 60; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + System.out.println("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + coins = new int[] { 1, 49, 50 }; + amt = 98; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + System.out.println("實際上需要的最少數量為 2 ,即 49 + 49"); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/fractional_knapsack.java b/zh-hant/codes/java/chapter_greedy/fractional_knapsack.java new file mode 100644 index 0000000000..054f1fd7b4 --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/fractional_knapsack.java @@ -0,0 +1,59 @@ +/** + * File: fractional_knapsack.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; +import java.util.Comparator; + +/* 物品 */ +class Item { + int w; // 物品重量 + int v; // 物品價值 + + public Item(int w, int v) { + this.w = w; + this.v = v; + } +} + +public class fractional_knapsack { + /* 分數背包:貪婪 */ + static double fractionalKnapsack(int[] wgt, int[] val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + Item[] items = new Item[wgt.length]; + for (int i = 0; i < wgt.length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); + // 迴圈貪婪選擇 + double res = 0; + for (Item item : items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (double) item.v / item.w * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + + // 貪婪演算法 + double res = fractionalKnapsack(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/max_capacity.java b/zh-hant/codes/java/chapter_greedy/max_capacity.java new file mode 100644 index 0000000000..1d76259464 --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/max_capacity.java @@ -0,0 +1,38 @@ +/** + * File: max_capacity.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +public class max_capacity { + /* 最大容量:貪婪 */ + static int maxCapacity(int[] ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.length - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + public static void main(String[] args) { + int[] ht = { 3, 8, 5, 2, 7, 7, 3, 4 }; + + // 貪婪演算法 + int res = maxCapacity(ht); + System.out.println("最大容量為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/max_product_cutting.java b/zh-hant/codes/java/chapter_greedy/max_product_cutting.java new file mode 100644 index 0000000000..6bc9d8c91f --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/max_product_cutting.java @@ -0,0 +1,40 @@ +/** + * File: max_product_cutting.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.lang.Math; + +public class max_product_cutting { + /* 最大切分乘積:貪婪 */ + public static 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 (int) Math.pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return (int) Math.pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return (int) Math.pow(3, a); + } + + public static void main(String[] args) { + int n = 58; + + // 貪婪演算法 + int res = maxProductCutting(n); + System.out.println("最大切分乘積為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/array_hash_map.java b/zh-hant/codes/java/chapter_hashing/array_hash_map.java new file mode 100644 index 0000000000..61c509fe62 --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/array_hash_map.java @@ -0,0 +1,141 @@ +/** + * File: array_hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; + +/* 鍵值對 */ +class Pair { + public int key; + public String val; + + public Pair(int key, String val) { + this.key = key; + this.val = val; + } +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private List buckets; + + public ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + buckets = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + buckets.add(null); + } + } + + /* 雜湊函式 */ + private int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查詢操作 */ + public String get(int key) { + int index = hashFunc(key); + Pair pair = buckets.get(index); + if (pair == null) + return null; + return pair.val; + } + + /* 新增操作 */ + public void put(int key, String val) { + Pair pair = new Pair(key, val); + int index = hashFunc(key); + buckets.set(index, pair); + } + + /* 刪除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 置為 null ,代表刪除 + buckets.set(index, null); + } + + /* 獲取所有鍵值對 */ + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + pairSet.add(pair); + } + return pairSet; + } + + /* 獲取所有鍵 */ + public List keySet() { + List keySet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + keySet.add(pair.key); + } + return keySet; + } + + /* 獲取所有值 */ + public List valueSet() { + List valueSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + valueSet.add(pair.val); + } + return valueSet; + } + + /* 列印雜湊表 */ + public void print() { + for (Pair kv : pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + } +} + +public class array_hash_map { + public static void main(String[] args) { + /* 初始化雜湊表 */ + ArrayHashMap map = new ArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map.get(15937); + System.out.println("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + System.out.println("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.print(); + + /* 走訪雜湊表 */ + System.out.println("\n走訪鍵值對 Key->Value"); + for (Pair kv : map.pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + System.out.println("\n單獨走訪鍵 Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\n單獨走訪值 Value"); + for (String val : map.valueSet()) { + System.out.println(val); + } + } +} diff --git a/zh-hant/codes/java/chapter_hashing/built_in_hash.java b/zh-hant/codes/java/chapter_hashing/built_in_hash.java new file mode 100644 index 0000000000..e2238d068c --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/built_in_hash.java @@ -0,0 +1,38 @@ +/** + * File: built_in_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import utils.*; +import java.util.*; + +public class built_in_hash { + public static void main(String[] args) { + int num = 3; + int hashNum = Integer.hashCode(num); + System.out.println("整數 " + num + " 的雜湊值為 " + hashNum); + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + System.out.println("布林量 " + bol + " 的雜湊值為 " + hashBol); + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + System.out.println("小數 " + dec + " 的雜湊值為 " + hashDec); + + String str = "Hello 演算法"; + int hashStr = str.hashCode(); + System.out.println("字串 " + str + " 的雜湊值為 " + hashStr); + + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + System.out.println("陣列 " + Arrays.toString(arr) + " 的雜湊值為 " + hashTup); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + System.out.println("節點物件 " + obj + " 的雜湊值為 " + hashObj); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/hash_map.java b/zh-hant/codes/java/chapter_hashing/hash_map.java new file mode 100644 index 0000000000..3ba08f191d --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/hash_map.java @@ -0,0 +1,52 @@ +/** + * File: hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; +import utils.*; + +public class hash_map { + public static void main(String[] args) { + /* 初始化雜湊表 */ + Map map = new HashMap<>(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map.get(15937); + System.out.println("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + System.out.println("\n刪除 10583 後,雜湊表為\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* 走訪雜湊表 */ + System.out.println("\n走訪鍵值對 Key->Value"); + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + System.out.println("\n單獨走訪鍵 Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\n單獨走訪值 Value"); + for (String val : map.values()) { + System.out.println(val); + } + } +} diff --git a/zh-hant/codes/java/chapter_hashing/hash_map_chaining.java b/zh-hant/codes/java/chapter_hashing/hash_map_chaining.java new file mode 100644 index 0000000000..5c5d17d542 --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/hash_map_chaining.java @@ -0,0 +1,148 @@ +/** + * File: hash_map_chaining.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.ArrayList; +import java.util.List; + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + List> buckets; // 桶陣列 + + /* 建構子 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return (double) size / capacity; + } + + /* 查詢操作 */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 走訪桶,若找到 key ,則返回對應 val + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 走訪桶,從中刪除鍵值對 + for (Pair pair : bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + List> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (List bucket : bucketsTmp) { + for (Pair pair : bucket) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } +} + +public class hash_map_chaining { + public static void main(String[] args) { + /* 初始化雜湊表 */ + HashMapChaining map = new HashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map.get(13276); + System.out.println("\n輸入學號 13276 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + System.out.println("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.print(); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/hash_map_open_addressing.java b/zh-hant/codes/java/chapter_hashing/hash_map_open_addressing.java new file mode 100644 index 0000000000..f398a513e0 --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/hash_map_open_addressing.java @@ -0,0 +1,158 @@ +/** + * File: hash_map_open_addressing.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private int size; // 鍵值對數量 + private int capacity = 4; // 雜湊表容量 + private final double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + private final int extendRatio = 2; // 擴容倍數 + private Pair[] buckets; // 桶陣列 + private final Pair TOMBSTONE = new Pair(-1, "-1"); // 刪除標記 + + /* 建構子 */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* 雜湊函式 */ + private int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + private double loadFactor() { + return (double) size / capacity; + } + + /* 搜尋 key 對應的桶索引 */ + private int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index].key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + public String get(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則返回對應 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + public void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = new Pair(key, val); + size++; + } + + /* 刪除操作 */ + public void remove(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 擴容雜湊表 */ + private void extend() { + // 暫存原雜湊表 + Pair[] bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (Pair pair : bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + public void print() { + for (Pair pair : buckets) { + if (pair == null) { + System.out.println("null"); + } else if (pair == TOMBSTONE) { + System.out.println("TOMBSTONE"); + } else { + System.out.println(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + public static void main(String[] args) { + // 初始化雜湊表 + HashMapOpenAddressing hashmap = new HashMapOpenAddressing(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小囉"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + hashmap.print(); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + String name = hashmap.get(13276); + System.out.println("\n輸入學號 13276 ,查詢到姓名 " + name); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750); + System.out.println("\n刪除 16750 後,雜湊表為\nKey -> Value"); + hashmap.print(); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/simple_hash.java b/zh-hant/codes/java/chapter_hashing/simple_hash.java new file mode 100644 index 0000000000..b9acc79eed --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/simple_hash.java @@ -0,0 +1,65 @@ +/** + * File: simple_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +public class simple_hash { + /* 加法雜湊 */ + static int addHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* 乘法雜湊 */ + static int mulHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (31 * hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* 互斥或雜湊 */ + static int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash ^= (int) c; + } + return hash & MODULUS; + } + + /* 旋轉雜湊 */ + static int rotHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; + } + return (int) hash; + } + + public static void main(String[] args) { + String key = "Hello 演算法"; + + int hash = addHash(key); + System.out.println("加法雜湊值為 " + hash); + + hash = mulHash(key); + System.out.println("乘法雜湊值為 " + hash); + + hash = xorHash(key); + System.out.println("互斥或雜湊值為 " + hash); + + hash = rotHash(key); + System.out.println("旋轉雜湊值為 " + hash); + } +} diff --git a/zh-hant/codes/java/chapter_heap/heap.java b/zh-hant/codes/java/chapter_heap/heap.java new file mode 100644 index 0000000000..407c82678b --- /dev/null +++ b/zh-hant/codes/java/chapter_heap/heap.java @@ -0,0 +1,66 @@ +/** + * File: heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class heap { + public static void testPush(Queue heap, int val) { + heap.offer(val); // 元素入堆積 + System.out.format("\n元素 %d 入堆積後\n", val); + PrintUtil.printHeap(heap); + } + + public static void testPop(Queue heap) { + int val = heap.poll(); // 堆積頂元素出堆積 + System.out.format("\n堆積頂元素 %d 出堆積後\n", val); + PrintUtil.printHeap(heap); + } + + public static void main(String[] args) { + /* 初始化堆積 */ + // 初始化小頂堆積 + Queue minHeap = new PriorityQueue<>(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + System.out.println("\n以下測試樣例為大頂堆積"); + + /* 元素入堆積 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + System.out.format("\n堆積頂元素為 %d\n", peek); + + /* 堆積頂元素出堆積 */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + System.out.format("\n堆積元素數量為 %d\n", size); + + /* 判斷堆積是否為空 */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\n堆積是否為空 %b\n", isEmpty); + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + System.out.println("\n輸入串列並建立小頂堆積後"); + PrintUtil.printHeap(minHeap); + } +} diff --git a/zh-hant/codes/java/chapter_heap/my_heap.java b/zh-hant/codes/java/chapter_heap/my_heap.java new file mode 100644 index 0000000000..a6d8225043 --- /dev/null +++ b/zh-hant/codes/java/chapter_heap/my_heap.java @@ -0,0 +1,159 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +/* 大頂堆積 */ +class MaxHeap { + // 使用串列而非陣列,這樣無須考慮擴容問題 + private List maxHeap; + + /* 建構子,根據輸入串列建堆積 */ + public MaxHeap(List nums) { + // 將串列元素原封不動新增進堆積 + maxHeap = new ArrayList<>(nums); + // 堆積化除葉節點以外的其他所有節點 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + private int left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + private int right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + private int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 交換元素 */ + private void swap(int i, int j) { + int tmp = maxHeap.get(i); + maxHeap.set(i, maxHeap.get(j)); + maxHeap.set(j, tmp); + } + + /* 獲取堆積大小 */ + public int size() { + return maxHeap.size(); + } + + /* 判斷堆積是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + public int peek() { + return maxHeap.get(0); + } + + /* 元素入堆積 */ + public void push(int val) { + // 新增節點 + maxHeap.add(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) + break; + // 交換兩節點 + swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + public int pop() { + // 判空處理 + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(0, size() - 1); + // 刪除節點 + int val = maxHeap.remove(size() - 1); + // 從頂至底堆積化 + siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) + ma = l; + if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) + break; + // 交換兩節點 + swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + public void print() { + Queue queue = new PriorityQueue<>((a, b) -> { return b - a; }); + queue.addAll(maxHeap); + PrintUtil.printHeap(queue); + } +} + +public class my_heap { + public static void main(String[] args) { + /* 初始化大頂堆積 */ + MaxHeap maxHeap = new MaxHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)); + System.out.println("\n輸入串列並建堆積後"); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + System.out.format("\n堆積頂元素為 %d\n", peek); + + /* 元素入堆積 */ + int val = 7; + maxHeap.push(val); + System.out.format("\n元素 %d 入堆積後\n", val); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + System.out.format("\n堆積頂元素 %d 出堆積後\n", peek); + maxHeap.print(); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + System.out.format("\n堆積元素數量為 %d\n", size); + + /* 判斷堆積是否為空 */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\n堆積是否為空 %b\n", isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_heap/top_k.java b/zh-hant/codes/java/chapter_heap/top_k.java new file mode 100644 index 0000000000..96f76927ac --- /dev/null +++ b/zh-hant/codes/java/chapter_heap/top_k.java @@ -0,0 +1,40 @@ +/** + * File: top_k.java + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class top_k { + /* 基於堆積查詢陣列中最大的 k 個元素 */ + static Queue topKHeap(int[] nums, int k) { + // 初始化小頂堆積 + Queue heap = new PriorityQueue(); + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + + public static void main(String[] args) { + int[] nums = { 1, 7, 6, 3, 2 }; + int k = 3; + + Queue res = topKHeap(nums, k); + System.out.println("最大的 " + k + " 個元素為"); + PrintUtil.printHeap(res); + } +} diff --git a/zh-hant/codes/java/chapter_searching/binary_search.java b/zh-hant/codes/java/chapter_searching/binary_search.java new file mode 100644 index 0000000000..06b5c257c6 --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/binary_search.java @@ -0,0 +1,58 @@ +/** + * File: binary_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search { + /* 二分搜尋(雙閉區間) */ + static int binarySearch(int[] nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.length - 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; + } + + /* 二分搜尋(左閉右開區間) */ + static int binarySearchLCRO(int[] nums, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 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; + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + /* 二分搜尋(雙閉區間) */ + int index = binarySearch(nums, target); + System.out.println("目標元素 6 的索引 = " + index); + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target); + System.out.println("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_searching/binary_search_edge.java b/zh-hant/codes/java/chapter_searching/binary_search_edge.java new file mode 100644 index 0000000000..124e563f5c --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/binary_search_edge.java @@ -0,0 +1,49 @@ +/** + * File: binary_search_edge.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search_edge { + /* 二分搜尋最左一個 target */ + static int binarySearchLeftEdge(int[] nums, int target) { + // 等價於查詢 target 的插入點 + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } + + /* 二分搜尋最右一個 target */ + static int binarySearchRightEdge(int[] nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } + + public static void main(String[] args) { + // 包含重複元素的陣列 + int[] nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\n陣列 nums = " + java.util.Arrays.toString(nums)); + + // 二分搜尋左邊界和右邊界 + for (int target : new int[] { 6, 7 }) { + int index = binarySearchLeftEdge(nums, target); + System.out.println("最左一個元素 " + target + " 的索引為 " + index); + index = binarySearchRightEdge(nums, target); + System.out.println("最右一個元素 " + target + " 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/java/chapter_searching/binary_search_insertion.java b/zh-hant/codes/java/chapter_searching/binary_search_insertion.java new file mode 100644 index 0000000000..445ae13ced --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/binary_search_insertion.java @@ -0,0 +1,63 @@ +/** + * File: binary_search_insertion.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +class binary_search_insertion { + /* 二分搜尋插入點(無重複元素) */ + static int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.length - 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; + } + + /* 二分搜尋插入點(存在重複元素) */ + static int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.length - 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; + } + + public static void main(String[] args) { + // 無重複元素的陣列 + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + System.out.println("\n陣列 nums = " + java.util.Arrays.toString(nums)); + // 二分搜尋插入點 + for (int target : new int[] { 6, 9 }) { + int index = binarySearchInsertionSimple(nums, target); + System.out.println("元素 " + target + " 的插入點的索引為 " + index); + } + + // 包含重複元素的陣列 + nums = new int[] { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\n陣列 nums = " + java.util.Arrays.toString(nums)); + // 二分搜尋插入點 + for (int target : new int[] { 2, 6, 20 }) { + int index = binarySearchInsertion(nums, target); + System.out.println("元素 " + target + " 的插入點的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/java/chapter_searching/hashing_search.java b/zh-hant/codes/java/chapter_searching/hashing_search.java new file mode 100644 index 0000000000..6ead475c3e --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/hashing_search.java @@ -0,0 +1,51 @@ +/** + * File: hashing_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; +import java.util.*; + +public class hashing_search { + /* 雜湊查詢(陣列) */ + static int hashingSearchArray(Map map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.getOrDefault(target, -1); + } + + /* 雜湊查詢(鏈結串列) */ + static ListNode hashingSearchLinkedList(Map map, int target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.getOrDefault(target, null); + } + + public static void main(String[] args) { + int target = 3; + + /* 雜湊查詢(陣列) */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // 初始化雜湊表 + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + map.put(nums[i], i); // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + System.out.println("目標元素 3 的索引 = " + index); + + /* 雜湊查詢(鏈結串列) */ + ListNode head = ListNode.arrToLinkedList(nums); + // 初始化雜湊表 + Map map1 = new HashMap<>(); + while (head != null) { + map1.put(head.val, head); // key: 節點值,value: 節點 + head = head.next; + } + ListNode node = hashingSearchLinkedList(map1, target); + System.out.println("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/java/chapter_searching/linear_search.java b/zh-hant/codes/java/chapter_searching/linear_search.java new file mode 100644 index 0000000000..963c5aa8a2 --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/linear_search.java @@ -0,0 +1,50 @@ +/** + * File: linear_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; + +public class linear_search { + /* 線性查詢(陣列) */ + static int linearSearchArray(int[] nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目標元素,返回 -1 + return -1; + } + + /* 線性查詢(鏈結串列) */ + static ListNode linearSearchLinkedList(ListNode head, int target) { + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.val == target) + return head; + head = head.next; + } + // 未找到目標節點,返回 null + return null; + } + + public static void main(String[] args) { + int target = 3; + + /* 在陣列中執行線性查詢 */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + int index = linearSearchArray(nums, target); + System.out.println("目標元素 3 的索引 = " + index); + + /* 在鏈結串列中執行線性查詢 */ + ListNode head = ListNode.arrToLinkedList(nums); + ListNode node = linearSearchLinkedList(head, target); + System.out.println("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/java/chapter_searching/two_sum.java b/zh-hant/codes/java/chapter_searching/two_sum.java new file mode 100644 index 0000000000..78860ccc54 --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/two_sum.java @@ -0,0 +1,53 @@ +/** + * File: two_sum.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import java.util.*; + +public class two_sum { + /* 方法一:暴力列舉 */ + static int[] twoSumBruteForce(int[] nums, int target) { + int size = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } + } + return new int[0]; + } + + /* 方法二:輔助雜湊表 */ + static int[] twoSumHashTable(int[] nums, int target) { + int size = nums.length; + // 輔助雜湊表,空間複雜度為 O(n) + Map dic = new HashMap<>(); + // 單層迴圈,時間複雜度為 O(n) + for (int i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return new int[] { dic.get(target - nums[i]), i }; + } + dic.put(nums[i], i); + } + return new int[0]; + } + + public static void main(String[] args) { + // ======= Test Case ======= + int[] nums = { 2, 7, 11, 15 }; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + int[] res = twoSumBruteForce(nums, target); + System.out.println("方法一 res = " + Arrays.toString(res)); + // 方法二 + res = twoSumHashTable(nums, target); + System.out.println("方法二 res = " + Arrays.toString(res)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/bubble_sort.java b/zh-hant/codes/java/chapter_sorting/bubble_sort.java new file mode 100644 index 0000000000..10d8990e42 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/bubble_sort.java @@ -0,0 +1,57 @@ +/** + * File: bubble_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bubble_sort { + /* 泡沫排序 */ + static void bubbleSort(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 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; + } + } + } + } + + /* 泡沫排序(標誌最佳化) */ + static void bubbleSortWithFlag(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + boolean flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [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; + flag = true; // 記錄交換元素 + } + } + if (!flag) + break; // 此輪“冒泡”未交換任何元素,直接跳出 + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + bubbleSort(nums); + System.out.println("泡沫排序完成後 nums = " + Arrays.toString(nums)); + + int[] nums1 = { 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(nums1); + System.out.println("泡沫排序完成後 nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/bucket_sort.java b/zh-hant/codes/java/chapter_sorting/bucket_sort.java new file mode 100644 index 0000000000..1e2c98fffa --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/bucket_sort.java @@ -0,0 +1,47 @@ +/** + * File: bucket_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bucket_sort { + /* 桶排序 */ + static void bucketSort(float[] nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.length / 2; + List> buckets = new ArrayList<>(); + for (int i = 0; i < k; i++) { + buckets.add(new ArrayList<>()); + } + // 1. 將陣列元素分配到各個桶中 + for (float num : nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + int i = (int) (num * k); + // 將 num 新增進桶 i + buckets.get(i).add(num); + } + // 2. 對各個桶執行排序 + for (List bucket : buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + Collections.sort(bucket); + } + // 3. 走訪桶合併結果 + int i = 0; + for (List bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } + } + + public static void main(String[] args) { + // 設輸入資料為浮點數,範圍為 [0, 1) + float[] nums = { 0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f }; + bucketSort(nums); + System.out.println("桶排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/counting_sort.java b/zh-hant/codes/java/chapter_sorting/counting_sort.java new file mode 100644 index 0000000000..f4267328b1 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/counting_sort.java @@ -0,0 +1,78 @@ +/** + * File: counting_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class counting_sort { + /* 計數排序 */ + // 簡單實現,無法用於排序物件 + static void countingSortNaive(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 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; + } + } + } + + /* 計數排序 */ + // 完整實現,可排序物件,並且是穩定排序 + static void countingSort(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + public static void main(String[] args) { + int[] nums = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSortNaive(nums); + System.out.println("計數排序(無法排序物件)完成後 nums = " + Arrays.toString(nums)); + + int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSort(nums1); + System.out.println("計數排序完成後 nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/heap_sort.java b/zh-hant/codes/java/chapter_sorting/heap_sort.java new file mode 100644 index 0000000000..c3af943dfd --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/heap_sort.java @@ -0,0 +1,57 @@ +/** + * File: heap_sort.java + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class heap_sort { + /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ + public static void siftDown(int[] nums, int n, int i) { + while (true) { + // 判斷節點 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; + } + } + + /* 堆積排序 */ + public static void heapSort(int[] nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.length / 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + heapSort(nums); + System.out.println("堆積排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/insertion_sort.java b/zh-hant/codes/java/chapter_sorting/insertion_sort.java new file mode 100644 index 0000000000..b107215e8f --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/insertion_sort.java @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class insertion_sort { + /* 插入排序 */ + static void insertionSort(int[] nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + insertionSort(nums); + System.out.println("插入排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/merge_sort.java b/zh-hant/codes/java/chapter_sorting/merge_sort.java new file mode 100644 index 0000000000..df89a4830c --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/merge_sort.java @@ -0,0 +1,58 @@ +/** + * File: merge_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class merge_sort { + /* 合併左子陣列和右子陣列 */ + static void merge(int[] nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + int[] tmp = new int[right - left + 1]; + // 初始化左子陣列和右子陣列的起始索引 + 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 < tmp.length; k++) { + nums[left + k] = tmp[k]; + } + } + + /* 合併排序 */ + static 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); + } + + public static void main(String[] args) { + /* 合併排序 */ + int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; + mergeSort(nums, 0, nums.length - 1); + System.out.println("合併排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/quick_sort.java b/zh-hant/codes/java/chapter_sorting/quick_sort.java new file mode 100644 index 0000000000..c4d68a9503 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/quick_sort.java @@ -0,0 +1,158 @@ +/** + * File: quick_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static 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; // 返回基準數的索引 + } + + /* 快速排序 */ + public static 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); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + static 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; + } + + /* 哨兵劃分(三數取中值) */ + static int partition(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; // 返回基準數的索引 + } + + /* 快速排序 */ + public static 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); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static 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; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + public static void quickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +public class quick_sort { + public static void main(String[] args) { + /* 快速排序 */ + int[] nums = { 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(nums, 0, nums.length - 1); + System.out.println("快速排序完成後 nums = " + Arrays.toString(nums)); + + /* 快速排序(中位基準數最佳化) */ + int[] nums1 = { 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + System.out.println("快速排序(中位基準數最佳化)完成後 nums1 = " + Arrays.toString(nums1)); + + /* 快速排序(尾遞迴最佳化) */ + int[] nums2 = { 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + System.out.println("快速排序(尾遞迴最佳化)完成後 nums2 = " + Arrays.toString(nums2)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/radix_sort.java b/zh-hant/codes/java/chapter_sorting/radix_sort.java new file mode 100644 index 0000000000..dbdeeb58de --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/radix_sort.java @@ -0,0 +1,69 @@ +/** + * File: radix_sort.java + * Created Time: 2023-01-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class radix_sort { + /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + static int digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10; + } + + /* 計數排序(根據 nums 第 k 位排序) */ + static void countingSortDigit(int[] nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + int[] counter = new int[10]; + int n = nums.length; + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + int[] res = new int[n]; + for (int i = n - 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 < n; i++) + nums[i] = res[i]; + } + + /* 基數排序 */ + static void radixSort(int[] nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + int m = Integer.MIN_VALUE; + for (int num : nums) + if (num > m) + m = num; + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } + } + + public static void main(String[] args) { + // 基數排序 + int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 }; + radixSort(nums); + System.out.println("基數排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/selection_sort.java b/zh-hant/codes/java/chapter_sorting/selection_sort.java new file mode 100644 index 0000000000..47c14391b8 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/selection_sort.java @@ -0,0 +1,35 @@ +/** + * File: selection_sort.java + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class selection_sort { + /* 選擇排序 */ + public static void selectionSort(int[] nums) { + int n = nums.length; + // 外迴圈:未排序區間為 [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; + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + selectionSort(nums); + System.out.println("選擇排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/array_deque.java b/zh-hant/codes/java/chapter_stack_and_queue/array_deque.java new file mode 100644 index 0000000000..cf8c4de955 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/array_deque.java @@ -0,0 +1,151 @@ +/** + * File: array_deque.java + * Created Time: 2023-02-16 + * Author: krahets (krahets@163.com), FangYuan33 (374072213@qq.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private int[] nums; // 用於儲存雙向佇列元素的陣列 + private int front; // 佇列首指標,指向佇列首元素 + private int queSize; // 雙向佇列長度 + + /* 建構子 */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + public int capacity() { + return nums.length; + } + + /* 獲取雙向佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* 計算環形陣列索引 */ + private int index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 佇列首入列 */ + public void pushFirst(int num) { + if (queSize == capacity()) { + System.out.println("雙向佇列已滿"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = index(front - 1); + // 將 num 新增至佇列首 + nums[front] = num; + queSize++; + } + + /* 佇列尾入列 */ + public void pushLast(int num) { + if (queSize == capacity()) { + System.out.println("雙向佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = index(front + queSize); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 佇列首出列 */ + public int popFirst() { + int num = peekFirst(); + // 佇列首指標向後移動一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 佇列尾出列 */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* 訪問佇列尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 計算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回陣列用於列印 */ + public int[] toArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +} + +public class array_deque { + public static void main(String[] args) { + /* 初始化雙向佇列 */ + ArrayDeque deque = new ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("雙向佇列 deque = " + Arrays.toString(deque.toArray())); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.pushLast(4); + System.out.println("元素 4 佇列尾入列後 deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("元素 1 佇列首入列後 deque = " + Arrays.toString(deque.toArray())); + + /* 元素出列 */ + int popLast = deque.popLast(); + System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + Arrays.toString(deque.toArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + System.out.println("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/array_queue.java b/zh-hant/codes/java/chapter_stack_and_queue/array_queue.java new file mode 100644 index 0000000000..3fc6e44834 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/array_queue.java @@ -0,0 +1,115 @@ +/** + * File: array_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private int[] nums; // 用於儲存佇列元素的陣列 + private int front; // 佇列首指標,指向佇列首元素 + private int queSize; // 佇列長度 + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取佇列的容量 */ + public int capacity() { + return nums.length; + } + + /* 獲取佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* 入列 */ + public void push(int num) { + if (queSize == capacity()) { + System.out.println("佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (front + queSize) % capacity(); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 出列 */ + public int pop() { + int num = peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % capacity(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* 返回陣列 */ + public int[] toArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % capacity()]; + } + return res; + } +} + +public class array_queue { + public static void main(String[] args) { + /* 初始化佇列 */ + int capacity = 10; + ArrayQueue queue = new ArrayQueue(capacity); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("佇列 queue = " + Arrays.toString(queue.toArray())); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + System.out.println("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.pop(); + System.out.println("出列元素 pop = " + pop + ",出列後 queue = " + Arrays.toString(queue.toArray())); + + /* 獲取佇列的長度 */ + int size = queue.size(); + System.out.println("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + System.out.println("佇列是否為空 = " + isEmpty); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + System.out.println("第 " + i + " 輪入列 + 出列後 queue = " + Arrays.toString(queue.toArray())); + } + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/array_stack.java b/zh-hant/codes/java/chapter_stack_and_queue/array_stack.java new file mode 100644 index 0000000000..44e80f597a --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/array_stack.java @@ -0,0 +1,84 @@ +/** + * File: array_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private ArrayList stack; + + public ArrayStack() { + // 初始化串列(動態陣列) + stack = new ArrayList<>(); + } + + /* 獲取堆疊的長度 */ + public int size() { + return stack.size(); + } + + /* 判斷堆疊是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入堆疊 */ + public void push(int num) { + stack.add(num); + } + + /* 出堆疊 */ + public int pop() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.remove(size() - 1); + } + + /* 訪問堆疊頂元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.get(size() - 1); + } + + /* 將 List 轉化為 Array 並返回 */ + public Object[] toArray() { + return stack.toArray(); + } +} + +public class array_stack { + public static void main(String[] args) { + /* 初始化堆疊 */ + ArrayStack stack = new ArrayStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("堆疊 stack = " + Arrays.toString(stack.toArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + System.out.println("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.pop(); + System.out.println("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + Arrays.toString(stack.toArray())); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + System.out.println("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + System.out.println("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/deque.java b/zh-hant/codes/java/chapter_stack_and_queue/deque.java new file mode 100644 index 0000000000..23b4b35c42 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/deque.java @@ -0,0 +1,46 @@ +/** + * File: deque.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class deque { + public static void main(String[] args) { + /* 初始化雙向佇列 */ + Deque deque = new LinkedList<>(); + deque.offerLast(3); + deque.offerLast(2); + deque.offerLast(5); + System.out.println("雙向佇列 deque = " + deque); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.offerLast(4); + System.out.println("元素 4 佇列尾入列後 deque = " + deque); + deque.offerFirst(1); + System.out.println("元素 1 佇列首入列後 deque = " + deque); + + /* 元素出列 */ + int popLast = deque.pollLast(); + System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + deque); + int popFirst = deque.pollFirst(); + System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + deque); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + System.out.println("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_deque.java b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_deque.java new file mode 100644 index 0000000000..c3168ae330 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -0,0 +1,175 @@ +/** + * File: linkedlist_deque.java + * Created Time: 2023-01-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 雙向鏈結串列節點 */ +class ListNode { + int val; // 節點值 + ListNode next; // 後繼節點引用 + ListNode prev; // 前驅節點引用 + + ListNode(int val) { + this.val = val; + prev = next = null; + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private ListNode front, rear; // 頭節點 front ,尾節點 rear + private int queSize = 0; // 雙向佇列的長度 + + public LinkedListDeque() { + front = rear = null; + } + + /* 獲取雙向佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入列操作 */ + private void push(int num, boolean isFront) { + ListNode node = new ListNode(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front.prev = node; + node.next = front; + front = node; // 更新頭節點 + // 佇列尾入列操作 + } else { + // 將 node 新增至鏈結串列尾部 + rear.next = node; + node.prev = rear; + rear = node; // 更新尾節點 + } + queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + public void pushFirst(int num) { + push(num, true); + } + + /* 佇列尾入列 */ + public void pushLast(int num) { + push(num, false); + } + + /* 出列操作 */ + private int pop(boolean isFront) { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + int val; + // 佇列首出列操作 + if (isFront) { + val = front.val; // 暫存頭節點值 + // 刪除頭節點 + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + front = fNext; // 更新頭節點 + // 佇列尾出列操作 + } else { + val = rear.val; // 暫存尾節點值 + // 刪除尾節點 + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + rear = rPrev; // 更新尾節點 + } + queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + public int popFirst() { + return pop(true); + } + + /* 佇列尾出列 */ + public int popLast() { + return pop(false); + } + + /* 訪問佇列首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* 訪問佇列尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return rear.val; + } + + /* 返回陣列用於列印 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_deque { + public static void main(String[] args) { + /* 初始化雙向佇列 */ + LinkedListDeque deque = new LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("雙向佇列 deque = " + Arrays.toString(deque.toArray())); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.pushLast(4); + System.out.println("元素 4 佇列尾入列後 deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("元素 1 佇列首入列後 deque = " + Arrays.toString(deque.toArray())); + + /* 元素出列 */ + int popLast = deque.popLast(); + System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + Arrays.toString(deque.toArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + System.out.println("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_queue.java new file mode 100644 index 0000000000..994d9446e7 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -0,0 +1,104 @@ +/** + * File: linkedlist_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private ListNode front, rear; // 頭節點 front ,尾節點 rear + private int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* 獲取佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入列 */ + public void push(int num) { + // 在尾節點後新增 num + ListNode node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == null) { + front = node; + rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出列 */ + public int pop() { + int num = peek(); + // 刪除頭節點 + front = front.next; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + public static void main(String[] args) { + /* 初始化佇列 */ + LinkedListQueue queue = new LinkedListQueue(); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("佇列 queue = " + Arrays.toString(queue.toArray())); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + System.out.println("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.pop(); + System.out.println("出列元素 pop = " + pop + ",出列後 queue = " + Arrays.toString(queue.toArray())); + + /* 獲取佇列的長度 */ + int size = queue.size(); + System.out.println("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + System.out.println("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_stack.java new file mode 100644 index 0000000000..0730e606cf --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -0,0 +1,95 @@ +/** + * File: linkedlist_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; +import utils.*; + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private ListNode stackPeek; // 將頭節點作為堆疊頂 + private int stkSize = 0; // 堆疊的長度 + + public LinkedListStack() { + stackPeek = null; + } + + /* 獲取堆疊的長度 */ + public int size() { + return stkSize; + } + + /* 判斷堆疊是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入堆疊 */ + public void push(int num) { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* 出堆疊 */ + public int pop() { + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stackPeek.val; + } + + /* 將 List 轉化為 Array 並返回 */ + public int[] toArray() { + ListNode node = stackPeek; + int[] res = new int[size()]; + for (int i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + public static void main(String[] args) { + /* 初始化堆疊 */ + LinkedListStack stack = new LinkedListStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("堆疊 stack = " + Arrays.toString(stack.toArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + System.out.println("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.pop(); + System.out.println("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + Arrays.toString(stack.toArray())); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + System.out.println("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + System.out.println("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/queue.java b/zh-hant/codes/java/chapter_stack_and_queue/queue.java new file mode 100644 index 0000000000..27670cbc1c --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/queue.java @@ -0,0 +1,40 @@ +/** + * File: queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class queue { + public static void main(String[] args) { + /* 初始化佇列 */ + Queue queue = new LinkedList<>(); + + /* 元素入列 */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + System.out.println("佇列 queue = " + queue); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + System.out.println("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.poll(); + System.out.println("出列元素 pop = " + pop + ",出列後 queue = " + queue); + + /* 獲取佇列的長度 */ + int size = queue.size(); + System.out.println("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + System.out.println("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/stack.java b/zh-hant/codes/java/chapter_stack_and_queue/stack.java new file mode 100644 index 0000000000..212a51b856 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/stack.java @@ -0,0 +1,40 @@ +/** + * File: stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class stack { + public static void main(String[] args) { + /* 初始化堆疊 */ + Stack stack = new Stack<>(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("堆疊 stack = " + stack); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + System.out.println("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.pop(); + System.out.println("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + stack); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + System.out.println("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + System.out.println("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_tree/array_binary_tree.java b/zh-hant/codes/java/chapter_tree/array_binary_tree.java new file mode 100644 index 0000000000..34278bfbc4 --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/array_binary_tree.java @@ -0,0 +1,136 @@ +/** + * File: array_binary_tree.java + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + private List tree; + + /* 建構子 */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } + + /* 串列容量 */ + public int size() { + return tree.size(); + } + + /* 獲取索引為 i 節點的值 */ + public Integer val(int i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + public Integer left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + public Integer right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + public Integer parent(int i) { + return (i - 1) / 2; + } + + /* 層序走訪 */ + public List levelOrder() { + List res = new ArrayList<>(); + // 直接走訪陣列 + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + return res; + } + + /* 深度優先走訪 */ + private void dfs(Integer i, String order, List res) { + // 若為空位,則返回 + if (val(i) == null) + return; + // 前序走訪 + if ("pre".equals(order)) + res.add(val(i)); + dfs(left(i), order, res); + // 中序走訪 + if ("in".equals(order)) + res.add(val(i)); + dfs(right(i), order, res); + // 後序走訪 + if ("post".equals(order)) + res.add(val(i)); + } + + /* 前序走訪 */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } + + /* 中序走訪 */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } + + /* 後序走訪 */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } +} + +public class array_binary_tree { + public static void main(String[] args) { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + List arr = Arrays.asList(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15); + + TreeNode root = TreeNode.listToTree(arr); + System.out.println("\n初始化二元樹\n"); + System.out.println("二元樹的陣列表示:"); + System.out.println(arr); + System.out.println("二元樹的鏈結串列表示:"); + PrintUtil.printTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt = new ArrayBinaryTree(arr); + + // 訪問節點 + int i = 1; + Integer l = abt.left(i); + Integer r = abt.right(i); + Integer p = abt.parent(i); + System.out.println("\n當前節點的索引為 " + i + " ,值為 " + abt.val(i)); + System.out.println("其左子節點的索引為 " + l + " ,值為 " + (l == null ? "null" : abt.val(l))); + System.out.println("其右子節點的索引為 " + r + " ,值為 " + (r == null ? "null" : abt.val(r))); + System.out.println("其父節點的索引為 " + p + " ,值為 " + (p == null ? "null" : abt.val(p))); + + // 走訪樹 + List res = abt.levelOrder(); + System.out.println("\n層序走訪為:" + res); + res = abt.preOrder(); + System.out.println("前序走訪為:" + res); + res = abt.inOrder(); + System.out.println("中序走訪為:" + res); + res = abt.postOrder(); + System.out.println("後序走訪為:" + res); + } +} diff --git a/zh-hant/codes/java/chapter_tree/avl_tree.java b/zh-hant/codes/java/chapter_tree/avl_tree.java new file mode 100644 index 0000000000..f6124530ce --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/avl_tree.java @@ -0,0 +1,220 @@ +/** + * File: avl_tree.java + * Created Time: 2022-12-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* AVL 樹 */ +class AVLTree { + TreeNode root; // 根節點 + + /* 獲取節點高度 */ + public int height(TreeNode node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == null ? -1 : node.height; + } + + /* 更新節點高度 */ + private void updateHeight(TreeNode node) { + // 節點高度等於最高子樹高度 + 1 + node.height = Math.max(height(node.left), height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + public int balanceFactor(TreeNode node) { + // 空節點平衡因子為 0 + if (node == null) + return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node.left) - height(node.right); + } + + /* 右旋操作 */ + private TreeNode rightRotate(TreeNode node) { + TreeNode child = node.left; + TreeNode grandChild = child.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + private TreeNode leftRotate(TreeNode node) { + TreeNode child = node.right; + TreeNode grandChild = child.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private TreeNode rotate(TreeNode node) { + // 獲取節點 node 的平衡因子 + int balanceFactor = balanceFactor(node); + // 左偏樹 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + public void insert(int val) { + root = insertHelper(root, val); + } + + /* 遞迴插入節點(輔助方法) */ + private TreeNode insertHelper(TreeNode node, int val) { + if (node == null) + return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重複節點不插入,直接返回 + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + public void remove(int val) { + root = removeHelper(root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + private TreeNode removeHelper(TreeNode node, int val) { + if (node == null) + return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode child = node.left != null ? node.left : node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) + return null; + // 子節點數量 = 1 ,直接刪除 node + else + node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + public TreeNode search(int val) { + TreeNode cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < val) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > val) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } +} + +public class avl_tree { + static void testInsert(AVLTree tree, int val) { + tree.insert(val); + System.out.println("\n插入節點 " + val + " 後,AVL 樹為"); + PrintUtil.printTree(tree.root); + } + + static void testRemove(AVLTree tree, int val) { + tree.remove(val); + System.out.println("\n刪除節點 " + val + " 後,AVL 樹為"); + PrintUtil.printTree(tree.root); + } + + public static void main(String[] args) { + /* 初始化空 AVL 樹 */ + AVLTree avlTree = new AVLTree(); + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重複節點 */ + testInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8); // 刪除度為 0 的節點 + testRemove(avlTree, 5); // 刪除度為 1 的節點 + testRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode node = avlTree.search(7); + System.out.println("\n查詢到的節點物件為 " + node + ",節點值 = " + node.val); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_search_tree.java b/zh-hant/codes/java/chapter_tree/binary_search_tree.java new file mode 100644 index 0000000000..00a9065406 --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_search_tree.java @@ -0,0 +1,158 @@ +/** + * File: binary_search_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* 二元搜尋樹 */ +class BinarySearchTree { + private TreeNode root; + + /* 建構子 */ + public BinarySearchTree() { + // 初始化空樹 + root = null; + } + + /* 獲取二元樹根節點 */ + public TreeNode getRoot() { + return root; + } + + /* 查詢節點 */ + public TreeNode search(int num) { + TreeNode cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + public void insert(int num) { + // 若樹為空,則初始化根節點 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子樹中 + else + cur = cur.left; + } + // 插入節點 + TreeNode node = new TreeNode(num); + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + + /* 刪除節點 */ + public void remove(int num) { + // 若樹為空,直接提前返回 + if (root == null) + return; + TreeNode cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val == num) + break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur == null) + return; + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + TreeNode child = cur.left != null ? cur.left : cur.right; + // 刪除節點 cur + if (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + TreeNode tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + remove(tmp.val); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + public static void main(String[] args) { + /* 初始化二元搜尋樹 */ + BinarySearchTree bst = new BinarySearchTree(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + int[] nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }; + for (int num : nums) { + bst.insert(num); + } + System.out.println("\n初始化的二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + + /* 查詢節點 */ + TreeNode node = bst.search(7); + System.out.println("\n查詢到的節點物件為 " + node + ",節點值 = " + node.val); + + /* 插入節點 */ + bst.insert(16); + System.out.println("\n插入節點 16 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + + /* 刪除節點 */ + bst.remove(1); + System.out.println("\n刪除節點 1 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(2); + System.out.println("\n刪除節點 2 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(4); + System.out.println("\n刪除節點 4 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_tree.java b/zh-hant/codes/java/chapter_tree/binary_tree.java new file mode 100644 index 0000000000..81b8946dc6 --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_tree.java @@ -0,0 +1,40 @@ +/** + * File: binary_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +public class binary_tree { + public static void main(String[] args) { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + System.out.println("\n初始化二元樹\n"); + PrintUtil.printTree(n1); + + /* 插入與刪除節點 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + System.out.println("\n插入節點 P 後\n"); + PrintUtil.printTree(n1); + // 刪除節點 P + n1.left = n2; + System.out.println("\n刪除節點 P 後\n"); + PrintUtil.printTree(n1); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_tree_bfs.java b/zh-hant/codes/java/chapter_tree/binary_tree_bfs.java new file mode 100644 index 0000000000..1c2711f57e --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_tree_bfs.java @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_bfs { + /* 層序走訪 */ + static List levelOrder(TreeNode root) { + // 初始化佇列,加入根節點 + Queue queue = new LinkedList<>(); + queue.add(root); + // 初始化一個串列,用於儲存走訪序列 + List list = new ArrayList<>(); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); // 隊列出隊 + list.add(node.val); // 儲存節點值 + if (node.left != null) + queue.offer(node.left); // 左子節點入列 + if (node.right != null) + queue.offer(node.right); // 右子節點入列 + } + return list; + } + + public static void main(String[] args) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹\n"); + PrintUtil.printTree(root); + + /* 層序走訪 */ + List list = levelOrder(root); + System.out.println("\n層序走訪的節點列印序列 = " + list); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_tree_dfs.java b/zh-hant/codes/java/chapter_tree/binary_tree_dfs.java new file mode 100644 index 0000000000..e4dfdc3f58 --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_tree_dfs.java @@ -0,0 +1,68 @@ +/** + * File: binary_tree_dfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_dfs { + // 初始化串列,用於儲存走訪序列 + static ArrayList list = new ArrayList<>(); + + /* 前序走訪 */ + static void preOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.add(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* 中序走訪 */ + static void inOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left); + list.add(root.val); + inOrder(root.right); + } + + /* 後序走訪 */ + static void postOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left); + postOrder(root.right); + list.add(root.val); + } + + public static void main(String[] args) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹\n"); + PrintUtil.printTree(root); + + /* 前序走訪 */ + list.clear(); + preOrder(root); + System.out.println("\n前序走訪的節點列印序列 = " + list); + + /* 中序走訪 */ + list.clear(); + inOrder(root); + System.out.println("\n中序走訪的節點列印序列 = " + list); + + /* 後序走訪 */ + list.clear(); + postOrder(root); + System.out.println("\n後序走訪的節點列印序列 = " + list); + } +} diff --git a/zh-hant/codes/java/utils/ListNode.java b/zh-hant/codes/java/utils/ListNode.java new file mode 100755 index 0000000000..e1ce136319 --- /dev/null +++ b/zh-hant/codes/java/utils/ListNode.java @@ -0,0 +1,28 @@ +/** + * File: ListNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +/* 鏈結串列節點 */ +public class ListNode { + public int val; + public ListNode next; + + public ListNode(int x) { + val = x; + } + + /* 將串列反序列化為鏈結串列 */ + public static ListNode arrToLinkedList(int[] arr) { + ListNode dum = new ListNode(0); + ListNode head = dum; + for (int val : arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } +} diff --git a/zh-hant/codes/java/utils/PrintUtil.java b/zh-hant/codes/java/utils/PrintUtil.java new file mode 100755 index 0000000000..490cd3c163 --- /dev/null +++ b/zh-hant/codes/java/utils/PrintUtil.java @@ -0,0 +1,116 @@ +/** + * File: PrintUtil.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +class Trunk { + Trunk prev; + String str; + + Trunk(Trunk prev, String str) { + this.prev = prev; + this.str = str; + } +}; + +public class PrintUtil { + /* 列印矩陣(Array) */ + public static void printMatrix(T[][] matrix) { + System.out.println("["); + for (T[] row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 列印矩陣(List) */ + public static void printMatrix(List> matrix) { + System.out.println("["); + for (List row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 列印鏈結串列 */ + public static void printLinkedList(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + list.add(String.valueOf(head.val)); + head = head.next; + } + System.out.println(String.join(" -> ", list)); + } + + /* 列印二元樹 */ + public static void printTree(TreeNode root) { + printTree(root, null, false); + } + + /** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void printTree(TreeNode root, Trunk prev, boolean isRight) { + if (root == null) { + return; + } + + String prev_str = " "; + Trunk trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + showTrunks(trunk); + System.out.println(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + printTree(root.left, trunk, false); + } + + public static void showTrunks(Trunk p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + System.out.print(p.str); + } + + /* 列印雜湊表 */ + public static void printHashMap(Map map) { + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + } + + /* 列印堆積(優先佇列) */ + public static void printHeap(Queue queue) { + List list = new ArrayList<>(queue); + System.out.print("堆積的陣列表示:"); + System.out.println(list); + System.out.println("堆積的樹狀表示:"); + TreeNode root = TreeNode.listToTree(list); + printTree(root); + } +} diff --git a/zh-hant/codes/java/utils/TreeNode.java b/zh-hant/codes/java/utils/TreeNode.java new file mode 100644 index 0000000000..b3495d4571 --- /dev/null +++ b/zh-hant/codes/java/utils/TreeNode.java @@ -0,0 +1,73 @@ +/** + * File: TreeNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 二元樹節點類別 */ +public class TreeNode { + public int val; // 節點值 + public int height; // 節點高度 + public TreeNode left; // 左子節點引用 + public TreeNode right; // 右子節點引用 + + /* 建構子 */ + public TreeNode(int x) { + val = x; + } + + // 序列化編碼規則請參考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二元樹的陣列表示: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二元樹的鏈結串列表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 將串列反序列化為二元樹:遞迴 */ + private static TreeNode listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.size() || arr.get(i) == null) { + return null; + } + TreeNode root = new TreeNode(arr.get(i)); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; + } + + /* 將串列反序列化為二元樹 */ + public static TreeNode listToTree(List arr) { + return listToTreeDFS(arr, 0); + } + + /* 將二元樹序列化為串列:遞迴 */ + private static void treeToListDFS(TreeNode root, int i, List res) { + if (root == null) + return; + while (i >= res.size()) { + res.add(null); + } + res.set(i, root.val); + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); + } + + /* 將二元樹序列化為串列 */ + public static List treeToList(TreeNode root) { + List res = new ArrayList<>(); + treeToListDFS(root, 0, res); + return res; + } +} diff --git a/zh-hant/codes/java/utils/Vertex.java b/zh-hant/codes/java/utils/Vertex.java new file mode 100644 index 0000000000..5de8bf3c8e --- /dev/null +++ b/zh-hant/codes/java/utils/Vertex.java @@ -0,0 +1,36 @@ +/** + * File: Vertex.java + * Created Time: 2023-02-15 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 頂點類別 */ +public class Vertex { + public int val; + + public Vertex(int val) { + this.val = val; + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static Vertex[] valsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.length]; + for (int i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static List vetsToVals(List vets) { + List vals = new ArrayList<>(); + for (Vertex vet : vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/zh-hant/codes/javascript/.prettierrc b/zh-hant/codes/javascript/.prettierrc new file mode 100644 index 0000000000..3f4aa8cb65 --- /dev/null +++ b/zh-hant/codes/javascript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/array.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/array.js new file mode 100644 index 0000000000..d54e06d7e4 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/array.js @@ -0,0 +1,97 @@ +/** + * File: array.js + * Created Time: 2022-11-27 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 隨機訪問元素 */ +function randomAccess(nums) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + const random_index = Math.floor(Math.random() * nums.length); + // 獲取並返回隨機元素 + const random_num = nums[random_index]; + return random_num; +} + +/* 擴展陣列長度 */ +// 請注意,JavaScript 的 Array 是動態陣列,可以直接擴展 +// 為了方便學習,本函式將 Array 看作長度不可變的陣列 +function extend(nums, enlarge) { + // 初始化一個擴展長度後的陣列 + const res = new Array(nums.length + enlarge).fill(0); + // 將原陣列中的所有元素複製到新陣列 + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 num */ +function insert(nums, num, index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +function remove(nums, index) { + // 把索引 index 之後的所有元素向前移動一位 + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +function traverse(nums) { + let count = 0; + // 透過索引走訪陣列 + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (const num of nums) { + count += num; + } +} + +/* 在陣列中查詢指定元素 */ +function find(nums, target) { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) return i; + } + return -1; +} + +/* Driver Code */ +/* 初始化陣列 */ +const arr = new Array(5).fill(0); +console.log('陣列 arr =', arr); +let nums = [1, 3, 2, 5, 4]; +console.log('陣列 nums =', nums); + +/* 隨機訪問 */ +let random_num = randomAccess(nums); +console.log('在 nums 中獲取隨機元素', random_num); + +/* 長度擴展 */ +nums = extend(nums, 3); +console.log('將陣列長度擴展至 8 ,得到 nums =', nums); + +/* 插入元素 */ +insert(nums, 6, 3); +console.log('在索引 3 處插入數字 6 ,得到 nums =', nums); + +/* 刪除元素 */ +remove(nums, 2); +console.log('刪除索引 2 處的元素,得到 nums =', nums); + +/* 走訪陣列 */ +traverse(nums); + +/* 查詢元素 */ +let index = find(nums, 3); +console.log('在 nums 中查詢元素 3 ,得到索引 =', index); diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/linked_list.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/linked_list.js new file mode 100644 index 0000000000..7abb68baa4 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/linked_list.js @@ -0,0 +1,82 @@ +/** + * File: linked_list.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) + */ + +const { printLinkedList } = require('../modules/PrintUtil'); +const { ListNode } = require('../modules/ListNode'); + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +function insert(n0, P) { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +function remove(n0) { + if (!n0.next) return; + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +function access(head, index) { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +function find(head, target) { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* 初始化鏈結串列 */ +// 初始化各個節點 +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// 構建節點之間的引用 +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('初始化的鏈結串列為'); +printLinkedList(n0); + +/* 插入節點 */ +insert(n0, new ListNode(0)); +console.log('插入節點後的鏈結串列為'); +printLinkedList(n0); + +/* 刪除節點 */ +remove(n0); +console.log('刪除節點後的鏈結串列為'); +printLinkedList(n0); + +/* 訪問節點 */ +const node = access(n0, 3); +console.log('鏈結串列中索引 3 處的節點的值 = ' + node.val); + +/* 查詢節點 */ +const index = find(n0, 2); +console.log('鏈結串列中值為 2 的節點的索引 = ' + index); diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/list.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/list.js new file mode 100644 index 0000000000..80f1457576 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/list.js @@ -0,0 +1,57 @@ +/** + * File: list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 初始化串列 */ +const nums = [1, 3, 2, 5, 4]; +console.log(`串列 nums = ${nums}`); + +/* 訪問元素 */ +const num = nums[1]; +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums[1] = 0; +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums}`); + +/* 清空串列 */ +nums.length = 0; +console.log(`清空串列後 nums = ${nums}`); + +/* 在尾部新增元素 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`新增元素後 nums = ${nums}`); + +/* 在中間插入元素 */ +nums.splice(3, 0, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums}`); + +/* 刪除元素 */ +nums.splice(3, 1); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums}`); + +/* 透過索引走訪串列 */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* 直接走訪串列元素 */ +count = 0; +for (const x of nums) { + count += x; +} + +/* 拼接兩個串列 */ +const nums1 = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`將串列 nums1 拼接到 nums 之後,得到 nums = ${nums}`); + +/* 排序串列 */ +nums.sort((a, b) => a - b); +console.log(`排序串列後 nums = ${nums}`); diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/my_list.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/my_list.js new file mode 100644 index 0000000000..030ba37505 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/my_list.js @@ -0,0 +1,141 @@ +/** + * File: my_list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 串列類別 */ +class MyList { + #arr = new Array(); // 陣列(儲存串列元素) + #capacity = 10; // 串列容量 + #size = 0; // 串列長度(當前元素數量) + #extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + constructor() { + this.#arr = new Array(this.#capacity); + } + + /* 獲取串列長度(當前元素數量)*/ + size() { + return this.#size; + } + + /* 獲取串列容量 */ + capacity() { + return this.#capacity; + } + + /* 訪問元素 */ + get(index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + return this.#arr[index]; + } + + /* 更新元素 */ + set(index, num) { + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + this.#arr[index] = num; + } + + /* 在尾部新增元素 */ + add(num) { + // 如果長度等於容量,則需要擴容 + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // 將新元素新增到串列尾部 + this.#arr[this.#size] = num; + this.#size++; + } + + /* 在中間插入元素 */ + insert(index, num) { + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + // 元素數量超出容量時,觸發擴容機制 + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // 將索引 index 以及之後的元素都向後移動一位 + for (let j = this.#size - 1; j >= index; j--) { + this.#arr[j + 1] = this.#arr[j]; + } + // 更新元素數量 + this.#arr[index] = num; + this.#size++; + } + + /* 刪除元素 */ + remove(index) { + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + let num = this.#arr[index]; + // 將索引 index 之後的元素都向前移動一位 + for (let j = index; j < this.#size - 1; j++) { + this.#arr[j] = this.#arr[j + 1]; + } + // 更新元素數量 + this.#size--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + this.#arr = this.#arr.concat( + new Array(this.capacity() * (this.#extendRatio - 1)) + ); + // 更新串列容量 + this.#capacity = this.#arr.length; + } + + /* 將串列轉換為陣列 */ + toArray() { + let size = this.size(); + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* 初始化串列 */ +const nums = new MyList(); +/* 在尾部新增元素 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); + +/* 在中間插入元素 */ +nums.insert(3, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}`); + +/* 刪除元素 */ +nums.remove(3); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums.toArray()}`); + +/* 訪問元素 */ +const num = nums.get(1); +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums.set(1, 0); +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}`); + +/* 測試擴容機制 */ +for (let i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); +} +console.log( + `擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); diff --git a/zh-hant/codes/javascript/chapter_backtracking/n_queens.js b/zh-hant/codes/javascript/chapter_backtracking/n_queens.js new file mode 100644 index 0000000000..90b089668b --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/n_queens.js @@ -0,0 +1,55 @@ +/** + * File: n_queens.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +function backtrack(row, n, state, res, cols, diags1, diags2) { + // 當放置完所有行時,記錄解 + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // 走訪所有列 + for (let col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + const diag1 = row - col + n - 1; + const diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +function nQueens(n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 記錄列是否有皇后 + const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后 + const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后 + const res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`輸入棋盤長寬為 ${n}`); +console.log(`皇后放置方案共有 ${res.length} 種`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/permutations_i.js b/zh-hant/codes/javascript/chapter_backtracking/permutations_i.js new file mode 100644 index 0000000000..82826c5770 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/permutations_i.js @@ -0,0 +1,42 @@ +/** + * File: permutations_i.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +function backtrack(state, choices, selected, res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 I */ +function permutationsI(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 3]; +const res = permutationsI(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_backtracking/permutations_ii.js b/zh-hant/codes/javascript/chapter_backtracking/permutations_ii.js new file mode 100644 index 0000000000..9e60e7a94e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/permutations_ii.js @@ -0,0 +1,44 @@ +/** + * File: permutations_ii.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 II */ +function backtrack(state, choices, selected, res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.has(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 II */ +function permutationsII(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 2]; +const res = permutationsII(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js new file mode 100644 index 0000000000..4b1e6f061d --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js @@ -0,0 +1,33 @@ +/** + * File: preorder_traversal_i_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序走訪:例題一 */ +function preOrder(root, res) { + if (root === null) { + return; + } + if (root.val === 7) { + // 記錄解 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const res = []; +preOrder(root, res); + +console.log('\n輸出所有值為 7 的節點'); +console.log(res.map((node) => node.val)); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js new file mode 100644 index 0000000000..c8b7bf72ff --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js @@ -0,0 +1,40 @@ +/** + * File: preorder_traversal_ii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序走訪:例題二 */ +function preOrder(root, path, res) { + if (root === null) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js new file mode 100644 index 0000000000..cc4ebb99c3 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js @@ -0,0 +1,41 @@ +/** + * File: preorder_traversal_iii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序走訪:例題三 */ +function preOrder(root, path, res) { + // 剪枝 + if (root === null || root.val === 3) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js new file mode 100644 index 0000000000..48a1c34ac0 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js @@ -0,0 +1,68 @@ +/** + * File: preorder_traversal_iii_template.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 判斷當前狀態是否為解 */ +function isSolution(state) { + return state && state[state.length - 1]?.val === 7; +} + +/* 記錄解 */ +function recordSolution(state, res) { + res.push([...state]); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +function isValid(state, choice) { + return choice !== null && choice.val !== 3; +} + +/* 更新狀態 */ +function makeChoice(state, choice) { + state.push(choice); +} + +/* 恢復狀態 */ +function undoChoice(state) { + state.pop(); +} + +/* 回溯演算法:例題三 */ +function backtrack(state, choices, res) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, [choice.left, choice.right], res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 回溯演算法 +const res = []; +backtrack([], [root], res); + +console.log('\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i.js b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i.js new file mode 100644 index 0000000000..76782849a9 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i.js @@ -0,0 +1,46 @@ +/** + * File: subset_sum_i.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack(state, target, choices, start, res) { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I */ +function subsetSumI(nums, target) { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i_naive.js b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i_naive.js new file mode 100644 index 0000000000..5b4d0b2537 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i_naive.js @@ -0,0 +1,44 @@ +/** + * File: subset_sum_i_naive.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack(state, target, total, choices, res) { + // 子集和等於 target 時,記錄解 + if (total === target) { + res.push([...state]); + return; + } + // 走訪所有選擇 + for (let i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I(包含重複子集) */ +function subsetSumINaive(nums, target) { + const state = []; // 狀態(子集) + const total = 0; // 子集和 + const res = []; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); +console.log('請注意,該方法輸出的結果包含重複集合'); diff --git a/zh-hant/codes/javascript/chapter_backtracking/subset_sum_ii.js b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_ii.js new file mode 100644 index 0000000000..2823596077 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_ii.js @@ -0,0 +1,51 @@ +/** + * File: subset_sum_ii.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +function backtrack(state, target, choices, start, res) { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 II */ +function subsetSumII(nums, target) { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/iteration.js b/zh-hant/codes/javascript/chapter_computational_complexity/iteration.js new file mode 100644 index 0000000000..d43abf3e5c --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/iteration.js @@ -0,0 +1,70 @@ +/** + * File: iteration.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for 迴圈 */ +function forLoop(n) { + let res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +function whileLoop(n) { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +function whileLoopII(n) { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +function nestedForLoop(n) { + let res = ''; + // 迴圈 i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = forLoop(n); +console.log(`for 迴圈的求和結果 res = ${res}`); + +res = whileLoop(n); +console.log(`while 迴圈的求和結果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while 迴圈(兩次更新)求和結果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`雙層 for 迴圈的走訪結果 ${resStr}`); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/recursion.js b/zh-hant/codes/javascript/chapter_computational_complexity/recursion.js new file mode 100644 index 0000000000..59134cf2ae --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/recursion.js @@ -0,0 +1,69 @@ +/** + * File: recursion.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 遞迴 */ +function recur(n) { + // 終止條件 + if (n === 1) return 1; + // 遞:遞迴呼叫 + const res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +function forLoopRecur(n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + const stack = []; + let res = 0; + // 遞:遞迴呼叫 + for (let i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (stack.length) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +function tailRecur(n, res) { + // 終止條件 + if (n === 0) return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +function fib(n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = recur(n); +console.log(`遞迴函式的求和結果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`使用迭代模擬遞迴的求和結果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`尾遞迴函式的求和結果 res = ${res}`); + +res = fib(n); +console.log(`費波那契數列的第 ${n} 項為 ${res}`); + diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/space_complexity.js b/zh-hant/codes/javascript/chapter_computational_complexity/space_complexity.js new file mode 100644 index 0000000000..4c7bebba17 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/space_complexity.js @@ -0,0 +1,103 @@ +/** + * File: space_complexity.js + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +const { ListNode } = require('../modules/ListNode'); +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 函式 */ +function constFunc() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +function constant(n) { + // 常數、變數、物件佔用 O(1) 空間 + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + const c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 線性階 */ +function linear(n) { + // 長度為 n 的陣列佔用 O(n) 空間 + const nums = new Array(n); + // 長度為 n 的串列佔用 O(n) 空間 + const nodes = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 線性階(遞迴實現) */ +function linearRecur(n) { + console.log(`遞迴 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 平方階 */ +function quadratic(n) { + // 矩陣佔用 O(n^2) 空間 + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二維串列佔用 O(n^2) 空間 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 平方階(遞迴實現) */ +function quadraticRecur(n) { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`遞迴 n = ${n} 中的 nums 長度 = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +function buildTree(n) { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 常數階 +constant(n); +// 線性階 +linear(n); +linearRecur(n); +// 平方階 +quadratic(n); +quadraticRecur(n); +// 指數階 +const root = buildTree(n); +printTree(root); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/time_complexity.js b/zh-hant/codes/javascript/chapter_computational_complexity/time_complexity.js new file mode 100644 index 0000000000..fc55ec7c1b --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/time_complexity.js @@ -0,0 +1,155 @@ +/** + * File: time_complexity.js + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 常數階 */ +function constant(n) { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* 線性階 */ +function linear(n) { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* 線性階(走訪陣列) */ +function arrayTraversal(nums) { + let count = 0; + // 迴圈次數與陣列長度成正比 + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* 平方階 */ +function quadratic(n) { + let count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +function bubbleSort(nums) { + let count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +function exponential(n) { + let count = 0, + base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +function expRecur(n) { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +function logarithmic(n) { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +function logRecur(n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* 線性對數階 */ +function linearLogRecur(n) { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +function factorialRecur(n) { + if (n === 0) return 1; + let count = 0; + // 從 1 個分裂出 n 個 + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 +const n = 8; +console.log('輸入資料大小 n = ' + n); + +let count = constant(n); +console.log('常數階的操作數量 = ' + count); + +count = linear(n); +console.log('線性階的操作數量 = ' + count); +count = arrayTraversal(new Array(n)); +console.log('線性階(走訪陣列)的操作數量 = ' + count); + +count = quadratic(n); +console.log('平方階的操作數量 = ' + count); +let nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('平方階(泡沫排序)的操作數量 = ' + count); + +count = exponential(n); +console.log('指數階(迴圈實現)的操作數量 = ' + count); +count = expRecur(n); +console.log('指數階(遞迴實現)的操作數量 = ' + count); + +count = logarithmic(n); +console.log('對數階(迴圈實現)的操作數量 = ' + count); +count = logRecur(n); +console.log('對數階(遞迴實現)的操作數量 = ' + count); + +count = linearLogRecur(n); +console.log('線性對數階(遞迴實現)的操作數量 = ' + count); + +count = factorialRecur(n); +console.log('階乘階(遞迴實現)的操作數量 = ' + count); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js b/zh-hant/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js new file mode 100644 index 0000000000..114a9fe211 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js @@ -0,0 +1,43 @@ +/** + * File: worst_best_time_complexity.js + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +function randomNumbers(n) { + const nums = Array(n); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +function findOne(nums) { + for (let i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n陣列 [ 1, 2, ..., n ] 被打亂後 = [' + nums.join(', ') + ']'); + console.log('數字 1 的索引為 ' + index); +} diff --git a/zh-hant/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js b/zh-hant/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js new file mode 100644 index 0000000000..d340949f57 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js @@ -0,0 +1,39 @@ +/** + * File: binary_search_recur.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +function dfs(nums, target, i, j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + const m = i + ((j - i) >> 1); + 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; + } +} + +/* 二分搜尋 */ +function binarySearch(nums, target) { + const n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分搜尋(雙閉區間) +const index = binarySearch(nums, target); +console.log(`目標元素 6 的索引 = ${index}`); diff --git a/zh-hant/codes/javascript/chapter_divide_and_conquer/build_tree.js b/zh-hant/codes/javascript/chapter_divide_and_conquer/build_tree.js new file mode 100644 index 0000000000..e89d5ce51b --- /dev/null +++ b/zh-hant/codes/javascript/chapter_divide_and_conquer/build_tree.js @@ -0,0 +1,44 @@ +/** + * File: build_tree.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { printTree } = require('../modules/PrintUtil'); +const { TreeNode } = require('../modules/TreeNode'); + +/* 構建二元樹:分治 */ +function dfs(preorder, inorderMap, i, l, r) { + // 子樹區間為空時終止 + if (r - l < 0) return null; + // 初始化根節點 + const root = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + const m = inorderMap.get(preorder[i]); + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +function buildTree(preorder, inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前序走訪 = ' + JSON.stringify(preorder)); +console.log('中序走訪 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('構建的二元樹為:'); +printTree(root); diff --git a/zh-hant/codes/javascript/chapter_divide_and_conquer/hanota.js b/zh-hant/codes/javascript/chapter_divide_and_conquer/hanota.js new file mode 100644 index 0000000000..e30775b274 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_divide_and_conquer/hanota.js @@ -0,0 +1,52 @@ +/** + * File: hanota.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 移動一個圓盤 */ +function move(src, tar) { + // 從 src 頂部拿出一個圓盤 + const pan = src.pop(); + // 將圓盤放入 tar 頂部 + tar.push(pan); +} + +/* 求解河內塔問題 f(i) */ +function dfs(i, src, buf, tar) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i === 1) { + move(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解河內塔問題 */ +function solveHanota(A, B, C) { + const n = A.length; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +// 串列尾部是柱子頂部 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初始狀態下:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('圓盤移動完成後:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js new file mode 100644 index 0000000000..047238c858 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js @@ -0,0 +1,34 @@ +/** + * File: climbing_stairs_backtrack.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯 */ +function backtrack(choices, state, n, res) { + // 當爬到第 n 階時,方案數量加 1 + if (state === n) res.set(0, res.get(0) + 1); + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +function climbingStairsBacktrack(n) { + const choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + const state = 0; // 從第 0 階開始爬 + const res = new Map(); + res.set(0, 0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js new file mode 100644 index 0000000000..b0db6cf789 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_constraint_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +function climbingStairsConstraintDP(n) { + if (n === 1 || n === 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = Array.from(new Array(n + 1), () => new Array(3)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js new file mode 100644 index 0000000000..3b5ad9afbc --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js @@ -0,0 +1,24 @@ +/** + * File: climbing_stairs_dfs.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 搜尋 */ +function dfs(i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +function climbingStairsDFS(n) { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js new file mode 100644 index 0000000000..bbffc533a5 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_dfs_mem.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 記憶化搜尋 */ +function dfs(i, 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] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +function climbingStairsDFSMem(n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js new file mode 100644 index 0000000000..abdf106bf6 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dp.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +function climbingStairsDP(n) { + if (n === 1 || n === 2) return n; + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1).fill(-1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +function climbingStairsDPComp(n) { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); +res = climbingStairsDPComp(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js new file mode 100644 index 0000000000..0d6aca277d --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js @@ -0,0 +1,66 @@ +/** + * File: coin_change.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換:動態規劃 */ +function coinChangeDP(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 狀態轉移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +function coinChangeDPComp(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 動態規劃 +let res = coinChangeDP(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); + +// 空間最佳化後的動態規劃 +res = coinChangeDPComp(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js new file mode 100644 index 0000000000..39edcb6dff --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換 II:動態規劃 */ +function coinChangeIIDP(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +function coinChangeIIDPComp(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 動態規劃 +let res = coinChangeIIDP(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); + +// 空間最佳化後的動態規劃 +res = coinChangeIIDPComp(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js b/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js new file mode 100644 index 0000000000..fccb443a3e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js @@ -0,0 +1,135 @@ +/** + * File: edit_distance.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 編輯距離:暴力搜尋 */ +function editDistanceDFS(s, t, i, 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.min(insert, del, replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +function editDistanceDFSMem(s, t, mem, i, 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +function editDistanceDP(s, t) { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + // 狀態轉移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +function editDistanceDPComp(s, t) { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 狀態轉移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (let i = 1; i <= n; i++) { + // 狀態轉移:首列 + let leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜尋 +let res = editDistanceDFS(s, t, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 記憶化搜尋 +const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 動態規劃 +res = editDistanceDP(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 空間最佳化後的動態規劃 +res = editDistanceDPComp(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js new file mode 100644 index 0000000000..9bcd4727aa --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js @@ -0,0 +1,113 @@ +/** + * File: knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜尋 */ +function knapsackDFS(wgt, val, i, c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return Math.max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +function knapsackDFSMem(wgt, val, mem, i, c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +function knapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(n + 1) + .fill(0) + .map(() => Array(cap + 1).fill(0)); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +function knapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + // 倒序走訪 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜尋 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 動態規劃 +res = knapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 空間最佳化後的動態規劃 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js b/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js new file mode 100644 index 0000000000..7224d5a3c3 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js @@ -0,0 +1,49 @@ +/** + * File: min_cost_climbing_stairs_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬樓梯最小代價:動態規劃 */ +function minCostClimbingStairsDP(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +function minCostClimbingStairsDPComp(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log('輸入樓梯的代價串列為:', cost); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完樓梯的最低代價為:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完樓梯的最低代價為:${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js b/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js new file mode 100644 index 0000000000..bd49d9f607 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js @@ -0,0 +1,121 @@ +/** + * File: min_path_sum.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路徑和:暴力搜尋 */ +function minPathSumDFS(grid, i, j) { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路徑和:記憶化搜尋 */ +function minPathSumDFSMem(grid, mem, i, j) { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有記錄,則直接返回 + if (mem[i][j] !== -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +function minPathSumDP(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +function minPathSumDPComp(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (let i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 暴力搜尋 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 動態規劃 +res = minPathSumDP(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 空間最佳化後的動態規劃 +res = minPathSumDPComp(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js new file mode 100644 index 0000000000..f5c48c8c17 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:動態規劃 */ +function unboundedKnapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +function unboundedKnapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 動態規劃 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 空間最佳化後的動態規劃 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_graph/graph_adjacency_list.js b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_list.js new file mode 100644 index 0000000000..6b7ff8c715 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_list.js @@ -0,0 +1,141 @@ +/** + * File: graph_adjacency_list.js + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { Vertex } = require('../modules/Vertex'); + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + adjList; + + /* 建構子 */ + constructor(edges) { + this.adjList = new Map(); + // 新增所有頂點和邊 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + size() { + return this.adjList.size; + } + + /* 新增邊 */ + addEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 新增邊 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 刪除邊 */ + removeEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 刪除邊 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 新增頂點 */ + addVertex(vet) { + if (this.adjList.has(vet)) return; + // 在鄰接表中新增一個新鏈結串列 + this.adjList.set(vet, []); + } + + /* 刪除頂點 */ + removeVertex(vet) { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + this.adjList.delete(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (const set of this.adjList.values()) { + const index = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 列印鄰接表 */ + print() { + console.log('鄰接表 ='); + for (const [key, value] of this.adjList) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +if (require.main === module) { + /* Driver Code */ + /* 初始化無向圖 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初始化後,圖為'); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v0, v2 + graph.addEdge(v0, v2); + console.log('\n新增邊 1-2 後,圖為'); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v0, v1 + graph.removeEdge(v0, v1); + console.log('\n刪除邊 1-3 後,圖為'); + graph.print(); + + /* 新增頂點 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n新增頂點 6 後,圖為'); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v1 + graph.removeVertex(v1); + console.log('\n刪除頂點 3 後,圖為'); + graph.print(); +} + +module.exports = { + GraphAdjList, +}; diff --git a/zh-hant/codes/javascript/chapter_graph/graph_adjacency_matrix.js b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_matrix.js new file mode 100644 index 0000000000..eb1b5245ee --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_matrix.js @@ -0,0 +1,132 @@ +/** + * File: graph_adjacency_matrix.js + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + constructor(vertices, edges) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + for (const val of vertices) { + this.addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + size() { + return this.vertices.length; + } + + /* 新增頂點 */ + addVertex(val) { + const n = this.size(); + // 向頂點串列中新增新頂點的值 + this.vertices.push(val); + // 在鄰接矩陣中新增一行 + const newRow = []; + for (let j = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在鄰接矩陣中新增一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 刪除頂點 */ + removeVertex(index) { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在頂點串列中移除索引 index 的頂點 + this.vertices.splice(index, 1); + + // 在鄰接矩陣中刪除索引 index 的行 + this.adjMat.splice(index, 1); + // 在鄰接矩陣中刪除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + addEdge(i, j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + removeEdge(i, j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + print() { + console.log('頂點串列 = ', this.vertices); + console.log('鄰接矩陣 =', this.adjMat); + } +} + +/* Driver Code */ +/* 初始化無向圖 */ +// 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 +const vertices = [1, 3, 2, 5, 4]; +const edges = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph = new GraphAdjMat(vertices, edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 新增邊 */ +// 頂點 1, 2 的索引分別為 0, 2 +graph.addEdge(0, 2); +console.log('\n新增邊 1-2 後,圖為'); +graph.print(); + +/* 刪除邊 */ +// 頂點 1, 3 的索引分別為 0, 1 +graph.removeEdge(0, 1); +console.log('\n刪除邊 1-3 後,圖為'); +graph.print(); + +/* 新增頂點 */ +graph.addVertex(6); +console.log('\n新增頂點 6 後,圖為'); +graph.print(); + +/* 刪除頂點 */ +// 頂點 3 的索引為 1 +graph.removeVertex(1); +console.log('\n刪除頂點 3 後,圖為'); +graph.print(); diff --git a/zh-hant/codes/javascript/chapter_graph/graph_bfs.js b/zh-hant/codes/javascript/chapter_graph/graph_bfs.js new file mode 100644 index 0000000000..4e46a624f2 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_bfs.js @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { GraphAdjList } = require('./graph_adjacency_list'); +const { Vertex } = require('../modules/Vertex'); + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphBFS(graph, startVet) { + // 頂點走訪序列 + const res = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + const visited = new Set(); + visited.add(startVet); + // 佇列用於實現 BFS + const que = [startVet]; + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.length) { + const vet = que.shift(); // 佇列首頂點出隊 + res.push(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.push(adjVet); // 只入列未訪問的頂點 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 廣度優先走訪 */ +const res = graphBFS(graph, v[0]); +console.log('\n廣度優先走訪(BFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/javascript/chapter_graph/graph_dfs.js b/zh-hant/codes/javascript/chapter_graph/graph_dfs.js new file mode 100644 index 0000000000..1d5ea527cd --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_dfs.js @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { Vertex } = require('../modules/Vertex'); +const { GraphAdjList } = require('./graph_adjacency_list'); + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function dfs(graph, visited, res, vet) { + res.push(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphDFS(graph, startVet) { + // 頂點走訪序列 + const res = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + const visited = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 深度優先走訪 */ +const res = graphDFS(graph, v[0]); +console.log('\n深度優先走訪(DFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/javascript/chapter_greedy/coin_change_greedy.js b/zh-hant/codes/javascript/chapter_greedy/coin_change_greedy.js new file mode 100644 index 0000000000..f5bfde2871 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/coin_change_greedy.js @@ -0,0 +1,48 @@ +/** + * File: coin_change_greedy.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 零錢兌換:貪婪 */ +function coinChangeGreedy(coins, amt) { + // 假設 coins 陣列有序 + let i = coins.length - 1; + let count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 貪婪:能夠保證找到全域性最優解 +let coins = [1, 5, 10, 20, 50, 100]; +let amt = 186; +let res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 3 ,即 20 + 20 + 20'); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 2 ,即 49 + 49'); diff --git a/zh-hant/codes/javascript/chapter_greedy/fractional_knapsack.js b/zh-hant/codes/javascript/chapter_greedy/fractional_knapsack.js new file mode 100644 index 0000000000..771aa9170f --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/fractional_knapsack.js @@ -0,0 +1,46 @@ +/** + * File: fractional_knapsack.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 物品 */ +class Item { + constructor(w, v) { + this.w = w; // 物品重量 + this.v = v; // 物品價值 + } +} + +/* 分數背包:貪婪 */ +function fractionalKnapsack(wgt, val, cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + const items = wgt.map((w, i) => new Item(w, val[i])); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 迴圈貪婪選擇 + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (item.v / item.w) * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 貪婪演算法 +const res = fractionalKnapsack(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_greedy/max_capacity.js b/zh-hant/codes/javascript/chapter_greedy/max_capacity.js new file mode 100644 index 0000000000..1203ac2dee --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/max_capacity.js @@ -0,0 +1,34 @@ +/** + * File: max_capacity.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:貪婪 */ +function maxCapacity(ht) { + // 初始化 i, j,使其分列陣列兩端 + let i = 0, + j = ht.length - 1; + // 初始最大容量為 0 + let res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + const cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 貪婪演算法 +const res = maxCapacity(ht); +console.log(`最大容量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_greedy/max_product_cutting.js b/zh-hant/codes/javascript/chapter_greedy/max_product_cutting.js new file mode 100644 index 0000000000..b7eefdbe1c --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/max_product_cutting.js @@ -0,0 +1,33 @@ +/** + * File: max_product_cutting.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切分乘積:貪婪 */ +function maxProductCutting(n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a = Math.floor(n / 3); + let b = n % 3; + if (b === 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 當餘數為 2 時,不做處理 + return Math.pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return Math.pow(3, a); +} + +/* Driver Code */ +let n = 58; + +// 貪婪演算法 +let res = maxProductCutting(n); +console.log(`最大切分乘積為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_hashing/array_hash_map.js b/zh-hant/codes/javascript/chapter_hashing/array_hash_map.js new file mode 100644 index 0000000000..b7525185e9 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/array_hash_map.js @@ -0,0 +1,128 @@ +/** + * File: array_hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + #buckets; + constructor() { + // 初始化陣列,包含 100 個桶 + this.#buckets = new Array(100).fill(null); + } + + /* 雜湊函式 */ + #hashFunc(key) { + return key % 100; + } + + /* 查詢操作 */ + get(key) { + let index = this.#hashFunc(key); + let pair = this.#buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* 新增操作 */ + set(key, val) { + let index = this.#hashFunc(key); + this.#buckets[index] = new Pair(key, val); + } + + /* 刪除操作 */ + delete(key) { + let index = this.#hashFunc(key); + // 置為 null ,代表刪除 + this.#buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + entries() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i]); + } + } + return arr; + } + + /* 獲取所有鍵 */ + keys() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].key); + } + } + return arr; + } + + /* 獲取所有值 */ + values() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].val); + } + } + return arr; + } + + /* 列印雜湊表 */ + print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new ArrayHashMap(); +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +map.set(10583, '小鴨'); +console.info('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +let name = map.get(15937); +console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.delete(10583); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +map.print(); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\n單獨走訪鍵 Key'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\n單獨走訪值 Value'); +for (const val of map.values()) { + console.info(val); +} diff --git a/zh-hant/codes/javascript/chapter_hashing/hash_map.js b/zh-hant/codes/javascript/chapter_hashing/hash_map.js new file mode 100644 index 0000000000..4f7f2c3447 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/hash_map.js @@ -0,0 +1,44 @@ +/** + * File: hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new Map(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +map.set(10583, '小鴨'); +console.info('\n新增完成後,雜湊表為\nKey -> Value'); +console.info(map); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +let name = map.get(15937); +console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.delete(10583); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +console.info(map); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\n單獨走訪鍵 Key'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\n單獨走訪值 Value'); +for (const v of map.values()) { + console.info(v); +} diff --git a/zh-hant/codes/javascript/chapter_hashing/hash_map_chaining.js b/zh-hant/codes/javascript/chapter_hashing/hash_map_chaining.js new file mode 100644 index 0000000000..741dd4aa42 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/hash_map_chaining.js @@ -0,0 +1,142 @@ +/** + * File: hash_map_chaining.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + #size; // 鍵值對數量 + #capacity; // 雜湊表容量 + #loadThres; // 觸發擴容的負載因子閾值 + #extendRatio; // 擴容倍數 + #buckets; // 桶陣列 + + /* 建構子 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 雜湊函式 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 負載因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 查詢操作 */ + get(key) { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若找到 key ,則返回對應 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + put(key, val) { + // 當負載因子超過閾值時,執行擴容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 刪除操作 */ + remove(key) { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* 擴容雜湊表 */ + #extend() { + // 暫存原雜湊表 + const bucketsTmp = this.#buckets; + // 初始化擴容後的新雜湊表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print() { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new HashMapChaining(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小囉'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +const name = map.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.remove(12836); +console.log('\n刪除 12836 後,雜湊表為\nKey -> Value'); +map.print(); diff --git a/zh-hant/codes/javascript/chapter_hashing/hash_map_open_addressing.js b/zh-hant/codes/javascript/chapter_hashing/hash_map_open_addressing.js new file mode 100644 index 0000000000..55d2b50d78 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/hash_map_open_addressing.js @@ -0,0 +1,177 @@ +/** + * File: hashMapOpenAddressing.js + * Created Time: 2023-06-13 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + #size; // 鍵值對數量 + #capacity; // 雜湊表容量 + #loadThres; // 觸發擴容的負載因子閾值 + #extendRatio; // 擴容倍數 + #buckets; // 桶陣列 + #TOMBSTONE; // 刪除標記 + + /* 建構子 */ + constructor() { + this.#size = 0; // 鍵值對數量 + this.#capacity = 4; // 雜湊表容量 + this.#loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + this.#extendRatio = 2; // 擴容倍數 + this.#buckets = Array(this.#capacity).fill(null); // 桶陣列 + this.#TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 + } + + /* 雜湊函式 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 負載因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 搜尋 key 對應的桶索引 */ + #findBucket(key) { + let index = this.#hashFunc(key); + let firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (this.#buckets[index] !== null) { + // 若遇到 key ,返回對應的桶索引 + if (this.#buckets[index].key === key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone !== -1) { + this.#buckets[firstTombstone] = this.#buckets[index]; + this.#buckets[index] = this.#TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if ( + firstTombstone === -1 && + this.#buckets[index] === this.#TOMBSTONE + ) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % this.#capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + get(key) { + // 搜尋 key 對應的桶索引 + const index = this.#findBucket(key); + // 若找到鍵值對,則返回對應 val + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + return this.#buckets[index].val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + put(key, val) { + // 當負載因子超過閾值時,執行擴容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + // 搜尋 key 對應的桶索引 + const index = this.#findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index].val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + this.#buckets[index] = new Pair(key, val); + this.#size++; + } + + /* 刪除操作 */ + remove(key) { + // 搜尋 key 對應的桶索引 + const index = this.#findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index] = this.#TOMBSTONE; + this.#size--; + } + } + + /* 擴容雜湊表 */ + #extend() { + // 暫存原雜湊表 + const bucketsTmp = this.#buckets; + // 初始化擴容後的新雜湊表 + this.#capacity *= this.#extendRatio; + this.#buckets = Array(this.#capacity).fill(null); + this.#size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.#TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print() { + for (const pair of this.#buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.#TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// 初始化雜湊表 +const hashmap = new HashMapOpenAddressing(); + +// 新增操作 +// 在雜湊表中新增鍵值對 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小囉'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +hashmap.print(); + +// 查詢操作 +// 向雜湊表中輸入鍵 key ,得到值 val +const name = hashmap.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +// 刪除操作 +// 在雜湊表中刪除鍵值對 (key, val) +hashmap.remove(16750); +console.log('\n刪除 16750 後,雜湊表為\nKey -> Value'); +hashmap.print(); diff --git a/zh-hant/codes/javascript/chapter_hashing/simple_hash.js b/zh-hant/codes/javascript/chapter_hashing/simple_hash.js new file mode 100644 index 0000000000..11b87b91e4 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/simple_hash.js @@ -0,0 +1,60 @@ +/** + * File: simple_hash.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法雜湊 */ +function addHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法雜湊 */ +function mulHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 互斥或雜湊 */ +function xorHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* 旋轉雜湊 */ +function rotHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 演算法'; + +let hash = addHash(key); +console.log('加法雜湊值為 ' + hash); + +hash = mulHash(key); +console.log('乘法雜湊值為 ' + hash); + +hash = xorHash(key); +console.log('互斥或雜湊值為 ' + hash); + +hash = rotHash(key); +console.log('旋轉雜湊值為 ' + hash); diff --git a/zh-hant/codes/javascript/chapter_heap/my_heap.js b/zh-hant/codes/javascript/chapter_heap/my_heap.js new file mode 100644 index 0000000000..303ac2c293 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_heap/my_heap.js @@ -0,0 +1,158 @@ +/** + * File: my_heap.js + * Created Time: 2023-02-06 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { printHeap } = require('../modules/PrintUtil'); + +/* 最大堆積類別 */ +class MaxHeap { + #maxHeap; + + /* 建構子,建立空堆積或根據輸入串列建堆積 */ + constructor(nums) { + // 將串列元素原封不動新增進堆積 + this.#maxHeap = nums === undefined ? [] : [...nums]; + // 堆積化除葉節點以外的其他所有節點 + for (let i = this.#parent(this.size() - 1); i >= 0; i--) { + this.#siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + #left(i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + #right(i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + #parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 交換元素 */ + #swap(i, j) { + const tmp = this.#maxHeap[i]; + this.#maxHeap[i] = this.#maxHeap[j]; + this.#maxHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + size() { + return this.#maxHeap.length; + } + + /* 判斷堆積是否為空 */ + isEmpty() { + return this.size() === 0; + } + + /* 訪問堆積頂元素 */ + peek() { + return this.#maxHeap[0]; + } + + /* 元素入堆積 */ + push(val) { + // 新增節點 + this.#maxHeap.push(val); + // 從底至頂堆積化 + this.#siftUp(this.size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + #siftUp(i) { + while (true) { + // 獲取節點 i 的父節點 + const p = this.#parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; + // 交換兩節點 + this.#swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + pop() { + // 判空處理 + if (this.isEmpty()) throw new Error('堆積為空'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + this.#swap(0, this.size() - 1); + // 刪除節點 + const val = this.#maxHeap.pop(); + // 從頂至底堆積化 + this.#siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + #siftDown(i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + const l = this.#left(i), + r = this.#right(i); + let ma = i; + if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; + if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma === i) break; + // 交換兩節點 + this.#swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + print() { + printHeap(this.#maxHeap); + } + + /* 取出堆積中元素 */ + getMaxHeap() { + return this.#maxHeap; + } +} + +/* Driver Code */ +if (require.main === module) { + /* 初始化大頂堆積 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\n輸入串列並建堆積後'); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + let peek = maxHeap.peek(); + console.log(`\n堆積頂元素為 ${peek}`); + + /* 元素入堆積 */ + let val = 7; + maxHeap.push(val); + console.log(`\n元素 ${val} 入堆積後`); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + console.log(`\n堆積頂元素 ${peek} 出堆積後`); + maxHeap.print(); + + /* 獲取堆積大小 */ + let size = maxHeap.size(); + console.log(`\n堆積元素數量為 ${size}`); + + /* 判斷堆積是否為空 */ + let isEmpty = maxHeap.isEmpty(); + console.log(`\n堆積是否為空 ${isEmpty}`); +} + +module.exports = { + MaxHeap, +}; diff --git a/zh-hant/codes/javascript/chapter_heap/top_k.js b/zh-hant/codes/javascript/chapter_heap/top_k.js new file mode 100644 index 0000000000..ba6f6d1563 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_heap/top_k.js @@ -0,0 +1,58 @@ +/** + * File: top_k.js + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +const { MaxHeap } = require('./my_heap'); + +/* 元素入堆積 */ +function pushMinHeap(maxHeap, val) { + // 元素取反 + maxHeap.push(-val); +} + +/* 元素出堆積 */ +function popMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.pop(); +} + +/* 訪問堆積頂元素 */ +function peekMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.peek(); +} + +/* 取出堆積中元素 */ +function getMinHeap(maxHeap) { + // 元素取反 + return maxHeap.getMaxHeap().map((num) => -num); +} + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +function topKHeap(nums, k) { + // 初始化小頂堆積 + // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 + const maxHeap = new MaxHeap([]); + // 將陣列的前 k 個元素入堆積 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (let i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆積中元素 + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大的 ${k} 個元素為`, res); diff --git a/zh-hant/codes/javascript/chapter_searching/binary_search.js b/zh-hant/codes/javascript/chapter_searching/binary_search.js new file mode 100644 index 0000000000..6d3309b0be --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/binary_search.js @@ -0,0 +1,60 @@ +/** + * File: binary_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +/* 二分搜尋(雙閉區間) */ +function binarySearch(nums, target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + let i = 0, + j = nums.length - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + // 計算中點索引 m ,使用 parseInt() 向下取整 + const m = parseInt(i + (j - i) / 2); + 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; +} + +/* 二分搜尋(左閉右開區間) */ +function binarySearchLCRO(nums, target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + let i = 0, + j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + // 計算中點索引 m ,使用 parseInt() 向下取整 + const m = parseInt(i + (j - i) / 2); + 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; +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* 二分搜尋(雙閉區間) */ +let index = binarySearch(nums, target); +console.log('目標元素 6 的索引 = ' + index); + +/* 二分搜尋(左閉右開區間) */ +index = binarySearchLCRO(nums, target); +console.log('目標元素 6 的索引 = ' + index); diff --git a/zh-hant/codes/javascript/chapter_searching/binary_search_edge.js b/zh-hant/codes/javascript/chapter_searching/binary_search_edge.js new file mode 100644 index 0000000000..1e05369255 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/binary_search_edge.js @@ -0,0 +1,45 @@ +/** + * File: binary_search_edge.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +const { binarySearchInsertion } = require('./binary_search_insertion.js'); + +/* 二分搜尋最左一個 target */ +function binarySearchLeftEdge(nums, target) { + // 等價於查詢 target 的插入點 + const i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +function binarySearchRightEdge(nums, target) { + // 轉化為查詢最左一個 target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + const j = i - 1; + // 未找到 target ,返回 -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +// 包含重複元素的陣列 +const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋左邊界和右邊界 +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('最左一個元素 ' + target + ' 的索引為 ' + index); + index = binarySearchRightEdge(nums, target); + console.log('最右一個元素 ' + target + ' 的索引為 ' + index); +} diff --git a/zh-hant/codes/javascript/chapter_searching/binary_search_insertion.js b/zh-hant/codes/javascript/chapter_searching/binary_search_insertion.js new file mode 100644 index 0000000000..32779c54bc --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/binary_search_insertion.js @@ -0,0 +1,64 @@ +/** + * File: binary_search_insertion.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +function binarySearchInsertionSimple(nums, target) { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* 二分搜尋插入點(存在重複元素) */ +function binarySearchInsertion(nums, target) { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* Driver Code */ +// 無重複元素的陣列 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +// 包含重複元素的陣列 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +module.exports = { + binarySearchInsertion, +}; diff --git a/zh-hant/codes/javascript/chapter_searching/hashing_search.js b/zh-hant/codes/javascript/chapter_searching/hashing_search.js new file mode 100644 index 0000000000..5f9ad6c951 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/hashing_search.js @@ -0,0 +1,45 @@ +/** + * File: hashing_search.js + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { arrToLinkedList } = require('../modules/ListNode'); + +/* 雜湊查詢(陣列) */ +function hashingSearchArray(map, target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.has(target) ? map.get(target) : -1; +} + +/* 雜湊查詢(鏈結串列) */ +function hashingSearchLinkedList(map, target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.has(target) ? map.get(target) : null; +} + +/* Driver Code */ +const target = 3; + +/* 雜湊查詢(陣列) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// 初始化雜湊表 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 元素,value: 索引 +} +const index = hashingSearchArray(map, target); +console.log('目標元素 3 的索引 = ' + index); + +/* 雜湊查詢(鏈結串列) */ +let head = arrToLinkedList(nums); +// 初始化雜湊表 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: 節點值,value: 節點 + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('目標節點值 3 的對應節點物件為', node); diff --git a/zh-hant/codes/javascript/chapter_searching/linear_search.js b/zh-hant/codes/javascript/chapter_searching/linear_search.js new file mode 100644 index 0000000000..599b657083 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/linear_search.js @@ -0,0 +1,47 @@ +/** + * File: linear_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +const { ListNode, arrToLinkedList } = require('../modules/ListNode'); + +/* 線性查詢(陣列) */ +function linearSearchArray(nums, target) { + // 走訪陣列 + for (let i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] === target) { + return i; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列)*/ +function linearSearchLinkedList(head, target) { + // 走訪鏈結串列 + while (head) { + // 找到目標節點,返回之 + if (head.val === target) { + return head; + } + head = head.next; + } + // 未找到目標節點,返回 null + return null; +} + +/* Driver Code */ +const target = 3; + +/* 在陣列中執行線性查詢 */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('目標元素 3 的索引 = ' + index); + +/* 在鏈結串列中執行線性查詢 */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('目標節點值 3 的對應節點物件為 ', node); diff --git a/zh-hant/codes/javascript/chapter_searching/two_sum.js b/zh-hant/codes/javascript/chapter_searching/two_sum.js new file mode 100644 index 0000000000..693498b97a --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/two_sum.js @@ -0,0 +1,46 @@ +/** + * File: two_sum.js + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法一:暴力列舉 */ +function twoSumBruteForce(nums, target) { + const n = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法二:輔助雜湊表 */ +function twoSumHashTable(nums, target) { + // 輔助雜湊表,空間複雜度為 O(n) + let m = {}; + // 單層迴圈,時間複雜度為 O(n) + for (let i = 0; i < nums.length; i++) { + if (m[target - nums[i]] !== undefined) { + return [m[target - nums[i]], i]; + } else { + m[nums[i]] = i; + } + } + return []; +} + +/* Driver Code */ +// 方法一 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法一 res = ', res); + +// 方法二 +res = twoSumHashTable(nums, target); +console.log('方法二 res = ', res); diff --git a/zh-hant/codes/javascript/chapter_sorting/bubble_sort.js b/zh-hant/codes/javascript/chapter_sorting/bubble_sort.js new file mode 100644 index 0000000000..a90400cb5c --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/bubble_sort.js @@ -0,0 +1,49 @@ +/** + * File: bubble_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 泡沫排序 */ +function bubbleSort(nums) { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +function bubbleSortWithFlag(nums) { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('泡沫排序完成後 nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('泡沫排序完成後 nums =', nums1); diff --git a/zh-hant/codes/javascript/chapter_sorting/bucket_sort.js b/zh-hant/codes/javascript/chapter_sorting/bucket_sort.js new file mode 100644 index 0000000000..82fb5c33ef --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/bucket_sort.js @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 桶排序 */ +function bucketSort(nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + const k = nums.length / 2; + const buckets = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 將陣列元素分配到各個桶中 + for (const num of nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + const i = Math.floor(num * k); + // 將 num 新增進桶 i + buckets[i].push(num); + } + // 2. 對各個桶執行排序 + for (const bucket of buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort((a, b) => a - b); + } + // 3. 走訪桶合併結果 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('桶排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/counting_sort.js b/zh-hant/codes/javascript/chapter_sorting/counting_sort.js new file mode 100644 index 0000000000..bb57a3f077 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/counting_sort.js @@ -0,0 +1,71 @@ +/** + * File: counting_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +function countingSortNaive(nums) { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +function countingSort(nums) { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + const n = nums.length; + const res = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('計數排序(無法排序物件)完成後 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('計數排序完成後 nums1 =', nums1); diff --git a/zh-hant/codes/javascript/chapter_sorting/heap_sort.js b/zh-hant/codes/javascript/chapter_sorting/heap_sort.js new file mode 100644 index 0000000000..722470d2b9 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/heap_sort.js @@ -0,0 +1,49 @@ +/** + * File: heap_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +function siftDown(nums, n, i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let 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; + } + // 交換兩節點 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +function heapSort(nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (let i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('堆積排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/insertion_sort.js b/zh-hant/codes/javascript/chapter_sorting/insertion_sort.js new file mode 100644 index 0000000000..8668cb68c2 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/insertion_sort.js @@ -0,0 +1,25 @@ +/** + * File: insertion_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 插入排序 */ +function insertionSort(nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (let i = 1; i < nums.length; i++) { + let base = nums[i], + j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('插入排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/merge_sort.js b/zh-hant/codes/javascript/chapter_sorting/merge_sort.js new file mode 100644 index 0000000000..1b5f1b2bc9 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/merge_sort.js @@ -0,0 +1,52 @@ +/** + * File: merge_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 合併左子陣列和右子陣列 */ +function merge(nums, left, mid, right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + const tmp = new Array(right - left + 1); + // 初始化左子陣列和右子陣列的起始索引 + let 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 < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +function mergeSort(nums, left, right) { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + let mid = Math.floor(left + (right - left) / 2); // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('合併排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/quick_sort.js b/zh-hant/codes/javascript/chapter_sorting/quick_sort.js new file mode 100644 index 0000000000..43e506fa92 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/quick_sort.js @@ -0,0 +1,161 @@ +/** + * File: quick_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums, left, right) { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // 從左向右找首個大於基準數的元素 + } + // 元素交換 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums, left, right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + medianThree(nums, left, mid, right) { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m 在 l 和 r 之間 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l 在 m 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* 哨兵劃分(三數取中值) */ + partition(nums, left, right) { + // 選取三個候選元素的中位數 + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // 將中位數交換至陣列最左端 + this.swap(nums, left, med); + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums, left, right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums, left, right) { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + quickSort(nums, left, right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + let pivot = this.partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* 快速排序 */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('快速排序完成後 nums =', nums); + +/* 快速排序(中位基準數最佳化) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1); + +/* 快速排序(尾遞迴最佳化) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('快速排序(尾遞迴最佳化)完成後 nums =', nums2); diff --git a/zh-hant/codes/javascript/chapter_sorting/radix_sort.js b/zh-hant/codes/javascript/chapter_sorting/radix_sort.js new file mode 100644 index 0000000000..b4412d4a42 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/radix_sort.js @@ -0,0 +1,66 @@ +/** + * File: radix_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +function digit(num, exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return Math.floor(num / exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +function countingSortDigit(nums, exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + const counter = new Array(10).fill(0); + const n = nums.length; + // 統計 0~9 各數字的出現次數 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基數排序 */ +function radixSort(nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + let m = Number.MIN_VALUE; + for (const num of nums) { + if (num > m) { + m = num; + } + } + // 按照從低位到高位的順序走訪 + for (let exp = 1; exp <= m; exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基數排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/selection_sort.js b/zh-hant/codes/javascript/chapter_sorting/selection_sort.js new file mode 100644 index 0000000000..9b223d60a0 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/selection_sort.js @@ -0,0 +1,27 @@ +/** + * File: selection_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 選擇排序 */ +function selectionSort(nums) { + let n = nums.length; + // 外迴圈:未排序區間為 [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('選擇排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/array_deque.js b/zh-hant/codes/javascript/chapter_stack_and_queue/array_deque.js new file mode 100644 index 0000000000..23ea4c8928 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/array_deque.js @@ -0,0 +1,156 @@ +/** + * File: array_deque.js + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + #nums; // 用於儲存雙向佇列元素的陣列 + #front; // 佇列首指標,指向佇列首元素 + #queSize; // 雙向佇列長度 + + /* 建構子 */ + constructor(capacity) { + this.#nums = new Array(capacity); + this.#front = 0; + this.#queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + capacity() { + return this.#nums.length; + } + + /* 獲取雙向佇列的長度 */ + size() { + return this.#queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 計算環形陣列索引 */ + index(i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + this.capacity()) % this.capacity(); + } + + /* 佇列首入列 */ + pushFirst(num) { + if (this.#queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + this.#front = this.index(this.#front - 1); + // 將 num 新增至佇列首 + this.#nums[this.#front] = num; + this.#queSize++; + } + + /* 佇列尾入列 */ + pushLast(num) { + if (this.#queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + const rear = this.index(this.#front + this.#queSize); + // 將 num 新增至佇列尾 + this.#nums[rear] = num; + this.#queSize++; + } + + /* 佇列首出列 */ + popFirst() { + const num = this.peekFirst(); + // 佇列首指標向後移動一位 + this.#front = this.index(this.#front + 1); + this.#queSize--; + return num; + } + + /* 佇列尾出列 */ + popLast() { + const num = this.peekLast(); + this.#queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peekFirst() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.#nums[this.#front]; + } + + /* 訪問佇列尾元素 */ + peekLast() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 計算尾元素索引 + const last = this.index(this.#front + this.#queSize - 1); + return this.#nums[last]; + } + + /* 返回陣列用於列印 */ + toArray() { + // 僅轉換有效長度範圍內的串列元素 + const res = []; + for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { + res[i] = this.#nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const capacity = 5; +const deque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('雙向佇列 deque = [' + deque.toArray() + ']'); + +/* 訪問元素 */ +const peekFirst = deque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +deque.pushLast(4); +console.log('元素 4 佇列尾入列後 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('元素 1 佇列首入列後 deque = [' + deque.toArray() + ']'); + +/* 元素出列 */ +const popLast = deque.popLast(); +console.log( + '佇列尾出列元素 = ' + + popLast + + ',佇列尾出列後 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '佇列首出列元素 = ' + + popFirst + + ',佇列首出列後 deque = [' + + deque.toArray() + + ']' +); + +/* 獲取雙向佇列的長度 */ +const size = deque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = deque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/array_queue.js b/zh-hant/codes/javascript/chapter_stack_and_queue/array_queue.js new file mode 100644 index 0000000000..b2ce129183 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/array_queue.js @@ -0,0 +1,106 @@ +/** + * File: array_queue.js + * Created Time: 2022-12-13 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + #nums; // 用於儲存佇列元素的陣列 + #front = 0; // 佇列首指標,指向佇列首元素 + #queSize = 0; // 佇列長度 + + constructor(capacity) { + this.#nums = new Array(capacity); + } + + /* 獲取佇列的容量 */ + get capacity() { + return this.#nums.length; + } + + /* 獲取佇列的長度 */ + get size() { + return this.#queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 入列 */ + push(num) { + if (this.size === this.capacity) { + console.log('佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + const rear = (this.#front + this.size) % this.capacity; + // 將 num 新增至佇列尾 + this.#nums[rear] = num; + this.#queSize++; + } + + /* 出列 */ + pop() { + const num = this.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + this.#front = (this.#front + 1) % this.capacity; + this.#queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek() { + if (this.isEmpty()) throw new Error('佇列為空'); + return this.#nums[this.#front]; + } + + /* 返回 Array */ + toArray() { + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(this.size); + for (let i = 0, j = this.#front; i < this.size; i++, j++) { + arr[i] = this.#nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue =', queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); + +/* 測試環形陣列 */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 輪入列 + 出列後 queue =', queue.toArray()); +} diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/array_stack.js b/zh-hant/codes/javascript/chapter_stack_and_queue/array_stack.js new file mode 100644 index 0000000000..391978eab4 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/array_stack.js @@ -0,0 +1,75 @@ +/** + * File: array_stack.js + * Created Time: 2022-12-09 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + #stack; + constructor() { + this.#stack = []; + } + + /* 獲取堆疊的長度 */ + get size() { + return this.#stack.length; + } + + /* 判斷堆疊是否為空 */ + isEmpty() { + return this.#stack.length === 0; + } + + /* 入堆疊 */ + push(num) { + this.#stack.push(num); + } + + /* 出堆疊 */ + pop() { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.#stack.pop(); + } + + /* 訪問堆疊頂元素 */ + top() { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.#stack[this.#stack.length - 1]; + } + + /* 返回 Array */ + toArray() { + return this.#stack; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new ArrayStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = '); +console.log(stack.toArray()); + +/* 訪問堆疊頂元素 */ +const top = stack.top(); +console.log('堆疊頂元素 top = ' + top); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = '); +console.log(stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/deque.js b/zh-hant/codes/javascript/chapter_stack_and_queue/deque.js new file mode 100644 index 0000000000..90e0f5fe9e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/deque.js @@ -0,0 +1,44 @@ +/** + * File: deque.js + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 初始化雙向佇列 */ +// JavaScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 +const deque = []; + +/* 元素入列 */ +deque.push(2); +deque.push(5); +deque.push(4); +// 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) +deque.unshift(3); +deque.unshift(1); +console.log('雙向佇列 deque = ', deque); + +/* 訪問元素 */ +const peekFirst = deque[0]; +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = deque[deque.length - 1]; +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素出列 */ +// 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) +const popFront = deque.shift(); +console.log( + '佇列首出列元素 popFront = ' + popFront + ',佇列首出列後 deque = ' + deque +); +const popBack = deque.pop(); +console.log( + '佇列尾出列元素 popBack = ' + popBack + ',佇列尾出列後 deque = ' + deque +); + +/* 獲取雙向佇列的長度 */ +const size = deque.length; +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = size === 0; +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js new file mode 100644 index 0000000000..f640433e54 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.js + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + prev; // 前驅節點引用 (指標) + next; // 後繼節點引用 (指標) + val; // 節點值 + + constructor(val) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + #front; // 頭節點 front + #rear; // 尾節點 rear + #queSize; // 雙向佇列的長度 + + constructor() { + this.#front = null; + this.#rear = null; + this.#queSize = 0; + } + + /* 佇列尾入列操作 */ + pushLast(val) { + const node = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 將 node 新增至鏈結串列尾部 + this.#rear.next = node; + node.prev = this.#rear; + this.#rear = node; // 更新尾節點 + } + this.#queSize++; + } + + /* 佇列首入列操作 */ + pushFirst(val) { + const node = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 將 node 新增至鏈結串列頭部 + this.#front.prev = node; + node.next = this.#front; + this.#front = node; // 更新頭節點 + } + this.#queSize++; + } + + /* 佇列尾出列操作 */ + popLast() { + if (this.#queSize === 0) { + return null; + } + const value = this.#rear.val; // 儲存尾節點值 + // 刪除尾節點 + let temp = this.#rear.prev; + if (temp !== null) { + temp.next = null; + this.#rear.prev = null; + } + this.#rear = temp; // 更新尾節點 + this.#queSize--; + return value; + } + + /* 佇列首出列操作 */ + popFirst() { + if (this.#queSize === 0) { + return null; + } + const value = this.#front.val; // 儲存尾節點值 + // 刪除頭節點 + let temp = this.#front.next; + if (temp !== null) { + temp.prev = null; + this.#front.next = null; + } + this.#front = temp; // 更新頭節點 + this.#queSize--; + return value; + } + + /* 訪問佇列尾元素 */ + peekLast() { + return this.#queSize === 0 ? null : this.#rear.val; + } + + /* 訪問佇列首元素 */ + peekFirst() { + return this.#queSize === 0 ? null : this.#front.val; + } + + /* 獲取雙向佇列的長度 */ + size() { + return this.#queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 列印雙向佇列 */ + print() { + const arr = []; + let temp = this.#front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const linkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('雙向佇列 linkedListDeque = '); +linkedListDeque.print(); + +/* 訪問元素 */ +const peekFirst = linkedListDeque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = linkedListDeque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +linkedListDeque.pushLast(4); +console.log('元素 4 佇列尾入列後 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('元素 1 佇列首入列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 元素出列 */ +const popLast = linkedListDeque.popLast(); +console.log('佇列尾出列元素 = ' + popLast + ',佇列尾出列後 linkedListDeque = '); +linkedListDeque.print(); +const popFirst = linkedListDeque.popFirst(); +console.log('佇列首出列元素 = ' + popFirst + ',佇列首出列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 獲取雙向佇列的長度 */ +const size = linkedListDeque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = linkedListDeque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js new file mode 100644 index 0000000000..bc6c6178dd --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js @@ -0,0 +1,99 @@ +/** + * File: linkedlist_queue.js + * Created Time: 2022-12-20 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + #front; // 頭節點 #front + #rear; // 尾節點 #rear + #queSize = 0; + + constructor() { + this.#front = null; + this.#rear = null; + } + + /* 獲取佇列的長度 */ + get size() { + return this.#queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty() { + return this.size === 0; + } + + /* 入列 */ + push(num) { + // 在尾節點後新增 num + const node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (!this.#front) { + this.#front = node; + this.#rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + this.#rear.next = node; + this.#rear = node; + } + this.#queSize++; + } + + /* 出列 */ + pop() { + const num = this.peek(); + // 刪除頭節點 + this.#front = this.#front.next; + this.#queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek() { + if (this.size === 0) throw new Error('佇列為空'); + return this.#front.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray() { + let node = this.#front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const queue = new LinkedListQueue(); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue = ' + queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue = ' + queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js new file mode 100644 index 0000000000..11f257af6e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js @@ -0,0 +1,88 @@ +/** + * File: linkedlist_stack.js + * Created Time: 2022-12-22 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + #stackPeek; // 將頭節點作為堆疊頂 + #stkSize = 0; // 堆疊的長度 + + constructor() { + this.#stackPeek = null; + } + + /* 獲取堆疊的長度 */ + get size() { + return this.#stkSize; + } + + /* 判斷堆疊是否為空 */ + isEmpty() { + return this.size === 0; + } + + /* 入堆疊 */ + push(num) { + const node = new ListNode(num); + node.next = this.#stackPeek; + this.#stackPeek = node; + this.#stkSize++; + } + + /* 出堆疊 */ + pop() { + const num = this.peek(); + this.#stackPeek = this.#stackPeek.next; + this.#stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + peek() { + if (!this.#stackPeek) throw new Error('堆疊為空'); + return this.#stackPeek.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray() { + let node = this.#stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new LinkedListStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = ' + stack.toArray()); + +/* 訪問堆疊頂元素 */ +const peek = stack.peek(); +console.log('堆疊頂元素 peek = ' + peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = ' + stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/queue.js b/zh-hant/codes/javascript/chapter_stack_and_queue/queue.js new file mode 100644 index 0000000000..8a5fffc8fe --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/queue.js @@ -0,0 +1,35 @@ +/** + * File: queue.js + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化佇列 */ +// JavaScript 沒有內建的佇列,可以把 Array 當作佇列來使用 +const queue = []; + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue); + +/* 訪問佇列首元素 */ +const peek = queue[0]; +console.log('佇列首元素 peek =', peek); + +/* 元素出列 */ +// 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) +const pop = queue.shift(); +console.log('出列元素 pop =', pop, ',出列後 queue = ', queue); + +/* 獲取佇列的長度 */ +const size = queue.length; +console.log('佇列長度 size =', size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.length === 0; +console.log('佇列是否為空 = ', isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/stack.js b/zh-hant/codes/javascript/chapter_stack_and_queue/stack.js new file mode 100644 index 0000000000..32bfc2f5a8 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/stack.js @@ -0,0 +1,35 @@ +/** + * File: stack.js + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化堆疊 */ +// JavaScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 +const stack = []; + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack =', stack); + +/* 訪問堆疊頂元素 */ +const peek = stack[stack.length - 1]; +console.log('堆疊頂元素 peek =', peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop =', pop); +console.log('出堆疊後 stack =', stack); + +/* 獲取堆疊的長度 */ +const size = stack.length; +console.log('堆疊的長度 size =', size); + +/* 判斷是否為空 */ +const isEmpty = stack.length === 0; +console.log('堆疊是否為空 =', isEmpty); diff --git a/zh-hant/codes/javascript/chapter_tree/array_binary_tree.js b/zh-hant/codes/javascript/chapter_tree/array_binary_tree.js new file mode 100644 index 0000000000..8f588f0b75 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/array_binary_tree.js @@ -0,0 +1,147 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + #tree; + + /* 建構子 */ + constructor(arr) { + this.#tree = arr; + } + + /* 串列容量 */ + size() { + return this.#tree.length; + } + + /* 獲取索引為 i 節點的值 */ + val(i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + left(i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + right(i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 層序走訪 */ + levelOrder() { + let res = []; + // 直接走訪陣列 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度優先走訪 */ + #dfs(i, order, res) { + // 若為空位,則返回 + if (this.val(i) === null) return; + // 前序走訪 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序走訪 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 後序走訪 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序走訪 */ + preOrder() { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序走訪 */ + inOrder() { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 後序走訪 */ + postOrder() { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 初始化二元樹 +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n初始化二元樹\n'); +console.log('二元樹的陣列表示:'); +console.log(arr); +console.log('二元樹的鏈結串列表示:'); +printTree(root); + +// 陣列表示下的二元樹類別 +const abt = new ArrayBinaryTree(arr); + +// 訪問節點 +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n當前節點的索引為 ' + i + ' ,值為 ' + abt.val(i)); +console.log( + '其左子節點的索引為 ' + l + ' ,值為 ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + '其右子節點的索引為 ' + r + ' ,值為 ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + '其父節點的索引為 ' + p + ' ,值為 ' + (p === null ? 'null' : abt.val(p)) +); + +// 走訪樹 +let res = abt.levelOrder(); +console.log('\n層序走訪為:' + res); +res = abt.preOrder(); +console.log('前序走訪為:' + res); +res = abt.inOrder(); +console.log('中序走訪為:' + res); +res = abt.postOrder(); +console.log('後序走訪為:' + res); diff --git a/zh-hant/codes/javascript/chapter_tree/avl_tree.js b/zh-hant/codes/javascript/chapter_tree/avl_tree.js new file mode 100644 index 0000000000..bdcb8be3f5 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/avl_tree.js @@ -0,0 +1,208 @@ +/** + * File: avl_tree.js + * Created Time: 2023-02-05 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* AVL 樹*/ +class AVLTree { + /* 建構子 */ + constructor() { + this.root = null; //根節點 + } + + /* 獲取節點高度 */ + height(node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node === null ? -1 : node.height; + } + + /* 更新節點高度 */ + #updateHeight(node) { + // 節點高度等於最高子樹高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + balanceFactor(node) { + // 空節點平衡因子為 0 + if (node === null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return this.height(node.left) - this.height(node.right); + } + + /* 右旋操作 */ + #rightRotate(node) { + const child = node.left; + const grandChild = child.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + #leftRotate(node) { + const child = node.right; + const grandChild = child.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + #rotate(node) { + // 獲取節點 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏樹 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.#rightRotate(node); + } else { + // 先左旋後右旋 + node.left = this.#leftRotate(node.left); + return this.#rightRotate(node); + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.#leftRotate(node); + } else { + // 先右旋後左旋 + node.right = this.#rightRotate(node.right); + return this.#leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + insert(val) { + this.root = this.#insertHelper(this.root, val); + } + + /* 遞迴插入節點(輔助方法) */ + #insertHelper(node, val) { + if (node === null) return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) node.left = this.#insertHelper(node.left, val); + else if (val > node.val) + node.right = this.#insertHelper(node.right, val); + else return node; // 重複節點不插入,直接返回 + this.#updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.#rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + remove(val) { + this.root = this.#removeHelper(this.root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + #removeHelper(node, val) { + if (node === null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) node.left = this.#removeHelper(node.left, val); + else if (val > node.val) + node.right = this.#removeHelper(node.right, val); + else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child === null) return null; + // 子節點數量 = 1 ,直接刪除 node + else node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.#removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.#updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.#rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + search(val) { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < val) cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > val) cur = cur.left; + // 找到目標節點,跳出迴圈 + else break; + } + // 返回目標節點 + return cur; + } +} + +function testInsert(tree, val) { + tree.insert(val); + console.log('\n插入節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +function testRemove(tree, val) { + tree.remove(val); + console.log('\n刪除節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +/* Driver Code */ +/* 初始化空 AVL 樹 */ +const avlTree = new AVLTree(); +/* 插入節點 */ +// 請關注插入節點後,AVL 樹是如何保持平衡的 +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 插入重複節點 */ +testInsert(avlTree, 7); + +/* 刪除節點 */ +// 請關注刪除節點後,AVL 樹是如何保持平衡的 +testRemove(avlTree, 8); // 刪除度為 0 的節點 +testRemove(avlTree, 5); // 刪除度為 1 的節點 +testRemove(avlTree, 4); // 刪除度為 2 的節點 + +/* 查詢節點 */ +const node = avlTree.search(7); +console.log('\n查詢到的節點物件為', node, ',節點值 = ' + node.val); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_search_tree.js b/zh-hant/codes/javascript/chapter_tree/binary_search_tree.js new file mode 100644 index 0000000000..ee0f50cc0c --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_search_tree.js @@ -0,0 +1,139 @@ +/** + * File: binary_search_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 二元搜尋樹 */ +class BinarySearchTree { + /* 建構子 */ + constructor() { + // 初始化空樹 + this.root = null; + } + + /* 獲取二元樹根節點 */ + getRoot() { + return this.root; + } + + /* 查詢節點 */ + search(num) { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) cur = cur.left; + // 找到目標節點,跳出迴圈 + else break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + insert(num) { + // 若樹為空,則初始化根節點 + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur = this.root, + pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到重複節點,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子樹中 + else cur = cur.left; + } + // 插入節點 + const node = new TreeNode(num); + if (pre.val < num) pre.right = node; + else pre.left = node; + } + + /* 刪除節點 */ + remove(num) { + // 若樹為空,直接提前返回 + if (this.root === null) return; + let cur = this.root, + pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val === num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur === null) return; + // 子節點數量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + const child = cur.left !== null ? cur.left : cur.right; + // 刪除節點 cur + if (cur !== this.root) { + if (pre.left === cur) pre.left = child; + else pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + this.root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + let tmp = cur.right; + while (tmp.left !== null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + this.remove(tmp.val); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +/* 初始化二元搜尋樹 */ +const bst = new BinarySearchTree(); +// 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\n初始化的二元樹為\n'); +printTree(bst.getRoot()); + +/* 查詢節點 */ +const node = bst.search(7); +console.log('\n查詢到的節點物件為 ' + node + ',節點值 = ' + node.val); + +/* 插入節點 */ +bst.insert(16); +console.log('\n插入節點 16 後,二元樹為\n'); +printTree(bst.getRoot()); + +/* 刪除節點 */ +bst.remove(1); +console.log('\n刪除節點 1 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\n刪除節點 2 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\n刪除節點 4 後,二元樹為\n'); +printTree(bst.getRoot()); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_tree.js b/zh-hant/codes/javascript/chapter_tree/binary_tree.js new file mode 100644 index 0000000000..818dbf30a7 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_tree.js @@ -0,0 +1,35 @@ +/** + * File: binary_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 初始化二元樹 */ +// 初始化節點 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// 構建節點之間的引用(指標) +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\n初始化二元樹\n'); +printTree(n1); + +/* 插入與刪除節點 */ +const P = new TreeNode(0); +// 在 n1 -> n2 中間插入節點 P +n1.left = P; +P.left = n2; +console.log('\n插入節點 P 後\n'); +printTree(n1); +// 刪除節點 P +n1.left = n2; +console.log('\n刪除節點 P 後\n'); +printTree(n1); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_tree_bfs.js b/zh-hant/codes/javascript/chapter_tree/binary_tree_bfs.js new file mode 100644 index 0000000000..8bf3e526e7 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_tree_bfs.js @@ -0,0 +1,34 @@ +/** + * File: binary_tree_bfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 層序走訪 */ +function levelOrder(root) { + // 初始化佇列,加入根節點 + const queue = [root]; + // 初始化一個串列,用於儲存走訪序列 + const list = []; + while (queue.length) { + let node = queue.shift(); // 隊列出隊 + list.push(node.val); // 儲存節點值 + if (node.left) queue.push(node.left); // 左子節點入列 + if (node.right) queue.push(node.right); // 右子節點入列 + } + return list; +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 層序走訪 */ +const list = levelOrder(root); +console.log('\n層序走訪的節點列印序列 = ' + list); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_tree_dfs.js b/zh-hant/codes/javascript/chapter_tree/binary_tree_dfs.js new file mode 100644 index 0000000000..4b45affbf5 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_tree_dfs.js @@ -0,0 +1,60 @@ +/** + * File: binary_tree_dfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +// 初始化串列,用於儲存走訪序列 +const list = []; + +/* 前序走訪 */ +function preOrder(root) { + if (root === null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* 中序走訪 */ +function inOrder(root) { + if (root === null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* 後序走訪 */ +function postOrder(root) { + if (root === null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 前序走訪 */ +list.length = 0; +preOrder(root); +console.log('\n前序走訪的節點列印序列 = ' + list); + +/* 中序走訪 */ +list.length = 0; +inOrder(root); +console.log('\n中序走訪的節點列印序列 = ' + list); + +/* 後序走訪 */ +list.length = 0; +postOrder(root); +console.log('\n後序走訪的節點列印序列 = ' + list); diff --git a/zh-hant/codes/javascript/modules/ListNode.js b/zh-hant/codes/javascript/modules/ListNode.js new file mode 100755 index 0000000000..e6f8b3d88c --- /dev/null +++ b/zh-hant/codes/javascript/modules/ListNode.js @@ -0,0 +1,31 @@ +/** + * File: ListNode.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 鏈結串列節點 */ +class ListNode { + val; // 節點值 + next; // 指向下一節點的引用(指標) + constructor(val, next) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* 將串列反序列化為鏈結串列 */ +function arrToLinkedList(arr) { + const dum = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +module.exports = { + ListNode, + arrToLinkedList, +}; diff --git a/zh-hant/codes/javascript/modules/PrintUtil.js b/zh-hant/codes/javascript/modules/PrintUtil.js new file mode 100644 index 0000000000..1803f4533c --- /dev/null +++ b/zh-hant/codes/javascript/modules/PrintUtil.js @@ -0,0 +1,86 @@ +/** + * File: PrintUtil.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('./TreeNode'); + +/* 列印鏈結串列 */ +function printLinkedList(head) { + let list = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +function Trunk(prev, str) { + this.prev = prev; + this.str = str; +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root) { + printTree(root, null, false); +} + +/* 列印二元樹 */ +function printTree(root, prev, isRight) { + if (root === null) { + return; + } + + let prev_str = ' '; + let trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (!prev) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +function showTrunks(p) { + if (!p) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* 列印堆積 */ +function printHeap(arr) { + console.log('堆積的陣列表示:'); + console.log(arr); + console.log('堆積的樹狀表示:'); + printTree(arrToTree(arr)); +} + +module.exports = { + printLinkedList, + printTree, + printHeap, +}; diff --git a/zh-hant/codes/javascript/modules/TreeNode.js b/zh-hant/codes/javascript/modules/TreeNode.js new file mode 100644 index 0000000000..b2ad065b5a --- /dev/null +++ b/zh-hant/codes/javascript/modules/TreeNode.js @@ -0,0 +1,35 @@ +/** + * File: TreeNode.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 二元樹節點 */ +class TreeNode { + val; // 節點值 + left; // 左子節點指標 + right; // 右子節點指標 + height; //節點高度 + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + this.height = height === undefined ? 0 : height; + } +} + +/* 將陣列反序列化為二元樹 */ +function arrToTree(arr, i = 0) { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +module.exports = { + TreeNode, + arrToTree, +}; diff --git a/zh-hant/codes/javascript/modules/Vertex.js b/zh-hant/codes/javascript/modules/Vertex.js new file mode 100644 index 0000000000..22eacbbde7 --- /dev/null +++ b/zh-hant/codes/javascript/modules/Vertex.js @@ -0,0 +1,35 @@ +/** + * File: Vertex.js + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 頂點類別 */ +class Vertex { + val; + constructor(val) { + this.val = val; + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + static valsToVets(vals) { + const vets = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + static vetsToVals(vets) { + const vals = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +module.exports = { + Vertex, +}; diff --git a/zh-hant/codes/javascript/test_all.js b/zh-hant/codes/javascript/test_all.js new file mode 100644 index 0000000000..b43ef49f5f --- /dev/null +++ b/zh-hant/codes/javascript/test_all.js @@ -0,0 +1,63 @@ +import { bold, brightRed } from 'jsr:@std/fmt/colors'; +import { expandGlob } from 'jsr:@std/fs'; +import { relative, resolve } from 'jsr:@std/path'; + +/** + * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry + * @type {WalkEntry[]} + */ +const entries = []; + +for await (const entry of expandGlob( + resolve(import.meta.dirname, './chapter_*/*.js') +)) { + entries.push(entry); +} + +/** @type {{ status: Promise; stderr: ReadableStream; }[]} */ +const processes = []; + +for (const file of entries) { + const execute = new Deno.Command('node', { + args: [relative(import.meta.dirname, file.path)], + cwd: import.meta.dirname, + stdin: 'piped', + stdout: 'piped', + stderr: 'piped', + }); + + const process = execute.spawn(); + processes.push({ status: process.status, stderr: process.stderr }); +} + +const results = await Promise.all( + processes.map(async (item) => { + const status = await item.status; + return { status, stderr: item.stderr }; + }) +); + +/** @type {ReadableStream[]} */ +const errors = []; + +for (const result of results) { + if (!result.status.success) { + errors.push(result.stderr); + } +} + +console.log(`Tested ${entries.length} files`); +console.log(`Found exception in ${errors.length} files`); + +if (errors.length) { + console.log(); + + for (const error of errors) { + const reader = error.getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); + } + + throw new Error('Test failed'); +} diff --git a/zh-hant/codes/kotlin/chapter_array_and_linkedlist/array.kt b/zh-hant/codes/kotlin/chapter_array_and_linkedlist/array.kt new file mode 100644 index 0000000000..e502379784 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_array_and_linkedlist/array.kt @@ -0,0 +1,102 @@ +/** + * File: array.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_array_and_linkedlist + +import java.util.concurrent.ThreadLocalRandom + +/* 隨機訪問元素 */ +fun randomAccess(nums: IntArray): Int { + // 在區間 [0, nums.size) 中隨機抽取一個數字 + val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) + // 獲取並返回隨機元素 + val randomNum = nums[randomIndex] + return randomNum +} + +/* 擴展陣列長度 */ +fun extend(nums: IntArray, enlarge: Int): IntArray { + // 初始化一個擴展長度後的陣列 + val res = IntArray(nums.size + enlarge) + // 將原陣列中的所有元素複製到新陣列 + for (i in nums.indices) { + res[i] = nums[i] + } + // 返回擴展後的新陣列 + return res +} + +/* 在陣列的索引 index 處插入元素 num */ +fun insert(nums: IntArray, num: Int, index: Int) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (i in nums.size - 1 downTo index + 1) { + nums[i] = nums[i - 1] + } + // 將 num 賦給 index 處的元素 + nums[index] = num +} + +/* 刪除索引 index 處的元素 */ +fun remove(nums: IntArray, index: Int) { + // 把索引 index 之後的所有元素向前移動一位 + for (i in index.. P -> n1 + val p = n0.next + val n1 = p?.next + n0.next = n1 +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +fun access(head: ListNode?, index: Int): ListNode? { + var h = head + for (i in 0..= size) + throw IndexOutOfBoundsException("索引越界") + return arr[index] + } + + /* 更新元素 */ + fun set(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + arr[index] = num + } + + /* 在尾部新增元素 */ + fun add(num: Int) { + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity() + arr[size] = num + // 更新元素數量 + size++ + } + + /* 在中間插入元素 */ + fun insert(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity() + // 將索引 index 以及之後的元素都向後移動一位 + for (j in size - 1 downTo index) + arr[j + 1] = arr[j] + arr[index] = num + // 更新元素數量 + size++ + } + + /* 刪除元素 */ + fun remove(index: Int): Int { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + val num = arr[index] + // 將將索引 index 之後的元素都向前移動一位 + for (j in index..>, + res: MutableList>?>, + cols: BooleanArray, + diags1: BooleanArray, + diags2: BooleanArray +) { + // 當放置完所有行時,記錄解 + if (row == n) { + val copyState = mutableListOf>() + for (sRow in state) { + copyState.add(sRow.toMutableList()) + } + res.add(copyState) + return + } + // 走訪所有列 + for (col in 0..>?> { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + val state = mutableListOf>() + for (i in 0..() + for (j in 0..>?>() + + backtrack(0, n, state, res, cols, diags1, diags2) + + return res +} + +/* Driver Code */ +fun main() { + val n = 4 + val res = nQueens(n) + + println("輸入棋盤長寬為 $n") + println("皇后放置方案共有 ${res.size} 種") + for (state in res) { + println("--------------------") + for (row in state!!) { + println(row) + } + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/permutations_i.kt b/zh-hant/codes/kotlin/chapter_backtracking/permutations_i.kt new file mode 100644 index 0000000000..d8e922be77 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/permutations_i.kt @@ -0,0 +1,53 @@ +/** + * File: permutations_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_i + +/* 回溯演算法:全排列 I */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // 走訪所有選擇 + for (i in choices.indices) { + val choice = choices[i] + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true + state.add(choice) + // 進行下一輪選擇 + backtrack(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全排列 I */ +fun permutationsI(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 3) + + val res = permutationsI(nums) + + println("輸入陣列 nums = ${nums.contentToString()}") + println("所有排列 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/permutations_ii.kt b/zh-hant/codes/kotlin/chapter_backtracking/permutations_ii.kt new file mode 100644 index 0000000000..fecc2ce4ca --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/permutations_ii.kt @@ -0,0 +1,54 @@ +/** + * File: permutations_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_ii + +/* 回溯演算法:全排列 II */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // 走訪所有選擇 + val duplicated = HashSet() + for (i in choices.indices) { + val choice = choices[i] + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice) // 記錄選擇過的元素值 + selected[i] = true + state.add(choice) + // 進行下一輪選擇 + backtrack(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全排列 II */ +fun permutationsII(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 2) + val res = permutationsII(nums) + + println("輸入陣列 nums = ${nums.contentToString()}") + println("所有排列 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt new file mode 100644 index 0000000000..ba7aa0fe6f --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_i_compact + +import utils.TreeNode +import utils.printTree + +var res: MutableList? = null + +/* 前序走訪:例題一 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + if (root._val == 7) { + // 記錄解 + res!!.add(root) + } + preOrder(root.left) + preOrder(root.right) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 前序走訪 + res = mutableListOf() + preOrder(root) + + println("\n輸出所有值為 7 的節點") + val vals = mutableListOf() + for (node in res!!) { + vals.add(node._val) + } + println(vals) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt new file mode 100644 index 0000000000..264b6e102e --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_ii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前序走訪:例題二 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + // 嘗試 + path!!.add(root) + if (root._val == 7) { + // 記錄解 + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // 回退 + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 前序走訪 + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\n輸出所有根節點到節點 7 的路徑") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt new file mode 100644 index 0000000000..79922d8298 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前序走訪:例題三 */ +fun preOrder(root: TreeNode?) { + // 剪枝 + if (root == null || root._val == 3) { + return + } + // 嘗試 + path!!.add(root) + if (root._val == 7) { + // 記錄解 + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // 回退 + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 前序走訪 + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt new file mode 100644 index 0000000000..3be3c01bb0 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt @@ -0,0 +1,82 @@ +/** + * File: preorder_traversal_iii_template.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_template + +import utils.TreeNode +import utils.printTree + +/* 判斷當前狀態是否為解 */ +fun isSolution(state: MutableList): Boolean { + return state.isNotEmpty() && state[state.size - 1]?._val == 7 +} + +/* 記錄解 */ +fun recordSolution(state: MutableList?, res: MutableList?>) { + res.add(state!!.toMutableList()) +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +fun isValid(state: MutableList?, choice: TreeNode?): Boolean { + return choice != null && choice._val != 3 +} + +/* 更新狀態 */ +fun makeChoice(state: MutableList, choice: TreeNode?) { + state.add(choice) +} + +/* 恢復狀態 */ +fun undoChoice(state: MutableList, choice: TreeNode?) { + state.removeLast() +} + +/* 回溯演算法:例題三 */ +fun backtrack( + state: MutableList, + choices: MutableList, + res: MutableList?> +) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res) + } + // 走訪所有選擇 + for (choice in choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + // 進行下一輪選擇 + backtrack(state, mutableListOf(choice!!.left, choice.right), res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 回溯演算法 + val res = mutableListOf?>() + backtrack(mutableListOf(), mutableListOf(root), res) + + println("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點") + for (path in res) { + val vals = mutableListOf() + for (node in path!!) { + if (node != null) { + vals.add(node._val) + } + } + println(vals) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i.kt b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i.kt new file mode 100644 index 0000000000..981787e498 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i.kt @@ -0,0 +1,58 @@ +/** + * File: subset_sum_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i + +/* 回溯演算法:子集和 I */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(state.toMutableList()) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (i in start..?> { + val state = mutableListOf() // 狀態(子集) + nums.sort() // 對 nums 進行排序 + val start = 0 // 走訪起始點 + val res = mutableListOf?>() // 結果串列(子集串列) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + + val res = subsetSumI(nums, target) + + println("輸入陣列 nums = ${nums.contentToString()}, target = $target") + println("所有和等於 $target 的子集 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt new file mode 100644 index 0000000000..72375957a4 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i_native.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i_naive + +/* 回溯演算法:子集和 I */ +fun backtrack( + state: MutableList, + target: Int, + total: Int, + choices: IntArray, + res: MutableList?> +) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.add(state.toMutableList()) + return + } + // 走訪所有選擇 + for (i in choices.indices) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue + } + // 嘗試:做出選擇,更新元素和 total + state.add(choices[i]) + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeAt(state.size - 1) + } +} + +/* 求解子集和 I(包含重複子集) */ +fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // 狀態(子集) + val total = 0 // 子集和 + val res = mutableListOf?>() // 結果串列(子集串列) + backtrack(state, target, total, nums, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + val res = subsetSumINaive(nums, target) + + println("輸入陣列 nums = ${nums.contentToString()}, target = $target") + println("所有和等於 $target 的子集 res = $res") + println("請注意,該方法輸出的結果包含重複集合") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_ii.kt b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_ii.kt new file mode 100644 index 0000000000..dd7a55f646 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_ii.kt @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_ii + +/* 回溯演算法:子集和 II */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(state.toMutableList()) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (i in start.. start && choices[i] == choices[i - 1]) { + continue + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]) + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeAt(state.size - 1) + } +} + +/* 求解子集和 II */ +fun subsetSumII(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // 狀態(子集) + nums.sort() // 對 nums 進行排序 + val start = 0 // 走訪起始點 + val res = mutableListOf?>() // 結果串列(子集串列) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 4, 5) + val target = 9 + val res = subsetSumII(nums, target) + + println("輸入陣列 nums = ${nums.contentToString()}, target = $target") + println("所有和等於 $target 的子集 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_computational_complexity/iteration.kt b/zh-hant/codes/kotlin/chapter_computational_complexity/iteration.kt new file mode 100644 index 0000000000..d387e4e60d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_computational_complexity/iteration.kt @@ -0,0 +1,74 @@ +/** + * File: iteration.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.iteration + +/* for 迴圈 */ +fun forLoop(n: Int): Int { + var res = 0 + // 迴圈求和 1, 2, ..., n-1, n + for (i in 1..n) { + res += i + } + return res +} + +/* while 迴圈 */ +fun whileLoop(n: Int): Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i + i++ // 更新條件變數 + } + return res +} + +/* while 迴圈(兩次更新) */ +fun whileLoopII(n: Int): Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i + // 更新條件變數 + i++ + i *= 2 + } + return res +} + +/* 雙層 for 迴圈 */ +fun nestedForLoop(n: Int): String { + val res = StringBuilder() + // 迴圈 i = 1, 2, ..., n-1, n + for (i in 1..n) { + // 迴圈 j = 1, 2, ..., n-1, n + for (j in 1..n) { + res.append(" ($i, $j), ") + } + } + return res.toString() +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = forLoop(n) + println("\nfor 迴圈的求和結果 res = $res") + + res = whileLoop(n) + println("\nwhile 迴圈的求和結果 res = $res") + + res = whileLoopII(n) + println("\nwhile 迴圈 (兩次更新) 求和結果 res = $res") + + val resStr = nestedForLoop(n) + println("\n雙層 for 迴圈的走訪結果 $resStr") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_computational_complexity/recursion.kt b/zh-hant/codes/kotlin/chapter_computational_complexity/recursion.kt new file mode 100644 index 0000000000..fae34bb19e --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_computational_complexity/recursion.kt @@ -0,0 +1,78 @@ +/** + * File: recursion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.recursion + +import java.util.* + +/* 遞迴 */ +fun recur(n: Int): Int { + // 終止條件 + if (n == 1) + return 1 + // 遞: 遞迴呼叫 + val res = recur(n - 1) + // 迴: 返回結果 + return n + res +} + +/* 使用迭代模擬遞迴 */ +fun forLoopRecur(n: Int): Int { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + val stack = Stack() + var res = 0 + // 遞: 遞迴呼叫 + for (i in n downTo 0) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i) + } + // 迴: 返回結果 + while (stack.isNotEmpty()) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop() + } + // res = 1+2+3+...+n + return res +} + +/* 尾遞迴 */ +tailrec fun tailRecur(n: Int, res: Int): Int { + // 新增 tailrec 關鍵詞,以開啟尾遞迴最佳化 + // 終止條件 + if (n == 0) + return res + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n) +} + +/* 費波那契數列:遞迴 */ +fun fib(n: Int): Int { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1 + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + val res = fib(n - 1) + fib(n - 2) + // 返回結果 f(n) + return res +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = recur(n) + println("\n遞迴函式的求和結果 res = $res") + + res = forLoopRecur(n) + println("\n使用迭代模擬遞迴求和結果 res = $res") + + res = tailRecur(n, 0) + println("\n尾遞迴函式的求和結果 res = $res") + + res = fib(n) + println("\n費波那契數列的第 $n 項為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_computational_complexity/space_complexity.kt b/zh-hant/codes/kotlin/chapter_computational_complexity/space_complexity.kt new file mode 100644 index 0000000000..a8f391e607 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_computational_complexity/space_complexity.kt @@ -0,0 +1,109 @@ +/** + * File: space_complexity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.space_complexity + +import utils.ListNode +import utils.TreeNode +import utils.printTree + +/* 函式 */ +fun function(): Int { + // 執行某些操作 + return 0 +} + +/* 常數階 */ +fun constant(n: Int) { + // 常數、變數、物件佔用 O(1) 空間 + val a = 0 + var b = 0 + val nums = Array(10000) { 0 } + val node = ListNode(0) + // 迴圈中的變數佔用 O(1) 空間 + for (i in 0..() + for (i in 0..() + for (i in 0..?>(n) + // 二維串列佔用 O(n^2) 空間 + val numList = mutableListOf>() + for (i in 0..() + for (j in 0.. nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + count += 3 // 元素交換包含 3 個單元操作 + } + } + } + return count +} + +/* 指數階(迴圈實現) */ +fun exponential(n: Int): Int { + var count = 0 + var base = 1 + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (i in 0.. 1) { + n1 /= 2 + count++ + } + return count +} + +/* 對數階(遞迴實現) */ +fun logRecur(n: Int): Int { + if (n <= 1) + return 0 + return logRecur(n / 2) + 1 +} + +/* 線性對數階 */ +fun linearLogRecur(n: Int): Int { + if (n <= 1) + return 1 + var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) + for (i in 0.. { + val nums = IntArray(n) + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (i in 0..(n) + for (i in 0..): Int { + for (i in nums.indices) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i + } + return -1 +} + +/* Driver Code */ +fun main() { + for (i in 0..9) { + val n = 100 + val nums = randomNumbers(n) + val index = findOne(nums) + println("\n陣列 [ 1, 2, ..., n ] 被打亂後 = ${nums.contentToString()}") + println("數字 1 的索引為 $index") + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt b/zh-hant/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt new file mode 100644 index 0000000000..56d3034840 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt @@ -0,0 +1,49 @@ +/** + * File: binary_search_recur.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.binary_search_recur + +/* 二分搜尋:問題 f(i, j) */ +fun dfs( + nums: IntArray, + target: Int, + i: Int, + j: Int +): Int { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1 + } + // 計算中點索引 m + val m = (i + j) / 2 + return if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + dfs(nums, target, m + 1, j) + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + dfs(nums, target, i, m - 1) + } else { + // 找到目標元素,返回其索引 + m + } +} + +/* 二分搜尋 */ +fun binarySearch(nums: IntArray, target: Int): Int { + val n = nums.size + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + // 二分搜尋(雙閉區間) + val index = binarySearch(nums, target) + println("目標元素 6 的索引 = $index") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_divide_and_conquer/build_tree.kt b/zh-hant/codes/kotlin/chapter_divide_and_conquer/build_tree.kt new file mode 100644 index 0000000000..a2fdf921d0 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_divide_and_conquer/build_tree.kt @@ -0,0 +1,55 @@ +/** + * File: build_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.build_tree + +import utils.TreeNode +import utils.printTree + +/* 構建二元樹:分治 */ +fun dfs( + preorder: IntArray, + inorderMap: Map, + i: Int, + l: Int, + r: Int +): TreeNode? { + // 子樹區間為空時終止 + if (r - l < 0) return null + // 初始化根節點 + val root = TreeNode(preorder[i]) + // 查詢 m ,從而劃分左右子樹 + val m = inorderMap[preorder[i]]!! + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) + // 返回根節點 + return root +} + +/* 構建二元樹 */ +fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + val inorderMap = HashMap() + for (i in inorder.indices) { + inorderMap[inorder[i]] = i + } + val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) + return root +} + +/* Driver Code */ +fun main() { + val preorder = intArrayOf(3, 9, 2, 1, 7) + val inorder = intArrayOf(9, 3, 1, 2, 7) + println("前序走訪 = ${preorder.contentToString()}") + println("中序走訪 = ${inorder.contentToString()}") + + val root = buildTree(preorder, inorder) + println("構建的二元樹為:") + printTree(root) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_divide_and_conquer/hanota.kt b/zh-hant/codes/kotlin/chapter_divide_and_conquer/hanota.kt new file mode 100644 index 0000000000..dfb58f6d58 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_divide_and_conquer/hanota.kt @@ -0,0 +1,56 @@ +/** + * File: hanota.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.hanota + +/* 移動一個圓盤 */ +fun move(src: MutableList, tar: MutableList) { + // 從 src 頂部拿出一個圓盤 + val pan = src.removeAt(src.size - 1) + // 將圓盤放入 tar 頂部 + tar.add(pan) +} + +/* 求解河內塔問題 f(i) */ +fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i == 1) { + move(src, tar) + return + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf) + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar) + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar) +} + +/* 求解河內塔問題 */ +fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { + val n = A.size + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C) +} + +/* Driver Code */ +fun main() { + // 串列尾部是柱子頂部 + val A = mutableListOf(5, 4, 3, 2, 1) + val B = mutableListOf() + val C = mutableListOf() + println("初始狀態下:") + println("A = $A") + println("B = $B") + println("C = $C") + + solveHanota(A, B, C) + + println("圓盤移動完成後:") + println("A = $A") + println("B = $B") + println("C = $C") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt new file mode 100644 index 0000000000..62766e02cb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt @@ -0,0 +1,45 @@ +/** + * File: climbing_stairs_backtrack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 回溯 */ +fun backtrack( + choices: MutableList, + state: Int, + n: Int, + res: MutableList +) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res[0] = res[0] + 1 + // 走訪所有選擇 + for (choice in choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res) + // 回退 + } +} + +/* 爬樓梯:回溯 */ +fun climbingStairsBacktrack(n: Int): Int { + val choices = mutableListOf(1, 2) // 可選擇向上爬 1 階或 2 階 + val state = 0 // 從第 0 階開始爬 + val res = mutableListOf() + res.add(0) // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res) + return res[0] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsBacktrack(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt new file mode 100644 index 0000000000..3691a5023c --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt @@ -0,0 +1,35 @@ +/** + * File: climbing_stairs_constraint_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 帶約束爬樓梯:動態規劃 */ +fun climbingStairsConstraintDP(n: Int): Int { + if (n == 1 || n == 2) { + return 1 + } + // 初始化 dp 表,用於儲存子問題的解 + val dp = Array(n + 1) { IntArray(3) } + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (i in 3..n) { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsConstraintDP(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt new file mode 100644 index 0000000000..f8f2528239 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt @@ -0,0 +1,29 @@ +/** + * File: climbing_stairs_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 搜尋 */ +fun dfs(i: Int): Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) return i + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1) + dfs(i - 2) + return count +} + +/* 爬樓梯:搜尋 */ +fun climbingStairsDFS(n: Int): Int { + return dfs(n) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFS(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt new file mode 100644 index 0000000000..460123076f --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_dfs_mem.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 記憶化搜尋 */ +fun dfs(i: Int, mem: IntArray): Int { + // 已知 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] + val count = dfs(i - 1, mem) + dfs(i - 2, mem) + // 記錄 dp[i] + mem[i] = count + return count +} + +/* 爬樓梯:記憶化搜尋 */ +fun climbingStairsDFSMem(n: Int): Int { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + val mem = IntArray(n + 1) + mem.fill(-1) + return dfs(n, mem) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFSMem(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt new file mode 100644 index 0000000000..c55e32cc6d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 爬樓梯:動態規劃 */ +fun climbingStairsDP(n: Int): Int { + if (n == 1 || n == 2) return n + // 初始化 dp 表,用於儲存子問題的解 + val dp = IntArray(n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = 1 + dp[2] = 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (i in 3..n) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +fun climbingStairsDPComp(n: Int): Int { + if (n == 1 || n == 2) return n + var a = 1 + var b = 2 + for (i in 3..n) { + val temp = b + b += a + a = temp + } + return b +} + +/* Driver Code */ +fun main() { + val n = 9 + + var res = climbingStairsDP(n) + println("爬 $n 階樓梯共有 $res 種方案") + + res = climbingStairsDPComp(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change.kt new file mode 100644 index 0000000000..8602b912a7 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change.kt @@ -0,0 +1,71 @@ +/** + * File: coin_change.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 零錢兌換:動態規劃 */ +fun coinChangeDP(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 狀態轉移:首行首列 + for (a in 1..amt) { + dp[0][a] = MAX + } + // 狀態轉移:其餘行和列 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return if (dp[n][amt] != MAX) dp[n][amt] else -1 +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +fun coinChangeDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // 初始化 dp 表 + val dp = IntArray(amt + 1) + dp.fill(MAX) + dp[0] = 0 + // 狀態轉移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return if (dp[amt] != MAX) dp[amt] else -1 +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 4 + + // 動態規劃 + var res = coinChangeDP(coins, amt) + println("湊到目標金額所需的最少硬幣數量為 $res") + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt) + println("湊到目標金額所需的最少硬幣數量為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt new file mode 100644 index 0000000000..d3c1ecdedc --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 零錢兌換 II:動態規劃 */ +fun coinChangeIIDP(coins: IntArray, amt: Int): Int { + val n = coins.size + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 初始化首列 + for (i in 0..n) { + dp[i][0] = 1 + } + // 狀態轉移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + // 初始化 dp 表 + val dp = IntArray(amt + 1) + dp[0] = 1 + // 狀態轉移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 5 + + // 動態規劃 + var res = coinChangeIIDP(coins, amt) + println("湊出目標金額的硬幣組合數量為 $res") + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt) + println("湊出目標金額的硬幣組合數量為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/edit_distance.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/edit_distance.kt new file mode 100644 index 0000000000..c1a5030652 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/edit_distance.kt @@ -0,0 +1,143 @@ +/** + * File: edit_distance.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 編輯距離:暴力搜尋 */ +fun editDistanceDFS( + s: String, + t: String, + i: Int, + j: Int +): Int { + // 若 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 + val insert = editDistanceDFS(s, t, i, j - 1) + val delete = editDistanceDFS(s, t, i - 1, j) + val replace = editDistanceDFS(s, t, i - 1, j - 1) + // 返回最少編輯步數 + return min(min(insert, delete), replace) + 1 +} + +/* 編輯距離:記憶化搜尋 */ +fun editDistanceDFSMem( + s: String, + t: String, + mem: Array, + i: Int, + j: Int +): Int { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) return 0 + // 若 s 為空,則返回 t 長度 + if (i == 0) return j + // 若 t 為空,則返回 s 長度 + if (j == 0) return i + // 若已有記錄,則直接返回之 + if (mem[i][j] != -1) return mem[i][j] + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + val insert = editDistanceDFSMem(s, t, mem, i, j - 1) + val delete = editDistanceDFSMem(s, t, mem, i - 1, j) + val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 記錄並返回最少編輯步數 + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 編輯距離:動態規劃 */ +fun editDistanceDP(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = Array(n + 1) { IntArray(m + 1) } + // 狀態轉移:首行首列 + for (i in 1..n) { + dp[i][0] = i + } + for (j in 1..m) { + dp[0][j] = j + } + // 狀態轉移:其餘行和列 + for (i in 1..n) { + for (j in 1..m) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +fun editDistanceDPComp(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = IntArray(m + 1) + // 狀態轉移:首行 + for (j in 1..m) { + dp[j] = j + } + // 狀態轉移:其餘行 + for (i in 1..n) { + // 狀態轉移:首列 + var leftup = dp[0] // 暫存 dp[i-1, j-1] + dp[0] = i + // 狀態轉移:其餘列 + for (j in 1..m) { + val temp = dp[j] + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m] +} + +/* Driver Code */ +fun main() { + val s = "bag" + val t = "pack" + val n = s.length + val m = t.length + + // 暴力搜尋 + var res = editDistanceDFS(s, t, n, m) + println("將 $s 更改為 $t 最少需要編輯 $res 步") + + // 記憶化搜尋 + val mem = Array(n + 1) { IntArray(m + 1) } + for (row in mem) + row.fill(-1) + res = editDistanceDFSMem(s, t, mem, n, m) + println("將 $s 更改為 $t 最少需要編輯 $res 步") + + // 動態規劃 + res = editDistanceDP(s, t) + println("將 $s 更改為 $t 最少需要編輯 $res 步") + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t) + println("將 $s 更改為 $t 最少需要編輯 $res 步") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/knapsack.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/knapsack.kt new file mode 100644 index 0000000000..d8e3b8abcb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/knapsack.kt @@ -0,0 +1,125 @@ +/** + * File: knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.max + +/* 0-1 背包:暴力搜尋 */ +fun knapsackDFS( + wgt: IntArray, + _val: IntArray, + i: Int, + c: Int +): Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0 + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, _val, i - 1, c) + } + // 計算不放入和放入物品 i 的最大價值 + val no = knapsackDFS(wgt, _val, i - 1, c) + val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] + // 返回兩種方案中價值更大的那一個 + return max(no, yes) +} + +/* 0-1 背包:記憶化搜尋 */ +fun knapsackDFSMem( + wgt: IntArray, + _val: IntArray, + mem: Array, + i: Int, + c: Int +): Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0 + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c] + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, _val, mem, i - 1, c) + } + // 計算不放入和放入物品 i 的最大價值 + val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) + val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(cap + 1) } + // 狀態轉移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = IntArray(cap + 1) + // 狀態轉移 + for (i in 1..n) { + // 倒序走訪 + for (c in cap downTo 1) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + val n = wgt.size + + // 暴力搜尋 + var res = knapsackDFS(wgt, _val, n, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 記憶化搜尋 + val mem = Array(n + 1) { IntArray(cap + 1) } + for (row in mem) { + row.fill(-1) + } + res = knapsackDFSMem(wgt, _val, mem, n, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 動態規劃 + res = knapsackDP(wgt, _val, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, _val, cap) + println("不超過背包容量的最大物品價值為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt new file mode 100644 index 0000000000..c5ea7ab6b4 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 爬樓梯最小代價:動態規劃 */ +fun minCostClimbingStairsDP(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + // 初始化 dp 表,用於儲存子問題的解 + val dp = IntArray(n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (i in 3..n) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +fun minCostClimbingStairsDPComp(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + var a = cost[1] + var b = cost[2] + for (i in 3..n) { + val tmp = b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} + +/* Driver Code */ +fun main() { + val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) + println("輸入樓梯的代價串列為 ${cost.contentToString()}") + + var res = minCostClimbingStairsDP(cost) + println("爬完樓梯的最低代價為 $res") + + res = minCostClimbingStairsDPComp(cost) + println("爬完樓梯的最低代價為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt new file mode 100644 index 0000000000..afbdae41f1 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 最小路徑和:暴力搜尋 */ +fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + val up = minPathSumDFS(grid, i - 1, j) + val left = minPathSumDFS(grid, i, j - 1) + // 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) + grid[i][j] +} + +/* 最小路徑和:記憶化搜尋 */ +fun minPathSumDFSMem( + grid: Array, + mem: Array, + i: Int, + j: Int +): Int { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j] + } + // 左邊和上邊單元格的最小路徑代價 + val up = minPathSumDFSMem(grid, mem, i - 1, j) + val left = minPathSumDFSMem(grid, mem, i, j - 1) + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* 最小路徑和:動態規劃 */ +fun minPathSumDP(grid: Array): Int { + val n = grid.size + val m = grid[0].size + // 初始化 dp 表 + val dp = Array(n) { IntArray(m) } + dp[0][0] = grid[0][0] + // 狀態轉移:首行 + for (j in 1..): Int { + val n = grid.size + val m = grid[0].size + // 初始化 dp 表 + val dp = IntArray(m) + // 狀態轉移:首行 + dp[0] = grid[0][0] + for (j in 1.. c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 完全背包:空間最佳化後的動態規劃 */ +fun unboundedKnapsackDPComp( + wgt: IntArray, + _val: IntArray, + cap: Int +): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = IntArray(cap + 1) + // 狀態轉移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(1, 2, 3) + val _val = intArrayOf(5, 11, 15) + val cap = 4 + + // 動態規劃 + var res = unboundedKnapsackDP(wgt, _val, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, _val, cap) + println("不超過背包容量的最大物品價值為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_list.kt b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_list.kt new file mode 100644 index 0000000000..2a0c7f65d3 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_list.kt @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList(edges: Array>) { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + val adjList = HashMap>() + + /* 建構子 */ + init { + // 新增所有頂點和邊 + for (edge in edges) { + addVertex(edge[0]!!) + addVertex(edge[1]!!) + addEdge(edge[0]!!, edge[1]!!) + } + } + + /* 獲取頂點數量 */ + fun size(): Int { + return adjList.size + } + + /* 新增邊 */ + fun addEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 新增邊 vet1 - vet2 + adjList[vet1]?.add(vet2) + adjList[vet2]?.add(vet1) + } + + /* 刪除邊 */ + fun removeEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 刪除邊 vet1 - vet2 + adjList[vet1]?.remove(vet2) + adjList[vet2]?.remove(vet1) + } + + /* 新增頂點 */ + fun addVertex(vet: Vertex) { + if (adjList.containsKey(vet)) + return + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = mutableListOf() + } + + /* 刪除頂點 */ + fun removeVertex(vet: Vertex) { + if (!adjList.containsKey(vet)) + throw IllegalArgumentException() + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.remove(vet) + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (list in adjList.values) { + list.remove(vet) + } + } + + /* 列印鄰接表 */ + fun print() { + println("鄰接表 =") + for (pair in adjList.entries) { + val tmp = mutableListOf() + for (vertex in pair.value) { + tmp.add(vertex._val) + } + println("${pair.key._val}: $tmp,") + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[3]), + arrayOf(v[2], v[4]), + arrayOf(v[3], v[4]) + ) + val graph = GraphAdjList(edges) + println("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0]!!, v[2]!!) + println("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0]!!, v[1]!!) + println("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + val v5 = Vertex(6) + graph.addVertex(v5) + println("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]!!) + println("\n刪除頂點 3 後,圖為") + graph.print() +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt new file mode 100644 index 0000000000..11b580ba51 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.printMatrix + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat(vertices: IntArray, edges: Array) { + val vertices = mutableListOf() // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + val adjMat = mutableListOf>() // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + init { + // 新增頂點 + for (vertex in vertices) { + addVertex(vertex) + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (edge in edges) { + addEdge(edge[0], edge[1]) + } + } + + /* 獲取頂點數量 */ + fun size(): Int { + return vertices.size + } + + /* 新增頂點 */ + fun addVertex(_val: Int) { + val n = size() + // 向頂點串列中新增新頂點的值 + vertices.add(_val) + // 在鄰接矩陣中新增一行 + val newRow = mutableListOf() + for (j in 0..= size()) + throw IndexOutOfBoundsException() + // 在頂點串列中移除索引 index 的頂點 + vertices.removeAt(index) + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.removeAt(index) + // 在鄰接矩陣中刪除索引 index 的列 + for (row in adjMat) { + row.removeAt(index) + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + fun addEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + fun removeEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 列印鄰接矩陣 */ + fun print() { + print("頂點串列 = ") + println(vertices) + println("鄰接矩陣 =") + printMatrix(adjMat) + } +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + val vertices = intArrayOf(1, 3, 2, 5, 4) + val edges = arrayOf( + intArrayOf(0, 1), + intArrayOf(0, 3), + intArrayOf(1, 2), + intArrayOf(2, 3), + intArrayOf(2, 4), + intArrayOf(3, 4) + ) + val graph = GraphAdjMat(vertices, edges) + println("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2) + println("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1) + println("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + graph.addVertex(6) + println("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1) + println("\n刪除頂點 3 後,圖為") + graph.print() +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_bfs.kt b/zh-hant/codes/kotlin/chapter_graph/graph_bfs.kt new file mode 100644 index 0000000000..d76e3d8100 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_bfs.kt @@ -0,0 +1,65 @@ +/** + * File: graph_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex +import java.util.* + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { + // 頂點走訪序列 + val res = mutableListOf() + // 雜湊集合,用於記錄已被訪問過的頂點 + val visited = HashSet() + visited.add(startVet) + // 佇列用於實現 BFS + val que = LinkedList() + que.offer(startVet) + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!que.isEmpty()) { + val vet = que.poll() // 佇列首頂點出隊 + res.add(vet) // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // 跳過已被訪問的頂點 + que.offer(adjVet) // 只入列未訪問的頂點 + visited.add(adjVet) // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[1], v[4]), + arrayOf(v[2], v[5]), + arrayOf(v[3], v[4]), + arrayOf(v[3], v[6]), + arrayOf(v[4], v[5]), + arrayOf(v[4], v[7]), + arrayOf(v[5], v[8]), + arrayOf(v[6], v[7]), + arrayOf(v[7], v[8]) + ) + val graph = GraphAdjList(edges) + println("\n初始化後,圖為") + graph.print() + + /* 廣度優先走訪 */ + val res = graphBFS(graph, v[0]!!) + println("\n廣度優先走訪(BFS)頂點序列為") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_dfs.kt b/zh-hant/codes/kotlin/chapter_graph/graph_dfs.kt new file mode 100644 index 0000000000..2b0217035d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_dfs.kt @@ -0,0 +1,60 @@ +/** + * File: graph_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 深度優先走訪輔助函式 */ +fun dfs( + graph: GraphAdjList, + visited: MutableSet, + res: MutableList, + vet: Vertex? +) { + res.add(vet) // 記錄訪問頂點 + visited.add(vet) // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // 跳過已被訪問的頂點 + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet) + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { + // 頂點走訪序列 + val res = mutableListOf() + // 雜湊集合,用於記錄已被訪問過的頂點 + val visited = HashSet() + dfs(graph, visited, res, startVet) + return res +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[5]), + arrayOf(v[4], v[5]), + arrayOf(v[5], v[6]) + ) + val graph = GraphAdjList(edges) + println("\n初始化後,圖為") + graph.print() + + /* 深度優先走訪 */ + val res = graphDFS(graph, v[0]) + println("\n深度優先走訪(DFS)頂點序列為") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/coin_change_greedy.kt b/zh-hant/codes/kotlin/chapter_greedy/coin_change_greedy.kt new file mode 100644 index 0000000000..13e4310e51 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/coin_change_greedy.kt @@ -0,0 +1,53 @@ +/** + * File: coin_change_greedy.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* 零錢兌換:貪婪 */ +fun coinChangeGreedy(coins: IntArray, amt: Int): Int { + // 假設 coins 串列有序 + var am = amt + var i = coins.size - 1 + var count = 0 + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (am > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > am) { + i-- + } + // 選擇 coins[i] + am -= coins[i] + count++ + } + // 若未找到可行方案,則返回 -1 + return if (am == 0) count else -1 +} + +/* Driver Code */ +fun main() { + // 貪婪:能夠保證找到全域性最優解 + var coins = intArrayOf(1, 5, 10, 20, 50, 100) + var amt = 186 + var res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("湊到 $amt 所需的最少硬幣數量為 $res") + + // 貪婪:無法保證找到全域性最優解 + coins = intArrayOf(1, 20, 50) + amt = 60 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("湊到 $amt 所需的最少硬幣數量為 $res") + println("實際上需要的最少數量為 3 ,即 20 + 20 + 20") + + // 貪婪:無法保證找到全域性最優解 + coins = intArrayOf(1, 49, 50) + amt = 98 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("湊到 $amt 所需的最少硬幣數量為 $res") + println("實際上需要的最少數量為 2 ,即 49 + 49") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/fractional_knapsack.kt b/zh-hant/codes/kotlin/chapter_greedy/fractional_knapsack.kt new file mode 100644 index 0000000000..d876834986 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/fractional_knapsack.kt @@ -0,0 +1,51 @@ +/** + * File: fractional_knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* 物品 */ +class Item( + val w: Int, // 物品 + val v: Int // 物品價值 +) + +/* 分數背包:貪婪 */ +fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { + // 建立物品串列,包含兩個屬性:重量、價值 + var cap = c + val items = arrayOfNulls(wgt.size) + for (i in wgt.indices) { + items[i] = Item(wgt[i], _val[i]) + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } + // 迴圈貪婪選擇 + var res = 0.0 + for (item in items) { + if (item!!.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v + cap -= item.w + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += item.v.toDouble() / item.w * cap + // 已無剩餘容量,因此跳出迴圈 + break + } + } + return res +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + + // 貪婪演算法 + val res = fractionalKnapsack(wgt, _val, cap) + println("不超過背包容量的最大物品價值為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/max_capacity.kt b/zh-hant/codes/kotlin/chapter_greedy/max_capacity.kt new file mode 100644 index 0000000000..dab670e58b --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/max_capacity.kt @@ -0,0 +1,41 @@ +/** + * File: max_capacity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.max +import kotlin.math.min + +/* 最大容量:貪婪 */ +fun maxCapacity(ht: IntArray): Int { + // 初始化 i, j,使其分列陣列兩端 + var i = 0 + var j = ht.size - 1 + // 初始最大容量為 0 + var res = 0 + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + val cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 向內移動短板 + if (ht[i] < ht[j]) { + i++ + } else { + j-- + } + } + return res +} + +/* Driver Code */ +fun main() { + val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) + + // 貪婪演算法 + val res = maxCapacity(ht) + println("最大容量為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/max_product_cutting.kt b/zh-hant/codes/kotlin/chapter_greedy/max_product_cutting.kt new file mode 100644 index 0000000000..f5979a957d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/max_product_cutting.kt @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.pow + +/* 最大切分乘積:貪婪 */ +fun maxProductCutting(n: Int): Int { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1) + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + val a = n / 3 + val b = n % 3 + if (b == 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return 3.0.pow((a - 1)).toInt() * 2 * 2 + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return 3.0.pow(a).toInt() * 2 * 2 + } + // 當餘數為 0 時,不做處理 + return 3.0.pow(a).toInt() +} + +/* Driver Code */ +fun main() { + val n = 58 + + // 貪婪演算法 + val res = maxProductCutting(n) + println("最大切分乘積為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/array_hash_map.kt b/zh-hant/codes/kotlin/chapter_hashing/array_hash_map.kt new file mode 100644 index 0000000000..ff67f33453 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/array_hash_map.kt @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 鍵值對 */ +class Pair( + var key: Int, + var _val: String +) + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + // 初始化陣列,包含 100 個桶 + private val buckets = arrayOfNulls(100) + + /* 雜湊函式 */ + fun hashFunc(key: Int): Int { + val index = key % 100 + return index + } + + /* 查詢操作 */ + fun get(key: Int): String? { + val index = hashFunc(key) + val pair = buckets[index] ?: return null + return pair._val + } + + /* 新增操作 */ + fun put(key: Int, _val: String) { + val pair = Pair(key, _val) + val index = hashFunc(key) + buckets[index] = pair + } + + /* 刪除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + // 置為 null ,代表刪除 + buckets[index] = null + } + + /* 獲取所有鍵值對 */ + fun pairSet(): MutableList { + val pairSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + pairSet.add(pair) + } + return pairSet + } + + /* 獲取所有鍵 */ + fun keySet(): MutableList { + val keySet = mutableListOf() + for (pair in buckets) { + if (pair != null) + keySet.add(pair.key) + } + return keySet + } + + /* 獲取所有值 */ + fun valueSet(): MutableList { + val valueSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + valueSet.add(pair._val) + } + return valueSet + } + + /* 列印雜湊表 */ + fun print() { + for (kv in pairSet()) { + val key = kv.key + val _val = kv._val + println("$key -> $_val") + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化雜湊表 */ + val map = ArrayHashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈") + map.put(15937, "小囉") + map.put(16750, "小算") + map.put(13276, "小法") + map.put(10583, "小鴨") + println("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name = map.get(15937) + println("\n輸入學號 15937 ,查詢到姓名 $name") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583) + println("\n刪除 10583 後,雜湊表為\nKey -> Value") + map.print() + + /* 走訪雜湊表 */ + println("\n走訪鍵值對 Key -> Value") + for (kv in map.pairSet()) { + println("${kv.key} -> ${kv._val}") + } + println("\n單獨走訪鍵 Key") + for (key in map.keySet()) { + println(key) + } + println("\n單獨走訪值 Value") + for (_val in map.valueSet()) { + println(_val) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/built_in_hash.kt b/zh-hant/codes/kotlin/chapter_hashing/built_in_hash.kt new file mode 100644 index 0000000000..480044ee76 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/built_in_hash.kt @@ -0,0 +1,36 @@ +/** + * File: built_in_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.ListNode + +/* Driver Code */ +fun main() { + val num = 3 + val hashNum = num.hashCode() + println("整數 $num 的雜湊值為 $hashNum") + + val bol = true + val hashBol = bol.hashCode() + println("布林量 $bol 的雜湊值為 $hashBol") + + val dec = 3.14159 + val hashDec = dec.hashCode() + println("小數 $dec 的雜湊值為 $hashDec") + + val str = "Hello 演算法" + val hashStr = str.hashCode() + println("字串 $str 的雜湊值為 $hashStr") + + val arr = arrayOf(12836, "小哈") + val hashTup = arr.contentHashCode() + println("陣列 ${arr.contentToString()} 的雜湊值為 $hashTup") + + val obj = ListNode(0) + val hashObj = obj.hashCode() + println("節點物件 $obj 的雜湊值為 $hashObj") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/hash_map.kt b/zh-hant/codes/kotlin/chapter_hashing/hash_map.kt new file mode 100644 index 0000000000..72fb2e26dd --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/hash_map.kt @@ -0,0 +1,50 @@ +/** + * File: hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.printHashMap + +/* Driver Code */ +fun main() { + /* 初始化雜湊表 */ + val map = HashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + println("\n新增完成後,雜湊表為\nKey -> Value") + printHashMap(map) + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name = map[15937] + println("\n輸入學號 15937 ,查詢到姓名 $name") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583) + println("\n刪除 10583 後,雜湊表為\nKey -> Value") + printHashMap(map) + + /* 走訪雜湊表 */ + println("\n走訪鍵值對 Key->Value") + for ((key, value) in map) { + println("$key -> $value") + } + println("\n單獨走訪鍵 Key") + for (key in map.keys) { + println(key) + } + println("\n單獨走訪值 Value") + for (_val in map.values) { + println(_val) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/hash_map_chaining.kt b/zh-hant/codes/kotlin/chapter_hashing/hash_map_chaining.kt new file mode 100644 index 0000000000..b477a3613d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/hash_map_chaining.kt @@ -0,0 +1,145 @@ +/** + * File: hash_map_chaining.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + var size: Int // 鍵值對數量 + var capacity: Int // 雜湊表容量 + val loadThres: Double // 觸發擴容的負載因子閾值 + val extendRatio: Int // 擴容倍數 + var buckets: MutableList> // 桶陣列 + + /* 建構子 */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = mutableListOf() + for (i in 0.. loadThres) { + extend() + } + val index = hashFunc(key) + val bucket = buckets[index] + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (pair in bucket) { + if (pair.key == key) { + pair._val = _val + return + } + } + // 若無該 key ,則將鍵值對新增至尾部 + val pair = Pair(key, _val) + bucket.add(pair) + size++ + } + + /* 刪除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + val bucket = buckets[index] + // 走訪桶,從中刪除鍵值對 + for (pair in bucket) { + if (pair.key == key) { + bucket.remove(pair) + size-- + break + } + } + } + + /* 擴容雜湊表 */ + fun extend() { + // 暫存原雜湊表 + val bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + // mutablelist 無固定大小 + buckets = mutableListOf() + for (i in 0..() + for (pair in bucket) { + val k = pair.key + val v = pair._val + res.add("$k -> $v") + } + println(res) + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化雜湊表 */ + val map = HashMapChaining() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈") + map.put(15937, "小囉") + map.put(16750, "小算") + map.put(13276, "小法") + map.put(10583, "小鴨") + println("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name = map.get(13276) + println("\n輸入學號 13276 ,查詢到姓名 $name") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836) + println("\n刪除 12836 後,雜湊表為\nKey -> Value") + map.print() +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt b/zh-hant/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt new file mode 100644 index 0000000000..2810ea8d40 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt @@ -0,0 +1,161 @@ +/** + * File: hash_map_open_addressing.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private var size: Int // 鍵值對數量 + private var capacity: Int // 雜湊表容量 + private val loadThres: Double // 觸發擴容的負載因子閾值 + private val extendRatio: Int // 擴容倍數 + private var buckets: Array // 桶陣列 + private val TOMBSTONE: Pair // 刪除標記 + + /* 建構子 */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = arrayOfNulls(capacity) + TOMBSTONE = Pair(-1, "-1") + } + + /* 雜湊函式 */ + fun hashFunc(key: Int): Int { + return key % capacity + } + + /* 負載因子 */ + fun loadFactor(): Double { + return (size / capacity).toDouble() + } + + /* 搜尋 key 對應的桶索引 */ + fun findBucket(key: Int): Int { + var index = hashFunc(key) + var firstTombstone = -1 + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index]?.key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 返回移動後的桶索引 + } + return index // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity + } + // 若 key 不存在,則返回新增點的索引 + return if (firstTombstone == -1) index else firstTombstone + } + + /* 查詢操作 */ + fun get(key: Int): String? { + // 搜尋 key 對應的桶索引 + val index = findBucket(key) + // 若找到鍵值對,則返回對應 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index]?._val + } + // 若鍵值對不存在,則返回 null + return null + } + + /* 新增操作 */ + fun put(key: Int, _val: String) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend() + } + // 搜尋 key 對應的桶索引 + val index = findBucket(key) + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index]!!._val = _val + return + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = Pair(key, _val) + size++ + } + + /* 刪除操作 */ + fun remove(key: Int) { + // 搜尋 key 對應的桶索引 + val index = findBucket(key) + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE + size-- + } + } + + /* 擴容雜湊表 */ + fun extend() { + // 暫存原雜湊表 + val bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + buckets = arrayOfNulls(capacity) + size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair._val) + } + } + } + + /* 列印雜湊表 */ + fun print() { + for (pair in buckets) { + if (pair == null) { + println("null") + } else if (pair == TOMBSTONE) { + println("TOMESTOME") + } else { + println("${pair.key} -> ${pair._val}") + } + } + } +} + +/* Driver Code */ +fun main() { + // 初始化雜湊表 + val hashmap = HashMapOpenAddressing() + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小囉") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鴨") + println("\n新增完成後,雜湊表為\nKey -> Value") + hashmap.print() + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + val name = hashmap.get(13276) + println("\n輸入學號 13276 ,查詢到姓名 $name") + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750) + println("\n刪除 16750 後,雜湊表為\nKey -> Value") + hashmap.print() +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/simple_hash.kt b/zh-hant/codes/kotlin/chapter_hashing/simple_hash.kt new file mode 100644 index 0000000000..73c9b598f5 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/simple_hash.kt @@ -0,0 +1,64 @@ +/** + * File: simple_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 加法雜湊 */ +fun addHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* 乘法雜湊 */ +fun mulHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (31 * hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* 互斥或雜湊 */ +fun xorHash(key: String): Int { + var hash = 0 + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = hash xor c.code + } + return hash and MODULUS +} + +/* 旋轉雜湊 */ +fun rotHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS + } + return hash.toInt() +} + +/* Driver Code */ +fun main() { + val key = "Hello 演算法" + + var hash = addHash(key) + println("加法雜湊值為 $hash") + + hash = mulHash(key) + println("乘法雜湊值為 $hash") + + hash = xorHash(key) + println("互斥或雜湊值為 $hash") + + hash = rotHash(key) + println("旋轉雜湊值為 $hash") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_heap/heap.kt b/zh-hant/codes/kotlin/chapter_heap/heap.kt new file mode 100644 index 0000000000..5a43200320 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_heap/heap.kt @@ -0,0 +1,66 @@ +/** + * File: heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +fun testPush(heap: Queue, _val: Int) { + heap.offer(_val) // 元素入堆積 + print("\n元素 $_val 入堆積後\n") + printHeap(heap) +} + +fun testPop(heap: Queue) { + val _val = heap.poll() // 堆積頂元素出堆積 + print("\n堆積頂元素 $_val 出堆積後\n") + printHeap(heap) +} + +/* Driver Code */ +fun main() { + /* 初始化堆積 */ + // 初始化小頂堆積 + var minHeap = PriorityQueue() + + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + println("\n以下測試樣例為大頂堆積") + + /* 元素入堆積 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* 獲取堆積頂元素 */ + val peek = maxHeap.peek() + print("\n堆積頂元素為 $peek\n") + + /* 堆積頂元素出堆積 */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* 獲取堆積大小 */ + val size = maxHeap.size + print("\n堆積元素數量為 $size\n") + + /* 判斷堆積是否為空 */ + val isEmpty = maxHeap.isEmpty() + print("\n堆積是否為空 $isEmpty\n") + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + println("\n輸入串列並建立小頂堆積後") + printHeap(minHeap) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_heap/my_heap.kt b/zh-hant/codes/kotlin/chapter_heap/my_heap.kt new file mode 100644 index 0000000000..692b95fc7b --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_heap/my_heap.kt @@ -0,0 +1,160 @@ +/** + * File: my_heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* 大頂堆積 */ +class MaxHeap(nums: MutableList?) { + // 使用串列而非陣列,這樣無須考慮擴容問題 + private val maxHeap = mutableListOf() + + /* 建構子,根據輸入串列建堆積 */ + init { + // 將串列元素原封不動新增進堆積 + maxHeap.addAll(nums!!) + // 堆積化除葉節點以外的其他所有節點 + for (i in parent(size() - 1) downTo 0) { + siftDown(i) + } + } + + /* 獲取左子節點的索引 */ + private fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 獲取右子節點的索引 */ + private fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 獲取父節點的索引 */ + private fun parent(i: Int): Int { + return (i - 1) / 2 // 向下整除 + } + + /* 交換元素 */ + private fun swap(i: Int, j: Int) { + val temp = maxHeap[i] + maxHeap[i] = maxHeap[j] + maxHeap[j] = temp + } + + /* 獲取堆積大小 */ + fun size(): Int { + return maxHeap.size + } + + /* 判斷堆積是否為空 */ + fun isEmpty(): Boolean { + /* 判斷堆積是否為空 */ + return size() == 0 + } + + /* 訪問堆積頂元素 */ + fun peek(): Int { + return maxHeap[0] + } + + /* 元素入堆積 */ + fun push(_val: Int) { + // 新增節點 + maxHeap.add(_val) + // 從底至頂堆積化 + siftUp(size() - 1) + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private fun siftUp(it: Int) { + // Kotlin的函式參數不可變,因此建立臨時變數 + var i = it + while (true) { + // 獲取節點 i 的父節點 + val p = parent(i) + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) break + // 交換兩節點 + swap(i, p) + // 迴圈向上堆積化 + i = p + } + } + + /* 元素出堆積 */ + fun pop(): Int { + // 判空處理 + if (isEmpty()) throw IndexOutOfBoundsException() + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(0, size() - 1) + // 刪除節點 + val _val = maxHeap.removeAt(size() - 1) + // 從頂至底堆積化 + siftDown(0) + // 返回堆積頂元素 + return _val + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private fun siftDown(it: Int) { + // Kotlin的函式參數不可變,因此建立臨時變數 + var i = it + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + val l = left(i) + val r = right(i) + var ma = i + if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l + if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break + // 交換兩節點 + swap(i, ma) + // 迴圈向下堆積化 + i = ma + } + } + + /* 列印堆積(二元樹) */ + fun print() { + val queue = PriorityQueue { a: Int, b: Int -> b - a } + queue.addAll(maxHeap) + printHeap(queue) + } +} + +/* Driver Code */ +fun main() { + /* 初始化大頂堆積 */ + val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) + println("\n輸入串列並建堆積後") + maxHeap.print() + + /* 獲取堆積頂元素 */ + var peek = maxHeap.peek() + print("\n堆積頂元素為 $peek\n") + + /* 元素入堆積 */ + val _val = 7 + maxHeap.push(_val) + print("\n元素 $_val 入堆積後\n") + maxHeap.print() + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop() + print("\n堆積頂元素 $peek 出堆積後\n") + maxHeap.print() + + /* 獲取堆積大小 */ + val size = maxHeap.size() + print("\n堆積元素數量為 $size\n") + + /* 判斷堆積是否為空 */ + val isEmpty = maxHeap.isEmpty() + print("\n堆積是否為空 $isEmpty\n") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_heap/top_k.kt b/zh-hant/codes/kotlin/chapter_heap/top_k.kt new file mode 100644 index 0000000000..51bc703cce --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_heap/top_k.kt @@ -0,0 +1,38 @@ +/** + * File: top_k.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +fun topKHeap(nums: IntArray, k: Int): Queue { + // 初始化小頂堆積 + val heap = PriorityQueue() + // 將陣列的前 k 個元素入堆積 + for (i in 0.. heap.peek()) { + heap.poll() + heap.offer(nums[i]) + } + } + return heap +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 7, 6, 3, 2) + val k = 3 + val res = topKHeap(nums, k) + println("最大的 $k 個元素為") + printHeap(res) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/binary_search.kt b/zh-hant/codes/kotlin/chapter_searching/binary_search.kt new file mode 100644 index 0000000000..d146f1fe69 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/binary_search.kt @@ -0,0 +1,59 @@ +/** + * File: binary_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分搜尋(雙閉區間) */ +fun binarySearch(nums: IntArray, target: Int): Int { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + var i = 0 + var j = nums.size - 1 + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + val 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 +} + +/* 二分搜尋(左閉右開區間) */ +fun binarySearchLCRO(nums: IntArray, target: Int): Int { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + var i = 0 + var j = nums.size + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + val 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 +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + /* 二分搜尋(雙閉區間) */ + var index = binarySearch(nums, target) + println("目標元素 6 的索引 = $index") + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target) + println("目標元素 6 的索引 = $index") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/binary_search_edge.kt b/zh-hant/codes/kotlin/chapter_searching/binary_search_edge.kt new file mode 100644 index 0000000000..08eddce27f --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/binary_search_edge.kt @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分搜尋最左一個 target */ +fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { + // 等價於查詢 target 的插入點 + val i = binarySearchInsertion(nums, target) + // 未找到 target ,返回 -1 + if (i == nums.size || nums[i] != target) { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分搜尋最右一個 target */ +fun binarySearchRightEdge(nums: IntArray, target: Int): Int { + // 轉化為查詢最左一個 target + 1 + val i = binarySearchInsertion(nums, target + 1) + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + val j = i - 1 + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1 + } + // 找到 target ,返回索引 j + return j +} + +/* Driver Code */ +fun main() { + // 包含重複元素的陣列 + val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n陣列 nums = ${nums.contentToString()}") + + // 二分搜尋左邊界和右邊界 + for (target in intArrayOf(6, 7)) { + var index = binarySearchLeftEdge(nums, target) + println("最左一個元素 $target 的索引為 $index") + index = binarySearchRightEdge(nums, target) + println("最右一個元素 $target 的索引為 $index") + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/binary_search_insertion.kt b/zh-hant/codes/kotlin/chapter_searching/binary_search_insertion.kt new file mode 100644 index 0000000000..366d836044 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/binary_search_insertion.kt @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分搜尋插入點(無重複元素) */ +fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 初始化雙閉區間 [0, n-1] + while (i <= j) { + val 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 +} + +/* 二分搜尋插入點(存在重複元素) */ +fun binarySearchInsertion(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 初始化雙閉區間 [0, n-1] + while (i <= j) { + val 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 +} + +/* Driver Code */ +fun main() { + // 無重複元素的陣列 + var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + println("\n陣列 nums = ${nums.contentToString()}") + // 二分搜尋插入點 + for (target in intArrayOf(6, 9)) { + val index = binarySearchInsertionSimple(nums, target) + println("元素 $target 的插入點的索引為 $index") + } + + // 包含重複元素的陣列 + nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n陣列 nums = ${nums.contentToString()}") + + // 二分搜尋插入點 + for (target in intArrayOf(2, 6, 20)) { + val index = binarySearchInsertion(nums, target) + println("元素 $target 的插入點的索引為 $index") + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/hashing_search.kt b/zh-hant/codes/kotlin/chapter_searching/hashing_search.kt new file mode 100644 index 0000000000..cdde812dcb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/hashing_search.kt @@ -0,0 +1,49 @@ +/** + * File: hashing_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* 雜湊查詢(陣列) */ +fun hashingSearchArray(map: Map, target: Int): Int { + // 雜湊表的 key: 目標元素,_val: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.getOrDefault(target, -1) +} + +/* 雜湊查詢(鏈結串列) */ +fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { + // 雜湊表的 key: 目標節點值,_val: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.getOrDefault(target, null) +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* 雜湊查詢(陣列) */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + // 初始化雜湊表 + val map = HashMap() + for (i in nums.indices) { + map[nums[i]] = i // key: 元素,_val: 索引 + } + val index = hashingSearchArray(map, target) + println("目標元素 3 的索引 = $index") + + /* 雜湊查詢(鏈結串列) */ + var head = ListNode.arrToLinkedList(nums) + // 初始化雜湊表 + val map1 = HashMap() + while (head != null) { + map1[head._val] = head // key: 節點值,_val: 節點 + head = head.next + } + val node = hashingSearchLinkedList(map1, target) + println("目標節點值 3 的對應節點物件為 $node") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/linear_search.kt b/zh-hant/codes/kotlin/chapter_searching/linear_search.kt new file mode 100644 index 0000000000..586c639db2 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/linear_search.kt @@ -0,0 +1,50 @@ +/** + * File: linear_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* 線性查詢(陣列) */ +fun linearSearchArray(nums: IntArray, target: Int): Int { + // 走訪陣列 + for (i in nums.indices) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 線性查詢(鏈結串列) */ +fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { + // 走訪鏈結串列 + var head = h + while (head != null) { + // 找到目標節點,返回之 + if (head._val == target) + return head + head = head.next + } + // 未找到目標節點,返回 null + return null +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* 在陣列中執行線性查詢 */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + val index = linearSearchArray(nums, target) + println("目標元素 3 的索引 = $index") + + /* 在鏈結串列中執行線性查詢 */ + val head = ListNode.arrToLinkedList(nums) + val node = linearSearchLinkedList(head, target) + println("目標節點值 3 的對應節點物件為 $node") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/two_sum.kt b/zh-hant/codes/kotlin/chapter_searching/two_sum.kt new file mode 100644 index 0000000000..2eec6f9286 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/two_sum.kt @@ -0,0 +1,49 @@ +/** + * File: two_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 方法一:暴力列舉 */ +fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { + val size = nums.size + // 兩層迴圈,時間複雜度為 O(n^2) + for (i in 0..() + // 單層迴圈,時間複雜度為 O(n) + for (i in 0.. nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + } + } + } +} + +/* 泡沫排序(標誌最佳化) */ +fun bubbleSortWithFlag(nums: IntArray) { + // 外迴圈:未排序區間為 [0, i] + for (i in nums.size - 1 downTo 1) { + var flag = false // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (j in 0.. nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + flag = true // 記錄交換元素 + } + } + if (!flag) break // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSort(nums) + println("泡沫排序完成後 nums = ${nums.contentToString()}") + + val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSortWithFlag(nums1) + println("泡沫排序完成後 nums1 = ${nums1.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/bucket_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/bucket_sort.kt new file mode 100644 index 0000000000..0d88f7501e --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/bucket_sort.kt @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 桶排序 */ +fun bucketSort(nums: FloatArray) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + val k = nums.size / 2 + val buckets = mutableListOf>() + for (i in 0.. nums[ma]) + ma = l + if (r < n && nums[r] > nums[ma]) + ma = r + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) + break + // 交換兩節點 + val temp = nums[i] + nums[i] = nums[ma] + nums[ma] = temp + // 迴圈向下堆積化 + i = ma + } +} + +/* 堆積排序 */ +fun heapSort(nums: IntArray) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (i in nums.size / 2 - 1 downTo 0) { + siftDown(nums, nums.size, i) + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (i in nums.size - 1 downTo 1) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + val temp = nums[0] + nums[0] = nums[i] + nums[i] = temp + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0) + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + heapSort(nums) + println("堆積排序完成後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/insertion_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/insertion_sort.kt new file mode 100644 index 0000000000..10b224338c --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/insertion_sort.kt @@ -0,0 +1,29 @@ +/** + * File: insertion_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 插入排序 */ +fun insertionSort(nums: IntArray) { + //外迴圈: 已排序元素為 1, 2, ..., n + for (i in nums.indices) { + val base = nums[i] + var j = i - 1 + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位 + j-- + } + nums[j + 1] = base // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + insertionSort(nums) + println("插入排序完成後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/merge_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/merge_sort.kt new file mode 100644 index 0000000000..8b328e50eb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/merge_sort.kt @@ -0,0 +1,56 @@ +/** + * File: merge_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 合併左子陣列和右子陣列 */ +fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + val tmp = IntArray(right - left + 1) + // 初始化左子陣列和右子陣列的起始索引 + var i = left + var j = mid + 1 + var 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 (l in tmp.indices) { + nums[left + l] = tmp[l] + } +} + +/* 合併排序 */ +fun mergeSort(nums: IntArray, left: Int, right: Int) { + // 終止條件 + if (left >= right) return // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + val mid = left + (right - left) / 2 // 計算中點 + mergeSort(nums, left, mid) // 遞迴左子陣列 + mergeSort(nums, mid + 1, right) // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right) +} + +/* Driver Code */ +fun main() { + /* 合併排序 */ + val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) + mergeSort(nums, 0, nums.size - 1) + println("合併排序完成後 nums = ${nums.contentToString()}") +} diff --git a/zh-hant/codes/kotlin/chapter_sorting/quick_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/quick_sort.kt new file mode 100644 index 0000000000..f7758f3d4a --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/quick_sort.kt @@ -0,0 +1,121 @@ +/** + * File: quick_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 元素交換 */ +fun swap(nums: IntArray, i: Int, j: Int) { + val temp = nums[i] + nums[i] = nums[j] + nums[j] = temp +} + +/* 哨兵劃分 */ +fun partition(nums: IntArray, left: Int, right: Int): Int { + // 以 nums[left] 為基準數 + var i = left + var 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 // 返回基準數的索引 +} + +/* 快速排序 */ +fun quickSort(nums: IntArray, left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return + // 哨兵劃分 + val pivot = partition(nums, left, right) + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* 選取三個候選元素的中位數 */ +fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { + val l = nums[left] + val m = nums[mid] + val r = nums[right] + if ((m in l..r) || (m in r..l)) + return mid // m 在 l 和 r 之間 + if ((l in m..r) || (l in r..m)) + return left // l 在 m 和 r 之間 + return right +} + +/* 哨兵劃分(三數取中值) */ +fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { + // 選取三個候選元素的中位數 + val med = medianThree(nums, left, (left + right) / 2, right) + // 將中位數交換至陣列最左端 + swap(nums, left, med) + // 以 nums[left] 為基準數 + var i = left + var 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 // 返回基準數的索引 +} + +/* 快速排序 */ +fun quickSortMedian(nums: IntArray, left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return + // 哨兵劃分 + val pivot = partitionMedian(nums, left, right) + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* 快速排序(尾遞迴最佳化) */ +fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { + // 子陣列長度為 1 時終止 + var l = left + var r = right + while (l < r) { + // 哨兵劃分操作 + val pivot = partition(nums, l, r) + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - l < r - pivot) { + quickSort(nums, l, pivot - 1) // 遞迴排序左子陣列 + l = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, r) // 遞迴排序右子陣列 + r = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] + } + } +} + +/* Driver Code */ +fun main() { + /* 快速排序 */ + val nums = intArrayOf(2, 4, 1, 0, 3, 5) + quickSort(nums, 0, nums.size - 1) + println("快速排序完成後 nums = ${nums.contentToString()}") + + /* 快速排序(中位基準數最佳化) */ + val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortMedian(nums1, 0, nums1.size - 1) + println("快速排序(中位基準數最佳化)完成後 nums1 = ${nums1.contentToString()}") + + /* 快速排序(尾遞迴最佳化) */ + val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortTailCall(nums2, 0, nums2.size - 1) + println("快速排序(尾遞迴最佳化)完成後 nums2 = ${nums2.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/radix_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/radix_sort.kt new file mode 100644 index 0000000000..87a323ea94 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/radix_sort.kt @@ -0,0 +1,68 @@ +/** + * File: radix_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +fun digit(num: Int, exp: Int): Int { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10 +} + +/* 計數排序(根據 nums 第 k 位排序) */ +fun countingSortDigit(nums: IntArray, exp: Int) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + val counter = IntArray(10) + val n = nums.size + // 統計 0~9 各數字的出現次數 + for (i in 0.. m) m = num + var exp = 1 + // 按照從低位到高位的順序走訪 + while (exp <= m) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp) + exp *= 10 + } +} + +/* Driver Code */ +fun main() { + // 基數排序 + val nums = intArrayOf( + 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 + ) + radixSort(nums) + println("基數排序完成後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/selection_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/selection_sort.kt new file mode 100644 index 0000000000..704dc2c402 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/selection_sort.kt @@ -0,0 +1,32 @@ +/** + * File: selection_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 選擇排序 */ +fun selectionSort(nums: IntArray) { + val n = nums.size + // 外迴圈:未排序區間為 [i, n-1] + for (i in 0..() + + /* 獲取堆疊的長度 */ + fun size(): Int { + return stack.size + } + + /* 判斷堆疊是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入堆疊 */ + fun push(num: Int) { + stack.add(num) + } + + /* 出堆疊 */ + fun pop(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack.removeAt(size() - 1) + } + + /* 訪問堆疊頂元素 */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack[size() - 1] + } + + /* 將 List 轉化為 Array 並返回 */ + fun toArray(): Array { + return stack.toTypedArray() + } +} + +/* Driver Code */ +fun main() { + /* 初始化堆疊 */ + val stack = ArrayStack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("堆疊 stack = ${stack.toArray().contentToString()}") + + /* 訪問堆疊頂元素 */ + val peek = stack.peek() + println("堆疊頂元素 peek = $peek") + + /* 元素出堆疊 */ + val pop = stack.pop() + println("出堆疊元素 pop = $pop,出堆疊後 stack = ${stack.toArray().contentToString()}") + + /* 獲取堆疊的長度 */ + val size = stack.size() + println("堆疊的長度 size = $size") + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + println("堆疊是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/deque.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/deque.kt new file mode 100644 index 0000000000..3ed10abe80 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/deque.kt @@ -0,0 +1,45 @@ +/** + * File: deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化雙向佇列 */ + val deque = LinkedList() + deque.offerLast(3) + deque.offerLast(2) + deque.offerLast(5) + println("雙向佇列 deque = $deque") + + /* 訪問元素 */ + val peekFirst = deque.peekFirst() + println("佇列首元素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("佇列尾元素 peekLast = $peekLast") + + /* 元素入列 */ + deque.offerLast(4) + println("元素 4 佇列尾入列後 deque = $deque") + deque.offerFirst(1) + println("元素 1 佇列首入列後 deque = $deque") + + /* 元素出列 */ + val popLast = deque.pollLast() + println("佇列尾出列元素 = $popLast,佇列尾出列後 deque = $deque") + val popFirst = deque.pollFirst() + println("佇列首出列元素 = $popFirst,佇列首出列後 deque = $deque") + + /* 獲取雙向佇列的長度 */ + val size = deque.size + println("雙向佇列長度 size = $size") + + /* 判斷雙向佇列是否為空 */ + val isEmpty = deque.isEmpty() + println("雙向佇列是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt new file mode 100644 index 0000000000..d5ae2b229a --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt @@ -0,0 +1,163 @@ +/** + * File: linkedlist_deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 雙向鏈結串列節點 */ +class ListNode(var _val: Int) { + // 節點值 + var next: ListNode? = null // 後繼節點引用 + var prev: ListNode? = null // 前驅節點引用 +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private var front: ListNode? = null // 頭節點 front + private var rear: ListNode? = null // 尾節點 rear + private var queSize: Int = 0 // 雙向佇列的長度 + + /* 獲取雙向佇列的長度 */ + fun size(): Int { + return queSize + } + + /* 判斷雙向佇列是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入列操作 */ + fun push(num: Int, isFront: Boolean) { + val node = ListNode(num) + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (isEmpty()) { + rear = node + front = rear + // 佇列首入列操作 + } else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front?.prev = node + node.next = front + front = node // 更新頭節點 + // 佇列尾入列操作 + } else { + // 將 node 新增至鏈結串列尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾節點 + } + queSize++ // 更新佇列長度 + } + + /* 佇列首入列 */ + fun pushFirst(num: Int) { + push(num, true) + } + + /* 佇列尾入列 */ + fun pushLast(num: Int) { + push(num, false) + } + + /* 出列操作 */ + fun pop(isFront: Boolean): Int { + if (isEmpty()) + throw IndexOutOfBoundsException() + val _val: Int + // 佇列首出列操作 + if (isFront) { + _val = front!!._val // 暫存頭節點值 + // 刪除頭節點 + val fNext = front!!.next + if (fNext != null) { + fNext.prev = null + front!!.next = null + } + front = fNext // 更新頭節點 + // 佇列尾出列操作 + } else { + _val = rear!!._val // 暫存尾節點值 + // 刪除尾節點 + val rPrev = rear!!.prev + if (rPrev != null) { + rPrev.next = null + rear!!.prev = null + } + rear = rPrev // 更新尾節點 + } + queSize-- // 更新佇列長度 + return _val + } + + /* 佇列首出列 */ + fun popFirst(): Int { + return pop(true) + } + + /* 佇列尾出列 */ + fun popLast(): Int { + return pop(false) + } + + /* 訪問佇列首元素 */ + fun peekFirst(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* 訪問佇列尾元素 */ + fun peekLast(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return rear!!._val + } + + /* 返回陣列用於列印 */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化雙向佇列 */ + val deque = LinkedListDeque() + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + println("雙向佇列 deque = ${deque.toArray().contentToString()}") + + /* 訪問元素 */ + val peekFirst = deque.peekFirst() + println("佇列首元素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("佇列尾元素 peekLast = $peekLast") + + /* 元素入列 */ + deque.pushLast(4) + println("元素 4 佇列尾入列後 deque = ${deque.toArray().contentToString()}") + deque.pushFirst(1) + println("元素 1 佇列首入列後 deque = ${deque.toArray().contentToString()}") + + /* 元素出列 */ + val popLast = deque.popLast() + println("佇列尾出列元素 = ${popLast},佇列尾出列後 deque = ${deque.toArray().contentToString()}") + val popFirst = deque.popFirst() + println("佇列首出列元素 = ${popFirst},佇列首出列後 deque = ${deque.toArray().contentToString()}") + + /* 獲取雙向佇列的長度 */ + val size = deque.size() + println("雙向佇列長度 size = $size") + + /* 判斷雙向佇列是否為空 */ + val isEmpty = deque.isEmpty() + println("雙向佇列是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt new file mode 100644 index 0000000000..311de93ec5 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt @@ -0,0 +1,98 @@ +/** + * File: linkedlist_queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue( + // 頭節點 front ,尾節點 rear + private var front: ListNode? = null, + private var rear: ListNode? = null, + private var queSize: Int = 0 +) { + + /* 獲取佇列的長度 */ + fun size(): Int { + return queSize + } + + /* 判斷佇列是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入列 */ + fun push(num: Int) { + // 在尾節點後新增 num + val node = ListNode(num) + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == null) { + front = node + rear = node + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + rear?.next = node + rear = node + } + queSize++ + } + + /* 出列 */ + fun pop(): Int { + val num = peek() + // 刪除頭節點 + front = front?.next + queSize-- + return num + } + + /* 訪問佇列首元素 */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* 將鏈結串列轉化為 Array 並返回 */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化佇列 */ + val queue = LinkedListQueue() + + /* 元素入列 */ + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + println("佇列 queue = ${queue.toArray().contentToString()}") + + /* 訪問佇列首元素 */ + val peek = queue.peek() + println("佇列首元素 peek = $peek") + + /* 元素出列 */ + val pop = queue.pop() + println("出列元素 pop = $pop,出列後 queue = ${queue.toArray().contentToString()}") + + /* 獲取佇列的長度 */ + val size = queue.size() + println("佇列長度 size = $size") + + /* 判斷佇列是否為空 */ + val isEmpty = queue.isEmpty() + println("佇列是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt new file mode 100644 index 0000000000..edee3d99f0 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt @@ -0,0 +1,87 @@ +/** + * File: linkedlist_stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack( + private var stackPeek: ListNode? = null, // 將頭節點作為堆疊頂 + private var stkSize: Int = 0 // 堆疊的長度 +) { + + /* 獲取堆疊的長度 */ + fun size(): Int { + return stkSize + } + + /* 判斷堆疊是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入堆疊 */ + fun push(num: Int) { + val node = ListNode(num) + node.next = stackPeek + stackPeek = node + stkSize++ + } + + /* 出堆疊 */ + fun pop(): Int? { + val num = peek() + stackPeek = stackPeek?.next + stkSize-- + return num + } + + /* 訪問堆疊頂元素 */ + fun peek(): Int? { + if (isEmpty()) throw IndexOutOfBoundsException() + return stackPeek?._val + } + + /* 將 List 轉化為 Array 並返回 */ + fun toArray(): IntArray { + var node = stackPeek + val res = IntArray(size()) + for (i in res.size - 1 downTo 0) { + res[i] = node?._val!! + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化堆疊 */ + val stack = LinkedListStack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("堆疊 stack = ${stack.toArray().contentToString()}") + + /* 訪問堆疊頂元素 */ + val peek = stack.peek()!! + println("堆疊頂元素 peek = $peek") + + /* 元素出堆疊 */ + val pop = stack.pop()!! + println("出堆疊元素 pop = $pop,出堆疊後 stack = ${stack.toArray().contentToString()}") + + /* 獲取堆疊的長度 */ + val size = stack.size() + println("堆疊的長度 size = $size") + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + println("堆疊是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/queue.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/queue.kt new file mode 100644 index 0000000000..af5ef54ef4 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/queue.kt @@ -0,0 +1,39 @@ +/** + * File: queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化佇列 */ + val queue = LinkedList() + + /* 元素入列 */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + println("佇列 queue = $queue") + + /* 訪問佇列首元素 */ + val peek = queue.peek() + println("佇列首元素 peek = $peek") + + /* 元素出列 */ + val pop = queue.poll() + println("出列元素 pop = $pop,出列後 queue = $queue") + + /* 獲取佇列的長度 */ + val size = queue.size + println("佇列長度 size = $size") + + /* 判斷佇列是否為空 */ + val isEmpty = queue.isEmpty() + println("佇列是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/stack.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/stack.kt new file mode 100644 index 0000000000..866d1ac17e --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/stack.kt @@ -0,0 +1,39 @@ +/** + * File: stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化堆疊 */ + val stack = Stack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("堆疊 stack = $stack") + + /* 訪問堆疊頂元素 */ + val peek = stack.peek() + println("堆疊頂元素 peek = $peek") + + /* 元素出堆疊 */ + val pop = stack.pop() + println("出堆疊元素 pop = $pop,出堆疊後 stack = $stack") + + /* 獲取堆疊的長度 */ + val size = stack.size + println("堆疊的長度 size = $size") + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + println("堆疊是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/array_binary_tree.kt b/zh-hant/codes/kotlin/chapter_tree/array_binary_tree.kt new file mode 100644 index 0000000000..e10513797d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/array_binary_tree.kt @@ -0,0 +1,127 @@ +/** + * File: array_binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree(private val tree: MutableList) { + /* 串列容量 */ + fun size(): Int { + return tree.size + } + + /* 獲取索引為 i 節點的值 */ + fun _val(i: Int): Int? { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= size()) return null + return tree[i] + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 獲取索引為 i 節點的父節點的索引 */ + fun parent(i: Int): Int { + return (i - 1) / 2 + } + + /* 層序走訪 */ + fun levelOrder(): MutableList { + val res = mutableListOf() + // 直接走訪陣列 + for (i in 0..) { + // 若為空位,則返回 + if (_val(i) == null) + return + // 前序走訪 + if ("pre" == order) + res.add(_val(i)) + dfs(left(i), order, res) + // 中序走訪 + if ("in" == order) + res.add(_val(i)) + dfs(right(i), order, res) + // 後序走訪 + if ("post" == order) + res.add(_val(i)) + } + + /* 前序走訪 */ + fun preOrder(): MutableList { + val res = mutableListOf() + dfs(0, "pre", res) + return res + } + + /* 中序走訪 */ + fun inOrder(): MutableList { + val res = mutableListOf() + dfs(0, "in", res) + return res + } + + /* 後序走訪 */ + fun postOrder(): MutableList { + val res = mutableListOf() + dfs(0, "post", res) + return res + } +} + +/* Driver Code */ +fun main() { + // 初始化二元樹 + // 這裡藉助了一個從串列直接生成二元樹的函式 + val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) + + val root = TreeNode.listToTree(arr) + println("\n初始化二元樹\n") + println("二元樹的陣列表示:") + println(arr) + println("二元樹的鏈結串列表示:") + printTree(root) + + // 陣列表示下的二元樹類別 + val abt = ArrayBinaryTree(arr) + + // 訪問節點 + val i = 1 + val l = abt.left(i) + val r = abt.right(i) + val p = abt.parent(i) + println("當前節點的索引為 $i ,值為 ${abt._val(i)}") + println("其左子節點的索引為 $l ,值為 ${abt._val(l)}") + println("其右子節點的索引為 $r ,值為 ${abt._val(r)}") + println("其父節點的索引為 $p ,值為 ${abt._val(p)}") + + // 走訪樹 + var res = abt.levelOrder() + println("\n層序走訪為:$res") + res = abt.preOrder() + println("前序走訪為:$res") + res = abt.inOrder() + println("中序走訪為:$res") + res = abt.postOrder() + println("後序走訪為:$res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/avl_tree.kt b/zh-hant/codes/kotlin/chapter_tree/avl_tree.kt new file mode 100644 index 0000000000..127dc8c0c7 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/avl_tree.kt @@ -0,0 +1,223 @@ +/** + * File: avl_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import kotlin.math.max + +/* AVL 樹 */ +class AVLTree { + var root: TreeNode? = null // 根節點 + + /* 獲取節點高度 */ + fun height(node: TreeNode?): Int { + // 空節點高度為 -1 ,葉節點高度為 0 + return node?.height ?: -1 + } + + /* 更新節點高度 */ + private fun updateHeight(node: TreeNode?) { + // 節點高度等於最高子樹高度 + 1 + node?.height = max(height(node?.left), height(node?.right)) + 1 + } + + /* 獲取平衡因子 */ + fun balanceFactor(node: TreeNode?): Int { + // 空節點平衡因子為 0 + if (node == null) return 0 + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node.left) - height(node.right) + } + + /* 右旋操作 */ + private fun rightRotate(node: TreeNode?): TreeNode { + val child = node!!.left + val grandChild = child!!.right + // 以 child 為原點,將 node 向右旋轉 + child.right = node + node.left = grandChild + // 更新節點高度 + updateHeight(node) + updateHeight(child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 左旋操作 */ + private fun leftRotate(node: TreeNode?): TreeNode { + val child = node!!.right + val grandChild = child!!.left + // 以 child 為原點,將 node 向左旋轉 + child.left = node + node.right = grandChild + // 更新節點高度 + updateHeight(node) + updateHeight(child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private fun rotate(node: TreeNode): TreeNode { + // 獲取節點 node 的平衡因子 + val balanceFactor = balanceFactor(node) + // 左偏樹 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右旋 + return rightRotate(node) + } else { + // 先左旋後右旋 + node.left = leftRotate(node.left) + return rightRotate(node) + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左旋 + return leftRotate(node) + } else { + // 先右旋後左旋 + node.right = rightRotate(node.right) + return leftRotate(node) + } + } + // 平衡樹,無須旋轉,直接返回 + return node + } + + /* 插入節點 */ + fun insert(_val: Int) { + root = insertHelper(root, _val) + } + + /* 遞迴插入節點(輔助方法) */ + private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { + if (n == null) + return TreeNode(_val) + var node = n + /* 1. 查詢插入位置並插入節點 */ + if (_val < node._val) + node.left = insertHelper(node.left, _val) + else if (_val > node._val) + node.right = insertHelper(node.right, _val) + else + return node // 重複節點不插入,直接返回 + updateHeight(node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node) + // 返回子樹的根節點 + return node + } + + /* 刪除節點 */ + fun remove(_val: Int) { + root = removeHelper(root, _val) + } + + /* 遞迴刪除節點(輔助方法) */ + private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { + var node = n ?: return null + /* 1. 查詢節點並刪除 */ + if (_val < node._val) + node.left = removeHelper(node.left, _val) + else if (_val > node._val) + node.right = removeHelper(node.right, _val) + else { + if (node.left == null || node.right == null) { + val child = if (node.left != null) + node.left + else + node.right + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) + return null + // 子節點數量 = 1 ,直接刪除 node + else + node = child + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + var temp = node.right + while (temp!!.left != null) { + temp = temp.left + } + node.right = removeHelper(node.right, temp._val) + node._val = temp._val + } + } + updateHeight(node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node) + // 返回子樹的根節點 + return node + } + + /* 查詢節點 */ + fun search(_val: Int): TreeNode? { + var cur = root + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + cur = if (cur._val < _val) + cur.right!! + // 目標節點在 cur 的左子樹中 + else if (cur._val > _val) + cur.left + // 找到目標節點,跳出迴圈 + else + break + } + // 返回目標節點 + return cur + } +} + +fun testInsert(tree: AVLTree, _val: Int) { + tree.insert(_val) + println("\n插入節點 $_val 後,AVL 樹為") + printTree(tree.root) +} + +fun testRemove(tree: AVLTree, _val: Int) { + tree.remove(_val) + println("\n刪除節點 $_val 後,AVL 樹為") + printTree(tree.root) +} + +/* Driver Code */ +fun main() { + /* 初始化空 AVL 樹 */ + val avlTree = AVLTree() + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1) + testInsert(avlTree, 2) + testInsert(avlTree, 3) + testInsert(avlTree, 4) + testInsert(avlTree, 5) + testInsert(avlTree, 8) + testInsert(avlTree, 7) + testInsert(avlTree, 9) + testInsert(avlTree, 10) + testInsert(avlTree, 6) + + /* 插入重複節點 */ + testInsert(avlTree, 7) + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8) // 刪除度為 0 的節點 + testRemove(avlTree, 5) // 刪除度為 1 的節點 + testRemove(avlTree, 4) // 刪除度為 2 的節點 + + /* 查詢節點 */ + val node = avlTree.search(7) + println("\n 查詢到的節點物件為 $node,節點值 = ${node?._val}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_search_tree.kt b/zh-hant/codes/kotlin/chapter_tree/binary_search_tree.kt new file mode 100644 index 0000000000..fa20d8d70a --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_search_tree.kt @@ -0,0 +1,157 @@ +/** + * File: binary_search_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 二元搜尋樹 */ +class BinarySearchTree { + // 初始化空樹 + private var root: TreeNode? = null + + /* 獲取二元樹根節點 */ + fun getRoot(): TreeNode? { + return root + } + + /* 查詢節點 */ + fun search(num: Int): TreeNode? { + var cur = root + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + cur = if (cur._val < num) + cur.right + // 目標節點在 cur 的左子樹中 + else if (cur._val > num) + cur.left + // 找到目標節點,跳出迴圈 + else + break + } + // 返回目標節點 + return cur + } + + /* 插入節點 */ + fun insert(num: Int) { + // 若樹為空,則初始化根節點 + if (root == null) { + root = TreeNode(num) + return + } + var cur = root + var pre: TreeNode? = null + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur._val == num) + return + pre = cur + // 插入位置在 cur 的右子樹中 + cur = if (cur._val < num) + cur.right + // 插入位置在 cur 的左子樹中 + else + cur.left + } + // 插入節點 + val node = TreeNode(num) + if (pre?._val!! < num) + pre.right = node + else + pre.left = node + } + + /* 刪除節點 */ + fun remove(num: Int) { + // 若樹為空,直接提前返回 + if (root == null) + return + var cur = root + var pre: TreeNode? = null + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur._val == num) + break + pre = cur + // 待刪除節點在 cur 的右子樹中 + cur = if (cur._val < num) + cur.right + // 待刪除節點在 cur 的左子樹中 + else + cur.left + } + // 若無待刪除節點,則直接返回 + if (cur == null) + return + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + val child = if (cur.left != null) + cur.left + else + cur.right + // 刪除節點 cur + if (cur != root) { + if (pre!!.left == cur) + pre.left = child + else + pre.right = child + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child + } + // 子節點數量 = 2 + } else { + // 獲取中序走訪中 cur 的下一個節點 + var tmp = cur.right + while (tmp!!.left != null) { + tmp = tmp.left + } + // 遞迴刪除節點 tmp + remove(tmp._val) + // 用 tmp 覆蓋 cur + cur._val = tmp._val + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化二元搜尋樹 */ + val bst = BinarySearchTree() + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) + for (num in nums) { + bst.insert(num) + } + println("\n初始化的二元樹為\n") + printTree(bst.getRoot()) + + /* 查詢節點 */ + val node = bst.search(7) + println("查詢到的節點物件為 $node,節點值 = ${node?._val}") + + /* 插入節點 */ + bst.insert(16) + println("\n插入節點 16 後,二元樹為\n") + printTree(bst.getRoot()) + + /* 刪除節點 */ + bst.remove(1) + println("\n刪除節點 1 後,二元樹為\n") + printTree(bst.getRoot()) + bst.remove(2) + println("\n刪除節點 2 後,二元樹為\n") + printTree(bst.getRoot()) + bst.remove(4) + println("\n刪除節點 4 後,二元樹為\n") + printTree(bst.getRoot()) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_tree.kt b/zh-hant/codes/kotlin/chapter_tree/binary_tree.kt new file mode 100644 index 0000000000..79a4130c94 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_tree.kt @@ -0,0 +1,40 @@ +/** + * File: binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* Driver Code */ +fun main() { + /* 初始化二元樹 */ + // 初始化節點 + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + println("\n初始化二元樹\n") + printTree(n1) + + /* 插入與刪除節點 */ + val P = TreeNode(0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + println("\n插入節點 P 後\n") + printTree(n1) + // 刪除節點 P + n1.left = n2 + println("\n刪除節點 P 後\n") + printTree(n1) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_tree_bfs.kt b/zh-hant/codes/kotlin/chapter_tree/binary_tree_bfs.kt new file mode 100644 index 0000000000..ed9e9910ea --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_tree_bfs.kt @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import java.util.* + +/* 層序走訪 */ +fun levelOrder(root: TreeNode?): MutableList { + // 初始化佇列,加入根節點 + val queue = LinkedList() + queue.add(root) + // 初始化一個串列,用於儲存走訪序列 + val list = mutableListOf() + while (queue.isNotEmpty()) { + val node = queue.poll() // 隊列出隊 + list.add(node?._val!!) // 儲存節點值 + if (node.left != null) + queue.offer(node.left) // 左子節點入列 + if (node.right != null) + queue.offer(node.right) // 右子節點入列 + } + return list +} + +/* Driver Code */ +fun main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從串列直接生成二元樹的函式 + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n初始化二元樹\n") + printTree(root) + + /* 層序走訪 */ + val list = levelOrder(root) + println("\n層序走訪的節點列印序列 = $list") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_tree_dfs.kt b/zh-hant/codes/kotlin/chapter_tree/binary_tree_dfs.kt new file mode 100644 index 0000000000..25ca552a27 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_tree_dfs.kt @@ -0,0 +1,64 @@ +/** + * File: binary_tree_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +// 初始化串列,用於儲存走訪序列 +var list = mutableListOf() + +/* 前序走訪 */ +fun preOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.add(root._val) + preOrder(root.left) + preOrder(root.right) +} + +/* 中序走訪 */ +fun inOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left) + list.add(root._val) + inOrder(root.right) +} + +/* 後序走訪 */ +fun postOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left) + postOrder(root.right) + list.add(root._val) +} + +/* Driver Code */ +fun main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從串列直接生成二元樹的函式 + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n初始化二元樹\n") + printTree(root) + + /* 前序走訪 */ + list.clear() + preOrder(root) + println("\n前序走訪的節點列印序列 = $list") + + /* 中序走訪 */ + list.clear() + inOrder(root) + println("\n中序走訪的節點列印序列 = $list") + + /* 後序走訪 */ + list.clear() + postOrder(root) + println("\n後序走訪的節點列印序列 = $list") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/utils/ListNode.kt b/zh-hant/codes/kotlin/utils/ListNode.kt new file mode 100644 index 0000000000..0bd7015969 --- /dev/null +++ b/zh-hant/codes/kotlin/utils/ListNode.kt @@ -0,0 +1,25 @@ +/** + * File: ListNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 鏈結串列節點 */ +class ListNode(var _val: Int) { + var next: ListNode? = null + + companion object { + /* 將串列反序列化為鏈結串列 */ + fun arrToLinkedList(arr: IntArray): ListNode? { + val dum = ListNode(0) + var head = dum + for (_val in arr) { + head.next = ListNode(_val) + head = head.next!! + } + return dum.next + } + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/utils/PrintUtil.kt b/zh-hant/codes/kotlin/utils/PrintUtil.kt new file mode 100644 index 0000000000..1f0fa147d4 --- /dev/null +++ b/zh-hant/codes/kotlin/utils/PrintUtil.kt @@ -0,0 +1,107 @@ +/** + * File: PrintUtil.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +import java.util.* + +class Trunk(var prev: Trunk?, var str: String) + +/* 列印矩陣(Array) */ +fun printMatrix(matrix: Array>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 列印矩陣(List) */ +fun printMatrix(matrix: MutableList>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 列印鏈結串列 */ +fun printLinkedList(h: ListNode?) { + var head = h + val list = mutableListOf() + while (head != null) { + list.add(head._val.toString()) + head = head.next + } + println(list.joinToString(separator = " -> ")) +} + +/* 列印二元樹 */ +fun printTree(root: TreeNode?) { + printTree(root, null, false) +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { + if (root == null) { + return + } + + var prevStr = " " + val trunk = Trunk(prev, prevStr) + + printTree(root.right, trunk, true) + + if (prev == null) { + trunk.str = "———" + } else if (isRight) { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + + showTrunks(trunk) + println(" ${root._val}") + + if (prev != null) { + prev.str = prevStr + } + trunk.str = " |" + + printTree(root.left, trunk, false) +} + +fun showTrunks(p: Trunk?) { + if (p == null) { + return + } + showTrunks(p.prev) + print(p.str) +} + +/* 列印雜湊表 */ +fun printHashMap(map: Map) { + for ((key, value) in map) { + println("${key.toString()} -> $value") + } +} + +/* 列印堆積 */ +fun printHeap(queue: Queue?) { + val list = mutableListOf() + queue?.let { list.addAll(it) } + print("堆積的陣列表示:") + println(list) + println("堆積的樹狀表示:") + val root = TreeNode.listToTree(list) + printTree(root) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/utils/TreeNode.kt b/zh-hant/codes/kotlin/utils/TreeNode.kt new file mode 100644 index 0000000000..b85e2bdb31 --- /dev/null +++ b/zh-hant/codes/kotlin/utils/TreeNode.kt @@ -0,0 +1,69 @@ +/** + * File: TreeNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 二元樹節點類別 */ +/* 建構子 */ +class TreeNode( + var _val: Int // 節點值 +) { + var height: Int = 0 // 節點高度 + var left: TreeNode? = null // 左子節點引用 + var right: TreeNode? = null // 右子節點引用 + + // 序列化編碼規則請參考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二元樹的陣列表示: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二元樹的鏈結串列表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 將串列反序列化為二元樹:遞迴 */ + companion object { + private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { + if (i < 0 || i >= arr.size || arr[i] == null) { + return null + } + val root = TreeNode(arr[i]!!) + root.left = listToTreeDFS(arr, 2 * i + 1) + root.right = listToTreeDFS(arr, 2 * i + 2) + return root + } + + /* 將串列反序列化為二元樹 */ + fun listToTree(arr: MutableList): TreeNode? { + return listToTreeDFS(arr, 0) + } + + /* 將二元樹序列化為串列:遞迴 */ + private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { + if (root == null) return + while (i >= res.size) { + res.add(null) + } + res[i] = root._val + treeToListDFS(root.left, 2 * i + 1, res) + treeToListDFS(root.right, 2 * i + 2, res) + } + + /* 將二元樹序列化為串列 */ + fun treeToList(root: TreeNode?): MutableList { + val res = mutableListOf() + treeToListDFS(root, 0, res) + return res + } + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/utils/Vertex.kt b/zh-hant/codes/kotlin/utils/Vertex.kt new file mode 100644 index 0000000000..bc04d6dbfc --- /dev/null +++ b/zh-hant/codes/kotlin/utils/Vertex.kt @@ -0,0 +1,30 @@ +/** + * File: Vertex.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 頂點類別 */ +class Vertex(val _val: Int) { + companion object { + /* 輸入值串列 vals ,返回頂點串列 vets */ + fun valsToVets(vals: IntArray): Array { + val vets = arrayOfNulls(vals.size) + for (i in vals.indices) { + vets[i] = Vertex(vals[i]) + } + return vets + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + fun vetsToVals(vets: MutableList): MutableList { + val vals = mutableListOf() + for (vet in vets) { + vals.add(vet!!._val) + } + return vals + } + } +} \ No newline at end of file diff --git a/zh-hant/codes/python/.gitignore b/zh-hant/codes/python/.gitignore new file mode 100644 index 0000000000..bee8a64b79 --- /dev/null +++ b/zh-hant/codes/python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/array.py b/zh-hant/codes/python/chapter_array_and_linkedlist/array.py new file mode 100644 index 0000000000..9a9e2a7e0d --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/array.py @@ -0,0 +1,100 @@ +""" +File: array.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_access(nums: list[int]) -> int: + """隨機訪問元素""" + # 在區間 [0, len(nums)-1] 中隨機抽取一個數字 + random_index = random.randint(0, len(nums) - 1) + # 獲取並返回隨機元素 + random_num = nums[random_index] + return random_num + + +# 請注意,Python 的 list 是動態陣列,可以直接擴展 +# 為了方便學習,本函式將 list 看作長度不可變的陣列 +def extend(nums: list[int], enlarge: int) -> list[int]: + """擴展陣列長度""" + # 初始化一個擴展長度後的陣列 + res = [0] * (len(nums) + enlarge) + # 將原陣列中的所有元素複製到新陣列 + for i in range(len(nums)): + res[i] = nums[i] + # 返回擴展後的新陣列 + return res + + +def insert(nums: list[int], num: int, index: int): + """在陣列的索引 index 處插入元素 num""" + # 把索引 index 以及之後的所有元素向後移動一位 + for i in range(len(nums) - 1, index, -1): + nums[i] = nums[i - 1] + # 將 num 賦給 index 處的元素 + nums[index] = num + + +def remove(nums: list[int], index: int): + """刪除索引 index 處的元素""" + # 把索引 index 之後的所有元素向前移動一位 + for i in range(index, len(nums) - 1): + nums[i] = nums[i + 1] + + +def traverse(nums: list[int]): + """走訪陣列""" + count = 0 + # 透過索引走訪陣列 + for i in range(len(nums)): + count += nums[i] + # 直接走訪陣列元素 + for num in nums: + count += num + # 同時走訪資料索引和元素 + for i, num in enumerate(nums): + count += nums[i] + count += num + + +def find(nums: list[int], target: int) -> int: + """在陣列中查詢指定元素""" + for i in range(len(nums)): + if nums[i] == target: + return i + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化陣列 + arr = [0] * 5 + print("陣列 arr =", arr) + nums = [1, 3, 2, 5, 4] + print("陣列 nums =", nums) + + # 隨機訪問 + random_num: int = random_access(nums) + print("在 nums 中獲取隨機元素", random_num) + + # 長度擴展 + nums: list[int] = extend(nums, 3) + print("將陣列長度擴展至 8 ,得到 nums =", nums) + + # 插入元素 + insert(nums, 6, 3) + print("在索引 3 處插入數字 6 ,得到 nums =", nums) + + # 刪除元素 + remove(nums, 2) + print("刪除索引 2 處的元素,得到 nums =", nums) + + # 走訪陣列 + traverse(nums) + + # 查詢元素 + index: int = find(nums, 3) + print("在 nums 中查詢元素 3 ,得到索引 =", index) diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/linked_list.py b/zh-hant/codes/python/chapter_array_and_linkedlist/linked_list.py new file mode 100644 index 0000000000..9ec65d0153 --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -0,0 +1,85 @@ +""" +File: linked_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, print_linked_list + + +def insert(n0: ListNode, P: ListNode): + """在鏈結串列的節點 n0 之後插入節點 P""" + n1 = n0.next + P.next = n1 + n0.next = P + + +def remove(n0: ListNode): + """刪除鏈結串列的節點 n0 之後的首個節點""" + if not n0.next: + return + # n0 -> P -> n1 + P = n0.next + n1 = P.next + n0.next = n1 + + +def access(head: ListNode, index: int) -> ListNode | None: + """訪問鏈結串列中索引為 index 的節點""" + for _ in range(index): + if not head: + return None + head = head.next + return head + + +def find(head: ListNode, target: int) -> int: + """在鏈結串列中查詢值為 target 的首個節點""" + index = 0 + while head: + if head.val == target: + return index + head = head.next + index += 1 + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化鏈結串列 + # 初始化各個節點 + n0 = ListNode(1) + n1 = ListNode(3) + n2 = ListNode(2) + n3 = ListNode(5) + n4 = ListNode(4) + # 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("初始化的鏈結串列為") + print_linked_list(n0) + + # 插入節點 + p = ListNode(0) + insert(n0, p) + print("插入節點後的鏈結串列為") + print_linked_list(n0) + + # 刪除節點 + remove(n0) + print("刪除節點後的鏈結串列為") + print_linked_list(n0) + + # 訪問節點 + node: ListNode = access(n0, 3) + print("鏈結串列中索引 3 處的節點的值 = {}".format(node.val)) + + # 查詢節點 + index: int = find(n0, 2) + print("鏈結串列中值為 2 的節點的索引 = {}".format(index)) diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/list.py b/zh-hant/codes/python/chapter_array_and_linkedlist/list.py new file mode 100644 index 0000000000..4d2b31ad26 --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/list.py @@ -0,0 +1,56 @@ +""" +File: list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # 初始化串列 + nums: list[int] = [1, 3, 2, 5, 4] + print("\n串列 nums =", nums) + + # 訪問元素 + x: int = nums[1] + print("\n訪問索引 1 處的元素,得到 x =", x) + + # 更新元素 + nums[1] = 0 + print("\n將索引 1 處的元素更新為 0 ,得到 nums =", nums) + + # 清空串列 + nums.clear() + print("\n清空串列後 nums =", nums) + + # 在尾部新增元素 + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("\n新增元素後 nums =", nums) + + # 在中間插入元素 + nums.insert(3, 6) + print("\n在索引 3 處插入數字 6 ,得到 nums =", nums) + + # 刪除元素 + nums.pop(3) + print("\n刪除索引 3 處的元素,得到 nums =", nums) + + # 透過索引走訪串列 + count = 0 + for i in range(len(nums)): + count += nums[i] + # 直接走訪串列元素 + for num in nums: + count += num + + # 拼接兩個串列 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + print("\n將串列 nums1 拼接到 nums 之後,得到 nums =", nums) + + # 排序串列 + nums.sort() + print("\n排序串列後 nums =", nums) diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/my_list.py b/zh-hant/codes/python/chapter_array_and_linkedlist/my_list.py new file mode 100644 index 0000000000..553bbeb13e --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/my_list.py @@ -0,0 +1,118 @@ +""" +File: my_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +class MyList: + """串列類別""" + + def __init__(self): + """建構子""" + self._capacity: int = 10 # 串列容量 + self._arr: list[int] = [0] * self._capacity # 陣列(儲存串列元素) + self._size: int = 0 # 串列長度(當前元素數量) + self._extend_ratio: int = 2 # 每次串列擴容的倍數 + + def size(self) -> int: + """獲取串列長度(當前元素數量)""" + return self._size + + def capacity(self) -> int: + """獲取串列容量""" + return self._capacity + + def get(self, index: int) -> int: + """訪問元素""" + # 索引如果越界,則丟擲異常,下同 + if index < 0 or index >= self._size: + raise IndexError("索引越界") + return self._arr[index] + + def set(self, num: int, index: int): + """更新元素""" + if index < 0 or index >= self._size: + raise IndexError("索引越界") + self._arr[index] = num + + def add(self, num: int): + """在尾部新增元素""" + # 元素數量超出容量時,觸發擴容機制 + if self.size() == self.capacity(): + self.extend_capacity() + self._arr[self._size] = num + self._size += 1 + + def insert(self, num: int, index: int): + """在中間插入元素""" + if index < 0 or index >= self._size: + raise IndexError("索引越界") + # 元素數量超出容量時,觸發擴容機制 + if self._size == self.capacity(): + self.extend_capacity() + # 將索引 index 以及之後的元素都向後移動一位 + for j in range(self._size - 1, index - 1, -1): + self._arr[j + 1] = self._arr[j] + self._arr[index] = num + # 更新元素數量 + self._size += 1 + + def remove(self, index: int) -> int: + """刪除元素""" + if index < 0 or index >= self._size: + raise IndexError("索引越界") + num = self._arr[index] + # 將索引 index 之後的元素都向前移動一位 + for j in range(index, self._size - 1): + self._arr[j] = self._arr[j + 1] + # 更新元素數量 + self._size -= 1 + # 返回被刪除的元素 + return num + + def extend_capacity(self): + """串列擴容""" + # 新建一個長度為原陣列 _extend_ratio 倍的新陣列,並將原陣列複製到新陣列 + self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1) + # 更新串列容量 + self._capacity = len(self._arr) + + def to_array(self) -> list[int]: + """返回有效長度的串列""" + return self._arr[: self._size] + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化串列 + nums = MyList() + # 在尾部新增元素 + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + print(f"串列 nums = {nums.to_array()} ,容量 = {nums.capacity()} ,長度 = {nums.size()}") + + # 在中間插入元素 + nums.insert(6, index=3) + print("在索引 3 處插入數字 6 ,得到 nums =", nums.to_array()) + + # 刪除元素 + nums.remove(3) + print("刪除索引 3 處的元素,得到 nums =", nums.to_array()) + + # 訪問元素 + num = nums.get(1) + print("訪問索引 1 處的元素,得到 num =", num) + + # 更新元素 + nums.set(0, 1) + print("將索引 1 處的元素更新為 0 ,得到 nums =", nums.to_array()) + + # 測試擴容機制 + for i in range(10): + # 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i) + print(f"擴容後的串列 {nums.to_array()} ,容量 = {nums.capacity()} ,長度 = {nums.size()}") diff --git a/zh-hant/codes/python/chapter_backtracking/n_queens.py b/zh-hant/codes/python/chapter_backtracking/n_queens.py new file mode 100644 index 0000000000..3ec29d3deb --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/n_queens.py @@ -0,0 +1,62 @@ +""" +File: n_queens.py +Created Time: 2023-04-26 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + row: int, + n: int, + state: list[list[str]], + res: list[list[list[str]]], + cols: list[bool], + diags1: list[bool], + diags2: list[bool], +): + """回溯演算法:n 皇后""" + # 當放置完所有行時,記錄解 + if row == n: + res.append([list(row) for row in state]) + return + # 走訪所有列 + for col in range(n): + # 計算該格子對應的主對角線和次對角線 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if not cols[col] and not diags1[diag1] and not diags2[diag2]: + # 嘗試:將皇后放置在該格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = True + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:將該格子恢復為空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = False + + +def n_queens(n: int) -> list[list[list[str]]]: + """求解 n 皇后""" + # 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + state = [["#" for _ in range(n)] for _ in range(n)] + cols = [False] * n # 記錄列是否有皇后 + diags1 = [False] * (2 * n - 1) # 記錄主對角線上是否有皇后 + diags2 = [False] * (2 * n - 1) # 記錄次對角線上是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 4 + res = n_queens(n) + + print(f"輸入棋盤長寬為 {n}") + print(f"皇后放置方案共有 {len(res)} 種") + for state in res: + print("--------------------") + for row in state: + print(row) diff --git a/zh-hant/codes/python/chapter_backtracking/permutations_i.py b/zh-hant/codes/python/chapter_backtracking/permutations_i.py new file mode 100644 index 0000000000..6b371cd4eb --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/permutations_i.py @@ -0,0 +1,44 @@ +""" +File: permutations_i.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """回溯演算法:全排列 I""" + # 當狀態長度等於元素數量時,記錄解 + if len(state) == len(choices): + res.append(list(state)) + return + # 走訪所有選擇 + for i, choice in enumerate(choices): + # 剪枝:不允許重複選擇元素 + if not selected[i]: + # 嘗試:做出選擇,更新狀態 + selected[i] = True + state.append(choice) + # 進行下一輪選擇 + backtrack(state, choices, selected, res) + # 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = False + state.pop() + + +def permutations_i(nums: list[int]) -> list[list[int]]: + """全排列 I""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 3] + + res = permutations_i(nums) + + print(f"輸入陣列 nums = {nums}") + print(f"所有排列 res = {res}") diff --git a/zh-hant/codes/python/chapter_backtracking/permutations_ii.py b/zh-hant/codes/python/chapter_backtracking/permutations_ii.py new file mode 100644 index 0000000000..2206d801c8 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/permutations_ii.py @@ -0,0 +1,46 @@ +""" +File: permutations_ii.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """回溯演算法:全排列 II""" + # 當狀態長度等於元素數量時,記錄解 + if len(state) == len(choices): + res.append(list(state)) + return + # 走訪所有選擇 + duplicated = set[int]() + for i, choice in enumerate(choices): + # 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if not selected[i] and choice not in duplicated: + # 嘗試:做出選擇,更新狀態 + duplicated.add(choice) # 記錄選擇過的元素值 + selected[i] = True + state.append(choice) + # 進行下一輪選擇 + backtrack(state, choices, selected, res) + # 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = False + state.pop() + + +def permutations_ii(nums: list[int]) -> list[list[int]]: + """全排列 II""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 2] + + res = permutations_ii(nums) + + print(f"輸入陣列 nums = {nums}") + print(f"所有排列 res = {res}") diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_i_compact.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_i_compact.py new file mode 100644 index 0000000000..cdaa27a954 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_i_compact.py @@ -0,0 +1,36 @@ +""" +File: preorder_traversal_i_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序走訪:例題一""" + if root is None: + return + if root.val == 7: + # 記錄解 + res.append(root) + pre_order(root.left) + pre_order(root.right) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + print_tree(root) + + # 前序走訪 + res = list[TreeNode]() + pre_order(root) + + print("\n輸出所有值為 7 的節點") + print([node.val for node in res]) diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py new file mode 100644 index 0000000000..2550721265 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py @@ -0,0 +1,42 @@ +""" +File: preorder_traversal_ii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序走訪:例題二""" + if root is None: + return + # 嘗試 + path.append(root) + if root.val == 7: + # 記錄解 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + print_tree(root) + + # 前序走訪 + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\n輸出所有根節點到節點 7 的路徑") + for path in res: + print([node.val for node in path]) diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py new file mode 100644 index 0000000000..d12b8a5d7f --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -0,0 +1,43 @@ +""" +File: preorder_traversal_iii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序走訪:例題三""" + # 剪枝 + if root is None or root.val == 3: + return + # 嘗試 + path.append(root) + if root.val == 7: + # 記錄解 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + print_tree(root) + + # 前序走訪 + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for path in res: + print([node.val for node in path]) diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_template.py new file mode 100644 index 0000000000..2f9d07be25 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -0,0 +1,71 @@ +""" +File: preorder_traversal_iii_template.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def is_solution(state: list[TreeNode]) -> bool: + """判斷當前狀態是否為解""" + return state and state[-1].val == 7 + + +def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): + """記錄解""" + res.append(list(state)) + + +def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: + """判斷在當前狀態下,該選擇是否合法""" + return choice is not None and choice.val != 3 + + +def make_choice(state: list[TreeNode], choice: TreeNode): + """更新狀態""" + state.append(choice) + + +def undo_choice(state: list[TreeNode], choice: TreeNode): + """恢復狀態""" + state.pop() + + +def backtrack( + state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] +): + """回溯演算法:例題三""" + # 檢查是否為解 + if is_solution(state): + # 記錄解 + record_solution(state, res) + # 走訪所有選擇 + for choice in choices: + # 剪枝:檢查選擇是否合法 + if is_valid(state, choice): + # 嘗試:做出選擇,更新狀態 + make_choice(state, choice) + # 進行下一輪選擇 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + print_tree(root) + + # 回溯演算法 + res = [] + backtrack(state=[], choices=[root], res=res) + + print("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點") + for path in res: + print([node.val for node in path]) diff --git a/zh-hant/codes/python/chapter_backtracking/subset_sum_i.py b/zh-hant/codes/python/chapter_backtracking/subset_sum_i.py new file mode 100644 index 0000000000..898554710e --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/subset_sum_i.py @@ -0,0 +1,48 @@ +""" +File: subset_sum_i.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """回溯演算法:子集和 I""" + # 子集和等於 target 時,記錄解 + if target == 0: + res.append(list(state)) + return + # 走訪所有選擇 + # 剪枝二:從 start 開始走訪,避免生成重複子集 + for i in range(start, len(choices)): + # 剪枝一:若子集和超過 target ,則直接結束迴圈 + # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0: + break + # 嘗試:做出選擇,更新 target, start + state.append(choices[i]) + # 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop() + + +def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 I""" + state = [] # 狀態(子集) + nums.sort() # 對 nums 進行排序 + start = 0 # 走訪起始點 + res = [] # 結果串列(子集串列) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + print(f"輸入陣列 nums = {nums}, target = {target}") + print(f"所有和等於 {target} 的子集 res = {res}") diff --git a/zh-hant/codes/python/chapter_backtracking/subset_sum_i_naive.py b/zh-hant/codes/python/chapter_backtracking/subset_sum_i_naive.py new file mode 100644 index 0000000000..83345f27f9 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/subset_sum_i_naive.py @@ -0,0 +1,50 @@ +""" +File: subset_sum_i_naive.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], + target: int, + total: int, + choices: list[int], + res: list[list[int]], +): + """回溯演算法:子集和 I""" + # 子集和等於 target 時,記錄解 + if total == target: + res.append(list(state)) + return + # 走訪所有選擇 + for i in range(len(choices)): + # 剪枝:若子集和超過 target ,則跳過該選擇 + if total + choices[i] > target: + continue + # 嘗試:做出選擇,更新元素和 total + state.append(choices[i]) + # 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop() + + +def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 I(包含重複子集)""" + state = [] # 狀態(子集) + total = 0 # 子集和 + res = [] # 結果串列(子集串列) + backtrack(state, target, total, nums, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + print(f"輸入陣列 nums = {nums}, target = {target}") + print(f"所有和等於 {target} 的子集 res = {res}") + print(f"請注意,該方法輸出的結果包含重複集合") diff --git a/zh-hant/codes/python/chapter_backtracking/subset_sum_ii.py b/zh-hant/codes/python/chapter_backtracking/subset_sum_ii.py new file mode 100644 index 0000000000..8c7edf3305 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/subset_sum_ii.py @@ -0,0 +1,52 @@ +""" +File: subset_sum_ii.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """回溯演算法:子集和 II""" + # 子集和等於 target 時,記錄解 + if target == 0: + res.append(list(state)) + return + # 走訪所有選擇 + # 剪枝二:從 start 開始走訪,避免生成重複子集 + # 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i in range(start, len(choices)): + # 剪枝一:若子集和超過 target ,則直接結束迴圈 + # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0: + break + # 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if i > start and choices[i] == choices[i - 1]: + continue + # 嘗試:做出選擇,更新 target, start + state.append(choices[i]) + # 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop() + + +def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 II""" + state = [] # 狀態(子集) + nums.sort() # 對 nums 進行排序 + start = 0 # 走訪起始點 + res = [] # 結果串列(子集串列) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + print(f"輸入陣列 nums = {nums}, target = {target}") + print(f"所有和等於 {target} 的子集 res = {res}") diff --git a/zh-hant/codes/python/chapter_computational_complexity/iteration.py b/zh-hant/codes/python/chapter_computational_complexity/iteration.py new file mode 100644 index 0000000000..d11ee696e7 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/iteration.py @@ -0,0 +1,65 @@ +""" +File: iteration.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def for_loop(n: int) -> int: + """for 迴圈""" + res = 0 + # 迴圈求和 1, 2, ..., n-1, n + for i in range(1, n + 1): + res += i + return res + + +def while_loop(n: int) -> int: + """while 迴圈""" + res = 0 + i = 1 # 初始化條件變數 + # 迴圈求和 1, 2, ..., n-1, n + while i <= n: + res += i + i += 1 # 更新條件變數 + return res + + +def while_loop_ii(n: int) -> int: + """while 迴圈(兩次更新)""" + res = 0 + i = 1 # 初始化條件變數 + # 迴圈求和 1, 4, 10, ... + while i <= n: + res += i + # 更新條件變數 + i += 1 + i *= 2 + return res + + +def nested_for_loop(n: int) -> str: + """雙層 for 迴圈""" + res = "" + # 迴圈 i = 1, 2, ..., n-1, n + for i in range(1, n + 1): + # 迴圈 j = 1, 2, ..., n-1, n + for j in range(1, n + 1): + res += f"({i}, {j}), " + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = for_loop(n) + print(f"\nfor 迴圈的求和結果 res = {res}") + + res = while_loop(n) + print(f"\nwhile 迴圈的求和結果 res = {res}") + + res = while_loop_ii(n) + print(f"\nwhile 迴圈(兩次更新)求和結果 res = {res}") + + res = nested_for_loop(n) + print(f"\n雙層 for 迴圈的走訪結果 {res}") diff --git a/zh-hant/codes/python/chapter_computational_complexity/recursion.py b/zh-hant/codes/python/chapter_computational_complexity/recursion.py new file mode 100644 index 0000000000..33bfd37b04 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/recursion.py @@ -0,0 +1,69 @@ +""" +File: recursion.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def recur(n: int) -> int: + """遞迴""" + # 終止條件 + if n == 1: + return 1 + # 遞:遞迴呼叫 + res = recur(n - 1) + # 迴:返回結果 + return n + res + + +def for_loop_recur(n: int) -> int: + """使用迭代模擬遞迴""" + # 使用一個顯式的堆疊來模擬系統呼叫堆疊 + stack = [] + res = 0 + # 遞:遞迴呼叫 + for i in range(n, 0, -1): + # 透過“入堆疊操作”模擬“遞” + stack.append(i) + # 迴:返回結果 + while stack: + # 透過“出堆疊操作”模擬“迴” + res += stack.pop() + # res = 1+2+3+...+n + return res + + +def tail_recur(n, res): + """尾遞迴""" + # 終止條件 + if n == 0: + return res + # 尾遞迴呼叫 + return tail_recur(n - 1, res + n) + + +def fib(n: int) -> int: + """費波那契數列:遞迴""" + # 終止條件 f(1) = 0, f(2) = 1 + if n == 1 or n == 2: + return n - 1 + # 遞迴呼叫 f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # 返回結果 f(n) + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = recur(n) + print(f"\n遞迴函式的求和結果 res = {res}") + + res = for_loop_recur(n) + print(f"\n使用迭代模擬遞迴求和結果 res = {res}") + + res = tail_recur(n, 0) + print(f"\n尾遞迴函式的求和結果 res = {res}") + + res = fib(n) + print(f"\n費波那契數列的第 {n} 項為 {res}") diff --git a/zh-hant/codes/python/chapter_computational_complexity/space_complexity.py b/zh-hant/codes/python/chapter_computational_complexity/space_complexity.py new file mode 100644 index 0000000000..5ecb609487 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/space_complexity.py @@ -0,0 +1,90 @@ +""" +File: space_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, TreeNode, print_tree + + +def function() -> int: + """函式""" + # 執行某些操作 + return 0 + + +def constant(n: int): + """常數階""" + # 常數、變數、物件佔用 O(1) 空間 + a = 0 + nums = [0] * 10000 + node = ListNode(0) + # 迴圈中的變數佔用 O(1) 空間 + for _ in range(n): + c = 0 + # 迴圈中的函式佔用 O(1) 空間 + for _ in range(n): + function() + + +def linear(n: int): + """線性階""" + # 長度為 n 的串列佔用 O(n) 空間 + nums = [0] * n + # 長度為 n 的雜湊表佔用 O(n) 空間 + hmap = dict[int, str]() + for i in range(n): + hmap[i] = str(i) + + +def linear_recur(n: int): + """線性階(遞迴實現)""" + print("遞迴 n =", n) + if n == 1: + return + linear_recur(n - 1) + + +def quadratic(n: int): + """平方階""" + # 二維串列佔用 O(n^2) 空間 + num_matrix = [[0] * n for _ in range(n)] + + +def quadratic_recur(n: int) -> int: + """平方階(遞迴實現)""" + if n <= 0: + return 0 + # 陣列 nums 長度為 n, n-1, ..., 2, 1 + nums = [0] * n + return quadratic_recur(n - 1) + + +def build_tree(n: int) -> TreeNode | None: + """指數階(建立滿二元樹)""" + if n == 0: + return None + root = TreeNode(0) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + # 常數階 + constant(n) + # 線性階 + linear(n) + linear_recur(n) + # 平方階 + quadratic(n) + quadratic_recur(n) + # 指數階 + root = build_tree(n) + print_tree(root) diff --git a/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py b/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py new file mode 100644 index 0000000000..dc17e7f0a4 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py @@ -0,0 +1,153 @@ +""" +File: time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def constant(n: int) -> int: + """常數階""" + count = 0 + size = 100000 + for _ in range(size): + count += 1 + return count + + +def linear(n: int) -> int: + """線性階""" + count = 0 + for _ in range(n): + count += 1 + return count + + +def array_traversal(nums: list[int]) -> int: + """線性階(走訪陣列)""" + count = 0 + # 迴圈次數與陣列長度成正比 + for num in nums: + count += 1 + return count + + +def quadratic(n: int) -> int: + """平方階""" + count = 0 + # 迴圈次數與資料大小 n 成平方關係 + for i in range(n): + for j in range(n): + count += 1 + return count + + +def bubble_sort(nums: list[int]) -> int: + """平方階(泡沫排序)""" + count = 0 # 計數器 + # 外迴圈:未排序區間為 [0, i] + for i in range(len(nums) - 1, 0, -1): + # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交換 nums[j] 與 nums[j + 1] + tmp: int = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 元素交換包含 3 個單元操作 + return count + + +def exponential(n: int) -> int: + """指數階(迴圈實現)""" + count = 0 + base = 1 + # 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for _ in range(n): + for _ in range(base): + count += 1 + base *= 2 + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count + + +def exp_recur(n: int) -> int: + """指數階(遞迴實現)""" + if n == 1: + return 1 + return exp_recur(n - 1) + exp_recur(n - 1) + 1 + + +def logarithmic(n: int) -> int: + """對數階(迴圈實現)""" + count = 0 + while n > 1: + n = n / 2 + count += 1 + return count + + +def log_recur(n: int) -> int: + """對數階(遞迴實現)""" + if n <= 1: + return 0 + return log_recur(n / 2) + 1 + + +def linear_log_recur(n: int) -> int: + """線性對數階""" + if n <= 1: + return 1 + # 一分為二,子問題的規模減小一半 + count = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 當前子問題包含 n 個操作 + for _ in range(n): + count += 1 + return count + + +def factorial_recur(n: int) -> int: + """階乘階(遞迴實現)""" + if n == 0: + return 1 + count = 0 + # 從 1 個分裂出 n 個 + for _ in range(n): + count += factorial_recur(n - 1) + return count + + +"""Driver Code""" +if __name__ == "__main__": + # 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + n = 8 + print("輸入資料大小 n =", n) + + count = constant(n) + print("常數階的操作數量 =", count) + + count = linear(n) + print("線性階的操作數量 =", count) + count = array_traversal([0] * n) + print("線性階(走訪陣列)的操作數量 =", count) + + count = quadratic(n) + print("平方階的操作數量 =", count) + nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] + count = bubble_sort(nums) + print("平方階(泡沫排序)的操作數量 =", count) + + count = exponential(n) + print("指數階(迴圈實現)的操作數量 =", count) + count = exp_recur(n) + print("指數階(遞迴實現)的操作數量 =", count) + + count = logarithmic(n) + print("對數階(迴圈實現)的操作數量 =", count) + count = log_recur(n) + print("對數階(遞迴實現)的操作數量 =", count) + + count = linear_log_recur(n) + print("線性對數階(遞迴實現)的操作數量 =", count) + + count = factorial_recur(n) + print("階乘階(遞迴實現)的操作數量 =", count) diff --git a/zh-hant/codes/python/chapter_computational_complexity/worst_best_time_complexity.py b/zh-hant/codes/python/chapter_computational_complexity/worst_best_time_complexity.py new file mode 100644 index 0000000000..0a11e369d0 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -0,0 +1,36 @@ +""" +File: worst_best_time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_numbers(n: int) -> list[int]: + """生成一個陣列,元素為: 1, 2, ..., n ,順序被打亂""" + # 生成陣列 nums =: 1, 2, 3, ..., n + nums = [i for i in range(1, n + 1)] + # 隨機打亂陣列元素 + random.shuffle(nums) + return nums + + +def find_one(nums: list[int]) -> int: + """查詢陣列 nums 中數字 1 所在索引""" + for i in range(len(nums)): + # 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + # 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if nums[i] == 1: + return i + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + for i in range(10): + n = 100 + nums: list[int] = random_numbers(n) + index: int = find_one(nums) + print("\n陣列 [ 1, 2, ..., n ] 被打亂後 =", nums) + print("數字 1 的索引為", index) diff --git a/zh-hant/codes/python/chapter_divide_and_conquer/binary_search_recur.py b/zh-hant/codes/python/chapter_divide_and_conquer/binary_search_recur.py new file mode 100644 index 0000000000..bcfc365346 --- /dev/null +++ b/zh-hant/codes/python/chapter_divide_and_conquer/binary_search_recur.py @@ -0,0 +1,40 @@ +""" +File: binary_search_recur.py +Created Time: 2023-07-17 +Author: krahets (krahets@163.com) +""" + + +def dfs(nums: list[int], target: int, i: int, j: int) -> int: + """二分搜尋:問題 f(i, j)""" + # 若區間為空,代表無目標元素,則返回 -1 + if i > j: + return -1 + # 計算中點索引 m + m = (i + j) // 2 + if nums[m] < target: + # 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j) + elif nums[m] > target: + # 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1) + else: + # 找到目標元素,返回其索引 + return m + + +def binary_search(nums: list[int], target: int) -> int: + """二分搜尋""" + n = len(nums) + # 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1) + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分搜尋(雙閉區間) + index = binary_search(nums, target) + print("目標元素 6 的索引 = ", index) diff --git a/zh-hant/codes/python/chapter_divide_and_conquer/build_tree.py b/zh-hant/codes/python/chapter_divide_and_conquer/build_tree.py new file mode 100644 index 0000000000..67471ead56 --- /dev/null +++ b/zh-hant/codes/python/chapter_divide_and_conquer/build_tree.py @@ -0,0 +1,54 @@ +""" +File: build_tree.py +Created Time: 2023-07-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +def dfs( + preorder: list[int], + inorder_map: dict[int, int], + i: int, + l: int, + r: int, +) -> TreeNode | None: + """構建二元樹:分治""" + # 子樹區間為空時終止 + if r - l < 0: + return None + # 初始化根節點 + root = TreeNode(preorder[i]) + # 查詢 m ,從而劃分左右子樹 + m = inorder_map[preorder[i]] + # 子問題:構建左子樹 + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # 子問題:構建右子樹 + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + # 返回根節點 + return root + + +def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: + """構建二元樹""" + # 初始化雜湊表,儲存 inorder 元素到索引的對映 + inorder_map = {val: i for i, val in enumerate(inorder)} + root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + print(f"前序走訪 = {preorder}") + print(f"中序走訪 = {inorder}") + + root = build_tree(preorder, inorder) + print("構建的二元樹為:") + print_tree(root) diff --git a/zh-hant/codes/python/chapter_divide_and_conquer/hanota.py b/zh-hant/codes/python/chapter_divide_and_conquer/hanota.py new file mode 100644 index 0000000000..d416dd175c --- /dev/null +++ b/zh-hant/codes/python/chapter_divide_and_conquer/hanota.py @@ -0,0 +1,53 @@ +""" +File: hanota.py +Created Time: 2023-07-16 +Author: krahets (krahets@163.com) +""" + + +def move(src: list[int], tar: list[int]): + """移動一個圓盤""" + # 從 src 頂部拿出一個圓盤 + pan = src.pop() + # 將圓盤放入 tar 頂部 + tar.append(pan) + + +def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): + """求解河內塔問題 f(i)""" + # 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1: + move(src, tar) + return + # 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf) + # 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar) + # 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar) + + +def solve_hanota(A: list[int], B: list[int], C: list[int]): + """求解河內塔問題""" + n = len(A) + # 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C) + + +"""Driver Code""" +if __name__ == "__main__": + # 串列尾部是柱子頂部 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + print("初始狀態下:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") + + solve_hanota(A, B, C) + + print("圓盤移動完成後:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py new file mode 100644 index 0000000000..0012219b52 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py @@ -0,0 +1,37 @@ +""" +File: climbing_stairs_backtrack.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: + """回溯""" + # 當爬到第 n 階時,方案數量加 1 + if state == n: + res[0] += 1 + # 走訪所有選擇 + for choice in choices: + # 剪枝:不允許越過第 n 階 + if state + choice > n: + continue + # 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res) + # 回退 + + +def climbing_stairs_backtrack(n: int) -> int: + """爬樓梯:回溯""" + choices = [1, 2] # 可選擇向上爬 1 階或 2 階 + state = 0 # 從第 0 階開始爬 + res = [0] # 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res) + return res[0] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_backtrack(n) + print(f"爬 {n} 階樓梯共有 {res} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py new file mode 100644 index 0000000000..9086798163 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py @@ -0,0 +1,29 @@ +""" +File: climbing_stairs_constraint_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_constraint_dp(n: int) -> int: + """帶約束爬樓梯:動態規劃""" + if n == 1 or n == 2: + return 1 + # 初始化 dp 表,用於儲存子問題的解 + dp = [[0] * 3 for _ in range(n + 1)] + # 初始狀態:預設最小子問題的解 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 狀態轉移:從較小子問題逐步求解較大子問題 + for i in range(3, n + 1): + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + return dp[n][1] + dp[n][2] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_constraint_dp(n) + print(f"爬 {n} 階樓梯共有 {res} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py new file mode 100644 index 0000000000..4697129c64 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py @@ -0,0 +1,28 @@ +""" +File: climbing_stairs_dfs.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int) -> int: + """搜尋""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1) + dfs(i - 2) + return count + + +def climbing_stairs_dfs(n: int) -> int: + """爬樓梯:搜尋""" + return dfs(n) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs(n) + print(f"爬 {n} 階樓梯共有 {res} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py new file mode 100644 index 0000000000..af2bd23d67 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py @@ -0,0 +1,35 @@ +""" +File: climbing_stairs_dfs_mem.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int, mem: list[int]) -> int: + """記憶化搜尋""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # 若存在記錄 dp[i] ,則直接返回之 + if mem[i] != -1: + return mem[i] + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # 記錄 dp[i] + mem[i] = count + return count + + +def climbing_stairs_dfs_mem(n: int) -> int: + """爬樓梯:記憶化搜尋""" + # mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + mem = [-1] * (n + 1) + return dfs(n, mem) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs_mem(n) + print(f"爬 {n} 階樓梯共有 {res} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py new file mode 100644 index 0000000000..e14ce694ea --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py @@ -0,0 +1,40 @@ +""" +File: climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_dp(n: int) -> int: + """爬樓梯:動態規劃""" + if n == 1 or n == 2: + return n + # 初始化 dp 表,用於儲存子問題的解 + dp = [0] * (n + 1) + # 初始狀態:預設最小子問題的解 + dp[1], dp[2] = 1, 2 + # 狀態轉移:從較小子問題逐步求解較大子問題 + for i in range(3, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] + + +def climbing_stairs_dp_comp(n: int) -> int: + """爬樓梯:空間最佳化後的動態規劃""" + if n == 1 or n == 2: + return n + a, b = 1, 2 + for _ in range(3, n + 1): + a, b = b, a + b + return b + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dp(n) + print(f"爬 {n} 階樓梯共有 {res} 種方案") + + res = climbing_stairs_dp_comp(n) + print(f"爬 {n} 階樓梯共有 {res} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/coin_change.py b/zh-hant/codes/python/chapter_dynamic_programming/coin_change.py new file mode 100644 index 0000000000..9d860fd4c4 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/coin_change.py @@ -0,0 +1,60 @@ +""" +File: coin_change.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_dp(coins: list[int], amt: int) -> int: + """零錢兌換:動態規劃""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 狀態轉移:首行首列 + for a in range(1, amt + 1): + dp[0][a] = MAX + # 狀態轉移:其餘行和列 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + else: + # 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + return dp[n][amt] if dp[n][amt] != MAX else -1 + + +def coin_change_dp_comp(coins: list[int], amt: int) -> int: + """零錢兌換:空間最佳化後的動態規劃""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [MAX] * (amt + 1) + dp[0] = 0 + # 狀態轉移 + for i in range(1, n + 1): + # 正序走訪 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + else: + # 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + return dp[amt] if dp[amt] != MAX else -1 + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 4 + + # 動態規劃 + res = coin_change_dp(coins, amt) + print(f"湊到目標金額所需的最少硬幣數量為 {res}") + + # 空間最佳化後的動態規劃 + res = coin_change_dp_comp(coins, amt) + print(f"湊到目標金額所需的最少硬幣數量為 {res}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/coin_change_ii.py b/zh-hant/codes/python/chapter_dynamic_programming/coin_change_ii.py new file mode 100644 index 0000000000..6edb254a9d --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/coin_change_ii.py @@ -0,0 +1,58 @@ +""" +File: coin_change_ii.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_ii_dp(coins: list[int], amt: int) -> int: + """零錢兌換 II:動態規劃""" + n = len(coins) + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 初始化首列 + for i in range(n + 1): + dp[i][0] = 1 + # 狀態轉移 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + else: + # 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + return dp[n][amt] + + +def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: + """零錢兌換 II:空間最佳化後的動態規劃""" + n = len(coins) + # 初始化 dp 表 + dp = [0] * (amt + 1) + dp[0] = 1 + # 狀態轉移 + for i in range(1, n + 1): + # 正序走訪 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + else: + # 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + return dp[amt] + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 5 + + # 動態規劃 + res = coin_change_ii_dp(coins, amt) + print(f"湊出目標金額的硬幣組合數量為 {res}") + + # 空間最佳化後的動態規劃 + res = coin_change_ii_dp_comp(coins, amt) + print(f"湊出目標金額的硬幣組合數量為 {res}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/edit_distance.py b/zh-hant/codes/python/chapter_dynamic_programming/edit_distance.py new file mode 100644 index 0000000000..bc361b6536 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/edit_distance.py @@ -0,0 +1,123 @@ +""" +File: edit_distancde.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + + +def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: + """編輯距離:暴力搜尋""" + # 若 s 和 t 都為空,則返回 0 + if i == 0 and 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 edit_distance_dfs(s, t, i - 1, j - 1) + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # 返回最少編輯步數 + return min(insert, delete, replace) + 1 + + +def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int: + """編輯距離:記憶化搜尋""" + # 若 s 和 t 都為空,則返回 0 + if i == 0 and 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 edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 記錄並返回最少編輯步數 + mem[i][j] = min(insert, delete, replace) + 1 + return mem[i][j] + + +def edit_distance_dp(s: str, t: str) -> int: + """編輯距離:動態規劃""" + n, m = len(s), len(t) + dp = [[0] * (m + 1) for _ in range(n + 1)] + # 狀態轉移:首行首列 + for i in range(1, n + 1): + dp[i][0] = i + for j in range(1, m + 1): + dp[0][j] = j + # 狀態轉移:其餘行和列 + for i in range(1, n + 1): + for j in range(1, m + 1): + if s[i - 1] == t[j - 1]: + # 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1] + else: + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + return dp[n][m] + + +def edit_distance_dp_comp(s: str, t: str) -> int: + """編輯距離:空間最佳化後的動態規劃""" + n, m = len(s), len(t) + dp = [0] * (m + 1) + # 狀態轉移:首行 + for j in range(1, m + 1): + dp[j] = j + # 狀態轉移:其餘行 + for i in range(1, n + 1): + # 狀態轉移:首列 + leftup = dp[0] # 暫存 dp[i-1, j-1] + dp[0] += 1 + # 狀態轉移:其餘列 + for j in range(1, m + 1): + temp = dp[j] + if s[i - 1] == t[j - 1]: + # 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup + else: + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(dp[j - 1], dp[j], leftup) + 1 + leftup = temp # 更新為下一輪的 dp[i-1, j-1] + return dp[m] + + +"""Driver Code""" +if __name__ == "__main__": + s = "bag" + t = "pack" + n, m = len(s), len(t) + + # 暴力搜尋 + res = edit_distance_dfs(s, t, n, m) + print(f"將 {s} 更改為 {t} 最少需要編輯 {res} 步") + + # 記憶化搜尋 + mem = [[-1] * (m + 1) for _ in range(n + 1)] + res = edit_distance_dfs_mem(s, t, mem, n, m) + print(f"將 {s} 更改為 {t} 最少需要編輯 {res} 步") + + # 動態規劃 + res = edit_distance_dp(s, t) + print(f"將 {s} 更改為 {t} 最少需要編輯 {res} 步") + + # 空間最佳化後的動態規劃 + res = edit_distance_dp_comp(s, t) + print(f"將 {s} 更改為 {t} 最少需要編輯 {res} 步") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/knapsack.py b/zh-hant/codes/python/chapter_dynamic_programming/knapsack.py new file mode 100644 index 0000000000..a31b0bcee6 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/knapsack.py @@ -0,0 +1,101 @@ +""" +File: knapsack.py +Created Time: 2023-07-03 +Author: krahets (krahets@163.com) +""" + + +def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: + """0-1 背包:暴力搜尋""" + # 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 or c == 0: + return 0 + # 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs(wgt, val, i - 1, c) + # 計算不放入和放入物品 i 的最大價值 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 返回兩種方案中價值更大的那一個 + return max(no, yes) + + +def knapsack_dfs_mem( + wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int +) -> int: + """0-1 背包:記憶化搜尋""" + # 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 or c == 0: + return 0 + # 若已有記錄,則直接返回 + if mem[i][c] != -1: + return mem[i][c] + # 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) + # 計算不放入和放入物品 i 的最大價值 + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes) + return mem[i][c] + + +def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:動態規劃""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 狀態轉移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:空間最佳化後的動態規劃""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 狀態轉移 + for i in range(1, n + 1): + # 倒序走訪 + for c in range(cap, 0, -1): + if wgt[i - 1] > c: + # 若超過背包容量,則不選物品 i + dp[c] = dp[c] + else: + # 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # 暴力搜尋 + res = knapsack_dfs(wgt, val, n, cap) + print(f"不超過背包容量的最大物品價值為 {res}") + + # 記憶化搜尋 + mem = [[-1] * (cap + 1) for _ in range(n + 1)] + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + print(f"不超過背包容量的最大物品價值為 {res}") + + # 動態規劃 + res = knapsack_dp(wgt, val, cap) + print(f"不超過背包容量的最大物品價值為 {res}") + + # 空間最佳化後的動態規劃 + res = knapsack_dp_comp(wgt, val, cap) + print(f"不超過背包容量的最大物品價值為 {res}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py b/zh-hant/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py new file mode 100644 index 0000000000..dd461bef63 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py @@ -0,0 +1,43 @@ +""" +File: min_cost_climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def min_cost_climbing_stairs_dp(cost: list[int]) -> int: + """爬樓梯最小代價:動態規劃""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + # 初始化 dp 表,用於儲存子問題的解 + dp = [0] * (n + 1) + # 初始狀態:預設最小子問題的解 + dp[1], dp[2] = cost[1], cost[2] + # 狀態轉移:從較小子問題逐步求解較大子問題 + for i in range(3, n + 1): + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + return dp[n] + + +def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: + """爬樓梯最小代價:空間最佳化後的動態規劃""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + a, b = cost[1], cost[2] + for i in range(3, n + 1): + a, b = b, min(a, b) + cost[i] + return b + + +"""Driver Code""" +if __name__ == "__main__": + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print(f"輸入樓梯的代價串列為 {cost}") + + res = min_cost_climbing_stairs_dp(cost) + print(f"爬完樓梯的最低代價為 {res}") + + res = min_cost_climbing_stairs_dp_comp(cost) + print(f"爬完樓梯的最低代價為 {res}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/min_path_sum.py b/zh-hant/codes/python/chapter_dynamic_programming/min_path_sum.py new file mode 100644 index 0000000000..f202abcaea --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/min_path_sum.py @@ -0,0 +1,104 @@ +""" +File: min_path_sum.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + +from math import inf + + +def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: + """最小路徑和:暴力搜尋""" + # 若為左上角單元格,則終止搜尋 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,則返回 +∞ 代價 + if i < 0 or j < 0: + return inf + # 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) + grid[i][j] + + +def min_path_sum_dfs_mem( + grid: list[list[int]], mem: list[list[int]], i: int, j: int +) -> int: + """最小路徑和:記憶化搜尋""" + # 若為左上角單元格,則終止搜尋 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,則返回 +∞ 代價 + if i < 0 or j < 0: + return inf + # 若已有記錄,則直接返回 + if mem[i][j] != -1: + return mem[i][j] + # 左邊和上邊單元格的最小路徑代價 + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] + + +def min_path_sum_dp(grid: list[list[int]]) -> int: + """最小路徑和:動態規劃""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [[0] * m for _ in range(n)] + dp[0][0] = grid[0][0] + # 狀態轉移:首行 + for j in range(1, m): + dp[0][j] = dp[0][j - 1] + grid[0][j] + # 狀態轉移:首列 + for i in range(1, n): + dp[i][0] = dp[i - 1][0] + grid[i][0] + # 狀態轉移:其餘行和列 + for i in range(1, n): + for j in range(1, m): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + return dp[n - 1][m - 1] + + +def min_path_sum_dp_comp(grid: list[list[int]]) -> int: + """最小路徑和:空間最佳化後的動態規劃""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [0] * m + # 狀態轉移:首行 + dp[0] = grid[0][0] + for j in range(1, m): + dp[j] = dp[j - 1] + grid[0][j] + # 狀態轉移:其餘行 + for i in range(1, n): + # 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0] + # 狀態轉移:其餘列 + for j in range(1, m): + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + return dp[m - 1] + + +"""Driver Code""" +if __name__ == "__main__": + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = len(grid), len(grid[0]) + + # 暴力搜尋 + res = min_path_sum_dfs(grid, n - 1, m - 1) + print(f"從左上角到右下角的最小路徑和為 {res}") + + # 記憶化搜尋 + mem = [[-1] * m for _ in range(n)] + res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) + print(f"從左上角到右下角的最小路徑和為 {res}") + + # 動態規劃 + res = min_path_sum_dp(grid) + print(f"從左上角到右下角的最小路徑和為 {res}") + + # 空間最佳化後的動態規劃 + res = min_path_sum_dp_comp(grid) + print(f"從左上角到右下角的最小路徑和為 {res}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/unbounded_knapsack.py b/zh-hant/codes/python/chapter_dynamic_programming/unbounded_knapsack.py new file mode 100644 index 0000000000..3472fae3c8 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/unbounded_knapsack.py @@ -0,0 +1,55 @@ +""" +File: unbounded_knapsack.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:動態規劃""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 狀態轉移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:空間最佳化後的動態規劃""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 狀態轉移 + for i in range(1, n + 1): + # 正序走訪 + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超過背包容量,則不選物品 i + dp[c] = dp[c] + else: + # 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 動態規劃 + res = unbounded_knapsack_dp(wgt, val, cap) + print(f"不超過背包容量的最大物品價值為 {res}") + + # 空間最佳化後的動態規劃 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + print(f"不超過背包容量的最大物品價值為 {res}") diff --git a/zh-hant/codes/python/chapter_graph/graph_adjacency_list.py b/zh-hant/codes/python/chapter_graph/graph_adjacency_list.py new file mode 100644 index 0000000000..4c02d72093 --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_adjacency_list.py @@ -0,0 +1,111 @@ +""" +File: graph_adjacency_list.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets + + +class GraphAdjList: + """基於鄰接表實現的無向圖類別""" + + def __init__(self, edges: list[list[Vertex]]): + """建構子""" + # 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + self.adj_list = dict[Vertex, list[Vertex]]() + # 新增所有頂點和邊 + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + def size(self) -> int: + """獲取頂點數量""" + return len(self.adj_list) + + def add_edge(self, vet1: Vertex, vet2: Vertex): + """新增邊""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 新增邊 vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """刪除邊""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 刪除邊 vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + def add_vertex(self, vet: Vertex): + """新增頂點""" + if vet in self.adj_list: + return + # 在鄰接表中新增一個新鏈結串列 + self.adj_list[vet] = [] + + def remove_vertex(self, vet: Vertex): + """刪除頂點""" + if vet not in self.adj_list: + raise ValueError() + # 在鄰接表中刪除頂點 vet 對應的鏈結串列 + self.adj_list.pop(vet) + # 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) + + def print(self): + """列印鄰接表""" + print("鄰接表 =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化無向圖 + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList(edges) + print("\n初始化後,圖為") + graph.print() + + # 新增邊 + # 頂點 1, 2 即 v[0], v[2] + graph.add_edge(v[0], v[2]) + print("\n新增邊 1-2 後,圖為") + graph.print() + + # 刪除邊 + # 頂點 1, 3 即 v[0], v[1] + graph.remove_edge(v[0], v[1]) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + # 新增頂點 + v5 = Vertex(6) + graph.add_vertex(v5) + print("\n新增頂點 6 後,圖為") + graph.print() + + # 刪除頂點 + # 頂點 3 即 v[1] + graph.remove_vertex(v[1]) + print("\n刪除頂點 3 後,圖為") + graph.print() diff --git a/zh-hant/codes/python/chapter_graph/graph_adjacency_matrix.py b/zh-hant/codes/python/chapter_graph/graph_adjacency_matrix.py new file mode 100644 index 0000000000..e0a4bdd873 --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -0,0 +1,116 @@ +""" +File: graph_adjacency_matrix.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, print_matrix + + +class GraphAdjMat: + """基於鄰接矩陣實現的無向圖類別""" + + def __init__(self, vertices: list[int], edges: list[list[int]]): + """建構子""" + # 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + self.vertices: list[int] = [] + # 鄰接矩陣,行列索引對應“頂點索引” + self.adj_mat: list[list[int]] = [] + # 新增頂點 + for val in vertices: + self.add_vertex(val) + # 新增邊 + # 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """獲取頂點數量""" + return len(self.vertices) + + def add_vertex(self, val: int): + """新增頂點""" + n = self.size() + # 向頂點串列中新增新頂點的值 + self.vertices.append(val) + # 在鄰接矩陣中新增一行 + new_row = [0] * n + self.adj_mat.append(new_row) + # 在鄰接矩陣中新增一列 + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """刪除頂點""" + if index >= self.size(): + raise IndexError() + # 在頂點串列中移除索引 index 的頂點 + self.vertices.pop(index) + # 在鄰接矩陣中刪除索引 index 的行 + self.adj_mat.pop(index) + # 在鄰接矩陣中刪除索引 index 的列 + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """新增邊""" + # 參數 i, j 對應 vertices 元素索引 + # 索引越界與相等處理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + def remove_edge(self, i: int, j: int): + """刪除邊""" + # 參數 i, j 對應 vertices 元素索引 + # 索引越界與相等處理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 + + def print(self): + """列印鄰接矩陣""" + print("頂點串列 =", self.vertices) + print("鄰接矩陣 =") + print_matrix(self.adj_mat) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化無向圖 + # 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat(vertices, edges) + print("\n初始化後,圖為") + graph.print() + + # 新增邊 + # 頂點 1, 2 的索引分別為 0, 2 + graph.add_edge(0, 2) + print("\n新增邊 1-2 後,圖為") + graph.print() + + # 刪除邊 + # 頂點 1, 3 的索引分別為 0, 1 + graph.remove_edge(0, 1) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + # 新增頂點 + graph.add_vertex(6) + print("\n新增頂點 6 後,圖為") + graph.print() + + # 刪除頂點 + # 頂點 3 的索引為 1 + graph.remove_vertex(1) + print("\n刪除頂點 3 後,圖為") + graph.print() diff --git a/zh-hant/codes/python/chapter_graph/graph_bfs.py b/zh-hant/codes/python/chapter_graph/graph_bfs.py new file mode 100644 index 0000000000..0f8b7f415f --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_bfs.py @@ -0,0 +1,64 @@ +""" +File: graph_bfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets, vets_to_vals +from collections import deque +from graph_adjacency_list import GraphAdjList + + +def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """廣度優先走訪""" + # 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + # 頂點走訪序列 + res = [] + # 雜湊集合,用於記錄已被訪問過的頂點 + visited = set[Vertex]([start_vet]) + # 佇列用於實現 BFS + que = deque[Vertex]([start_vet]) + # 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while len(que) > 0: + vet = que.popleft() # 佇列首頂點出隊 + res.append(vet) # 記錄訪問頂點 + # 走訪該頂點的所有鄰接頂點 + for adj_vet in graph.adj_list[vet]: + if adj_vet in visited: + continue # 跳過已被訪問的頂點 + que.append(adj_vet) # 只入列未訪問的頂點 + visited.add(adj_vet) # 標記該頂點已被訪問 + # 返回頂點走訪序列 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化無向圖 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [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]], + ] + graph = GraphAdjList(edges) + print("\n初始化後,圖為") + graph.print() + + # 廣度優先走訪 + res = graph_bfs(graph, v[0]) + print("\n廣度優先走訪(BFS)頂點序列為") + print(vets_to_vals(res)) diff --git a/zh-hant/codes/python/chapter_graph/graph_dfs.py b/zh-hant/codes/python/chapter_graph/graph_dfs.py new file mode 100644 index 0000000000..c37b5d6f0a --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_dfs.py @@ -0,0 +1,57 @@ +""" +File: graph_dfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vets_to_vals, vals_to_vets +from graph_adjacency_list import GraphAdjList + + +def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """深度優先走訪輔助函式""" + res.append(vet) # 記錄訪問頂點 + visited.add(vet) # 標記該頂點已被訪問 + # 走訪該頂點的所有鄰接頂點 + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # 跳過已被訪問的頂點 + # 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet) + + +def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """深度優先走訪""" + # 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + # 頂點走訪序列 + res = [] + # 雜湊集合,用於記錄已被訪問過的頂點 + visited = set[Vertex]() + dfs(graph, visited, res, start_vet) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化無向圖 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList(edges) + print("\n初始化後,圖為") + graph.print() + + # 深度優先走訪 + res = graph_dfs(graph, v[0]) + print("\n深度優先走訪(DFS)頂點序列為") + print(vets_to_vals(res)) diff --git a/zh-hant/codes/python/chapter_greedy/coin_change_greedy.py b/zh-hant/codes/python/chapter_greedy/coin_change_greedy.py new file mode 100644 index 0000000000..595fd1a13f --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/coin_change_greedy.py @@ -0,0 +1,48 @@ +""" +File: coin_change_greedy.py +Created Time: 2023-07-18 +Author: krahets (krahets@163.com) +""" + + +def coin_change_greedy(coins: list[int], amt: int) -> int: + """零錢兌換:貪婪""" + # 假設 coins 串列有序 + i = len(coins) - 1 + count = 0 + # 迴圈進行貪婪選擇,直到無剩餘金額 + while amt > 0: + # 找到小於且最接近剩餘金額的硬幣 + while i > 0 and coins[i] > amt: + i -= 1 + # 選擇 coins[i] + amt -= coins[i] + count += 1 + # 若未找到可行方案,則返回 -1 + return count if amt == 0 else -1 + + +"""Driver Code""" +if __name__ == "__main__": + # 貪婪:能夠保證找到全域性最優解 + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"湊到 {amt} 所需的最少硬幣數量為 {res}") + + # 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"湊到 {amt} 所需的最少硬幣數量為 {res}") + print(f"實際上需要的最少數量為 3 ,即 20 + 20 + 20") + + # 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"湊到 {amt} 所需的最少硬幣數量為 {res}") + print(f"實際上需要的最少數量為 2 ,即 49 + 49") diff --git a/zh-hant/codes/python/chapter_greedy/fractional_knapsack.py b/zh-hant/codes/python/chapter_greedy/fractional_knapsack.py new file mode 100644 index 0000000000..dd318c3156 --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/fractional_knapsack.py @@ -0,0 +1,46 @@ +""" +File: fractional_knapsack.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + + +class Item: + """物品""" + + def __init__(self, w: int, v: int): + self.w = w # 物品重量 + self.v = v # 物品價值 + + +def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: + """分數背包:貪婪""" + # 建立物品串列,包含兩個屬性:重量、價值 + items = [Item(w, v) for w, v in zip(wgt, val)] + # 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort(key=lambda item: item.v / item.w, reverse=True) + # 迴圈貪婪選擇 + res = 0 + for item in items: + if item.w <= cap: + # 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v + cap -= item.w + else: + # 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (item.v / item.w) * cap + # 已無剩餘容量,因此跳出迴圈 + break + return res + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # 貪婪演算法 + res = fractional_knapsack(wgt, val, cap) + print(f"不超過背包容量的最大物品價值為 {res}") diff --git a/zh-hant/codes/python/chapter_greedy/max_capacity.py b/zh-hant/codes/python/chapter_greedy/max_capacity.py new file mode 100644 index 0000000000..574f962f82 --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/max_capacity.py @@ -0,0 +1,33 @@ +""" +File: max_capacity.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + + +def max_capacity(ht: list[int]) -> int: + """最大容量:貪婪""" + # 初始化 i, j,使其分列陣列兩端 + i, j = 0, len(ht) - 1 + # 初始最大容量為 0 + res = 0 + # 迴圈貪婪選擇,直至兩板相遇 + while i < j: + # 更新最大容量 + cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + # 向內移動短板 + if ht[i] < ht[j]: + i += 1 + else: + j -= 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # 貪婪演算法 + res = max_capacity(ht) + print(f"最大容量為 {res}") diff --git a/zh-hant/codes/python/chapter_greedy/max_product_cutting.py b/zh-hant/codes/python/chapter_greedy/max_product_cutting.py new file mode 100644 index 0000000000..0a9af87340 --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/max_product_cutting.py @@ -0,0 +1,33 @@ +""" +File: max_product_cutting.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + +import math + + +def max_product_cutting(n: int) -> int: + """最大切分乘積:貪婪""" + # 當 n <= 3 時,必須切分出一個 1 + if n <= 3: + return 1 * (n - 1) + # 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + a, b = n // 3, n % 3 + if b == 1: + # 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return int(math.pow(3, a - 1)) * 2 * 2 + if b == 2: + # 當餘數為 2 時,不做處理 + return int(math.pow(3, a)) * 2 + # 當餘數為 0 時,不做處理 + return int(math.pow(3, a)) + + +"""Driver Code""" +if __name__ == "__main__": + n = 58 + + # 貪婪演算法 + res = max_product_cutting(n) + print(f"最大切分乘積為 {res}") diff --git a/zh-hant/codes/python/chapter_hashing/array_hash_map.py b/zh-hant/codes/python/chapter_hashing/array_hash_map.py new file mode 100644 index 0000000000..1cd2ffa83f --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/array_hash_map.py @@ -0,0 +1,117 @@ +""" +File: array_hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + + +class Pair: + """鍵值對""" + + def __init__(self, key: int, val: str): + self.key = key + self.val = val + + +class ArrayHashMap: + """基於陣列實現的雜湊表""" + + def __init__(self): + """建構子""" + # 初始化陣列,包含 100 個桶 + self.buckets: list[Pair | None] = [None] * 100 + + def hash_func(self, key: int) -> int: + """雜湊函式""" + index = key % 100 + return index + + def get(self, key: int) -> str: + """查詢操作""" + index: int = self.hash_func(key) + pair: Pair = self.buckets[index] + if pair is None: + return None + return pair.val + + def put(self, key: int, val: str): + """新增操作""" + pair = Pair(key, val) + index: int = self.hash_func(key) + self.buckets[index] = pair + + def remove(self, key: int): + """刪除操作""" + index: int = self.hash_func(key) + # 置為 None ,代表刪除 + self.buckets[index] = None + + def entry_set(self) -> list[Pair]: + """獲取所有鍵值對""" + result: list[Pair] = [] + for pair in self.buckets: + if pair is not None: + result.append(pair) + return result + + def key_set(self) -> list[int]: + """獲取所有鍵""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.key) + return result + + def value_set(self) -> list[str]: + """獲取所有值""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.val) + return result + + def print(self): + """列印雜湊表""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化雜湊表 + hmap = ArrayHashMap() + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + hmap.print() + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name = hmap.get(15937) + print("\n輸入學號 15937 ,查詢到姓名 " + name) + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hmap.remove(10583) + print("\n刪除 10583 後,雜湊表為\nKey -> Value") + hmap.print() + + # 走訪雜湊表 + print("\n走訪鍵值對 Key->Value") + for pair in hmap.entry_set(): + print(pair.key, "->", pair.val) + + print("\n單獨走訪鍵 Key") + for key in hmap.key_set(): + print(key) + + print("\n單獨走訪值 Value") + for val in hmap.value_set(): + print(val) diff --git a/zh-hant/codes/python/chapter_hashing/built_in_hash.py b/zh-hant/codes/python/chapter_hashing/built_in_hash.py new file mode 100644 index 0000000000..31258b4211 --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/built_in_hash.py @@ -0,0 +1,37 @@ +""" +File: built_in_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + +"""Driver Code""" +if __name__ == "__main__": + num = 3 + hash_num = hash(num) + print(f"整數 {num} 的雜湊值為 {hash_num}") + + bol = True + hash_bol = hash(bol) + print(f"布林量 {bol} 的雜湊值為 {hash_bol}") + + dec = 3.14159 + hash_dec = hash(dec) + print(f"小數 {dec} 的雜湊值為 {hash_dec}") + + str = "Hello 演算法" + hash_str = hash(str) + print(f"字串 {str} 的雜湊值為 {hash_str}") + + tup = (12836, "小哈") + hash_tup = hash(tup) + print(f"元組 {tup} 的雜湊值為 {hash(hash_tup)}") + + obj = ListNode(0) + hash_obj = hash(obj) + print(f"節點物件 {obj} 的雜湊值為 {hash_obj}") diff --git a/zh-hant/codes/python/chapter_hashing/hash_map.py b/zh-hant/codes/python/chapter_hashing/hash_map.py new file mode 100644 index 0000000000..70cb4c3ba9 --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/hash_map.py @@ -0,0 +1,50 @@ +""" +File: hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_dict + +"""Driver Code""" +if __name__ == "__main__": + # 初始化雜湊表 + hmap = dict[int, str]() + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + print("\n新增完成後,雜湊表為\nKey -> Value") + print_dict(hmap) + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name: str = hmap[15937] + print("\n輸入學號 15937 ,查詢到姓名 " + name) + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hmap.pop(10583) + print("\n刪除 10583 後,雜湊表為\nKey -> Value") + print_dict(hmap) + + # 走訪雜湊表 + print("\n走訪鍵值對 Key->Value") + for key, value in hmap.items(): + print(key, "->", value) + + print("\n單獨走訪鍵 Key") + for key in hmap.keys(): + print(key) + + print("\n單獨走訪值 Value") + for val in hmap.values(): + print(val) diff --git a/zh-hant/codes/python/chapter_hashing/hash_map_chaining.py b/zh-hant/codes/python/chapter_hashing/hash_map_chaining.py new file mode 100644 index 0000000000..edf1a6afcd --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/hash_map_chaining.py @@ -0,0 +1,118 @@ +""" +File: hash_map_chaining.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapChaining: + """鏈式位址雜湊表""" + + def __init__(self): + """建構子""" + self.size = 0 # 鍵值對數量 + self.capacity = 4 # 雜湊表容量 + self.load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 + self.extend_ratio = 2 # 擴容倍數 + self.buckets = [[] for _ in range(self.capacity)] # 桶陣列 + + def hash_func(self, key: int) -> int: + """雜湊函式""" + return key % self.capacity + + def load_factor(self) -> float: + """負載因子""" + return self.size / self.capacity + + def get(self, key: int) -> str | None: + """查詢操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 走訪桶,若找到 key ,則返回對應 val + for pair in bucket: + if pair.key == key: + return pair.val + # 若未找到 key ,則返回 None + return None + + def put(self, key: int, val: str): + """新增操作""" + # 當負載因子超過閾值時,執行擴容 + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + bucket = self.buckets[index] + # 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for pair in bucket: + if pair.key == key: + pair.val = val + return + # 若無該 key ,則將鍵值對新增至尾部 + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 + + def remove(self, key: int): + """刪除操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 走訪桶,從中刪除鍵值對 + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + break + + def extend(self): + """擴容雜湊表""" + # 暫存原雜湊表 + buckets = self.buckets + # 初始化擴容後的新雜湊表 + self.capacity *= self.extend_ratio + self.buckets = [[] for _ in range(self.capacity)] + self.size = 0 + # 將鍵值對從原雜湊表搬運至新雜湊表 + for bucket in buckets: + for pair in bucket: + self.put(pair.key, pair.val) + + def print(self): + """列印雜湊表""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化雜湊表 + hashmap = HashMapChaining() + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小囉") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鴨") + print("\n新增完成後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name = hashmap.get(13276) + print("\n輸入學號 13276 ,查詢到姓名 " + name) + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hashmap.remove(12836) + print("\n刪除 12836 後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() diff --git a/zh-hant/codes/python/chapter_hashing/hash_map_open_addressing.py b/zh-hant/codes/python/chapter_hashing/hash_map_open_addressing.py new file mode 100644 index 0000000000..c1fa21316d --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -0,0 +1,138 @@ +""" +File: hash_map_open_addressing.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapOpenAddressing: + """開放定址雜湊表""" + + def __init__(self): + """建構子""" + self.size = 0 # 鍵值對數量 + self.capacity = 4 # 雜湊表容量 + self.load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 + self.extend_ratio = 2 # 擴容倍數 + self.buckets: list[Pair | None] = [None] * self.capacity # 桶陣列 + self.TOMBSTONE = Pair(-1, "-1") # 刪除標記 + + def hash_func(self, key: int) -> int: + """雜湊函式""" + return key % self.capacity + + def load_factor(self) -> float: + """負載因子""" + return self.size / self.capacity + + def find_bucket(self, key: int) -> int: + """搜尋 key 對應的桶索引""" + index = self.hash_func(key) + first_tombstone = -1 + # 線性探查,當遇到空桶時跳出 + while self.buckets[index] is not None: + # 若遇到 key ,返回對應的桶索引 + if self.buckets[index].key == key: + # 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if first_tombstone != -1: + self.buckets[first_tombstone] = self.buckets[index] + self.buckets[index] = self.TOMBSTONE + return first_tombstone # 返回移動後的桶索引 + return index # 返回桶索引 + # 記錄遇到的首個刪除標記 + if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: + first_tombstone = index + # 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % self.capacity + # 若 key 不存在,則返回新增點的索引 + return index if first_tombstone == -1 else first_tombstone + + def get(self, key: int) -> str: + """查詢操作""" + # 搜尋 key 對應的桶索引 + index = self.find_bucket(key) + # 若找到鍵值對,則返回對應 val + if self.buckets[index] not in [None, self.TOMBSTONE]: + return self.buckets[index].val + # 若鍵值對不存在,則返回 None + return None + + def put(self, key: int, val: str): + """新增操作""" + # 當負載因子超過閾值時,執行擴容 + if self.load_factor() > self.load_thres: + self.extend() + # 搜尋 key 對應的桶索引 + index = self.find_bucket(key) + # 若找到鍵值對,則覆蓋 val 並返回 + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index].val = val + return + # 若鍵值對不存在,則新增該鍵值對 + self.buckets[index] = Pair(key, val) + self.size += 1 + + def remove(self, key: int): + """刪除操作""" + # 搜尋 key 對應的桶索引 + index = self.find_bucket(key) + # 若找到鍵值對,則用刪除標記覆蓋它 + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index] = self.TOMBSTONE + self.size -= 1 + + def extend(self): + """擴容雜湊表""" + # 暫存原雜湊表 + buckets_tmp = self.buckets + # 初始化擴容後的新雜湊表 + self.capacity *= self.extend_ratio + self.buckets = [None] * self.capacity + self.size = 0 + # 將鍵值對從原雜湊表搬運至新雜湊表 + for pair in buckets_tmp: + if pair not in [None, self.TOMBSTONE]: + self.put(pair.key, pair.val) + + def print(self): + """列印雜湊表""" + for pair in self.buckets: + if pair is None: + print("None") + elif pair is self.TOMBSTONE: + print("TOMBSTONE") + else: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化雜湊表 + hashmap = HashMapOpenAddressing() + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小囉") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + hashmap.print() + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 val + name = hashmap.get(13276) + print("\n輸入學號 13276 ,查詢到姓名 " + name) + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750) + print("\n刪除 16750 後,雜湊表為\nKey -> Value") + hashmap.print() diff --git a/zh-hant/codes/python/chapter_hashing/simple_hash.py b/zh-hant/codes/python/chapter_hashing/simple_hash.py new file mode 100644 index 0000000000..f3c012defa --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/simple_hash.py @@ -0,0 +1,58 @@ +""" +File: simple_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + + +def add_hash(key: str) -> int: + """加法雜湊""" + hash = 0 + modulus = 1000000007 + for c in key: + hash += ord(c) + return hash % modulus + + +def mul_hash(key: str) -> int: + """乘法雜湊""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = 31 * hash + ord(c) + return hash % modulus + + +def xor_hash(key: str) -> int: + """互斥或雜湊""" + hash = 0 + modulus = 1000000007 + for c in key: + hash ^= ord(c) + return hash % modulus + + +def rot_hash(key: str) -> int: + """旋轉雜湊""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = (hash << 4) ^ (hash >> 28) ^ ord(c) + return hash % modulus + + +"""Driver Code""" +if __name__ == "__main__": + key = "Hello 演算法" + + hash = add_hash(key) + print(f"加法雜湊值為 {hash}") + + hash = mul_hash(key) + print(f"乘法雜湊值為 {hash}") + + hash = xor_hash(key) + print(f"互斥或雜湊值為 {hash}") + + hash = rot_hash(key) + print(f"旋轉雜湊值為 {hash}") diff --git a/zh-hant/codes/python/chapter_heap/heap.py b/zh-hant/codes/python/chapter_heap/heap.py new file mode 100644 index 0000000000..8c077a7732 --- /dev/null +++ b/zh-hant/codes/python/chapter_heap/heap.py @@ -0,0 +1,71 @@ +""" +File: heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def test_push(heap: list, val: int, flag: int = 1): + heapq.heappush(heap, flag * val) # 元素入堆積 + print(f"\n元素 {val} 入堆積後") + print_heap([flag * val for val in heap]) + + +def test_pop(heap: list, flag: int = 1): + val = flag * heapq.heappop(heap) # 堆積頂元素出堆積 + print(f"\n堆積頂元素 {val} 出堆積後") + print_heap([flag * val for val in heap]) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化小頂堆積 + min_heap, flag = [], 1 + # 初始化大頂堆積 + max_heap, flag = [], -1 + + print("\n以下測試樣例為大頂堆積") + # Python 的 heapq 模組預設實現小頂堆積 + # 考慮將“元素取負”後再入堆積,這樣就可以將大小關係顛倒,從而實現大頂堆積 + # 在本示例中,flag = 1 時對應小頂堆積,flag = -1 時對應大頂堆積 + + # 元素入堆積 + test_push(max_heap, 1, flag) + test_push(max_heap, 3, flag) + test_push(max_heap, 2, flag) + test_push(max_heap, 5, flag) + test_push(max_heap, 4, flag) + + # 獲取堆積頂元素 + peek: int = flag * max_heap[0] + print(f"\n堆積頂元素為 {peek}") + + # 堆積頂元素出堆積 + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + # 獲取堆積大小 + size: int = len(max_heap) + print(f"\n堆積元素數量為 {size}") + + # 判斷堆積是否為空 + is_empty: bool = not max_heap + print(f"\n堆積是否為空 {is_empty}") + + # 輸入串列並建堆積 + # 時間複雜度為 O(n) ,而非 O(nlogn) + min_heap = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + print("\n輸入串列並建立小頂堆積後") + print_heap(min_heap) diff --git a/zh-hant/codes/python/chapter_heap/my_heap.py b/zh-hant/codes/python/chapter_heap/my_heap.py new file mode 100644 index 0000000000..5b2c419353 --- /dev/null +++ b/zh-hant/codes/python/chapter_heap/my_heap.py @@ -0,0 +1,137 @@ +""" +File: my_heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + + +class MaxHeap: + """大頂堆積""" + + def __init__(self, nums: list[int]): + """建構子,根據輸入串列建堆積""" + # 將串列元素原封不動新增進堆積 + self.max_heap = nums + # 堆積化除葉節點以外的其他所有節點 + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) + + def left(self, i: int) -> int: + """獲取左子節點的索引""" + return 2 * i + 1 + + def right(self, i: int) -> int: + """獲取右子節點的索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int: + """獲取父節點的索引""" + return (i - 1) // 2 # 向下整除 + + def swap(self, i: int, j: int): + """交換元素""" + self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] + + def size(self) -> int: + """獲取堆積大小""" + return len(self.max_heap) + + def is_empty(self) -> bool: + """判斷堆積是否為空""" + return self.size() == 0 + + def peek(self) -> int: + """訪問堆積頂元素""" + return self.max_heap[0] + + def push(self, val: int): + """元素入堆積""" + # 新增節點 + self.max_heap.append(val) + # 從底至頂堆積化 + self.sift_up(self.size() - 1) + + def sift_up(self, i: int): + """從節點 i 開始,從底至頂堆積化""" + while True: + # 獲取節點 i 的父節點 + p = self.parent(i) + # 當“越過根節點”或“節點無須修復”時,結束堆積化 + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # 交換兩節點 + self.swap(i, p) + # 迴圈向上堆積化 + i = p + + def pop(self) -> int: + """元素出堆積""" + # 判空處理 + if self.is_empty(): + raise IndexError("堆積為空") + # 交換根節點與最右葉節點(交換首元素與尾元素) + self.swap(0, self.size() - 1) + # 刪除節點 + val = self.max_heap.pop() + # 從頂至底堆積化 + self.sift_down(0) + # 返回堆積頂元素 + return val + + def sift_down(self, i: int): + """從節點 i 開始,從頂至底堆積化""" + while True: + # 判斷節點 i, l, r 中值最大的節點,記為 ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i: + break + # 交換兩節點 + self.swap(i, ma) + # 迴圈向下堆積化 + i = ma + + def print(self): + """列印堆積(二元樹)""" + print_heap(self.max_heap) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化大頂堆積 + max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\n輸入串列並建堆積後") + max_heap.print() + + # 獲取堆積頂元素 + peek = max_heap.peek() + print(f"\n堆積頂元素為 {peek}") + + # 元素入堆積 + val = 7 + max_heap.push(val) + print(f"\n元素 {val} 入堆積後") + max_heap.print() + + # 堆積頂元素出堆積 + peek = max_heap.pop() + print(f"\n堆積頂元素 {peek} 出堆積後") + max_heap.print() + + # 獲取堆積大小 + size = max_heap.size() + print(f"\n堆積元素數量為 {size}") + + # 判斷堆積是否為空 + is_empty = max_heap.is_empty() + print(f"\n堆積是否為空 {is_empty}") diff --git a/zh-hant/codes/python/chapter_heap/top_k.py b/zh-hant/codes/python/chapter_heap/top_k.py new file mode 100644 index 0000000000..8b43d60ad8 --- /dev/null +++ b/zh-hant/codes/python/chapter_heap/top_k.py @@ -0,0 +1,39 @@ +""" +File: top_k.py +Created Time: 2023-06-10 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def top_k_heap(nums: list[int], k: int) -> list[int]: + """基於堆積查詢陣列中最大的 k 個元素""" + # 初始化小頂堆積 + heap = [] + # 將陣列的前 k 個元素入堆積 + for i in range(k): + heapq.heappush(heap, nums[i]) + # 從第 k+1 個元素開始,保持堆積的長度為 k + for i in range(k, len(nums)): + # 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if nums[i] > heap[0]: + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + return heap + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + print(f"最大的 {k} 個元素為") + print_heap(res) diff --git a/zh-hant/codes/python/chapter_searching/binary_search.py b/zh-hant/codes/python/chapter_searching/binary_search.py new file mode 100644 index 0000000000..4adcf70b0f --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/binary_search.py @@ -0,0 +1,52 @@ +""" +File: binary_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + + +def binary_search(nums: list[int], target: int) -> int: + """二分搜尋(雙閉區間)""" + # 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + i, j = 0, len(nums) - 1 + # 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while i <= j: + # 理論上 Python 的數字可以無限大(取決於記憶體大小),無須考慮大數越界問題 + m = (i + j) // 2 # 計算中點索引 m + if nums[m] < target: + i = m + 1 # 此情況說明 target 在區間 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # 此情況說明 target 在區間 [i, m-1] 中 + else: + return m # 找到目標元素,返回其索引 + return -1 # 未找到目標元素,返回 -1 + + +def binary_search_lcro(nums: list[int], target: int) -> int: + """二分搜尋(左閉右開區間)""" + # 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + i, j = 0, len(nums) + # 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while i < j: + m = (i + j) // 2 # 計算中點索引 m + if nums[m] < target: + i = m + 1 # 此情況說明 target 在區間 [m+1, j) 中 + elif nums[m] > target: + j = m # 此情況說明 target 在區間 [i, m) 中 + else: + return m # 找到目標元素,返回其索引 + return -1 # 未找到目標元素,返回 -1 + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分搜尋(雙閉區間) + index = binary_search(nums, target) + print("目標元素 6 的索引 = ", index) + + # 二分搜尋(左閉右開區間) + index = binary_search_lcro(nums, target) + print("目標元素 6 的索引 = ", index) diff --git a/zh-hant/codes/python/chapter_searching/binary_search_edge.py b/zh-hant/codes/python/chapter_searching/binary_search_edge.py new file mode 100644 index 0000000000..32f0c03b3b --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/binary_search_edge.py @@ -0,0 +1,49 @@ +""" +File: binary_search_edge.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from binary_search_insertion import binary_search_insertion + + +def binary_search_left_edge(nums: list[int], target: int) -> int: + """二分搜尋最左一個 target""" + # 等價於查詢 target 的插入點 + i = binary_search_insertion(nums, target) + # 未找到 target ,返回 -1 + if i == len(nums) or nums[i] != target: + return -1 + # 找到 target ,返回索引 i + return i + + +def binary_search_right_edge(nums: list[int], target: int) -> int: + """二分搜尋最右一個 target""" + # 轉化為查詢最左一個 target + 1 + i = binary_search_insertion(nums, target + 1) + # j 指向最右一個 target ,i 指向首個大於 target 的元素 + j = i - 1 + # 未找到 target ,返回 -1 + if j == -1 or nums[j] != target: + return -1 + # 找到 target ,返回索引 j + return j + + +"""Driver Code""" +if __name__ == "__main__": + # 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\n陣列 nums = {nums}") + + # 二分搜尋左邊界和右邊界 + for target in [6, 7]: + index = binary_search_left_edge(nums, target) + print(f"最左一個元素 {target} 的索引為 {index}") + index = binary_search_right_edge(nums, target) + print(f"最右一個元素 {target} 的索引為 {index}") diff --git a/zh-hant/codes/python/chapter_searching/binary_search_insertion.py b/zh-hant/codes/python/chapter_searching/binary_search_insertion.py new file mode 100644 index 0000000000..f793e53b13 --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/binary_search_insertion.py @@ -0,0 +1,54 @@ +""" +File: binary_search_insertion.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + + +def binary_search_insertion_simple(nums: list[int], target: int) -> int: + """二分搜尋插入點(無重複元素)""" + i, j = 0, len(nums) - 1 # 初始化雙閉區間 [0, n-1] + while i <= j: + m = (i + j) // 2 # 計算中點索引 m + if nums[m] < target: + i = m + 1 # target 在區間 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在區間 [i, m-1] 中 + else: + return m # 找到 target ,返回插入點 m + # 未找到 target ,返回插入點 i + return i + + +def binary_search_insertion(nums: list[int], target: int) -> int: + """二分搜尋插入點(存在重複元素)""" + i, j = 0, len(nums) - 1 # 初始化雙閉區間 [0, n-1] + while i <= j: + m = (i + j) // 2 # 計算中點索引 m + if nums[m] < target: + i = m + 1 # target 在區間 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在區間 [i, m-1] 中 + else: + j = m - 1 # 首個小於 target 的元素在區間 [i, m-1] 中 + # 返回插入點 i + return i + + +"""Driver Code""" +if __name__ == "__main__": + # 無重複元素的陣列 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print(f"\n陣列 nums = {nums}") + # 二分搜尋插入點 + for target in [6, 9]: + index = binary_search_insertion_simple(nums, target) + print(f"元素 {target} 的插入點的索引為 {index}") + + # 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\n陣列 nums = {nums}") + # 二分搜尋插入點 + for target in [2, 6, 20]: + index = binary_search_insertion(nums, target) + print(f"元素 {target} 的插入點的索引為 {index}") diff --git a/zh-hant/codes/python/chapter_searching/hashing_search.py b/zh-hant/codes/python/chapter_searching/hashing_search.py new file mode 100644 index 0000000000..6d01825f4d --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/hashing_search.py @@ -0,0 +1,51 @@ +""" +File: hashing_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def hashing_search_array(hmap: dict[int, int], target: int) -> int: + """雜湊查詢(陣列)""" + # 雜湊表的 key: 目標元素,value: 索引 + # 若雜湊表中無此 key ,返回 -1 + return hmap.get(target, -1) + + +def hashing_search_linkedlist( + hmap: dict[int, ListNode], target: int +) -> ListNode | None: + """雜湊查詢(鏈結串列)""" + # 雜湊表的 key: 目標元素,value: 節點物件 + # 若雜湊表中無此 key ,返回 None + return hmap.get(target, None) + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # 雜湊查詢(陣列) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # 初始化雜湊表 + map0 = dict[int, int]() + for i in range(len(nums)): + map0[nums[i]] = i # key: 元素,value: 索引 + index: int = hashing_search_array(map0, target) + print("目標元素 3 的索引 =", index) + + # 雜湊查詢(鏈結串列) + head: ListNode = list_to_linked_list(nums) + # 初始化雜湊表 + map1 = dict[int, ListNode]() + while head: + map1[head.val] = head # key: 節點值,value: 節點 + head = head.next + node: ListNode = hashing_search_linkedlist(map1, target) + print("目標節點值 3 的對應節點物件為", node) diff --git a/zh-hant/codes/python/chapter_searching/linear_search.py b/zh-hant/codes/python/chapter_searching/linear_search.py new file mode 100644 index 0000000000..cb902410da --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/linear_search.py @@ -0,0 +1,45 @@ +""" +File: linear_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def linear_search_array(nums: list[int], target: int) -> int: + """線性查詢(陣列)""" + # 走訪陣列 + for i in range(len(nums)): + if nums[i] == target: # 找到目標元素,返回其索引 + return i + return -1 # 未找到目標元素,返回 -1 + + +def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: + """線性查詢(鏈結串列)""" + # 走訪鏈結串列 + while head: + if head.val == target: # 找到目標節點,返回之 + return head + head = head.next + return None # 未找到目標節點,返回 None + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # 在陣列中執行線性查詢 + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index: int = linear_search_array(nums, target) + print("目標元素 3 的索引 =", index) + + # 在鏈結串列中執行線性查詢 + head: ListNode = list_to_linked_list(nums) + node: ListNode | None = linear_search_linkedlist(head, target) + print("目標節點值 3 的對應節點物件為", node) diff --git a/zh-hant/codes/python/chapter_searching/two_sum.py b/zh-hant/codes/python/chapter_searching/two_sum.py new file mode 100644 index 0000000000..6b43eea57f --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/two_sum.py @@ -0,0 +1,42 @@ +""" +File: two_sum.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def two_sum_brute_force(nums: list[int], target: int) -> list[int]: + """方法一:暴力列舉""" + # 兩層迴圈,時間複雜度為 O(n^2) + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return [i, j] + return [] + + +def two_sum_hash_table(nums: list[int], target: int) -> list[int]: + """方法二:輔助雜湊表""" + # 輔助雜湊表,空間複雜度為 O(n) + dic = {} + # 單層迴圈,時間複雜度為 O(n) + for i in range(len(nums)): + if target - nums[i] in dic: + return [dic[target - nums[i]], i] + dic[nums[i]] = i + return [] + + +"""Driver Code""" +if __name__ == "__main__": + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Driver Code ====== + # 方法一 + res: list[int] = two_sum_brute_force(nums, target) + print("方法一 res =", res) + # 方法二 + res: list[int] = two_sum_hash_table(nums, target) + print("方法二 res =", res) diff --git a/zh-hant/codes/python/chapter_sorting/bubble_sort.py b/zh-hant/codes/python/chapter_sorting/bubble_sort.py new file mode 100644 index 0000000000..558bb3098e --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/bubble_sort.py @@ -0,0 +1,44 @@ +""" +File: bubble_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def bubble_sort(nums: list[int]): + """泡沫排序""" + n = len(nums) + # 外迴圈:未排序區間為 [0, i] + for i in range(n - 1, 0, -1): + # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + + +def bubble_sort_with_flag(nums: list[int]): + """泡沫排序(標誌最佳化)""" + n = len(nums) + # 外迴圈:未排序區間為 [0, i] + for i in range(n - 1, 0, -1): + flag = False # 初始化標誌位 + # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = True # 記錄交換元素 + if not flag: + break # 此輪“冒泡”未交換任何元素,直接跳出 + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + print("泡沫排序完成後 nums =", nums) + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + print("泡沫排序完成後 nums =", nums1) diff --git a/zh-hant/codes/python/chapter_sorting/bucket_sort.py b/zh-hant/codes/python/chapter_sorting/bucket_sort.py new file mode 100644 index 0000000000..6340577c7e --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/bucket_sort.py @@ -0,0 +1,35 @@ +""" +File: bucket_sort.py +Created Time: 2023-03-30 +Author: krahets (krahets@163.com) +""" + + +def bucket_sort(nums: list[float]): + """桶排序""" + # 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + k = len(nums) // 2 + buckets = [[] for _ in range(k)] + # 1. 將陣列元素分配到各個桶中 + for num in nums: + # 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + i = int(num * k) + # 將 num 新增進桶 i + buckets[i].append(num) + # 2. 對各個桶執行排序 + for bucket in buckets: + # 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort() + # 3. 走訪桶合併結果 + i = 0 + for bucket in buckets: + for num in bucket: + nums[i] = num + i += 1 + + +if __name__ == "__main__": + # 設輸入資料為浮點數,範圍為 [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + print("桶排序完成後 nums =", nums) diff --git a/zh-hant/codes/python/chapter_sorting/counting_sort.py b/zh-hant/codes/python/chapter_sorting/counting_sort.py new file mode 100644 index 0000000000..0bc327ba8b --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/counting_sort.py @@ -0,0 +1,62 @@ +""" +File: counting_sort.py +Created Time: 2023-03-21 +Author: krahets (krahets@163.com) +""" + + +def counting_sort_naive(nums: list[int]): + """計數排序""" + # 簡單實現,無法用於排序物件 + # 1. 統計陣列最大元素 m + m = max(nums) + # 2. 統計各數字的出現次數 + # counter[num] 代表 num 的出現次數 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 走訪 counter ,將各元素填入原陣列 nums + i = 0 + for num in range(m + 1): + for _ in range(counter[num]): + nums[i] = num + i += 1 + + +def counting_sort(nums: list[int]): + """計數排序""" + # 完整實現,可排序物件,並且是穩定排序 + # 1. 統計陣列最大元素 m + m = max(nums) + # 2. 統計各數字的出現次數 + # counter[num] 代表 num 的出現次數 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + # 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for i in range(m): + counter[i + 1] += counter[i] + # 4. 倒序走訪 nums ,將各元素填入結果陣列 res + # 初始化陣列 res 用於記錄結果 + n = len(nums) + res = [0] * n + for i in range(n - 1, -1, -1): + num = nums[i] + res[counter[num] - 1] = num # 將 num 放置到對應索引處 + counter[num] -= 1 # 令前綴和自減 1 ,得到下次放置 num 的索引 + # 使用結果陣列 res 覆蓋原陣列 nums + for i in range(n): + nums[i] = res[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + print(f"計數排序(無法排序物件)完成後 nums = {nums}") + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + print(f"計數排序完成後 nums1 = {nums1}") diff --git a/zh-hant/codes/python/chapter_sorting/heap_sort.py b/zh-hant/codes/python/chapter_sorting/heap_sort.py new file mode 100644 index 0000000000..a4eaf0621d --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/heap_sort.py @@ -0,0 +1,45 @@ +""" +File: heap_sort.py +Created Time: 2023-05-24 +Author: krahets (krahets@163.com) +""" + + +def sift_down(nums: list[int], n: int, i: int): + """堆積的長度為 n ,從節點 i 開始,從頂至底堆積化""" + while True: + # 判斷節點 i, l, r 中值最大的節點,記為 ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + if l < n and nums[l] > nums[ma]: + ma = l + if r < n and nums[r] > nums[ma]: + ma = r + # 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i: + break + # 交換兩節點 + nums[i], nums[ma] = nums[ma], nums[i] + # 迴圈向下堆積化 + i = ma + + +def heap_sort(nums: list[int]): + """堆積排序""" + # 建堆積操作:堆積化除葉節點以外的其他所有節點 + for i in range(len(nums) // 2 - 1, -1, -1): + sift_down(nums, len(nums), i) + # 從堆積中提取最大元素,迴圈 n-1 輪 + for i in range(len(nums) - 1, 0, -1): + # 交換根節點與最右葉節點(交換首元素與尾元素) + nums[0], nums[i] = nums[i], nums[0] + # 以根節點為起點,從頂至底進行堆積化 + sift_down(nums, i, 0) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + print("堆積排序完成後 nums =", nums) diff --git a/zh-hant/codes/python/chapter_sorting/insertion_sort.py b/zh-hant/codes/python/chapter_sorting/insertion_sort.py new file mode 100644 index 0000000000..35175b718e --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/insertion_sort.py @@ -0,0 +1,25 @@ +""" +File: insertion_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def insertion_sort(nums: list[int]): + """插入排序""" + # 外迴圈:已排序區間為 [0, i-1] + for i in range(1, len(nums)): + base = nums[i] + j = i - 1 + # 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while j >= 0 and nums[j] > base: + nums[j + 1] = nums[j] # 將 nums[j] 向右移動一位 + j -= 1 + nums[j + 1] = base # 將 base 賦值到正確位置 + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + insertion_sort(nums) + print("插入排序完成後 nums =", nums) diff --git a/zh-hant/codes/python/chapter_sorting/merge_sort.py b/zh-hant/codes/python/chapter_sorting/merge_sort.py new file mode 100644 index 0000000000..b0d93ba738 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/merge_sort.py @@ -0,0 +1,55 @@ +""" +File: merge_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com), krahets (krahets@163.com) +""" + + +def merge(nums: list[int], left: int, mid: int, right: int): + """合併左子陣列和右子陣列""" + # 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + # 建立一個臨時陣列 tmp ,用於存放合併後的結果 + tmp = [0] * (right - left + 1) + # 初始化左子陣列和右子陣列的起始索引 + i, j, k = left, mid + 1, 0 + # 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while i <= mid and j <= right: + if nums[i] <= nums[j]: + tmp[k] = nums[i] + i += 1 + else: + tmp[k] = nums[j] + j += 1 + k += 1 + # 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while i <= mid: + tmp[k] = nums[i] + i += 1 + k += 1 + while j <= right: + tmp[k] = nums[j] + j += 1 + k += 1 + # 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for k in range(0, len(tmp)): + nums[left + k] = tmp[k] + + +def merge_sort(nums: list[int], left: int, right: int): + """合併排序""" + # 終止條件 + if left >= right: + return # 當子陣列長度為 1 時終止遞迴 + # 劃分階段 + mid = (left + right) // 2 # 計算中點 + merge_sort(nums, left, mid) # 遞迴左子陣列 + merge_sort(nums, mid + 1, right) # 遞迴右子陣列 + # 合併階段 + merge(nums, left, mid, right) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, len(nums) - 1) + print("合併排序完成後 nums =", nums) diff --git a/zh-hant/codes/python/chapter_sorting/quick_sort.py b/zh-hant/codes/python/chapter_sorting/quick_sort.py new file mode 100644 index 0000000000..0258da54b8 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/quick_sort.py @@ -0,0 +1,129 @@ +""" +File: quick_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +class QuickSort: + """快速排序類別""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵劃分""" + # 以 nums[left] 為基準數 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 從右向左找首個小於基準數的元素 + while i < j and nums[i] <= nums[left]: + i += 1 # 從左向右找首個大於基準數的元素 + # 元素交換 + nums[i], nums[j] = nums[j], nums[i] + # 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i # 返回基準數的索引 + + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序""" + # 子陣列長度為 1 時終止遞迴 + if left >= right: + return + # 哨兵劃分 + pivot = self.partition(nums, left, right) + # 遞迴左子陣列、右子陣列 + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortMedian: + """快速排序類別(中位基準數最佳化)""" + + def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: + """選取三個候選元素的中位數""" + l, m, r = nums[left], nums[mid], nums[right] + if (l <= m <= r) or (r <= m <= l): + return mid # m 在 l 和 r 之間 + if (m <= l <= r) or (r <= l <= m): + return left # l 在 m 和 r 之間 + return right + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵劃分(三數取中值)""" + # 以 nums[left] 為基準數 + med = self.median_three(nums, left, (left + right) // 2, right) + # 將中位數交換至陣列最左端 + nums[left], nums[med] = nums[med], nums[left] + # 以 nums[left] 為基準數 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 從右向左找首個小於基準數的元素 + while i < j and nums[i] <= nums[left]: + i += 1 # 從左向右找首個大於基準數的元素 + # 元素交換 + nums[i], nums[j] = nums[j], nums[i] + # 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i # 返回基準數的索引 + + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序""" + # 子陣列長度為 1 時終止遞迴 + if left >= right: + return + # 哨兵劃分 + pivot = self.partition(nums, left, right) + # 遞迴左子陣列、右子陣列 + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortTailCall: + """快速排序類別(尾遞迴最佳化)""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵劃分""" + # 以 nums[left] 為基準數 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 從右向左找首個小於基準數的元素 + while i < j and nums[i] <= nums[left]: + i += 1 # 從左向右找首個大於基準數的元素 + # 元素交換 + nums[i], nums[j] = nums[j], nums[i] + # 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i # 返回基準數的索引 + + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序(尾遞迴最佳化)""" + # 子陣列長度為 1 時終止 + while left < right: + # 哨兵劃分操作 + pivot = self.partition(nums, left, right) + # 對兩個子陣列中較短的那個執行快速排序 + if pivot - left < right - pivot: + self.quick_sort(nums, left, pivot - 1) # 遞迴排序左子陣列 + left = pivot + 1 # 剩餘未排序區間為 [pivot + 1, right] + else: + self.quick_sort(nums, pivot + 1, right) # 遞迴排序右子陣列 + right = pivot - 1 # 剩餘未排序區間為 [left, pivot - 1] + + +"""Driver Code""" +if __name__ == "__main__": + # 快速排序 + nums = [2, 4, 1, 0, 3, 5] + QuickSort().quick_sort(nums, 0, len(nums) - 1) + print("快速排序完成後 nums =", nums) + + # 快速排序(中位基準數最佳化) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) + print("快速排序(中位基準數最佳化)完成後 nums =", nums1) + + # 快速排序(尾遞迴最佳化) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1) + print("快速排序(尾遞迴最佳化)完成後 nums =", nums2) diff --git a/zh-hant/codes/python/chapter_sorting/radix_sort.py b/zh-hant/codes/python/chapter_sorting/radix_sort.py new file mode 100644 index 0000000000..42706e5f3d --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/radix_sort.py @@ -0,0 +1,69 @@ +""" +File: radix_sort.py +Created Time: 2023-03-26 +Author: krahets (krahets@163.com) +""" + + +def digit(num: int, exp: int) -> int: + """獲取元素 num 的第 k 位,其中 exp = 10^(k-1)""" + # 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num // exp) % 10 + + +def counting_sort_digit(nums: list[int], exp: int): + """計數排序(根據 nums 第 k 位排序)""" + # 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + counter = [0] * 10 + n = len(nums) + # 統計 0~9 各數字的出現次數 + for i in range(n): + d = digit(nums[i], exp) # 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1 # 統計數字 d 的出現次數 + # 求前綴和,將“出現個數”轉換為“陣列索引” + for i in range(1, 10): + counter[i] += counter[i - 1] + # 倒序走訪,根據桶內統計結果,將各元素填入 res + res = [0] * n + for i in range(n - 1, -1, -1): + d = digit(nums[i], exp) + j = counter[d] - 1 # 獲取 d 在陣列中的索引 j + res[j] = nums[i] # 將當前元素填入索引 j + counter[d] -= 1 # 將 d 的數量減 1 + # 使用結果覆蓋原陣列 nums + for i in range(n): + nums[i] = res[i] + + +def radix_sort(nums: list[int]): + """基數排序""" + # 獲取陣列的最大元素,用於判斷最大位數 + m = max(nums) + # 按照從低位到高位的順序走訪 + exp = 1 + while exp <= m: + # 對陣列元素的第 k 位執行計數排序 + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # 即 exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + + +"""Driver Code""" +if __name__ == "__main__": + # 基數排序 + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + print("基數排序完成後 nums =", nums) diff --git a/zh-hant/codes/python/chapter_sorting/selection_sort.py b/zh-hant/codes/python/chapter_sorting/selection_sort.py new file mode 100644 index 0000000000..98ff9bc4c5 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/selection_sort.py @@ -0,0 +1,26 @@ +""" +File: selection_sort.py +Created Time: 2023-05-22 +Author: krahets (krahets@163.com) +""" + + +def selection_sort(nums: list[int]): + """選擇排序""" + n = len(nums) + # 外迴圈:未排序區間為 [i, n-1] + for i in range(n - 1): + # 內迴圈:找到未排序區間內的最小元素 + k = i + for j in range(i + 1, n): + if nums[j] < nums[k]: + k = j # 記錄最小元素的索引 + # 將該最小元素與未排序區間的首個元素交換 + nums[i], nums[k] = nums[k], nums[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + print("選擇排序完成後 nums =", nums) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/array_deque.py b/zh-hant/codes/python/chapter_stack_and_queue/array_deque.py new file mode 100644 index 0000000000..f833ce3398 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/array_deque.py @@ -0,0 +1,129 @@ +""" +File: array_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ArrayDeque: + """基於環形陣列實現的雙向佇列""" + + def __init__(self, capacity: int): + """建構子""" + self._nums: list[int] = [0] * capacity + self._front: int = 0 + self._size: int = 0 + + def capacity(self) -> int: + """獲取雙向佇列的容量""" + return len(self._nums) + + def size(self) -> int: + """獲取雙向佇列的長度""" + return self._size + + def is_empty(self) -> bool: + """判斷雙向佇列是否為空""" + return self._size == 0 + + def index(self, i: int) -> int: + """計算環形陣列索引""" + # 透過取餘操作實現陣列首尾相連 + # 當 i 越過陣列尾部後,回到頭部 + # 當 i 越過陣列頭部後,回到尾部 + return (i + self.capacity()) % self.capacity() + + def push_first(self, num: int): + """佇列首入列""" + if self._size == self.capacity(): + print("雙向佇列已滿") + return + # 佇列首指標向左移動一位 + # 透過取餘操作實現 front 越過陣列頭部後回到尾部 + self._front = self.index(self._front - 1) + # 將 num 新增至佇列首 + self._nums[self._front] = num + self._size += 1 + + def push_last(self, num: int): + """佇列尾入列""" + if self._size == self.capacity(): + print("雙向佇列已滿") + return + # 計算佇列尾指標,指向佇列尾索引 + 1 + rear = self.index(self._front + self._size) + # 將 num 新增至佇列尾 + self._nums[rear] = num + self._size += 1 + + def pop_first(self) -> int: + """佇列首出列""" + num = self.peek_first() + # 佇列首指標向後移動一位 + self._front = self.index(self._front + 1) + self._size -= 1 + return num + + def pop_last(self) -> int: + """佇列尾出列""" + num = self.peek_last() + self._size -= 1 + return num + + def peek_first(self) -> int: + """訪問佇列首元素""" + if self.is_empty(): + raise IndexError("雙向佇列為空") + return self._nums[self._front] + + def peek_last(self) -> int: + """訪問佇列尾元素""" + if self.is_empty(): + raise IndexError("雙向佇列為空") + # 計算尾元素索引 + last = self.index(self._front + self._size - 1) + return self._nums[last] + + def to_array(self) -> list[int]: + """返回陣列用於列印""" + # 僅轉換有效長度範圍內的串列元素 + res = [] + for i in range(self._size): + res.append(self._nums[self.index(self._front + i)]) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化雙向佇列 + deque = ArrayDeque(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("雙向佇列 deque =", deque.to_array()) + + # 訪問元素 + peek_first: int = deque.peek_first() + print("佇列首元素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("佇列尾元素 peek_last =", peek_last) + + # 元素入列 + deque.push_last(4) + print("元素 4 佇列尾入列後 deque =", deque.to_array()) + deque.push_first(1) + print("元素 1 佇列首入列後 deque =", deque.to_array()) + + # 元素出列 + pop_last: int = deque.pop_last() + print("佇列尾出列元素 =", pop_last, ",佇列尾出列後 deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("佇列首出列元素 =", pop_first, ",佇列首出列後 deque =", deque.to_array()) + + # 獲取雙向佇列的長度 + size: int = deque.size() + print("雙向佇列長度 size =", size) + + # 判斷雙向佇列是否為空 + is_empty: bool = deque.is_empty() + print("雙向佇列是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/array_queue.py b/zh-hant/codes/python/chapter_stack_and_queue/array_queue.py new file mode 100644 index 0000000000..c419be3bc6 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/array_queue.py @@ -0,0 +1,98 @@ +""" +File: array_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayQueue: + """基於環形陣列實現的佇列""" + + def __init__(self, size: int): + """建構子""" + self._nums: list[int] = [0] * size # 用於儲存佇列元素的陣列 + self._front: int = 0 # 佇列首指標,指向佇列首元素 + self._size: int = 0 # 佇列長度 + + def capacity(self) -> int: + """獲取佇列的容量""" + return len(self._nums) + + def size(self) -> int: + """獲取佇列的長度""" + return self._size + + def is_empty(self) -> bool: + """判斷佇列是否為空""" + return self._size == 0 + + def push(self, num: int): + """入列""" + if self._size == self.capacity(): + raise IndexError("佇列已滿") + # 計算佇列尾指標,指向佇列尾索引 + 1 + # 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + rear: int = (self._front + self._size) % self.capacity() + # 將 num 新增至佇列尾 + self._nums[rear] = num + self._size += 1 + + def pop(self) -> int: + """出列""" + num: int = self.peek() + # 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + self._front = (self._front + 1) % self.capacity() + self._size -= 1 + return num + + def peek(self) -> int: + """訪問佇列首元素""" + if self.is_empty(): + raise IndexError("佇列為空") + return self._nums[self._front] + + def to_list(self) -> list[int]: + """返回串列用於列印""" + res = [0] * self.size() + j: int = self._front + for i in range(self.size()): + res[i] = self._nums[(j % self.capacity())] + j += 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化佇列 + queue = ArrayQueue(10) + + # 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("佇列 queue =", queue.to_list()) + + # 訪問佇列首元素 + peek: int = queue.peek() + print("佇列首元素 peek =", peek) + + # 元素出列 + pop: int = queue.pop() + print("出列元素 pop =", pop) + print("出列後 queue =", queue.to_list()) + + # 獲取佇列的長度 + size: int = queue.size() + print("佇列長度 size =", size) + + # 判斷佇列是否為空 + is_empty: bool = queue.is_empty() + print("佇列是否為空 =", is_empty) + + # 測試環形陣列 + for i in range(10): + queue.push(i) + queue.pop() + print("第", i, "輪入列 + 出列後 queue = ", queue.to_list()) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/array_stack.py b/zh-hant/codes/python/chapter_stack_and_queue/array_stack.py new file mode 100644 index 0000000000..bc4b74d69f --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/array_stack.py @@ -0,0 +1,72 @@ +""" +File: array_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayStack: + """基於陣列實現的堆疊""" + + def __init__(self): + """建構子""" + self._stack: list[int] = [] + + def size(self) -> int: + """獲取堆疊的長度""" + return len(self._stack) + + def is_empty(self) -> bool: + """判斷堆疊是否為空""" + return self.size() == 0 + + def push(self, item: int): + """入堆疊""" + self._stack.append(item) + + def pop(self) -> int: + """出堆疊""" + if self.is_empty(): + raise IndexError("堆疊為空") + return self._stack.pop() + + def peek(self) -> int: + """訪問堆疊頂元素""" + if self.is_empty(): + raise IndexError("堆疊為空") + return self._stack[-1] + + def to_list(self) -> list[int]: + """返回串列用於列印""" + return self._stack + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化堆疊 + stack = ArrayStack() + + # 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("堆疊 stack =", stack.to_list()) + + # 訪問堆疊頂元素 + peek: int = stack.peek() + print("堆疊頂元素 peek =", peek) + + # 元素出堆疊 + pop: int = stack.pop() + print("出堆疊元素 pop =", pop) + print("出堆疊後 stack =", stack.to_list()) + + # 獲取堆疊的長度 + size: int = stack.size() + print("堆疊的長度 size =", size) + + # 判斷是否為空 + is_empty: bool = stack.is_empty() + print("堆疊是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/deque.py b/zh-hant/codes/python/chapter_stack_and_queue/deque.py new file mode 100644 index 0000000000..b562721d59 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/deque.py @@ -0,0 +1,42 @@ +""" +File: deque.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # 初始化雙向佇列 + deq: deque[int] = deque() + + # 元素入列 + deq.append(2) # 新增至佇列尾 + deq.append(5) + deq.append(4) + deq.appendleft(3) # 新增至佇列首 + deq.appendleft(1) + print("雙向佇列 deque =", deq) + + # 訪問元素 + front: int = deq[0] # 佇列首元素 + print("佇列首元素 front =", front) + rear: int = deq[-1] # 佇列尾元素 + print("佇列尾元素 rear =", rear) + + # 元素出列 + pop_front: int = deq.popleft() # 佇列首元素出列 + print("佇列首出列元素 pop_front =", pop_front) + print("佇列首出列後 deque =", deq) + pop_rear: int = deq.pop() # 佇列尾元素出列 + print("佇列尾出列元素 pop_rear =", pop_rear) + print("佇列尾出列後 deque =", deq) + + # 獲取雙向佇列的長度 + size: int = len(deq) + print("雙向佇列長度 size =", size) + + # 判斷雙向佇列是否為空 + is_empty: bool = len(deq) == 0 + print("雙向佇列是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_deque.py new file mode 100644 index 0000000000..c7b1742726 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -0,0 +1,151 @@ +""" +File: linkedlist_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """雙向鏈結串列節點""" + + def __init__(self, val: int): + """建構子""" + self.val: int = val + self.next: ListNode | None = None # 後繼節點引用 + self.prev: ListNode | None = None # 前驅節點引用 + + +class LinkedListDeque: + """基於雙向鏈結串列實現的雙向佇列""" + + def __init__(self): + """建構子""" + self._front: ListNode | None = None # 頭節點 front + self._rear: ListNode | None = None # 尾節點 rear + self._size: int = 0 # 雙向佇列的長度 + + def size(self) -> int: + """獲取雙向佇列的長度""" + return self._size + + def is_empty(self) -> bool: + """判斷雙向佇列是否為空""" + return self._size == 0 + + def push(self, num: int, is_front: bool): + """入列操作""" + node = ListNode(num) + # 若鏈結串列為空,則令 front 和 rear 都指向 node + if self.is_empty(): + self._front = self._rear = node + # 佇列首入列操作 + elif is_front: + # 將 node 新增至鏈結串列頭部 + self._front.prev = node + node.next = self._front + self._front = node # 更新頭節點 + # 佇列尾入列操作 + else: + # 將 node 新增至鏈結串列尾部 + self._rear.next = node + node.prev = self._rear + self._rear = node # 更新尾節點 + self._size += 1 # 更新佇列長度 + + def push_first(self, num: int): + """佇列首入列""" + self.push(num, True) + + def push_last(self, num: int): + """佇列尾入列""" + self.push(num, False) + + def pop(self, is_front: bool) -> int: + """出列操作""" + if self.is_empty(): + raise IndexError("雙向佇列為空") + # 佇列首出列操作 + if is_front: + val: int = self._front.val # 暫存頭節點值 + # 刪除頭節點 + fnext: ListNode | None = self._front.next + if fnext is not None: + fnext.prev = None + self._front.next = None + self._front = fnext # 更新頭節點 + # 佇列尾出列操作 + else: + val: int = self._rear.val # 暫存尾節點值 + # 刪除尾節點 + rprev: ListNode | None = self._rear.prev + if rprev is not None: + rprev.next = None + self._rear.prev = None + self._rear = rprev # 更新尾節點 + self._size -= 1 # 更新佇列長度 + return val + + def pop_first(self) -> int: + """佇列首出列""" + return self.pop(True) + + def pop_last(self) -> int: + """佇列尾出列""" + return self.pop(False) + + def peek_first(self) -> int: + """訪問佇列首元素""" + if self.is_empty(): + raise IndexError("雙向佇列為空") + return self._front.val + + def peek_last(self) -> int: + """訪問佇列尾元素""" + if self.is_empty(): + raise IndexError("雙向佇列為空") + return self._rear.val + + def to_array(self) -> list[int]: + """返回陣列用於列印""" + node = self._front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化雙向佇列 + deque = LinkedListDeque() + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("雙向佇列 deque =", deque.to_array()) + + # 訪問元素 + peek_first: int = deque.peek_first() + print("佇列首元素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("佇列尾元素 peek_last =", peek_last) + + # 元素入列 + deque.push_last(4) + print("元素 4 佇列尾入列後 deque =", deque.to_array()) + deque.push_first(1) + print("元素 1 佇列首入列後 deque =", deque.to_array()) + + # 元素出列 + pop_last: int = deque.pop_last() + print("佇列尾出列元素 =", pop_last, ",佇列尾出列後 deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("佇列首出列元素 =", pop_first, ",佇列首出列後 deque =", deque.to_array()) + + # 獲取雙向佇列的長度 + size: int = deque.size() + print("雙向佇列長度 size =", size) + + # 判斷雙向佇列是否為空 + is_empty: bool = deque.is_empty() + print("雙向佇列是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_queue.py b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_queue.py new file mode 100644 index 0000000000..51346c6be9 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_queue.py @@ -0,0 +1,97 @@ +""" +File: linkedlist_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListQueue: + """基於鏈結串列實現的佇列""" + + def __init__(self): + """建構子""" + self._front: ListNode | None = None # 頭節點 front + self._rear: ListNode | None = None # 尾節點 rear + self._size: int = 0 + + def size(self) -> int: + """獲取佇列的長度""" + return self._size + + def is_empty(self) -> bool: + """判斷佇列是否為空""" + return self._size == 0 + + def push(self, num: int): + """入列""" + # 在尾節點後新增 num + node = ListNode(num) + # 如果佇列為空,則令頭、尾節點都指向該節點 + if self._front is None: + self._front = node + self._rear = node + # 如果佇列不為空,則將該節點新增到尾節點後 + else: + self._rear.next = node + self._rear = node + self._size += 1 + + def pop(self) -> int: + """出列""" + num = self.peek() + # 刪除頭節點 + self._front = self._front.next + self._size -= 1 + return num + + def peek(self) -> int: + """訪問佇列首元素""" + if self.is_empty(): + raise IndexError("佇列為空") + return self._front.val + + def to_list(self) -> list[int]: + """轉化為串列用於列印""" + queue = [] + temp = self._front + while temp: + queue.append(temp.val) + temp = temp.next + return queue + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化佇列 + queue = LinkedListQueue() + + # 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("佇列 queue =", queue.to_list()) + + # 訪問佇列首元素 + peek: int = queue.peek() + print("佇列首元素 front =", peek) + + # 元素出列 + pop_front: int = queue.pop() + print("出列元素 pop =", pop_front) + print("出列後 queue =", queue.to_list()) + + # 獲取佇列的長度 + size: int = queue.size() + print("佇列長度 size =", size) + + # 判斷佇列是否為空 + is_empty: bool = queue.is_empty() + print("佇列是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_stack.py b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_stack.py new file mode 100644 index 0000000000..5bddf08812 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_stack.py @@ -0,0 +1,89 @@ +""" +File: linkedlist_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListStack: + """基於鏈結串列實現的堆疊""" + + def __init__(self): + """建構子""" + self._peek: ListNode | None = None + self._size: int = 0 + + def size(self) -> int: + """獲取堆疊的長度""" + return self._size + + def is_empty(self) -> bool: + """判斷堆疊是否為空""" + return self._size == 0 + + def push(self, val: int): + """入堆疊""" + node = ListNode(val) + node.next = self._peek + self._peek = node + self._size += 1 + + def pop(self) -> int: + """出堆疊""" + num = self.peek() + self._peek = self._peek.next + self._size -= 1 + return num + + def peek(self) -> int: + """訪問堆疊頂元素""" + if self.is_empty(): + raise IndexError("堆疊為空") + return self._peek.val + + def to_list(self) -> list[int]: + """轉化為串列用於列印""" + arr = [] + node = self._peek + while node: + arr.append(node.val) + node = node.next + arr.reverse() + return arr + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化堆疊 + stack = LinkedListStack() + + # 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("堆疊 stack =", stack.to_list()) + + # 訪問堆疊頂元素 + peek: int = stack.peek() + print("堆疊頂元素 peek =", peek) + + # 元素出堆疊 + pop: int = stack.pop() + print("出堆疊元素 pop =", pop) + print("出堆疊後 stack =", stack.to_list()) + + # 獲取堆疊的長度 + size: int = stack.size() + print("堆疊的長度 size =", size) + + # 判斷是否為空 + is_empty: bool = stack.is_empty() + print("堆疊是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/queue.py b/zh-hant/codes/python/chapter_stack_and_queue/queue.py new file mode 100644 index 0000000000..3d49e232c3 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/queue.py @@ -0,0 +1,39 @@ +""" +File: queue.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # 初始化佇列 + # 在 Python 中,我們一般將雙向佇列類別 deque 看作佇列使用 + # 雖然 queue.Queue() 是純正的佇列類別,但不太好用 + que: deque[int] = deque() + + # 元素入列 + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + print("佇列 que =", que) + + # 訪問佇列首元素 + front: int = que[0] + print("佇列首元素 front =", front) + + # 元素出列 + pop: int = que.popleft() + print("出列元素 pop =", pop) + print("出列後 que =", que) + + # 獲取佇列的長度 + size: int = len(que) + print("佇列長度 size =", size) + + # 判斷佇列是否為空 + is_empty: bool = len(que) == 0 + print("佇列是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/stack.py b/zh-hant/codes/python/chapter_stack_and_queue/stack.py new file mode 100644 index 0000000000..89f4fdac25 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/stack.py @@ -0,0 +1,36 @@ +""" +File: stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # 初始化堆疊 + # Python 沒有內建的堆疊類別,可以把 list 當作堆疊來使用 + stack: list[int] = [] + + # 元素入堆疊 + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("堆疊 stack =", stack) + + # 訪問堆疊頂元素 + peek: int = stack[-1] + print("堆疊頂元素 peek =", peek) + + # 元素出堆疊 + pop: int = stack.pop() + print("出堆疊元素 pop =", pop) + print("出堆疊後 stack =", stack) + + # 獲取堆疊的長度 + size: int = len(stack) + print("堆疊的長度 size =", size) + + # 判斷是否為空 + is_empty: bool = len(stack) == 0 + print("堆疊是否為空 =", is_empty) diff --git a/zh-hant/codes/python/chapter_tree/array_binary_tree.py b/zh-hant/codes/python/chapter_tree/array_binary_tree.py new file mode 100644 index 0000000000..d46338351a --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/array_binary_tree.py @@ -0,0 +1,119 @@ +""" +File: array_binary_tree.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree + + +class ArrayBinaryTree: + """陣列表示下的二元樹類別""" + + def __init__(self, arr: list[int | None]): + """建構子""" + self._tree = list(arr) + + def size(self): + """串列容量""" + return len(self._tree) + + def val(self, i: int) -> int | None: + """獲取索引為 i 節點的值""" + # 若索引越界,則返回 None ,代表空位 + if i < 0 or i >= self.size(): + return None + return self._tree[i] + + def left(self, i: int) -> int | None: + """獲取索引為 i 節點的左子節點的索引""" + return 2 * i + 1 + + def right(self, i: int) -> int | None: + """獲取索引為 i 節點的右子節點的索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int | None: + """獲取索引為 i 節點的父節點的索引""" + return (i - 1) // 2 + + def level_order(self) -> list[int]: + """層序走訪""" + self.res = [] + # 直接走訪陣列 + for i in range(self.size()): + if self.val(i) is not None: + self.res.append(self.val(i)) + return self.res + + def dfs(self, i: int, order: str): + """深度優先走訪""" + if self.val(i) is None: + return + # 前序走訪 + if order == "pre": + self.res.append(self.val(i)) + self.dfs(self.left(i), order) + # 中序走訪 + if order == "in": + self.res.append(self.val(i)) + self.dfs(self.right(i), order) + # 後序走訪 + if order == "post": + self.res.append(self.val(i)) + + def pre_order(self) -> list[int]: + """前序走訪""" + self.res = [] + self.dfs(0, order="pre") + return self.res + + def in_order(self) -> list[int]: + """中序走訪""" + self.res = [] + self.dfs(0, order="in") + return self.res + + def post_order(self) -> list[int]: + """後序走訪""" + self.res = [] + self.dfs(0, order="post") + return self.res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二元樹 + # 這裡藉助了一個從陣列直接生成二元樹的函式 + arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + root = list_to_tree(arr) + print("\n初始化二元樹\n") + print("二元樹的陣列表示:") + print(arr) + print("二元樹的鏈結串列表示:") + print_tree(root) + + # 陣列表示下的二元樹類別 + abt = ArrayBinaryTree(arr) + + # 訪問節點 + i = 1 + l, r, p = abt.left(i), abt.right(i), abt.parent(i) + print(f"\n當前節點的索引為 {i} ,值為 {abt.val(i)}") + print(f"其左子節點的索引為 {l} ,值為 {abt.val(l)}") + print(f"其右子節點的索引為 {r} ,值為 {abt.val(r)}") + print(f"其父節點的索引為 {p} ,值為 {abt.val(p)}") + + # 走訪樹 + res = abt.level_order() + print("\n層序走訪為:", res) + res = abt.pre_order() + print("前序走訪為:", res) + res = abt.in_order() + print("中序走訪為:", res) + res = abt.post_order() + print("後序走訪為:", res) diff --git a/zh-hant/codes/python/chapter_tree/avl_tree.py b/zh-hant/codes/python/chapter_tree/avl_tree.py new file mode 100644 index 0000000000..f23d00f3b5 --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/avl_tree.py @@ -0,0 +1,200 @@ +""" +File: avl_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +class AVLTree: + """AVL 樹""" + + def __init__(self): + """建構子""" + self._root = None + + def get_root(self) -> TreeNode | None: + """獲取二元樹根節點""" + return self._root + + def height(self, node: TreeNode | None) -> int: + """獲取節點高度""" + # 空節點高度為 -1 ,葉節點高度為 0 + if node is not None: + return node.height + return -1 + + def update_height(self, node: TreeNode | None): + """更新節點高度""" + # 節點高度等於最高子樹高度 + 1 + node.height = max([self.height(node.left), self.height(node.right)]) + 1 + + def balance_factor(self, node: TreeNode | None) -> int: + """獲取平衡因子""" + # 空節點平衡因子為 0 + if node is None: + return 0 + # 節點平衡因子 = 左子樹高度 - 右子樹高度 + return self.height(node.left) - self.height(node.right) + + def right_rotate(self, node: TreeNode | None) -> TreeNode | None: + """右旋操作""" + child = node.left + grand_child = child.right + # 以 child 為原點,將 node 向右旋轉 + child.right = node + node.left = grand_child + # 更新節點高度 + self.update_height(node) + self.update_height(child) + # 返回旋轉後子樹的根節點 + return child + + def left_rotate(self, node: TreeNode | None) -> TreeNode | None: + """左旋操作""" + child = node.right + grand_child = child.left + # 以 child 為原點,將 node 向左旋轉 + child.left = node + node.right = grand_child + # 更新節點高度 + self.update_height(node) + self.update_height(child) + # 返回旋轉後子樹的根節點 + return child + + def rotate(self, node: TreeNode | None) -> TreeNode | None: + """執行旋轉操作,使該子樹重新恢復平衡""" + # 獲取節點 node 的平衡因子 + balance_factor = self.balance_factor(node) + # 左偏樹 + if balance_factor > 1: + if self.balance_factor(node.left) >= 0: + # 右旋 + return self.right_rotate(node) + else: + # 先左旋後右旋 + node.left = self.left_rotate(node.left) + return self.right_rotate(node) + # 右偏樹 + elif balance_factor < -1: + if self.balance_factor(node.right) <= 0: + # 左旋 + return self.left_rotate(node) + else: + # 先右旋後左旋 + node.right = self.right_rotate(node.right) + return self.left_rotate(node) + # 平衡樹,無須旋轉,直接返回 + return node + + def insert(self, val): + """插入節點""" + self._root = self.insert_helper(self._root, val) + + def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: + """遞迴插入節點(輔助方法)""" + if node is None: + return TreeNode(val) + # 1. 查詢插入位置並插入節點 + if val < node.val: + node.left = self.insert_helper(node.left, val) + elif val > node.val: + node.right = self.insert_helper(node.right, val) + else: + # 重複節點不插入,直接返回 + return node + # 更新節點高度 + self.update_height(node) + # 2. 執行旋轉操作,使該子樹重新恢復平衡 + return self.rotate(node) + + def remove(self, val: int): + """刪除節點""" + self._root = self.remove_helper(self._root, val) + + def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: + """遞迴刪除節點(輔助方法)""" + if node is None: + return None + # 1. 查詢節點並刪除 + if val < node.val: + node.left = self.remove_helper(node.left, val) + elif val > node.val: + node.right = self.remove_helper(node.right, val) + else: + if node.left is None or node.right is None: + child = node.left or node.right + # 子節點數量 = 0 ,直接刪除 node 並返回 + if child is None: + return None + # 子節點數量 = 1 ,直接刪除 node + else: + node = child + else: + # 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + temp = node.right + while temp.left is not None: + temp = temp.left + node.right = self.remove_helper(node.right, temp.val) + node.val = temp.val + # 更新節點高度 + self.update_height(node) + # 2. 執行旋轉操作,使該子樹重新恢復平衡 + return self.rotate(node) + + def search(self, val: int) -> TreeNode | None: + """查詢節點""" + cur = self._root + # 迴圈查詢,越過葉節點後跳出 + while cur is not None: + # 目標節點在 cur 的右子樹中 + if cur.val < val: + cur = cur.right + # 目標節點在 cur 的左子樹中 + elif cur.val > val: + cur = cur.left + # 找到目標節點,跳出迴圈 + else: + break + # 返回目標節點 + return cur + + +"""Driver Code""" +if __name__ == "__main__": + + def test_insert(tree: AVLTree, val: int): + tree.insert(val) + print("\n插入節點 {} 後,AVL 樹為".format(val)) + print_tree(tree.get_root()) + + def test_remove(tree: AVLTree, val: int): + tree.remove(val) + print("\n刪除節點 {} 後,AVL 樹為".format(val)) + print_tree(tree.get_root()) + + # 初始化空 AVL 樹 + avl_tree = AVLTree() + + # 插入節點 + # 請關注插入節點後,AVL 樹是如何保持平衡的 + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]: + test_insert(avl_tree, val) + + # 插入重複節點 + test_insert(avl_tree, 7) + + # 刪除節點 + # 請關注刪除節點後,AVL 樹是如何保持平衡的 + test_remove(avl_tree, 8) # 刪除度為 0 的節點 + test_remove(avl_tree, 5) # 刪除度為 1 的節點 + test_remove(avl_tree, 4) # 刪除度為 2 的節點 + + result_node = avl_tree.search(7) + print("\n查詢到的節點物件為 {},節點值 = {}".format(result_node, result_node.val)) diff --git a/zh-hant/codes/python/chapter_tree/binary_search_tree.py b/zh-hant/codes/python/chapter_tree/binary_search_tree.py new file mode 100644 index 0000000000..1e7ea1c691 --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_search_tree.py @@ -0,0 +1,146 @@ +""" +File: binary_search_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +class BinarySearchTree: + """二元搜尋樹""" + + def __init__(self): + """建構子""" + # 初始化空樹 + self._root = None + + def get_root(self) -> TreeNode | None: + """獲取二元樹根節點""" + return self._root + + def search(self, num: int) -> TreeNode | None: + """查詢節點""" + cur = self._root + # 迴圈查詢,越過葉節點後跳出 + while cur is not None: + # 目標節點在 cur 的右子樹中 + if cur.val < num: + cur = cur.right + # 目標節點在 cur 的左子樹中 + elif cur.val > num: + cur = cur.left + # 找到目標節點,跳出迴圈 + else: + break + return cur + + def insert(self, num: int): + """插入節點""" + # 若樹為空,則初始化根節點 + if self._root is None: + self._root = TreeNode(num) + return + # 迴圈查詢,越過葉節點後跳出 + cur, pre = self._root, None + while cur is not None: + # 找到重複節點,直接返回 + if cur.val == num: + return + pre = cur + # 插入位置在 cur 的右子樹中 + if cur.val < num: + cur = cur.right + # 插入位置在 cur 的左子樹中 + else: + cur = cur.left + # 插入節點 + node = TreeNode(num) + if pre.val < num: + pre.right = node + else: + pre.left = node + + def remove(self, num: int): + """刪除節點""" + # 若樹為空,直接提前返回 + if self._root is None: + return + # 迴圈查詢,越過葉節點後跳出 + cur, pre = self._root, None + while cur is not None: + # 找到待刪除節點,跳出迴圈 + if cur.val == num: + break + pre = cur + # 待刪除節點在 cur 的右子樹中 + if cur.val < num: + cur = cur.right + # 待刪除節點在 cur 的左子樹中 + else: + cur = cur.left + # 若無待刪除節點,則直接返回 + if cur is None: + return + + # 子節點數量 = 0 or 1 + if cur.left is None or cur.right is None: + # 當子節點數量 = 0 / 1 時, child = null / 該子節點 + child = cur.left or cur.right + # 刪除節點 cur + if cur != self._root: + if pre.left == cur: + pre.left = child + else: + pre.right = child + else: + # 若刪除節點為根節點,則重新指定根節點 + self._root = child + # 子節點數量 = 2 + else: + # 獲取中序走訪中 cur 的下一個節點 + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # 遞迴刪除節點 tmp + self.remove(tmp.val) + # 用 tmp 覆蓋 cur + cur.val = tmp.val + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二元搜尋樹 + bst = BinarySearchTree() + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + for num in nums: + bst.insert(num) + print("\n初始化的二元樹為\n") + print_tree(bst.get_root()) + + # 查詢節點 + node = bst.search(7) + print("\n查詢到的節點物件為: {},節點值 = {}".format(node, node.val)) + + # 插入節點 + bst.insert(16) + print("\n插入節點 16 後,二元樹為\n") + print_tree(bst.get_root()) + + # 刪除節點 + bst.remove(1) + print("\n刪除節點 1 後,二元樹為\n") + print_tree(bst.get_root()) + + bst.remove(2) + print("\n刪除節點 2 後,二元樹為\n") + print_tree(bst.get_root()) + + bst.remove(4) + print("\n刪除節點 4 後,二元樹為\n") + print_tree(bst.get_root()) diff --git a/zh-hant/codes/python/chapter_tree/binary_tree.py b/zh-hant/codes/python/chapter_tree/binary_tree.py new file mode 100644 index 0000000000..7a62f98593 --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_tree.py @@ -0,0 +1,41 @@ +""" +File: binary_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二元樹 + # 初始化節點 + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\n初始化二元樹\n") + print_tree(n1) + + # 插入與刪除節點 + P = TreeNode(0) + # 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + print("\n插入節點 P 後\n") + print_tree(n1) + # 刪除節點 + n1.left = n2 + print("\n刪除節點 P 後\n") + print_tree(n1) diff --git a/zh-hant/codes/python/chapter_tree/binary_tree_bfs.py b/zh-hant/codes/python/chapter_tree/binary_tree_bfs.py new file mode 100644 index 0000000000..58cdcc4a3f --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_tree_bfs.py @@ -0,0 +1,42 @@ +""" +File: binary_tree_bfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree +from collections import deque + + +def level_order(root: TreeNode | None) -> list[int]: + """層序走訪""" + # 初始化佇列,加入根節點 + queue: deque[TreeNode] = deque() + queue.append(root) + # 初始化一個串列,用於儲存走訪序列 + res = [] + while queue: + node: TreeNode = queue.popleft() # 隊列出隊 + res.append(node.val) # 儲存節點值 + if node.left is not None: + queue.append(node.left) # 左子節點入列 + if node.right is not None: + queue.append(node.right) # 右子節點入列 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二元樹 + # 這裡藉助了一個從陣列直接生成二元樹的函式 + root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\n初始化二元樹\n") + print_tree(root) + + # 層序走訪 + res: list[int] = level_order(root) + print("\n層序走訪的節點列印序列 = ", res) diff --git a/zh-hant/codes/python/chapter_tree/binary_tree_dfs.py b/zh-hant/codes/python/chapter_tree/binary_tree_dfs.py new file mode 100644 index 0000000000..73f8f605fb --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_tree_dfs.py @@ -0,0 +1,65 @@ +""" +File: binary_tree_dfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree + + +def pre_order(root: TreeNode | None): + """前序走訪""" + if root is None: + return + # 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + res.append(root.val) + pre_order(root=root.left) + pre_order(root=root.right) + + +def in_order(root: TreeNode | None): + """中序走訪""" + if root is None: + return + # 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + in_order(root=root.left) + res.append(root.val) + in_order(root=root.right) + + +def post_order(root: TreeNode | None): + """後序走訪""" + if root is None: + return + # 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + post_order(root=root.left) + post_order(root=root.right) + res.append(root.val) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二元樹 + # 這裡藉助了一個從陣列直接生成二元樹的函式 + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\n初始化二元樹\n") + print_tree(root) + + # 前序走訪 + res = [] + pre_order(root) + print("\n前序走訪的節點列印序列 = ", res) + + # 中序走訪 + res.clear() + in_order(root) + print("\n中序走訪的節點列印序列 = ", res) + + # 後序走訪 + res.clear() + post_order(root) + print("\n後序走訪的節點列印序列 = ", res) diff --git a/zh-hant/codes/python/modules/__init__.py b/zh-hant/codes/python/modules/__init__.py new file mode 100644 index 0000000000..b10799e3c5 --- /dev/null +++ b/zh-hant/codes/python/modules/__init__.py @@ -0,0 +1,19 @@ +# Follow the PEP 585 - Type Hinting Generics In Standard Collections +# https://peps.python.org/pep-0585/ +from __future__ import annotations + +# Import common libs here to simplify the code by `from module import *` +from .list_node import ( + ListNode, + list_to_linked_list, + linked_list_to_list, +) +from .tree_node import TreeNode, list_to_tree, tree_to_list +from .vertex import Vertex, vals_to_vets, vets_to_vals +from .print_util import ( + print_matrix, + print_linked_list, + print_tree, + print_dict, + print_heap, +) diff --git a/zh-hant/codes/python/modules/list_node.py b/zh-hant/codes/python/modules/list_node.py new file mode 100644 index 0000000000..40a5fa4584 --- /dev/null +++ b/zh-hant/codes/python/modules/list_node.py @@ -0,0 +1,32 @@ +""" +File: list_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """鏈結串列節點類別""" + + def __init__(self, val: int): + self.val: int = val # 節點值 + self.next: ListNode | None = None # 後繼節點引用 + + +def list_to_linked_list(arr: list[int]) -> ListNode | None: + """將串列反序列化為鏈結串列""" + dum = head = ListNode(0) + for a in arr: + node = ListNode(a) + head.next = node + head = head.next + return dum.next + + +def linked_list_to_list(head: ListNode | None) -> list[int]: + """將鏈結串列序列化為串列""" + arr: list[int] = [] + while head: + arr.append(head.val) + head = head.next + return arr diff --git a/zh-hant/codes/python/modules/print_util.py b/zh-hant/codes/python/modules/print_util.py new file mode 100644 index 0000000000..ff4da22136 --- /dev/null +++ b/zh-hant/codes/python/modules/print_util.py @@ -0,0 +1,81 @@ +""" +File: print_util.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com) +""" + +from .tree_node import TreeNode, list_to_tree +from .list_node import ListNode, linked_list_to_list + + +def print_matrix(mat: list[list[int]]): + """列印矩陣""" + s = [] + for arr in mat: + s.append(" " + str(arr)) + print("[\n" + ",\n".join(s) + "\n]") + + +def print_linked_list(head: ListNode | None): + """列印鏈結串列""" + arr: list[int] = linked_list_to_list(head) + print(" -> ".join([str(a) for a in arr])) + + +class Trunk: + def __init__(self, prev, string: str | None = None): + self.prev = prev + self.str = string + + +def show_trunks(p: Trunk | None): + if p is None: + return + show_trunks(p.prev) + print(p.str, end="") + + +def print_tree( + root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False +): + """ + 列印二元樹 + This tree printer is borrowed from TECHIE DELIGHT + https://www.techiedelight.com/c-program-print-binary-tree/ + """ + if root is None: + return + + prev_str = " " + trunk = Trunk(prev, prev_str) + print_tree(root.right, trunk, True) + + if prev is None: + trunk.str = "———" + elif is_right: + trunk.str = "/———" + prev_str = " |" + else: + trunk.str = "\———" + prev.str = prev_str + + show_trunks(trunk) + print(" " + str(root.val)) + if prev: + prev.str = prev_str + trunk.str = " |" + print_tree(root.left, trunk, False) + + +def print_dict(hmap: dict): + """列印字典""" + for key, value in hmap.items(): + print(key, "->", value) + + +def print_heap(heap: list[int]): + """列印堆積""" + print("堆積的陣列表示:", heap) + print("堆積的樹狀表示:") + root: TreeNode | None = list_to_tree(heap) + print_tree(root) diff --git a/zh-hant/codes/python/modules/tree_node.py b/zh-hant/codes/python/modules/tree_node.py new file mode 100644 index 0000000000..887fe79031 --- /dev/null +++ b/zh-hant/codes/python/modules/tree_node.py @@ -0,0 +1,69 @@ +""" +File: tree_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + +from collections import deque + + +class TreeNode: + """二元樹節點類別""" + + def __init__(self, val: int = 0): + self.val: int = val # 節點值 + self.height: int = 0 # 節點高度 + self.left: TreeNode | None = None # 左子節點引用 + self.right: TreeNode | None = None # 右子節點引用 + + # 序列化編碼規則請參考: + # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + # 二元樹的陣列表示: + # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + # 二元樹的鏈結串列表示: + # /——— 15 + # /——— 7 + # /——— 3 + # | \——— 6 + # | \——— 12 + # ——— 1 + # \——— 2 + # | /——— 9 + # \——— 4 + # \——— 8 + + +def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: + """將串列反序列化為二元樹:遞迴""" + # 如果索引超出陣列長度,或者對應的元素為 None ,則返回 None + if i < 0 or i >= len(arr) or arr[i] is None: + return None + # 構建當前節點 + root = TreeNode(arr[i]) + # 遞迴構建左右子樹 + root.left = list_to_tree_dfs(arr, 2 * i + 1) + root.right = list_to_tree_dfs(arr, 2 * i + 2) + return root + + +def list_to_tree(arr: list[int]) -> TreeNode | None: + """將串列反序列化為二元樹""" + return list_to_tree_dfs(arr, 0) + + +def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: + """將二元樹序列化為串列:遞迴""" + if root is None: + return + if i >= len(res): + res += [None] * (i - len(res) + 1) + res[i] = root.val + tree_to_list_dfs(root.left, 2 * i + 1, res) + tree_to_list_dfs(root.right, 2 * i + 2, res) + + +def tree_to_list(root: TreeNode | None) -> list[int]: + """將二元樹序列化為串列""" + res = [] + tree_to_list_dfs(root, 0, res) + return res diff --git a/zh-hant/codes/python/modules/vertex.py b/zh-hant/codes/python/modules/vertex.py new file mode 100644 index 0000000000..bffd3154ff --- /dev/null +++ b/zh-hant/codes/python/modules/vertex.py @@ -0,0 +1,20 @@ +# File: vertex.py +# Created Time: 2023-02-23 +# Author: krahets (krahets@163.com) + + +class Vertex: + """頂點類別""" + + def __init__(self, val: int): + self.val = val + + +def vals_to_vets(vals: list[int]) -> list["Vertex"]: + """輸入值串列 vals ,返回頂點串列 vets""" + return [Vertex(val) for val in vals] + + +def vets_to_vals(vets: list["Vertex"]) -> list[int]: + """輸入頂點串列 vets ,返回值串列 vals""" + return [vet.val for vet in vets] diff --git a/zh-hant/codes/python/test_all.py b/zh-hant/codes/python/test_all.py new file mode 100644 index 0000000000..a90c773a4f --- /dev/null +++ b/zh-hant/codes/python/test_all.py @@ -0,0 +1,33 @@ +import os +import glob +import subprocess + +env = os.environ.copy() +env["PYTHONIOENCODING"] = "utf-8" + +if __name__ == "__main__": + # find source code files + src_paths = sorted(glob.glob("codes/python/chapter_*/*.py")) + errors = [] + + # run python code + for src_path in src_paths: + process = subprocess.Popen( + ["python", src_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + encoding='utf-8' + ) + # Wait for the process to complete, and get the output and error messages + stdout, stderr = process.communicate() + # Check the exit status + exit_status = process.returncode + if exit_status != 0: + errors.append(stderr) + + print(f"Tested {len(src_paths)} files") + print(f"Found exception in {len(errors)} files") + if len(errors) > 0: + raise RuntimeError("\n\n".join(errors)) diff --git a/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/array.md b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/array.md new file mode 100644 index 0000000000..4f2ce6e048 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/array.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%A8%E6%A9%9F%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5B0%2C%20len%28nums%29-1%5D%20%E4%B8%AD%E9%9A%A8%E6%A9%9F%E6%8A%BD%E5%8F%96%E4%B8%80%E5%80%8B%E6%95%B8%E5%AD%97%0A%20%20%20%20random_index%20%3D%20random.randint%280%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%B8%A6%E8%BF%94%E5%9B%9E%E9%9A%A8%E6%A9%9F%E5%85%83%E7%B4%A0%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%9A%A8%E6%A9%9F%E8%A8%AA%E5%95%8F%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E7%8D%B2%E5%8F%96%E9%9A%A8%E6%A9%9F%E5%85%83%E7%B4%A0%22%2C%20random_num%29%0A&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%99%A3%E5%88%97%E7%9A%84%E7%B4%A2%E5%BC%95%20index%20%E8%99%95%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%20num%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%BE%8C%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%BE%8C%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%20index%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%B0%87%20num%20%E8%B3%A6%E7%B5%A6%20index%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20insert%28nums%2C%206%2C%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E6%8F%92%E5%85%A5%E6%95%B8%E5%AD%97%206%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%B9%8B%E5%BE%8C%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%89%8D%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28index%2C%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20remove%28nums%2C%202%29%0A%20%20%20%20print%28%22%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%202%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E7%B4%A2%E5%BC%95%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%E5%90%8C%E6%99%82%E8%B5%B0%E8%A8%AA%E8%B3%87%E6%96%99%E7%B4%A2%E5%BC%95%E5%92%8C%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i%2C%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%99%A3%E5%88%97%E4%B8%AD%E6%9F%A5%E8%A9%A2%E6%8C%87%E5%AE%9A%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E5%85%83%E7%B4%A0%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums%2C%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E6%9F%A5%E8%A9%A2%E5%85%83%E7%B4%A0%203%20%EF%BC%8C%E5%BE%97%E5%88%B0%E7%B4%A2%E5%BC%95%20%3D%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8CPython%20%E7%9A%84%20list%20%E6%98%AF%E5%8B%95%E6%85%8B%E9%99%A3%E5%88%97%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%93%B4%E5%B1%95%0A%23%20%E7%82%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E5%AD%B8%E7%BF%92%EF%BC%8C%E6%9C%AC%E5%87%BD%E5%BC%8F%E5%B0%87%20list%20%E7%9C%8B%E4%BD%9C%E9%95%B7%E5%BA%A6%E4%B8%8D%E5%8F%AF%E8%AE%8A%E7%9A%84%E9%99%A3%E5%88%97%0Adef%20extend%28nums%3A%20list%5Bint%5D%2C%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%93%B4%E5%B1%95%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E5%80%8B%E6%93%B4%E5%B1%95%E9%95%B7%E5%BA%A6%E5%BE%8C%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%E5%B0%87%E5%8E%9F%E9%99%A3%E5%88%97%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%88%B0%E6%96%B0%E9%99%A3%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%93%B4%E5%B1%95%E5%BE%8C%E7%9A%84%E6%96%B0%E9%99%A3%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%99%A3%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%95%B7%E5%BA%A6%E6%93%B4%E5%B1%95%0A%20%20%20%20nums%20%3D%20extend%28nums%2C%203%29%0A%20%20%20%20print%28%22%E5%B0%87%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E6%93%B4%E5%B1%95%E8%87%B3%208%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md new file mode 100644 index 0000000000..c268e4197e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20insert%28n0%3A%20ListNode%2C%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%9A%84%E7%AF%80%E9%BB%9E%20n0%20%E4%B9%8B%E5%BE%8C%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%20P%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0%2C%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%9A%84%E7%AF%80%E9%BB%9E%20n0%20%E4%B9%8B%E5%BE%8C%E7%9A%84%E9%A6%96%E5%80%8B%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20access%28head%3A%20ListNode%2C%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E7%B4%A2%E5%BC%95%E7%82%BA%20index%20%E7%9A%84%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E7%AF%80%E9%BB%9E%0A%20%20%20%20node%20%3D%20access%28n0%2C%203%29%0A%20%20%20%20print%28%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E7%9A%84%E7%AF%80%E9%BB%9E%E7%9A%84%E5%80%BC%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20find%28head%3A%20ListNode%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E6%9F%A5%E8%A9%A2%E5%80%BC%E7%82%BA%20target%20%E7%9A%84%E9%A6%96%E5%80%8B%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%0A%20%20%20%20index%20%3D%20find%28n0%2C%202%29%0A%20%20%20%20print%28%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E4%B8%AD%E5%80%BC%E7%82%BA%202%20%E7%9A%84%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/my_list.md b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/my_list.md new file mode 100644 index 0000000000..b27c96c021 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/my_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20MyList%3A%0A%20%20%20%20%22%22%22%E4%B8%B2%E5%88%97%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%B8%B2%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%88%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E6%95%B8%E9%87%8F%EF%BC%89%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%B8%B2%E5%88%97%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%96%B0%E5%A2%9E%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%BE%8C%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%BE%8C%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201%2C%20index%20-%201%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%20i%20%E4%B9%8B%E5%BE%8C%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%89%8D%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index%2C%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%B2%E5%88%97%E6%93%B4%E5%AE%B9%22%22%22%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20%2A%20self.capacity%28%29%20%2A%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%96%B0%E5%A2%9E%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%286%2C%20index%3D3%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.remove%283%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.set%280%2C%201%29%0A%0A%20%20%20%20%23%20%E6%B8%AC%E8%A9%A6%E6%93%B4%E5%AE%B9%E6%A9%9F%E5%88%B6%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/n_queens.md b/zh-hant/codes/pythontutor/chapter_backtracking/n_queens.md new file mode 100644 index 0000000000..8a6813200e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/n_queens.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20row%3A%20int%2C%0A%20%20%20%20n%3A%20int%2C%0A%20%20%20%20state%3A%20list%5Blist%5Bstr%5D%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D%2C%0A%20%20%20%20cols%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags1%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags2%3A%20list%5Bbool%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9AN%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E6%94%BE%E7%BD%AE%E5%AE%8C%E6%89%80%E6%9C%89%E8%A1%8C%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E5%88%97%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E8%A9%B2%E6%A0%BC%E5%AD%90%E5%B0%8D%E6%87%89%E7%9A%84%E4%B8%BB%E5%B0%8D%E8%A7%92%E7%B7%9A%E5%92%8C%E6%AC%A1%E5%B0%8D%E8%A7%92%E7%B7%9A%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E8%A9%B2%E6%A0%BC%E5%AD%90%E6%89%80%E5%9C%A8%E5%88%97%E3%80%81%E4%B8%BB%E5%B0%8D%E8%A7%92%E7%B7%9A%E3%80%81%E6%AC%A1%E5%B0%8D%E8%A7%92%E7%B7%9A%E4%B8%8A%E5%AD%98%E5%9C%A8%E7%9A%87%E5%90%8E%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20not%20diags1%5Bdiag1%5D%20and%20not%20diags2%5Bdiag2%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%B0%87%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E5%9C%A8%E8%A9%B2%E6%A0%BC%E5%AD%90%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22Q%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%94%BE%E7%BD%AE%E4%B8%8B%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E5%B0%87%E8%A9%B2%E6%A0%BC%E5%AD%90%E6%81%A2%E5%BE%A9%E7%82%BA%E7%A9%BA%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22%23%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%20N%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20n%2An%20%E5%A4%A7%E5%B0%8F%E7%9A%84%E6%A3%8B%E7%9B%A4%EF%BC%8C%E5%85%B6%E4%B8%AD%20%27Q%27%20%E4%BB%A3%E8%A1%A8%E7%9A%87%E5%90%8E%EF%BC%8C%27%23%27%20%E4%BB%A3%E8%A1%A8%E7%A9%BA%E4%BD%8D%0A%20%20%20%20state%20%3D%20%5B%5B%22%23%22%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20%2A%20n%20%20%23%20%E8%A8%98%E9%8C%84%E5%88%97%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E8%A8%98%E9%8C%84%E4%B8%BB%E5%B0%8D%E8%A7%92%E7%B7%9A%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E8%A8%98%E9%8C%84%E6%AC%A1%E5%B0%8D%E8%A7%92%E7%B7%9A%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E6%A3%8B%E7%9B%A4%E9%95%B7%E5%AF%AC%E7%82%BA%20%7Bn%7D%22%29%0A%20%20%20%20print%28f%22%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E6%96%B9%E6%A1%88%E5%85%B1%E6%9C%89%20%7Blen%28res%29%7D%20%E7%A8%AE%22%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%22--------------------%22%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/permutations_i.md b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_i.md new file mode 100644 index 0000000000..f4ec2dee4b --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E7%8B%80%E6%85%8B%E9%95%B7%E5%BA%A6%E7%AD%89%E6%96%BC%E5%85%83%E7%B4%A0%E6%95%B8%E9%87%8F%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/permutations_ii.md b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_ii.md new file mode 100644 index 0000000000..ab383b311d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E7%8B%80%E6%85%8B%E9%95%B7%E5%BA%A6%E7%AD%89%E6%96%BC%E5%85%83%E7%B4%A0%E6%95%B8%E9%87%8F%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E5%85%83%E7%B4%A0%20%E4%B8%94%20%E4%B8%8D%E5%85%81%E8%A8%B1%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E7%9B%B8%E7%AD%89%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%E8%A8%98%E9%8C%84%E9%81%B8%E6%93%87%E9%81%8E%E7%9A%84%E5%85%83%E7%B4%A0%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md new file mode 100644 index 0000000000..633affb373 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%B8%80%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E5%80%BC%E7%82%BA%207%20%E7%9A%84%E7%AF%80%E9%BB%9E%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md new file mode 100644 index 0000000000..03c0f96423 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%BA%8C%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%98%97%E8%A9%A6%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E7%AF%80%E9%BB%9E%E5%88%B0%E7%AF%80%E9%BB%9E%207%20%E7%9A%84%E8%B7%AF%E5%BE%91%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md new file mode 100644 index 0000000000..ef4cbcd7ed --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%98%97%E8%A9%A6%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E7%AF%80%E9%BB%9E%E5%88%B0%E7%AF%80%E9%BB%9E%207%20%E7%9A%84%E8%B7%AF%E5%BE%91%EF%BC%8C%E8%B7%AF%E5%BE%91%E4%B8%AD%E4%B8%8D%E5%8C%85%E5%90%AB%E5%80%BC%E7%82%BA%203%20%E7%9A%84%E7%AF%80%E9%BB%9E%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md new file mode 100644 index 0000000000..321bfa8727 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E7%95%B6%E5%89%8D%E7%8B%80%E6%85%8B%E6%98%AF%E5%90%A6%E7%82%BA%E8%A7%A3%22%22%22%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%98%E9%8C%84%E8%A7%A3%22%22%22%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%9C%A8%E7%95%B6%E5%89%8D%E7%8B%80%E6%85%8B%E4%B8%8B%EF%BC%8C%E8%A9%B2%E9%81%B8%E6%93%87%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%22%22%22%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20%21%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%22%22%22%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%81%A2%E5%BE%A9%E7%8B%80%E6%85%8B%22%22%22%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28%0A%20%20%20%20state%3A%20list%5BTreeNode%5D%2C%20choices%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E4%BE%8B%E9%A1%8C%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E6%AA%A2%E6%9F%A5%E6%98%AF%E5%90%A6%E7%82%BA%E8%A7%A3%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20%20%20%20%20record_solution%28state%2C%20res%29%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E6%AA%A2%E6%9F%A5%E9%81%B8%E6%93%87%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%0A%20%20%20%20%20%20%20%20if%20is_valid%28state%2C%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state%2C%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20%5Bchoice.left%2C%20choice.right%5D%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state%2C%20choice%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3D%5Broot%5D%2C%20res%3Dres%29%0A%20%20%20%20print%28%22%5Cn%E8%BC%B8%E5%87%BA%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%91%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i.md b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i.md new file mode 100644 index 0000000000..2c56147980 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E6%96%BC%20target%20%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E5%BE%9E%20start%20%E9%96%8B%E5%A7%8B%E8%B5%B0%E8%A8%AA%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E8%A4%87%E5%AD%90%E9%9B%86%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E9%81%8E%20target%20%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E7%B5%90%E6%9D%9F%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%23%20%E9%80%99%E6%98%AF%E5%9B%A0%E7%82%BA%E9%99%A3%E5%88%97%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%BE%8C%E9%82%8A%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E9%81%8E%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%20target%2C%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8B%80%E6%85%8B%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%B0%8D%20nums%20%E9%80%B2%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%B5%B7%E5%A7%8B%E9%BB%9E%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E4%B8%B2%E5%88%97%EF%BC%88%E5%AD%90%E9%9B%86%E4%B8%B2%E5%88%97%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E6%96%BC%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md new file mode 100644 index 0000000000..6145cbe38d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%0A%20%20%20%20target%3A%20int%2C%0A%20%20%20%20total%3A%20int%2C%0A%20%20%20%20choices%3A%20list%5Bint%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E6%96%BC%20target%20%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E9%81%8E%20target%20%EF%BC%8C%E5%89%87%E8%B7%B3%E9%81%8E%E8%A9%B2%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%E5%92%8C%20total%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%2C%20total%20%2B%20choices%5Bi%5D%2C%20choices%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%EF%BC%88%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%AD%90%E9%9B%86%EF%BC%89%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8B%80%E6%85%8B%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20total%20%3D%200%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E4%B8%B2%E5%88%97%EF%BC%88%E5%AD%90%E9%9B%86%E4%B8%B2%E5%88%97%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20total%2C%20nums%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E6%96%BC%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%A9%B2%E6%96%B9%E6%B3%95%E8%BC%B8%E5%87%BA%E7%9A%84%E7%B5%90%E6%9E%9C%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E9%9B%86%E5%90%88%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_ii.md b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_ii.md new file mode 100644 index 0000000000..07add40bf1 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E6%BC%94%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E6%96%BC%20target%20%E6%99%82%EF%BC%8C%E8%A8%98%E9%8C%84%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E5%BE%9E%20start%20%E9%96%8B%E5%A7%8B%E8%B5%B0%E8%A8%AA%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E8%A4%87%E5%AD%90%E9%9B%86%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%89%EF%BC%9A%E5%BE%9E%20start%20%E9%96%8B%E5%A7%8B%E8%B5%B0%E8%A8%AA%EF%BC%8C%E9%81%BF%E5%85%8D%E9%87%8D%E8%A4%87%E9%81%B8%E6%93%87%E5%90%8C%E4%B8%80%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E9%81%8E%20target%20%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E7%B5%90%E6%9D%9F%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%23%20%E9%80%99%E6%98%AF%E5%9B%A0%E7%82%BA%E9%99%A3%E5%88%97%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%BE%8C%E9%82%8A%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E9%81%8E%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E5%9B%9B%EF%BC%9A%E5%A6%82%E6%9E%9C%E8%A9%B2%E5%85%83%E7%B4%A0%E8%88%87%E5%B7%A6%E9%82%8A%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%EF%BC%8C%E8%AA%AA%E6%98%8E%E8%A9%B2%E6%90%9C%E5%B0%8B%E5%88%86%E6%94%AF%E9%87%8D%E8%A4%87%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E9%81%8E%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%20target%2C%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E9%80%B2%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BC%AA%E9%81%B8%E6%93%87%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%20%2B%201%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%8A%B7%E9%81%B8%E6%93%87%EF%BC%8C%E6%81%A2%E5%BE%A9%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8B%80%E6%85%8B%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%B0%8D%20nums%20%E9%80%B2%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%B5%B7%E5%A7%8B%E9%BB%9E%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E4%B8%B2%E5%88%97%EF%BC%88%E5%AD%90%E9%9B%86%E4%B8%B2%E5%88%97%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E6%96%BC%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/iteration.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/iteration.md new file mode 100644 index 0000000000..7fa1b6ccc0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/iteration.md @@ -0,0 +1,29 @@ + + + +https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22for%20%E8%BF%B4%E5%9C%88%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%B1%82%E5%92%8C%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnfor%20%E8%BF%B4%E5%9C%88%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& + + +https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E8%BF%B4%E5%9C%88%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%B1%82%E5%92%8C%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E6%9B%B4%E6%96%B0%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E8%BF%B4%E5%9C%88%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E8%BF%B4%E5%9C%88%EF%BC%88%E5%85%A9%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%B1%82%E5%92%8C%201%2C%204%2C%2010%2C%20...%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%A2%9D%E4%BB%B6%E8%AE%8A%E6%95%B8%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20%2A%3D%202%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E8%BF%B4%E5%9C%88%EF%BC%88%E5%85%A9%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%E9%9B%99%E5%B1%A4%20for%20%E8%BF%B4%E5%9C%88%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%20i%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%20j%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D%2C%20%7Bj%7D%29%2C%20%22%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%9B%99%E5%B1%A4%20for%20%E8%BF%B4%E5%9C%88%E7%9A%84%E8%B5%B0%E8%A8%AA%E7%B5%90%E6%9E%9C%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E5%80%8B%E9%A1%AF%E5%BC%8F%E7%9A%84%E5%A0%86%E7%96%8A%E4%BE%86%E6%A8%A1%E6%93%AC%E7%B3%BB%E7%B5%B1%E5%91%BC%E5%8F%AB%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%85%A5%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E9%81%9E%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%87%BA%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E8%BF%B4%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/recursion.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/recursion.md new file mode 100644 index 0000000000..768b0200b9 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/recursion.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%81%9E%E8%BF%B4%E5%87%BD%E5%BC%8F%E7%9A%84%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E8%B2%BB%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B8%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E5%80%8B%E9%A1%AF%E5%BC%8F%E7%9A%84%E5%A0%86%E7%96%8A%E4%BE%86%E6%A8%A1%E6%93%AC%E7%B3%BB%E7%B5%B1%E5%91%BC%E5%8F%AB%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%81%9E%EF%BC%9A%E9%81%9E%E8%BF%B4%E5%91%BC%E5%8F%AB%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%85%A5%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E9%81%9E%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E8%BF%B4%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%B5%90%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E2%80%9C%E5%87%BA%E5%A0%86%E7%96%8A%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%93%AC%E2%80%9C%E8%BF%B4%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%93%AC%E9%81%9E%E8%BF%B4%E6%B1%82%E5%92%8C%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/space_complexity.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/space_complexity.md new file mode 100644 index 0000000000..75470528a8 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20%23%20%E5%9F%B7%E8%A1%8C%E6%9F%90%E4%BA%9B%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B8%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E5%B8%B8%E6%95%B8%E3%80%81%E8%AE%8A%E6%95%B8%E3%80%81%E7%89%A9%E4%BB%B6%E4%BD%94%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E4%B8%AD%E7%9A%84%E8%AE%8A%E6%95%B8%E4%BD%94%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E4%B8%AD%E7%9A%84%E5%87%BD%E5%BC%8F%E4%BD%94%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%B8%B8%E6%95%B8%E9%9A%8E%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E9%95%B7%E5%BA%A6%E7%82%BA%20n%20%E7%9A%84%E4%B8%B2%E5%88%97%E4%BD%94%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%23%20%E9%95%B7%E5%BA%A6%E7%82%BA%20n%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%BD%94%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20hmap%20%3D%20dict%5Bint%2C%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E6%80%A7%E9%9A%8E%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20print%28%22%E9%81%9E%E8%BF%B4%20n%20%3D%22%2C%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E6%80%A7%E9%9A%8E%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E4%BA%8C%E7%B6%AD%E4%B8%B2%E5%88%97%E4%BD%94%E7%94%A8%20O%28n%5E2%29%20%E7%A9%BA%E9%96%93%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20%2A%20n%20for%20_%20in%20range%28n%29%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%9A%8E%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E9%99%A3%E5%88%97%20nums%20%E9%95%B7%E5%BA%A6%E7%82%BA%20n%2C%20n-1%2C%20...%2C%202%2C%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%9A%8E%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E5%BB%BA%E7%AB%8B%E6%BB%BF%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E6%8C%87%E6%95%B8%E9%9A%8E%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/time_complexity.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/time_complexity.md new file mode 100644 index 0000000000..b10f17a10e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,38 @@ + + + +https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B8%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%E5%B8%B8%E6%95%B8%E9%9A%8E%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E6%80%A7%E9%9A%8E%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E9%9A%8E%EF%BC%88%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%AC%A1%E6%95%B8%E8%88%87%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E6%88%90%E6%AD%A3%E6%AF%94%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20%2A%20n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E6%80%A7%E9%9A%8E%EF%BC%88%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%AC%A1%E6%95%B8%E8%88%87%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%E6%88%90%E5%B9%B3%E6%96%B9%E9%97%9C%E4%BF%82%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%9A%8E%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%9A%8E%EF%BC%88%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%E8%A8%88%E6%95%B8%E5%99%A8%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E8%87%B3%E8%A9%B2%E5%8D%80%E9%96%93%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%20nums%5Bj%5D%20%E8%88%87%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E5%8C%85%E5%90%AB%203%20%E5%80%8B%E5%96%AE%E5%85%83%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%5D%20%20%23%20%5Bn%2C%20n-1%2C%20...%2C%202%2C%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%9A%8E%EF%BC%88%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%E7%B4%B0%E8%83%9E%E6%AF%8F%E8%BC%AA%E4%B8%80%E5%88%86%E7%82%BA%E4%BA%8C%EF%BC%8C%E5%BD%A2%E6%88%90%E6%95%B8%E5%88%97%201%2C%202%2C%204%2C%208%2C%20...%2C%202%5E%28n-1%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20%2A%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20/%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E8%BF%B4%E5%9C%88%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20/%202%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E6%80%A7%E5%B0%8D%E6%95%B8%E9%9A%8E%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20//%202%29%20%2B%20linear_log_recur%28n%20//%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E6%80%A7%E5%B0%8D%E6%95%B8%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E4%B9%98%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%9E%201%20%E5%80%8B%E5%88%86%E8%A3%82%E5%87%BA%20n%20%E5%80%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E5%A4%A7%E5%B0%8F%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%E9%9A%8E%E4%B9%98%E9%9A%8E%EF%BC%88%E9%81%9E%E8%BF%B4%E5%AF%A6%E7%8F%BE%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B8%E9%87%8F%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md new file mode 100644 index 0000000000..8e47318799 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E7%94%9F%E6%88%90%E4%B8%80%E5%80%8B%E9%99%A3%E5%88%97%EF%BC%8C%E5%85%83%E7%B4%A0%E7%82%BA%3A%201%2C%202%2C%20...%2C%20n%20%EF%BC%8C%E9%A0%86%E5%BA%8F%E8%A2%AB%E6%89%93%E4%BA%82%22%22%22%0A%20%20%20%20%23%20%E7%94%9F%E6%88%90%E9%99%A3%E5%88%97%20nums%20%3D%3A%201%2C%202%2C%203%2C%20...%2C%20n%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E9%9A%A8%E6%A9%9F%E6%89%93%E4%BA%82%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E9%99%A3%E5%88%97%20nums%20%E4%B8%AD%E6%95%B8%E5%AD%97%201%20%E6%89%80%E5%9C%A8%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E9%99%A3%E5%88%97%E9%A0%AD%E9%83%A8%E6%99%82%EF%BC%8C%E9%81%94%E5%88%B0%E6%9C%80%E4%BD%B3%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%20O%281%29%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E9%99%A3%E5%88%97%E5%B0%BE%E9%83%A8%E6%99%82%EF%BC%8C%E9%81%94%E5%88%B0%E6%9C%80%E5%B7%AE%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%20O%28n%29%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%E9%99%A3%E5%88%97%20%5B%201%2C%202%2C%20...%2C%20n%20%5D%20%E8%A2%AB%E6%89%93%E4%BA%82%E5%BE%8C%20%3D%22%2C%20nums%29%0A%20%20%20%20print%28%22%E6%95%B8%E5%AD%97%201%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%22%2C%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 0000000000..6ad25fd26c --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%9A%E5%95%8F%E9%A1%8C%20f%28i%2C%20j%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%EF%BC%8C%E4%BB%A3%E8%A1%A8%E7%84%A1%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28m%2B1%2C%20j%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20m%20%2B%201%2C%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28i%2C%20m-1%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20i%2C%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E6%B1%82%E8%A7%A3%E5%95%8F%E9%A1%8C%20f%280%2C%20n-1%29%0A%20%20%20%20return%20dfs%28nums%2C%20target%2C%200%2C%20n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_divide_and_conquer/build_tree.md b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/build_tree.md new file mode 100644 index 0000000000..dc33db4c23 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/build_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D%2C%0A%20%20%20%20inorder_map%3A%20dict%5Bint%2C%20int%5D%2C%0A%20%20%20%20i%3A%20int%2C%0A%20%20%20%20l%3A%20int%2C%0A%20%20%20%20r%3A%20int%2C%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%A7%8B%E5%BB%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E5%88%86%E6%B2%BB%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%A8%B9%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%E6%99%82%E7%B5%82%E6%AD%A2%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%20m%20%EF%BC%8C%E5%BE%9E%E8%80%8C%E5%8A%83%E5%88%86%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%EF%BC%9A%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%2C%20l%2C%20m%20-%201%29%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%EF%BC%9A%E6%A7%8B%E5%BB%BA%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.right%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%20%2B%20m%20-%20l%2C%20m%20%2B%201%2C%20r%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D%2C%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%A7%8B%E5%BB%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E5%84%B2%E5%AD%98%20inorder%20%E5%85%83%E7%B4%A0%E5%88%B0%E7%B4%A2%E5%BC%95%E7%9A%84%E5%B0%8D%E6%98%A0%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i%2C%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder%2C%20inorder_map%2C%200%2C%200%2C%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3%2C%209%2C%202%2C%201%2C%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9%2C%203%2C%201%2C%202%2C%207%5D%0A%20%20%20%20print%28f%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder%2C%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_divide_and_conquer/hanota.md b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/hanota.md new file mode 100644 index 0000000000..2644f47f09 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/hanota.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E7%A7%BB%E5%8B%95%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%22%22%22%0A%20%20%20%20%23%20%E5%BE%9E%20src%20%E9%A0%82%E9%83%A8%E6%8B%BF%E5%87%BA%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%E5%B0%87%E5%9C%93%E7%9B%A4%E6%94%BE%E5%85%A5%20tar%20%E9%A0%82%E9%83%A8%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int%2C%20src%3A%20list%5Bint%5D%2C%20buf%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B2%B3%E5%85%A7%E5%A1%94%E5%95%8F%E9%A1%8C%20f%28i%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%20src%20%E5%8F%AA%E5%89%A9%E4%B8%8B%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E5%B0%87%E5%85%B6%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28i-1%29%20%EF%BC%9A%E5%B0%87%20src%20%E9%A0%82%E9%83%A8%20i-1%20%E5%80%8B%E5%9C%93%E7%9B%A4%E8%97%89%E5%8A%A9%20tar%20%E7%A7%BB%E5%88%B0%20buf%0A%20%20%20%20dfs%28i%20-%201%2C%20src%2C%20tar%2C%20buf%29%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%20f%281%29%20%EF%BC%9A%E5%B0%87%20src%20%E5%89%A9%E9%A4%98%E4%B8%80%E5%80%8B%E5%9C%93%E7%9B%A4%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%23%20%E5%AD%90%E5%95%8F%E9%A1%8C%20f%28i-1%29%20%EF%BC%9A%E5%B0%87%20buf%20%E9%A0%82%E9%83%A8%20i-1%20%E5%80%8B%E5%9C%93%E7%9B%A4%E8%97%89%E5%8A%A9%20src%20%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20dfs%28i%20-%201%2C%20buf%2C%20src%2C%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D%2C%20B%3A%20list%5Bint%5D%2C%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B2%B3%E5%85%A7%E5%A1%94%E5%95%8F%E9%A1%8C%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20%E5%B0%87%20A%20%E9%A0%82%E9%83%A8%20n%20%E5%80%8B%E5%9C%93%E7%9B%A4%E8%97%89%E5%8A%A9%20B%20%E7%A7%BB%E5%88%B0%20C%0A%20%20%20%20dfs%28n%2C%20A%2C%20B%2C%20C%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%B2%E5%88%97%E5%B0%BE%E9%83%A8%E6%98%AF%E6%9F%B1%E5%AD%90%E9%A0%82%E9%83%A8%0A%20%20%20%20A%20%3D%20%5B5%2C%204%2C%203%2C%202%2C%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%E4%B8%8B%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A%2C%20B%2C%20C%29%0A%0A%20%20%20%20print%28%22%E5%9C%93%E7%9B%A4%E7%A7%BB%E5%8B%95%E5%AE%8C%E6%88%90%E5%BE%8C%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md new file mode 100644 index 0000000000..06ac53823a --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D%2C%20state%3A%20int%2C%20n%3A%20int%2C%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%E7%88%AC%E5%88%B0%E7%AC%AC%20n%20%E9%9A%8E%E6%99%82%EF%BC%8C%E6%96%B9%E6%A1%88%E6%95%B8%E9%87%8F%E5%8A%A0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%89%80%E6%9C%89%E9%81%B8%E6%93%87%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%A8%B1%E8%B6%8A%E9%81%8E%E7%AC%AC%20n%20%E9%9A%8E%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%98%97%E8%A9%A6%EF%BC%9A%E5%81%9A%E5%87%BA%E9%81%B8%E6%93%87%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8B%80%E6%85%8B%0A%20%20%20%20%20%20%20%20backtrack%28choices%2C%20state%20%2B%20choice%2C%20n%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1%2C%202%5D%20%20%23%20%E5%8F%AF%E9%81%B8%E6%93%87%E5%90%91%E4%B8%8A%E7%88%AC%201%20%E9%9A%8E%E6%88%96%202%20%E9%9A%8E%0A%20%20%20%20state%20%3D%200%20%20%23%20%E5%BE%9E%E7%AC%AC%200%20%E9%9A%8E%E9%96%8B%E5%A7%8B%E7%88%AC%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%E4%BD%BF%E7%94%A8%20res%5B0%5D%20%E8%A8%98%E9%8C%84%E6%96%B9%E6%A1%88%E6%95%B8%E9%87%8F%0A%20%20%20%20backtrack%28choices%2C%20state%2C%20n%2C%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md new file mode 100644 index 0000000000..6fb172c6c4 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%B6%E7%B4%84%E6%9D%9F%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%EF%BC%9A%E9%A0%90%E8%A8%AD%E6%9C%80%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%5B1%5D%2C%20dp%5B1%5D%5B2%5D%20%3D%201%2C%200%0A%20%20%20%20dp%5B2%5D%5B1%5D%2C%20dp%5B2%5D%5B2%5D%20%3D%200%2C%201%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%BE%9E%E8%BC%83%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BC%83%E5%A4%A7%E5%AD%90%E5%95%8F%E9%A1%8C%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md new file mode 100644 index 0000000000..34d5f0c8e7 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md new file mode 100644 index 0000000000..b05da19290 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%2C%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20%E8%8B%A5%E5%AD%98%E5%9C%A8%E8%A8%98%E9%8C%84%20dp%5Bi%5D%20%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20mem%5Bi%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%2C%20mem%29%20%2B%20dfs%28i%20-%202%2C%20mem%29%0A%20%20%20%20%23%20%E8%A8%98%E9%8C%84%20dp%5Bi%5D%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%E8%A8%98%E9%8C%84%E7%88%AC%E5%88%B0%E7%AC%AC%20i%20%E9%9A%8E%E7%9A%84%E6%96%B9%E6%A1%88%E7%B8%BD%E6%95%B8%EF%BC%8C-1%20%E4%BB%A3%E8%A1%A8%E7%84%A1%E8%A8%98%E9%8C%84%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n%2C%20mem%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md new file mode 100644 index 0000000000..f479a284ed --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%EF%BC%9A%E9%A0%90%E8%A8%AD%E6%9C%80%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%201%2C%202%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%BE%9E%E8%BC%83%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BC%83%E5%A4%A7%E5%AD%90%E5%95%8F%E9%A1%8C%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a%2C%20b%20%3D%201%2C%202%0A%20%20%20%20for%20_%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%9A%8E%E6%A8%93%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A8%AE%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change.md new file mode 100644 index 0000000000..d45fc36eae --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D%2C%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D%2C%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md new file mode 100644 index 0000000000..5affb6e56b --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%20II%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%87%BA%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E7%9A%84%E7%A1%AC%E5%B9%A3%E7%B5%84%E5%90%88%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%20II%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%A1%AC%E5%B9%A3%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%87%BA%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E7%9A%84%E7%A1%AC%E5%B9%A3%E7%B5%84%E5%90%88%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/edit_distance.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/edit_distance.md new file mode 100644 index 0000000000..16e7d37683 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/edit_distance.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E8%BC%AF%E8%B7%9D%E9%9B%A2%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%85%A9%E5%AD%97%E5%85%83%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%B7%B3%E9%81%8E%E6%AD%A4%E5%85%A9%E5%AD%97%E5%85%83%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%AA%E9%99%A4%E3%80%81%E6%9B%BF%E6%8F%9B%E9%80%99%E4%B8%89%E7%A8%AE%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%E5%B0%87%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E7%82%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%B7%A8%E8%BC%AF%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E8%BC%AF%E8%B7%9D%E9%9B%A2%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%E6%9A%AB%E5%AD%98%20dp%5Bi-1%2C%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%85%A9%E5%AD%97%E5%85%83%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%B7%B3%E9%81%8E%E6%AD%A4%E5%85%A9%E5%AD%97%E5%85%83%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%AA%E9%99%A4%E3%80%81%E6%9B%BF%E6%8F%9B%E9%80%99%E4%B8%89%E7%A8%AE%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%B7%A8%E8%BC%AF%E6%AD%A5%E6%95%B8%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%2C%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%9B%B4%E6%96%B0%E7%82%BA%E4%B8%8B%E4%B8%80%E8%BC%AA%E7%9A%84%20dp%5Bi-1%2C%20j-1%5D%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%E5%B0%87%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E7%82%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%B7%A8%E8%BC%AF%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/knapsack.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/knapsack.md new file mode 100644 index 0000000000..9bd8f6f1a7 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/knapsack.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20i%3A%20int%2C%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%81%B8%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E7%84%A1%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%E5%83%B9%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E5%8F%AA%E8%83%BD%E9%81%B8%E6%93%87%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E5%83%B9%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B8%AD%E5%83%B9%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E5%80%8B%0A%20%20%20%20return%20max%28no%2C%20yes%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%81%B8%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E7%84%A1%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%E5%83%B9%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%A8%98%E9%8C%84%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E5%8F%AA%E8%83%BD%E9%81%B8%E6%93%87%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E5%83%B9%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E4%B8%A6%E8%BF%94%E5%9B%9E%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E4%B8%AD%E5%83%B9%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E5%80%8B%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no%2C%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md new file mode 100644 index 0000000000..56bceed713 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E5%83%B9%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8B%80%E6%85%8B%EF%BC%9A%E9%A0%90%E8%A8%AD%E6%9C%80%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%BE%9E%E8%BC%83%E5%B0%8F%E5%AD%90%E5%95%8F%E9%A1%8C%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BC%83%E5%A4%A7%E5%AD%90%E5%95%8F%E9%A1%8C%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D%2C%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E6%A8%93%E6%A2%AF%E7%9A%84%E4%BB%A3%E5%83%B9%E4%B8%B2%E5%88%97%E7%82%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A8%93%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E5%83%B9%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A8%93%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E5%83%B9%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a%2C%20b%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20min%28a%2C%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E8%BC%B8%E5%85%A5%E6%A8%93%E6%A2%AF%E7%9A%84%E4%BB%A3%E5%83%B9%E4%B8%B2%E5%88%97%E7%82%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A8%93%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E5%83%B9%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md new file mode 100644 index 0000000000..c34e3f15cd --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E7%82%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%96%AE%E5%85%83%E6%A0%BC%EF%BC%8C%E5%89%87%E7%B5%82%E6%AD%A2%E6%90%9C%E5%B0%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E5%83%B9%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i-1%2C%20j%29%20%E5%92%8C%20%28i%2C%20j-1%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i%2C%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20return%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E5%B0%8B%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E7%82%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%96%AE%E5%85%83%E6%A0%BC%EF%BC%8C%E5%89%87%E7%B5%82%E6%AD%A2%E6%90%9C%E5%B0%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E5%83%B9%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%A8%98%E9%8C%84%EF%BC%8C%E5%89%87%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%E5%B7%A6%E9%82%8A%E5%92%8C%E4%B8%8A%E9%82%8A%E5%96%AE%E5%85%83%E6%A0%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E8%A8%98%E9%8C%84%E4%B8%A6%E8%BF%94%E5%9B%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i%2C%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E4%BB%A3%E5%83%B9%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%E8%A8%98%E6%86%B6%E5%8C%96%E6%90%9C%E5%B0%8B%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20m%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%EF%BC%9A%E5%85%B6%E9%A4%98%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%E5%BE%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%91%E5%92%8C%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md new file mode 100644 index 0000000000..deac7b6e4f --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8B%80%E6%85%8B%E8%BD%89%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%89%87%E4%B8%8D%E9%81%B8%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%81%B8%E5%92%8C%E9%81%B8%E7%89%A9%E5%93%81%20i%20%E9%80%99%E5%85%A9%E7%A8%AE%E6%96%B9%E6%A1%88%E7%9A%84%E8%BC%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E4%BD%B3%E5%8C%96%E5%BE%8C%E7%9A%84%E5%8B%95%E6%85%8B%E8%A6%8F%E5%8A%83%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_list.md b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_list.md new file mode 100644 index 0000000000..d77e8a1ab8 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BC%B8%E5%85%A5%E5%80%BC%E4%B8%B2%E5%88%97%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E8%A1%A8%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%89%80%E6%9C%89%E9%A0%82%E9%BB%9E%E5%92%8C%E9%82%8A%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E9%A0%82%E9%BB%9E%E6%95%B8%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%82%8A%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E8%A1%A8%E4%B8%AD%E6%96%B0%E5%A2%9E%E4%B8%80%E5%80%8B%E6%96%B0%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E8%A1%A8%E4%B8%AD%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%20vet%20%E5%B0%8D%E6%87%89%E7%9A%84%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E5%85%B6%E4%BB%96%E9%A0%82%E9%BB%9E%E7%9A%84%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%EF%BC%8C%E5%88%AA%E9%99%A4%E6%89%80%E6%9C%89%E5%8C%85%E5%90%AB%20vet%20%E7%9A%84%E9%82%8A%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1%2C%203%2C%202%2C%205%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%0A%20%20%20%20graph.add_edge%28v%5B0%5D%2C%20v%5B2%5D%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%82%8A%0A%20%20%20%20graph.remove_edge%28v%5B0%5D%2C%20v%5B1%5D%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md new file mode 100644 index 0000000000..f3cc3f979a --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20vertices%3A%20list%5Bint%5D%2C%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D%2C%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E9%A0%82%E9%BB%9E%E6%95%B8%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%E4%B8%AD%E6%96%B0%E5%A2%9E%E6%96%B0%E9%A0%82%E9%BB%9E%E7%9A%84%E5%80%BC%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E6%96%B0%E5%A2%9E%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E6%96%B0%E5%A2%9E%E4%B8%80%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%E4%B8%AD%E7%A7%BB%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E8%A1%8C%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%84%B0%E6%8E%A5%E7%9F%A9%E9%99%A3%E4%B8%AD%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E8%88%87%E7%9B%B8%E7%AD%89%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E8%88%87%E7%9B%B8%E7%AD%89%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20vertices%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0%2C%201%5D%2C%20%5B0%2C%203%5D%2C%20%5B1%2C%202%5D%2C%20%5B2%2C%203%5D%2C%20%5B2%2C%204%5D%2C%20%5B3%2C%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices%2C%20edges%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%82%8A%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%201%2C%202%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%A5%E7%82%BA%200%2C%202%0A%20%20%20%20graph.add_edge%280%2C%202%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%82%8A%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%201%2C%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%A5%E7%82%BA%200%2C%201%0A%20%20%20%20graph.remove_edge%280%2C%201%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%0A%20%20%20%20graph.add_vertex%286%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%A0%82%E9%BB%9E%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%201%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_bfs.md b/zh-hant/codes/pythontutor/chapter_graph/graph_bfs.md new file mode 100644 index 0000000000..264af71a96 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BC%B8%E5%85%A5%E5%80%BC%E4%B8%B2%E5%88%97%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E8%A1%A8%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E5%BB%A3%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E8%A8%98%E9%8C%84%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E9%81%8E%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E4%BD%87%E5%88%97%E7%94%A8%E6%96%BC%E5%AF%A6%E7%8F%BE%20BFS%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E4%BB%A5%E9%A0%82%E9%BB%9E%20vet%20%E7%82%BA%E8%B5%B7%E9%BB%9E%EF%BC%8C%E8%BF%B4%E5%9C%88%E7%9B%B4%E8%87%B3%E8%A8%AA%E5%95%8F%E5%AE%8C%E6%89%80%E6%9C%89%E9%A0%82%E9%BB%9E%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E9%A0%82%E9%BB%9E%E5%87%BA%E9%9A%8A%0A%20%20%20%20%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%98%E9%8C%84%E8%A8%AA%E5%95%8F%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%A9%B2%E9%A0%82%E9%BB%9E%E7%9A%84%E6%89%80%E6%9C%89%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E9%81%8E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%20%20%23%20%E5%8F%AA%E5%85%A5%E5%88%97%E6%9C%AA%E8%A8%AA%E5%95%8F%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%20%20%23%20%E6%A8%99%E8%A8%98%E8%A9%B2%E9%A0%82%E9%BB%9E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E5%BB%A3%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20graph_bfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_dfs.md b/zh-hant/codes/pythontutor/chapter_graph/graph_dfs.md new file mode 100644 index 0000000000..726199cd87 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BC%B8%E5%85%A5%E5%80%BC%E4%B8%B2%E5%88%97%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A0%82%E9%BB%9E%E4%B8%B2%E5%88%97%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%84%B0%E6%8E%A5%E8%A1%A8%E5%AF%A6%E7%8F%BE%E7%9A%84%E7%84%A1%E5%90%91%E5%9C%96%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%82%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E9%A0%82%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20dfs%28graph%3A%20GraphAdjList%2C%20visited%3A%20set%5BVertex%5D%2C%20res%3A%20list%5BVertex%5D%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%E8%BC%94%E5%8A%A9%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%98%E9%8C%84%E8%A8%AA%E5%95%8F%E9%A0%82%E9%BB%9E%0A%20%20%20%20visited.add%28vet%29%20%20%23%20%E6%A8%99%E8%A8%98%E8%A9%B2%E9%A0%82%E9%BB%9E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E8%A9%B2%E9%A0%82%E9%BB%9E%E7%9A%84%E6%89%80%E6%9C%89%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E9%81%8E%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E8%A8%AA%E5%95%8F%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20adjVet%29%0A%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E9%84%B0%E6%8E%A5%E8%A1%A8%E4%BE%86%E8%A1%A8%E7%A4%BA%E5%9C%96%EF%BC%8C%E4%BB%A5%E4%BE%BF%E7%8D%B2%E5%8F%96%E6%8C%87%E5%AE%9A%E9%A0%82%E9%BB%9E%E7%9A%84%E6%89%80%E6%9C%89%E9%84%B0%E6%8E%A5%E9%A0%82%E9%BB%9E%0A%20%20%20%20%23%20%E9%A0%82%E9%BB%9E%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E7%94%A8%E6%96%BC%E8%A8%98%E9%8C%84%E5%B7%B2%E8%A2%AB%E8%A8%AA%E5%95%8F%E9%81%8E%E7%9A%84%E9%A0%82%E9%BB%9E%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20start_vet%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%84%A1%E5%90%91%E5%9C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%0A%20%20%20%20%23%20%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20graph_dfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/coin_change_greedy.md b/zh-hant/codes/pythontutor/chapter_greedy/coin_change_greedy.md new file mode 100644 index 0000000000..a066eaf1e1 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/coin_change_greedy.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%8C%A2%E5%85%8C%E6%8F%9B%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E5%81%87%E8%A8%AD%20coins%20%E4%B8%B2%E5%88%97%E6%9C%89%E5%BA%8F%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E9%80%B2%E8%A1%8C%E8%B2%AA%E5%A9%AA%E9%81%B8%E6%93%87%EF%BC%8C%E7%9B%B4%E5%88%B0%E7%84%A1%E5%89%A9%E9%A4%98%E9%87%91%E9%A1%8D%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E5%B0%8F%E6%96%BC%E4%B8%94%E6%9C%80%E6%8E%A5%E8%BF%91%E5%89%A9%E9%A4%98%E9%87%91%E9%A1%8D%E7%9A%84%E7%A1%AC%E5%B9%A3%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E9%81%B8%E6%93%87%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%E8%8B%A5%E6%9C%AA%E6%89%BE%E5%88%B0%E5%8F%AF%E8%A1%8C%E6%96%B9%E6%A1%88%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%EF%BC%9A%E8%83%BD%E5%A4%A0%E4%BF%9D%E8%AD%89%E6%89%BE%E5%88%B0%E5%85%A8%E5%9F%9F%E6%80%A7%E6%9C%80%E5%84%AA%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%EF%BC%9A%E7%84%A1%E6%B3%95%E4%BF%9D%E8%AD%89%E6%89%BE%E5%88%B0%E5%85%A8%E5%9F%9F%E6%80%A7%E6%9C%80%E5%84%AA%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E6%B9%8A%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B9%A3%E6%95%B8%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E5%AF%A6%E9%9A%9B%E4%B8%8A%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%95%B8%E9%87%8F%E7%82%BA%203%20%EF%BC%8C%E5%8D%B3%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/fractional_knapsack.md b/zh-hant/codes/pythontutor/chapter_greedy/fractional_knapsack.md new file mode 100644 index 0000000000..2aa74e8900 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/fractional_knapsack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%E7%89%A9%E5%93%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%E7%89%A9%E5%93%81%E9%87%8D%E9%87%8F%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%86%E6%95%B8%E8%83%8C%E5%8C%85%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E7%AB%8B%E7%89%A9%E5%93%81%E4%B8%B2%E5%88%97%EF%BC%8C%E5%8C%85%E5%90%AB%E5%85%A9%E5%80%8B%E5%B1%AC%E6%80%A7%EF%BC%9A%E9%87%8D%E9%87%8F%E3%80%81%E5%83%B9%E5%80%BC%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E5%96%AE%E4%BD%8D%E5%83%B9%E5%80%BC%20item.v%20/%20item.w%20%E5%BE%9E%E9%AB%98%E5%88%B0%E4%BD%8E%E9%80%B2%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20/%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E8%B2%AA%E5%A9%AA%E9%81%B8%E6%93%87%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%E5%85%85%E8%B6%B3%EF%BC%8C%E5%89%87%E5%B0%87%E7%95%B6%E5%89%8D%E7%89%A9%E5%93%81%E6%95%B4%E5%80%8B%E8%A3%9D%E9%80%B2%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%E4%B8%8D%E8%B6%B3%EF%BC%8C%E5%89%87%E5%B0%87%E7%95%B6%E5%89%8D%E7%89%A9%E5%93%81%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86%E8%A3%9D%E9%80%B2%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20/%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B7%B2%E7%84%A1%E5%89%A9%E9%A4%98%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%9B%A0%E6%AD%A4%E8%B7%B3%E5%87%BA%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E9%81%8E%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E5%83%B9%E5%80%BC%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/max_capacity.md b/zh-hant/codes/pythontutor/chapter_greedy/max_capacity.md new file mode 100644 index 0000000000..4b24dc3b5b --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/max_capacity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20i%2C%20j%EF%BC%8C%E4%BD%BF%E5%85%B6%E5%88%86%E5%88%97%E9%99%A3%E5%88%97%E5%85%A9%E7%AB%AF%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E7%82%BA%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E8%B2%AA%E5%A9%AA%E9%81%B8%E6%93%87%EF%BC%8C%E7%9B%B4%E8%87%B3%E5%85%A9%E6%9D%BF%E7%9B%B8%E9%81%87%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E5%85%A7%E7%A7%BB%E5%8B%95%E7%9F%AD%E6%9D%BF%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/max_product_cutting.md b/zh-hant/codes/pythontutor/chapter_greedy/max_product_cutting.md new file mode 100644 index 0000000000..631cf53125 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/max_product_cutting.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A9%8D%EF%BC%9A%E8%B2%AA%E5%A9%AA%22%22%22%0A%20%20%20%20%23%20%E7%95%B6%20n%20%3C%3D%203%20%E6%99%82%EF%BC%8C%E5%BF%85%E9%A0%88%E5%88%87%E5%88%86%E5%87%BA%E4%B8%80%E5%80%8B%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E5%9C%B0%E5%88%87%E5%88%86%E5%87%BA%203%20%EF%BC%8Ca%20%E7%82%BA%203%20%E7%9A%84%E5%80%8B%E6%95%B8%EF%BC%8Cb%20%E7%82%BA%E9%A4%98%E6%95%B8%0A%20%20%20%20a%2C%20b%20%3D%20n%20//%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E9%A4%98%E6%95%B8%E7%82%BA%201%20%E6%99%82%EF%BC%8C%E5%B0%87%E4%B8%80%E5%B0%8D%201%20%2A%203%20%E8%BD%89%E5%8C%96%E7%82%BA%202%20%2A%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E9%A4%98%E6%95%B8%E7%82%BA%202%20%E6%99%82%EF%BC%8C%E4%B8%8D%E5%81%9A%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%E7%95%B6%E9%A4%98%E6%95%B8%E7%82%BA%200%20%E6%99%82%EF%BC%8C%E4%B8%8D%E5%81%9A%E8%99%95%E7%90%86%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%E8%B2%AA%E5%A9%AA%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A9%8D%E7%82%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_hashing/array_hash_map.md b/zh-hant/codes/pythontutor/chapter_hashing/array_hash_map.md new file mode 100644 index 0000000000..0729d498ae --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_hashing/array_hash_map.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%8D%B5%E5%80%BC%E5%B0%8D%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0A%0Aclass%20ArrayHashMap%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%99%A3%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%EF%BC%8C%E5%8C%85%E5%90%AB%2020%20%E5%80%8B%E6%A1%B6%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20%2A%2020%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%9B%9C%E6%B9%8A%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20%23%20%E7%BD%AE%E7%82%BA%20None%20%EF%BC%8C%E4%BB%A3%E8%A1%A8%E5%88%AA%E9%99%A4%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E6%89%80%E6%9C%89%E9%8D%B5%E5%80%BC%E5%B0%8D%22%22%22%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E6%89%80%E6%9C%89%E9%8D%B5%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E6%89%80%E6%9C%89%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E5%8D%B0%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key%2C%20%22-%3E%22%2C%20pair.val%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.put%2812836%2C%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hmap.put%2815937%2C%20%22%E5%B0%8F%E5%9B%89%22%29%0A%20%20%20%20hmap.put%2816750%2C%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hmap.put%2813276%2C%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hmap.put%2810583%2C%20%22%E5%B0%8F%E9%B4%A8%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.remove%2810583%29%0A%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20print%28%22%5Cn%E8%B5%B0%E8%A8%AA%E9%8D%B5%E5%80%BC%E5%B0%8D%20Key-%3EValue%22%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key%2C%20%22-%3E%22%2C%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_hashing/hash_map_chaining.md b/zh-hant/codes/pythontutor/chapter_hashing/hash_map_chaining.md new file mode 100644 index 0000000000..fd485d4cf0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_hashing/hash_map_chaining.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%8D%B5%E5%80%BC%E5%B0%8D%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E5%BC%8F%E4%BD%8D%E5%9D%80%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20/%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%9B%9C%E6%B9%8A%E5%87%BD%E5%BC%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%B2%A0%E8%BC%89%E5%9B%A0%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%20/%20self.capacity%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%93%B4%E5%AE%B9%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20%2A%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key%2C%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E5%8D%B0%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%22%20-%3E%20%22%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.put%2812836%2C%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hashmap.put%2815937%2C%20%22%E5%B0%8F%E5%9B%89%22%29%0A%20%20%20%20hashmap.put%2816750%2C%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hashmap.put%2813276%2C%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hashmap.put%2810583%2C%20%22%E5%B0%8F%E9%B4%A8%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_hashing/simple_hash.md b/zh-hant/codes/pythontutor/chapter_hashing/simple_hash.md new file mode 100644 index 0000000000..1ac1dc6616 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_hashing/simple_hash.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E6%B3%95%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%98%E6%B3%95%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20%2A%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%92%E6%96%A5%E6%88%96%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%97%8B%E8%BD%89%E9%9B%9C%E6%B9%8A%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E6%BC%94%E7%AE%97%E6%B3%95%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E6%B3%95%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%98%E6%B3%95%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%E4%BA%92%E6%96%A5%E6%88%96%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E6%97%8B%E8%BD%89%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20%7Bhash%7D%22%29%0A&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_heap/my_heap.md b/zh-hant/codes/pythontutor/chapter_heap/my_heap.md new file mode 100644 index 0000000000..2faa1d00e3 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_heap/my_heap.md @@ -0,0 +1,20 @@ + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%EF%BC%8C%E6%A0%B9%E6%93%9A%E8%BC%B8%E5%85%A5%E4%B8%B2%E5%88%97%E5%BB%BA%E5%A0%86%E7%A9%8D%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20%23%20%E5%A0%86%E7%A9%8D%E5%8C%96%E9%99%A4%E8%91%89%E7%AF%80%E9%BB%9E%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E7%AF%80%E9%BB%9E%20i%2C%20l%2C%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%A8%98%E7%82%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20self.left%28i%29%2C%20self.right%28i%29%2C%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%AF%80%E9%BB%9E%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l%2C%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E7%84%A1%E9%A0%88%E7%B9%BC%E7%BA%8C%E5%A0%86%E7%A9%8D%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8B%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1%2C%202%2C%203%2C%204%2C%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%E7%A9%8D%20%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E7%82%BA%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%9E%E5%BA%95%E8%87%B3%E9%A0%82%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E5%BA%95%E8%87%B3%E9%A0%82%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E7%AF%80%E9%BB%9E%20i%20%E7%9A%84%E7%88%B6%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E2%80%9C%E8%B6%8A%E9%81%8E%E6%A0%B9%E7%AF%80%E9%BB%9E%E2%80%9D%E6%88%96%E2%80%9C%E7%AF%80%E9%BB%9E%E7%84%A1%E9%A0%88%E4%BF%AE%E5%BE%A9%E2%80%9D%E6%99%82%EF%BC%8C%E7%B5%90%E6%9D%9F%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8A%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8B%95%E6%96%B0%E5%A2%9E%E9%80%B2%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E7%A9%BA%E8%99%95%E7%90%86%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%A9%8D%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E6%A0%B9%E7%AF%80%E9%BB%9E%E8%88%87%E6%9C%80%E5%8F%B3%E8%91%89%E7%AF%80%E9%BB%9E%EF%BC%88%E4%BA%A4%E6%8F%9B%E9%A6%96%E5%85%83%E7%B4%A0%E8%88%87%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20self.swap%280%2C%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E7%AF%80%E9%BB%9E%20i%2C%20l%2C%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%A8%98%E7%82%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20self.left%28i%29%2C%20self.right%28i%29%2C%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%AF%80%E9%BB%9E%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l%2C%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E7%84%A1%E9%A0%88%E7%B9%BC%E7%BA%8C%E5%A0%86%E7%A9%8D%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8B%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%AB%8B%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BC%B8%E5%85%A5%E9%99%A3%E5%88%97%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%B7%B2%E7%B6%93%E6%98%AF%E4%B8%80%E5%80%8B%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%207%2C%206%2C%207%2C%206%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%2C%205%5D%29%0A%0A%20%20%20%20%23%20%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_heap/top_k.md b/zh-hant/codes/pythontutor/chapter_heap/top_k.md new file mode 100644 index 0000000000..7cb86518f9 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_heap/top_k.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E5%A0%86%E7%A9%8D%E6%9F%A5%E8%A9%A2%E9%99%A3%E5%88%97%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%20k%20%E5%80%8B%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%B0%87%E9%99%A3%E5%88%97%E7%9A%84%E5%89%8D%20k%20%E5%80%8B%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%E5%BE%9E%E7%AC%AC%20k%2B1%20%E5%80%8B%E5%85%83%E7%B4%A0%E9%96%8B%E5%A7%8B%EF%BC%8C%E4%BF%9D%E6%8C%81%E5%A0%86%E7%A9%8D%E7%9A%84%E9%95%B7%E5%BA%A6%E7%82%BA%20k%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E5%A4%A7%E6%96%BC%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%EF%BC%8C%E5%89%87%E5%B0%87%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%E3%80%81%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/binary_search.md b/zh-hant/codes/pythontutor/chapter_searching/binary_search.md new file mode 100644 index 0000000000..0c283dd426 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/binary_search.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%20%EF%BC%8C%E5%8D%B3%20i%2C%20j%20%E5%88%86%E5%88%A5%E6%8C%87%E5%90%91%E9%99%A3%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%EF%BC%8C%E7%95%B6%E6%90%9C%E5%B0%8B%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%E6%99%82%E8%B7%B3%E5%87%BA%EF%BC%88%E7%95%B6%20i%20%3E%20j%20%E6%99%82%E7%82%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%90%86%E8%AB%96%E4%B8%8A%20Python%20%E7%9A%84%E6%95%B8%E5%AD%97%E5%8F%AF%E4%BB%A5%E7%84%A1%E9%99%90%E5%A4%A7%EF%BC%88%E5%8F%96%E6%B1%BA%E6%96%BC%E8%A8%98%E6%86%B6%E9%AB%94%E5%A4%A7%E5%B0%8F%EF%BC%89%EF%BC%8C%E7%84%A1%E9%A0%88%E8%80%83%E6%85%AE%E5%A4%A7%E6%95%B8%E8%B6%8A%E7%95%8C%E5%95%8F%E9%A1%8C%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8D%80%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8D%80%E9%96%93%20%5B0%2C%20n%29%20%EF%BC%8C%E5%8D%B3%20i%2C%20j%20%E5%88%86%E5%88%A5%E6%8C%87%E5%90%91%E9%99%A3%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%2B1%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%0A%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%EF%BC%8C%E7%95%B6%E6%90%9C%E5%B0%8B%E5%8D%80%E9%96%93%E7%82%BA%E7%A9%BA%E6%99%82%E8%B7%B3%E5%87%BA%EF%BC%88%E7%95%B6%20i%20%3D%20j%20%E6%99%82%E7%82%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%E6%AD%A4%E6%83%85%E6%B3%81%E8%AA%AA%E6%98%8E%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8D%80%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A8%99%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/binary_search_edge.md b/zh-hant/codes/pythontutor/chapter_searching/binary_search_edge.md new file mode 100644 index 0000000000..bc12840afd --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/binary_search_edge.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%9C%80%E5%B7%A6%E4%B8%80%E5%80%8B%20target%22%22%22%0A%20%20%20%20%23%20%E7%AD%89%E5%83%B9%E6%96%BC%E6%9F%A5%E8%A9%A2%20target%20%E7%9A%84%E6%8F%92%E5%85%A5%E9%BB%9E%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20i%0A%20%20%20%20return%20i%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E5%B7%A6%E9%82%8A%E7%95%8C%E5%92%8C%E5%8F%B3%E9%82%8A%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%B7%A6%E4%B8%80%E5%80%8B%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%9C%80%E5%8F%B3%E4%B8%80%E5%80%8B%20target%22%22%22%0A%20%20%20%20%23%20%E8%BD%89%E5%8C%96%E7%82%BA%E6%9F%A5%E8%A9%A2%E6%9C%80%E5%B7%A6%E4%B8%80%E5%80%8B%20target%20%2B%201%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%E6%8C%87%E5%90%91%E6%9C%80%E5%8F%B3%E4%B8%80%E5%80%8B%20target%20%EF%BC%8Ci%20%E6%8C%87%E5%90%91%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20return%20j%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E5%B7%A6%E9%82%8A%E7%95%8C%E5%92%8C%E5%8F%B3%E9%82%8A%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%8F%B3%E4%B8%80%E5%80%8B%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/binary_search_insertion.md b/zh-hant/codes/pythontutor/chapter_searching/binary_search_insertion.md new file mode 100644 index 0000000000..5785420b83 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/binary_search_insertion.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E7%84%A1%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20m%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E7%84%A1%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E9%96%89%E5%8D%80%E9%96%93%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8D%80%E9%96%93%20%5Bi%2C%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E9%BB%9E%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E8%A4%87%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%8F%92%E5%85%A5%E9%BB%9E%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%E7%82%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/two_sum.md b/zh-hant/codes/pythontutor/chapter_searching/two_sum.md new file mode 100644 index 0000000000..ae5615ac38 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/two_sum.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%B8%80%EF%BC%9A%E6%9A%B4%E5%8A%9B%E5%88%97%E8%88%89%22%22%22%0A%20%20%20%20%23%20%E5%85%A9%E5%B1%A4%E8%BF%B4%E5%9C%88%EF%BC%8C%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E7%82%BA%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi%2C%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E8%BC%94%E5%8A%A9%E9%9B%9C%E6%B9%8A%E8%A1%A8%22%22%22%0A%20%20%20%20%23%20%E8%BC%94%E5%8A%A9%E9%9B%9C%E6%B9%8A%E8%A1%A8%EF%BC%8C%E7%A9%BA%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E7%82%BA%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%E5%96%AE%E5%B1%A4%E8%BF%B4%E5%9C%88%EF%BC%8C%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E7%82%BA%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D%2C%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/bubble_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/bubble_sort.md new file mode 100644 index 0000000000..c2f82ecccf --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/bubble_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E8%87%B3%E8%A9%B2%E5%8D%80%E9%96%93%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%20nums%5Bj%5D%20%E8%88%87%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A8%99%E8%AA%8C%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A8%99%E8%AA%8C%E4%BD%8D%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%E8%87%B3%E8%A9%B2%E5%8D%80%E9%96%93%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%20nums%5Bj%5D%20%E8%88%87%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E8%A8%98%E9%8C%84%E4%BA%A4%E6%8F%9B%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E6%AD%A4%E8%BC%AA%E2%80%9C%E5%86%92%E6%B3%A1%E2%80%9D%E6%9C%AA%E4%BA%A4%E6%8F%9B%E4%BB%BB%E4%BD%95%E5%85%83%E7%B4%A0%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%87%BA%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E6%B3%A1%E6%B2%AB%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/bucket_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/bucket_sort.md new file mode 100644 index 0000000000..ed458e2951 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/bucket_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E6%A1%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20k%20%3D%20n/2%20%E5%80%8B%E6%A1%B6%EF%BC%8C%E9%A0%90%E6%9C%9F%E5%90%91%E6%AF%8F%E5%80%8B%E6%A1%B6%E5%88%86%E9%85%8D%202%20%E5%80%8B%E5%85%83%E7%B4%A0%0A%20%20%20%20k%20%3D%20len%28nums%29%20//%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E5%B0%87%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%E5%88%86%E9%85%8D%E5%88%B0%E5%90%84%E5%80%8B%E6%A1%B6%E4%B8%AD%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E7%AF%84%E5%9C%8D%E7%82%BA%20%5B0%2C%201%29%EF%BC%8C%E4%BD%BF%E7%94%A8%20num%20%2A%20k%20%E5%B0%8D%E6%98%A0%E5%88%B0%E7%B4%A2%E5%BC%95%E7%AF%84%E5%9C%8D%20%5B0%2C%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%20num%20%E6%96%B0%E5%A2%9E%E9%80%B2%E6%A1%B6%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%B0%8D%E5%90%84%E5%80%8B%E6%A1%B6%E5%9F%B7%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%85%A7%E5%BB%BA%E6%8E%92%E5%BA%8F%E5%87%BD%E5%BC%8F%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9B%BF%E6%8F%9B%E6%88%90%E5%85%B6%E4%BB%96%E6%8E%92%E5%BA%8F%E6%BC%94%E7%AE%97%E6%B3%95%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E8%B5%B0%E8%A8%AA%E6%A1%B6%E5%90%88%E4%BD%B5%E7%B5%90%E6%9E%9C%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%A8%AD%E8%BC%B8%E5%85%A5%E8%B3%87%E6%96%99%E7%82%BA%E6%B5%AE%E9%BB%9E%E6%95%B8%EF%BC%8C%E7%AF%84%E5%9C%8D%E7%82%BA%20%5B0%2C%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E6%A1%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/counting_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/counting_sort.md new file mode 100644 index 0000000000..7a2d651957 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/counting_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%B0%A1%E5%96%AE%E5%AF%A6%E7%8F%BE%EF%BC%8C%E7%84%A1%E6%B3%95%E7%94%A8%E6%96%BC%E6%8E%92%E5%BA%8F%E7%89%A9%E4%BB%B6%0A%20%20%20%20%23%201.%20%E7%B5%B1%E8%A8%88%E9%99%A3%E5%88%97%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m%2C%20num%29%0A%20%20%20%20%23%202.%20%E7%B5%B1%E8%A8%88%E5%90%84%E6%95%B8%E5%AD%97%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E8%B5%B0%E8%A8%AA%20counter%20%EF%BC%8C%E5%B0%87%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E5%8E%9F%E9%99%A3%E5%88%97%20nums%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%EF%BC%88%E7%84%A1%E6%B3%95%E6%8E%92%E5%BA%8F%E7%89%A9%E4%BB%B6%EF%BC%89%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E6%95%B4%E5%AF%A6%E7%8F%BE%EF%BC%8C%E5%8F%AF%E6%8E%92%E5%BA%8F%E7%89%A9%E4%BB%B6%EF%BC%8C%E4%B8%A6%E4%B8%94%E6%98%AF%E7%A9%A9%E5%AE%9A%E6%8E%92%E5%BA%8F%0A%20%20%20%20%23%201.%20%E7%B5%B1%E8%A8%88%E9%99%A3%E5%88%97%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E7%B5%B1%E8%A8%88%E5%90%84%E6%95%B8%E5%AD%97%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E6%B1%82%20counter%20%E7%9A%84%E5%89%8D%E7%B6%B4%E5%92%8C%EF%BC%8C%E5%B0%87%E2%80%9C%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%E2%80%9D%E8%BD%89%E6%8F%9B%E7%82%BA%E2%80%9C%E5%B0%BE%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20%23%20%E5%8D%B3%20counter%5Bnum%5D-1%20%E6%98%AF%20num%20%E5%9C%A8%20res%20%E4%B8%AD%E6%9C%80%E5%BE%8C%E4%B8%80%E6%AC%A1%E5%87%BA%E7%8F%BE%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%E5%80%92%E5%BA%8F%E8%B5%B0%E8%A8%AA%20nums%20%EF%BC%8C%E5%B0%87%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B5%90%E6%9E%9C%E9%99%A3%E5%88%97%20res%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%20res%20%E7%94%A8%E6%96%BC%E8%A8%98%E9%8C%84%E7%B5%90%E6%9E%9C%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%E5%B0%87%20num%20%E6%94%BE%E7%BD%AE%E5%88%B0%E5%B0%8D%E6%87%89%E7%B4%A2%E5%BC%95%E8%99%95%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E4%BB%A4%E5%89%8D%E7%B6%B4%E5%92%8C%E8%87%AA%E6%B8%9B%201%20%EF%BC%8C%E5%BE%97%E5%88%B0%E4%B8%8B%E6%AC%A1%E6%94%BE%E7%BD%AE%20num%20%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%B5%90%E6%9E%9C%E9%99%A3%E5%88%97%20res%20%E8%A6%86%E8%93%8B%E5%8E%9F%E9%99%A3%E5%88%97%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/heap_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/heap_sort.md new file mode 100644 index 0000000000..99f2556441 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/heap_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%A9%8D%E7%9A%84%E9%95%B7%E5%BA%A6%E7%82%BA%20n%20%EF%BC%8C%E5%BE%9E%E7%AF%80%E9%BB%9E%20i%20%E9%96%8B%E5%A7%8B%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E5%A0%86%E7%A9%8D%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E7%AF%80%E9%BB%9E%20i%2C%20l%2C%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%A8%98%E7%82%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E7%AF%80%E9%BB%9E%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l%2C%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%89%87%E7%84%A1%E9%A0%88%E7%B9%BC%E7%BA%8C%E5%A0%86%E7%A9%8D%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E5%85%A9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E5%90%91%E4%B8%8B%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%A9%8D%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E5%A0%86%E7%A9%8D%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A0%86%E7%A9%8D%E5%8C%96%E9%99%A4%E8%91%89%E7%AF%80%E9%BB%9E%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E7%AF%80%E9%BB%9E%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20//%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%E5%BE%9E%E5%A0%86%E7%A9%8D%E4%B8%AD%E6%8F%90%E5%8F%96%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%B4%E5%9C%88%20n-1%20%E8%BC%AA%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8F%9B%E6%A0%B9%E7%AF%80%E9%BB%9E%E8%88%87%E6%9C%80%E5%8F%B3%E8%91%89%E7%AF%80%E9%BB%9E%EF%BC%88%E4%BA%A4%E6%8F%9B%E9%A6%96%E5%85%83%E7%B4%A0%E8%88%87%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%A5%E6%A0%B9%E7%AF%80%E9%BB%9E%E7%82%BA%E8%B5%B7%E9%BB%9E%EF%BC%8C%E5%BE%9E%E9%A0%82%E8%87%B3%E5%BA%95%E9%80%B2%E8%A1%8C%E5%A0%86%E7%A9%8D%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%A9%8D%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/insertion_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/insertion_sort.md new file mode 100644 index 0000000000..1324586663 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/insertion_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E5%B0%87%20base%20%E6%8F%92%E5%85%A5%E5%88%B0%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%20%5B0%2C%20i-1%5D%20%E4%B8%AD%E7%9A%84%E6%AD%A3%E7%A2%BA%E4%BD%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%E5%B0%87%20nums%5Bj%5D%20%E5%90%91%E5%8F%B3%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%E5%B0%87%20base%20%E8%B3%A6%E5%80%BC%E5%88%B0%E6%AD%A3%E7%A2%BA%E4%BD%8D%E7%BD%AE%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/merge_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/merge_sort.md new file mode 100644 index 0000000000..e25994ddc6 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/merge_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E4%BD%B5%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%92%8C%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%8D%80%E9%96%93%E7%82%BA%20%5Bleft%2C%20mid%5D%2C%20%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E5%8D%80%E9%96%93%E7%82%BA%20%5Bmid%2B1%2C%20right%5D%0A%20%20%20%20%23%20%E5%BB%BA%E7%AB%8B%E4%B8%80%E5%80%8B%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%20tmp%20%EF%BC%8C%E7%94%A8%E6%96%BC%E5%AD%98%E6%94%BE%E5%90%88%E4%BD%B5%E5%BE%8C%E7%9A%84%E7%B5%90%E6%9E%9C%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%92%8C%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E8%B5%B7%E5%A7%8B%E7%B4%A2%E5%BC%95%0A%20%20%20%20i%2C%20j%2C%20k%20%3D%20left%2C%20mid%20%2B%201%2C%200%0A%20%20%20%20%23%20%E7%95%B6%E5%B7%A6%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E9%83%BD%E9%82%84%E6%9C%89%E5%85%83%E7%B4%A0%E6%99%82%EF%BC%8C%E9%80%B2%E8%A1%8C%E6%AF%94%E8%BC%83%E4%B8%A6%E5%B0%87%E8%BC%83%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%88%B0%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%87%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E5%92%8C%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%89%A9%E9%A4%98%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%88%B0%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%87%E8%87%A8%E6%99%82%E9%99%A3%E5%88%97%20tmp%20%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E8%A4%87%E8%A3%BD%E5%9B%9E%E5%8E%9F%E9%99%A3%E5%88%97%20nums%20%E7%9A%84%E5%B0%8D%E6%87%89%E5%8D%80%E9%96%93%0A%20%20%20%20for%20k%20in%20range%280%2C%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E4%BD%B5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E6%AD%A2%E6%A2%9D%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E7%95%B6%E5%AD%90%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E6%99%82%E7%B5%82%E6%AD%A2%E9%81%9E%E8%BF%B4%0A%20%20%20%20%23%20%E5%8A%83%E5%88%86%E9%9A%8E%E6%AE%B5%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20//%202%20%20%23%20%E8%A8%88%E7%AE%97%E4%B8%AD%E9%BB%9E%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%20%20%23%20%E9%81%9E%E8%BF%B4%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%20%20%23%20%E9%81%9E%E8%BF%B4%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20%23%20%E5%90%88%E4%BD%B5%E9%9A%8E%E6%AE%B5%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%90%88%E4%BD%B5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/quick_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/quick_sort.md new file mode 100644 index 0000000000..287d4c0791 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/quick_sort.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E6%99%82%E7%B5%82%E6%AD%A2%E9%81%9E%E8%BF%B4%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%0A%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%E3%80%81%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%81%B8%E5%8F%96%E4%B8%89%E5%80%8B%E5%80%99%E9%81%B8%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B8%22%22%22%0A%20%20%20%20l%2C%20m%2C%20r%20%3D%20nums%5Bleft%5D%2C%20nums%5Bmid%5D%2C%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%E5%9C%A8%20l%20%E5%92%8C%20r%20%E4%B9%8B%E9%96%93%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%E5%9C%A8%20m%20%E5%92%8C%20r%20%E4%B9%8B%E9%96%93%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%EF%BC%88%E4%B8%89%E6%95%B8%E5%8F%96%E4%B8%AD%E5%80%BC%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20med%20%3D%20median_three%28nums%2C%20left%2C%20%28left%20%2B%20right%29%20//%202%2C%20right%29%0A%20%20%20%20%23%20%E5%B0%87%E4%B8%AD%E4%BD%8D%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E9%99%A3%E5%88%97%E6%9C%80%E5%B7%A6%E7%AB%AF%0A%20%20%20%20nums%5Bleft%5D%2C%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D%2C%20nums%5Bleft%5D%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%AD%E4%BD%8D%E5%9F%BA%E6%BA%96%E6%95%B8%E6%9C%80%E4%BD%B3%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%EF%BC%88%E4%B8%AD%E4%BD%8D%E5%9F%BA%E6%BA%96%E6%95%B8%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E7%82%BA%E5%9F%BA%E6%BA%96%E6%95%B8%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%BE%9E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E5%80%8B%E5%B0%8F%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%BE%9E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E5%80%8B%E5%A4%A7%E6%96%BC%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%87%E5%9F%BA%E6%BA%96%E6%95%B8%E4%BA%A4%E6%8F%9B%E8%87%B3%E5%85%A9%E5%AD%90%E9%99%A3%E5%88%97%E7%9A%84%E5%88%86%E7%95%8C%E7%B7%9A%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E6%BA%96%E6%95%B8%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%81%9E%E8%BF%B4%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E6%99%82%E7%B5%82%E6%AD%A2%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%8A%83%E5%88%86%E6%93%8D%E4%BD%9C%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%8D%E5%85%A9%E5%80%8B%E5%AD%90%E9%99%A3%E5%88%97%E4%B8%AD%E8%BC%83%E7%9F%AD%E7%9A%84%E9%82%A3%E5%80%8B%E5%9F%B7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%20%20%23%20%E9%81%9E%E8%BF%B4%E6%8E%92%E5%BA%8F%E5%B7%A6%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%20%20%23%20%E5%89%A9%E9%A4%98%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5Bpivot%20%2B%201%2C%20right%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%20%20%23%20%E9%81%9E%E8%BF%B4%E6%8E%92%E5%BA%8F%E5%8F%B3%E5%AD%90%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%20%20%23%20%E5%89%A9%E9%A4%98%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5Bleft%2C%20pivot%20-%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%81%9E%E8%BF%B4%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%81%9E%E8%BF%B4%E6%9C%80%E4%BD%B3%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/radix_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/radix_sort.md new file mode 100644 index 0000000000..ae35a4486f --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/radix_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int%2C%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%85%83%E7%B4%A0%20num%20%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E5%85%B6%E4%B8%AD%20exp%20%3D%2010%5E%28k-1%29%22%22%22%0A%20%20%20%20%23%20%E5%82%B3%E5%85%A5%20exp%20%E8%80%8C%E9%9D%9E%20k%20%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%9C%A8%E6%AD%A4%E9%87%8D%E8%A4%87%E5%9F%B7%E8%A1%8C%E6%98%82%E8%B2%B4%E7%9A%84%E6%AC%A1%E6%96%B9%E8%A8%88%E7%AE%97%0A%20%20%20%20return%20%28num%20//%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D%2C%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%B9%E6%93%9A%20nums%20%E7%AC%AC%20k%20%E4%BD%8D%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%8D%81%E9%80%B2%E4%BD%8D%E5%88%B6%E7%9A%84%E4%BD%8D%E7%AF%84%E5%9C%8D%E7%82%BA%200~9%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E9%9C%80%E8%A6%81%E9%95%B7%E5%BA%A6%E7%82%BA%2010%20%E7%9A%84%E6%A1%B6%E9%99%A3%E5%88%97%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E7%B5%B1%E8%A8%88%200~9%20%E5%90%84%E6%95%B8%E5%AD%97%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%20%20%23%20%E7%8D%B2%E5%8F%96%20nums%5Bi%5D%20%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E8%A8%98%E7%82%BA%20d%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E7%B5%B1%E8%A8%88%E6%95%B8%E5%AD%97%20d%20%E7%9A%84%E5%87%BA%E7%8F%BE%E6%AC%A1%E6%95%B8%0A%20%20%20%20%23%20%E6%B1%82%E5%89%8D%E7%B6%B4%E5%92%8C%EF%BC%8C%E5%B0%87%E2%80%9C%E5%87%BA%E7%8F%BE%E5%80%8B%E6%95%B8%E2%80%9D%E8%BD%89%E6%8F%9B%E7%82%BA%E2%80%9C%E9%99%A3%E5%88%97%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20for%20i%20in%20range%281%2C%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E8%B5%B0%E8%A8%AA%EF%BC%8C%E6%A0%B9%E6%93%9A%E6%A1%B6%E5%85%A7%E7%B5%B1%E8%A8%88%E7%B5%90%E6%9E%9C%EF%BC%8C%E5%B0%87%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%20res%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20%E7%8D%B2%E5%8F%96%20d%20%E5%9C%A8%E9%99%A3%E5%88%97%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E5%B0%87%E7%95%B6%E5%89%8D%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20%E5%B0%87%20d%20%E7%9A%84%E6%95%B8%E9%87%8F%E6%B8%9B%201%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%B5%90%E6%9E%9C%E8%A6%86%E8%93%8B%E5%8E%9F%E9%99%A3%E5%88%97%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B8%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E9%99%A3%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E7%94%A8%E6%96%BC%E5%88%A4%E6%96%B7%E6%9C%80%E5%A4%A7%E4%BD%8D%E6%95%B8%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E5%BE%9E%E4%BD%8E%E4%BD%8D%E5%88%B0%E9%AB%98%E4%BD%8D%E7%9A%84%E9%A0%86%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%8D%E9%99%A3%E5%88%97%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%E5%9F%B7%E8%A1%8C%E8%A8%88%E6%95%B8%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E5%8D%B3%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums%2C%20exp%29%0A%20%20%20%20%20%20%20%20exp%20%2A%3D%2010%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B8%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105%2C%0A%20%20%20%20%20%20%20%20356%2C%0A%20%20%20%20%20%20%20%20428%2C%0A%20%20%20%20%20%20%20%20348%2C%0A%20%20%20%20%20%20%20%20818%2C%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B8%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/selection_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/selection_sort.md new file mode 100644 index 0000000000..7674a911c0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/selection_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%82%BA%20%5Bi%2C%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A7%E8%BF%B4%E5%9C%88%EF%BC%9A%E6%89%BE%E5%88%B0%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E5%85%A7%E7%9A%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E8%A8%98%E9%8C%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%E8%A9%B2%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E8%88%87%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8D%80%E9%96%93%E7%9A%84%E9%A6%96%E5%80%8B%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_queue.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_queue.md new file mode 100644 index 0000000000..34c3cc775e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E7%92%B0%E5%BD%A2%E9%99%A3%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E4%BD%87%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20size%20%20%23%20%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E4%BD%87%E5%88%97%E5%85%83%E7%B4%A0%E7%9A%84%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E6%8C%87%E6%A8%99%EF%BC%8C%E6%8C%87%E5%90%91%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%20%20%23%20%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E4%BD%87%E5%88%97%E5%B7%B2%E6%BB%BF%22%29%0A%20%20%20%20%20%20%20%20%23%20%E8%A8%88%E7%AE%97%E4%BD%87%E5%88%97%E5%B0%BE%E6%8C%87%E6%A8%99%EF%BC%8C%E6%8C%87%E5%90%91%E4%BD%87%E5%88%97%E5%B0%BE%E7%B4%A2%E5%BC%95%20%2B%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E5%8F%96%E9%A4%98%E6%93%8D%E4%BD%9C%E5%AF%A6%E7%8F%BE%20rear%20%E8%B6%8A%E9%81%8E%E9%99%A3%E5%88%97%E5%B0%BE%E9%83%A8%E5%BE%8C%E5%9B%9E%E5%88%B0%E9%A0%AD%E9%83%A8%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%87%20num%20%E6%96%B0%E5%A2%9E%E8%87%B3%E4%BD%87%E5%88%97%E5%B0%BE%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E6%8C%87%E6%A8%99%E5%90%91%E5%BE%8C%E7%A7%BB%E5%8B%95%E4%B8%80%E4%BD%8D%EF%BC%8C%E8%8B%A5%E8%B6%8A%E9%81%8E%E5%B0%BE%E9%83%A8%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%E5%88%B0%E9%99%A3%E5%88%97%E9%A0%AD%E9%83%A8%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E4%BD%87%E5%88%97%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5B%28j%20%25%20self.capacity%28%29%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_stack.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_stack.md new file mode 100644 index 0000000000..e67f74bd92 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%99%A3%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E5%A0%86%E7%96%8A%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self%2C%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%96%8A%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%96%8A%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%96%8A%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%96%8A%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md new file mode 100644 index 0000000000..8919ad3cb5 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E4%BD%87%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E9%A0%AD%E7%AF%80%E9%BB%9E%20front%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B0%BE%E7%AF%80%E9%BB%9E%20rear%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E7%AF%80%E9%BB%9E%E5%BE%8C%E6%96%B0%E5%A2%9E%20num%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E4%BD%87%E5%88%97%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E4%BB%A4%E9%A0%AD%E3%80%81%E5%B0%BE%E7%AF%80%E9%BB%9E%E9%83%BD%E6%8C%87%E5%90%91%E8%A9%B2%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E4%BD%87%E5%88%97%E4%B8%8D%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E5%B0%87%E8%A9%B2%E7%AF%80%E9%BB%9E%E6%96%B0%E5%A2%9E%E5%88%B0%E5%B0%BE%E7%AF%80%E9%BB%9E%E5%BE%8C%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%88%97%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E9%A0%AD%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E4%BD%87%E5%88%97%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%89%E5%8C%96%E7%82%BA%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%20queue%20%3D%22%2C%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop_front%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%BE%8C%20queue%20%3D%22%2C%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md new file mode 100644 index 0000000000..1af66bc3b6 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%96%BC%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E5%AF%A6%E7%8F%BE%E7%9A%84%E5%A0%86%E7%96%8A%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%B7%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E5%A0%86%E7%96%8A%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E7%96%8A%E7%82%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%89%E5%8C%96%E7%82%BA%E4%B8%B2%E5%88%97%E7%94%A8%E6%96%BC%E5%88%97%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A0%86%E7%96%8A%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%96%8A%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%96%8A%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/array_binary_tree.md b/zh-hant/codes/pythontutor/chapter_tree/array_binary_tree.md new file mode 100644 index 0000000000..74f3ebdf4d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/array_binary_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%20%20%20%20%22%22%22%E9%99%A3%E5%88%97%E8%A1%A8%E7%A4%BA%E4%B8%8B%E7%9A%84%E4%BA%8C%E5%85%83%E6%A8%B9%E9%A1%9E%E5%88%A5%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%B2%E5%88%97%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%B4%A2%E5%BC%95%E7%82%BA%20i%20%E7%AF%80%E9%BB%9E%E7%9A%84%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E7%8D%B2%E5%8F%96%E7%88%B6%E7%AF%80%E9%BB%9E%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E8%B5%B0%E8%A8%AA%E9%99%A3%E5%88%97%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self%2C%20i%3A%20int%2C%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E5%84%AA%E5%85%88%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22pre%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22in%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22post%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%22pre%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%22in%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%22post%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20arr%20%3D%20%5B1%2C%202%2C%203%2C%204%2C%20None%2C%206%2C%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E7%AF%80%E9%BB%9E%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20l%2C%20r%2C%20p%20%3D%20abt.left%28i%29%2C%20abt.right%28i%29%2C%20abt.parent%28i%29%0A%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E6%A8%B9%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/binary_search_tree.md b/zh-hant/codes/pythontutor/chapter_tree/binary_search_tree.md new file mode 100644 index 0000000000..41eb88ec1c --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/binary_search_tree.md @@ -0,0 +1,14 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A8%B9%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self%2C%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%9F%A5%E8%A9%A2%EF%BC%8C%E8%B6%8A%E9%81%8E%E8%91%89%E7%AF%80%E9%BB%9E%E5%BE%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E7%AF%80%E9%BB%9E%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E7%AF%80%E9%BB%9E%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A8%99%E7%AF%80%E9%BB%9E%EF%BC%8C%E8%B7%B3%E5%87%BA%E8%BF%B4%E5%9C%88%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A8%B9%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%9F%A5%E8%A9%A2%EF%BC%8C%E8%B6%8A%E9%81%8E%E8%91%89%E7%AF%80%E9%BB%9E%E5%BE%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E8%A4%87%E7%AF%80%E9%BB%9E%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%22%5Cn%E6%9F%A5%E8%A9%A2%E5%88%B0%E7%9A%84%E7%AF%80%E9%BB%9E%E7%89%A9%E4%BB%B6%E7%82%BA%3A%20%7B%7D%EF%BC%8C%E7%AF%80%E9%BB%9E%E5%80%BC%20%3D%20%7B%7D%22.format%28node%2C%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A8%B9%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A8%B9%E7%82%BA%E7%A9%BA%EF%BC%8C%E5%89%87%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%B4%E5%9C%88%E6%9F%A5%E8%A9%A2%EF%BC%8C%E8%B6%8A%E9%81%8E%E8%91%89%E7%AF%80%E9%BB%9E%E5%BE%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E8%A4%87%E7%AF%80%E9%BB%9E%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A8%B9%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%BB%BA%E6%A7%8B%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E7%AF%80%E9%BB%9E%E6%95%B8%E9%87%8F%20%3D%200%20or%201%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%95%B6%E5%AD%90%E7%AF%80%E9%BB%9E%E6%95%B8%E9%87%8F%20%3D%200%20/%201%20%E6%99%82%EF%BC%8C%20child%20%3D%20null%20/%20%E8%A9%B2%E5%AD%90%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20%21%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E7%AF%80%E9%BB%9E%E6%95%B8%E9%87%8F%20%3D%202%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%E4%B8%AD%20cur%20%E7%9A%84%E4%B8%8B%E4%B8%80%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%94%A8%20tmp%20%E8%A6%86%E8%93%8B%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20bst.remove%281%29%20%23%20%E5%BA%A6%E7%82%BA%200%0A%20%20%20%20bst.remove%282%29%20%23%20%E5%BA%A6%E7%82%BA%201%0A%20%20%20%20bst.remove%284%29%20%23%20%E5%BA%A6%E7%82%BA%202&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/binary_tree_bfs.md b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_bfs.md new file mode 100644 index 0000000000..8fd4015754 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%EF%BC%8C%E5%8A%A0%E5%85%A5%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E5%80%8B%E4%B8%B2%E5%88%97%EF%BC%8C%E7%94%A8%E6%96%BC%E5%84%B2%E5%AD%98%E8%B5%B0%E8%A8%AA%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%20%20%23%20%E9%9A%8A%E5%88%97%E5%87%BA%E9%9A%8A%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%20%20%23%20%E5%84%B2%E5%AD%98%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%85%A5%E5%88%97%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%20%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%85%A5%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E9%80%99%E8%A3%A1%E8%97%89%E5%8A%A9%E4%BA%86%E4%B8%80%E5%80%8B%E5%BE%9E%E9%99%A3%E5%88%97%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%85%83%E6%A8%B9%E7%9A%84%E5%87%BD%E5%BC%8F%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%B1%A4%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/binary_tree_dfs.md b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_dfs.md new file mode 100644 index 0000000000..cdf05f3382 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%EF%BC%9A%E9%81%9E%E8%BF%B4%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E9%99%A3%E5%88%97%E9%95%B7%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%B0%8D%E6%87%89%E7%9A%84%E5%85%83%E7%B4%A0%E7%82%BA%20None%20%EF%BC%8C%E5%89%87%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%95%B6%E5%89%8D%E7%AF%80%E9%BB%9E%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%81%9E%E8%BF%B4%E6%A7%8B%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%87%E4%B8%B2%E5%88%97%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%82%BA%E4%BA%8C%E5%85%83%E6%A8%B9%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%E5%BA%8F%EF%BC%9A%E6%A0%B9%E7%AF%80%E9%BB%9E%20-%3E%20%E5%B7%A6%E5%AD%90%E6%A8%B9%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%E5%BA%8F%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A8%B9%20-%3E%20%E6%A0%B9%E7%AF%80%E9%BB%9E%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A8%B9%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%E5%BA%8F%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A8%B9%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A8%B9%20-%3E%20%E6%A0%B9%E7%AF%80%E9%BB%9E%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E9%80%99%E8%A3%A1%E8%97%89%E5%8A%A9%E4%BA%86%E4%B8%80%E5%80%8B%E5%BE%9E%E9%99%A3%E5%88%97%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%85%83%E6%A8%B9%E7%9A%84%E5%87%BD%E5%BC%8F%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%89%8D%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E4%B8%AD%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%BE%8C%E5%BA%8F%E8%B5%B0%E8%A8%AA%E7%9A%84%E7%AF%80%E9%BB%9E%E5%88%97%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/array.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/array.rb new file mode 100644 index 0000000000..e46bb115a1 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/array.rb @@ -0,0 +1,108 @@ +=begin +File: array.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 隨機訪問元素 ### +def random_access(nums) + # 在區間 [0, nums.length) 中隨機抽取一個數字 + random_index = Random.rand(0...nums.length) + + # 獲取並返回隨機元素 + nums[random_index] +end + + +### 擴展陣列長度 ### +# 請注意,Ruby 的 Array 是動態陣列,可以直接擴展 +# 為了方便學習,本函式將 Array 看作長度不可變的陣列 +def extend(nums, enlarge) + # 初始化一個擴展長度後的陣列 + res = Array.new(nums.length + enlarge, 0) + + # 將原陣列中的所有元素複製到新陣列 + for i in 0...nums.length + res[i] = nums[i] + end + + # 返回擴展後的新陣列 + res +end + +### 在陣列的索引 index 處插入元素 num ### +def insert(nums, num, index) + # 把索引 index 以及之後的所有元素向後移動一位 + for i in (nums.length - 1).downto(index + 1) + nums[i] = nums[i - 1] + end + + # 將 num 賦給 index 處的元素 + nums[index] = num +end + + +### 刪除索引 index 處的元素 ### +def remove(nums, index) + # 把索引 index 之後的所有元素向前移動一位 + for i in index...(nums.length - 1) + nums[i] = nums[i + 1] + end +end + +### 走訪陣列 ### +def traverse(nums) + count = 0 + + # 透過索引走訪陣列 + for i in 0...nums.length + count += nums[i] + end + + # 直接走訪陣列元素 + for num in nums + count += num + end +end + +### 在陣列中查詢指定元素 ### +def find(nums, target) + for i in 0...nums.length + return i if nums[i] == target + end + + -1 +end + + +### Driver Code ### +if __FILE__ == $0 + # 初始化陣列 + arr = Array.new(5, 0) + puts "陣列 arr = #{arr}" + nums = [1, 3, 2, 5, 4] + puts "陣列 nums = #{nums}" + + # 隨機訪問 + random_num = random_access(nums) + puts "在 nums 中獲取隨機元素 #{random_num}" + + # 長度擴展 + nums = extend(nums, 3) + puts "將陣列長度擴展至 8 ,得到 nums = #{nums}" + + # 插入元素 + insert(nums, 6, 3) + puts "在索引 3 處插入數字 6 ,得到 nums = #{nums}" + + # 刪除元素 + remove(nums, 2) + puts "刪除索引 2 處的元素,得到 nums = #{nums}" + + # 走訪陣列 + traverse(nums) + + # 查詢元素 + index = find(nums, 3) + puts "在 nums 中查詢元素 3 ,得到索引 = #{index}" +end diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/linked_list.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/linked_list.rb new file mode 100644 index 0000000000..e6da905206 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/linked_list.rb @@ -0,0 +1,83 @@ +=begin +File: linked_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/print_util' + +### 在鏈結串列的節點 n0 之後插入節點 _p ### +# Ruby 的 `p` 是一個內建函式, `P` 是一個常數,所以可以使用 `_p` 代替 +def insert(n0, _p) + n1 = n0.next + _p.next = n1 + n0.next = _p +end + +### 刪除鏈結串列的節點 n0 之後的首個節點 ### +def remove(n0) + return if n0.next.nil? + + # n0 -> remove_node -> n1 + remove_node = n0.next + n1 = remove_node.next + n0.next = n1 +end + +### 訪問鏈結串列中索引為 index 的節點 ### +def access(head, index) + for i in 0...index + return nil if head.nil? + head = head.next + end + + head +end + +### 在鏈結串列中查詢值為 target 的首個節點 ### +def find(head, target) + index = 0 + while head + return index if head.val == target + head = head.next + index += 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化鏈結串列 + # 初始化各個節點 + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + puts "初始化的鏈結串列為" + print_linked_list(n0) + + # 插入節點 + insert(n0, ListNode.new(0)) + print_linked_list n0 + + # 刪除節點 + remove(n0) + puts "刪除節點後的鏈結串列為" + print_linked_list(n0) + + # 訪問節點 + node = access(n0, 3) + puts "鏈結串列中索引 3 處的節點的值 = #{node.val}" + + # 查詢節點 + index = find(n0, 2) + puts "鏈結串列中值為 2 的節點的索引 = #{index}" +end diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/list.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/list.rb new file mode 100644 index 0000000000..984dfc2628 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/list.rb @@ -0,0 +1,60 @@ +=begin +File: list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化串列 + nums = [1, 3, 2, 5, 4] + puts "串列 nums = #{nums}" + + # 訪問元素 + num = nums[1] + puts "訪問索引 1 處的元素,得到 num = #{num}" + + # 更新元素 + nums[1] = 0 + puts "將索引 1 處的元素更新為 0 ,得到 nums = #{nums}" + + # 清空串列 + nums.clear + puts "清空串列後 nums = #{nums}" + + # 在尾部新增元素 + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + puts "新增元素後 nums = #{nums}" + + # 在中間插入元素 + nums.insert(3, 6) + puts "在索引 3 處插入元素 6 ,得到 nums = #{nums}" + + # 刪除元素 + nums.delete_at(3) + puts "刪除索引 3 處的元素,得到 nums = #{nums}" + + # 透過索引走訪串列 + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # 直接走訪串列元素 + count = 0 + nums.each do |x| + count += x + end + + # 拼接兩個串列 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + puts "將串列 nums1 拼接到 nums 之後,得到 nums = #{nums}" + + nums = nums.sort { |a, b| a <=> b } + puts "排序串列後 nums = #{nums}" +end diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/my_list.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/my_list.rb new file mode 100644 index 0000000000..e0e1e754d0 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/my_list.rb @@ -0,0 +1,132 @@ +=begin +File: my_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 串列類別 ### +class MyList + attr_reader :size # 獲取串列長度(當前元素數量) + attr_reader :capacity # 獲取串列容量 + + ### 建構子 ### + def initialize + @capacity = 10 + @size = 0 + @extend_ratio = 2 + @arr = Array.new(capacity) + end + + ### 訪問元素 ### + def get(index) + # 索引如果越界,則丟擲異常,下同 + raise IndexError, "索引越界" if index < 0 || index >= size + @arr[index] + end + + ### 訪問元素 ### + def set(index, num) + raise IndexError, "索引越界" if index < 0 || index >= size + @arr[index] = num + end + + ### 在尾部新增元素 ### + def add(num) + # 元素數量超出容量時,觸發擴容機制 + extend_capacity if size == capacity + @arr[size] = num + + # 更新元素數量 + @size += 1 + end + + ### 在中間插入元素 ### + def insert(index, num) + raise IndexError, "索引越界" if index < 0 || index >= size + + # 元素數量超出容量時,觸發擴容機制 + extend_capacity if size == capacity + + # 將索引 index 以及之後的元素都向後移動一位 + for j in (size - 1).downto(index) + @arr[j + 1] = @arr[j] + end + @arr[index] = num + + # 更新元素數量 + @size += 1 + end + + ### 刪除元素 ### + def remove(index) + raise IndexError, "索引越界" if index < 0 || index >= size + num = @arr[index] + + # 將將索引 index 之後的元素都向前移動一位 + for j in index...size + @arr[j] = @arr[j + 1] + end + + # 更新元素數量 + @size -= 1 + + # 返回被刪除的元素 + num + end + + ### 串列擴容 ### + def extend_capacity + # 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列 + arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) + # 更新串列容量 + @capacity = arr.length + end + + ### 將串列轉換為陣列 ### + def to_array + sz = size + # 僅轉換有效長度範圍內的串列元素 + arr = Array.new(sz) + for i in 0...sz + arr[i] = get(i) + end + arr + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化串列 + nums = MyList.new + + # 在尾部新增元素 + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + puts "串列 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,長度 = #{nums.size}" + + # 在中間插入元素 + nums.insert(3, 6) + puts "在索引 3 處插入數字 6 ,得到 nums = #{nums.to_array}" + + # 刪除元素 + nums.remove(3) + puts "刪除索引 3 的元素,得到 nums = #{nums.to_array}" + + # 訪問元素 + num = nums.get(1) + puts "訪問索引 1 處的元素,得到 num = #{num}" + + # 更新元素 + nums.set(1, 0) + puts "將索引 1 處的元素更新為 0 ,得到 nums = #{nums.to_array}" + + # 測試擴容機制 + for i in 0...10 + # 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i) + end + puts "擴容後的串列 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,長度 = #{nums.size}" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/n_queens.rb b/zh-hant/codes/ruby/chapter_backtracking/n_queens.rb new file mode 100644 index 0000000000..c4111e2ae7 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/n_queens.rb @@ -0,0 +1,61 @@ +=begin +File: n_queens.rb +Created Time: 2024-05-21 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:n 皇后 ### +def backtrack(row, n, state, res, cols, diags1, diags2) + # 當放置完所有行時,記錄解 + if row == n + res << state.map { |row| row.dup } + return + end + + # 走訪所有列 + for col in 0...n + # 計算該格子對應的主對角線和次對角線 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] + # 嘗試:將皇后放置在該格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = true + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:將該格子恢復為空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = false + end + end +end + +### 求解 n 皇后 ### +def n_queens(n) + # 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + state = Array.new(n) { Array.new(n, "#") } + cols = Array.new(n, false) # 記錄列是否有皇后 + diags1 = Array.new(2 * n - 1, false) # 記錄主對角線上是否有皇后 + diags2 = Array.new(2 * n - 1, false) # 記錄次對角線上是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 4 + res = n_queens(n) + + puts "輸入棋盤長寬為 #{n}" + puts "皇后放置方案共有 #{res.length} 種" + + for state in res + puts "--------------------" + for row in state + p row + end + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/permutations_i.rb b/zh-hant/codes/ruby/chapter_backtracking/permutations_i.rb new file mode 100644 index 0000000000..dd5a7ec13e --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/permutations_i.rb @@ -0,0 +1,46 @@ +=begin +File: permutations_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:全排列 I ### +def backtrack(state, choices, selected, res) + # 當狀態長度等於元素數量時,記錄解 + if state.length == choices.length + res << state.dup + return + end + + # 走訪所有選擇 + choices.each_with_index do |choice, i| + # 剪枝:不允許重複選擇元素 + unless selected[i] + # 嘗試:做出選擇,更新狀態 + selected[i] = true + state << choice + # 進行下一輪選擇 + backtrack(state, choices, selected, res) + # 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.pop + end + end +end + +### 全排列 I ### +def permutations_i(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 3] + + res = permutations_i(nums) + + puts "輸入陣列 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/permutations_ii.rb b/zh-hant/codes/ruby/chapter_backtracking/permutations_ii.rb new file mode 100644 index 0000000000..682fc71629 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/permutations_ii.rb @@ -0,0 +1,48 @@ +=begin +File: permutations_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:全排列 II ### +def backtrack(state, choices, selected, res) + # 當狀態長度等於元素數量時,記錄解 + if state.length == choices.length + res << state.dup + return + end + + # 走訪所有選擇 + duplicated = Set.new + choices.each_with_index do |choice, i| + # 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if !selected[i] && !duplicated.include?(choice) + # 嘗試:做出選擇,更新狀態 + duplicated.add(choice) + selected[i] = true + state << choice + # 進行下一輪選擇 + backtrack(state, choices, selected, res) + # 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.pop + end + end +end + +### 全排列 II ### +def permutations_ii(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 2] + + res = permutations_ii(nums) + + puts "輸入陣列 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb new file mode 100644 index 0000000000..b0de96d915 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb @@ -0,0 +1,33 @@ +=begin +File: preorder_traversal_i_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序走訪:例題一 ### +def pre_order(root) + return unless root + + # 記錄解 + $res << root if root.val == 7 + + pre_order(root.left) + pre_order(root.right) +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 前序走訪 + $res = [] + pre_order(root) + + puts "\n輸出所有值為 7 的節點" + p $res.map { |node| node.val } +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb new file mode 100644 index 0000000000..de25757cdd --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb @@ -0,0 +1,41 @@ +=begin +File: preorder_traversal_ii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序走訪:例題二 ### +def pre_order(root) + return unless root + + # 嘗試 + $path << root + + # 記錄解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 前序走訪 + $path, $res = [], [] + pre_order(root) + + puts "\n輸出所有根節點到節點 7 的路徑" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb new file mode 100644 index 0000000000..44d12956c8 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb @@ -0,0 +1,42 @@ +=begin +File: preorder_traversal_iii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序走訪:例題三 ### +def pre_order(root) + # 剪枝 + return if !root || root.val == 3 + + # 嘗試 + $path.append(root) + + # 記錄解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 前序走訪 + $path, $res = [], [] + pre_order(root) + + puts "\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb new file mode 100644 index 0000000000..cf770b5122 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb @@ -0,0 +1,68 @@ +=begin +File: preorder_traversal_iii_template.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 判斷當前狀態是否為解 ### +def is_solution?(state) + !state.empty? && state.last.val == 7 +end + +### 記錄解 ### +def record_solution(state, res) + res << state.dup +end + +### 判斷在當前狀態下,該選擇是否合法 ### +def is_valid?(state, choice) + choice && choice.val != 3 +end + +### 更新狀態 ### +def make_choice(state, choice) + state << choice +end + +### 恢復狀態 ### +def undo_choice(state, choice) + state.pop +end + +### 回溯演算法:例題三 ### +def backtrack(state, choices, res) + # 檢查是否為解 + record_solution(state, res) if is_solution?(state) + + # 走訪所有選擇 + for choice in choices + # 剪枝:檢查選擇是否合法 + if is_valid?(state, choice) + # 嘗試:做出選擇,更新狀態 + make_choice(state, choice) + # 進行下一輪選擇 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice) + end + end +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹" + print_tree(root) + + # 回溯演算法 + res = [] + backtrack([], [root], res) + + puts "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" + for path in res + p path.map { |node| node.val } + end +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i.rb b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i.rb new file mode 100644 index 0000000000..3390e98b8a --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i.rb @@ -0,0 +1,47 @@ +=begin +File: subset_sum_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:子集和 I ### +def backtrack(state, target, choices, start, res) + # 子集和等於 target 時,記錄解 + if target.zero? + res << state.dup + return + end + # 走訪所有選擇 + # 剪枝二:從 start 開始走訪,避免生成重複子集 + for i in start...choices.length + # 剪枝一:若子集和超過 target ,則直接結束迴圈 + # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + break if target - choices[i] < 0 + # 嘗試:做出選擇,更新 target, start + state << choices[i] + # 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop + end +end + +### 求解子集和 I ### +def subset_sum_i(nums, target) + state = [] # 狀態(子集) + nums.sort! # 對 nums 進行排序 + start = 0 # 走訪起始點 + res = [] # 結果串列(子集串列) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + puts "輸入陣列 = #{nums}, target = #{target}" + puts "所有和等於 #{target} 的子集 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb new file mode 100644 index 0000000000..d5b3ac06b1 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb @@ -0,0 +1,46 @@ +=begin +File: subset_sum_i_naive.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:子集和 I ### +def backtrack(state, target, total, choices, res) + # 子集和等於 target 時,記錄解 + if total == target + res << state.dup + return + end + + # 走訪所有選擇 + for i in 0...choices.length + # 剪枝:若子集和超過 target ,則跳過該選擇 + next if total + choices[i] > target + # 嘗試:做出選擇,更新元素和 total + state << choices[i] + # 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop + end +end + +### 求解子集和 I(包含重複子集)### +def subset_sum_i_naive(nums, target) + state = [] # 狀態(子集) + total = 0 # 子集和 + res = [] # 結果串列(子集串列) + backtrack(state, target, total, nums, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + puts "輸入陣列 nums = #{nums}, target = #{target}" + puts "所有和等於 #{target} 的子集 res = #{res}" + puts "請注意,該方法輸出的結果包含重複集合" +end diff --git a/zh-hant/codes/ruby/chapter_backtracking/subset_sum_ii.rb b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_ii.rb new file mode 100644 index 0000000000..343437f6bd --- /dev/null +++ b/zh-hant/codes/ruby/chapter_backtracking/subset_sum_ii.rb @@ -0,0 +1,51 @@ +=begin +File: subset_sum_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯演算法:子集和 II ### +def backtrack(state, target, choices, start, res) + # 子集和等於 target 時,記錄解 + if target.zero? + res << state.dup + return + end + + # 走訪所有選擇 + # 剪枝二:從 start 開始走訪,避免生成重複子集 + # 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i in start...choices.length + # 剪枝一:若子集和超過 target ,則直接結束迴圈 + # 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + break if target - choices[i] < 0 + # 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + next if i > start && choices[i] == choices[i - 1] + # 嘗試:做出選擇,更新 target, start + state << choices[i] + # 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤銷選擇,恢復到之前的狀態 + state.pop + end +end + +### 求解子集和 II ### +def subset_sum_ii(nums, target) + state = [] # 狀態(子集) + nums.sort! # 對 nums 進行排序 + start = 0 # 走訪起始點 + res = [] # 結果串列(子集串列) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + puts "輸入陣列 nums = #{nums}, target = #{target}" + puts "所有和等於 #{target} 的子集 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/iteration.rb b/zh-hant/codes/ruby/chapter_computational_complexity/iteration.rb new file mode 100644 index 0000000000..05cf0a918a --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/iteration.rb @@ -0,0 +1,79 @@ +=begin +File: iteration.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) +=end + +### for 迴圈 ### +def for_loop(n) + res = 0 + + # 迴圈求和 1, 2, ..., n-1, n + for i in 1..n + res += i + end + + res +end + +### while 迴圈 ### +def while_loop(n) + res = 0 + i = 1 # 初始化條件變數 + + # 迴圈求和 1, 2, ..., n-1, n + while i <= n + res += i + i += 1 # 更新條件變數 + end + + res +end + +### while 迴圈(兩次更新)### +def while_loop_ii(n) + res = 0 + i = 1 # 初始化條件變數 + + # 迴圈求和 1, 4, 10, ... + while i <= n + res += i + # 更新條件變數 + i += 1 + i *= 2 + end + + res +end + +### 雙層 for 迴圈 ### +def nested_for_loop(n) + res = "" + + # 迴圈 i = 1, 2, ..., n-1, n + for i in 1..n + # 迴圈 j = 1, 2, ..., n-1, n + for j in 1..n + res += "(#{i}, #{j}), " + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = for_loop(n) + puts "\nfor 迴圈的求和結果 res = #{res}" + + res = while_loop(n) + puts "\nwhile 迴圈的求和結果 res = #{res}" + + res = while_loop_ii(n) + puts "\nwhile 迴圈(兩次更新)求和結果 res = #{res}" + + res = nested_for_loop(n) + puts "\n雙層 for 迴圈的走訪結果 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/recursion.rb b/zh-hant/codes/ruby/chapter_computational_complexity/recursion.rb new file mode 100644 index 0000000000..8c4ae970a1 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/recursion.rb @@ -0,0 +1,70 @@ +=begin +File: recursion.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 遞迴 ### +def recur(n) + # 終止條件 + return 1 if n == 1 + # 遞:遞迴呼叫 + res = recur(n - 1) + # 迴:返回結果 + n + res +end + +### 使用迭代模擬遞迴 ### +def for_loop_recur(n) + # 使用一個顯式的堆疊來模擬系統呼叫堆疊 + stack = [] + res = 0 + + # 遞:遞迴呼叫 + for i in n.downto(0) + # 透過“入堆疊操作”模擬“遞” + stack << i + end + # 迴:返回結果 + while !stack.empty? + res += stack.pop + end + + # res = 1+2+3+...+n + res +end + +### 尾遞迴 ### +def tail_recur(n, res) + # 終止條件 + return res if n == 0 + # 尾遞迴呼叫 + tail_recur(n - 1, res + n) +end + +### 費波那契數列:遞迴 ### +def fib(n) + # 終止條件 f(1) = 0, f(2) = 1 + return n - 1 if n == 1 || n == 2 + # 遞迴呼叫 f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # 返回結果 f(n) + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = recur(n) + puts "\n遞迴函式的求和結果 res = #{res}" + + res = for_loop_recur(n) + puts "\n使用迭代模擬遞迴求和結果 res = #{res}" + + res = tail_recur(n, 0) + puts "\n尾遞迴函式的求和結果 res = #{res}" + + res = fib(n) + puts "\n費波那契數列的第 #{n} 項為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/space_complexity.rb b/zh-hant/codes/ruby/chapter_computational_complexity/space_complexity.rb new file mode 100644 index 0000000000..522992ee46 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/space_complexity.rb @@ -0,0 +1,92 @@ +=begin +File: space_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 函式 ### +def function + # 執行某些操作 + 0 +end + +### 常數階 ### +def constant(n) + # 常數、變數、物件佔用 O(1) 空間 + a = 0 + nums = [0] * 10000 + node = ListNode.new + + # 迴圈中的變數佔用 O(1) 空間 + (0...n).each { c = 0 } + # 迴圈中的函式佔用 O(1) 空間 + (0...n).each { function } +end + +### 線性階 ### +def linear(n) + # 長度為 n 的串列佔用 O(n) 空間 + nums = Array.new(n, 0) + + # 長度為 n 的雜湊表佔用 O(n) 空間 + hmap = {} + for i in 0...n + hmap[i] = i.to_s + end +end + +### 線性階(遞迴實現)### +def linear_recur(n) + puts "遞迴 n = #{n}" + return if n == 1 + linear_recur(n - 1) +end + +### 平方階 ### +def quadratic(n) + # 二維串列佔用 O(n^2) 空間 + Array.new(n) { Array.new(n, 0) } +end + +### 平方階(遞迴實現)### +def quadratic_recur(n) + return 0 unless n > 0 + + # 陣列 nums 長度為 n, n-1, ..., 2, 1 + nums = Array.new(n, 0) + quadratic_recur(n - 1) +end + +### 指數階(建立滿二元樹)### +def build_tree(n) + return if n == 0 + + TreeNode.new.tap do |root| + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + end +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + # 常數階 + constant(n) + + # 線性階 + linear(n) + linear_recur(n) + + # 平方階 + quadratic(n) + quadratic_recur(n) + + # 指數階 + root = build_tree(n) + print_tree(root) +end diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/time_complexity.rb b/zh-hant/codes/ruby/chapter_computational_complexity/time_complexity.rb new file mode 100644 index 0000000000..31e597a1b7 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/time_complexity.rb @@ -0,0 +1,165 @@ +=begin +File: time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 常數階 ### +def constant(n) + count = 0 + size = 100000 + + (0...size).each { count += 1 } + + count +end + +### 線性階 ### +def linear(n) + count = 0 + (0...n).each { count += 1 } + count +end + +### 線性階(走訪陣列)### +def array_traversal(nums) + count = 0 + + # 迴圈次數與陣列長度成正比 + for num in nums + count += 1 + end + + count +end + +### 平方階 ### +def quadratic(n) + count = 0 + + # 迴圈次數與資料大小 n 成平方關係 + for i in 0...n + for j in 0...n + count += 1 + end + end + + count +end + +### 平方階(泡沫排序)### +def bubble_sort(nums) + count = 0 # 計數器 + + # 外迴圈:未排序區間為 [0, i] + for i in (nums.length - 1).downto(0) + # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0...i + if nums[j] > nums[j + 1] + # 交換 nums[j] 與 nums[j + 1] + tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 元素交換包含 3 個單元操作 + end + end + end + + count +end + +### 指數階(迴圈實現)### +def exponential(n) + count, base = 0, 1 + + # 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + (0...n).each do + (0...base).each { count += 1 } + base *= 2 + end + + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +end + +### 指數階(遞迴實現)### +def exp_recur(n) + return 1 if n == 1 + exp_recur(n - 1) + exp_recur(n - 1) + 1 +end + +### 對數階(迴圈實現)### +def logarithmic(n) + count = 0 + + while n > 1 + n /= 2 + count += 1 + end + + count +end + +### 對數階(遞迴實現)### +def log_recur(n) + return 0 unless n > 1 + log_recur(n / 2) + 1 +end + +### 線性對數階 ### +def linear_log_recur(n) + return 1 unless n > 1 + + count = linear_log_recur(n / 2) + linear_log_recur(n / 2) + (0...n).each { count += 1 } + + count +end + +### 階乘階(遞迴實現)### +def factorial_recur(n) + return 1 if n == 0 + + count = 0 + # 從 1 個分裂出 n 個 + (0...n).each { count += factorial_recur(n - 1) } + + count +end + +### Driver Code ### +if __FILE__ == $0 + # 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + n = 8 + puts "輸入資料大小 n = #{n}" + + count = constant(n) + puts "常數階的操作數量 = #{count}" + + count = linear(n) + puts "線性階的操作數量 = #{count}" + count = array_traversal(Array.new(n, 0)) + puts "線性階(走訪陣列)的操作數量 = #{count}" + + count = quadratic(n) + puts "平方階的操作數量 = #{count}" + nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] + count = bubble_sort(nums) + puts "平方階(泡沫排序)的操作數量 = #{count}" + + count = exponential(n) + puts "指數階(迴圈實現)的操作數量 = #{count}" + count = exp_recur(n) + puts "指數階(遞迴實現)的操作數量 = #{count}" + + count = logarithmic(n) + puts "對數階(迴圈實現)的操作數量 = #{count}" + count = log_recur(n) + puts "對數階(遞迴實現)的操作數量 = #{count}" + + count = linear_log_recur(n) + puts "線性對數階(遞迴實現)的操作數量 = #{count}" + + count = factorial_recur(n) + puts "階乘階(遞迴實現)的操作數量 = #{count}" +end diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb b/zh-hant/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb new file mode 100644 index 0000000000..c69445a680 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb @@ -0,0 +1,35 @@ +=begin +File: worst_best_time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 生成一個陣列,元素為: 1, 2, ..., n ,順序被打亂 ### +def random_numbers(n) + # 生成陣列 nums =: 1, 2, 3, ..., n + nums = Array.new(n) { |i| i + 1 } + # 隨機打亂陣列元素 + nums.shuffle! +end + +### 查詢陣列 nums 中數字 1 所在索引 ### +def find_one(nums) + for i in 0...nums.length + # 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + # 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + return i if nums[i] == 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + for i in 0...10 + n = 100 + nums = random_numbers(n) + index = find_one(nums) + puts "\n陣列 [ 1, 2, ..., n ] 被打亂後 = #{nums}" + puts "數字 1 的索引為 #{index}" + end +end diff --git a/zh-hant/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb b/zh-hant/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb new file mode 100644 index 0000000000..5a624bac45 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb @@ -0,0 +1,42 @@ +=begin +File: binary_search_recur.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二分搜尋:問題 f(i, j) ### +def dfs(nums, target, i, j) + # 若區間為空,代表無目標元素,則返回 -1 + return -1 if i > j + + # 計算中點索引 m + m = (i + j) / 2 + + if nums[m] < target + # 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j) + elsif nums[m] > target + # 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1) + else + # 找到目標元素,返回其索引 + return m + end +end + +### 二分搜尋 ### +def binary_search(nums, target) + n = nums.length + # 求解問題 f(0, n-1) + dfs(nums, target, 0, n - 1) +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分搜尋(雙閉區間) + index = binary_search(nums, target) + puts "目標元素 6 的索引 = #{index}" +end diff --git a/zh-hant/codes/ruby/chapter_divide_and_conquer/build_tree.rb b/zh-hant/codes/ruby/chapter_divide_and_conquer/build_tree.rb new file mode 100644 index 0000000000..2ffdbaf07e --- /dev/null +++ b/zh-hant/codes/ruby/chapter_divide_and_conquer/build_tree.rb @@ -0,0 +1,46 @@ +=begin +File: build_tree.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 構建二元樹:分治 ### +def dfs(preorder, inorder_map, i, l, r) + # 子樹區間為空時終止 + return if r - l < 0 + + # 初始化根節點 + root = TreeNode.new(preorder[i]) + # 查詢 m ,從而劃分左右子樹 + m = inorder_map[preorder[i]] + # 子問題:構建左子樹 + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # 子問題:構建右子樹 + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + + # 返回根節點 + root +end + +### 構建二元樹 ### +def build_tree(preorder, inorder) + # 初始化雜湊表,儲存 inorder 元素到索引的對映 + inorder_map = {} + inorder.each_with_index { |val, i| inorder_map[val] = i } + dfs(preorder, inorder_map, 0, 0, inorder.length - 1) +end + +### Driver Code ### +if __FILE__ == $0 + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + puts "前序走訪 = #{preorder}" + puts "中序走訪 = #{inorder}" + + root = build_tree(preorder, inorder) + puts "構建的二元樹為:" + print_tree(root) +end diff --git a/zh-hant/codes/ruby/chapter_divide_and_conquer/hanota.rb b/zh-hant/codes/ruby/chapter_divide_and_conquer/hanota.rb new file mode 100644 index 0000000000..e68a64bf49 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_divide_and_conquer/hanota.rb @@ -0,0 +1,55 @@ +=begin +File: hanota.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 移動一個圓盤 ### +def move(src, tar) + # 從 src 頂部拿出一個圓盤 + pan = src.pop + # 將圓盤放入 tar 頂部 + tar << pan +end + +### 求解河內塔問題 f(i) ### +def dfs(i, src, buf, tar) + # 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 + move(src, tar) + return + end + + # 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf) + # 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar) + # 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar) +end + +### 求解河內塔問題 ### +def solve_hanota(_A, _B, _C) + n = _A.length + # 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, _A, _B, _C) +end + +### Driver Code ### +if __FILE__ == $0 + # 串列尾部是柱子頂部 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + puts "初始狀態下:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" + + solve_hanota(A, B, C) + + puts "圓盤移動完成後:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb new file mode 100644 index 0000000000..bebadd5f62 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb @@ -0,0 +1,37 @@ +=begin +File: climbing_stairs_backtrack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯 ### +def backtrack(choices, state, n, res) + # 當爬到第 n 階時,方案數量加 1 + res[0] += 1 if state == n + # 走訪所有選擇 + for choice in choices + # 剪枝:不允許越過第 n 階 + next if state + choice > n + + # 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res) + end + # 回退 +end + +### 爬樓梯:回溯 ### +def climbing_stairs_backtrack(n) + choices = [1, 2] # 可選擇向上爬 1 階或 2 階 + state = 0 # 從第 0 階開始爬 + res = [0] # 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res) + res.first +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_backtrack(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb new file mode 100644 index 0000000000..35bab162ec --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb @@ -0,0 +1,31 @@ +=begin +File: climbing_stairs_constraint_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 帶約束爬樓梯:動態規劃 ### +def climbing_stairs_constraint_dp(n) + return 1 if n == 1 || n == 2 + + # 初始化 dp 表,用於儲存子問題的解 + dp = Array.new(n + 1) { Array.new(3, 0) } + # 初始狀態:預設最小子問題的解 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3...(n + 1) + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + end + + dp[n][1] + dp[n][2] +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_constraint_dp(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb new file mode 100644 index 0000000000..a3b0f3f176 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb @@ -0,0 +1,26 @@ +=begin +File: climbing_stairs_dfs.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 搜尋 ### +def dfs(i) + # 已知 dp[1] 和 dp[2] ,返回之 + return i if i == 1 || i == 2 + # dp[i] = dp[i-1] + dp[i-2] + dfs(i - 1) + dfs(i - 2) +end + +### 爬樓梯:搜尋 ### +def climbing_stairs_dfs(n) + dfs(n) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb new file mode 100644 index 0000000000..66a758f6f9 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb @@ -0,0 +1,33 @@ +=begin +File: climbing_stairs_dfs_mem.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 記憶化搜尋 ### +def dfs(i, mem) + # 已知 dp[1] 和 dp[2] ,返回之 + return i if i == 1 || i == 2 + # 若存在記錄 dp[i] ,則直接返回之 + return mem[i] if mem[i] != -1 + + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # 記錄 dp[i] + mem[i] = count +end + +### 爬樓梯:記憶化搜尋 ### +def climbing_stairs_dfs_mem(n) + # mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + mem = Array.new(n + 1, -1) + dfs(n, mem) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs_mem(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb new file mode 100644 index 0000000000..8b9d7fb582 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb @@ -0,0 +1,40 @@ +=begin +File: climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 爬樓梯:動態規劃 ### +def climbing_stairs_dp(n) + return n if n == 1 || n == 2 + + # 初始化 dp 表,用於儲存子問題的解 + dp = Array.new(n + 1, 0) + # 初始狀態:預設最小子問題的解 + dp[1], dp[2] = 1, 2 + # 狀態轉移:從較小子問題逐步求解較大子問題 + (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } + + dp[n] +end + +### 爬樓梯:空間最佳化後的動態規劃 ### +def climbing_stairs_dp_comp(n) + return n if n == 1 || n == 2 + + a, b = 1, 2 + (3...(n + 1)).each { a, b = b, a + b } + + b +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dp(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" + + res = climbing_stairs_dp_comp(n) + puts "爬 #{n} 階樓梯共有 #{res} 種方案" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change.rb new file mode 100644 index 0000000000..73ce685ef0 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change.rb @@ -0,0 +1,65 @@ +=begin +File: coin_change.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零錢兌換:動態規劃 ### +def coin_change_dp(coins, amt) + n = coins.length + _MAX = amt + 1 + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 狀態轉移:首行首列 + (1...(amt + 1)).each { |a| dp[0][a] = _MAX } + # 狀態轉移:其餘行和列 + for i in 1...(n + 1) + for a in 1...(amt + 1) + 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]] + 1].min + end + end + end + dp[n][amt] != _MAX ? dp[n][amt] : -1 +end + +### 零錢兌換:空間最佳化後的動態規劃 ### +def coin_change_dp_comp(coins, amt) + n = coins.length + _MAX = amt + 1 + # 初始化 dp 表 + dp = Array.new(amt + 1, _MAX) + dp[0] = 0 + # 狀態轉移 + for i in 1...(n + 1) + # 正序走訪 + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + else + # 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min + end + end + end + dp[amt] != _MAX ? dp[amt] : -1 +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 4 + + # 動態規劃 + res = coin_change_dp(coins, amt) + puts "湊到目標金額所需的最少硬幣數量為 #{res}" + + # 空間最佳化後的動態規劃 + res = coin_change_dp_comp(coins, amt) + puts "湊到目標金額所需的最少硬幣數量為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb new file mode 100644 index 0000000000..6fbeb51078 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb @@ -0,0 +1,63 @@ +=begin +File: coin_change_ii.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零錢兌換 II:動態規劃 ### +def coin_change_ii_dp(coins, amt) + n = coins.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 初始化首列 + (0...(n + 1)).each { |i| dp[i][0] = 1 } + # 狀態轉移 + for i in 1...(n + 1) + for a in 1...(amt + 1) + 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]] + end + end + end + dp[n][amt] +end + +### 零錢兌換 II:空間最佳化後的動態規劃 ### +def coin_change_ii_dp_comp(coins, amt) + n = coins.length + # 初始化 dp 表 + dp = Array.new(amt + 1, 0) + dp[0] = 1 + # 狀態轉移 + for i in 1...(n + 1) + # 正序走訪 + for a in 1...(amt + 1) + if coins[i - 1] > a + # 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + else + # 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + end + end + end + dp[amt] +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 5 + + # 動態規劃 + res = coin_change_ii_dp(coins, amt) + puts "湊出目標金額的硬幣組合數量為 #{res}" + + # 空間最佳化後的動態規劃 + res = coin_change_ii_dp_comp(coins, amt) + puts "湊出目標金額的硬幣組合數量為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/edit_distance.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/edit_distance.rb new file mode 100644 index 0000000000..1531d88d42 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/edit_distance.rb @@ -0,0 +1,115 @@ +=begin +File: edit_distance.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 編輯距離:暴力搜尋 ### +def edit_distance_dfs(s, t, i, j) + # 若 s 和 t 都為空,則返回 0 + return 0 if i == 0 && j == 0 + # 若 s 為空,則返回 t 長度 + return j if i == 0 + # 若 t 為空,則返回 s 長度 + return i if j == 0 + # 若兩字元相等,則直接跳過此兩字元 + return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # 返回最少編輯步數 + [insert, delete, replace].min + 1 +end + +def edit_distance_dfs_mem(s, t, mem, i, j) + # 若 s 和 t 都為空,則返回 0 + return 0 if i == 0 && j == 0 + # 若 s 為空,則返回 t 長度 + return j if i == 0 + # 若 t 為空,則返回 s 長度 + return i if j == 0 + # 若已有記錄,則直接返回之 + return mem[i][j] if mem[i][j] != -1 + # 若兩字元相等,則直接跳過此兩字元 + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 記錄並返回最少編輯步數 + mem[i][j] = [insert, delete, replace].min + 1 +end + +### 編輯距離:動態規劃 ### +def edit_distance_dp(s, t) + n, m = s.length, t.length + dp = Array.new(n + 1) { Array.new(m + 1, 0) } + # 狀態轉移:首行首列 + (1...(n + 1)).each { |i| dp[i][0] = i } + (1...(m + 1)).each { |j| dp[0][j] = j } + # 狀態轉移:其餘行和列 + for i in 1...(n + 1) + for j in 1...(m +1) + if s[i - 1] == t[j - 1] + # 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1] + else + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 + end + end + end + dp[n][m] +end + +### 編輯距離:空間最佳化後的動態規劃 ### +def edit_distance_dp_comp(s, t) + n, m = s.length, t.length + dp = Array.new(m + 1, 0) + # 狀態轉移:首行 + (1...(m + 1)).each { |j| dp[j] = j } + # 狀態轉移:其餘行 + for i in 1...(n + 1) + # 狀態轉移:首列 + leftup = dp.first # 暫存 dp[i-1, j-1] + dp[0] += 1 + # 狀態轉移:其餘列 + for j in 1...(m + 1) + temp = dp[j] + if s[i - 1] == t[j - 1] + # 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup + else + # 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = [dp[j - 1], dp[j], leftup].min + 1 + end + leftup = temp # 更新為下一輪的 dp[i-1, j-1] + end + end + dp[m] +end + +### Driver Code ### +if __FILE__ == $0 + s = 'bag' + t = 'pack' + n, m = s.length, t.length + + # 暴力搜尋 + res = edit_distance_dfs(s, t, n, m) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" + + # 記憶化搜尋 + mem = Array.new(n + 1) { Array.new(m + 1, -1) } + res = edit_distance_dfs_mem(s, t, mem, n, m) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" + + # 動態規劃 + res = edit_distance_dp(s, t) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" + + # 空間最佳化後的動態規劃 + res = edit_distance_dp_comp(s, t) + puts "將 #{s} 更改為 #{t} 最少需要編輯 #{res} 步" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/knapsack.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/knapsack.rb new file mode 100644 index 0000000000..ccc425b4f4 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/knapsack.rb @@ -0,0 +1,99 @@ +=begin +File: knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 0-1 背包:暴力搜尋 ### +def knapsack_dfs(wgt, val, i, c) + # 若已選完所有物品或背包無剩餘容量,則返回價值 0 + return 0 if i == 0 || c == 0 + # 若超過背包容量,則只能選擇不放入背包 + return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c + # 計算不放入和放入物品 i 的最大價值 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 返回兩種方案中價值更大的那一個 + [no, yes].max +end + +### 0-1 背包:記憶化搜尋 ### +def knapsack_dfs_mem(wgt, val, mem, i, c) + # 若已選完所有物品或背包無剩餘容量,則返回價值 0 + return 0 if i == 0 || c == 0 + # 若已有記錄,則直接返回 + return mem[i][c] if mem[i][c] != -1 + # 若超過背包容量,則只能選擇不放入背包 + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c + # 計算不放入和放入物品 i 的最大價值 + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = [no, yes].max +end + +### 0-1 背包:動態規劃 ### +def knapsack_dp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 狀態轉移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +### 0-1 背包:空間最佳化後的動態規劃 ### +def knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(cap + 1, 0) + # 狀態轉移 + for i in 1...(n + 1) + # 倒序走訪 + for c in cap.downto(1) + if wgt[i - 1] > c + # 若超過背包容量,則不選物品 i + dp[c] = dp[c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 暴力搜尋 + res = knapsack_dfs(wgt, val, n, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 記憶化搜尋 + mem = Array.new(n + 1) { Array.new(cap + 1, -1) } + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 動態規劃 + res = knapsack_dp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 空間最佳化後的動態規劃 + res = knapsack_dp_comp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb new file mode 100644 index 0000000000..37de099b7b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb @@ -0,0 +1,39 @@ +=begin +File: min_cost_climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 爬樓梯最小代價:動態規劃 ### +def min_cost_climbing_stairs_dp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + # 初始化 dp 表,用於儲存子問題的解 + dp = Array.new(n + 1, 0) + # 初始狀態:預設最小子問題的解 + dp[1], dp[2] = cost[1], cost[2] + # 狀態轉移:從較小子問題逐步求解較大子問題 + (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } + dp[n] +end + +# 爬樓梯最小代價:空間最佳化後的動態規劃 +def min_cost_climbing_stairs_dp_comp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + a, b = cost[1], cost[2] + (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } + b +end + +### Driver Code ### +if __FILE__ == $0 + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + puts "輸入樓梯的代價串列為 #{cost}" + + res = min_cost_climbing_stairs_dp(cost) + puts "爬完樓梯的最低代價為 #{res}" + + res = min_cost_climbing_stairs_dp_comp(cost) + puts "爬完樓梯的最低代價為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/min_path_sum.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/min_path_sum.rb new file mode 100644 index 0000000000..141d1b92d0 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/min_path_sum.rb @@ -0,0 +1,93 @@ +=begin +File: min_path_sum.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最小路徑和:暴力搜尋 ### +def min_path_sum_dfs(grid, i, j) + # 若為左上角單元格,則終止搜尋 + return grid[i][j] if i == 0 && j == 0 + # 若行列索引越界,則返回 +∞ 代價 + return Float::INFINITY if i < 0 || j < 0 + # 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 返回從左上角到 (i, j) 的最小路徑代價 + [left, up].min + grid[i][j] +end + +### 最小路徑和:記憶化搜尋 ### +def min_path_sum_dfs_mem(grid, mem, i, j) + # 若為左上角單元格,則終止搜尋 + return grid[0][0] if i == 0 && j == 0 + # 若行列索引越界,則返回 +∞ 代價 + return Float::INFINITY if i < 0 || j < 0 + # 若已有記錄,則直接返回 + return mem[i][j] if mem[i][j] != -1 + # 左邊和上邊單元格的最小路徑代價 + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = [left, up].min + grid[i][j] +end + +### 最小路徑和:動態規劃 ### +def min_path_sum_dp(grid) + n, m = grid.length, grid.first.length + # 初始化 dp 表 + dp = Array.new(n) { Array.new(m, 0) } + dp[0][0] = grid[0][0] + # 狀態轉移:首行 + (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } + # 狀態轉移:首列 + (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } + # 狀態轉移:其餘行和列 + for i in 1...n + for j in 1...m + dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] + end + end + dp[n -1][m -1] +end + +### 最小路徑和:空間最佳化後的動態規劃 ### +def min_path_sum_dp_comp(grid) + n, m = grid.length, grid.first.length + # 初始化 dp 表 + dp = Array.new(m, 0) + # 狀態轉移:首行 + dp[0] = grid[0][0] + (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } + # 狀態轉移:其餘行 + for i in 1...n + # 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0] + # 狀態轉移:其餘列 + (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } + end + dp[m - 1] +end + +### Driver Code ### +if __FILE__ == $0 + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = grid.length, grid.first.length + + # 暴力搜尋 + res = min_path_sum_dfs(grid, n - 1, m - 1) + puts "從左上角到右下角的最小路徑和為 #{res}" + + # 記憶化搜尋 + mem = Array.new(n) { Array.new(m, - 1) } + res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) + puts "從左上角到右下角的最小路徑和為 #{res}" + + # 動態規劃 + res = min_path_sum_dp(grid) + puts "從左上角到右下角的最小路徑和為 #{res}" + + # 空間最佳化後的動態規劃 + res = min_path_sum_dp_comp(grid) + puts "從左上角到右下角的最小路徑和為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb b/zh-hant/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb new file mode 100644 index 0000000000..33ef0051cc --- /dev/null +++ b/zh-hant/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb @@ -0,0 +1,61 @@ +=begin +File: unbounded_knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 完全背包:動態規劃 ### +def unbounded_knapsack_dp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 狀態轉移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +### 完全背包:空間最佳化後的動態規劃 ##3 +def unbounded_knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # 初始化 dp 表 + dp = Array.new(cap + 1, 0) + # 狀態轉移 + for i in 1...(n + 1) + # 正序走訪 + for c in 1...(cap + 1) + if wgt[i -1] > c + # 若超過背包容量,則不選物品 i + dp[c] = dp[c] + else + # 不選和選物品 i 這兩種方案的較大值 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 動態規劃 + res = unbounded_knapsack_dp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" + + # 空間最佳化後的動態規劃 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_graph/graph_adjacency_list.rb b/zh-hant/codes/ruby/chapter_graph/graph_adjacency_list.rb new file mode 100644 index 0000000000..48108fe481 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_graph/graph_adjacency_list.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_list.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/vertex' + +### 基於鄰接表實現的無向圖類別 ### +class GraphAdjList + attr_reader :adj_list + + ### 建構子 ### + def initialize(edges) + # 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + @adj_list = {} + # 新增所有頂點和邊 + for edge in edges + add_vertex(edge[0]) + add_vertex(edge[1]) + add_edge(edge[0], edge[1]) + end + end + + ### 獲取頂點數量 ### + def size + @adj_list.length + end + + ### 新增邊 ### + def add_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + @adj_list[vet1] << vet2 + @adj_list[vet2] << vet1 + end + + ### 刪除邊 ### + def remove_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + # 刪除邊 vet1 - vet2 + @adj_list[vet1].delete(vet2) + @adj_list[vet2].delete(vet1) + end + + ### 新增頂點 ### + def add_vertex(vet) + return if @adj_list.include?(vet) + + # 在鄰接表中新增一個新鏈結串列 + @adj_list[vet] = [] + end + + ### 刪除頂點 ### + def remove_vertex(vet) + raise ArgumentError unless @adj_list.include?(vet) + + # 在鄰接表中刪除頂點 vet 對應的鏈結串列 + @adj_list.delete(vet) + # 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for vertex in @adj_list + @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) + end + end + + ### 列印鄰接表 ### + def __print__ + puts '鄰接表 =' + for vertex in @adj_list + tmp = @adj_list[vertex.first].map { |v| v.val } + puts "#{vertex.first.val}: #{tmp}," + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化無向圖 + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList.new(edges) + puts "\n初始化後,圖為" + graph.__print__ + + # 新增邊 + # 頂點 1,2 即 v[0],v[2] + graph.add_edge(v[0], v[2]) + puts "\n新增邊 1-2 後,圖為" + graph.__print__ + + # 刪除邊 + # 頂點 1,3 即 v[0],v[1] + graph.remove_edge(v[0], v[1]) + puts "\n刪除邊 1-3 後,圖為" + graph.__print__ + + # 新增頂點 + v5 = Vertex.new(6) + graph.add_vertex(v5) + puts "\n新增頂點 6 後,圖為" + graph.__print__ + + # 刪除頂點 + # 頂點 3 即 v[1] + graph.remove_vertex(v[1]) + puts "\n刪除頂點 3 後,圖為" + graph.__print__ +end diff --git a/zh-hant/codes/ruby/chapter_graph/graph_adjacency_matrix.rb b/zh-hant/codes/ruby/chapter_graph/graph_adjacency_matrix.rb new file mode 100644 index 0000000000..a343fee21b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_graph/graph_adjacency_matrix.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_matrix.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +### 基於鄰接矩陣實現的無向圖類別 ### +class GraphAdjMat + def initialize(vertices, edges) + ### 建構子 ### + # 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + @vertices = [] + # 鄰接矩陣,行列索引對應“頂點索引” + @adj_mat = [] + # 新增頂點 + vertices.each { |val| add_vertex(val) } + # 新增邊 + # 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + edges.each { |e| add_edge(e[0], e[1]) } + end + + ### 獲取頂點數量 ### + def size + @vertices.length + end + + ### 新增頂點 ### + def add_vertex(val) + n = size + # 向頂點串列中新增新頂點的值 + @vertices << val + # 在鄰接矩陣中新增一行 + new_row = Array.new(n, 0) + @adj_mat << new_row + # 在鄰接矩陣中新增一列 + @adj_mat.each { |row| row << 0 } + end + + ### 刪除頂點 ### + def remove_vertex(index) + raise IndexError if index >= size + + # 在頂點串列中移除索引 index 的頂點 + @vertices.delete_at(index) + # 在鄰接矩陣中刪除索引 index 的行 + @adj_mat.delete_at(index) + # 在鄰接矩陣中刪除索引 index 的列 + @adj_mat.each { |row| row.delete_at(index) } + end + + ### 新增邊 ### + def add_edge(i, j) + # 參數 i, j 對應 vertices 元素索引 + # 索引越界與相等處理 + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + # 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + @adj_mat[i][j] = 1 + @adj_mat[j][i] = 1 + end + + ### 刪除邊 ### + def remove_edge(i, j) + # 參數 i, j 對應 vertices 元素索引 + # 索引越界與相等處理 + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + @adj_mat[i][j] = 0 + @adj_mat[j][i] = 0 + end + + ### 列印鄰接矩陣 ### + def __print__ + puts "頂點串列 = #{@vertices}" + puts '鄰接矩陣 =' + print_matrix(@adj_mat) + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化無向圖 + # 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat.new(vertices, edges) + puts "\n初始化後,圖為" + graph.__print__ + + # 新增邊 + # 頂點 1, 2 的索引分別為 0, 2 + graph.add_edge(0, 2) + puts "\n新增邊 1-2 後,圖為" + graph.__print__ + + # 刪除邊 + # 定點 1, 3 的索引分別為 0, 1 + graph.remove_edge(0, 1) + puts "\n刪除邊 1-3 後,圖為" + graph.__print__ + + # 新增頂點 + graph.add_vertex(6) + puts "\n新增頂點 6 後,圖為" + graph.__print__ + + # 刪除頂點 + # 頂點 3 的索引為 1 + graph.remove_vertex(1) + puts "\n刪除頂點 3 後,圖為" + graph.__print__ +end diff --git a/zh-hant/codes/ruby/chapter_graph/graph_bfs.rb b/zh-hant/codes/ruby/chapter_graph/graph_bfs.rb new file mode 100644 index 0000000000..337bc7879a --- /dev/null +++ b/zh-hant/codes/ruby/chapter_graph/graph_bfs.rb @@ -0,0 +1,61 @@ +=begin +File: graph_bfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +### 廣度優先走訪 ### +def graph_bfs(graph, start_vet) + # 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + # 頂點走訪序列 + res = [] + # 雜湊集合,用於記錄已被訪問過的頂點 + visited = Set.new([start_vet]) + # 佇列用於實現 BFS + que = [start_vet] + # 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while que.length > 0 + vet = que.shift # 佇列首頂點出隊 + res << vet # 記錄訪問頂點 + # 走訪該頂點的所有鄰接頂點 + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # 跳過已被訪問的頂點 + que << adj_vet # 只入列未訪問的頂點 + visited.add(adj_vet) # 標記該頂點已被訪問 + end + end + # 返回頂點走訪序列 + res +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化無向圖 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [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]], + ] + graph = GraphAdjList.new(edges) + puts "\n初始化後,圖為" + graph.__print__ + + # 廣度優先走訪 + res = graph_bfs(graph, v.first) + puts "\n廣度優先便利(BFS)頂點序列為" + p vets_to_vals(res) +end diff --git a/zh-hant/codes/ruby/chapter_graph/graph_dfs.rb b/zh-hant/codes/ruby/chapter_graph/graph_dfs.rb new file mode 100644 index 0000000000..77eb709aa5 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_graph/graph_dfs.rb @@ -0,0 +1,54 @@ +=begin +File: graph_dfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +### 深度優先走訪輔助函式 ### +def dfs(graph, visited, res, vet) + res << vet # 記錄訪問頂點 + visited.add(vet) # 標記該頂點已被訪問 + # 走訪該頂點的所有鄰接頂點 + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # 跳過已被訪問的頂點 + # 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adj_vet) + end +end + +### 深度優先走訪 ### +def graph_dfs(graph, start_vet) + # 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + # 頂點走訪序列 + res = [] + # 雜湊集合,用於記錄已被訪問過的頂點 + visited = Set.new + dfs(graph, visited, res, start_vet) + res +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化無向圖 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList.new(edges) + puts "\n初始化後,圖為" + graph.__print__ + + # 深度優先走訪 + res = graph_dfs(graph, v[0]) + puts "\n深度優先走訪(DFS)頂點序列為" + p vets_to_vals(res) +end diff --git a/zh-hant/codes/ruby/chapter_greedy/coin_change_greedy.rb b/zh-hant/codes/ruby/chapter_greedy/coin_change_greedy.rb new file mode 100644 index 0000000000..513d15eb72 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/coin_change_greedy.rb @@ -0,0 +1,50 @@ +=begin +File: coin_change_greedy.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 零錢兌換:貪婪 ### +def coin_change_greedy(coins, amt) + # 假設 coins 串列有序 + i = coins.length - 1 + count = 0 + # 迴圈進行貪婪選擇,直到無剩餘金額 + while amt > 0 + # 找到小於且最接近剩餘金額的硬幣 + while i > 0 && coins[i] > amt + i -= 1 + end + # 選擇 coins[i] + amt -= coins[i] + count += 1 + end + # 若未找到可行方案, 則返回 -1 + amt == 0 ? count : -1 +end + +### Driver Code ### +if __FILE__ == $0 + # 貪婪:能夠保證找到全域性最優解 + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" + + # 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" + puts "實際上需要的最少數量為 3 , 即 20 + 20 + 20" + + # 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "湊到 #{amt} 所需的最少硬幣數量為 #{res}" + puts "實際上需要的最少數量為 2 , 即 49 + 49" +end diff --git a/zh-hant/codes/ruby/chapter_greedy/fractional_knapsack.rb b/zh-hant/codes/ruby/chapter_greedy/fractional_knapsack.rb new file mode 100644 index 0000000000..838954ccae --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/fractional_knapsack.rb @@ -0,0 +1,51 @@ +=begin +File: fractional_knapsack.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 物品 ### +class Item + attr_accessor :w # 物品重量 + attr_accessor :v # 物品價值 + + def initialize(w, v) + @w = w + @v = v + end +end + +### 分數背包:貪婪 ### +def fractional_knapsack(wgt, val, cap) + # 建立物品串列,包含兩個屬性:重量,價值 + items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } + # 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } + # 迴圈貪婪選擇 + res = 0 + for item in items + if item.w <= cap + # 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v + cap -= item.w + else + # 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (item.v.to_f / item.w) * cap + # 已無剩餘容量,因此跳出迴圈 + break + end + end + res +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 貪婪演算法 + res = fractional_knapsack(wgt, val, cap) + puts "不超過背包容量的最大物品價值為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_greedy/max_capacity.rb b/zh-hant/codes/ruby/chapter_greedy/max_capacity.rb new file mode 100644 index 0000000000..ab826c58bf --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/max_capacity.rb @@ -0,0 +1,37 @@ +=begin +File: max_capacity.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大容量:貪婪 ### +def max_capacity(ht) + # 初始化 i, j,使其分列陣列兩端 + i, j = 0, ht.length - 1 + # 初始最大容量為 0 + res = 0 + + # 迴圈貪婪選擇,直至兩板相遇 + while i < j + # 更新最大容量 + cap = [ht[i], ht[j]].min * (j - i) + res = [res, cap].max + # 向內移動短板 + if ht[i] < ht[j] + i += 1 + else + j -= 1 + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # 貪婪演算法 + res = max_capacity(ht) + puts "最大容量為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_greedy/max_product_cutting.rb b/zh-hant/codes/ruby/chapter_greedy/max_product_cutting.rb new file mode 100644 index 0000000000..eba81d601b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_greedy/max_product_cutting.rb @@ -0,0 +1,28 @@ +=begin +File: max_product_cutting.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大切分乘積:貪婪 ### +def max_product_cutting(n) + # 當 n <= 3 時,必須切分出一個 1 + return 1 * (n - 1) if n <= 3 + # 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + a, b = n / 3, n % 3 + # 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return (3.pow(a - 1) * 2 * 2).to_i if b == 1 + # 當餘數為 2 時,不做處理 + return (3.pow(a) * 2).to_i if b == 2 + # 當餘數為 0 時,不做處理 + 3.pow(a).to_i +end + +### Driver Code ### +if __FILE__ == $0 + n = 58 + + # 貪婪演算法 + res = max_product_cutting(n) + puts "最大切分乘積為 #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_hashing/array_hash_map.rb b/zh-hant/codes/ruby/chapter_hashing/array_hash_map.rb new file mode 100644 index 0000000000..b732709043 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_hashing/array_hash_map.rb @@ -0,0 +1,121 @@ +=begin +File: array_hash_map.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 鍵值對 ### +class Pair + attr_accessor :key, :val + + def initialize(key, val) + @key = key + @val = val + end +end + +### 基於陣列實現的雜湊表 ### +class ArrayHashMap + ### 建構子 ### + def initialize + # 初始化陣列,包含 100 個桶 + @buckets = Array.new(100) + end + + ### 雜湊函式 ### + def hash_func(key) + index = key % 100 + end + + ### 查詢操作 ### + def get(key) + index = hash_func(key) + pair = @buckets[index] + + return if pair.nil? + pair.val + end + + ### 新增操作 ### + def put(key, val) + pair = Pair.new(key, val) + index = hash_func(key) + @buckets[index] = pair + end + + ### 刪除操作 ### + def remove(key) + index = hash_func(key) + # 置為 nil ,代表刪除 + @buckets[index] = nil + end + + ### 獲取所有鍵值對 ### + def entry_set + result = [] + @buckets.each { |pair| result << pair unless pair.nil? } + result + end + + ### 獲取所有鍵 ### + def key_set + result = [] + @buckets.each { |pair| result << pair.key unless pair.nil? } + result + end + + ### 獲取所有值 ### + def value_set + result = [] + @buckets.each { |pair| result << pair.val unless pair.nil? } + result + end + + ### 列印雜湊表 ### + def print + @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化雜湊表 + hmap = ArrayHashMap.new + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + puts "\n新增完成後,雜湊表為\nKey -> Value" + hmap.print + + # 查詢操作 + # 向雜湊表中輸入鍵 key , 得到值 value + name = hmap.get(15937) + puts "\n輸入學號 15937 ,查詢到姓名 #{name}" + + # 刪除操作 + # 在雜湊表中刪除值對 (key, value) + hmap.remove(10583) + puts "\n刪除 10583 後,雜湊表為\nKey -> Value" + hmap.print + + # 走訪雜湊表 + puts "\n走訪鍵值對 Key->Value" + for pair in hmap.entry_set + puts "#{pair.key} -> #{pair.val}" + end + + puts "\n單獨篇走訪鍵 Key" + for key in hmap.key_set + puts key + end + + puts "\n單獨走訪值 Value" + for val in hmap.value_set + puts val + end +end diff --git a/zh-hant/codes/ruby/chapter_hashing/built_in_hash.rb b/zh-hant/codes/ruby/chapter_hashing/built_in_hash.rb new file mode 100644 index 0000000000..28b33f42a6 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_hashing/built_in_hash.rb @@ -0,0 +1,34 @@ +=begin +File: built_in_hash.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### Driver Code ### +if __FILE__ == $0 + num = 3 + hash_num = num.hash + puts "整數 #{num} 的雜湊值為 #{hash_num}" + + bol = true + hash_bol = bol.hash + puts "布林量 #{bol} 的雜湊值為 #{hash_bol}" + + dec = 3.14159 + hash_dec = dec.hash + puts "小數 #{dec} 的雜湊值為 #{hash_dec}" + + str = "Hello 演算法" + hash_str = str.hash + puts "字串 #{str} 的雜湊值為 #{hash_str}" + + tup = [12836, '小哈'] + hash_tup = tup.hash + puts "元組 #{tup} 的雜湊值為 #{hash_tup}" + + obj = ListNode.new(0) + hash_obj = obj.hash + puts "節點物件 #{obj} 的雜湊值為 #{hash_obj}" +end diff --git a/zh-hant/codes/ruby/chapter_hashing/hash_map.rb b/zh-hant/codes/ruby/chapter_hashing/hash_map.rb new file mode 100644 index 0000000000..1a6fcd284e --- /dev/null +++ b/zh-hant/codes/ruby/chapter_hashing/hash_map.rb @@ -0,0 +1,44 @@ +=begin +File: hash_map.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # 初始化雜湊表 + hmap = {} + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + puts "\n新增完成後,雜湊表為\nKey -> Value" + print_hash_map(hmap) + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name = hmap[15937] + puts "\n輸入學號 15937 ,查詢到姓名 #{name}" + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hmap.delete(10583) + puts "\n刪除 10583 後,雜湊表為\nKey -> Value" + print_hash_map(hmap) + + # 走訪雜湊表 + puts "\n走訪鍵值對 Key->Value" + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } + + puts "\n單獨走訪鍵 Key" + hmap.keys.each { |key| puts key } + + puts "\n單獨走訪值 Value" + hmap.values.each { |val| puts val } +end diff --git a/zh-hant/codes/ruby/chapter_hashing/hash_map_chaining.rb b/zh-hant/codes/ruby/chapter_hashing/hash_map_chaining.rb new file mode 100644 index 0000000000..2d7ef73463 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_hashing/hash_map_chaining.rb @@ -0,0 +1,128 @@ +=begin +File: hash_map_chaining.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +### 鍵式位址雜湊表 ### +class HashMapChaining + ### 建構子 ### + def initialize + @size = 0 # 鍵值對數量 + @capacity = 4 # 雜湊表容量 + @load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 + @extend_ratio = 2 # 擴容倍數 + @buckets = Array.new(@capacity) { [] } # 桶陣列 + end + + ### 雜湊函式 ### + def hash_func(key) + key % @capacity + end + + ### 負載因子 ### + def load_factor + @size / @capacity + end + + ### 查詢操作 ### + def get(key) + index = hash_func(key) + bucket = @buckets[index] + # 走訪桶,若找到 key ,則返回對應 val + for pair in bucket + return pair.val if pair.key == key + end + # 若未找到 key , 則返回 nil + nil + end + + ### 新增操作 ### + def put(key, val) + # 當負載因子超過閾值時,執行擴容 + extend if load_factor > @load_thres + index = hash_func(key) + bucket = @buckets[index] + # 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for pair in bucket + if pair.key == key + pair.val = val + return + end + end + # 若無該 key ,則將鍵值對新增至尾部 + pair = Pair.new(key, val) + bucket << pair + @size += 1 + end + + ### 刪除操作 ### + def remove(key) + index = hash_func(key) + bucket = @buckets[index] + # 走訪桶,從中刪除鍵值對 + for pair in bucket + if pair.key == key + bucket.delete(pair) + @size -= 1 + break + end + end + end + + ### 擴容雜湊表 ### + def extend + # 暫存原雜湊表 + buckets = @buckets + # 初始化擴容後的新雜湊表 + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) { [] } + @size = 0 + # 將鍵值對從原雜湊表搬運至新雜湊表 + for bucket in buckets + for pair in bucket + put(pair.key, pair.val) + end + end + end + + ### 列印雜湊表 ### + def print + for bucket in @buckets + res = [] + for pair in bucket + res << "#{pair.key} -> #{pair.val}" + end + pp res + end + end +end + +### Driver Code ### +if __FILE__ == $0 + ### 初始化雜湊表 + hashmap = HashMapChaining.new + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小囉") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鴨") + puts "\n新增完成後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name = hashmap.get(13276) + puts "\n輸入學號 13276 ,查詢到姓名 #{name}" + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hashmap.remove(12836) + puts "\n刪除 12836 後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print +end diff --git a/zh-hant/codes/ruby/chapter_hashing/hash_map_open_addressing.rb b/zh-hant/codes/ruby/chapter_hashing/hash_map_open_addressing.rb new file mode 100644 index 0000000000..c31587eae6 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_hashing/hash_map_open_addressing.rb @@ -0,0 +1,147 @@ +=begin +File: hash_map_open_addressing.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +### 開放定址雜湊表 ### +class HashMapOpenAddressing + TOMBSTONE = Pair.new(-1, '-1') # 刪除標記 + + ### 建構子 ### + def initialize + @size = 0 # 鍵值對數量 + @capacity = 4 # 雜湊表容量 + @load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 + @extend_ratio = 2 # 擴容倍數 + @buckets = Array.new(@capacity) # 桶陣列 + end + + ### 雜湊函式 ### + def hash_func(key) + key % @capacity + end + + ### 負載因子 ### + def load_factor + @size / @capacity + end + + ### 搜尋 key 對應的桶索引 ### + def find_bucket(key) + index = hash_func(key) + first_tombstone = -1 + # 線性探查,當遇到空桶時跳出 + while !@buckets[index].nil? + # 若遇到 key ,返回對應的桶索引 + if @buckets[index].key == key + # 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if first_tombstone != -1 + @buckets[first_tombstone] = @buckets[index] + @buckets[index] = TOMBSTONE + return first_tombstone # 返回移動後的桶索引 + end + return index # 返回桶索引 + end + # 記錄遇到的首個刪除標記 + first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE + # 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % @capacity + end + # 若 key 不存在,則返回新增點的索引 + first_tombstone == -1 ? index : first_tombstone + end + + ### 查詢操作 ### + def get(key) + # 搜尋 key 對應的桶索引 + index = find_bucket(key) + # 若找到鍵值對,則返回對應 val + return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) + # 若鍵值對不存在,則返回 nil + nil + end + + ### 新增操作 ### + def put(key, val) + # 當負載因子超過閾值時,執行擴容 + extend if load_factor > @load_thres + # 搜尋 key 對應的桶索引 + index = find_bucket(key) + # 若找到鍵值對,則覆蓋 val 開返回 + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index].val = val + return + end + # 若鍵值對不存在,則新增該鍵值對 + @buckets[index] = Pair.new(key, val) + @size += 1 + end + + ### 刪除操作 ### + def remove(key) + # 搜尋 key 對應的桶索引 + index = find_bucket(key) + # 若找到鍵值對,則用刪除標記覆蓋它 + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index] = TOMBSTONE + @size -= 1 + end + end + + ### 擴容雜湊表 ### + def extend + # 暫存原雜湊表 + buckets_tmp = @buckets + # 初始化擴容後的新雜湊表 + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) + @size = 0 + # 將鍵值對從原雜湊表搬運至新雜湊表 + for pair in buckets_tmp + put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) + end + end + + ### 列印雜湊表 ### + def print + for pair in @buckets + if pair.nil? + puts "Nil" + elsif pair == TOMBSTONE + puts "TOMBSTONE" + else + puts "#{pair.key} -> #{pair.val}" + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化雜湊表 + hashmap = HashMapOpenAddressing.new + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小囉") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鴨") + puts "\n新增完成後,雜湊表為\nKey -> Value" + hashmap.print + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 val + name = hashmap.get(13276) + puts "\n輸入學號 13276 ,查詢到姓名 #{name}" + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750) + puts "\n刪除 16750 後,雜湊表為\nKey -> Value" + hashmap.print +end diff --git a/zh-hant/codes/ruby/chapter_hashing/simple_hash.rb b/zh-hant/codes/ruby/chapter_hashing/simple_hash.rb new file mode 100644 index 0000000000..c8838a074e --- /dev/null +++ b/zh-hant/codes/ruby/chapter_hashing/simple_hash.rb @@ -0,0 +1,62 @@ +=begin +File: simple_hash.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 加法雜湊 ### +def add_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash += c.ord } + + hash % modulus +end + +### 乘法雜湊 ### +def mul_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = 31 * hash + c.ord } + + hash % modulus +end + +### 互斥或雜湊 ### +def xor_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash ^= c.ord } + + hash % modulus +end + +### 旋轉雜湊 ### +def rot_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } + + hash % modulus +end + +### Driver Code ### +if __FILE__ == $0 + key = "Hello 演算法" + + hash = add_hash(key) + puts "加法雜湊值為 #{hash}" + + hash = mul_hash(key) + puts "乘法雜湊值為 #{hash}" + + hash = xor_hash(key) + puts "互斥或雜湊值為 #{hash}" + + hash = rot_hash(key) + puts "旋轉雜湊值為 #{hash}" +end diff --git a/zh-hant/codes/ruby/chapter_heap/my_heap.rb b/zh-hant/codes/ruby/chapter_heap/my_heap.rb new file mode 100644 index 0000000000..90f61badb4 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_heap/my_heap.rb @@ -0,0 +1,147 @@ +=begin +File: my_heap.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/print_util' + +### 大頂堆積 ### +class MaxHeap + attr_reader :max_heap + + ### 建構子,根據輸入串列建堆積 ### + def initialize(nums) + # 將串列元素原封不動新增進堆積 + @max_heap = nums + # 堆積化除葉節點以外的其他所有節點 + parent(size - 1).downto(0) do |i| + sift_down(i) + end + end + + ### 獲取左子節點的索引 ### + def left(i) + 2 * i + 1 + end + + ### 獲取右子節點的索引 ### + def right(i) + 2 * i + 2 + end + + ### 獲取父節點的索引 ### + def parent(i) + (i - 1) / 2 # 向下整除 + end + + ### 交換元素 ### + def swap(i, j) + @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] + end + + ### 獲取堆積大小 ### + def size + @max_heap.length + end + + ### 判斷堆積是否為空 ### + def is_empty? + size == 0 + end + + ### 訪問堆積頂元素 ### + def peek + @max_heap[0] + end + + ### 元素入堆積 ### + def push(val) + # 新增節點 + @max_heap << val + # 從底至頂堆積化 + sift_up(size - 1) + end + + ### 從節點 i 開始,從底至頂堆積化 ### + def sift_up(i) + loop do + # 獲取節點 i 的父節點 + p = parent(i) + # 當“越過根節點”或“節點無須修復”時,結束堆積化 + break if p < 0 || @max_heap[i] <= @max_heap[p] + # 交換兩節點 + swap(i, p) + # 迴圈向上堆積化 + i = p + end + end + + ### 元素出堆積 ### + def pop + # 判空處理 + raise IndexError, "堆積為空" if is_empty? + # 交換根節點與最右葉節點(交換首元素與尾元素) + swap(0, size - 1) + # 刪除節點 + val = @max_heap.pop + # 從頂至底堆積化 + sift_down(0) + # 返回堆積頂元素 + val + end + + ### 從節點 i 開始,從頂至底堆積化 ### + def sift_down(i) + loop do + # 判斷節點 i, l, r 中值最大的節點,記為 ma + l, r, ma = left(i), right(i), i + ma = l if l < size && @max_heap[l] > @max_heap[ma] + ma = r if r < size && @max_heap[r] > @max_heap[ma] + + # 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + break if ma == i + + # 交換兩節點 + swap(i, ma) + # 迴圈向下堆積化 + i = ma + end + end + + ### 列印堆積(二元樹)### + def __print__ + print_heap(@max_heap) + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化大頂堆積 + max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + puts "\n輸入串列並建堆積後" + max_heap.__print__ + + # 獲取堆積頂元素 + peek = max_heap.peek + puts "\n堆積頂元素為 #{peek}" + + # 元素入堆積 + val = 7 + max_heap.push(val) + puts "\n元素 #{val} 入堆積後" + max_heap.__print__ + + # 堆積頂元素出堆積 + peek = max_heap.pop + puts "\n堆積頂元素 #{peek} 出堆積後" + max_heap.__print__ + + # 獲取堆積大小 + size = max_heap.size + puts "\n堆積元素數量為 #{size}" + + # 判斷堆積是否為空 + is_empty = max_heap.is_empty? + puts "\n堆積是否為空 #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_heap/top_k.rb b/zh-hant/codes/ruby/chapter_heap/top_k.rb new file mode 100644 index 0000000000..fb22062ba5 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_heap/top_k.rb @@ -0,0 +1,64 @@ +=begin +File: top_k.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative "./my_heap" + +### 元素入堆積 ### +def push_min_heap(heap, val) + # 元素取反 + heap.push(-val) +end + +### 元素出堆積 ### +def pop_min_heap(heap) + # 元素取反 + -heap.pop +end + +### 訪問堆積頂元素 ### +def peek_min_heap(heap) + # 元素取反 + -heap.peek +end + +### 取出堆積中元素 ### +def get_min_heap(heap) + # 將堆積中所有元素取反 + heap.max_heap.map { |x| -x } +end + +### 基於堆積查詢陣列中最大的 k 個元素 ### +def top_k_heap(nums, k) + # 初始化小頂堆積 + # 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 + max_heap = MaxHeap.new([]) + + # 將陣列的前 k 個元素入堆積 + for i in 0...k + push_min_heap(max_heap, nums[i]) + end + + # 從第 k+1 個元素開始,保持堆積的長度為 k + for i in k...nums.length + # 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if nums[i] > peek_min_heap(max_heap) + pop_min_heap(max_heap) + push_min_heap(max_heap, nums[i]) + end + end + + get_min_heap(max_heap) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + puts "最大的 #{k} 個元素為" + print_heap(res) +end diff --git a/zh-hant/codes/ruby/chapter_searching/binary_search.rb b/zh-hant/codes/ruby/chapter_searching/binary_search.rb new file mode 100644 index 0000000000..28b25fa533 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_searching/binary_search.rb @@ -0,0 +1,63 @@ +=begin +File: binary_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 二分搜尋(雙閉區間) ### +def binary_search(nums, target) + # 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + i, j = 0, nums.length - 1 + + # 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while i <= j + # 理論上 Ruby 的數字可以無限大(取決於記憶體大小),無須考慮大數越界問題 + m = (i + j) / 2 # 計算中點索引 m + + if nums[m] < target + i = m + 1 # 此情況說明 target 在區間 [m+1, j] 中 + elsif nums[m] > target + j = m - 1 # 此情況說明 target 在區間 [i, m-1] 中 + else + return m # 找到目標元素,返回其索引 + end + end + + -1 # 未找到目標元素,返回 -1 +end + +### 二分搜尋(左閉右開區間) ### +def binary_search_lcro(nums, target) + # 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + i, j = 0, nums.length + + # 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while i < j + # 計算中點索引 m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # 此情況說明 target 在區間 [m+1, j) 中 + elsif nums[m] > target + j = m - 1 # 此情況說明 target 在區間 [i, m) 中 + else + return m # 找到目標元素,返回其索引 + end + end + + -1 # 未找到目標元素,返回 -1 +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分搜尋(雙閉區間) + index = binary_search(nums, target) + puts "目標元素 6 的索引 = #{index}" + + # 二分搜尋(左閉右開區間) + index = binary_search_lcro(nums, target) + puts "目標元素 6 的索引 = #{index}" +end diff --git a/zh-hant/codes/ruby/chapter_searching/binary_search_edge.rb b/zh-hant/codes/ruby/chapter_searching/binary_search_edge.rb new file mode 100644 index 0000000000..e7cfdde299 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_searching/binary_search_edge.rb @@ -0,0 +1,47 @@ +=begin +File: binary_search_edge.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative './binary_search_insertion' + +### 二分搜尋最左一個 target ### +def binary_search_left_edge(nums, target) + # 等價於查詢 target 的插入點 + i = binary_search_insertion(nums, target) + + # 未找到 target ,返回 -1 + return -1 if i == nums.length || nums[i] != target + + i # 找到 target ,返回索引 i +end + +### 二分搜尋最右一個 target ### +def binary_search_right_edge(nums, target) + # 轉化為查詢最左一個 target + 1 + i = binary_search_insertion(nums, target + 1) + + # j 指向最右一個 target ,i 指向首個大於 target 的元素 + j = i - 1 + + # 未找到 target ,返回 -1 + return -1 if j == -1 || nums[j] != target + + j # 找到 target ,返回索引 j +end + +### Driver Code ### +if __FILE__ == $0 + # 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\n陣列 nums = #{nums}" + + # 二分搜尋左邊界和右邊界 + for target in [6, 7] + index = binary_search_left_edge(nums, target) + puts "最左一個元素 #{target} 的索引為 #{index}" + index = binary_search_right_edge(nums, target) + puts "最右一個元素 #{target} 的索引為 #{index}" + end +end diff --git a/zh-hant/codes/ruby/chapter_searching/binary_search_insertion.rb b/zh-hant/codes/ruby/chapter_searching/binary_search_insertion.rb new file mode 100644 index 0000000000..7928c5a267 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_searching/binary_search_insertion.rb @@ -0,0 +1,68 @@ +=begin +File: binary_search_insertion.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 二分搜尋插入點(無重複元素) ### +def binary_search_insertion_simple(nums, target) + # 初始化雙閉區間 [0, n-1] + i, j = 0, nums.length - 1 + + while i <= j + # 計算中點索引 m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target 在區間 [m+1, j] 中 + elsif nums[m] > target + j = m - 1 # target 在區間 [i, m-1] 中 + else + return m # 找到 target ,返回插入點 m + end + end + + i # 未找到 target ,返回插入點 i +end + +### 二分搜尋插入點(存在重複元素) ### +def binary_search_insertion(nums, target) + # 初始化雙閉區間 [0, n-1] + i, j = 0, nums.length - 1 + + while i <= j + # 計算中點索引 m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target 在區間 [m+1, j] 中 + elsif nums[m] > target + j = m - 1 # target 在區間 [i, m-1] 中 + else + j = m - 1 # 首個小於 target 的元素在區間 [i, m-1] 中 + end + end + + i # 返回插入點 i +end + +### Driver Code ### +if __FILE__ == $0 + # 無重複元素的陣列 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + puts "\n陣列 nums = #{nums}" + # 二分搜尋插入點 + for target in [6, 9] + index = binary_search_insertion_simple(nums, target) + puts "元素 #{target} 的插入點的索引為 #{index}" + end + + # 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\n陣列 nums = #{nums}" + # 二分搜尋插入點 + for target in [2, 6, 20] + index = binary_search_insertion(nums, target) + puts "元素 #{target} 的插入點的索引為 #{index}" + end +end diff --git a/zh-hant/codes/ruby/chapter_searching/hashing_search.rb b/zh-hant/codes/ruby/chapter_searching/hashing_search.rb new file mode 100644 index 0000000000..33543e91ab --- /dev/null +++ b/zh-hant/codes/ruby/chapter_searching/hashing_search.rb @@ -0,0 +1,47 @@ +=begin +File: hashing_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +### 雜湊查詢(陣列) ### +def hashing_search_array(hmap, target) + # 雜湊表的 key: 目標元素,value: 索引 + # 若雜湊表中無此 key ,返回 -1 + hmap[target] || -1 +end + +### 雜湊查詢(鏈結串列) ### +def hashing_search_linkedlist(hmap, target) + # 雜湊表的 key: 目標元素,value: 節點物件 + # 若雜湊表中無此 key ,返回 None + hmap[target] || nil +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # 雜湊查詢(陣列) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # 初始化雜湊表 + map0 = {} + for i in 0...nums.length + map0[nums[i]] = i # key: 元素,value: 索引 + end + index = hashing_search_array(map0, target) + puts "目標元素 3 的索引 = #{index}" + + # 雜湊查詢(鏈結串列) + head = arr_to_linked_list(nums) + # 初始化雜湊表 + map1 = {} + while head + map1[head.val] = head + head = head.next + end + node = hashing_search_linkedlist(map1, target) + puts "目標節點值 3 的對應節點物件為 #{node}" +end diff --git a/zh-hant/codes/ruby/chapter_searching/linear_search.rb b/zh-hant/codes/ruby/chapter_searching/linear_search.rb new file mode 100644 index 0000000000..2957f49d12 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_searching/linear_search.rb @@ -0,0 +1,44 @@ +=begin +File: linear_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +### 線性查詢(陣列) ### +def linear_search_array(nums, target) + # 走訪陣列 + for i in 0...nums.length + return i if nums[i] == target # 找到目標元素,返回其索引 + end + + -1 # 未找到目標元素,返回 -1 +end + +### 線性查詢(鏈結串列) ### +def linear_search_linkedlist(head, target) + # 走訪鏈結串列 + while head + return head if head.val == target # 找到目標節點,返回之 + + head = head.next + end + + nil # 未找到目標節點,返回 None +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # 在陣列中執行線性查詢 + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index = linear_search_array(nums, target) + puts "目標元素 3 的索引 = #{index}" + + # 在鏈結串列中執行線性查詢 + head = arr_to_linked_list(nums) + node = linear_search_linkedlist(head, target) + puts "目標節點值 3 的對應節點物件為 #{node}" +end diff --git a/zh-hant/codes/ruby/chapter_searching/two_sum.rb b/zh-hant/codes/ruby/chapter_searching/two_sum.rb new file mode 100644 index 0000000000..e1295a300c --- /dev/null +++ b/zh-hant/codes/ruby/chapter_searching/two_sum.rb @@ -0,0 +1,46 @@ +=begin +File: two_sum.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 方法一:暴力列舉 ### +def two_sum_brute_force(nums, target) + # 兩層迴圈,時間複雜度為 O(n^2) + for i in 0...(nums.length - 1) + for j in (i + 1)...nums.length + return [i, j] if nums[i] + nums[j] == target + end + end + + [] +end + +### 方法二:輔助雜湊表 ### +def two_sum_hash_table(nums, target) + # 輔助雜湊表,空間複雜度為 O(n) + dic = {} + # 單層迴圈,時間複雜度為 O(n) + for i in 0...nums.length + return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) + + dic[nums[i]] = i + end + + [] +end + +### Driver Code ### +if __FILE__ == $0 + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Driver Code ====== + # 方法一 + res = two_sum_brute_force(nums, target) + puts "方法一 res = #{res}" + # 方法二 + res = two_sum_hash_table(nums, target) + puts "方法二 res = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/bubble_sort.rb b/zh-hant/codes/ruby/chapter_sorting/bubble_sort.rb new file mode 100644 index 0000000000..bdd52c751e --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/bubble_sort.rb @@ -0,0 +1,51 @@ +=begin +File: bubble_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 泡沫排序 ### +def bubble_sort(nums) + n = nums.length + # 外迴圈:未排序區間為 [0, i] + for i in (n - 1).downto(1) + # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0...i + if nums[j] > nums[j + 1] + # 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + end + end + end +end + +### 泡沫排序(標誌最佳化)### +def bubble_sort_with_flag(nums) + n = nums.length + # 外迴圈:未排序區間為 [0, i] + for i in (n - 1).downto(1) + flag = false # 初始化標誌位 + + # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0...i + if nums[j] > nums[j + 1] + # 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = true # 記錄交換元素 + end + end + + break unless flag # 此輪“冒泡”未交換任何元素,直接跳出 + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + puts "泡沫排序完成後 nums = #{nums}" + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + puts "泡沫排序完成後 nums = #{nums1}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/bucket_sort.rb b/zh-hant/codes/ruby/chapter_sorting/bucket_sort.rb new file mode 100644 index 0000000000..5ce98050b5 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/bucket_sort.rb @@ -0,0 +1,43 @@ +=begin +File: bucket_sort.rb +Created Time: 2024-04-17 +Author: Martin Xu (martin.xus@gmail.com) +=end + +### 桶排序 ### +def bucket_sort(nums) + # 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + k = nums.length / 2 + buckets = Array.new(k) { [] } + + # 1. 將陣列元素分配到各個桶中 + nums.each do |num| + # 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + i = (num * k).to_i + # 將 num 新增進桶 i + buckets[i] << num + end + + # 2. 對各個桶執行排序 + buckets.each do |bucket| + # 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort! + end + + # 3. 走訪桶合併結果 + i = 0 + buckets.each do |bucket| + bucket.each do |num| + nums[i] = num + i += 1 + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 設輸入資料為浮點數,範圍為 [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + puts "桶排序完成後 nums = #{nums}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/counting_sort.rb b/zh-hant/codes/ruby/chapter_sorting/counting_sort.rb new file mode 100644 index 0000000000..b0cae6bd97 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/counting_sort.rb @@ -0,0 +1,62 @@ +=begin +File: counting_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 計數排序 ### +def counting_sort_naive(nums) + # 簡單實現,無法用於排序物件 + # 1. 統計陣列最大元素 m + m = 0 + nums.each { |num| m = [m, num].max } + # 2. 統計各數字的出現次數 + # counter[num] 代表 num 的出現次數 + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. 走訪 counter ,將各元素填入原陣列 nums + i = 0 + for num in 0...(m + 1) + (0...counter[num]).each do + nums[i] = num + i += 1 + end + end +end + +### 計數排序 ### +def counting_sort(nums) + # 完整實現,可排序物件,並且是穩定排序 + # 1. 統計陣列最大元素 m + m = nums.max + # 2. 統計各數字的出現次數 + # counter[num] 代表 num 的出現次數 + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + # 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + (0...m).each { |i| counter[i + 1] += counter[i] } + # 4. 倒序走訪 nums, 將各元素填入結果陣列 res + # 初始化陣列 res 用於記錄結果 + n = nums.length + res = Array.new(n, 0) + (n - 1).downto(0).each do |i| + num = nums[i] + res[counter[num] - 1] = num # 將 num 放置到對應索引處 + counter[num] -= 1 # 令前綴和自減 1 ,得到下次放置 num 的索引 + end + # 使用結果陣列 res 覆蓋原陣列 nums + (0...n).each { |i| nums[i] = res[i] } +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + puts "計數排序(無法排序物件)完成後 nums = #{nums}" + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + puts "計數排序完成後 nums1 = #{nums1}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/heap_sort.rb b/zh-hant/codes/ruby/chapter_sorting/heap_sort.rb new file mode 100644 index 0000000000..d562ba9b4b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/heap_sort.rb @@ -0,0 +1,45 @@ +=begin +File: heap_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +### 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 ### +def sift_down(nums, n, i) + while true + # 判斷節點 i, l, r 中值最大的節點,記為 ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + ma = l if l < n && nums[l] > nums[ma] + ma = r if r < n && nums[r] > nums[ma] + # 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + break if ma == i + # 交換兩節點 + nums[i], nums[ma] = nums[ma], nums[i] + # 迴圈向下堆積化 + i = ma + end +end + +### 堆積排序 ### +def heap_sort(nums) + # 建堆積操作:堆積化除葉節點以外的其他所有節點 + (nums.length / 2 - 1).downto(0) do |i| + sift_down(nums, nums.length, i) + end + # 從堆積中提取最大元素,迴圈 n-1 輪 + (nums.length - 1).downto(1) do |i| + # 交換根節點與最右葉節點(交換首元素與尾元素) + nums[0], nums[i] = nums[i], nums[0] + # 以根節點為起點,從頂至底進行堆積化 + sift_down(nums, i, 0) + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + puts "堆積排序完成後 nums = #{nums.inspect}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/insertion_sort.rb b/zh-hant/codes/ruby/chapter_sorting/insertion_sort.rb new file mode 100644 index 0000000000..b63a518c51 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/insertion_sort.rb @@ -0,0 +1,26 @@ +=begin +File: insertion_sort.rb +Created Time: 2024-04-02 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 插入排序 ### +def insertion_sort(nums) + n = nums.length + # 外迴圈:已排序區間為 [0, i-1] + for i in 1...n + base = nums[i] + j = i - 1 + # 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while j >= 0 && nums[j] > base + nums[j + 1] = nums[j] # 將 nums[j] 向右移動一位 + j -= 1 + end + nums[j + 1] = base # 將 base 賦值到正確位置 + end +end + +### Driver Code ### +nums = [4, 1, 3, 1, 5, 2] +insertion_sort(nums) +puts "插入排序完成後 nums = #{nums}" diff --git a/zh-hant/codes/ruby/chapter_sorting/merge_sort.rb b/zh-hant/codes/ruby/chapter_sorting/merge_sort.rb new file mode 100644 index 0000000000..5316bb0987 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/merge_sort.rb @@ -0,0 +1,60 @@ +=begin +File: merge_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +### 合併左子陣列和右子陣列 ### +def merge(nums, left, mid, right) + # 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + # 建立一個臨時陣列 tmp,用於存放合併後的結果 + tmp = Array.new(right - left + 1, 0) + # 初始化左子陣列和右子陣列的起始索引 + i, j, k = left, mid + 1, 0 + # 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while i <= mid && j <= right + if nums[i] <= nums[j] + tmp[k] = nums[i] + i += 1 + else + tmp[k] = nums[j] + j += 1 + end + k += 1 + end + # 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while i <= mid + tmp[k] = nums[i] + i += 1 + k += 1 + end + while j <= right + tmp[k] = nums[j] + j += 1 + k += 1 + end + # 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + (0...tmp.length).each do |k| + nums[left + k] = tmp[k] + end +end + +### 合併排序 ### +def merge_sort(nums, left, right) + # 終止條件 + # 當子陣列長度為 1 時終止遞迴 + return if left >= right + # 劃分階段 + mid = left + (right - left) / 2 # 計算中點 + merge_sort(nums, left, mid) # 遞迴左子陣列 + merge_sort(nums, mid + 1, right) # 遞迴右子陣列 + # 合併階段 + merge(nums, left, mid, right) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, nums.length - 1) + puts "合併排序完成後 nums = #{nums.inspect}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/quick_sort.rb b/zh-hant/codes/ruby/chapter_sorting/quick_sort.rb new file mode 100644 index 0000000000..221a17715c --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/quick_sort.rb @@ -0,0 +1,153 @@ +=begin +File: quick_sort.rb +Created Time: 2024-04-01 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 快速排序類別 ### +class QuickSort + class << self + ### 哨兵劃分 ### + def partition(nums, left, right) + # 以 nums[left] 為基準數 + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 從右向左找首個小於基準數的元素 + end + while i < j && nums[i] <= nums[left] + i += 1 # 從左向右找首個大於基準數的元素 + end + # 元素交換 + nums[i], nums[j] = nums[j], nums[i] + end + # 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + i # 返回基準數的索引 + end + + ### 快速排序類別 ### + def quick_sort(nums, left, right) + # 子陣列長度不為 1 時遞迴 + if left < right + # 哨兵劃分 + pivot = partition(nums, left, right) + # 遞迴左子陣列、右子陣列 + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +### 快速排序類別(中位數最佳化)### +class QuickSortMedian + class << self + ### 選取三個候選元素的中位數 ### + def median_three(nums, left, mid, right) + # 選取三個候選元素的中位數 + _l, _m, _r = nums[left], nums[mid], nums[right] + # m 在 l 和 r 之間 + return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) + # l 在 m 和 r 之間 + return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) + return right + end + + ### 哨兵劃分(三數取中值)### + def partition(nums, left, right) + ### 以 nums[left] 為基準數 + med = median_three(nums, left, (left + right) / 2, right) + # 將中位數交換至陣列最左斷 + nums[left], nums[med] = nums[med], nums[left] + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 從右向左找首個小於基準數的元素 + end + while i < j && nums[i] <= nums[left] + i += 1 # 從左向右找首個大於基準數的元素 + end + # 元素交換 + nums[i], nums[j] = nums[j], nums[i] + end + # 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + i # 返回基準數的索引 + end + + ### 快速排序 ### + def quick_sort(nums, left, right) + # 子陣列長度不為 1 時遞迴 + if left < right + # 哨兵劃分 + pivot = partition(nums, left, right) + # 遞迴左子陣列、右子陣列 + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +### 快速排序類別(尾遞迴最佳化)### +class QuickSortTailCall + class << self + ### 哨兵劃分 ### + def partition(nums, left, right) + # 以 nums[left]為基準數 + i = left + j = right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 從右向左找首個小於基準數的元素 + end + while i < j && nums[i] <= nums[left] + i += 1 # 從左向右找首個大於基準數的元素 + end + # 元素交換 + nums[i], nums[j] = nums[j], nums[i] + end + # 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + i # 返回基準數的索引 + end + + ### 快速排序(尾遞迴最佳化)### + def quick_sort(nums, left, right) + # 子陣列長度不為 1 時遞迴 + while left < right + # 哨兵劃分 + pivot = partition(nums, left, right) + # 對兩個子陣列中較短的那個執行快速排序 + if pivot - left < right - pivot + quick_sort(nums, left, pivot - 1) + left = pivot + 1 # 剩餘未排序區間為 [pivot + 1, right] + else + quick_sort(nums, pivot + 1, right) + right = pivot - 1 # 剩餘未排序區間為 [left, pivot - 1] + end + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 快速排序 + nums = [2, 4, 1, 0, 3, 5] + QuickSort.quick_sort(nums, 0, nums.length - 1) + puts "快速排序完成後 nums = #{nums}" + + # 快速排序(中位基準數最佳化) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) + puts "快速排序(中位基準數最佳化)完成後 nums1 = #{nums1}" + + # 快速排序(尾遞迴最佳化) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) + puts "快速排序(尾遞迴最佳化)完成後 nums2 = #{nums2}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/radix_sort.rb b/zh-hant/codes/ruby/chapter_sorting/radix_sort.rb new file mode 100644 index 0000000000..b2b5350e5f --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/radix_sort.rb @@ -0,0 +1,70 @@ +=begin +File: radix_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) ### +def digit(num, exp) + # 轉入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + (num / exp) % 10 +end + +### 計數排序(根據 nums 第 k 位排序)### +def counting_sort_digit(nums, exp) + # 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + counter = Array.new(10, 0) + n = nums.length + # 統計 0~9 各數字的出現次數 + for i in 0...n + d = digit(nums[i], exp) # 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1 # 統計數字 d 的出現次數 + end + # 求前綴和,將“出現個數”轉換為“陣列索引” + (1...10).each { |i| counter[i] += counter[i - 1] } + # 倒序走訪,根據桶內統計結果,將各元素填入 res + res = Array.new(n, 0) + for i in (n - 1).downto(0) + d = digit(nums[i], exp) + j = counter[d] - 1 # 獲取 d 在陣列中的索引 j + res[j] = nums[i] # 將當前元素填入索引 j + counter[d] -= 1 # 將 d 的數量減 1 + end + # 使用結果覆蓋原陣列 nums + (0...n).each { |i| nums[i] = res[i] } +end + +### 基數排序 ### +def radix_sort(nums) + # 獲取陣列的最大元素,用於判斷最大位數 + m = nums.max + # 按照從低位到高位的順序走訪 + exp = 1 + while exp <= m + # 對陣列元素的第 k 位執行計數排序 + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # 即 exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + end +end + +### Driver Code ### +if __FILE__ == $0 + # 基數排序 + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + puts "基數排序完成後 nums = #{nums}" +end diff --git a/zh-hant/codes/ruby/chapter_sorting/selection_sort.rb b/zh-hant/codes/ruby/chapter_sorting/selection_sort.rb new file mode 100644 index 0000000000..48a2a9fff6 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_sorting/selection_sort.rb @@ -0,0 +1,29 @@ +=begin +File: selection_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 選擇排序 ### +def selection_sort(nums) + n = nums.length + # 外迴圈:未排序區間為 [i, n-1] + for i in 0...(n - 1) + # 內迴圈:找到未排序區間內的最小元素 + k = i + for j in (i + 1)...n + if nums[j] < nums[k] + k = j # 記錄最小元素的索引 + end + end + # 將該最小元素與未排序區間的首個元素交換 + nums[i], nums[k] = nums[k], nums[i] + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + puts "選擇排序完成後 nums = #{nums}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/array_deque.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/array_deque.rb new file mode 100644 index 0000000000..f06ba0ee4b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/array_deque.rb @@ -0,0 +1,145 @@ +=begin +File: array_deque.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 基於環形陣列實現的雙向佇列 ### +class ArrayDeque + ### 獲取雙向佇列的長度 ### + attr_reader :size + + ### 建構子 ### + def initialize(capacity) + @nums = Array.new(capacity, 0) + @front = 0 + @size = 0 + end + + ### 獲取雙向佇列的容量 ### + def capacity + @nums.length + end + + ### 判斷雙向佇列是否為空 ### + def is_empty? + size.zero? + end + + ### 佇列首入列 ### + def push_first(num) + if size == capacity + puts '雙向佇列已滿' + return + end + + # 佇列首指標向左移動一位 + # 透過取餘操作實現 front 越過陣列頭部後回到尾部 + @front = index(@front - 1) + # 將 num 新增至佇列首 + @nums[@front] = num + @size += 1 + end + + ### 佇列尾入列 ### + def push_last(num) + if size == capacity + puts '雙向佇列已滿' + return + end + + # 計算佇列尾指標,指向佇列尾索引 + 1 + rear = index(@front + size) + # 將 num 新增至佇列尾 + @nums[rear] = num + @size += 1 + end + + ### 佇列首出列 ### + def pop_first + num = peek_first + # 佇列首指標向後移動一位 + @front = index(@front + 1) + @size -= 1 + num + end + + ### 佇列尾出列 ### + def pop_last + num = peek_last + @size -= 1 + num + end + + ### 訪問佇列首元素 ### + def peek_first + raise IndexError, '雙向佇列為空' if is_empty? + + @nums[@front] + end + + ### 訪問佇列尾元素 ### + def peek_last + raise IndexError, '雙向佇列為空' if is_empty? + + # 計算尾元素索引 + last = index(@front + size - 1) + @nums[last] + end + + ### 返回陣列用於列印 ### + def to_array + # 僅轉換有效長度範圍內的串列元素 + res = [] + for i in 0...size + res << @nums[index(@front + i)] + end + res + end + + private + + ### 計算環形陣列索引 ### + def index(i) + # 透過取餘操作實現陣列首尾相連 + # 當 i 越過陣列尾部後,回到頭部 + # 當 i 越過陣列頭部後,回到尾部 + (i + capacity) % capacity + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化雙向佇列 + deque = ArrayDeque.new(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "雙向佇列 deque = #{deque.to_array}" + + # 訪問元素 + peek_first = deque.peek_first + puts "佇列首元素 peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "佇列尾元素 peek_last = #{peek_last}" + + # 元素入列 + deque.push_last(4) + puts "元素 4 佇列尾入列後 deque = #{deque.to_array}" + deque.push_first(1) + puts "元素 1 佇列尾入列後 deque = #{deque.to_array}" + + # 元素出列 + pop_last = deque.pop_last + puts "佇列尾出列元素 = #{pop_last},佇列尾出列後 deque = #{deque.to_array}" + pop_first = deque.pop_first + puts "佇列尾出列元素 = #{pop_first},佇列尾出列後 deque = #{deque.to_array}" + + # 獲取雙向佇列的長度 + size = deque.size + puts "雙向佇列長度 size = #{size}" + + # 判斷雙向佇列是否為空 + is_empty = deque.is_empty? + puts "雙向佇列是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/array_queue.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/array_queue.rb new file mode 100644 index 0000000000..eb05d55dfc --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/array_queue.rb @@ -0,0 +1,107 @@ +=begin +File: array_queue.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 基於環形陣列實現的佇列 ### +class ArrayQueue + ### 獲取佇列的長度 ### + attr_reader :size + + ### 建構子 ### + def initialize(size) + @nums = Array.new(size, 0) # 用於儲存佇列元素的陣列 + @front = 0 # 佇列首指標,指向佇列首元素 + @size = 0 # 佇列長度 + end + + ### 獲取佇列的容量 ### + def capacity + @nums.length + end + + ### 判斷佇列是否為空 ### + def is_empty? + size.zero? + end + + ### 入列 ### + def push(num) + raise IndexError, '佇列已滿' if size == capacity + + # 計算佇列尾指標,指向佇列尾索引 + 1 + # 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + rear = (@front + size) % capacity + # 將 num 新增至佇列尾 + @nums[rear] = num + @size += 1 + end + + ### 出列 ### + def pop + num = peek + # 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + @front = (@front + 1) % capacity + @size -= 1 + num + end + + ### 訪問佇列首元素 ### + def peek + raise IndexError, '佇列為空' if is_empty? + + @nums[@front] + end + + ### 返回串列用於列印 ### + def to_array + res = Array.new(size, 0) + j = @front + + for i in 0...size + res[i] = @nums[j % capacity] + j += 1 + end + + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化佇列 + queue = ArrayQueue.new(10) + + # 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "佇列 queue = #{queue.to_array}" + + # 訪問佇列首元素 + peek = queue.peek + puts "佇列首元素 peek = #{peek}" + + # 元素出列 + pop = queue.pop + puts "出列元素 pop = #{pop}" + puts "出列後 queue = #{queue.to_array}" + + # 獲取佇列的長度 + size = queue.size + puts "佇列長度 size = #{size}" + + # 判斷佇列是否為空 + is_empty = queue.is_empty? + puts "佇列是否為空 = #{is_empty}" + + # 測試環形陣列 + for i in 0...10 + queue.push(i) + queue.pop + puts "第 #{i} 輪入列 + 出列後 queue = #{queue.to_array}" + end +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/array_stack.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/array_stack.rb new file mode 100644 index 0000000000..c3e68f0c41 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/array_stack.rb @@ -0,0 +1,78 @@ +=begin +File: array_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 基於陣列實現的堆疊 ### +class ArrayStack + ### 建構子 ### + def initialize + @stack = [] + end + + ### 獲取堆疊的長度 ### + def size + @stack.length + end + + ### 判斷堆疊是否為空 ### + def is_empty? + @stack.empty? + end + + ### 入堆疊 ### + def push(item) + @stack << item + end + + ### 出堆疊 ### + def pop + raise IndexError, '堆疊為空' if is_empty? + + @stack.pop + end + + ### 訪問堆疊頂元素 ### + def peek + raise IndexError, '堆疊為空' if is_empty? + + @stack.last + end + + ### 返回串列用於列印 ### + def to_array + @stack + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化堆疊 + stack = ArrayStack.new + + # 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "堆疊 stack = #{stack.to_array}" + + # 訪問堆疊頂元素 + peek = stack.peek + puts "堆疊頂元素 peek = #{peek}" + + # 元素出堆疊 + pop = stack.pop + puts "出堆疊元素 pop = #{pop}" + puts "出堆疊後 stack = #{stack.to_array}" + + # 獲取堆疊的長度 + size = stack.size + puts "堆疊的長度 size = #{size}" + + # 判斷是否為空 + is_empty = stack.is_empty? + puts "堆疊是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/deque.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/deque.rb new file mode 100644 index 0000000000..980bcace89 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/deque.rb @@ -0,0 +1,42 @@ +=begin +File: deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化雙向佇列 + # Ruby 沒有內直的雙端佇列,只能把 Array 當作雙端佇列來使用 + deque = [] + + # 元素如隊 + deque << 2 + deque << 5 + deque << 4 + # 請注意,由於是陣列,Array#unshift 方法的時間複雜度為 O(n) + deque.unshift(3) + deque.unshift(1) + puts "雙向佇列 deque = #{deque}" + + # 訪問元素 + peek_first = deque.first + puts "佇列首元素 peek_first = #{peek_first}" + peek_last = deque.last + puts "佇列尾元素 peek_last = #{peek_last}" + + # 元素出列 + # 請注意,由於是陣列, Array#shift 方法的時間複雜度為 O(n) + pop_front = deque.shift + puts "佇列首出列元素 pop_front = #{pop_front},佇列首出列後 deque = #{deque}" + pop_back = deque.pop + puts "佇列尾出列元素 pop_back = #{pop_back}, 佇列尾出列後 deque = #{deque}" + + # 獲取雙向佇列的長度 + size = deque.length + puts "雙向佇列長度 size = #{size}" + + # 判斷雙向佇列是否為空 + is_empty = size.zero? + puts "雙向佇列是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb new file mode 100644 index 0000000000..6bad53f1ac --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb @@ -0,0 +1,168 @@ +=begin +File: linkedlist_deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 雙向鏈結串列節點 +class ListNode + attr_accessor :val + attr_accessor :next # 後繼節點引用 + attr_accessor :prev # 前軀節點引用 + + ### 建構子 ### + def initialize(val) + @val = val + end +end + +### 基於雙向鏈結串列實現的雙向佇列 ### +class LinkedListDeque + ### 獲取雙向佇列的長度 ### + attr_reader :size + + ### 建構子 ### + def initialize + @front = nil # 頭節點 front + @rear = nil # 尾節點 rear + @size = 0 # 雙向佇列的長度 + end + + ### 判斷雙向佇列是否為空 ### + def is_empty? + size.zero? + end + + ### 入列操作 ### + def push(num, is_front) + node = ListNode.new(num) + # 若鏈結串列為空, 則令 front 和 rear 都指向 node + if is_empty? + @front = @rear = node + # 佇列首入列操作 + elsif is_front + # 將 node 新增至鏈結串列頭部 + @front.prev = node + node.next = @front + @front = node # 更新頭節點 + # 佇列尾入列操作 + else + # 將 node 新增至鏈結串列尾部 + @rear.next = node + node.prev = @rear + @rear = node # 更新尾節點 + end + @size += 1 # 更新佇列長度 + end + + ### 佇列首入列 ### + def push_first(num) + push(num, true) + end + + ### 佇列尾入列 ### + def push_last(num) + push(num, false) + end + + ### 出列操作 ### + def pop(is_front) + raise IndexError, '雙向佇列為空' if is_empty? + + # 佇列首出列操作 + if is_front + val = @front.val # 暫存頭節點值 + # 刪除頭節點 + fnext = @front.next + unless fnext.nil? + fnext.prev = nil + @front.next = nil + end + @front = fnext # 更新頭節點 + # 佇列尾出列操作 + else + val = @rear.val # 暫存尾節點值 + # 刪除尾節點 + rprev = @rear.prev + unless rprev.nil? + rprev.next = nil + @rear.prev = nil + end + @rear = rprev # 更新尾節點 + end + @size -= 1 # 更新佇列長度 + + val + end + + ### 佇列首出列 ### + def pop_first + pop(true) + end + + ### 佇列首出列 ### + def pop_last + pop(false) + end + + ### 訪問佇列首元素 ### + def peek_first + raise IndexError, '雙向佇列為空' if is_empty? + + @front.val + end + + ### 訪問佇列尾元素 ### + def peek_last + raise IndexError, '雙向佇列為空' if is_empty? + + @rear.val + end + + ### 返回陣列用於列印 ### + def to_array + node = @front + res = Array.new(size, 0) + for i in 0...size + res[i] = node.val + node = node.next + end + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化雙向佇列 + deque = LinkedListDeque.new + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "雙向佇列 deque = #{deque.to_array}" + + # 訪問元素 + peek_first = deque.peek_first + puts "佇列首元素 peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "佇列首元素 peek_last = #{peek_last}" + + # 元素入列 + deque.push_last(4) + puts "元素 4 佇列尾入列後 deque = #{deque.to_array}" + deque.push_first(1) + puts "元素 1 佇列首入列後 deque = #{deque.to_array}" + + # 元素出列 + pop_last = deque.pop_last + puts "佇列尾出列元素 = #{pop_last}, 佇列尾出列後 deque = #{deque.to_array}" + pop_first = deque.pop_first + puts "佇列首出列元素 = #{pop_first},佇列首出列後 deque = #{deque.to_array}" + + # 獲取雙向佇列的長度 + size = deque.size + puts "雙向佇列長度 size = #{size}" + + # 判斷雙向佇列是否為空 + is_empty = deque.is_empty? + puts "雙向佇列是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb new file mode 100644 index 0000000000..dd0aa135d9 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb @@ -0,0 +1,101 @@ +=begin +File: linkedlist_queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### 基於鏈結串列頭現的佇列 ### +class LinkedListQueue + ### 獲取佇列的長度 ### + attr_reader :size + + ### 建構子 ### + def initialize + @front = nil # 頭節點 front + @rear = nil # 尾節點 rear + @size = 0 + end + + ### 判斷佇列是否為空 ### + def is_empty? + @front.nil? + end + + ### 入列 ### + def push(num) + # 在尾節點後新增 num + node = ListNode.new(num) + + # 如果佇列為空,則令頭,尾節點都指向該節點 + if @front.nil? + @front = node + @rear = node + # 如果佇列不為空,則令該節點新增到尾節點後 + else + @rear.next = node + @rear = node + end + + @size += 1 + end + + ### 出列 ### + def pop + num = peek + # 刪除頭節點 + @front = @front.next + @size -= 1 + num + end + + ### 訪問佇列首元素 ### + def peek + raise IndexError, '佇列為空' if is_empty? + + @front.val + end + + ### 將鏈結串列為 Array 並返回 ### + def to_array + queue = [] + temp = @front + while temp + queue << temp.val + temp = temp.next + end + queue + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化佇列 + queue = LinkedListQueue.new + + # 元素如隊 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "佇列 queue = #{queue.to_array}" + + # 訪問佇列首元素 + peek = queue.peek + puts "佇列首元素 front = #{peek}" + + # 元素出列 + pop_front = queue.pop + puts "出列元素 pop = #{pop_front}" + puts "出列後 queue = #{queue.to_array}" + + # 獲取佇列的長度 + size = queue.size + puts "佇列長度 size = #{size}" + + # 判斷佇列是否為空 + is_empty = queue.is_empty? + puts "佇列是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb new file mode 100644 index 0000000000..758ca9771b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb @@ -0,0 +1,87 @@ +=begin +File: linkedlist_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### 基於鏈結串列實現的堆疊 ### +class LinkedListStack + attr_reader :size + + ### 建構子 ### + def initialize + @size = 0 + end + + ### 判斷堆疊是否為空 ### + def is_empty? + @peek.nil? + end + + ### 入堆疊 ### + def push(val) + node = ListNode.new(val) + node.next = @peek + @peek = node + @size += 1 + end + + ### 出堆疊 ### + def pop + num = peek + @peek = @peek.next + @size -= 1 + num + end + + ### 訪問堆疊頂元素 ### + def peek + raise IndexError, '堆疊為空' if is_empty? + + @peek.val + end + + ### 將鏈結串列轉化為 Array 並反回 ### + def to_array + arr = [] + node = @peek + while node + arr << node.val + node = node.next + end + arr.reverse + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化堆疊 + stack = LinkedListStack.new + + # 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "堆疊 stack = #{stack.to_array}" + + # 訪問堆疊頂元素 + peek = stack.peek + puts "堆疊頂元素 peek = #{peek}" + + # 元素出堆疊 + pop = stack.pop + puts "出堆疊元素 pop = #{pop}" + puts "出堆疊後 stack = #{stack.to_array}" + + # 獲取堆疊的長度 + size = stack.size + puts "堆疊的長度 size = #{size}" + + # 判斷是否為空 + is_empty = stack.is_empty? + puts "堆疊是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/queue.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/queue.rb new file mode 100644 index 0000000000..bb3b0ab23a --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/queue.rb @@ -0,0 +1,38 @@ +=begin +File: queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化佇列 + # Ruby 內建的佇列(Thread::Queue) 沒有 peek 和走訪方法,可以把 Array 當作佇列來使用 + queue = [] + + # 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "佇列 queue = #{queue}" + + # 訪問佇列元素 + peek = queue.first + puts "佇列首元素 peek = #{peek}" + + # 元素出列 + # 清注意,由於是陣列,Array#shift 方法時間複雜度為 O(n) + pop = queue.shift + puts "出列元素 pop = #{pop}" + puts "出列後 queue = #{queue}" + + # 獲取佇列的長度 + size = queue.length + puts "佇列長度 size = #{size}" + + # 判斷佇列是否為空 + is_empty = queue.empty? + puts "佇列是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_stack_and_queue/stack.rb b/zh-hant/codes/ruby/chapter_stack_and_queue/stack.rb new file mode 100644 index 0000000000..5a439623b3 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_stack_and_queue/stack.rb @@ -0,0 +1,37 @@ +=begin +File: stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 初始化堆疊 + # Ruby 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + stack = [] + + # 元素入堆疊 + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 + puts "堆疊 stack = #{stack}" + + # 訪問堆疊頂元素 + peek = stack.last + puts "堆疊頂元素 peek = #{peek}" + + # 元素出堆疊 + pop = stack.pop + puts "出堆疊元素 pop = #{pop}" + puts "出堆疊後 stack = #{stack}" + + # 獲取堆疊的長度 + size = stack.length + puts "堆疊的長度 size = #{size}" + + # 判斷是否為空 + is_empty = stack.empty? + puts "堆疊是否為空 = #{is_empty}" +end diff --git a/zh-hant/codes/ruby/chapter_tree/array_binary_tree.rb b/zh-hant/codes/ruby/chapter_tree/array_binary_tree.rb new file mode 100644 index 0000000000..d200c15127 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_tree/array_binary_tree.rb @@ -0,0 +1,124 @@ +=begin +File: array_binary_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 陣列表示下的二元樹類別 ### +class ArrayBinaryTree + ### 建構子 ### + def initialize(arr) + @tree = arr.to_a + end + + ### 串列容量 ### + def size + @tree.length + end + + ### 獲取索引為 i 節點的值 ### + def val(i) + # 若索引越界,則返回 nil ,代表空位 + return if i < 0 || i >= size + + @tree[i] + end + + ### 獲取索引為 i 節點的左子節點的索引 ### + def left(i) + 2 * i + 1 + end + + ### 獲取索引為 i 節點的右子節點的索引 ### + def right(i) + 2 * i + 2 + end + + ### 獲取索引為 i 節點的父節點的索引 ### + def parent(i) + (i - 1) / 2 + end + + ### 層序走訪 ### + def level_order + @res = [] + + # 直接走訪陣列 + for i in 0...size + @res << val(i) unless val(i).nil? + end + + @res + end + + ### 深度優先走訪 ### + def dfs(i, order) + return if val(i).nil? + # 前序走訪 + @res << val(i) if order == :pre + dfs(left(i), order) + # 中序走訪 + @res << val(i) if order == :in + dfs(right(i), order) + # 後序走訪 + @res << val(i) if order == :post + end + + ### 前序走訪 ### + def pre_order + @res = [] + dfs(0, :pre) + @res + end + + ### 中序走訪 ### + def in_order + @res = [] + dfs(0, :in) + @res + end + + ### 後序走訪 ### + def post_order + @res = [] + dfs(0, :post) + @res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二元樹 + # 這裡藉助了一個從陣列直接生成二元樹的函式 + arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + root = arr_to_tree(arr) + puts "\n初始化二元樹\n\n" + puts '二元樹的陣列表示:' + pp arr + puts '二元樹的鏈結串列表示:' + print_tree(root) + + # 陣列表示下的二元樹類別 + abt = ArrayBinaryTree.new(arr) + + # 訪問節點 + i = 1 + l, r, _p = abt.left(i), abt.right(i), abt.parent(i) + puts "\n當前節點的索引為 #{i} ,值為 #{abt.val(i).inspect}" + puts "其左子節點的索引為 #{l} ,值為 #{abt.val(l).inspect}" + puts "其右子節點的索引為 #{r} ,值為 #{abt.val(r).inspect}" + puts "其父節點的索引為 #{_p} ,值為 #{abt.val(_p).inspect}" + + # 走訪樹 + res = abt.level_order + puts "\n層序走訪為: #{res}" + res = abt.pre_order + puts "前序走訪為: #{res}" + res = abt.in_order + puts "中序走訪為: #{res}" + res = abt.post_order + puts "後序走訪為: #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_tree/avl_tree.rb b/zh-hant/codes/ruby/chapter_tree/avl_tree.rb new file mode 100644 index 0000000000..0ab4460fae --- /dev/null +++ b/zh-hant/codes/ruby/chapter_tree/avl_tree.rb @@ -0,0 +1,216 @@ +=begin +File: avl_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### AVL 樹 ### +class AVLTree + ### 建構子 ### + def initialize + @root = nil + end + + ### 獲取二元樹根節點 ### + def get_root + @root + end + + ### 獲取節點高度 ### + def height(node) + # 空節點高度為 -1 ,葉節點高度為 0 + return node.height unless node.nil? + + -1 + end + + ### 更新節點高度 ### + def update_height(node) + # 節點高度等於最高子樹高度 + 1 + node.height = [height(node.left), height(node.right)].max + 1 + end + + ### 獲取平衡因子 ### + def balance_factor(node) + # 空節點平衡因子為 0 + return 0 if node.nil? + + # 節點平衡因子 = 左子樹高度 - 右子樹高度 + height(node.left) - height(node.right) + end + + ### 右旋操作 ### + def right_rotate(node) + child = node.left + grand_child = child.right + # 以 child 為原點,將 node 向右旋轉 + child.right = node + node.left = grand_child + # 更新節點高度 + update_height(node) + update_height(child) + # 返回旋轉後子樹的根節點 + child + end + + ### 左旋操作 ### + def left_rotate(node) + child = node.right + grand_child = child.left + # 以 child 為原點,將 node 向左旋轉 + child.left = node + node.right = grand_child + # 更新節點高度 + update_height(node) + update_height(child) + # 返回旋轉後子樹的根節點 + child + end + + ### 執行旋轉操作,使該子樹重新恢復平衡 ### + def rotate(node) + # 獲取節點 node 的平衡因子 + balance_factor = balance_factor(node) + # 左遍樹 + if balance_factor > 1 + if balance_factor(node.left) >= 0 + # 右旋 + return right_rotate(node) + else + # 先左旋後右旋 + node.left = left_rotate(node.left) + return right_rotate(node) + end + # 右遍樹 + elsif balance_factor < -1 + if balance_factor(node.right) <= 0 + # 左旋 + return left_rotate(node) + else + # 先右旋後左旋 + node.right = right_rotate(node.right) + return left_rotate(node) + end + end + # 平衡樹,無須旋轉,直接返回 + node + end + + ### 插入節點 ### + def insert(val) + @root = insert_helper(@root, val) + end + + ### 遞迴插入節點(輔助方法)### + def insert_helper(node, val) + return TreeNode.new(val) if node.nil? + # 1. 查詢插入位置並插入節點 + if val < node.val + node.left = insert_helper(node.left, val) + elsif val > node.val + node.right = insert_helper(node.right, val) + else + # 重複節點不插入,直接返回 + return node + end + # 更新節點高度 + update_height(node) + # 2. 執行旋轉操作,使該子樹重新恢復平衡 + rotate(node) + end + + ### 刪除節點 ### + def remove(val) + @root = remove_helper(@root, val) + end + + ### 遞迴刪除節點(輔助方法)### + def remove_helper(node, val) + return if node.nil? + # 1. 查詢節點並刪除 + if val < node.val + node.left = remove_helper(node.left, val) + elsif val > node.val + node.right = remove_helper(node.right, val) + else + if node.left.nil? || node.right.nil? + child = node.left || node.right + # 子節點數量 = 0 ,直接刪除 node 並返回 + return if child.nil? + # 子節點數量 = 1 ,直接刪除 node + node = child + else + # 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + temp = node.right + while !temp.left.nil? + temp = temp.left + end + node.right = remove_helper(node.right, temp.val) + node.val = temp.val + end + end + # 更新節點高度 + update_height(node) + # 2. 執行旋轉操作,使該子樹重新恢復平衡 + rotate(node) + end + + ### 查詢節點 ### + def search(val) + cur = @root + # 迴圈查詢,越過葉節點後跳出 + while !cur.nil? + # 目標節點在 cur 的右子樹中 + if cur.val < val + cur = cur.right + # 目標節點在 cur 的左子樹中 + elsif cur.val > val + cur = cur.left + # 找到目標節點,跳出迴圈 + else + break + end + end + # 返回目標節點 + cur + end +end + +### Driver Code ### +if __FILE__ == $0 + def test_insert(tree, val) + tree.insert(val) + puts "\n插入節點 #{val} 後,AVL 樹為" + print_tree(tree.get_root) + end + + def test_remove(tree, val) + tree.remove(val) + puts "\n刪除節點 #{val} 後,AVL 樹為" + print_tree(tree.get_root) + end + + # 初始化空 AVL 樹 + avl_tree = AVLTree.new + + # 插入節點 + # 請關注插入節點後,AVL 樹是如何保持平衡的 + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] + test_insert(avl_tree, val) + end + + # 插入重複節點 + test_insert(avl_tree, 7) + + # 刪除節點 + # 請關注刪除節點後,AVL 樹是如何保持平衡的 + test_remove(avl_tree, 8) # 刪除度為 0 的節點 + test_remove(avl_tree, 5) # 刪除度為 1 的節點 + test_remove(avl_tree, 4) # 刪除度為 2 的節點 + + result_node = avl_tree.search(7) + puts "\n查詢到的節點物件為 #{result_node},節點值 = #{result_node.val}" +end diff --git a/zh-hant/codes/ruby/chapter_tree/binary_search_tree.rb b/zh-hant/codes/ruby/chapter_tree/binary_search_tree.rb new file mode 100644 index 0000000000..cd73e1b113 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_tree/binary_search_tree.rb @@ -0,0 +1,161 @@ +=begin +File: binary_search_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 二元搜尋樹 ### +class BinarySearchTree + ### 建構子 ### + def initialize + # 初始化空樹 + @root = nil + end + + ### 獲取二元樹根節點 ### + def get_root + @root + end + + ### 查詢節點 ### + def search(num) + cur = @root + + # 迴圈查詢,越過葉節點後跳出 + while !cur.nil? + # 目標節點在 cur 的右子樹中 + if cur.val < num + cur = cur.right + # 目標節點在 cur 的左子樹中 + elsif cur.val > num + cur = cur.left + # 找到目標節點,跳出迴圈 + else + break + end + end + + cur + end + + ### 插入節點 ### + def insert(num) + # 若樹為空,則初始化根節點 + if @root.nil? + @root = TreeNode.new(num) + return + end + + # 迴圈查詢,越過葉節點後跳出 + cur, pre = @root, nil + while !cur.nil? + # 找到重複節點,直接返回 + return if cur.val == num + + pre = cur + # 插入位置在 cur 的右子樹中 + if cur.val < num + cur = cur.right + # 插入位置在 cur 的左子樹中 + else + cur = cur.left + end + end + + # 插入節點 + node = TreeNode.new(num) + if pre.val < num + pre.right = node + else + pre.left = node + end + end + + ### 刪除節點 ### + def remove(num) + # 若樹為空,直接提前返回 + return if @root.nil? + + # 迴圈查詢,越過葉節點後跳出 + cur, pre = @root, nil + while !cur.nil? + # 找到待刪除節點,跳出迴圈 + break if cur.val == num + + pre = cur + # 待刪除節點在 cur 的右子樹中 + if cur.val < num + cur = cur.right + # 待刪除節點在 cur 的左子樹中 + else + cur = cur.left + end + end + # 若無待刪除節點,則直接返回 + return if cur.nil? + + # 子節點數量 = 0 or 1 + if cur.left.nil? || cur.right.nil? + # 當子節點數量 = 0 / 1 時, child = null / 該子節點 + child = cur.left || cur.right + # 刪除節點 cur + if cur != @root + if pre.left == cur + pre.left = child + else + pre.right = child + end + else + # 若刪除節點為根節點,則重新指定根節點 + @root = child + end + # 子節點數量 = 2 + else + # 獲取中序走訪中 cur 的下一個節點 + tmp = cur.right + while !tmp.left.nil? + tmp = tmp.left + end + # 遞迴刪除節點 tmp + remove(tmp.val) + # 用 tmp 覆蓋 cur + cur.val = tmp.val + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二元搜尋樹 + bst = BinarySearchTree.new + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + nums.each { |num| bst.insert(num) } + puts "\n初始化的二元樹為\n" + print_tree(bst.get_root) + + # 查詢節點 + node = bst.search(7) + puts "\n查詢到的節點物件為: #{node},節點值 = #{node.val}" + + # 插入節點 + bst.insert(16) + puts "\n插入節點 16 後,二元樹為\n" + print_tree(bst.get_root) + + # 刪除節點 + bst.remove(1) + puts "\n刪除節點 1 後,二元樹為\n" + print_tree(bst.get_root) + + bst.remove(2) + puts "\n刪除節點 2 後,二元樹為\n" + print_tree(bst.get_root) + + bst.remove(4) + puts "\n刪除節點 4 後,二元樹為\n" + print_tree(bst.get_root) +end diff --git a/zh-hant/codes/ruby/chapter_tree/binary_tree.rb b/zh-hant/codes/ruby/chapter_tree/binary_tree.rb new file mode 100644 index 0000000000..3de63105ca --- /dev/null +++ b/zh-hant/codes/ruby/chapter_tree/binary_tree.rb @@ -0,0 +1,38 @@ +=begin +File: binary_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # 初始化二元樹 + # 初始化節點 + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + puts "\n初始化二元樹\n\n" + print_tree(n1) + + # 插入與刪除節點 + _p = TreeNode.new(0) + # 在 n1 -> n2 中間插入節點 _p + n1.left = _p + _p.left = n2 + puts "\n插入節點 _p 後\n\n" + print_tree(n1) + # 刪除節點 + n1.left = n2 + puts "\n刪除節點 _p 後\n\n" + print_tree(n1) +end diff --git a/zh-hant/codes/ruby/chapter_tree/binary_tree_bfs.rb b/zh-hant/codes/ruby/chapter_tree/binary_tree_bfs.rb new file mode 100644 index 0000000000..ec57ac1833 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_tree/binary_tree_bfs.rb @@ -0,0 +1,36 @@ +=begin +File: binary_tree_bfs.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 層序走訪 ### +def level_order(root) + # 初始化佇列,加入根節點 + queue = [root] + # 初始化一個串列,用於儲存走訪序列 + res = [] + while !queue.empty? + node = queue.shift # 隊列出隊 + res << node.val # 儲存節點值 + queue << node.left unless node.left.nil? # 左子節點入列 + queue << node.right unless node.right.nil? # 右子節點入列 + end + res +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二元樹 + # 這裡藉助了一個從陣列直接生成二元樹的函式 + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹\n\n" + print_tree(root) + + # 層序走訪 + res = level_order(root) + puts "\n層序走訪的節點列印序列 = #{res}" +end diff --git a/zh-hant/codes/ruby/chapter_tree/binary_tree_dfs.rb b/zh-hant/codes/ruby/chapter_tree/binary_tree_dfs.rb new file mode 100644 index 0000000000..0a2c4cfd0f --- /dev/null +++ b/zh-hant/codes/ruby/chapter_tree/binary_tree_dfs.rb @@ -0,0 +1,62 @@ +=begin +File: binary_tree_dfs.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序走訪 ### +def pre_order(root) + return if root.nil? + + # 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + $res << root.val + pre_order(root.left) + pre_order(root.right) +end + +### 中序走訪 ### +def in_order(root) + return if root.nil? + + # 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + in_order(root.left) + $res << root.val + in_order(root.right) +end + +### 後序走訪 ### +def post_order(root) + return if root.nil? + + # 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + post_order(root.left) + post_order(root.right) + $res << root.val +end + +### Driver Code ### +if __FILE__ == $0 + # 初始化二元樹 + # 這裡藉助了一個從陣列直接生成二元樹的函式 + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\n初始化二元樹\n\n" + print_tree(root) + + # 前序走訪 + $res = [] + pre_order(root) + puts "\n前序走訪的節點列印序列 = #{$res}" + + # 中序走訪 + $res.clear + in_order(root) + puts "\nn中序走訪的節點列印序列 = #{$res}" + + # 後序走訪 + $res.clear + post_order(root) + puts "\nn後序走訪的節點列印序列 = #{$res}" +end diff --git a/zh-hant/codes/ruby/test_all.rb b/zh-hant/codes/ruby/test_all.rb new file mode 100644 index 0000000000..a4d417800a --- /dev/null +++ b/zh-hant/codes/ruby/test_all.rb @@ -0,0 +1,23 @@ +require 'open3' + +start_time = Time.now +ruby_code_dir = File.dirname(__FILE__) +files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") + +errors = [] + +files.each do |file| + stdout, stderr, status = Open3.capture3("ruby #{file}") + errors << stderr unless status.success? +end + +puts "\x1b[34mTested #{files.count} files\x1b[m" + +unless errors.empty? + puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" + raise errors.join("\n\n") +else + puts "\x1b[32mPASS\x1b[m" +end + +puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" diff --git a/zh-hant/codes/ruby/utils/list_node.rb b/zh-hant/codes/ruby/utils/list_node.rb new file mode 100644 index 0000000000..b09d382e80 --- /dev/null +++ b/zh-hant/codes/ruby/utils/list_node.rb @@ -0,0 +1,38 @@ +=begin +File: list_node.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 鏈結串列節點類別 ### +class ListNode + attr_accessor :val # 節點值 + attr_accessor :next # 指向下一節點的引用 + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end +end + +### 將串列反序列化為鏈結串列 ### +def arr_to_linked_list(arr) + head = current = ListNode.new(arr[0]) + + for i in 1...arr.length + current.next = ListNode.new(arr[i]) + current = current.next + end + + head +end + +### 將鏈結串列序列化為串列 ### +def linked_list_to_arr(head) + arr = [] + + while head + arr << head.val + head = head.next + end +end diff --git a/zh-hant/codes/ruby/utils/print_util.rb b/zh-hant/codes/ruby/utils/print_util.rb new file mode 100644 index 0000000000..1e2cb6db59 --- /dev/null +++ b/zh-hant/codes/ruby/utils/print_util.rb @@ -0,0 +1,80 @@ +=begin +File: print_util.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative "./tree_node" + +### 列印矩陣 ### +def print_matrix(mat) + s = [] + mat.each { |arr| s << " #{arr.to_s}" } + puts "[\n#{s.join(",\n")}\n]" +end + +### 列印鏈結串列 ### +def print_linked_list(head) + list = [] + while head + list << head.val + head = head.next + end + puts "#{list.join(" -> ")}" +end + +class Trunk + attr_accessor :prev, :str + + def initialize(prev, str) + @prev = prev + @str = str + end +end + +def show_trunk(p) + return if p.nil? + + show_trunk(p.prev) + print p.str +end + +### 列印二元樹 ### +# This tree printer is borrowed from TECHIE DELIGHT +# https://www.techiedelight.com/c-program-print-binary-tree/ +def print_tree(root, prev=nil, is_right=false) + return if root.nil? + + prev_str = " " + trunk = Trunk.new(prev, prev_str) + print_tree(root.right, trunk, true) + + if prev.nil? + trunk.str = "———" + elsif is_right + trunk.str = "/———" + prev_str = " |" + else + trunk.str = "\\———" + prev.str = prev_str + end + + show_trunk(trunk) + puts " #{root.val}" + prev.str = prev_str if prev + trunk.str = " |" + print_tree(root.left, trunk, false) +end + +### 列印雜湊表 ### +def print_hash_map(hmap) + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } +end + +### 列印堆積 ### +def print_heap(heap) + puts "堆積的陣列表示:#{heap}" + puts "堆積的樹狀表示:" + root = arr_to_tree(heap) + print_tree(root) +end diff --git a/zh-hant/codes/ruby/utils/tree_node.rb b/zh-hant/codes/ruby/utils/tree_node.rb new file mode 100644 index 0000000000..186ff12bd0 --- /dev/null +++ b/zh-hant/codes/ruby/utils/tree_node.rb @@ -0,0 +1,53 @@ +=begin +File: tree_node.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二元樹節點類別 ### +class TreeNode + attr_accessor :val # 節點值 + attr_accessor :height # 節點高度 + attr_accessor :left # 左子節點引用 + attr_accessor :right # 右子節點引用 + + def initialize(val=0) + @val = val + @height = 0 + end +end + +### 將串列反序列化為二元數樹:遞迴 ### +def arr_to_tree_dfs(arr, i) + # 如果索引超出陣列長度,或者對應的元素為 nil ,則返回 nil + return if i < 0 || i >= arr.length || arr[i].nil? + # 構建當前節點 + root = TreeNode.new(arr[i]) + # 遞迴構建左右子樹 + root.left = arr_to_tree_dfs(arr, 2 * i + 1) + root.right = arr_to_tree_dfs(arr, 2 * i + 2) + root +end + +### 將串列反序列化為二元樹 ### +def arr_to_tree(arr) + arr_to_tree_dfs(arr, 0) +end + +### 將二元樹序列化為串列:遞迴 ### +def tree_to_arr_dfs(root, i, res) + return if root.nil? + + res += Array.new(i - res.length + 1) if i >= res.length + res[i] = root.val + + tree_to_arr_dfs(root.left, 2 * i + 1, res) + tree_to_arr_dfs(root.right, 2 * i + 2, res) +end + +### 將二元樹序列化為串列 ### +def tree_to_arr(root) + res = [] + tree_to_arr_dfs(root, 0, res) + res +end diff --git a/zh-hant/codes/ruby/utils/vertex.rb b/zh-hant/codes/ruby/utils/vertex.rb new file mode 100644 index 0000000000..43f688a408 --- /dev/null +++ b/zh-hant/codes/ruby/utils/vertex.rb @@ -0,0 +1,24 @@ +=begin +File: vertex.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 頂點類別 ### +class Vertex + attr_accessor :val + + def initialize(val) + @val = val + end +end + +### 輸入值串列 vals ,返回頂點串列 vets ### +def vals_to_vets(vals) + Array.new(vals.length) { |i| Vertex.new(vals[i]) } +end + +### 輸入頂點串列 vets, 返回值串列 vals ### +def vets_to_vals(vets) + Array.new(vets.length) { |i| vets[i].val } +end diff --git a/zh-hant/codes/rust/.gitignore b/zh-hant/codes/rust/.gitignore new file mode 100644 index 0000000000..4470988469 --- /dev/null +++ b/zh-hant/codes/rust/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/zh-hant/codes/rust/Cargo.toml b/zh-hant/codes/rust/Cargo.toml new file mode 100644 index 0000000000..160f9134cf --- /dev/null +++ b/zh-hant/codes/rust/Cargo.toml @@ -0,0 +1,413 @@ +[package] +name = "hello-algo-rust" +version = "0.1.0" +edition = "2021" +publish = false + +# Run Command: cargo run --bin time_complexity +[[bin]] +name = "time_complexity" +path = "chapter_computational_complexity/time_complexity.rs" + +# Run Command: cargo run --bin worst_best_time_complexity +[[bin]] +name = "worst_best_time_complexity" +path = "chapter_computational_complexity/worst_best_time_complexity.rs" + +# Run Command: cargo run --bin space_complexity +[[bin]] +name = "space_complexity" +path = "chapter_computational_complexity/space_complexity.rs" + +# Run Command: cargo run --bin iteration +[[bin]] +name = "iteration" +path = "chapter_computational_complexity/iteration.rs" + +# Run Command: cargo run --bin recursion +[[bin]] +name = "recursion" +path = "chapter_computational_complexity/recursion.rs" + +# Run Command: cargo run --bin two_sum +[[bin]] +name = "two_sum" +path = "chapter_searching/two_sum.rs" + +# Run Command: cargo run --bin array +[[bin]] +name = "array" +path = "chapter_array_and_linkedlist/array.rs" + +# Run Command: cargo run --bin linked_list +[[bin]] +name = "linked_list" +path = "chapter_array_and_linkedlist/linked_list.rs" + +# Run Command: cargo run --bin list +[[bin]] +name = "list" +path = "chapter_array_and_linkedlist/list.rs" + +# Run Command: cargo run --bin my_list +[[bin]] +name = "my_list" +path = "chapter_array_and_linkedlist/my_list.rs" + +# Run Command: cargo run --bin stack +[[bin]] +name = "stack" +path = "chapter_stack_and_queue/stack.rs" + +# Run Command: cargo run --bin linkedlist_stack +[[bin]] +name = "linkedlist_stack" +path = "chapter_stack_and_queue/linkedlist_stack.rs" + +# Run Command: cargo run --bin queue +[[bin]] +name = "queue" +path = "chapter_stack_and_queue/queue.rs" + +# Run Command: cargo run --bin linkedlist_queue +[[bin]] +name = "linkedlist_queue" +path = "chapter_stack_and_queue/linkedlist_queue.rs" + +# Run Command: cargo run --bin deque +[[bin]] +name = "deque" +path = "chapter_stack_and_queue/deque.rs" + +# Run Command: cargo run --bin array_deque +[[bin]] +name = "array_deque" +path = "chapter_stack_and_queue/array_deque.rs" + +# Run Command: cargo run --bin linkedlist_deque +[[bin]] +name = "linkedlist_deque" +path = "chapter_stack_and_queue/linkedlist_deque.rs" + +# Run Command: cargo run --bin simple_hash +[[bin]] +name = "simple_hash" +path = "chapter_hashing/simple_hash.rs" + +# Run Command: cargo run --bin hash_map +[[bin]] +name = "hash_map" +path = "chapter_hashing/hash_map.rs" + +# Run Command: cargo run --bin array_hash_map +[[bin]] +name = "array_hash_map" +path = "chapter_hashing/array_hash_map.rs" + +# Run Command: cargo run --bin build_in_hash +[[bin]] +name = "build_in_hash" +path = "chapter_hashing/build_in_hash.rs" + +# Run Command: cargo run --bin hash_map_chaining +[[bin]] +name = "hash_map_chaining" +path = "chapter_hashing/hash_map_chaining.rs" + +# Run Command: cargo run --bin hash_map_open_addressing +[[bin]] +name = "hash_map_open_addressing" +path = "chapter_hashing/hash_map_open_addressing.rs" + +# Run Command: cargo run --bin binary_search +[[bin]] +name = "binary_search" +path = "chapter_searching/binary_search.rs" + +# Run Command: cargo run --bin binary_search_edge +[[bin]] +name = "binary_search_edge" +path = "chapter_searching/binary_search_edge.rs" + +# Run Command: cargo run --bin binary_search_insertion +[[bin]] +name = "binary_search_insertion" +path = "chapter_searching/binary_search_insertion.rs" + +# Run Command: cargo run --bin bubble_sort +[[bin]] +name = "bubble_sort" +path = "chapter_sorting/bubble_sort.rs" + +# Run Command: cargo run --bin insertion_sort +[[bin]] +name = "insertion_sort" +path = "chapter_sorting/insertion_sort.rs" + +# Run Command: cargo run --bin quick_sort +[[bin]] +name = "quick_sort" +path = "chapter_sorting/quick_sort.rs" + +# Run Command: cargo run --bin merge_sort +[[bin]] +name = "merge_sort" +path = "chapter_sorting/merge_sort.rs" + +# Run Command: cargo run --bin selection_sort +[[bin]] +name = "selection_sort" +path = "chapter_sorting/selection_sort.rs" + +# Run Command: cargo run --bin bucket_sort +[[bin]] +name = "bucket_sort" +path = "chapter_sorting/bucket_sort.rs" + +# Run Command: cargo run --bin heap_sort +[[bin]] +name = "heap_sort" +path = "chapter_sorting/heap_sort.rs" + +# Run Command: cargo run --bin counting_sort +[[bin]] +name = "counting_sort" +path = "chapter_sorting/counting_sort.rs" + +# Run Command: cargo run --bin radix_sort +[[bin]] +name = "radix_sort" +path = "chapter_sorting/radix_sort.rs" + +# Run Command: cargo run --bin array_stack +[[bin]] +name = "array_stack" +path = "chapter_stack_and_queue/array_stack.rs" + +# Run Command: cargo run --bin array_queue +[[bin]] +name = "array_queue" +path = "chapter_stack_and_queue/array_queue.rs" + +# Run Command: cargo run --bin array_binary_tree +[[bin]] +name = "array_binary_tree" +path = "chapter_tree/array_binary_tree.rs" + +# Run Command: cargo run --bin avl_tree +[[bin]] +name = "avl_tree" +path = "chapter_tree/avl_tree.rs" + +# Run Command: cargo run --bin binary_search_tree +[[bin]] +name = "binary_search_tree" +path = "chapter_tree/binary_search_tree.rs" + +# Run Command: cargo run --bin binary_tree_bfs +[[bin]] +name = "binary_tree_bfs" +path = "chapter_tree/binary_tree_bfs.rs" + +# Run Command: cargo run --bin binary_tree_dfs +[[bin]] +name = "binary_tree_dfs" +path = "chapter_tree/binary_tree_dfs.rs" + +# Run Command: cargo run --bin binary_tree +[[bin]] +name = "binary_tree" +path = "chapter_tree/binary_tree.rs" + +# Run Command: cargo run --bin heap +[[bin]] +name = "heap" +path = "chapter_heap/heap.rs" + +# Run Command: cargo run --bin my_heap +[[bin]] +name = "my_heap" +path = "chapter_heap/my_heap.rs" + +# Run Command: cargo run --bin top_k +[[bin]] +name = "top_k" +path = "chapter_heap/top_k.rs" + +# Run Command: cargo run --bin graph_adjacency_list +[[bin]] +name = "graph_adjacency_list" +path = "chapter_graph/graph_adjacency_list.rs" + +# Run Command: cargo run --bin graph_adjacency_matrix +[[bin]] +name = "graph_adjacency_matrix" +path = "chapter_graph/graph_adjacency_matrix.rs" + +# Run Command: cargo run --bin graph_bfs +[[bin]] +name = "graph_bfs" +path = "chapter_graph/graph_bfs.rs" + +# Run Command: cargo run --bin graph_dfs +[[bin]] +name = "graph_dfs" +path = "chapter_graph/graph_dfs.rs" + +# Run Command: cargo run --bin linear_search +[[bin]] +name = "linear_search" +path = "chapter_searching/linear_search.rs" + +# Run Command: cargo run --bin hashing_search +[[bin]] +name = "hashing_search" +path = "chapter_searching/hashing_search.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs +[[bin]] +name = "climbing_stairs_dfs" +path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs_mem +[[bin]] +name = "climbing_stairs_dfs_mem" +path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" + +# Run Command: cargo run --bin climbing_stairs_dp +[[bin]] +name = "climbing_stairs_dp" +path = "chapter_dynamic_programming/climbing_stairs_dp.rs" + +# Run Command: cargo run --bin min_cost_climbing_stairs_dp +[[bin]] +name = "min_cost_climbing_stairs_dp" +path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_constraint_dp +[[bin]] +name = "climbing_stairs_constraint_dp" +path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_backtrack +[[bin]] +name = "climbing_stairs_backtrack" +path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" + +# Run Command: cargo run --bin subset_sum_i_naive +[[bin]] +name = "subset_sum_i_naive" +path = "chapter_backtracking/subset_sum_i_naive.rs" + +# Run Command: cargo run --bin subset_sum_i +[[bin]] +name = "subset_sum_i" +path = "chapter_backtracking/subset_sum_i.rs" + +# Run Command: cargo run --bin subset_sum_ii +[[bin]] +name = "subset_sum_ii" +path = "chapter_backtracking/subset_sum_ii.rs" + +# Run Command: cargo run --bin coin_change +[[bin]] +name = "coin_change" +path = "chapter_dynamic_programming/coin_change.rs" + +# Run Command: cargo run --bin coin_change_ii +[[bin]] +name = "coin_change_ii" +path = "chapter_dynamic_programming/coin_change_ii.rs" + +# Run Command: cargo run --bin unbounded_knapsack +[[bin]] +name = "unbounded_knapsack" +path = "chapter_dynamic_programming/unbounded_knapsack.rs" + +# Run Command: cargo run --bin knapsack +[[bin]] +name = "knapsack" +path = "chapter_dynamic_programming/knapsack.rs" + +# Run Command: cargo run --bin min_path_sum +[[bin]] +name = "min_path_sum" +path = "chapter_dynamic_programming/min_path_sum.rs" + +# Run Command: cargo run --bin edit_distance +[[bin]] +name = "edit_distance" +path = "chapter_dynamic_programming/edit_distance.rs" + +# Run Command: cargo run --bin n_queens +[[bin]] +name = "n_queens" +path = "chapter_backtracking/n_queens.rs" + +# Run Command: cargo run --bin permutations_i +[[bin]] +name = "permutations_i" +path = "chapter_backtracking/permutations_i.rs" + +# Run Command: cargo run --bin permutations_ii +[[bin]] +name = "permutations_ii" +path = "chapter_backtracking/permutations_ii.rs" + +# Run Command: cargo run --bin preorder_traversal_i_compact +[[bin]] +name = "preorder_traversal_i_compact" +path = "chapter_backtracking/preorder_traversal_i_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_ii_compact +[[bin]] +name = "preorder_traversal_ii_compact" +path = "chapter_backtracking/preorder_traversal_ii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_compact +[[bin]] +name = "preorder_traversal_iii_compact" +path = "chapter_backtracking/preorder_traversal_iii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_template +[[bin]] +name = "preorder_traversal_iii_template" +path = "chapter_backtracking/preorder_traversal_iii_template.rs" + +# Run Command: cargo run --bin binary_search_recur +[[bin]] +name = "binary_search_recur" +path = "chapter_divide_and_conquer/binary_search_recur.rs" + +# Run Command: cargo run --bin hanota +[[bin]] +name = "hanota" +path = "chapter_divide_and_conquer/hanota.rs" + +# Run Command: cargo run --bin build_tree +[[bin]] +name = "build_tree" +path = "chapter_divide_and_conquer/build_tree.rs" + +# Run Command: cargo run --bin coin_change_greedy +[[bin]] +name = "coin_change_greedy" +path = "chapter_greedy/coin_change_greedy.rs" + +# Run Command: cargo run --bin fractional_knapsack +[[bin]] +name = "fractional_knapsack" +path = "chapter_greedy/fractional_knapsack.rs" + +# Run Command: cargo run --bin max_capacity +[[bin]] +name = "max_capacity" +path = "chapter_greedy/max_capacity.rs" + +# Run Command: cargo run --bin max_product_cutting +[[bin]] +name = "max_product_cutting" +path = "chapter_greedy/max_product_cutting.rs" + +[dependencies] +rand = "0.8.5" diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs new file mode 100644 index 0000000000..33e6118afe --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs @@ -0,0 +1,111 @@ +/* + * File: array.rs + * Created Time: 2023-01-15 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::Rng; + +/* 隨機訪問元素 */ +fn random_access(nums: &[i32]) -> i32 { + // 在區間 [0, nums.len()) 中隨機抽取一個數字 + let random_index = rand::thread_rng().gen_range(0..nums.len()); + // 獲取並返回隨機元素 + let random_num = nums[random_index]; + random_num +} + +/* 擴展陣列長度 */ +fn extend(nums: &[i32], enlarge: usize) -> Vec { + // 初始化一個擴展長度後的陣列 + let mut res: Vec = vec![0; nums.len() + enlarge]; + // 將原陣列中的所有元素複製到新 + res[0..nums.len()].copy_from_slice(nums); + + // 返回擴展後的新陣列 + res +} + +/* 在陣列的索引 index 處插入元素 num */ +fn insert(nums: &mut [i32], num: i32, index: usize) { + // 把索引 index 以及之後的所有元素向後移動一位 + for i in (index + 1..nums.len()).rev() { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +fn remove(nums: &mut [i32], index: usize) { + // 把索引 index 之後的所有元素向前移動一位 + for i in index..nums.len() - 1 { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +fn traverse(nums: &[i32]) { + let mut _count = 0; + // 透過索引走訪陣列 + for i in 0..nums.len() { + _count += nums[i]; + } + // 直接走訪陣列元素 + _count = 0; + for &num in nums { + _count += num; + } +} + +/* 在陣列中查詢指定元素 */ +fn find(nums: &[i32], target: i32) -> Option { + for i in 0..nums.len() { + if nums[i] == target { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + /* 初始化陣列 */ + let arr: [i32; 5] = [0; 5]; + print!("陣列 arr = "); + print_util::print_array(&arr); + // 在 Rust 中,指定長度時([i32; 5])為陣列,不指定長度時(&[i32])為切片 + // 由於 Rust 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 + // Vector 是 Rust 一般情況下用作動態陣列的型別 + // 為了方便實現擴容 extend() 方法,以下將 vector 看作陣列(array) + let nums: Vec = vec![1, 3, 2, 5, 4]; + print!("\n陣列 nums = "); + print_util::print_array(&nums); + + // 隨機訪問 + let random_num = random_access(&nums); + println!("\n在 nums 中獲取隨機元素 {}", random_num); + + // 長度擴展 + let mut nums: Vec = extend(&nums, 3); + print!("將陣列長度擴展至 8 ,得到 nums = "); + print_util::print_array(&nums); + + // 插入元素 + insert(&mut nums, 6, 3); + print!("\n在索引 3 處插入數字 6 ,得到 nums = "); + print_util::print_array(&nums); + + // 刪除元素 + remove(&mut nums, 2); + print!("\n刪除索引 2 處的元素,得到 nums = "); + print_util::print_array(&nums); + + // 走訪陣列 + traverse(&nums); + + // 查詢元素 + let index = find(&nums, 3).unwrap(); + println!("\n在 nums 中查詢元素 3 ,得到索引 = {}", index); +} diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/linked_list.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/linked_list.rs new file mode 100644 index 0000000000..9db771e8fe --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/linked_list.rs @@ -0,0 +1,100 @@ +/* + * File: linked_list.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; +use std::cell::RefCell; +use std::rc::Rc; + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +#[allow(non_snake_case)] +pub fn insert(n0: &Rc>>, P: Rc>>) { + let n1 = n0.borrow_mut().next.take(); + P.borrow_mut().next = n1; + n0.borrow_mut().next = Some(P); +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +#[allow(non_snake_case)] +pub fn remove(n0: &Rc>>) { + // n0 -> P -> n1 + let P = n0.borrow_mut().next.take(); + if let Some(node) = P { + let n1 = node.borrow_mut().next.take(); + n0.borrow_mut().next = n1; + } +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +pub fn access(head: Rc>>, index: i32) -> Option>>> { + fn dfs( + head: Option<&Rc>>>, + index: i32, + ) -> Option>>> { + if index <= 0 { + return head.cloned(); + } + + if let Some(node) = head { + dfs(node.borrow().next.as_ref(), index - 1) + } else { + None + } + } + + dfs(Some(head).as_ref(), index) +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +pub fn find(head: Rc>>, target: T) -> i32 { + fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { + if let Some(node) = head { + if node.borrow().val == target { + return idx; + } + return find(node.borrow().next.as_ref(), target, idx + 1); + } else { + -1 + } + } + + find(Some(head).as_ref(), target, 0) +} + +/* Driver Code */ +fn main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + let n0 = ListNode::new(1); + let n1 = ListNode::new(3); + let n2 = ListNode::new(2); + let n3 = ListNode::new(5); + let n4 = ListNode::new(4); + // 構建節點之間的引用 + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + print!("初始化的鏈結串列為 "); + print_util::print_linked_list(&n0); + + /* 插入節點 */ + insert(&n0, ListNode::new(0)); + print!("插入節點後的鏈結串列為 "); + print_util::print_linked_list(&n0); + + /* 刪除節點 */ + remove(&n0); + print!("刪除節點後的鏈結串列為 "); + print_util::print_linked_list(&n0); + + /* 訪問節點 */ + let node = access(n0.clone(), 3); + println!("鏈結串列中索引 3 處的節點的值 = {}", node.unwrap().borrow().val); + + /* 查詢節點 */ + let index = find(n0.clone(), 2); + println!("鏈結串列中值為 2 的節點的索引 = {}", index); +} diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/list.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/list.rs new file mode 100644 index 0000000000..e7ab8b05a3 --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/list.rs @@ -0,0 +1,71 @@ +/* + * File: list.rs + * Created Time: 2023-01-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; + +/* Driver Code */ +fn main() { + // 初始化串列 + let mut nums: Vec = vec![1, 3, 2, 5, 4]; + print!("串列 nums = "); + print_util::print_array(&nums); + + // 訪問元素 + let num = nums[1]; + println!("\n訪問索引 1 處的元素,得到 num = {num}"); + + // 更新元素 + nums[1] = 0; + print!("將索引 1 處的元素更新為 0 ,得到 nums = "); + print_util::print_array(&nums); + + // 清空串列 + nums.clear(); + print!("\n清空串列後 nums = "); + print_util::print_array(&nums); + + // 在尾部新增元素 + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + print!("\n新增元素後 nums = "); + print_util::print_array(&nums); + + // 在中間插入元素 + nums.insert(3, 6); + print!("\n在索引 3 處插入數字 6 ,得到 nums = "); + print_util::print_array(&nums); + + // 刪除元素 + nums.remove(3); + print!("\n刪除索引 3 處的元素,得到 nums = "); + print_util::print_array(&nums); + + // 透過索引走訪串列 + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + // 直接走訪串列元素 + _count = 0; + for x in &nums { + _count += x; + } + + // 拼接兩個串列 + let mut nums1 = vec![6, 8, 7, 10, 9]; + nums.append(&mut nums1); // append(移動) 之後 nums1 為空! + + // nums.extend(&nums1); // extend(借用) nums1 能繼續使用 + print!("\n將串列 nums1 拼接到 nums 之後,得到 nums = "); + print_util::print_array(&nums); + + // 排序串列 + nums.sort(); + print!("\n排序串列後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/my_list.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/my_list.rs new file mode 100644 index 0000000000..b114b60765 --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/my_list.rs @@ -0,0 +1,164 @@ +/* + * File: my_list.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* 串列類別 */ +#[allow(dead_code)] +struct MyList { + arr: Vec, // 陣列(儲存串列元素) + capacity: usize, // 串列容量 + size: usize, // 串列長度(當前元素數量) + extend_ratio: usize, // 每次串列擴容的倍數 +} + +#[allow(unused, unused_comparisons)] +impl MyList { + /* 建構子 */ + pub fn new(capacity: usize) -> Self { + let mut vec = vec![0; capacity]; + Self { + arr: vec, + capacity, + size: 0, + extend_ratio: 2, + } + } + + /* 獲取串列長度(當前元素數量)*/ + pub fn size(&self) -> usize { + return self.size; + } + + /* 獲取串列容量 */ + pub fn capacity(&self) -> usize { + return self.capacity; + } + + /* 訪問元素 */ + pub fn get(&self, index: usize) -> i32 { + // 索引如果越界,則丟擲異常,下同 + if index >= self.size { + panic!("索引越界") + }; + return self.arr[index]; + } + + /* 更新元素 */ + pub fn set(&mut self, index: usize, num: i32) { + if index >= self.size { + panic!("索引越界") + }; + self.arr[index] = num; + } + + /* 在尾部新增元素 */ + pub fn add(&mut self, num: i32) { + // 元素數量超出容量時,觸發擴容機制 + if self.size == self.capacity() { + self.extend_capacity(); + } + self.arr[self.size] = num; + // 更新元素數量 + self.size += 1; + } + + /* 在中間插入元素 */ + pub fn insert(&mut self, index: usize, num: i32) { + if index >= self.size() { + panic!("索引越界") + }; + // 元素數量超出容量時,觸發擴容機制 + if self.size == self.capacity() { + self.extend_capacity(); + } + // 將索引 index 以及之後的元素都向後移動一位 + for j in (index..self.size).rev() { + self.arr[j + 1] = self.arr[j]; + } + self.arr[index] = num; + // 更新元素數量 + self.size += 1; + } + + /* 刪除元素 */ + pub fn remove(&mut self, index: usize) -> i32 { + if index >= self.size() { + panic!("索引越界") + }; + let num = self.arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for j in index..self.size - 1 { + self.arr[j] = self.arr[j + 1]; + } + // 更新元素數量 + self.size -= 1; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + pub fn extend_capacity(&mut self) { + // 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列 + let new_capacity = self.capacity * self.extend_ratio; + self.arr.resize(new_capacity, 0); + // 更新串列容量 + self.capacity = new_capacity; + } + + /* 將串列轉換為陣列 */ + pub fn to_array(&self) -> Vec { + // 僅轉換有效長度範圍內的串列元素 + let mut arr = Vec::new(); + for i in 0..self.size { + arr.push(self.get(i)); + } + arr + } +} + +/* Driver Code */ +fn main() { + /* 初始化串列 */ + let mut nums = MyList::new(10); + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print!("串列 nums = "); + print_util::print_array(&nums.to_array()); + print!(" ,容量 = {} ,長度 = {}", nums.capacity(), nums.size()); + + /* 在中間插入元素 */ + nums.insert(3, 6); + print!("\n在索引 3 處插入數字 6 ,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 刪除元素 */ + nums.remove(3); + print!("\n刪除索引 3 處的元素,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 訪問元素 */ + let num = nums.get(1); + println!("\n訪問索引 1 處的元素,得到 num = {num}"); + + /* 更新元素 */ + nums.set(1, 0); + print!("將索引 1 處的元素更新為 0 ,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 測試擴容機制 */ + for i in 0..10 { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); + } + print!("\n擴容後的串列 nums = "); + print_util::print_array(&nums.to_array()); + print!(" ,容量 = {} ,長度 = {}", nums.capacity(), nums.size()); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/n_queens.rs b/zh-hant/codes/rust/chapter_backtracking/n_queens.rs new file mode 100644 index 0000000000..239ef03fb2 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/n_queens.rs @@ -0,0 +1,76 @@ +/* + * File: n_queens.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +fn backtrack( + row: usize, + n: usize, + state: &mut Vec>, + res: &mut Vec>>, + cols: &mut [bool], + diags1: &mut [bool], + diags2: &mut [bool], +) { + // 當放置完所有行時,記錄解 + if row == n { + res.push(state.clone()); + return; + } + // 走訪所有列 + for col in 0..n { + // 計算該格子對應的主對角線和次對角線 + let diag1 = row + n - 1 - col; + let diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = "#".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); + } + } +} + +/* 求解 n 皇后 */ +fn n_queens(n: usize) -> Vec>> { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; + let mut cols = vec![false; n]; // 記錄列是否有皇后 + let mut diags1 = vec![false; 2 * n - 1]; // 記錄主對角線上是否有皇后 + let mut diags2 = vec![false; 2 * n - 1]; // 記錄次對角線上是否有皇后 + let mut res: Vec>> = Vec::new(); + + backtrack( + 0, + n, + &mut state, + &mut res, + &mut cols, + &mut diags1, + &mut diags2, + ); + + res +} + +/* Driver Code */ +pub fn main() { + let n: usize = 4; + let res = n_queens(n); + + println!("輸入棋盤長寬為 {n}"); + println!("皇后放置方案共有 {} 種", res.len()); + for state in res.iter() { + println!("--------------------"); + for row in state.iter() { + println!("{:?}", row); + } + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/permutations_i.rs b/zh-hant/codes/rust/chapter_backtracking/permutations_i.rs new file mode 100644 index 0000000000..7b2f10bfa2 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/permutations_i.rs @@ -0,0 +1,46 @@ +/* + * File: permutations_i.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 當狀態長度等於元素數量時,記錄解 + if state.len() == choices.len() { + res.push(state); + return; + } + // 走訪所有選擇 + for i in 0..choices.len() { + let choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if !selected[i] { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state.clone(), choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + } +} + +/* 全排列 I */ +fn permutations_i(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); // 狀態(子集) + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 3]; + + let res = permutations_i(&mut nums); + + println!("輸入陣列 nums = {:?}", &nums); + println!("所有排列 res = {:?}", &res); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/permutations_ii.rs b/zh-hant/codes/rust/chapter_backtracking/permutations_ii.rs new file mode 100644 index 0000000000..8a5e852698 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/permutations_ii.rs @@ -0,0 +1,50 @@ +/* + * File: permutations_ii.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::collections::HashSet; + +/* 回溯演算法:全排列 II */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 當狀態長度等於元素數量時,記錄解 + if state.len() == choices.len() { + res.push(state); + return; + } + // 走訪所有選擇 + let mut duplicated = HashSet::::new(); + for i in 0..choices.len() { + let choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if !selected[i] && !duplicated.contains(&choice) { + // 嘗試:做出選擇,更新狀態 + duplicated.insert(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state.clone(), choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + } +} + +/* 全排列 II */ +fn permutations_ii(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 2]; + + let res = permutations_ii(&mut nums); + + println!("輸入陣列 nums = {:?}", &nums); + println!("所有排列 res = {:?}", &res); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs new file mode 100644 index 0000000000..cae60f54d1 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs @@ -0,0 +1,41 @@ +/* + * File: preorder_traversal_i_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前序走訪:例題一 */ +fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { + if root.is_none() { + return; + } + if let Some(node) = root { + if node.borrow().val == 7 { + // 記錄解 + res.push(node.clone()); + } + pre_order(res, node.borrow().left.as_ref()); + pre_order(res, node.borrow().right.as_ref()); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序走訪 + let mut res = Vec::new(); + pre_order(&mut res, root.as_ref()); + + println!("\n輸出所有值為 7 的節點"); + let mut vals = Vec::new(); + for node in res { + vals.push(node.borrow().val) + } + println!("{:?}", vals); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs new file mode 100644 index 0000000000..b627ebae25 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs @@ -0,0 +1,52 @@ +/* + * File: preorder_traversal_ii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前序走訪:例題二 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + if root.is_none() { + return; + } + if let Some(node) = root { + // 嘗試 + path.push(node.clone()); + if node.borrow().val == 7 { + // 記錄解 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // 回退 + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序走訪 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\n輸出所有根節點到節點 7 的路徑"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs new file mode 100644 index 0000000000..9e449bdbbc --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs @@ -0,0 +1,53 @@ +/* + * File: preorder_traversal_iii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前序走訪:例題三 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + // 剪枝 + if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { + return; + } + if let Some(node) = root { + // 嘗試 + path.push(node.clone()); + if node.borrow().val == 7 { + // 記錄解 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // 回退 + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序走訪 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs new file mode 100644 index 0000000000..7a1663b7d2 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs @@ -0,0 +1,88 @@ +/* + * File: preorder_traversal_iii_template.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 判斷當前狀態是否為解 */ +fn is_solution(state: &mut Vec>>) -> bool { + return !state.is_empty() && state.last().unwrap().borrow().val == 7; +} + +/* 記錄解 */ +fn record_solution( + state: &mut Vec>>, + res: &mut Vec>>>, +) { + res.push(state.clone()); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { + return choice.is_some() && choice.unwrap().borrow().val != 3; +} + +/* 更新狀態 */ +fn make_choice(state: &mut Vec>>, choice: Rc>) { + state.push(choice); +} + +/* 恢復狀態 */ +fn undo_choice(state: &mut Vec>>, _: Rc>) { + state.pop(); +} + +/* 回溯演算法:例題三 */ +fn backtrack( + state: &mut Vec>>, + choices: &Vec>>>, + res: &mut Vec>>>, +) { + // 檢查是否為解 + if is_solution(state) { + // 記錄解 + record_solution(state, res); + } + // 走訪所有選擇 + for &choice in choices.iter() { + // 剪枝:檢查選擇是否合法 + if is_valid(state, choice) { + // 嘗試:做出選擇,更新狀態 + make_choice(state, choice.unwrap().clone()); + // 進行下一輪選擇 + backtrack( + state, + &vec![ + choice.unwrap().borrow().left.as_ref(), + choice.unwrap().borrow().right.as_ref(), + ], + res, + ); + // 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice.unwrap().clone()); + } + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 回溯演算法 + let mut res = Vec::new(); + backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); + + println!("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/subset_sum_i.rs b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i.rs new file mode 100644 index 0000000000..91c1517ae6 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i.rs @@ -0,0 +1,56 @@ +/* + * File: subset_sum_i.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.push(state.clone()); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for i in start..choices.len() { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I */ +fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + let start = 0; // 走訪起始點 + let mut res = Vec::new(); // 結果串列(子集串列) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i(&mut nums, target); + + println!("輸入陣列 nums = {:?}, target = {}", &nums, target); + println!("所有和等於 {} 的子集 res = {:?}", target, &res); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/subset_sum_i_naive.rs b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i_naive.rs new file mode 100644 index 0000000000..34d8dbfe3f --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i_naive.rs @@ -0,0 +1,54 @@ +/* + * File: subset_sum_i_naive.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +fn backtrack( + state: &mut Vec, + target: i32, + total: i32, + choices: &[i32], + res: &mut Vec>, +) { + // 子集和等於 target 時,記錄解 + if total == target { + res.push(state.clone()); + return; + } + // 走訪所有選擇 + for i in 0..choices.len() { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if total + choices[i] > target { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I(包含重複子集) */ +fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 狀態(子集) + let total = 0; // 子集和 + let mut res = Vec::new(); // 結果串列(子集串列) + backtrack(&mut state, target, total, nums, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i_naive(&nums, target); + + println!("輸入陣列 nums = {:?}, target = {}", &nums, target); + println!("所有和等於 {} 的子集 res = {:?}", target, &res); + println!("請注意,該方法輸出的結果包含重複集合"); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/subset_sum_ii.rs b/zh-hant/codes/rust/chapter_backtracking/subset_sum_ii.rs new file mode 100644 index 0000000000..6da22c6743 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/subset_sum_ii.rs @@ -0,0 +1,61 @@ +/* + * File: subset_sum_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.push(state.clone()); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i in start..choices.len() { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if i > start && choices[i] == choices[i - 1] { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 II */ +fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + let start = 0; // 走訪起始點 + let mut res = Vec::new(); // 結果串列(子集串列) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 4, 5]; + let target = 9; + + let res = subset_sum_ii(&mut nums, target); + + println!("輸入陣列 nums = {:?}, target = {}", &nums, target); + println!("所有和等於 {} 的子集 res = {:?}", target, &res); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/iteration.rs b/zh-hant/codes/rust/chapter_computational_complexity/iteration.rs new file mode 100644 index 0000000000..ed1bbdc866 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/iteration.rs @@ -0,0 +1,74 @@ +/* + * File: iteration.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* for 迴圈 */ +fn for_loop(n: i32) -> i32 { + let mut res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for i in 1..=n { + res += i; + } + res +} + +/* while 迴圈 */ +fn while_loop(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 初始化條件變數 + + // 迴圈求和 1, 2, ..., n-1, n + while i <= n { + res += i; + i += 1; // 更新條件變數 + } + res +} + +/* while 迴圈(兩次更新) */ +fn while_loop_ii(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 初始化條件變數 + + // 迴圈求和 1, 4, 10, ... + while i <= n { + res += i; + // 更新條件變數 + i += 1; + i *= 2; + } + res +} + +/* 雙層 for 迴圈 */ +fn nested_for_loop(n: i32) -> String { + let mut res = vec![]; + // 迴圈 i = 1, 2, ..., n-1, n + for i in 1..=n { + // 迴圈 j = 1, 2, ..., n-1, n + for j in 1..=n { + res.push(format!("({}, {}), ", i, j)); + } + } + res.join("") +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = for_loop(n); + println!("\nfor 迴圈的求和結果 res = {res}"); + + res = while_loop(n); + println!("\nwhile 迴圈的求和結果 res = {res}"); + + res = while_loop_ii(n); + println!("\nwhile 迴圈(兩次更新)求和結果 res = {}", res); + + let res = nested_for_loop(n); + println!("\n雙層 for 迴圈的走訪結果 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/recursion.rs b/zh-hant/codes/rust/chapter_computational_complexity/recursion.rs new file mode 100644 index 0000000000..3e590fe82c --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/recursion.rs @@ -0,0 +1,76 @@ +/* + * File: recursion.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 遞迴 */ +fn recur(n: i32) -> i32 { + // 終止條件 + if n == 1 { + return 1; + } + // 遞:遞迴呼叫 + let res = recur(n - 1); + // 迴:返回結果 + n + res +} + +/* 使用迭代模擬遞迴 */ +fn for_loop_recur(n: i32) -> i32 { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + let mut stack = Vec::new(); + let mut res = 0; + // 遞:遞迴呼叫 + for i in (1..=n).rev() { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while !stack.is_empty() { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop().unwrap(); + } + // res = 1+2+3+...+n + res +} + +/* 尾遞迴 */ +fn tail_recur(n: i32, res: i32) -> i32 { + // 終止條件 + if n == 0 { + return res; + } + // 尾遞迴呼叫 + tail_recur(n - 1, res + n) +} + +/* 費波那契數列:遞迴 */ +fn fib(n: i32) -> i32 { + // 終止條件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1; + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + let res = fib(n - 1) + fib(n - 2); + // 返回結果 + res +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = recur(n); + println!("\n遞迴函式的求和結果 res = {res}"); + + res = for_loop_recur(n); + println!("\n使用迭代模擬遞迴求和結果 res = {res}"); + + res = tail_recur(n, 0); + println!("\n尾遞迴函式的求和結果 res = {res}"); + + res = fib(n); + println!("\n費波那契數列的第 {n} 項為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/space_complexity.rs b/zh-hant/codes/rust/chapter_computational_complexity/space_complexity.rs new file mode 100644 index 0000000000..a21f08bdf5 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/space_complexity.rs @@ -0,0 +1,114 @@ +/* + * File: space_complexity.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode, TreeNode}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* 函式 */ +fn function() -> i32 { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +#[allow(unused)] +fn constant(n: i32) { + // 常數、變數、物件佔用 O(1) 空間 + const A: i32 = 0; + let b = 0; + let nums = vec![0; 10000]; + let node = ListNode::new(0); + // 迴圈中的變數佔用 O(1) 空間 + for i in 0..n { + let c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for i in 0..n { + function(); + } +} + +/* 線性階 */ +#[allow(unused)] +fn linear(n: i32) { + // 長度為 n 的陣列佔用 O(n) 空間 + let mut nums = vec![0; n as usize]; + // 長度為 n 的串列佔用 O(n) 空間 + let mut nodes = Vec::new(); + for i in 0..n { + nodes.push(ListNode::new(i)) + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + let mut map = HashMap::new(); + for i in 0..n { + map.insert(i, i.to_string()); + } +} + +/* 線性階(遞迴實現) */ +fn linear_recur(n: i32) { + println!("遞迴 n = {}", n); + if n == 1 { + return; + }; + linear_recur(n - 1); +} + +/* 平方階 */ +#[allow(unused)] +fn quadratic(n: i32) { + // 矩陣佔用 O(n^2) 空間 + let num_matrix = vec![vec![0; n as usize]; n as usize]; + // 二維串列佔用 O(n^2) 空間 + let mut num_list = Vec::new(); + for i in 0..n { + let mut tmp = Vec::new(); + for j in 0..n { + tmp.push(0); + } + num_list.push(tmp); + } +} + +/* 平方階(遞迴實現) */ +fn quadratic_recur(n: i32) -> i32 { + if n <= 0 { + return 0; + }; + // 陣列 nums 長度為 n, n-1, ..., 2, 1 + let nums = vec![0; n as usize]; + println!("遞迴 n = {} 中的 nums 長度 = {}", n, nums.len()); + return quadratic_recur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +fn build_tree(n: i32) -> Option>> { + if n == 0 { + return None; + }; + let root = TreeNode::new(0); + root.borrow_mut().left = build_tree(n - 1); + root.borrow_mut().right = build_tree(n - 1); + return Some(root); +} + +/* Driver Code */ +fn main() { + let n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linear_recur(n); + // 平方階 + quadratic(n); + quadratic_recur(n); + // 指數階 + let root = build_tree(n); + print_util::print_tree(&root.unwrap()); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/time_complexity.rs b/zh-hant/codes/rust/chapter_computational_complexity/time_complexity.rs new file mode 100644 index 0000000000..07c0ab9c6e --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/time_complexity.rs @@ -0,0 +1,170 @@ +/* + * File: time_complexity.rs + * Created Time: 2023-01-10 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +/* 常數階 */ +fn constant(n: i32) -> i32 { + _ = n; + let mut count = 0; + let size = 100_000; + for _ in 0..size { + count += 1; + } + count +} + +/* 線性階 */ +fn linear(n: i32) -> i32 { + let mut count = 0; + for _ in 0..n { + count += 1; + } + count +} + +/* 線性階(走訪陣列) */ +fn array_traversal(nums: &[i32]) -> i32 { + let mut count = 0; + // 迴圈次數與陣列長度成正比 + for _ in nums { + count += 1; + } + count +} + +/* 平方階 */ +fn quadratic(n: i32) -> i32 { + let mut count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for _ in 0..n { + for _ in 0..n { + count += 1; + } + } + count +} + +/* 平方階(泡沫排序) */ +fn bubble_sort(nums: &mut [i32]) -> i32 { + let mut count = 0; // 計數器 + + // 外迴圈:未排序區間為 [0, i] + for i in (1..nums.len()).rev() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + count +} + +/* 指數階(迴圈實現) */ +fn exponential(n: i32) -> i32 { + let mut count = 0; + let mut base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for _ in 0..n { + for _ in 0..base { + count += 1 + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +} + +/* 指數階(遞迴實現) */ +fn exp_recur(n: i32) -> i32 { + if n == 1 { + return 1; + } + exp_recur(n - 1) + exp_recur(n - 1) + 1 +} + +/* 對數階(迴圈實現) */ +fn logarithmic(mut n: i32) -> i32 { + let mut count = 0; + while n > 1 { + n = n / 2; + count += 1; + } + count +} + +/* 對數階(遞迴實現) */ +fn log_recur(n: i32) -> i32 { + if n <= 1 { + return 0; + } + log_recur(n / 2) + 1 +} + +/* 線性對數階 */ +fn linear_log_recur(n: i32) -> i32 { + if n <= 1 { + return 1; + } + let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); + for _ in 0..n { + count += 1; + } + return count; +} + +/* 階乘階(遞迴實現) */ +fn factorial_recur(n: i32) -> i32 { + if n == 0 { + return 1; + } + let mut count = 0; + // 從 1 個分裂出 n 個 + for _ in 0..n { + count += factorial_recur(n - 1); + } + count +} + +/* Driver Code */ +fn main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + let n: i32 = 8; + println!("輸入資料大小 n = {}", n); + + let mut count = constant(n); + println!("常數階的操作數量 = {}", count); + + count = linear(n); + println!("線性階的操作數量 = {}", count); + count = array_traversal(&vec![0; n as usize]); + println!("線性階(走訪陣列)的操作數量 = {}", count); + + count = quadratic(n); + println!("平方階的操作數量 = {}", count); + let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] + count = bubble_sort(&mut nums); + println!("平方階(泡沫排序)的操作數量 = {}", count); + + count = exponential(n); + println!("指數階(迴圈實現)的操作數量 = {}", count); + count = exp_recur(n); + println!("指數階(遞迴實現)的操作數量 = {}", count); + + count = logarithmic(n); + println!("對數階(迴圈實現)的操作數量 = {}", count); + count = log_recur(n); + println!("對數階(遞迴實現)的操作數量 = {}", count); + + count = linear_log_recur(n); + println!("線性對數階(遞迴實現)的操作數量 = {}", count); + + count = factorial_recur(n); + println!("階乘階(遞迴實現)的操作數量 = {}", count); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs b/zh-hant/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs new file mode 100644 index 0000000000..ab912d9ed1 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs @@ -0,0 +1,42 @@ +/* + * File: worst_best_time_complexity.rs + * Created Time: 2023-01-13 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::seq::SliceRandom; +use rand::thread_rng; + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +fn random_numbers(n: i32) -> Vec { + // 生成陣列 nums = { 1, 2, 3, ..., n } + let mut nums = (1..=n).collect::>(); + // 隨機打亂陣列元素 + nums.shuffle(&mut thread_rng()); + nums +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +fn find_one(nums: &[i32]) -> Option { + for i in 0..nums.len() { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if nums[i] == 1 { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + for _ in 0..10 { + let n = 100; + let nums = random_numbers(n); + let index = find_one(&nums).unwrap(); + print!("\n陣列 [ 1, 2, ..., n ] 被打亂後 = "); + print_util::print_array(&nums); + println!("\n數字 1 的索引為 {}", index); + } +} diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs new file mode 100644 index 0000000000..4ac7a9fb67 --- /dev/null +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs @@ -0,0 +1,41 @@ +/* + * File: binary_search_recur.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { + // 若區間為空,代表無目標元素,則返回 -1 + if i > j { + return -1; + } + let m: i32 = i + (j - i) / 2; + if nums[m as usize] < target { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if nums[m as usize] > target { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + let n = nums.len() as i32; + // 求解問題 f(0, n-1) + dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + let index = binary_search(&nums, target); + println!("目標元素 6 的索引 = {index}"); +} diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/build_tree.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/build_tree.rs new file mode 100644 index 0000000000..4bf9f73d8f --- /dev/null +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/build_tree.rs @@ -0,0 +1,56 @@ +/* + * File: build_tree.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; +use std::collections::HashMap; +use std::{cell::RefCell, rc::Rc}; + +/* 構建二元樹:分治 */ +fn dfs( + preorder: &[i32], + inorder_map: &HashMap, + i: i32, + l: i32, + r: i32, +) -> Option>> { + // 子樹區間為空時終止 + if r - l < 0 { + return None; + } + // 初始化根節點 + let root = TreeNode::new(preorder[i as usize]); + // 查詢 m ,從而劃分左右子樹 + let m = inorder_map.get(&preorder[i as usize]).unwrap(); + // 子問題:構建左子樹 + root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); + // 返回根節點 + Some(root) +} + +/* 構建二元樹 */ +fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let mut inorder_map: HashMap = HashMap::new(); + for i in 0..inorder.len() { + inorder_map.insert(inorder[i], i as i32); + } + let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); + root +} + +/* Driver Code */ +fn main() { + let preorder = [3, 9, 2, 1, 7]; + let inorder = [9, 3, 1, 2, 7]; + println!("中序走訪 = {:?}", preorder); + println!("前序走訪 = {:?}", inorder); + + let root = build_tree(&preorder, &inorder); + println!("構建的二元樹為:"); + print_util::print_tree(root.as_ref().unwrap()); +} diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs new file mode 100644 index 0000000000..7b76499246 --- /dev/null +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs @@ -0,0 +1,55 @@ +/* + * File: hanota.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +#![allow(non_snake_case)] + +/* 移動一個圓盤 */ +fn move_pan(src: &mut Vec, tar: &mut Vec) { + // 從 src 頂部拿出一個圓盤 + let pan = src.pop().unwrap(); + // 將圓盤放入 tar 頂部 + tar.push(pan); +} + +/* 求解河內塔問題 f(i) */ +fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 { + move_pan(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move_pan(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解河內塔問題 */ +fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { + let n = A.len() as i32; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +pub fn main() { + let mut A = vec![5, 4, 3, 2, 1]; + let mut B = Vec::new(); + let mut C = Vec::new(); + println!("初始狀態下:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); + + solve_hanota(&mut A, &mut B, &mut C); + + println!("圓盤移動完成後:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs new file mode 100644 index 0000000000..503e9ed7e3 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs @@ -0,0 +1,41 @@ +/* + * File: climbing_stairs_backtrack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯 */ +fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { + // 當爬到第 n 階時,方案數量加 1 + if state == n { + res[0] = res[0] + 1; + } + // 走訪所有選擇 + for &choice in choices { + // 剪枝:不允許越過第 n 階 + if state + choice > n { + continue; + } + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +fn climbing_stairs_backtrack(n: usize) -> i32 { + let choices = vec![1, 2]; // 可選擇向上爬 1 階或 2 階 + let state = 0; // 從第 0 階開始爬 + let mut res = Vec::new(); + res.push(0); // 使用 res[0] 記錄方案數量 + backtrack(&choices, state, n as i32, &mut res); + res[0] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_backtrack(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs new file mode 100644 index 0000000000..139ab1fe8f --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs @@ -0,0 +1,33 @@ +/* + * File: climbing_stairs_constraint_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +fn climbing_stairs_constraint_dp(n: usize) -> i32 { + if n == 1 || n == 2 { + return 1; + }; + // 初始化 dp 表,用於儲存子問題的解 + let mut dp = vec![vec![-1; 3]; n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3..=n { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + dp[n][1] + dp[n][2] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_constraint_dp(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs new file mode 100644 index 0000000000..06e63e302c --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs @@ -0,0 +1,29 @@ +/* + * File: climbing_stairs_dfs.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 搜尋 */ +fn dfs(i: usize) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i as i32; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1) + dfs(i - 2); + count +} + +/* 爬樓梯:搜尋 */ +fn climbing_stairs_dfs(n: usize) -> i32 { + dfs(n) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs new file mode 100644 index 0000000000..33ad73fde3 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs @@ -0,0 +1,37 @@ +/* + * File: climbing_stairs_dfs_mem.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 記憶化搜尋 */ +fn dfs(i: usize, mem: &mut [i32]) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i as i32; + } + // 若存在記錄 dp[i] ,則直接返回之 + if mem[i] != -1 { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + count +} + +/* 爬樓梯:記憶化搜尋 */ +fn climbing_stairs_dfs_mem(n: usize) -> i32 { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + let mut mem = vec![-1; n + 1]; + dfs(n, &mut mem) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs_mem(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs new file mode 100644 index 0000000000..2966b21c65 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs @@ -0,0 +1,48 @@ +/* + * File: climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +fn climbing_stairs_dp(n: usize) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if n == 1 || n == 2 { + return n as i32; + } + // 初始化 dp 表,用於儲存子問題的解 + let mut dp = vec![-1; n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3..=n { + dp[i] = dp[i - 1] + dp[i - 2]; + } + dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +fn climbing_stairs_dp_comp(n: usize) -> i32 { + if n == 1 || n == 2 { + return n as i32; + } + let (mut a, mut b) = (1, 2); + for _ in 3..=n { + let tmp = b; + b = a + b; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dp(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); + + let res = climbing_stairs_dp_comp(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/coin_change.rs b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change.rs new file mode 100644 index 0000000000..dc787677fc --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change.rs @@ -0,0 +1,75 @@ +/* + * File: coin_change.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 零錢兌換:動態規劃 */ +fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // 初始化 dp 表 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 狀態轉移:首行首列 + for a in 1..=amt { + dp[0][a] = max; + } + // 狀態轉移:其餘行和列 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); + } + } + } + if dp[n][amt] != max { + return dp[n][amt] as i32; + } else { + -1 + } +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // 初始化 dp 表 + let mut dp = vec![0; amt + 1]; + dp.fill(max); + dp[0] = 0; + // 狀態轉移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); + } + } + } + if dp[amt] != max { + return dp[amt] as i32; + } else { + -1 + } +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 4; + + // 動態規劃 + let res = coin_change_dp(&coins, amt); + println!("湊到目標金額所需的最少硬幣數量為 {res}"); + + // 空間最佳化後的動態規劃 + let res = coin_change_dp_comp(&coins, amt); + println!("湊到目標金額所需的最少硬幣數量為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/coin_change_ii.rs b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change_ii.rs new file mode 100644 index 0000000000..3868934e28 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change_ii.rs @@ -0,0 +1,64 @@ +/* + * File: coin_change_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 零錢兌換 II:動態規劃 */ +fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 初始化首列 + for i in 0..=n { + dp[i][0] = 1; + } + // 狀態轉移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; + } + } + } + dp[n][amt] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // 初始化 dp 表 + let mut dp = vec![0; amt + 1]; + dp[0] = 1; + // 狀態轉移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; + } + } + } + dp[amt] +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 5; + + // 動態規劃 + let res = coin_change_ii_dp(&coins, amt); + println!("湊出目標金額的硬幣組合數量為 {res}"); + + // 空間最佳化後的動態規劃 + let res = coin_change_ii_dp_comp(&coins, amt); + println!("湊出目標金額的硬幣組合數量為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/edit_distance.rs b/zh-hant/codes/rust/chapter_dynamic_programming/edit_distance.rs new file mode 100644 index 0000000000..d037328f5a --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/edit_distance.rs @@ -0,0 +1,145 @@ +/* + * File: edit_distance.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 編輯距離:暴力搜尋 */ +fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { + // 若 s 和 t 都為空,則返回 0 + if i == 0 && j == 0 { + return 0; + } + // 若 s 為空,則返回 t 長度 + if i == 0 { + return j as i32; + } + // 若 t 為空,則返回 s 長度 + if j == 0 { + return i as i32; + } + // 若兩字元相等,則直接跳過此兩字元 + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs(s, t, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = edit_distance_dfs(s, t, i, j - 1); + let delete = edit_distance_dfs(s, t, i - 1, j); + let replace = edit_distance_dfs(s, t, i - 1, j - 1); + // 返回最少編輯步數 + std::cmp::min(std::cmp::min(insert, delete), replace) + 1 +} + +/* 編輯距離:記憶化搜尋 */ +fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { + // 若 s 和 t 都為空,則返回 0 + if i == 0 && j == 0 { + return 0; + } + // 若 s 為空,則返回 t 長度 + if i == 0 { + return j as i32; + } + // 若 t 為空,則返回 s 長度 + if j == 0 { + return i as i32; + } + // 若已有記錄,則直接返回之 + if mem[i][j] != -1 { + return mem[i][j]; + } + // 若兩字元相等,則直接跳過此兩字元 + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); + let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); + let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; + mem[i][j] +} + +/* 編輯距離:動態規劃 */ +fn edit_distance_dp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![vec![0; m + 1]; n + 1]; + // 狀態轉移:首行首列 + for i in 1..=n { + dp[i][0] = i as i32; + } + for j in 1..m { + dp[0][j] = j as i32; + } + // 狀態轉移:其餘行和列 + for i in 1..=n { + for j in 1..=m { + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = + std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![0; m + 1]; + // 狀態轉移:首行 + for j in 1..m { + dp[j] = j as i32; + } + // 狀態轉移:其餘行 + for i in 1..=n { + // 狀態轉移:首列 + let mut leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i as i32; + // 狀態轉移:其餘列 + for j in 1..=m { + let temp = dp[j]; + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + dp[m] +} + +/* Driver Code */ +pub fn main() { + let s = "bag"; + let t = "pack"; + let (n, m) = (s.len(), t.len()); + + // 暴力搜尋 + let res = edit_distance_dfs(s, t, n, m); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); + + // 記憶搜尋 + let mut mem = vec![vec![0; m + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); + + // 動態規劃 + let res = edit_distance_dp(s, t); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); + + // 空間最佳化後的動態規劃 + let res = edit_distance_dp_comp(s, t); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/knapsack.rs b/zh-hant/codes/rust/chapter_dynamic_programming/knapsack.rs new file mode 100644 index 0000000000..ba5a6d5427 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/knapsack.rs @@ -0,0 +1,113 @@ +/* + * File: knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 0-1 背包:暴力搜尋 */ +fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c as i32 { + return knapsack_dfs(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsack_dfs(wgt, val, i - 1, c); + let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + std::cmp::max(no, yes) +} + +/* 0-1 背包:記憶化搜尋 */ +fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0; + } + // 若已有記錄,則直接返回 + if mem[i][c] != -1 { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c as i32 { + return knapsack_dfs_mem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); + let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = std::cmp::max(no, yes); + mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 狀態轉移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = std::cmp::max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], + ); + } + } + } + dp[n][cap] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![0; cap + 1]; + // 狀態轉移 + for i in 1..=n { + // 倒序走訪 + for c in (1..=cap).rev() { + if wgt[i - 1] <= c as i32 { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap: usize = 50; + let n = wgt.len(); + + // 暴力搜尋 + let res = knapsack_dfs(&wgt, &val, n, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 記憶搜尋 + let mut mem = vec![vec![0; cap + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 動態規劃 + let res = knapsack_dp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 空間最佳化後的動態規劃 + let res = knapsack_dp_comp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs b/zh-hant/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs new file mode 100644 index 0000000000..da54f7a953 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs @@ -0,0 +1,52 @@ +/* + * File: min_cost_climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::cmp; + +/* 爬樓梯最小代價:動態規劃 */ +fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + let mut dp = vec![-1; n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3..=n { + dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; + } + dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + }; + let (mut a, mut b) = (cost[1], cost[2]); + for i in 3..=n { + let tmp = b; + b = cmp::min(a, tmp) + cost[i]; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + println!("輸入樓梯的代價串列為 {:?}", &cost); + + let res = min_cost_climbing_stairs_dp(&cost); + println!("爬完樓梯的最低代價為 {res}"); + + let res = min_cost_climbing_stairs_dp_comp(&cost); + println!("爬完樓梯的最低代價為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/min_path_sum.rs b/zh-hant/codes/rust/chapter_dynamic_programming/min_path_sum.rs new file mode 100644 index 0000000000..11c4c3afcd --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/min_path_sum.rs @@ -0,0 +1,120 @@ +/* + * File: min_path_sum.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 最小路徑和:暴力搜尋 */ +fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return i32::MAX; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + let up = min_path_sum_dfs(grid, i - 1, j); + let left = min_path_sum_dfs(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + std::cmp::min(left, up) + grid[i as usize][j as usize] +} + +/* 最小路徑和:記憶化搜尋 */ +fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return i32::MAX; + } + // 若已有記錄,則直接返回 + if mem[i as usize][j as usize] != -1 { + return mem[i as usize][j as usize]; + } + // 左邊和上邊單元格的最小路徑代價 + let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); + let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; + mem[i as usize][j as usize] +} + +/* 最小路徑和:動態規劃 */ +fn min_path_sum_dp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // 初始化 dp 表 + let mut dp = vec![vec![0; m]; n]; + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for j in 1..m { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for i in 1..n { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for i in 1..n { + for j in 1..m { + dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + dp[n - 1][m - 1] +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // 初始化 dp 表 + let mut dp = vec![0; m]; + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for j in 1..m { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for i in 1..n { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for j in 1..m { + dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + dp[m - 1] +} + +/* Driver Code */ +pub fn main() { + let grid = vec![ + vec![1, 3, 1, 5], + vec![2, 2, 4, 2], + vec![5, 3, 2, 1], + vec![4, 3, 5, 2], + ]; + let (n, m) = (grid.len(), grid[0].len()); + + // 暴力搜尋 + let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); + println!("從左上角到右下角的最小路徑和為 {res}"); + + // 記憶化搜尋 + let mut mem = vec![vec![0; m]; n]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); + println!("從左上角到右下角的最小路徑和為 {res}"); + + // 動態規劃 + let res = min_path_sum_dp(&grid); + println!("從左上角到右下角的最小路徑和為 {res}"); + + // 空間最佳化後的動態規劃 + let res = min_path_sum_dp_comp(&grid); + println!("從左上角到右下角的最小路徑和為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs b/zh-hant/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs new file mode 100644 index 0000000000..fea0159144 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs @@ -0,0 +1,60 @@ +/* + * File: unbounded_knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 完全背包:動態規劃 */ +fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 狀態轉移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![0; cap + 1]; + // 狀態轉移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [1, 2, 3]; + let val = [5, 11, 15]; + let cap: usize = 4; + + // 動態規劃 + let res = unbounded_knapsack_dp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 空間最佳化後的動態規劃 + let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_adjacency_list.rs b/zh-hant/codes/rust/chapter_graph/graph_adjacency_list.rs new file mode 100644 index 0000000000..c06e5fae99 --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_adjacency_list.rs @@ -0,0 +1,142 @@ +/* + * File: graph_adjacency_list.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; + +use std::collections::HashMap; + +/* 基於鄰接表實現的無向圖型別 */ +pub struct GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + pub adj_list: HashMap>, +} + +impl GraphAdjList { + /* 建構子 */ + pub fn new(edges: Vec<[Vertex; 2]>) -> Self { + let mut graph = GraphAdjList { + adj_list: HashMap::new(), + }; + // 新增所有頂點和邊 + for edge in edges { + graph.add_vertex(edge[0]); + graph.add_vertex(edge[1]); + graph.add_edge(edge[0], edge[1]); + } + + graph + } + + /* 獲取頂點數量 */ + #[allow(unused)] + pub fn size(&self) -> usize { + self.adj_list.len() + } + + /* 新增邊 */ + pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 新增邊 vet1 - vet2 + self.adj_list.get_mut(&vet1).unwrap().push(vet2); + self.adj_list.get_mut(&vet2).unwrap().push(vet1); + } + + /* 刪除邊 */ + #[allow(unused)] + pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 刪除邊 vet1 - vet2 + self.adj_list + .get_mut(&vet1) + .unwrap() + .retain(|&vet| vet != vet2); + self.adj_list + .get_mut(&vet2) + .unwrap() + .retain(|&vet| vet != vet1); + } + + /* 新增頂點 */ + pub fn add_vertex(&mut self, vet: Vertex) { + if self.adj_list.contains_key(&vet) { + return; + } + // 在鄰接表中新增一個新鏈結串列 + self.adj_list.insert(vet, vec![]); + } + + /* 刪除頂點 */ + #[allow(unused)] + pub fn remove_vertex(&mut self, vet: Vertex) { + if !self.adj_list.contains_key(&vet) { + panic!("value error"); + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + self.adj_list.remove(&vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for list in self.adj_list.values_mut() { + list.retain(|&v| v != vet); + } + } + + /* 列印鄰接表 */ + pub fn print(&self) { + println!("鄰接表 ="); + for (vertex, list) in &self.adj_list { + let list = list.iter().map(|vertex| vertex.val).collect::>(); + println!("{}: {:?},", vertex.val, list); + } + } +} + +/* Driver Code */ +#[allow(unused)] +fn main() { + /* 初始化無向圖 */ + let v = vals_to_vets(vec![1, 3, 2, 5, 4]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + + let mut graph = GraphAdjList::new(edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.add_edge(v[0], v[2]); + println!("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.remove_edge(v[0], v[1]); + println!("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + let v5 = Vertex { val: 6 }; + graph.add_vertex(v5); + println!("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.remove_vertex(v[1]); + println!("\n刪除頂點 3 後,圖為"); + graph.print(); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_adjacency_matrix.rs b/zh-hant/codes/rust/chapter_graph/graph_adjacency_matrix.rs new file mode 100644 index 0000000000..f95d4f9896 --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_adjacency_matrix.rs @@ -0,0 +1,136 @@ +/* + * File: graph_adjacency_matrix.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 基於鄰接矩陣實現的無向圖型別 */ +pub struct GraphAdjMat { + // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + pub vertices: Vec, + // 鄰接矩陣,行列索引對應“頂點索引” + pub adj_mat: Vec>, +} + +impl GraphAdjMat { + /* 建構子 */ + pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { + let mut graph = GraphAdjMat { + vertices: vec![], + adj_mat: vec![], + }; + // 新增頂點 + for val in vertices { + graph.add_vertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for edge in edges { + graph.add_edge(edge[0], edge[1]) + } + + graph + } + + /* 獲取頂點數量 */ + pub fn size(&self) -> usize { + self.vertices.len() + } + + /* 新增頂點 */ + pub fn add_vertex(&mut self, val: i32) { + let n = self.size(); + // 向頂點串列中新增新頂點的值 + self.vertices.push(val); + // 在鄰接矩陣中新增一行 + self.adj_mat.push(vec![0; n]); + // 在鄰接矩陣中新增一列 + for row in &mut self.adj_mat { + row.push(0); + } + } + + /* 刪除頂點 */ + pub fn remove_vertex(&mut self, index: usize) { + if index >= self.size() { + panic!("index error") + } + // 在頂點串列中移除索引 index 的頂點 + self.vertices.remove(index); + // 在鄰接矩陣中刪除索引 index 的行 + self.adj_mat.remove(index); + // 在鄰接矩陣中刪除索引 index 的列 + for row in &mut self.adj_mat { + row.remove(index); + } + } + + /* 新增邊 */ + pub fn add_edge(&mut self, i: usize, j: usize) { + // 參數 i, j 對應 vertices 元素索引 + // 索引越界與相等處理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + self.adj_mat[i][j] = 1; + self.adj_mat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + pub fn remove_edge(&mut self, i: usize, j: usize) { + // 參數 i, j 對應 vertices 元素索引 + // 索引越界與相等處理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + self.adj_mat[i][j] = 0; + self.adj_mat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + pub fn print(&self) { + println!("頂點串列 = {:?}", self.vertices); + println!("鄰接矩陣 ="); + println!("["); + for row in &self.adj_mat { + println!(" {:?},", row); + } + println!("]") + } +} + +/* Driver Code */ +fn main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + let vertices = vec![1, 3, 2, 5, 4]; + let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; + let mut graph = GraphAdjMat::new(vertices, edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.add_edge(0, 2); + println!("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.remove_edge(0, 1); + println!("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + graph.add_vertex(6); + println!("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.remove_vertex(1); + println!("\n刪除頂點 3 後,圖為"); + graph.print(); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_bfs.rs b/zh-hant/codes/rust/chapter_graph/graph_bfs.rs new file mode 100644 index 0000000000..4c0fbccfd3 --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_bfs.rs @@ -0,0 +1,70 @@ +/* + * File: graph_bfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::{HashSet, VecDeque}; + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 頂點走訪序列 + let mut res = vec![]; + // 雜湊集合,用於記錄已被訪問過的頂點 + let mut visited = HashSet::new(); + visited.insert(start_vet); + // 佇列用於實現 BFS + let mut que = VecDeque::new(); + que.push_back(start_vet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while !que.is_empty() { + let vet = que.pop_front().unwrap(); // 佇列首頂點出隊 + res.push(vet); // 記錄訪問頂點 + + // 走訪該頂點的所有鄰接頂點 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳過已被訪問的頂點 + } + que.push_back(adj_vet); // 只入列未訪問的頂點 + visited.insert(adj_vet); // 標記該頂點已被訪問 + } + } + } + // 返回頂點走訪序列 + res +} + +/* Driver Code */ +fn main() { + /* 初始化無向圖 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let edges = vec![ + [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]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 廣度優先走訪 */ + let res = graph_bfs(graph, v[0]); + println!("\n廣度優先走訪(BFS)頂點序列為"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_dfs.rs b/zh-hant/codes/rust/chapter_graph/graph_dfs.rs new file mode 100644 index 0000000000..b6e3c4c7d9 --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_dfs.rs @@ -0,0 +1,61 @@ +/* + * File: graph_dfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::HashSet; + +/* 深度優先走訪輔助函式 */ +fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { + res.push(vet); // 記錄訪問頂點 + visited.insert(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adj_vet); + } + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 頂點走訪序列 + let mut res = vec![]; + // 雜湊集合,用於記錄已被訪問過的頂點 + let mut visited = HashSet::new(); + dfs(&graph, &mut visited, &mut res, start_vet); + + res +} + +/* Driver Code */ +fn main() { + /* 初始化無向圖 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 深度優先走訪 */ + let res = graph_dfs(graph, v[0]); + println!("\n深度優先走訪(DFS)頂點序列為"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/zh-hant/codes/rust/chapter_greedy/coin_change_greedy.rs b/zh-hant/codes/rust/chapter_greedy/coin_change_greedy.rs new file mode 100644 index 0000000000..2104eff817 --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/coin_change_greedy.rs @@ -0,0 +1,54 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 零錢兌換:貪婪 */ +fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { + // 假設 coins 串列有序 + let mut i = coins.len() - 1; + let mut count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while amt > 0 { + // 找到小於且最接近剩餘金額的硬幣 + while i > 0 && coins[i] > amt { + i -= 1; + } + // 選擇 coins[i] + amt -= coins[i]; + count += 1; + } + // 若未找到可行方案,則返回 -1 + if amt == 0 { + count + } else { + -1 + } +} + +/* Driver Code */ +fn main() { + // 貪婪:能夠保證找到全域性最優解 + let coins = [1, 5, 10, 20, 50, 100]; + let amt = 186; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); + + // 貪婪:無法保證找到全域性最優解 + let coins = [1, 20, 50]; + let amt = 60; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); + println!("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + let coins = [1, 49, 50]; + let amt = 98; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); + println!("實際上需要的最少數量為 2 ,即 49 + 49"); +} diff --git a/zh-hant/codes/rust/chapter_greedy/fractional_knapsack.rs b/zh-hant/codes/rust/chapter_greedy/fractional_knapsack.rs new file mode 100644 index 0000000000..471469df76 --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/fractional_knapsack.rs @@ -0,0 +1,59 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 物品 */ +struct Item { + w: i32, // 物品重量 + v: i32, // 物品價值 +} + +impl Item { + fn new(w: i32, v: i32) -> Self { + Self { w, v } + } +} + +/* 分數背包:貪婪 */ +fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { + // 建立物品串列,包含兩個屬性:重量、價值 + let mut items = wgt + .iter() + .zip(val.iter()) + .map(|(&w, &v)| Item::new(w, v)) + .collect::>(); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort_by(|a, b| { + (b.v as f64 / b.w as f64) + .partial_cmp(&(a.v as f64 / a.w as f64)) + .unwrap() + }); + // 迴圈貪婪選擇 + let mut res = 0.0; + for item in &items { + if item.w <= cap { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v as f64; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += item.v as f64 / item.w as f64 * cap as f64; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + res +} + +/* Driver Code */ +fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap = 50; + + // 貪婪演算法 + let res = fractional_knapsack(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {}", res); +} diff --git a/zh-hant/codes/rust/chapter_greedy/max_capacity.rs b/zh-hant/codes/rust/chapter_greedy/max_capacity.rs new file mode 100644 index 0000000000..216a0b066a --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/max_capacity.rs @@ -0,0 +1,36 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大容量:貪婪 */ +fn max_capacity(ht: &[i32]) -> i32 { + // 初始化 i, j,使其分列陣列兩端 + let mut i = 0; + let mut j = ht.len() - 1; + // 初始最大容量為 0 + let mut res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while i < j { + // 更新最大容量 + let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; + res = std::cmp::max(res, cap); + // 向內移動短板 + if ht[i] < ht[j] { + i += 1; + } else { + j -= 1; + } + } + res +} + +/* Driver Code */ +fn main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪婪演算法 + let res = max_capacity(&ht); + println!("最大容量為 {}", res); +} diff --git a/zh-hant/codes/rust/chapter_greedy/max_product_cutting.rs b/zh-hant/codes/rust/chapter_greedy/max_product_cutting.rs new file mode 100644 index 0000000000..a31a55a04f --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/max_product_cutting.rs @@ -0,0 +1,35 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大切分乘積:貪婪 */ +fn max_product_cutting(n: i32) -> i32 { + // 當 n <= 3 時,必須切分出一個 1 + if n <= 3 { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a = n / 3; + let b = n % 3; + if b == 1 { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + 3_i32.pow(a as u32 - 1) * 2 * 2 + } else if b == 2 { + // 當餘數為 2 時,不做處理 + 3_i32.pow(a as u32) * 2 + } else { + // 當餘數為 0 時,不做處理 + 3_i32.pow(a as u32) + } +} + +/* Driver Code */ +fn main() { + let n = 58; + + // 貪婪演算法 + let res = max_product_cutting(n); + println!("最大切分乘積為 {}", res); +} diff --git a/zh-hant/codes/rust/chapter_hashing/array_hash_map.rs b/zh-hant/codes/rust/chapter_hashing/array_hash_map.rs new file mode 100644 index 0000000000..4b62198c8f --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/array_hash_map.rs @@ -0,0 +1,124 @@ +/** + * File: array_hash_map.rs + * Created Time: 2023-2-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com) + */ + +/* 鍵值對 */ +#[derive(Debug, Clone, PartialEq)] +pub struct Pair { + pub key: i32, + pub val: String, +} +/* 基於陣列實現的雜湊表 */ +pub struct ArrayHashMap { + buckets: Vec>, +} + +impl ArrayHashMap { + pub fn new() -> ArrayHashMap { + // 初始化陣列,包含 100 個桶 + Self { + buckets: vec![None; 100], + } + } + + /* 雜湊函式 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % 100 + } + + /* 查詢操作 */ + pub fn get(&self, key: i32) -> Option<&String> { + let index = self.hash_func(key); + self.buckets[index].as_ref().map(|pair| &pair.val) + } + + /* 新增操作 */ + pub fn put(&mut self, key: i32, val: &str) { + let index = self.hash_func(key); + self.buckets[index] = Some(Pair { + key, + val: val.to_string(), + }); + } + + /* 刪除操作 */ + pub fn remove(&mut self, key: i32) { + let index = self.hash_func(key); + // 置為 None ,代表刪除 + self.buckets[index] = None; + } + + /* 獲取所有鍵值對 */ + pub fn entry_set(&self) -> Vec<&Pair> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref()) + .collect() + } + + /* 獲取所有鍵 */ + pub fn key_set(&self) -> Vec<&i32> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) + .collect() + } + + /* 獲取所有值 */ + pub fn value_set(&self) -> Vec<&String> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) + .collect() + } + + /* 列印雜湊表 */ + pub fn print(&self) { + for pair in self.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + } +} + +fn main() { + /* 初始化雜湊表 */ + let mut map = ArrayHashMap::new(); + /*新增操作 */ + // 在雜湊表中新增鍵值對(key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + println!("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(15937).unwrap(); + println!("\n輸入學號 15937 ,查詢到姓名 {}", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + println!("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.print(); + + /* 走訪雜湊表 */ + println!("\n走訪鍵值對 Key->Value"); + for pair in map.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + + println!("\n單獨走訪鍵 Key"); + for key in map.key_set() { + println!("{}", key); + } + + println!("\n單獨走訪值 Value"); + for val in map.value_set() { + println!("{}", val); + } +} diff --git a/zh-hant/codes/rust/chapter_hashing/build_in_hash.rs b/zh-hant/codes/rust/chapter_hashing/build_in_hash.rs new file mode 100644 index 0000000000..f4dee23bde --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/build_in_hash.rs @@ -0,0 +1,49 @@ +/* + * File: build_in_hash.rs + * Created Time: 2023-7-6 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::ListNode; + +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +/* Driver Code */ +fn main() { + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + println!("整數 {} 的雜湊值為 {}", num, hash_num); + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + println!("布林量 {} 的雜湊值為 {}", bol, hash_bol); + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + println!("小數 {} 的雜湊值為 {}", dec, hash_dec); + + let str = "Hello 演算法"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + println!("字串 {} 的雜湊值為 {}", str, hash_str); + + let arr = (&12836, &"小哈"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + println!("元組 {:?} 的雜湊值為 {}", arr, hash_tup); + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + println!("節點物件 {:?} 的雜湊值為{}", node, hash); +} diff --git a/zh-hant/codes/rust/chapter_hashing/hash_map.rs b/zh-hant/codes/rust/chapter_hashing/hash_map.rs new file mode 100644 index 0000000000..330f66c8ca --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/hash_map.rs @@ -0,0 +1,48 @@ +/* + * File: hash_map.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::HashMap; + +/* Driver Code */ +pub fn main() { + // 初始化雜湊表 + let mut map = HashMap::new(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, value) + map.insert(12836, "小哈"); + map.insert(15937, "小囉"); + map.insert(16750, "小算"); + map.insert(13276, "小法"); + map.insert(10583, "小鴨"); + println!("\n新增完成後,雜湊表為\nKey -> Value"); + print_util::print_hash_map(&map); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(&15937).copied().unwrap(); + println!("\n輸入學號 15937 ,查詢到姓名 {name}"); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, value) + _ = map.remove(&10583); + println!("\n刪除 10583 後,雜湊表為\nKey -> Value"); + print_util::print_hash_map(&map); + + // 走訪雜湊表 + println!("\n走訪鍵值對 Key->Value"); + print_util::print_hash_map(&map); + println!("\n單獨走訪鍵 Key"); + for key in map.keys() { + println!("{key}"); + } + println!("\n單獨走訪值 value"); + for value in map.values() { + println!("{value}"); + } +} diff --git a/zh-hant/codes/rust/chapter_hashing/hash_map_chaining.rs b/zh-hant/codes/rust/chapter_hashing/hash_map_chaining.rs new file mode 100644 index 0000000000..1de42c332a --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/hash_map_chaining.rs @@ -0,0 +1,160 @@ +/* + * File: hash_map_chaining.rs + * Created Time: 2023-07-07 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +#[derive(Clone)] +/* 鍵值對 */ +struct Pair { + key: i32, + val: String, +} + +/* 鏈式位址雜湊表 */ +struct HashMapChaining { + size: usize, + capacity: usize, + load_thres: f32, + extend_ratio: usize, + buckets: Vec>, +} + +impl HashMapChaining { + /* 建構子 */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![vec![]; 4], + } + } + + /* 雜湊函式 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % self.capacity + } + + /* 負載因子 */ + fn load_factor(&self) -> f32 { + self.size as f32 / self.capacity as f32 + } + + /* 刪除操作 */ + fn remove(&mut self, key: i32) -> Option { + let index = self.hash_func(key); + + // 走訪桶,從中刪除鍵值對 + for (i, p) in self.buckets[index].iter_mut().enumerate() { + if p.key == key { + let pair = self.buckets[index].remove(i); + self.size -= 1; + return Some(pair.val); + } + } + + // 若未找到 key ,則返回 None + None + } + + /* 擴容雜湊表 */ + fn extend(&mut self) { + // 暫存原雜湊表 + let buckets_tmp = std::mem::take(&mut self.buckets); + + // 初始化擴容後的新雜湊表 + self.capacity *= self.extend_ratio; + self.buckets = vec![Vec::new(); self.capacity as usize]; + self.size = 0; + + // 將鍵值對從原雜湊表搬運至新雜湊表 + for bucket in buckets_tmp { + for pair in bucket { + self.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + fn print(&self) { + for bucket in &self.buckets { + let mut res = Vec::new(); + for pair in bucket { + res.push(format!("{} -> {}", pair.key, pair.val)); + } + println!("{:?}", res); + } + } + + /* 新增操作 */ + fn put(&mut self, key: i32, val: String) { + // 當負載因子超過閾值時,執行擴容 + if self.load_factor() > self.load_thres { + self.extend(); + } + + let index = self.hash_func(key); + + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for pair in self.buckets[index].iter_mut() { + if pair.key == key { + pair.val = val; + return; + } + } + + // 若無該 key ,則將鍵值對新增至尾部 + let pair = Pair { key, val }; + self.buckets[index].push(pair); + self.size += 1; + } + + /* 查詢操作 */ + fn get(&self, key: i32) -> Option<&str> { + let index = self.hash_func(key); + + // 走訪桶,若找到 key ,則返回對應 val + for pair in self.buckets[index].iter() { + if pair.key == key { + return Some(&pair.val); + } + } + + // 若未找到 key ,則返回 None + None + } +} + +/* Driver Code */ +pub fn main() { + /* 初始化雜湊表 */ + let mut map = HashMapChaining::new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈".to_string()); + map.put(15937, "小囉".to_string()); + map.put(16750, "小算".to_string()); + map.put(13276, "小法".to_string()); + map.put(10583, "小鴨".to_string()); + println!("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + println!( + "\n輸入學號 13276,查詢到姓名 {}", + match map.get(13276) { + Some(value) => value, + None => "Not a valid Key", + } + ); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + println!("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.print(); +} diff --git a/zh-hant/codes/rust/chapter_hashing/hash_map_open_addressing.rs b/zh-hant/codes/rust/chapter_hashing/hash_map_open_addressing.rs new file mode 100644 index 0000000000..984c5a5a3e --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/hash_map_open_addressing.rs @@ -0,0 +1,181 @@ +/* + * File: hash_map_open_addressing.rs + * Created Time: 2023-07-16 + * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) + */ +#![allow(non_snake_case)] +#![allow(unused)] + +mod array_hash_map; + +use array_hash_map::Pair; + +/* 開放定址雜湊表 */ +struct HashMapOpenAddressing { + size: usize, // 鍵值對數量 + capacity: usize, // 雜湊表容量 + load_thres: f64, // 觸發擴容的負載因子閾值 + extend_ratio: usize, // 擴容倍數 + buckets: Vec>, // 桶陣列 + TOMBSTONE: Option, // 刪除標記 +} + +impl HashMapOpenAddressing { + /* 建構子 */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![None; 4], + TOMBSTONE: Some(Pair { + key: -1, + val: "-1".to_string(), + }), + } + } + + /* 雜湊函式 */ + fn hash_func(&self, key: i32) -> usize { + (key % self.capacity as i32) as usize + } + + /* 負載因子 */ + fn load_factor(&self) -> f64 { + self.size as f64 / self.capacity as f64 + } + + /* 搜尋 key 對應的桶索引 */ + fn find_bucket(&mut self, key: i32) -> usize { + let mut index = self.hash_func(key); + let mut first_tombstone = -1; + // 線性探查,當遇到空桶時跳出 + while self.buckets[index].is_some() { + // 若遇到 key,返回對應的桶索引 + if self.buckets[index].as_ref().unwrap().key == key { + // 若之前遇到了刪除標記,則將建值對移動至該索引 + if first_tombstone != -1 { + self.buckets[first_tombstone as usize] = self.buckets[index].take(); + self.buckets[index] = self.TOMBSTONE.clone(); + return first_tombstone as usize; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { + first_tombstone = index as i32; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % self.capacity; + } + // 若 key 不存在,則返回新增點的索引 + if first_tombstone == -1 { + index + } else { + first_tombstone as usize + } + } + + /* 查詢操作 */ + fn get(&mut self, key: i32) -> Option<&str> { + // 搜尋 key 對應的桶索引 + let index = self.find_bucket(key); + // 若找到鍵值對,則返回對應 val + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + return self.buckets[index].as_ref().map(|pair| &pair.val as &str); + } + // 若鍵值對不存在,則返回 null + None + } + + /* 新增操作 */ + fn put(&mut self, key: i32, val: String) { + // 當負載因子超過閾值時,執行擴容 + if self.load_factor() > self.load_thres { + self.extend(); + } + // 搜尋 key 對應的桶索引 + let index = self.find_bucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index].as_mut().unwrap().val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + self.buckets[index] = Some(Pair { key, val }); + self.size += 1; + } + + /* 刪除操作 */ + fn remove(&mut self, key: i32) { + // 搜尋 key 對應的桶索引 + let index = self.find_bucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index] = self.TOMBSTONE.clone(); + self.size -= 1; + } + } + + /* 擴容雜湊表 */ + fn extend(&mut self) { + // 暫存原雜湊表 + let buckets_tmp = self.buckets.clone(); + // 初始化擴容後的新雜湊表 + self.capacity *= self.extend_ratio; + self.buckets = vec![None; self.capacity]; + self.size = 0; + + // 將鍵值對從原雜湊表搬運至新雜湊表 + for pair in buckets_tmp { + if pair.is_none() || pair == self.TOMBSTONE { + continue; + } + let pair = pair.unwrap(); + + self.put(pair.key, pair.val); + } + } + /* 列印雜湊表 */ + fn print(&self) { + for pair in &self.buckets { + if pair.is_none() { + println!("null"); + } else if pair == &self.TOMBSTONE { + println!("TOMBSTONE"); + } else { + let pair = pair.as_ref().unwrap(); + println!("{} -> {}", pair.key, pair.val); + } + } + } +} + +/* Driver Code */ +fn main() { + /* 初始化雜湊表 */ + let mut hashmap = HashMapOpenAddressing::new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hashmap.put(12836, "小哈".to_string()); + hashmap.put(15937, "小囉".to_string()); + hashmap.put(16750, "小算".to_string()); + hashmap.put(13276, "小法".to_string()); + hashmap.put(10583, "小鴨".to_string()); + + println!("\n新增完成後,雜湊表為\nKey -> Value"); + hashmap.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 val + let name = hashmap.get(13276).unwrap(); + println!("\n輸入學號 13276 ,查詢到姓名 {}", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750); + println!("\n刪除 16750 後,雜湊表為\nKey -> Value"); + hashmap.print(); +} diff --git a/zh-hant/codes/rust/chapter_hashing/simple_hash.rs b/zh-hant/codes/rust/chapter_hashing/simple_hash.rs new file mode 100644 index 0000000000..35b27d5603 --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/simple_hash.rs @@ -0,0 +1,70 @@ +/* + * File: simple_hash.rs + * Created Time: 2023-09-07 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 加法雜湊 */ +fn add_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* 乘法雜湊 */ +fn mul_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (31 * hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* 互斥或雜湊 */ +fn xor_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash ^= c as i64; + } + + (hash & MODULUS) as i32 +} + +/* 旋轉雜湊 */ +fn rot_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; + } + + hash as i32 +} + +/* Driver Code */ +fn main() { + let key = "Hello 演算法"; + + let hash = add_hash(key); + println!("加法雜湊值為 {hash}"); + + let hash = mul_hash(key); + println!("乘法雜湊值為 {hash}"); + + let hash = xor_hash(key); + println!("互斥或雜湊值為 {hash}"); + + let hash = rot_hash(key); + println!("旋轉雜湊值為 {hash}"); +} diff --git a/zh-hant/codes/rust/chapter_heap/heap.rs b/zh-hant/codes/rust/chapter_heap/heap.rs new file mode 100644 index 0000000000..1bf198ceaa --- /dev/null +++ b/zh-hant/codes/rust/chapter_heap/heap.rs @@ -0,0 +1,73 @@ +/* + * File: heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::BinaryHeap; + +fn test_push(heap: &mut BinaryHeap, val: i32, flag: i32) { + heap.push(flag * val); // 元素入堆積 + println!("\n元素 {} 入堆積後", val); + print_util::print_heap(heap.iter().map(|&val| flag * val).collect()); +} + +fn test_pop(heap: &mut BinaryHeap, flag: i32) { + let val = heap.pop().unwrap(); + println!("\n堆積頂元素 {} 出堆積後", flag * val); + print_util::print_heap(heap.iter().map(|&val| flag * val).collect()); +} + +/* Driver Code */ +fn main() { + /* 初始化堆積 */ + // 初始化小頂堆積 + #[allow(unused_assignments)] + let mut min_heap = BinaryHeap::new(); + // Rust 的 BinaryHeap 是大頂堆積,當入列時將元素值乘以 -1 將其反轉,當出列時將元素值乘以 -1 將其還原 + let min_heap_flag = -1; + // 初始化大頂堆積 + let mut max_heap = BinaryHeap::new(); + let max_heap_flag = 1; + + println!("\n以下測試樣例為大頂堆積"); + + /* 元素入堆積 */ + test_push(&mut max_heap, 1, max_heap_flag); + test_push(&mut max_heap, 3, max_heap_flag); + test_push(&mut max_heap, 2, max_heap_flag); + test_push(&mut max_heap, 5, max_heap_flag); + test_push(&mut max_heap, 4, max_heap_flag); + + /* 獲取堆積頂元素 */ + let peek = max_heap.peek().unwrap() * max_heap_flag; + println!("\n堆積頂元素為 {}", peek); + + /* 堆積頂元素出堆積 */ + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + + /* 獲取堆積大小 */ + let size = max_heap.len(); + println!("\n堆積元素數量為 {}", size); + + /* 判斷堆積是否為空 */ + let is_empty = max_heap.is_empty(); + println!("\n堆積是否為空 {}", is_empty); + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + min_heap = BinaryHeap::from( + vec![1, 3, 2, 5, 4] + .into_iter() + .map(|val| min_heap_flag * val) + .collect::>(), + ); + println!("\n輸入串列並建立小頂堆積後"); + print_util::print_heap(min_heap.iter().map(|&val| min_heap_flag * val).collect()); +} diff --git a/zh-hant/codes/rust/chapter_heap/my_heap.rs b/zh-hant/codes/rust/chapter_heap/my_heap.rs new file mode 100644 index 0000000000..493986a7ad --- /dev/null +++ b/zh-hant/codes/rust/chapter_heap/my_heap.rs @@ -0,0 +1,165 @@ +/* + * File: my_heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 大頂堆積 */ +struct MaxHeap { + // 使用 vector 而非陣列,這樣無須考慮擴容問題 + max_heap: Vec, +} + +impl MaxHeap { + /* 建構子,根據輸入串列建堆積 */ + fn new(nums: Vec) -> Self { + // 將串列元素原封不動新增進堆積 + let mut heap = MaxHeap { max_heap: nums }; + // 堆積化除葉節點以外的其他所有節點 + for i in (0..=Self::parent(heap.size() - 1)).rev() { + heap.sift_down(i); + } + heap + } + + /* 獲取左子節點的索引 */ + fn left(i: usize) -> usize { + 2 * i + 1 + } + + /* 獲取右子節點的索引 */ + fn right(i: usize) -> usize { + 2 * i + 2 + } + + /* 獲取父節點的索引 */ + fn parent(i: usize) -> usize { + (i - 1) / 2 // 向下整除 + } + + /* 交換元素 */ + fn swap(&mut self, i: usize, j: usize) { + self.max_heap.swap(i, j); + } + + /* 獲取堆積大小 */ + fn size(&self) -> usize { + self.max_heap.len() + } + + /* 判斷堆積是否為空 */ + fn is_empty(&self) -> bool { + self.max_heap.is_empty() + } + + /* 訪問堆積頂元素 */ + fn peek(&self) -> Option { + self.max_heap.first().copied() + } + + /* 元素入堆積 */ + fn push(&mut self, val: i32) { + // 新增節點 + self.max_heap.push(val); + // 從底至頂堆積化 + self.sift_up(self.size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + fn sift_up(&mut self, mut i: usize) { + loop { + // 節點 i 已經是堆積頂節點了,結束堆積化 + if i == 0 { + break; + } + // 獲取節點 i 的父節點 + let p = Self::parent(i); + // 當“節點無須修復”時,結束堆積化 + if self.max_heap[i] <= self.max_heap[p] { + break; + } + // 交換兩節點 + self.swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + fn pop(&mut self) -> i32 { + // 判空處理 + if self.is_empty() { + panic!("index out of bounds"); + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + self.swap(0, self.size() - 1); + // 刪除節點 + let val = self.max_heap.pop().unwrap(); + // 從頂至底堆積化 + self.sift_down(0); + // 返回堆積頂元素 + val + } + + /* 從節點 i 開始,從頂至底堆積化 */ + fn sift_down(&mut self, mut i: usize) { + loop { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let (l, r, mut ma) = (Self::left(i), Self::right(i), i); + if l < self.size() && self.max_heap[l] > self.max_heap[ma] { + ma = l; + } + if r < self.size() && self.max_heap[r] > self.max_heap[ma] { + ma = r; + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i { + break; + } + // 交換兩節點 + self.swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + fn print(&self) { + print_util::print_heap(self.max_heap.clone()); + } +} + +/* Driver Code */ +fn main() { + /* 初始化大頂堆積 */ + let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + println!("\n輸入串列並建堆積後"); + max_heap.print(); + + /* 獲取堆積頂元素 */ + let peek = max_heap.peek(); + if let Some(peek) = peek { + println!("\n堆積頂元素為 {}", peek); + } + + /* 元素入堆積 */ + let val = 7; + max_heap.push(val); + println!("\n元素 {} 入堆積後", val); + max_heap.print(); + + /* 堆積頂元素出堆積 */ + let peek = max_heap.pop(); + println!("\n堆積頂元素 {} 出堆積後", peek); + max_heap.print(); + + /* 獲取堆積大小 */ + let size = max_heap.size(); + println!("\n堆積元素數量為 {}", size); + + /* 判斷堆積是否為空 */ + let is_empty = max_heap.is_empty(); + println!("\n堆積是否為空 {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_heap/top_k.rs b/zh-hant/codes/rust/chapter_heap/top_k.rs new file mode 100644 index 0000000000..d16a3c28e8 --- /dev/null +++ b/zh-hant/codes/rust/chapter_heap/top_k.rs @@ -0,0 +1,39 @@ +/* + * File: top_k.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cmp::Reverse; +use std::collections::BinaryHeap; + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { + // BinaryHeap 是大頂堆積,使用 Reverse 將元素取反,從而實現小頂堆積 + let mut heap = BinaryHeap::>::new(); + // 將陣列的前 k 個元素入堆積 + for &num in nums.iter().take(k) { + heap.push(Reverse(num)); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for &num in nums.iter().skip(k) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if num > heap.peek().unwrap().0 { + heap.pop(); + heap.push(Reverse(num)); + } + } + heap +} + +/* Driver Code */ +fn main() { + let nums = vec![1, 7, 6, 3, 2]; + let k = 3; + + let res = top_k_heap(nums, k); + println!("最大的 {} 個元素為", k); + print_util::print_heap(res.into_iter().map(|item| item.0).collect()); +} diff --git a/zh-hant/codes/rust/chapter_searching/binary_search.rs b/zh-hant/codes/rust/chapter_searching/binary_search.rs new file mode 100644 index 0000000000..eba52b4eee --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/binary_search.rs @@ -0,0 +1,65 @@ +/* + * File: binary_search.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分搜尋(雙閉區間) */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + let mut i = 0; + let mut j = nums.len() as i32 - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while i <= j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + } else if nums[m as usize] > target { + // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 二分搜尋(左閉右開區間) */ +fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + let mut i = 0; + let mut j = nums.len() as i32; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while i < j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + } else if nums[m as usize] > target { + // 此情況說明 target 在區間 [i, m) 中 + j = m; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + let mut index = binary_search(&nums, target); + println!("目標元素 6 的索引 = {index}"); + + // 二分搜尋(左閉右開區間) + index = binary_search_lcro(&nums, target); + println!("目標元素 6 的索引 = {index}"); +} diff --git a/zh-hant/codes/rust/chapter_searching/binary_search_edge.rs b/zh-hant/codes/rust/chapter_searching/binary_search_edge.rs new file mode 100644 index 0000000000..19a496dd1c --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/binary_search_edge.rs @@ -0,0 +1,50 @@ +/* + * File: binary_search_edge.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ + +mod binary_search_insertion; + +use binary_search_insertion::binary_search_insertion; + +/* 二分搜尋最左一個 target */ +fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { + // 等價於查詢 target 的插入點 + let i = binary_search_insertion(nums, target); + // 未找到 target ,返回 -1 + if i == nums.len() as i32 || nums[i as usize] != target { + return -1; + } + // 找到 target ,返回索引 i + i +} + +/* 二分搜尋最右一個 target */ +fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { + // 轉化為查詢最左一個 target + 1 + let i = binary_search_insertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + let j = i - 1; + // 未找到 target ,返回 -1 + if j == -1 || nums[j as usize] != target { + return -1; + } + // 找到 target ,返回索引 j + j +} + +/* Driver Code */ +fn main() { + // 包含重複元素的陣列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n陣列 nums = {:?}", nums); + + // 二分搜尋左邊界和右邊界 + for target in [6, 7] { + let index = binary_search_left_edge(&nums, target); + println!("最左一個元素 {} 的索引為 {}", target, index); + let index = binary_search_right_edge(&nums, target); + println!("最右一個元素 {} 的索引為 {}", target, index); + } +} diff --git a/zh-hant/codes/rust/chapter_searching/binary_search_insertion.rs b/zh-hant/codes/rust/chapter_searching/binary_search_insertion.rs new file mode 100644 index 0000000000..3a6fc63f56 --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/binary_search_insertion.rs @@ -0,0 +1,61 @@ +/* + * File: binary_search_insertion.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ +#![allow(unused)] + +/* 二分搜尋插入點(無重複元素) */ +fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化雙閉區間 [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if nums[m as usize] > target { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; + } + } + // 未找到 target ,返回插入點 i + i +} + +/* 二分搜尋插入點(存在重複元素) */ +pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化雙閉區間 [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if nums[m as usize] > target { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + i +} + +/* Driver Code */ +fn main() { + // 無重複元素的陣列 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + println!("\n陣列 nums = {:?}", nums); + // 二分搜尋插入點 + for target in [6, 9] { + let index = binary_search_insertion_simple(&nums, target); + println!("元素 {} 的插入點的索引為 {}", target, index); + } + + // 包含重複元素的陣列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n陣列 nums = {:?}", nums); + // 二分搜尋插入點 + for target in [2, 6, 20] { + let index = binary_search_insertion(&nums, target); + println!("元素 {} 的插入點的索引為 {}", target, index); + } +} diff --git a/zh-hant/codes/rust/chapter_searching/hashing_search.rs b/zh-hant/codes/rust/chapter_searching/hashing_search.rs new file mode 100644 index 0000000000..d2b94c7a93 --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/hashing_search.rs @@ -0,0 +1,50 @@ +/* + * File: hashing_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* 雜湊查詢(陣列) */ +fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 None + map.get(&target) +} + +/* 雜湊查詢(鏈結串列) */ +fn hashing_search_linked_list( + map: &HashMap>>>, + target: i32, +) -> Option<&Rc>>> { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 None + map.get(&target) +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* 雜湊查詢(陣列) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化雜湊表 + let mut map = HashMap::new(); + for (i, num) in nums.iter().enumerate() { + map.insert(*num, i); // key: 元素,value: 索引 + } + let index = hashing_search_array(&map, target); + println!("目標元素 3 的索引 = {}", index.unwrap()); + + /* 雜湊查詢(鏈結串列) */ + let head = ListNode::arr_to_linked_list(&nums); + // 初始化雜湊表 + // let mut map1 = HashMap::new(); + let map1 = ListNode::linked_list_to_hashmap(head); + let node = hashing_search_linked_list(&map1, target); + println!("目標節點值 3 的對應節點物件為 {:?}", node); +} diff --git a/zh-hant/codes/rust/chapter_searching/linear_search.rs b/zh-hant/codes/rust/chapter_searching/linear_search.rs new file mode 100644 index 0000000000..1ef589cccf --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/linear_search.rs @@ -0,0 +1,54 @@ +/* + * File: linear_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* 線性查詢(陣列) */ +fn linear_search_array(nums: &[i32], target: i32) -> i32 { + // 走訪陣列 + for (i, num) in nums.iter().enumerate() { + // 找到目標元素,返回其索引 + if num == &target { + return i as i32; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列) */ +fn linear_search_linked_list( + head: Rc>>, + target: i32, +) -> Option>>> { + // 找到目標節點,返回之 + if head.borrow().val == target { + return Some(head); + }; + // 找到目標節點,返回之 + if let Some(node) = &head.borrow_mut().next { + return linear_search_linked_list(node.clone(), target); + } + // 未找到目標節點,返回 None + return None; +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* 在陣列中執行線性查詢 */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + let index = linear_search_array(&nums, target); + println!("目標元素 3 的索引 = {}", index); + + /* 在鏈結串列中執行線性查詢 */ + let head = ListNode::arr_to_linked_list(&nums); + let node = linear_search_linked_list(head.unwrap(), target); + println!("目標節點值 3 的對應節點物件為 {:?}", node); +} diff --git a/zh-hant/codes/rust/chapter_searching/two_sum.rs b/zh-hant/codes/rust/chapter_searching/two_sum.rs new file mode 100644 index 0000000000..5192d3accd --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/two_sum.rs @@ -0,0 +1,52 @@ +/* + * File: two_sum.rs + * Created Time: 2023-01-14 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::HashMap; + +/* 方法一:暴力列舉 */ +pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { + let size = nums.len(); + // 兩層迴圈,時間複雜度為 O(n^2) + for i in 0..size - 1 { + for j in i + 1..size { + if nums[i] + nums[j] == target { + return Some(vec![i as i32, j as i32]); + } + } + } + None +} + +/* 方法二:輔助雜湊表 */ +pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { + // 輔助雜湊表,空間複雜度為 O(n) + let mut dic = HashMap::new(); + // 單層迴圈,時間複雜度為 O(n) + for (i, num) in nums.iter().enumerate() { + match dic.get(&(target - num)) { + Some(v) => return Some(vec![*v as i32, i as i32]), + None => dic.insert(num, i as i32), + }; + } + None +} + +fn main() { + // ======= Test Case ======= + let nums = vec![2, 7, 11, 15]; + let target = 13; + + // ====== Driver Code ====== + // 方法一 + let res = two_sum_brute_force(&nums, target).unwrap(); + print!("方法一 res = "); + print_util::print_array(&res); + // 方法二 + let res = two_sum_hash_table(&nums, target).unwrap(); + print!("\n方法二 res = "); + print_util::print_array(&res); +} diff --git a/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs b/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs new file mode 100644 index 0000000000..8a89023179 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs @@ -0,0 +1,53 @@ +/* + * File: bubble_sort.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* 泡沫排序 */ +fn bubble_sort(nums: &mut [i32]) { + // 外迴圈:未排序區間為 [0, i] + for i in (1..nums.len()).rev() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + nums.swap(j, j + 1); + } + } + } +} + +/* 泡沫排序(標誌最佳化) */ +fn bubble_sort_with_flag(nums: &mut [i32]) { + // 外迴圈:未排序區間為 [0, i] + for i in (1..nums.len()).rev() { + let mut flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + nums.swap(j, j + 1); + flag = true; // 記錄交換元素 + } + } + if !flag { + break; // 此輪“冒泡”未交換任何元素,直接跳出 + }; + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + bubble_sort(&mut nums); + print!("泡沫排序完成後 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [4, 1, 3, 1, 5, 2]; + bubble_sort_with_flag(&mut nums1); + print!("\n泡沫排序完成後 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs b/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs new file mode 100644 index 0000000000..5efbebe1d5 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs @@ -0,0 +1,43 @@ +/* + * File: bucket_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 桶排序 */ +fn bucket_sort(nums: &mut [f64]) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + let k = nums.len() / 2; + let mut buckets = vec![vec![]; k]; + // 1. 將陣列元素分配到各個桶中 + for &num in nums.iter() { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + let i = (num * k as f64) as usize; + // 將 num 新增進桶 i + buckets[i].push(num); + } + // 2. 對各個桶執行排序 + for bucket in &mut buckets { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); + } + // 3. 走訪桶合併結果 + let mut i = 0; + for bucket in buckets.iter() { + for &num in bucket.iter() { + nums[i] = num; + i += 1; + } + } +} + +/* Driver Code */ +fn main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucket_sort(&mut nums); + print!("桶排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/counting_sort.rs b/zh-hant/codes/rust/chapter_sorting/counting_sort.rs new file mode 100644 index 0000000000..eac4c3f6d9 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/counting_sort.rs @@ -0,0 +1,70 @@ +/* + * File: counting_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +fn counting_sort_naive(nums: &mut [i32]) { + // 1. 統計陣列最大元素 m + let m = *nums.iter().max().unwrap(); + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + let mut counter = vec![0; m as usize + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + let mut i = 0; + for num in 0..m + 1 { + for _ in 0..counter[num as usize] { + nums[i] = num; + i += 1; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +fn counting_sort(nums: &mut [i32]) { + // 1. 統計陣列最大元素 m + let m = *nums.iter().max().unwrap() as usize; + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + let mut counter = vec![0; m + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for i in 0..m { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + let n = nums.len(); + let mut res = vec![0; n]; + for i in (0..n).rev() { + let num = nums[i]; + res[counter[num as usize] - 1] = num; // 將 num 放置到對應索引處 + counter[num as usize] -= 1; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + nums.copy_from_slice(&res) +} + +/* Driver Code */ +fn main() { + let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort_naive(&mut nums); + print!("計數排序(無法排序物件)完成後 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort(&mut nums1); + print!("\n計數排序完成後 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/zh-hant/codes/rust/chapter_sorting/heap_sort.rs b/zh-hant/codes/rust/chapter_sorting/heap_sort.rs new file mode 100644 index 0000000000..db1cbc95c6 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/heap_sort.rs @@ -0,0 +1,54 @@ +/* + * File: heap_sort.rs + * Created Time: 2023-07-04 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { + loop { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let mut 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; + } + // 交換兩節點 + nums.swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +fn heap_sort(nums: &mut [i32]) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for i in (0..nums.len() / 2).rev() { + sift_down(nums, nums.len(), i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for i in (1..nums.len()).rev() { + // 交換根節點與最右葉節點(交換首元素與尾元素) + nums.swap(0, i); + // 以根節點為起點,從頂至底進行堆積化 + sift_down(nums, i, 0); + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + heap_sort(&mut nums); + print!("堆積排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/insertion_sort.rs b/zh-hant/codes/rust/chapter_sorting/insertion_sort.rs new file mode 100644 index 0000000000..f99716f87f --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/insertion_sort.rs @@ -0,0 +1,29 @@ +/* + * File: insertion_sort.rs + * Created Time: 2023-02-13 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* 插入排序 */ +fn insertion_sort(nums: &mut [i32]) { + // 外迴圈:已排序區間為 [0, i-1] + for i in 1..nums.len() { + let (base, mut j) = (nums[i], (i - 1) as i32); + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while j >= 0 && nums[j as usize] > base { + nums[(j + 1) as usize] = nums[j as usize]; // 將 nums[j] 向右移動一位 + j -= 1; + } + nums[(j + 1) as usize] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + insertion_sort(&mut nums); + print!("插入排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/merge_sort.rs b/zh-hant/codes/rust/chapter_sorting/merge_sort.rs new file mode 100644 index 0000000000..5e75a127be --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/merge_sort.rs @@ -0,0 +1,66 @@ +/** + * File: merge_sort.rs + * Created Time: 2023-02-14 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +/* 合併左子陣列和右子陣列 */ +fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + let tmp_size = right - left + 1; + let mut tmp = vec![0; tmp_size]; + // 初始化左子陣列和右子陣列的起始索引 + let (mut i, mut j, mut k) = (left, mid + 1, 0); + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i]; + i += 1; + } else { + tmp[k] = nums[j]; + j += 1; + } + k += 1; + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while i <= mid { + tmp[k] = nums[i]; + k += 1; + i += 1; + } + while j <= right { + tmp[k] = nums[j]; + k += 1; + j += 1; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for k in 0..tmp_size { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +fn merge_sort(nums: &mut [i32], left: usize, right: usize) { + // 終止條件 + if left >= right { + return; // 當子陣列長度為 1 時終止遞迴 + } + + // 劃分階段 + let mid = left + (right - left) / 2; // 計算中點 + merge_sort(nums, left, mid); // 遞迴左子陣列 + merge_sort(nums, mid + 1, right); // 遞迴右子陣列 + + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +fn main() { + /* 合併排序 */ + let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; + let right = nums.len() - 1; + merge_sort(&mut nums, 0, right); + println!("合併排序完成後 nums = {:?}", nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/quick_sort.rs b/zh-hant/codes/rust/chapter_sorting/quick_sort.rs new file mode 100644 index 0000000000..eda8c9259c --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/quick_sort.rs @@ -0,0 +1,148 @@ +/** + * File: quick_sort.rs + * Created Time: 2023-02-16 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +/* 快速排序 */ +struct QuickSort; + +impl QuickSort { + /* 哨兵劃分 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 以 nums[left] 為基準數 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 從左向右找首個大於基準數的元素 + } + nums.swap(i, j); // 交換這兩個元素 + } + nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 + i // 返回基準數的索引 + } + + /* 快速排序 */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return; + } + // 哨兵劃分 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 遞迴左子陣列、右子陣列 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* 快速排序(中位基準數最佳化) */ +struct QuickSortMedian; + +impl QuickSortMedian { + /* 選取三個候選元素的中位數 */ + fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { + let (l, m, r) = (nums[left], nums[mid], 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 之間 + } + right + } + + /* 哨兵劃分(三數取中值) */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 選取三個候選元素的中位數 + let med = Self::median_three(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + nums.swap(left, med); + // 以 nums[left] 為基準數 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 從左向右找首個大於基準數的元素 + } + nums.swap(i, j); // 交換這兩個元素 + } + nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 + i // 返回基準數的索引 + } + + /* 快速排序 */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return; + } + // 哨兵劃分 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 遞迴左子陣列、右子陣列 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* 快速排序(尾遞迴最佳化) */ +struct QuickSortTailCall; + +impl QuickSortTailCall { + /* 哨兵劃分 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 以 nums[left] 為基準數 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 從左向右找首個大於基準數的元素 + } + nums.swap(i, j); // 交換這兩個元素 + } + nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 + i // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { + // 子陣列長度為 1 時終止 + while left < right { + // 哨兵劃分操作 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 對兩個子陣列中較短的那個執行快速排序 + if pivot - left < right - pivot { + Self::quick_sort(left, pivot - 1, nums); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + Self::quick_sort(pivot + 1, right, nums); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +fn main() { + /* 快速排序 */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序完成後 nums = {:?}", nums); + + /* 快速排序(中位基準數最佳化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序(中位基準數最佳化)完成後 nums = {:?}", nums); + + /* 快速排序(尾遞迴最佳化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序(尾遞迴最佳化)完成後 nums = {:?}", nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/radix_sort.rs b/zh-hant/codes/rust/chapter_sorting/radix_sort.rs new file mode 100644 index 0000000000..b991e915fd --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/radix_sort.rs @@ -0,0 +1,63 @@ +/* + * File: radix_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +fn digit(num: i32, exp: i32) -> usize { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return ((num / exp) % 10) as usize; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +fn counting_sort_digit(nums: &mut [i32], exp: i32) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + let mut counter = [0; 10]; + let n = nums.len(); + // 統計 0~9 各數字的出現次數 + for i in 0..n { + let d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for i in 1..10 { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + let mut res = vec![0; n]; + for i in (0..n).rev() { + let d = digit(nums[i], exp); + let j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d] -= 1; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + nums.copy_from_slice(&res); +} + +/* 基數排序 */ +fn radix_sort(nums: &mut [i32]) { + // 獲取陣列的最大元素,用於判斷最大位數 + let m = *nums.into_iter().max().unwrap(); + // 按照從低位到高位的順序走訪 + let mut exp = 1; + while exp <= m { + counting_sort_digit(nums, exp); + exp *= 10; + } +} + +/* Driver Code */ +fn main() { + // 基數排序 + let mut nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, + 63832996, + ]; + radix_sort(&mut nums); + print!("基數排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/selection_sort.rs b/zh-hant/codes/rust/chapter_sorting/selection_sort.rs new file mode 100644 index 0000000000..9c47336a71 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/selection_sort.rs @@ -0,0 +1,35 @@ +/* + * File: selection_sort.rs + * Created Time: 2023-05-30 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* 選擇排序 */ +fn selection_sort(nums: &mut [i32]) { + if nums.is_empty() { + return; + } + let n = nums.len(); + // 外迴圈:未排序區間為 [i, n-1] + for i in 0..n - 1 { + // 內迴圈:找到未排序區間內的最小元素 + let mut k = i; + for j in i + 1..n { + if nums[j] < nums[k] { + k = j; // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + nums.swap(i, k); + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + selection_sort(&mut nums); + print!("\n選擇排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/array_deque.rs b/zh-hant/codes/rust/chapter_stack_and_queue/array_deque.rs new file mode 100644 index 0000000000..5dbe16ef15 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/array_deque.rs @@ -0,0 +1,160 @@ +/* + * File: array_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; +/* 基於環形陣列實現的雙向佇列 */ +struct ArrayDeque { + nums: Vec, // 用於儲存雙向佇列元素的陣列 + front: usize, // 佇列首指標,指向佇列首元素 + que_size: usize, // 雙向佇列長度 +} + +impl ArrayDeque { + /* 建構子 */ + pub fn new(capacity: usize) -> Self { + Self { + nums: vec![0; capacity], + front: 0, + que_size: 0, + } + } + + /* 獲取雙向佇列的容量 */ + pub fn capacity(&self) -> usize { + self.nums.len() + } + + /* 獲取雙向佇列的長度 */ + pub fn size(&self) -> usize { + self.que_size + } + + /* 判斷雙向佇列是否為空 */ + pub fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 計算環形陣列索引 */ + fn index(&self, i: i32) -> usize { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return ((i + self.capacity() as i32) % self.capacity() as i32) as usize; + } + + /* 佇列首入列 */ + pub fn push_first(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("雙向佇列已滿"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + self.front = self.index(self.front as i32 - 1); + // 將 num 新增至佇列首 + self.nums[self.front] = num; + self.que_size += 1; + } + + /* 佇列尾入列 */ + pub fn push_last(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("雙向佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + let rear = self.index(self.front as i32 + self.que_size as i32); + // 將 num 新增至佇列尾 + self.nums[rear] = num; + self.que_size += 1; + } + + /* 佇列首出列 */ + fn pop_first(&mut self) -> i32 { + let num = self.peek_first(); + // 佇列首指標向後移動一位 + self.front = self.index(self.front as i32 + 1); + self.que_size -= 1; + num + } + + /* 佇列尾出列 */ + fn pop_last(&mut self) -> i32 { + let num = self.peek_last(); + self.que_size -= 1; + num + } + + /* 訪問佇列首元素 */ + fn peek_first(&self) -> i32 { + if self.is_empty() { + panic!("雙向佇列為空") + }; + self.nums[self.front] + } + + /* 訪問佇列尾元素 */ + fn peek_last(&self) -> i32 { + if self.is_empty() { + panic!("雙向佇列為空") + }; + // 計算尾元素索引 + let last = self.index(self.front as i32 + self.que_size as i32 - 1); + self.nums[last] + } + + /* 返回陣列用於列印 */ + fn to_array(&self) -> Vec { + // 僅轉換有效長度範圍內的串列元素 + let mut res = vec![0; self.que_size]; + let mut j = self.front; + for i in 0..self.que_size { + res[i] = self.nums[self.index(j as i32)]; + j += 1; + } + res + } +} + +/* Driver Code */ +fn main() { + /* 初始化雙向佇列 */ + let mut deque = ArrayDeque::new(10); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("雙向佇列 deque = "); + print_util::print_array(&deque.to_array()); + + /* 訪問元素 */ + let peek_first = deque.peek_first(); + print!("\n佇列首元素 peek_first = {}", peek_first); + let peek_last = deque.peek_last(); + print!("\n佇列尾元素 peek_last = {}", peek_last); + + /* 元素入列 */ + deque.push_last(4); + print!("\n元素 4 佇列尾入列後 deque = "); + print_util::print_array(&deque.to_array()); + deque.push_first(1); + print!("\n元素 1 佇列首入列後 deque = "); + print_util::print_array(&deque.to_array()); + + /* 元素出列 */ + let pop_last = deque.pop_last(); + print!("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", pop_last); + print_util::print_array(&deque.to_array()); + let pop_first = deque.pop_first(); + print!("\n佇列首出列元素 = {},佇列首出列後 deque = ", pop_first); + print_util::print_array(&deque.to_array()); + + /* 獲取雙向佇列的長度 */ + let size = deque.size(); + print!("\n雙向佇列長度 size = {}", size); + + /* 判斷雙向佇列是否為空 */ + let is_empty = deque.is_empty(); + print!("\n雙向佇列是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/array_queue.rs b/zh-hant/codes/rust/chapter_stack_and_queue/array_queue.rs new file mode 100644 index 0000000000..2802f5dad9 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/array_queue.rs @@ -0,0 +1,125 @@ +/* + * File: array_queue.rs + * Created Time: 2023-02-06 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +/* 基於環形陣列實現的佇列 */ +struct ArrayQueue { + nums: Vec, // 用於儲存佇列元素的陣列 + front: i32, // 佇列首指標,指向佇列首元素 + que_size: i32, // 佇列長度 + que_capacity: i32, // 佇列容量 +} + +impl ArrayQueue { + /* 建構子 */ + fn new(capacity: i32) -> ArrayQueue { + ArrayQueue { + nums: vec![0; capacity as usize], + front: 0, + que_size: 0, + que_capacity: capacity, + } + } + + /* 獲取佇列的容量 */ + fn capacity(&self) -> i32 { + self.que_capacity + } + + /* 獲取佇列的長度 */ + fn size(&self) -> i32 { + self.que_size + } + + /* 判斷佇列是否為空 */ + fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 入列 */ + fn push(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + let rear = (self.front + self.que_size) % self.que_capacity; + // 將 num 新增至佇列尾 + self.nums[rear as usize] = num; + self.que_size += 1; + } + + /* 出列 */ + fn pop(&mut self) -> i32 { + let num = self.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + self.front = (self.front + 1) % self.que_capacity; + self.que_size -= 1; + num + } + + /* 訪問佇列首元素 */ + fn peek(&self) -> i32 { + if self.is_empty() { + panic!("index out of bounds"); + } + self.nums[self.front as usize] + } + + /* 返回陣列 */ + fn to_vector(&self) -> Vec { + let cap = self.que_capacity; + let mut j = self.front; + let mut arr = vec![0; self.que_size as usize]; + for i in 0..self.que_size { + arr[i as usize] = self.nums[(j % cap) as usize]; + j += 1; + } + arr + } +} + +/* Driver Code */ +fn main() { + /* 初始化佇列 */ + let capacity = 10; + let mut queue = ArrayQueue::new(capacity); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + println!("佇列 queue = {:?}", queue.to_vector()); + + /* 訪問佇列首元素 */ + let peek = queue.peek(); + println!("佇列首元素 peek = {}", peek); + + /* 元素出列 */ + let pop = queue.pop(); + println!( + "出列元素 pop = {:?},出列後 queue = {:?}", + pop, + queue.to_vector() + ); + + /* 獲取佇列的長度 */ + let size = queue.size(); + println!("佇列長度 size = {}", size); + + /* 判斷佇列是否為空 */ + let is_empty = queue.is_empty(); + println!("佇列是否為空 = {}", is_empty); + + /* 測試環形陣列 */ + for i in 0..10 { + queue.push(i); + queue.pop(); + println!("第 {:?} 輪入列 + 出列後 queue = {:?}", i, queue.to_vector()); + } +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/array_stack.rs b/zh-hant/codes/rust/chapter_stack_and_queue/array_stack.rs new file mode 100644 index 0000000000..6a850f8c77 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/array_stack.rs @@ -0,0 +1,86 @@ +/* + * File: array_stack.rs + * Created Time: 2023-02-05 + * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* 基於陣列實現的堆疊 */ +struct ArrayStack { + stack: Vec, +} + +impl ArrayStack { + /* 初始化堆疊 */ + fn new() -> ArrayStack { + ArrayStack:: { + stack: Vec::::new(), + } + } + + /* 獲取堆疊的長度 */ + fn size(&self) -> usize { + self.stack.len() + } + + /* 判斷堆疊是否為空 */ + fn is_empty(&self) -> bool { + self.size() == 0 + } + + /* 入堆疊 */ + fn push(&mut self, num: T) { + self.stack.push(num); + } + + /* 出堆疊 */ + fn pop(&mut self) -> Option { + self.stack.pop() + } + + /* 訪問堆疊頂元素 */ + fn peek(&self) -> Option<&T> { + if self.is_empty() { + panic!("堆疊為空") + }; + self.stack.last() + } + + /* 返回 &Vec */ + fn to_array(&self) -> &Vec { + &self.stack + } +} + +/* Driver Code */ +fn main() { + // 初始化堆疊 + let mut stack = ArrayStack::::new(); + + // 元素入堆疊 + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("堆疊 stack = "); + print_util::print_array(stack.to_array()); + + //訪問堆疊頂元素 + let peek = stack.peek().unwrap(); + print!("\n堆疊頂元素 peek = {}", peek); + + // 元素出堆疊 + let pop = stack.pop().unwrap(); + print!("\n出堆疊元素 pop = {pop},出堆疊後 stack = "); + print_util::print_array(stack.to_array()); + + // 獲取堆疊的長度 + let size = stack.size(); + print!("\n堆疊的長度 size = {size}"); + + // 判斷是否為空 + let is_empty = stack.is_empty(); + print!("\n堆疊是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/deque.rs b/zh-hant/codes/rust/chapter_stack_and_queue/deque.rs new file mode 100644 index 0000000000..e5ad4abe1c --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/deque.rs @@ -0,0 +1,49 @@ +/* + * File: deque.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // 初始化雙向佇列 + let mut deque: VecDeque = VecDeque::new(); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + print!("雙向佇列 deque = "); + print_util::print_queue(&deque); + + // 訪問元素 + let peek_first = deque.front().unwrap(); + print!("\n佇列首元素 peekFirst = {peek_first}"); + let peek_last = deque.back().unwrap(); + print!("\n佇列尾元素 peekLast = {peek_last}"); + + /* 元素入列 */ + deque.push_back(4); + print!("\n元素 4 佇列尾入列後 deque = "); + print_util::print_queue(&deque); + deque.push_front(1); + print!("\n元素 1 佇列首入列後 deque = "); + print_util::print_queue(&deque); + + // 元素出列 + let pop_last = deque.pop_back().unwrap(); + print!("\n佇列尾出列元素 = {pop_last},佇列尾出列後 deque = "); + print_util::print_queue(&deque); + let pop_first = deque.pop_front().unwrap(); + print!("\n佇列首出列元素 = {pop_first},佇列首出列後 deque = "); + print_util::print_queue(&deque); + + // 獲取雙向佇列的長度 + let size = deque.len(); + print!("\n雙向佇列長度 size = {size}"); + + // 判斷雙向佇列是否為空 + let is_empty = deque.is_empty(); + print!("\n雙向佇列是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs new file mode 100644 index 0000000000..cf6387cd2d --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs @@ -0,0 +1,218 @@ +/* + * File: linkedlist_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 雙向鏈結串列節點 */ +pub struct ListNode { + pub val: T, // 節點值 + pub next: Option>>>, // 後繼節點指標 + pub prev: Option>>>, // 前驅節點指標 +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { + val, + next: None, + prev: None, + })) + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +#[allow(dead_code)] +pub struct LinkedListDeque { + front: Option>>>, // 頭節點 front + rear: Option>>>, // 尾節點 rear + que_size: usize, // 雙向佇列的長度 +} + +impl LinkedListDeque { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 獲取雙向佇列的長度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判斷雙向佇列是否為空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入列操作 */ + pub fn push(&mut self, num: T, is_front: bool) { + let node = ListNode::new(num); + // 佇列首入列操作 + if is_front { + match self.front.take() { + // 若鏈結串列為空,則令 front 和 rear 都指向 node + None => { + self.rear = Some(node.clone()); + self.front = Some(node); + } + // 將 node 新增至鏈結串列頭部 + Some(old_front) => { + old_front.borrow_mut().prev = Some(node.clone()); + node.borrow_mut().next = Some(old_front); + self.front = Some(node); // 更新頭節點 + } + } + } + // 佇列尾入列操作 + else { + match self.rear.take() { + // 若鏈結串列為空,則令 front 和 rear 都指向 node + None => { + self.front = Some(node.clone()); + self.rear = Some(node); + } + // 將 node 新增至鏈結串列尾部 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(node.clone()); + node.borrow_mut().prev = Some(old_rear); + self.rear = Some(node); // 更新尾節點 + } + } + } + self.que_size += 1; // 更新佇列長度 + } + + /* 佇列首入列 */ + pub fn push_first(&mut self, num: T) { + self.push(num, true); + } + + /* 佇列尾入列 */ + pub fn push_last(&mut self, num: T) { + self.push(num, false); + } + + /* 出列操作 */ + pub fn pop(&mut self, is_front: bool) -> Option { + // 若佇列為空,直接返回 None + if self.is_empty() { + return None; + }; + // 佇列首出列操作 + if is_front { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + new_front.borrow_mut().prev.take(); + self.front = Some(new_front); // 更新頭節點 + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; // 更新佇列長度 + old_front.borrow().val + }) + } + // 佇列尾出列操作 + else { + self.rear.take().map(|old_rear| { + match old_rear.borrow_mut().prev.take() { + Some(new_rear) => { + new_rear.borrow_mut().next.take(); + self.rear = Some(new_rear); // 更新尾節點 + } + None => { + self.front.take(); + } + } + self.que_size -= 1; // 更新佇列長度 + old_rear.borrow().val + }) + } + } + + /* 佇列首出列 */ + pub fn pop_first(&mut self) -> Option { + return self.pop(true); + } + + /* 佇列尾出列 */ + pub fn pop_last(&mut self) -> Option { + return self.pop(false); + } + + /* 訪問佇列首元素 */ + pub fn peek_first(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 訪問佇列尾元素 */ + pub fn peek_last(&self) -> Option<&Rc>>> { + self.rear.as_ref() + } + + /* 返回陣列用於列印 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + res + } +} + +/* Driver Code */ +fn main() { + /* 初始化雙向佇列 */ + let mut deque = LinkedListDeque::new(); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("雙向佇列 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 訪問元素 */ + let peek_first = deque.peek_first().unwrap().borrow().val; + print!("\n佇列首元素 peek_first = {}", peek_first); + let peek_last = deque.peek_last().unwrap().borrow().val; + print!("\n佇列尾元素 peek_last = {}", peek_last); + + /* 元素入列 */ + deque.push_last(4); + print!("\n元素 4 佇列尾入列後 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + deque.push_first(1); + print!("\n元素 1 佇列首入列後 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 元素出列 */ + let pop_last = deque.pop_last().unwrap(); + print!("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", pop_last); + print_util::print_array(&deque.to_array(deque.peek_first())); + let pop_first = deque.pop_first().unwrap(); + print!("\n佇列首出列元素 = {},佇列首出列後 deque = ", pop_first); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 獲取雙向佇列的長度 */ + let size = deque.size(); + print!("\n雙向佇列長度 size = {}", size); + + /* 判斷雙向佇列是否為空 */ + let is_empty = deque.is_empty(); + print!("\n雙向佇列是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs new file mode 100644 index 0000000000..04327ef7d7 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs @@ -0,0 +1,126 @@ +/* + * File: linkedlist_queue.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 基於鏈結串列實現的佇列 */ +#[allow(dead_code)] +pub struct LinkedListQueue { + front: Option>>>, // 頭節點 front + rear: Option>>>, // 尾節點 rear + que_size: usize, // 佇列的長度 +} + +impl LinkedListQueue { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 獲取佇列的長度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判斷佇列是否為空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入列 */ + pub fn push(&mut self, num: T) { + // 在尾節點後新增 num + let new_rear = ListNode::new(num); + match self.rear.take() { + // 如果佇列不為空,則將該節點新增到尾節點後 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + // 如果佇列為空,則令頭、尾節點都指向該節點 + None => { + self.front = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + } + self.que_size += 1; + } + + /* 出列 */ + pub fn pop(&mut self) -> Option { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + self.front = Some(new_front); + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; + old_front.borrow().val + }) + } + + /* 訪問佇列首元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 將鏈結串列轉化為 Array 並返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + + res + } +} + +/* Driver Code */ +fn main() { + /* 初始化佇列 */ + let mut queue = LinkedListQueue::new(); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print!("佇列 queue = "); + print_util::print_array(&queue.to_array(queue.peek())); + + /* 訪問佇列首元素 */ + let peek = queue.peek().unwrap().borrow().val; + print!("\n佇列首元素 peek = {}", peek); + + /* 元素出列 */ + let pop = queue.pop().unwrap(); + print!("\n出列元素 pop = {},出列後 queue = ", pop); + print_util::print_array(&queue.to_array(queue.peek())); + + /* 獲取佇列的長度 */ + let size = queue.size(); + print!("\n佇列長度 size = {}", size); + + /* 判斷佇列是否為空 */ + let is_empty = queue.is_empty(); + print!("\n佇列是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs new file mode 100644 index 0000000000..71753cc25d --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs @@ -0,0 +1,101 @@ +/* + * File: linkedlist_stack.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 基於鏈結串列實現的堆疊 */ +#[allow(dead_code)] +pub struct LinkedListStack { + stack_peek: Option>>>, // 將頭節點作為堆疊頂 + stk_size: usize, // 堆疊的長度 +} + +impl LinkedListStack { + pub fn new() -> Self { + Self { + stack_peek: None, + stk_size: 0, + } + } + + /* 獲取堆疊的長度 */ + pub fn size(&self) -> usize { + return self.stk_size; + } + + /* 判斷堆疊是否為空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入堆疊 */ + pub fn push(&mut self, num: T) { + let node = ListNode::new(num); + node.borrow_mut().next = self.stack_peek.take(); + self.stack_peek = Some(node); + self.stk_size += 1; + } + + /* 出堆疊 */ + pub fn pop(&mut self) -> Option { + self.stack_peek.take().map(|old_head| { + self.stack_peek = old_head.borrow_mut().next.take(); + self.stk_size -= 1; + + old_head.borrow().val + }) + } + + /* 訪問堆疊頂元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.stack_peek.as_ref() + } + + /* 將 List 轉化為 Array 並返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.push(node.borrow().val); + return nums; + } + return Vec::new(); + } +} + +/* Driver Code */ +fn main() { + /* 初始化堆疊 */ + let mut stack = LinkedListStack::new(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("堆疊 stack = "); + print_util::print_array(&stack.to_array(stack.peek())); + + /* 訪問堆疊頂元素 */ + let peek = stack.peek().unwrap().borrow().val; + print!("\n堆疊頂元素 peek = {}", peek); + + /* 元素出堆疊 */ + let pop = stack.pop().unwrap(); + print!("\n出堆疊元素 pop = {},出堆疊後 stack = ", pop); + print_util::print_array(&stack.to_array(stack.peek())); + + /* 獲取堆疊的長度 */ + let size = stack.size(); + print!("\n堆疊的長度 size = {}", size); + + /* 判斷是否為空 */ + let is_empty = stack.is_empty(); + print!("\n堆疊是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/queue.rs b/zh-hant/codes/rust/chapter_stack_and_queue/queue.rs new file mode 100644 index 0000000000..2605a41113 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/queue.rs @@ -0,0 +1,41 @@ +/* + * File: queue.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // 初始化佇列 + let mut queue: VecDeque = VecDeque::new(); + + // 元素入列 + queue.push_back(1); + queue.push_back(3); + queue.push_back(2); + queue.push_back(5); + queue.push_back(4); + print!("佇列 queue = "); + print_util::print_queue(&queue); + + // 訪問佇列首元素 + let peek = queue.front().unwrap(); + println!("\n佇列首元素 peek = {peek}"); + + // 元素出列 + let pop = queue.pop_front().unwrap(); + print!("出列元素 pop = {pop},出列後 queue = "); + print_util::print_queue(&queue); + + // 獲取佇列的長度 + let size = queue.len(); + print!("\n佇列長度 size = {size}"); + + // 判斷佇列是否為空 + let is_empty = queue.is_empty(); + print!("\n佇列是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/stack.rs b/zh-hant/codes/rust/chapter_stack_and_queue/stack.rs new file mode 100644 index 0000000000..83f895c715 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/stack.rs @@ -0,0 +1,40 @@ +/* + * File: stack.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* Driver Code */ +pub fn main() { + // 初始化堆疊 + // 在 rust 中,推薦將 Vec 當作堆疊來使用 + let mut stack: Vec = Vec::new(); + + // 元素入堆疊 + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("堆疊 stack = "); + print_util::print_array(&stack); + + // 訪問堆疊頂元素 + let peek = stack.last().unwrap(); + print!("\n堆疊頂元素 peek = {peek}"); + + // 元素出堆疊 + let pop = stack.pop().unwrap(); + print!("\n出堆疊元素 pop = {pop},出堆疊後 stack = "); + print_util::print_array(&stack); + + // 獲取堆疊的長度 + let size = stack.len(); + print!("\n堆疊的長度 size = {size}"); + + // 判斷堆疊是否為空 + let is_empty = stack.is_empty(); + print!("\n堆疊是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_tree/array_binary_tree.rs b/zh-hant/codes/rust/chapter_tree/array_binary_tree.rs new file mode 100644 index 0000000000..a7f32721df --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/array_binary_tree.rs @@ -0,0 +1,192 @@ +/* + * File: array_binary_tree.rs + * Created Time: 2023-07-25 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, tree_node}; + +/* 陣列表示下的二元樹類別 */ +struct ArrayBinaryTree { + tree: Vec>, +} + +impl ArrayBinaryTree { + /* 建構子 */ + fn new(arr: Vec>) -> Self { + Self { tree: arr } + } + + /* 串列容量 */ + fn size(&self) -> i32 { + self.tree.len() as i32 + } + + /* 獲取索引為 i 節點的值 */ + fn val(&self, i: i32) -> Option { + // 若索引越界,則返回 None ,代表空位 + if i < 0 || i >= self.size() { + None + } else { + self.tree[i as usize] + } + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + fn left(&self, i: i32) -> i32 { + 2 * i + 1 + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + fn right(&self, i: i32) -> i32 { + 2 * i + 2 + } + + /* 獲取索引為 i 節點的父節點的索引 */ + fn parent(&self, i: i32) -> i32 { + (i - 1) / 2 + } + + /* 層序走訪 */ + fn level_order(&self) -> Vec { + self.tree.iter().filter_map(|&x| x).collect() + } + + /* 深度優先走訪 */ + fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { + if self.val(i).is_none() { + return; + } + let val = self.val(i).unwrap(); + // 前序走訪 + if order == "pre" { + res.push(val); + } + self.dfs(self.left(i), order, res); + // 中序走訪 + if order == "in" { + res.push(val); + } + self.dfs(self.right(i), order, res); + // 後序走訪 + if order == "post" { + res.push(val); + } + } + + /* 前序走訪 */ + fn pre_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "pre", &mut res); + res + } + + /* 中序走訪 */ + fn in_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "in", &mut res); + res + } + + /* 後序走訪 */ + fn post_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "post", &mut res); + res + } +} + +/* Driver Code */ +fn main() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let arr = vec![ + Some(1), + Some(2), + Some(3), + Some(4), + None, + Some(6), + Some(7), + Some(8), + Some(9), + None, + None, + Some(12), + None, + None, + Some(15), + ]; + + let root = tree_node::vec_to_tree(arr.clone()).unwrap(); + println!("\n初始化二元樹\n"); + println!("二元樹的陣列表示:"); + println!( + "[{}]", + arr.iter() + .map(|&val| if let Some(val) = val { + format!("{val}") + } else { + "null".to_string() + }) + .collect::>() + .join(", ") + ); + println!("二元樹的鏈結串列表示:"); + print_util::print_tree(&root); + + // 陣列表示下的二元樹類別 + let abt = ArrayBinaryTree::new(arr); + + // 訪問節點 + let i = 1; + let l = abt.left(i); + let r = abt.right(i); + let p = abt.parent(i); + println!( + "\n當前節點的索引為 {} ,值為 {}", + i, + if let Some(val) = abt.val(i) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其左子節點的索引為 {} ,值為 {}", + l, + if let Some(val) = abt.val(l) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其右子節點的索引為 {} ,值為 {}", + r, + if let Some(val) = abt.val(r) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其父節點的索引為 {} ,值為 {}", + p, + if let Some(val) = abt.val(p) { + format!("{val}") + } else { + "null".to_string() + } + ); + + // 走訪樹 + let mut res = abt.level_order(); + println!("\n層序走訪為:{:?}", res); + res = abt.pre_order(); + println!("前序走訪為:{:?}", res); + res = abt.in_order(); + println!("中序走訪為:{:?}", res); + res = abt.post_order(); + println!("後序走訪為:{:?}", res); +} diff --git a/zh-hant/codes/rust/chapter_tree/avl_tree.rs b/zh-hant/codes/rust/chapter_tree/avl_tree.rs new file mode 100644 index 0000000000..eb48530cf7 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/avl_tree.rs @@ -0,0 +1,297 @@ +/* + * File: avl_tree.rs + * Created Time: 2023-07-14 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +type OptionTreeNodeRc = Option>>; + +/* AVL 樹 */ +struct AVLTree { + root: OptionTreeNodeRc, // 根節點 +} + +impl AVLTree { + /* 建構子 */ + fn new() -> Self { + Self { root: None } + } + + /* 獲取節點高度 */ + fn height(node: OptionTreeNodeRc) -> i32 { + // 空節點高度為 -1 ,葉節點高度為 0 + match node { + Some(node) => node.borrow().height, + None => -1, + } + } + + /* 更新節點高度 */ + fn update_height(node: OptionTreeNodeRc) { + if let Some(node) = node { + let left = node.borrow().left.clone(); + let right = node.borrow().right.clone(); + // 節點高度等於最高子樹高度 + 1 + node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; + } + } + + /* 獲取平衡因子 */ + fn balance_factor(node: OptionTreeNodeRc) -> i32 { + match node { + // 空節點平衡因子為 0 + None => 0, + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + Some(node) => { + Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) + } + } + } + + /* 右旋操作 */ + fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().left.clone().unwrap(); + let grand_child = child.borrow().right.clone(); + // 以 child 為原點,將 node 向右旋轉 + child.borrow_mut().right = Some(node.clone()); + node.borrow_mut().left = grand_child; + // 更新節點高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋轉後子樹的根節點 + Some(child) + } + None => None, + } + } + + /* 左旋操作 */ + fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().right.clone().unwrap(); + let grand_child = child.borrow().left.clone(); + // 以 child 為原點,將 node 向左旋轉 + child.borrow_mut().left = Some(node.clone()); + node.borrow_mut().right = grand_child; + // 更新節點高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋轉後子樹的根節點 + Some(child) + } + None => None, + } + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + // 獲取節點 node 的平衡因子 + let balance_factor = Self::balance_factor(node.clone()); + // 左偏樹 + if balance_factor > 1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().left.clone()) >= 0 { + // 右旋 + Self::right_rotate(Some(node)) + } else { + // 先左旋後右旋 + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::left_rotate(left); + Self::right_rotate(Some(node)) + } + } + // 右偏樹 + else if balance_factor < -1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().right.clone()) <= 0 { + // 左旋 + Self::left_rotate(Some(node)) + } else { + // 先右旋後左旋 + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::right_rotate(right); + Self::left_rotate(Some(node)) + } + } else { + // 平衡樹,無須旋轉,直接返回 + node + } + } + + /* 插入節點 */ + fn insert(&mut self, val: i32) { + self.root = Self::insert_helper(self.root.clone(), val); + } + + /* 遞迴插入節點(輔助方法) */ + fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查詢插入位置並插入節點 */ + match { + let node_val = node.borrow().val; + node_val + } + .cmp(&val) + { + Ordering::Greater => { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::insert_helper(left, val); + } + Ordering::Less => { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::insert_helper(right, val); + } + Ordering::Equal => { + return Some(node); // 重複節點不插入,直接返回 + } + } + Self::update_height(Some(node.clone())); // 更新節點高度 + + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子樹的根節點 + Some(node) + } + None => Some(TreeNode::new(val)), + } + } + + /* 刪除節點 */ + fn remove(&self, val: i32) { + Self::remove_helper(self.root.clone(), val); + } + + /* 遞迴刪除節點(輔助方法) */ + fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查詢節點並刪除 */ + if val < node.borrow().val { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::remove_helper(left, val); + } else if val > node.borrow().val { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, val); + } else if node.borrow().left.is_none() || node.borrow().right.is_none() { + let child = if node.borrow().left.is_some() { + node.borrow().left.clone() + } else { + node.borrow().right.clone() + }; + match child { + // 子節點數量 = 0 ,直接刪除 node 並返回 + None => { + return None; + } + // 子節點數量 = 1 ,直接刪除 node + Some(child) => node = child, + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + let mut temp = node.borrow().right.clone().unwrap(); + loop { + let temp_left = temp.borrow().left.clone(); + if temp_left.is_none() { + break; + } + temp = temp_left.unwrap(); + } + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); + node.borrow_mut().val = temp.borrow().val; + } + Self::update_height(Some(node.clone())); // 更新節點高度 + + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子樹的根節點 + Some(node) + } + None => None, + } + } + + /* 查詢節點 */ + fn search(&self, val: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // 迴圈查詢,越過葉節點後跳出 + while let Some(current) = cur.clone() { + match current.borrow().val.cmp(&val) { + // 目標節點在 cur 的右子樹中 + Ordering::Less => { + cur = current.borrow().right.clone(); + } + // 目標節點在 cur 的左子樹中 + Ordering::Greater => { + cur = current.borrow().left.clone(); + } + // 找到目標節點,跳出迴圈 + Ordering::Equal => { + break; + } + } + } + // 返回目標節點 + cur + } +} + +/* Driver Code */ +fn main() { + fn test_insert(tree: &mut AVLTree, val: i32) { + tree.insert(val); + println!("\n插入節點 {} 後,AVL 樹為", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + fn test_remove(tree: &mut AVLTree, val: i32) { + tree.remove(val); + println!("\n刪除節點 {} 後,AVL 樹為", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + /* 初始化空 AVL 樹 */ + let mut avl_tree = AVLTree::new(); + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + test_insert(&mut avl_tree, 1); + test_insert(&mut avl_tree, 2); + test_insert(&mut avl_tree, 3); + test_insert(&mut avl_tree, 4); + test_insert(&mut avl_tree, 5); + test_insert(&mut avl_tree, 8); + test_insert(&mut avl_tree, 7); + test_insert(&mut avl_tree, 9); + test_insert(&mut avl_tree, 10); + test_insert(&mut avl_tree, 6); + + /* 插入重複節點 */ + test_insert(&mut avl_tree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + test_remove(&mut avl_tree, 8); // 刪除度為 0 的節點 + test_remove(&mut avl_tree, 5); // 刪除度為 1 的節點 + test_remove(&mut avl_tree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + let node = avl_tree.search(7); + if let Some(node) = node { + println!( + "\n查詢到的節點物件為 {:?},節點值 = {}", + &*node.borrow(), + node.borrow().val + ); + } +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_search_tree.rs b/zh-hant/codes/rust/chapter_tree/binary_search_tree.rs new file mode 100644 index 0000000000..1186f6bca6 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_search_tree.rs @@ -0,0 +1,195 @@ +/* + * File: binary_search_tree.rs + * Created Time: 2023-04-20 + * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +use hello_algo_rust::include::TreeNode; + +type OptionTreeNodeRc = Option>>; + +/* 二元搜尋樹 */ +pub struct BinarySearchTree { + root: OptionTreeNodeRc, +} + +impl BinarySearchTree { + /* 建構子 */ + pub fn new() -> Self { + // 初始化空樹 + Self { root: None } + } + + /* 獲取二元樹根節點 */ + pub fn get_root(&self) -> OptionTreeNodeRc { + self.root.clone() + } + + /* 查詢節點 */ + pub fn search(&self, num: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // 迴圈查詢,越過葉節點後跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 目標節點在 cur 的右子樹中 + Ordering::Greater => cur = node.borrow().right.clone(), + // 目標節點在 cur 的左子樹中 + Ordering::Less => cur = node.borrow().left.clone(), + // 找到目標節點,跳出迴圈 + Ordering::Equal => break, + } + } + + // 返回目標節點 + cur + } + + /* 插入節點 */ + pub fn insert(&mut self, num: i32) { + // 若樹為空,則初始化根節點 + if self.root.is_none() { + self.root = Some(TreeNode::new(num)); + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 迴圈查詢,越過葉節點後跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到重複節點,直接返回 + Ordering::Equal => return, + // 插入位置在 cur 的右子樹中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 插入位置在 cur 的左子樹中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 插入節點 + let pre = pre.unwrap(); + let node = Some(TreeNode::new(num)); + if num > pre.borrow().val { + pre.borrow_mut().right = node; + } else { + pre.borrow_mut().left = node; + } + } + + /* 刪除節點 */ + pub fn remove(&mut self, num: i32) { + // 若樹為空,直接提前返回 + if self.root.is_none() { + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 迴圈查詢,越過葉節點後跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到待刪除節點,跳出迴圈 + Ordering::Equal => break, + // 待刪除節點在 cur 的右子樹中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 待刪除節點在 cur 的左子樹中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 若無待刪除節點,則直接返回 + if cur.is_none() { + return; + } + let cur = cur.unwrap(); + let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); + match (left_child.clone(), right_child.clone()) { + // 子節點數量 = 0 or 1 + (None, None) | (Some(_), None) | (None, Some(_)) => { + // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 + let child = left_child.or(right_child); + let pre = pre.unwrap(); + // 刪除節點 cur + if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { + let left = pre.borrow().left.clone(); + if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { + pre.borrow_mut().left = child; + } else { + pre.borrow_mut().right = child; + } + } else { + // 若刪除節點為根節點,則重新指定根節點 + self.root = child; + } + } + // 子節點數量 = 2 + (Some(_), Some(_)) => { + // 獲取中序走訪中 cur 的下一個節點 + let mut tmp = cur.borrow().right.clone(); + while let Some(node) = tmp.clone() { + if node.borrow().left.is_some() { + tmp = node.borrow().left.clone(); + } else { + break; + } + } + let tmp_val = tmp.unwrap().borrow().val; + // 遞迴刪除節點 tmp + self.remove(tmp_val); + // 用 tmp 覆蓋 cur + cur.borrow_mut().val = tmp_val; + } + } + } +} + +/* Driver Code */ +fn main() { + /* 初始化二元搜尋樹 */ + let mut bst = BinarySearchTree::new(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for &num in &nums { + bst.insert(num); + } + println!("\n初始化的二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* 查詢結點 */ + let node = bst.search(7); + println!( + "\n查詢到的節點物件為 {:?},節點值 = {}", + node.clone().unwrap(), + node.clone().unwrap().borrow().val + ); + + /* 插入節點 */ + bst.insert(16); + println!("\n插入節點 16 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* 刪除節點 */ + bst.remove(1); + println!("\n刪除節點 1 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(2); + println!("\n刪除節點 2 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(4); + println!("\n刪除節點 4 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_tree.rs b/zh-hant/codes/rust/chapter_tree/binary_tree.rs new file mode 100644 index 0000000000..22da1089a1 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_tree.rs @@ -0,0 +1,38 @@ +/** + * File: binary_tree.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ +use std::rc::Rc; +use hello_algo_rust::include::{print_util, TreeNode}; + +/* Driver Code */ +fn main() { + /* 初始化二元樹 */ + // 初始化節點 + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // 構建節點之間的引用(指標) + n1.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().right = Some(Rc::clone(&n3)); + n2.borrow_mut().left = Some(Rc::clone(&n4)); + n2.borrow_mut().right = Some(Rc::clone(&n5)); + println!("\n初始化二元樹\n"); + print_util::print_tree(&n1); + + // 插入節點與刪除節點 + let p = TreeNode::new(0); + // 在 n1 -> n2 中間插入節點 P + p.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().left = Some(Rc::clone(&p)); + println!("\n插入節點 P 後\n"); + print_util::print_tree(&n1); + // 刪除節點 P + drop(p); + n1.borrow_mut().left = Some(Rc::clone(&n2)); + println!("\n刪除節點 P 後\n"); + print_util::print_tree(&n1); +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_tree_bfs.rs b/zh-hant/codes/rust/chapter_tree/binary_tree_bfs.rs new file mode 100644 index 0000000000..20ef8b0058 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_tree_bfs.rs @@ -0,0 +1,45 @@ +/* + * File: binary_tree_bfs.rs + * Created Time: 2023-04-07 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::collections::VecDeque; +use std::{cell::RefCell, rc::Rc}; + +/* 層序走訪 */ +fn level_order(root: &Rc>) -> Vec { + // 初始化佇列,加入根節點 + let mut que = VecDeque::new(); + que.push_back(root.clone()); + // 初始化一個串列,用於儲存走訪序列 + let mut vec = Vec::new(); + + while let Some(node) = que.pop_front() { + // 隊列出隊 + vec.push(node.borrow().val); // 儲存節點值 + if let Some(left) = node.borrow().left.as_ref() { + que.push_back(left.clone()); // 左子節點入列 + } + if let Some(right) = node.borrow().right.as_ref() { + que.push_back(right.clone()); // 右子節點入列 + }; + } + vec +} + +/* Driver Code */ +fn main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); + println!("初始化二元樹\n"); + print_util::print_tree(&root); + + /* 層序走訪 */ + let vec = level_order(&root); + print!("\n層序走訪的節點列印序列 = {:?}", vec); +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_tree_dfs.rs b/zh-hant/codes/rust/chapter_tree/binary_tree_dfs.rs new file mode 100644 index 0000000000..35f236d0ac --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_tree_dfs.rs @@ -0,0 +1,87 @@ +/* + * File: binary_tree_dfs.rs + * Created Time: 2023-04-06 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 前序走訪 */ +fn pre_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + let node = node.borrow(); + res.push(node.val); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* 中序走訪 */ +fn in_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + let node = node.borrow(); + dfs(node.left.as_ref(), res); + res.push(node.val); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* 後序走訪 */ +fn post_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + let node = node.borrow(); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + res.push(node.val); + } + } + + dfs(root, &mut result); + + result +} + +/* Driver Code */ +fn main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); + println!("初始化二元樹\n"); + print_util::print_tree(root.as_ref().unwrap()); + + /* 前序走訪 */ + let vec = pre_order(root.as_ref()); + println!("\n前序走訪的節點列印序列 = {:?}", vec); + + /* 中序走訪 */ + let vec = in_order(root.as_ref()); + println!("\n中序走訪的節點列印序列 = {:?}", vec); + + /* 後序走訪 */ + let vec = post_order(root.as_ref()); + print!("\n後序走訪的節點列印序列 = {:?}", vec); +} diff --git a/zh-hant/codes/rust/include/include.rs b/zh-hant/codes/rust/include/include.rs new file mode 100644 index 0000000000..a288d8b509 --- /dev/null +++ b/zh-hant/codes/rust/include/include.rs @@ -0,0 +1,10 @@ +/* + * File: include.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) + */ + +pub mod print_util; +pub mod list_node; +pub mod tree_node; +pub mod vertex; \ No newline at end of file diff --git a/zh-hant/codes/rust/include/list_node.rs b/zh-hant/codes/rust/include/list_node.rs new file mode 100644 index 0000000000..941e334c3d --- /dev/null +++ b/zh-hant/codes/rust/include/list_node.rs @@ -0,0 +1,57 @@ +/* + * File: list_node.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) + */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct ListNode { + pub val: T, + pub next: Option>>>, +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { val, next: None })) + } + + /* 將陣列反序列化為鏈結串列 */ + pub fn arr_to_linked_list(array: &[T]) -> Option>>> + where + T: Copy + Clone, + { + let mut head = None; + // insert in reverse order + for item in array.iter().rev() { + let node = Rc::new(RefCell::new(ListNode { + val: *item, + next: head.take(), + })); + head = Some(node); + } + head + } + + /* 將鏈結串列轉化為雜湊表 */ + pub fn linked_list_to_hashmap( + linked_list: Option>>>, + ) -> HashMap>>> + where + T: std::hash::Hash + Eq + Copy + Clone, + { + let mut hashmap = HashMap::new(); + let mut node = linked_list; + + while let Some(cur) = node { + let borrow = cur.borrow(); + hashmap.insert(borrow.val.clone(), cur.clone()); + node = borrow.next.clone(); + } + + hashmap + } +} diff --git a/zh-hant/codes/rust/include/print_util.rs b/zh-hant/codes/rust/include/print_util.rs new file mode 100644 index 0000000000..a19a0b4af9 --- /dev/null +++ b/zh-hant/codes/rust/include/print_util.rs @@ -0,0 +1,103 @@ +/* + * File: print_util.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use std::cell::{Cell, RefCell}; +use std::fmt::Display; +use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; + +use crate::list_node::ListNode; +use crate::tree_node::{TreeNode, vec_to_tree}; + +struct Trunk<'a, 'b> { + prev: Option<&'a Trunk<'a, 'b>>, + str: Cell<&'b str>, +} + +/* 列印陣列 */ +pub fn print_array(nums: &[T]) { + print!("["); + if nums.len() > 0 { + for (i, num) in nums.iter().enumerate() { + print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); + } + } else { + print!("]"); + } +} + +/* 列印雜湊表 */ +pub fn print_hash_map(map: &HashMap) { + for (key, value) in map { + println!("{key} -> {value}"); + } +} + +/* 列印佇列(雙向佇列) */ +pub fn print_queue(queue: &VecDeque) { + print!("["); + let iter = queue.iter(); + for (i, data) in iter.enumerate() { + print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); + } +} + +/* 列印鏈結串列 */ +pub fn print_linked_list(head: &Rc>>) { + print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); + if let Some(node) = &head.borrow().next { + return print_linked_list(node); + } +} + +/* 列印二元樹 */ +pub fn print_tree(root: &Rc>) { + _print_tree(Some(root), None, false); +} + +/* 列印二元樹 */ +fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { + if let Some(node) = root { + let mut prev_str = " "; + let trunk = Trunk { prev, str: Cell::new(prev_str) }; + _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); + + if prev.is_none() { + trunk.str.set("———"); + } else if is_right { + trunk.str.set("/———"); + prev_str = " |"; + } else { + trunk.str.set("\\———"); + prev.as_ref().unwrap().str.set(prev_str); + } + + show_trunks(Some(&trunk)); + println!(" {}", node.borrow().val); + if let Some(prev) = prev { + prev.str.set(prev_str); + } + trunk.str.set(" |"); + + _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); + } +} + +fn show_trunks(trunk: Option<&Trunk>) { + if let Some(trunk) = trunk { + show_trunks(trunk.prev); + print!("{}", trunk.str.get()); + } +} + +/* 列印堆積 */ +pub fn print_heap(heap: Vec) { + println!("堆積的陣列表示:{:?}", heap); + println!("堆積的樹狀表示:"); + if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { + print_tree(&root); + } +} \ No newline at end of file diff --git a/zh-hant/codes/rust/include/tree_node.rs b/zh-hant/codes/rust/include/tree_node.rs new file mode 100644 index 0000000000..514491862a --- /dev/null +++ b/zh-hant/codes/rust/include/tree_node.rs @@ -0,0 +1,92 @@ +/* + * File: tree_node.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) + */ + +use std::cell::RefCell; +use std::rc::Rc; + +/* 二元樹節點型別 */ +#[derive(Debug)] +pub struct TreeNode { + pub val: i32, + pub height: i32, + pub parent: Option>>, + pub left: Option>>, + pub right: Option>>, +} + +impl TreeNode { + /* 建構子 */ + pub fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + parent: None, + left: None, + right: None, + })) + } +} + +#[macro_export] +macro_rules! op_vec { + ( $( $x:expr ),* ) => { + vec![ + $( Option::from($x).map(|x| x) ),* + ] + }; +} + +// 序列化編碼規則請參考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二元樹的陣列表示: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二元樹的鏈結串列表示: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* 將串列反序列化為二元樹:遞迴 */ +fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { + if i >= arr.len() || arr[i].is_none() { + return None; + } + let root = TreeNode::new(arr[i].unwrap()); + root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); + root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); + Some(root) +} + +/* 將串列反序列化為二元樹 */ +pub fn vec_to_tree(arr: Vec>) -> Option>> { + vec_to_tree_dfs(&arr, 0) +} + +/* 將二元樹序列化為串列:遞迴 */ +fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { + if let Some(root) = root { + // i + 1 is the minimum valid size to access index i + while res.len() < i + 1 { + res.push(None); + } + res[i] = Some(root.borrow().val); + tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); + tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); + } +} + +/* 將二元樹序列化為串列 */ +pub fn tree_to_vec(root: Option>>) -> Vec> { + let mut res = vec![]; + tree_to_vec_dfs(root.as_ref(), 0, &mut res); + res +} diff --git a/zh-hant/codes/rust/include/vertex.rs b/zh-hant/codes/rust/include/vertex.rs new file mode 100644 index 0000000000..6d9b5e5508 --- /dev/null +++ b/zh-hant/codes/rust/include/vertex.rs @@ -0,0 +1,21 @@ +/* + * File: vertex.rs + * Created Time: 2023-07-13 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 頂點型別 */ +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Vertex { + pub val: i32, +} + +/* 輸入值串列 vals ,返回頂點串列 vets */ +pub fn vals_to_vets(vals: Vec) -> Vec { + vals.into_iter().map(|val| Vertex { val }).collect() +} + +/* 輸入頂點串列 vets ,返回值串列 vals */ +pub fn vets_to_vals(vets: Vec) -> Vec { + vets.into_iter().map(|vet| vet.val).collect() +} diff --git a/zh-hant/codes/rust/src/include/list_node.rs b/zh-hant/codes/rust/src/include/list_node.rs new file mode 100644 index 0000000000..941e334c3d --- /dev/null +++ b/zh-hant/codes/rust/src/include/list_node.rs @@ -0,0 +1,57 @@ +/* + * File: list_node.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) + */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct ListNode { + pub val: T, + pub next: Option>>>, +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { val, next: None })) + } + + /* 將陣列反序列化為鏈結串列 */ + pub fn arr_to_linked_list(array: &[T]) -> Option>>> + where + T: Copy + Clone, + { + let mut head = None; + // insert in reverse order + for item in array.iter().rev() { + let node = Rc::new(RefCell::new(ListNode { + val: *item, + next: head.take(), + })); + head = Some(node); + } + head + } + + /* 將鏈結串列轉化為雜湊表 */ + pub fn linked_list_to_hashmap( + linked_list: Option>>>, + ) -> HashMap>>> + where + T: std::hash::Hash + Eq + Copy + Clone, + { + let mut hashmap = HashMap::new(); + let mut node = linked_list; + + while let Some(cur) = node { + let borrow = cur.borrow(); + hashmap.insert(borrow.val.clone(), cur.clone()); + node = borrow.next.clone(); + } + + hashmap + } +} diff --git a/zh-hant/codes/rust/src/include/mod.rs b/zh-hant/codes/rust/src/include/mod.rs new file mode 100644 index 0000000000..6cba6f9a52 --- /dev/null +++ b/zh-hant/codes/rust/src/include/mod.rs @@ -0,0 +1,16 @@ +/* + * File: include.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) + */ + +pub mod list_node; +pub mod print_util; +pub mod tree_node; +pub mod vertex; + +// rexport to include +pub use list_node::*; +pub use print_util::*; +pub use tree_node::*; +pub use vertex::*; diff --git a/zh-hant/codes/rust/src/include/print_util.rs b/zh-hant/codes/rust/src/include/print_util.rs new file mode 100644 index 0000000000..fd93f36264 --- /dev/null +++ b/zh-hant/codes/rust/src/include/print_util.rs @@ -0,0 +1,103 @@ +/* + * File: print_util.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use std::cell::{Cell, RefCell}; +use std::fmt::Display; +use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; + +use super::list_node::ListNode; +use super::tree_node::{TreeNode, vec_to_tree}; + +struct Trunk<'a, 'b> { + prev: Option<&'a Trunk<'a, 'b>>, + str: Cell<&'b str>, +} + +/* 列印陣列 */ +pub fn print_array(nums: &[T]) { + print!("["); + if nums.len() > 0 { + for (i, num) in nums.iter().enumerate() { + print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); + } + } else { + print!("]"); + } +} + +/* 列印雜湊表 */ +pub fn print_hash_map(map: &HashMap) { + for (key, value) in map { + println!("{key} -> {value}"); + } +} + +/* 列印佇列(雙向佇列) */ +pub fn print_queue(queue: &VecDeque) { + print!("["); + let iter = queue.iter(); + for (i, data) in iter.enumerate() { + print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); + } +} + +/* 列印鏈結串列 */ +pub fn print_linked_list(head: &Rc>>) { + print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); + if let Some(node) = &head.borrow().next { + return print_linked_list(node); + } +} + +/* 列印二元樹 */ +pub fn print_tree(root: &Rc>) { + _print_tree(Some(root), None, false); +} + +/* 列印二元樹 */ +fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { + if let Some(node) = root { + let mut prev_str = " "; + let trunk = Trunk { prev, str: Cell::new(prev_str) }; + _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); + + if prev.is_none() { + trunk.str.set("———"); + } else if is_right { + trunk.str.set("/———"); + prev_str = " |"; + } else { + trunk.str.set("\\———"); + prev.as_ref().unwrap().str.set(prev_str); + } + + show_trunks(Some(&trunk)); + println!(" {}", node.borrow().val); + if let Some(prev) = prev { + prev.str.set(prev_str); + } + trunk.str.set(" |"); + + _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); + } +} + +fn show_trunks(trunk: Option<&Trunk>) { + if let Some(trunk) = trunk { + show_trunks(trunk.prev); + print!("{}", trunk.str.get()); + } +} + +/* 列印堆積 */ +pub fn print_heap(heap: Vec) { + println!("堆積的陣列表示:{:?}", heap); + println!("堆積的樹狀表示:"); + if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { + print_tree(&root); + } +} diff --git a/zh-hant/codes/rust/src/include/tree_node.rs b/zh-hant/codes/rust/src/include/tree_node.rs new file mode 100644 index 0000000000..514491862a --- /dev/null +++ b/zh-hant/codes/rust/src/include/tree_node.rs @@ -0,0 +1,92 @@ +/* + * File: tree_node.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) + */ + +use std::cell::RefCell; +use std::rc::Rc; + +/* 二元樹節點型別 */ +#[derive(Debug)] +pub struct TreeNode { + pub val: i32, + pub height: i32, + pub parent: Option>>, + pub left: Option>>, + pub right: Option>>, +} + +impl TreeNode { + /* 建構子 */ + pub fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + parent: None, + left: None, + right: None, + })) + } +} + +#[macro_export] +macro_rules! op_vec { + ( $( $x:expr ),* ) => { + vec![ + $( Option::from($x).map(|x| x) ),* + ] + }; +} + +// 序列化編碼規則請參考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二元樹的陣列表示: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二元樹的鏈結串列表示: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* 將串列反序列化為二元樹:遞迴 */ +fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { + if i >= arr.len() || arr[i].is_none() { + return None; + } + let root = TreeNode::new(arr[i].unwrap()); + root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); + root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); + Some(root) +} + +/* 將串列反序列化為二元樹 */ +pub fn vec_to_tree(arr: Vec>) -> Option>> { + vec_to_tree_dfs(&arr, 0) +} + +/* 將二元樹序列化為串列:遞迴 */ +fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { + if let Some(root) = root { + // i + 1 is the minimum valid size to access index i + while res.len() < i + 1 { + res.push(None); + } + res[i] = Some(root.borrow().val); + tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); + tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); + } +} + +/* 將二元樹序列化為串列 */ +pub fn tree_to_vec(root: Option>>) -> Vec> { + let mut res = vec![]; + tree_to_vec_dfs(root.as_ref(), 0, &mut res); + res +} diff --git a/zh-hant/codes/rust/src/include/vertex.rs b/zh-hant/codes/rust/src/include/vertex.rs new file mode 100644 index 0000000000..3a1730dc53 --- /dev/null +++ b/zh-hant/codes/rust/src/include/vertex.rs @@ -0,0 +1,27 @@ +/* + * File: vertex.rs + * Created Time: 2023-07-13 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 頂點型別 */ +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Vertex { + pub val: i32, +} + +impl From for Vertex { + fn from(value: i32) -> Self { + Self { val: value } + } +} + +/* 輸入值串列 vals ,返回頂點串列 vets */ +pub fn vals_to_vets(vals: Vec) -> Vec { + vals.into_iter().map(|val| val.into()).collect() +} + +/* 輸入頂點串列 vets ,返回值串列 vals */ +pub fn vets_to_vals(vets: Vec) -> Vec { + vets.into_iter().map(|vet| vet.val).collect() +} diff --git a/zh-hant/codes/rust/src/lib.rs b/zh-hant/codes/rust/src/lib.rs new file mode 100644 index 0000000000..2883b91047 --- /dev/null +++ b/zh-hant/codes/rust/src/lib.rs @@ -0,0 +1 @@ +pub mod include; diff --git a/zh-hant/codes/swift/.gitignore b/zh-hant/codes/swift/.gitignore new file mode 100644 index 0000000000..6295af4cc3 --- /dev/null +++ b/zh-hant/codes/swift/.gitignore @@ -0,0 +1,130 @@ +# Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager +# Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager + +### Objective-C ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Objective-C Patch ### + +### Swift ### +# Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + + + + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + + +### SwiftPackageManager ### +Packages +xcuserdata +*.xcodeproj + + +# End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager diff --git a/zh-hant/codes/swift/Package.resolved b/zh-hant/codes/swift/Package.resolved new file mode 100644 index 0000000000..159c83d262 --- /dev/null +++ b/zh-hant/codes/swift/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "branch" : "release/1.1", + "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" + } + } + ], + "version" : 2 +} diff --git a/zh-hant/codes/swift/Package.swift b/zh-hant/codes/swift/Package.swift new file mode 100644 index 0000000000..5326dc1387 --- /dev/null +++ b/zh-hant/codes/swift/Package.swift @@ -0,0 +1,206 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "HelloAlgo", + products: [ + // chapter_computational_complexity + .executable(name: "iteration", targets: ["iteration"]), + .executable(name: "recursion", targets: ["recursion"]), + .executable(name: "time_complexity", targets: ["time_complexity"]), + .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), + .executable(name: "space_complexity", targets: ["space_complexity"]), + // chapter_array_and_linkedlist + .executable(name: "array", targets: ["array"]), + .executable(name: "linked_list", targets: ["linked_list"]), + .executable(name: "list", targets: ["list"]), + .executable(name: "my_list", targets: ["my_list"]), + // chapter_stack_and_queue + .executable(name: "stack", targets: ["stack"]), + .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), + .executable(name: "array_stack", targets: ["array_stack"]), + .executable(name: "queue", targets: ["queue"]), + .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), + .executable(name: "array_queue", targets: ["array_queue"]), + .executable(name: "deque", targets: ["deque"]), + .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), + .executable(name: "array_deque", targets: ["array_deque"]), + // chapter_hashing + .executable(name: "hash_map", targets: ["hash_map"]), + .executable(name: "array_hash_map", targets: ["array_hash_map"]), + .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), + .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), + .executable(name: "simple_hash", targets: ["simple_hash"]), + .executable(name: "built_in_hash", targets: ["built_in_hash"]), + // chapter_tree + .executable(name: "binary_tree", targets: ["binary_tree"]), + .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), + .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), + .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), + .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), + .executable(name: "avl_tree", targets: ["avl_tree"]), + // chapter_heap + .executable(name: "heap", targets: ["heap"]), + .executable(name: "my_heap", targets: ["my_heap"]), + .executable(name: "top_k", targets: ["top_k"]), + // chapter_graph + .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), + .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), + .executable(name: "graph_bfs", targets: ["graph_bfs"]), + .executable(name: "graph_dfs", targets: ["graph_dfs"]), + // chapter_searching + .executable(name: "binary_search", targets: ["binary_search"]), + .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), + .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), + .executable(name: "two_sum", targets: ["two_sum"]), + .executable(name: "linear_search", targets: ["linear_search"]), + .executable(name: "hashing_search", targets: ["hashing_search"]), + // chapter_sorting + .executable(name: "selection_sort", targets: ["selection_sort"]), + .executable(name: "bubble_sort", targets: ["bubble_sort"]), + .executable(name: "insertion_sort", targets: ["insertion_sort"]), + .executable(name: "quick_sort", targets: ["quick_sort"]), + .executable(name: "merge_sort", targets: ["merge_sort"]), + .executable(name: "heap_sort", targets: ["heap_sort"]), + .executable(name: "bucket_sort", targets: ["bucket_sort"]), + .executable(name: "counting_sort", targets: ["counting_sort"]), + .executable(name: "radix_sort", targets: ["radix_sort"]), + // chapter_divide_and_conquer + .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), + .executable(name: "build_tree", targets: ["build_tree"]), + .executable(name: "hanota", targets: ["hanota"]), + // chapter_backtracking + .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), + .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), + .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), + .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), + .executable(name: "permutations_i", targets: ["permutations_i"]), + .executable(name: "permutations_ii", targets: ["permutations_ii"]), + .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), + .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), + .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), + .executable(name: "n_queens", targets: ["n_queens"]), + // chapter_dynamic_programming + .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), + .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), + .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), + .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), + .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), + .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), + .executable(name: "min_path_sum", targets: ["min_path_sum"]), + .executable(name: "knapsack", targets: ["knapsack"]), + .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), + .executable(name: "coin_change", targets: ["coin_change"]), + .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), + .executable(name: "edit_distance", targets: ["edit_distance"]), + // chapter_greedy + .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), + .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), + .executable(name: "max_capacity", targets: ["max_capacity"]), + .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), + ], + targets: [ + // helper + .target(name: "utils", path: "utils"), + .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), + .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), + // chapter_computational_complexity + .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), + .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), + .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), + .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), + .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), + // chapter_array_and_linkedlist + .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), + .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), + .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), + .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), + // chapter_stack_and_queue + .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), + .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), + .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), + .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), + .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), + .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), + .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), + .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), + .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), + // chapter_hashing + .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), + .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), + .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), + .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), + .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), + .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), + // chapter_tree + .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), + .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), + .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), + .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), + .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), + .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), + // chapter_heap + .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), + .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), + .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), + // chapter_graph + .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), + .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), + .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), + .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), + // chapter_searching + .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), + .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), + .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), + .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), + .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), + .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), + // chapter_sorting + .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), + .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), + .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), + .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), + .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), + .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), + .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), + .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), + .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), + // chapter_divide_and_conquer + .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), + .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), + .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), + // chapter_backtracking + .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), + .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), + .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), + .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), + .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), + .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), + .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), + .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), + // chapter_dynamic_programming + .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), + .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), + .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), + .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), + .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), + .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), + .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), + .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), + .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), + .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), + .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), + .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), + // chapter_greedy + .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), + .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), + .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), + .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), + ] +) diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/array.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/array.swift new file mode 100644 index 0000000000..356b11e3a8 --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/array.swift @@ -0,0 +1,107 @@ +/** + * File: array.swift + * Created Time: 2023-01-05 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 隨機訪問元素 */ +func randomAccess(nums: [Int]) -> Int { + // 在區間 [0, nums.count) 中隨機抽取一個數字 + let randomIndex = nums.indices.randomElement()! + // 獲取並返回隨機元素 + let randomNum = nums[randomIndex] + return randomNum +} + +/* 擴展陣列長度 */ +func extend(nums: [Int], enlarge: Int) -> [Int] { + // 初始化一個擴展長度後的陣列 + var res = Array(repeating: 0, count: nums.count + enlarge) + // 將原陣列中的所有元素複製到新陣列 + for i in nums.indices { + res[i] = nums[i] + } + // 返回擴展後的新陣列 + return res +} + +/* 在陣列的索引 index 處插入元素 num */ +func insert(nums: inout [Int], num: Int, index: Int) { + // 把索引 index 以及之後的所有元素向後移動一位 + for i in nums.indices.dropFirst(index).reversed() { + nums[i] = nums[i - 1] + } + // 將 num 賦給 index 處的元素 + nums[index] = num +} + +/* 刪除索引 index 處的元素 */ +func remove(nums: inout [Int], index: Int) { + // 把索引 index 之後的所有元素向前移動一位 + for i in nums.indices.dropFirst(index).dropLast() { + nums[i] = nums[i + 1] + } +} + +/* 走訪陣列 */ +func traverse(nums: [Int]) { + var count = 0 + // 透過索引走訪陣列 + for i in nums.indices { + count += nums[i] + } + // 直接走訪陣列元素 + for num in nums { + count += num + } + // 同時走訪資料索引和元素 + for (i, num) in nums.enumerated() { + count += nums[i] + count += num + } +} + +/* 在陣列中查詢指定元素 */ +func find(nums: [Int], target: Int) -> Int { + for i in nums.indices { + if nums[i] == target { + return i + } + } + return -1 +} + +@main +enum _Array { + /* Driver Code */ + static func main() { + /* 初始化陣列 */ + let arr = Array(repeating: 0, count: 5) + print("陣列 arr = \(arr)") + var nums = [1, 3, 2, 5, 4] + print("陣列 nums = \(nums)") + + /* 隨機訪問 */ + let randomNum = randomAccess(nums: nums) + print("在 nums 中獲取隨機元素 \(randomNum)") + + /* 長度擴展 */ + nums = extend(nums: nums, enlarge: 3) + print("將陣列長度擴展至 8 ,得到 nums = \(nums)") + + /* 插入元素 */ + insert(nums: &nums, num: 6, index: 3) + print("在索引 3 處插入數字 6 ,得到 nums = \(nums)") + + /* 刪除元素 */ + remove(nums: &nums, index: 2) + print("刪除索引 2 處的元素,得到 nums = \(nums)") + + /* 走訪陣列 */ + traverse(nums: nums) + + /* 查詢元素 */ + let index = find(nums: nums, target: 3) + print("在 nums 中查詢元素 3 ,得到索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/linked_list.swift new file mode 100644 index 0000000000..3b25505f2d --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -0,0 +1,90 @@ +/** + * File: linked_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + P.next = n1 + n0.next = P +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +func access(head: ListNode, index: Int) -> ListNode? { + var head: ListNode? = head + for _ in 0 ..< index { + if head == nil { + return nil + } + head = head?.next + } + return head +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +func find(head: ListNode, target: Int) -> Int { + var head: ListNode? = head + var index = 0 + while head != nil { + if head?.val == target { + return index + } + head = head?.next + index += 1 + } + return -1 +} + +@main +enum LinkedList { + /* Driver Code */ + static func main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("初始化的鏈結串列為") + PrintUtil.printLinkedList(head: n0) + + /* 插入節點 */ + insert(n0: n0, P: ListNode(x: 0)) + print("插入節點後的鏈結串列為") + PrintUtil.printLinkedList(head: n0) + + /* 刪除節點 */ + remove(n0: n0) + print("刪除節點後的鏈結串列為") + PrintUtil.printLinkedList(head: n0) + + /* 訪問節點 */ + let node = access(head: n0, index: 3) + print("鏈結串列中索引 3 處的節點的值 = \(node!.val)") + + /* 查詢節點 */ + let index = find(head: n0, target: 2) + print("鏈結串列中值為 2 的節點的索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/list.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/list.swift new file mode 100644 index 0000000000..f2d1832a50 --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/list.swift @@ -0,0 +1,63 @@ +/** + * File: list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum List { + /* Driver Code */ + static func main() { + /* 初始化串列 */ + var nums = [1, 3, 2, 5, 4] + print("串列 nums = \(nums)") + + /* 訪問元素 */ + let num = nums[1] + print("訪問索引 1 處的元素,得到 num = \(num)") + + /* 更新元素 */ + nums[1] = 0 + print("將索引 1 處的元素更新為 0 ,得到 nums = \(nums)") + + /* 清空串列 */ + nums.removeAll() + print("清空串列後 nums = \(nums)") + + /* 在尾部新增元素 */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("新增元素後 nums = \(nums)") + + /* 在中間插入元素 */ + nums.insert(6, at: 3) + print("在索引 3 處插入數字 6 ,得到 nums = \(nums)") + + /* 刪除元素 */ + nums.remove(at: 3) + print("刪除索引 3 處的元素,得到 nums = \(nums)") + + /* 透過索引走訪串列 */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + /* 直接走訪串列元素 */ + count = 0 + for x in nums { + count += x + } + + /* 拼接兩個串列 */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) + print("將串列 nums1 拼接到 nums 之後,得到 nums = \(nums)") + + /* 排序串列 */ + nums.sort() + print("排序串列後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/my_list.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/my_list.swift new file mode 100644 index 0000000000..43f014992d --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/my_list.swift @@ -0,0 +1,146 @@ +/** + * File: my_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 串列類別 */ +class MyList { + private var arr: [Int] // 陣列(儲存串列元素) + private var _capacity: Int // 串列容量 + private var _size: Int // 串列長度(當前元素數量) + private let extendRatio: Int // 每次串列擴容的倍數 + + /* 建構子 */ + init() { + _capacity = 10 + _size = 0 + extendRatio = 2 + arr = Array(repeating: 0, count: _capacity) + } + + /* 獲取串列長度(當前元素數量)*/ + func size() -> Int { + _size + } + + /* 獲取串列容量 */ + func capacity() -> Int { + _capacity + } + + /* 訪問元素 */ + func get(index: Int) -> Int { + // 索引如果越界則丟擲錯誤,下同 + if index < 0 || index >= size() { + fatalError("索引越界") + } + return arr[index] + } + + /* 更新元素 */ + func set(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("索引越界") + } + arr[index] = num + } + + /* 在尾部新增元素 */ + func add(num: Int) { + // 元素數量超出容量時,觸發擴容機制 + if size() == capacity() { + extendCapacity() + } + arr[size()] = num + // 更新元素數量 + _size += 1 + } + + /* 在中間插入元素 */ + func insert(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("索引越界") + } + // 元素數量超出容量時,觸發擴容機制 + if size() == capacity() { + extendCapacity() + } + // 將索引 index 以及之後的元素都向後移動一位 + for j in (index ..< size()).reversed() { + arr[j + 1] = arr[j] + } + arr[index] = num + // 更新元素數量 + _size += 1 + } + + /* 刪除元素 */ + @discardableResult + func remove(index: Int) -> Int { + if index < 0 || index >= size() { + fatalError("索引越界") + } + let num = arr[index] + // 將將索引 index 之後的元素都向前移動一位 + for j in index ..< (size() - 1) { + arr[j] = arr[j + 1] + } + // 更新元素數量 + _size -= 1 + // 返回被刪除的元素 + return num + } + + /* 串列擴容 */ + func extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) + // 更新串列容量 + _capacity = arr.count + } + + /* 將串列轉換為陣列 */ + func toArray() -> [Int] { + Array(arr.prefix(size())) + } +} + +@main +enum _MyList { + /* Driver Code */ + static func main() { + /* 初始化串列 */ + let nums = MyList() + /* 在尾部新增元素 */ + nums.add(num: 1) + nums.add(num: 3) + nums.add(num: 2) + nums.add(num: 5) + nums.add(num: 4) + print("串列 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,長度 = \(nums.size())") + + /* 在中間插入元素 */ + nums.insert(index: 3, num: 6) + print("在索引 3 處插入數字 6 ,得到 nums = \(nums.toArray())") + + /* 刪除元素 */ + nums.remove(index: 3) + print("刪除索引 3 處的元素,得到 nums = \(nums.toArray())") + + /* 訪問元素 */ + let num = nums.get(index: 1) + print("訪問索引 1 處的元素,得到 num = \(num)") + + /* 更新元素 */ + nums.set(index: 1, num: 0) + print("將索引 1 處的元素更新為 0 ,得到 nums = \(nums.toArray())") + + /* 測試擴容機制 */ + for i in 0 ..< 10 { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(num: i) + } + print("擴容後的串列 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,長度 = \(nums.size())") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/n_queens.swift b/zh-hant/codes/swift/chapter_backtracking/n_queens.swift new file mode 100644 index 0000000000..ea0044fe22 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/n_queens.swift @@ -0,0 +1,67 @@ +/** + * File: n_queens.swift + * Created Time: 2023-05-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:n 皇后 */ +func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { + // 當放置完所有行時,記錄解 + if row == n { + res.append(state) + return + } + // 走訪所有列 + for col in 0 ..< n { + // 計算該格子對應的主對角線和次對角線 + let diag1 = row - col + n - 1 + let diag2 = row + col + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q" + cols[col] = true + diags1[diag1] = true + diags2[diag2] = true + // 放置下一行 + backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + // 回退:將該格子恢復為空位 + state[row][col] = "#" + cols[col] = false + diags1[diag1] = false + diags2[diag2] = false + } + } +} + +/* 求解 n 皇后 */ +func nQueens(n: Int) -> [[[String]]] { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + var state = Array(repeating: Array(repeating: "#", count: n), count: n) + var cols = Array(repeating: false, count: n) // 記錄列是否有皇后 + var diags1 = Array(repeating: false, count: 2 * n - 1) // 記錄主對角線上是否有皇后 + var diags2 = Array(repeating: false, count: 2 * n - 1) // 記錄次對角線上是否有皇后 + var res: [[[String]]] = [] + + backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + + return res +} + +@main +enum NQueens { + /* Driver Code */ + static func main() { + let n = 4 + let res = nQueens(n: n) + + print("輸入棋盤長寬為 \(n)") + print("皇后放置方案共有 \(res.count) 種") + for state in res { + print("--------------------") + for row in state { + print(row) + } + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/permutations_i.swift b/zh-hant/codes/swift/chapter_backtracking/permutations_i.swift new file mode 100644 index 0000000000..cb8db59ac5 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/permutations_i.swift @@ -0,0 +1,50 @@ +/** + * File: permutations_i.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:全排列 I */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 當狀態長度等於元素數量時,記錄解 + if state.count == choices.count { + res.append(state) + return + } + // 走訪所有選擇 + for (i, choice) in choices.enumerated() { + // 剪枝:不允許重複選擇元素 + if !selected[i] { + // 嘗試:做出選擇,更新狀態 + selected[i] = true + state.append(choice) + // 進行下一輪選擇 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeLast() + } + } +} + +/* 全排列 I */ +func permutationsI(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsI { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsI(nums: nums) + + print("輸入陣列 nums = \(nums)") + print("所有排列 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/permutations_ii.swift b/zh-hant/codes/swift/chapter_backtracking/permutations_ii.swift new file mode 100644 index 0000000000..ae821b44c7 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/permutations_ii.swift @@ -0,0 +1,52 @@ +/** + * File: permutations_ii.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:全排列 II */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 當狀態長度等於元素數量時,記錄解 + if state.count == choices.count { + res.append(state) + return + } + // 走訪所有選擇 + var duplicated: Set = [] + for (i, choice) in choices.enumerated() { + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if !selected[i], !duplicated.contains(choice) { + // 嘗試:做出選擇,更新狀態 + duplicated.insert(choice) // 記錄選擇過的元素值 + selected[i] = true + state.append(choice) + // 進行下一輪選擇 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeLast() + } + } +} + +/* 全排列 II */ +func permutationsII(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsII { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsII(nums: nums) + + print("輸入陣列 nums = \(nums)") + print("所有排列 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift new file mode 100644 index 0000000000..4cb11b41d3 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var res: [TreeNode] = [] + +/* 前序走訪:例題一 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + if root.val == 7 { + // 記錄解 + res.append(root) + } + preOrder(root: root.left) + preOrder(root: root.right) +} + +@main +enum PreorderTraversalICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 前序走訪 + res = [] + preOrder(root: root) + + print("\n輸出所有值為 7 的節點") + var vals: [Int] = [] + for node in res { + vals.append(node.val) + } + print(vals) + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift new file mode 100644 index 0000000000..3b15cd98ad --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前序走訪:例題二 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 嘗試 + path.append(root) + if root.val == 7 { + // 記錄解 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() +} + +@main +enum PreorderTraversalIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 前序走訪 + path = [] + res = [] + preOrder(root: root) + + print("\n輸出所有根節點到節點 7 的路徑") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift new file mode 100644 index 0000000000..686119eab5 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前序走訪:例題三 */ +func preOrder(root: TreeNode?) { + // 剪枝 + guard let root = root, root.val != 3 else { + return + } + // 嘗試 + path.append(root) + if root.val == 7 { + // 記錄解 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() +} + +@main +enum PreorderTraversalIIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 前序走訪 + path = [] + res = [] + preOrder(root: root) + + print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift new file mode 100644 index 0000000000..35dd33b9f9 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 判斷當前狀態是否為解 */ +func isSolution(state: [TreeNode]) -> Bool { + !state.isEmpty && state.last!.val == 7 +} + +/* 記錄解 */ +func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { + res.append(state) +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { + choice != nil && choice!.val != 3 +} + +/* 更新狀態 */ +func makeChoice(state: inout [TreeNode], choice: TreeNode) { + state.append(choice) +} + +/* 恢復狀態 */ +func undoChoice(state: inout [TreeNode], choice: TreeNode) { + state.removeLast() +} + +/* 回溯演算法:例題三 */ +func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { + // 檢查是否為解 + if isSolution(state: state) { + recordSolution(state: state, res: &res) + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:檢查選擇是否合法 + if isValid(state: state, choice: choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state: &state, choice: choice) + // 進行下一輪選擇 + backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state: &state, choice: choice) + } + } +} + +@main +enum PreorderTraversalIIITemplate { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 回溯演算法 + var state: [TreeNode] = [] + var res: [[TreeNode]] = [] + backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) + + print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/subset_sum_i.swift b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i.swift new file mode 100644 index 0000000000..06b4e7ecd3 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i.swift @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:子集和 I */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.append(state) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for i in choices.indices.dropFirst(start) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break + } + // 嘗試:做出選擇,更新 target, start + state.append(choices[i]) + // 進行下一輪選擇 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast() + } +} + +/* 求解子集和 I */ +func subsetSumI(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 狀態(子集) + let nums = nums.sorted() // 對 nums 進行排序 + let start = 0 // 走訪起始點 + var res: [[Int]] = [] // 結果串列(子集串列) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumI { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumI(nums: nums, target: target) + + print("輸入陣列 nums = \(nums), target = \(target)") + print("所有和等於 \(target) 的子集 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/subset_sum_i_naive.swift b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i_naive.swift new file mode 100644 index 0000000000..557e989f7d --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i_naive.swift @@ -0,0 +1,51 @@ +/** + * File: subset_sum_i_naive.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:子集和 I */ +func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { + // 子集和等於 target 時,記錄解 + if total == target { + res.append(state) + return + } + // 走訪所有選擇 + for i in choices.indices { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if total + choices[i] > target { + continue + } + // 嘗試:做出選擇,更新元素和 total + state.append(choices[i]) + // 進行下一輪選擇 + backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast() + } +} + +/* 求解子集和 I(包含重複子集) */ +func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 狀態(子集) + let total = 0 // 子集和 + var res: [[Int]] = [] // 結果串列(子集串列) + backtrack(state: &state, target: target, total: total, choices: nums, res: &res) + return res +} + +@main +enum SubsetSumINaive { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumINaive(nums: nums, target: target) + + print("輸入陣列 nums = \(nums), target = \(target)") + print("所有和等於 \(target) 的子集 res = \(res)") + print("請注意,該方法輸出的結果包含重複集合") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/subset_sum_ii.swift b/zh-hant/codes/swift/chapter_backtracking/subset_sum_ii.swift new file mode 100644 index 0000000000..0ee7d991ef --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/subset_sum_ii.swift @@ -0,0 +1,58 @@ +/** + * File: subset_sum_ii.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:子集和 II */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.append(state) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i in choices.indices.dropFirst(start) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if i > start, choices[i] == choices[i - 1] { + continue + } + // 嘗試:做出選擇,更新 target, start + state.append(choices[i]) + // 進行下一輪選擇 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast() + } +} + +/* 求解子集和 II */ +func subsetSumII(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 狀態(子集) + let nums = nums.sorted() // 對 nums 進行排序 + let start = 0 // 走訪起始點 + var res: [[Int]] = [] // 結果串列(子集串列) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumII { + /* Driver Code */ + static func main() { + let nums = [4, 4, 5] + let target = 9 + + let res = subsetSumII(nums: nums, target: target) + + print("輸入陣列 nums = \(nums), target = \(target)") + print("所有和等於 \(target) 的子集 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/iteration.swift b/zh-hant/codes/swift/chapter_computational_complexity/iteration.swift new file mode 100644 index 0000000000..0f4d0fcbb5 --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/iteration.swift @@ -0,0 +1,75 @@ +/** + * File: iteration.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* for 迴圈 */ +func forLoop(n: Int) -> Int { + var res = 0 + // 迴圈求和 1, 2, ..., n-1, n + for i in 1 ... n { + res += i + } + return res +} + +/* while 迴圈 */ +func whileLoop(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while i <= n { + res += i + i += 1 // 更新條件變數 + } + return res +} + +/* while 迴圈(兩次更新) */ +func whileLoopII(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while i <= n { + res += i + // 更新條件變數 + i += 1 + i *= 2 + } + return res +} + +/* 雙層 for 迴圈 */ +func nestedForLoop(n: Int) -> String { + var res = "" + // 迴圈 i = 1, 2, ..., n-1, n + for i in 1 ... n { + // 迴圈 j = 1, 2, ..., n-1, n + for j in 1 ... n { + res.append("(\(i), \(j)), ") + } + } + return res +} + +@main +enum Iteration { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = forLoop(n: n) + print("\nfor 迴圈的求和結果 res = \(res)") + + res = whileLoop(n: n) + print("\nwhile 迴圈的求和結果 res = \(res)") + + res = whileLoopII(n: n) + print("\nwhile 迴圈(兩次更新)求和結果 res = \(res)") + + let resStr = nestedForLoop(n: n) + print("\n雙層 for 迴圈的走訪結果 \(resStr)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/recursion.swift b/zh-hant/codes/swift/chapter_computational_complexity/recursion.swift new file mode 100644 index 0000000000..62afd9f723 --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/recursion.swift @@ -0,0 +1,79 @@ +/** + * File: recursion.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 遞迴 */ +func recur(n: Int) -> Int { + // 終止條件 + if n == 1 { + return 1 + } + // 遞:遞迴呼叫 + let res = recur(n: n - 1) + // 迴:返回結果 + return n + res +} + +/* 使用迭代模擬遞迴 */ +func forLoopRecur(n: Int) -> Int { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + var stack: [Int] = [] + var res = 0 + // 遞:遞迴呼叫 + for i in (1 ... n).reversed() { + // 透過“入堆疊操作”模擬“遞” + stack.append(i) + } + // 迴:返回結果 + while !stack.isEmpty { + // 透過“出堆疊操作”模擬“迴” + res += stack.removeLast() + } + // res = 1+2+3+...+n + return res +} + +/* 尾遞迴 */ +func tailRecur(n: Int, res: Int) -> Int { + // 終止條件 + if n == 0 { + return res + } + // 尾遞迴呼叫 + return tailRecur(n: n - 1, res: res + n) +} + +/* 費波那契數列:遞迴 */ +func fib(n: Int) -> Int { + // 終止條件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + let res = fib(n: n - 1) + fib(n: n - 2) + // 返回結果 f(n) + return res +} + +@main +enum Recursion { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = recursion.recur(n: n) + print("\n遞迴函式的求和結果 res = \(res)") + + res = recursion.forLoopRecur(n: n) + print("\n使用迭代模擬遞迴求和結果 res = \(res)") + + res = recursion.tailRecur(n: n, res: 0) + print("\n尾遞迴函式的求和結果 res = \(res)") + + res = recursion.fib(n: n) + print("\n費波那契數列的第 \(n) 項為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/space_complexity.swift b/zh-hant/codes/swift/chapter_computational_complexity/space_complexity.swift new file mode 100644 index 0000000000..99aeb8309a --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/space_complexity.swift @@ -0,0 +1,98 @@ +/** + * File: space_complexity.swift + * Created Time: 2023-01-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 函式 */ +@discardableResult +func function() -> Int { + // 執行某些操作 + return 0 +} + +/* 常數階 */ +func constant(n: Int) { + // 常數、變數、物件佔用 O(1) 空間 + let a = 0 + var b = 0 + let nums = Array(repeating: 0, count: 10000) + let node = ListNode(x: 0) + // 迴圈中的變數佔用 O(1) 空間 + for _ in 0 ..< n { + let c = 0 + } + // 迴圈中的函式佔用 O(1) 空間 + for _ in 0 ..< n { + function() + } +} + +/* 線性階 */ +func linear(n: Int) { + // 長度為 n 的陣列佔用 O(n) 空間 + let nums = Array(repeating: 0, count: n) + // 長度為 n 的串列佔用 O(n) 空間 + let nodes = (0 ..< n).map { ListNode(x: $0) } + // 長度為 n 的雜湊表佔用 O(n) 空間 + let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) +} + +/* 線性階(遞迴實現) */ +func linearRecur(n: Int) { + print("遞迴 n = \(n)") + if n == 1 { + return + } + linearRecur(n: n - 1) +} + +/* 平方階 */ +func quadratic(n: Int) { + // 二維串列佔用 O(n^2) 空間 + let numList = Array(repeating: Array(repeating: 0, count: n), count: n) +} + +/* 平方階(遞迴實現) */ +@discardableResult +func quadraticRecur(n: Int) -> Int { + if n <= 0 { + return 0 + } + // 陣列 nums 長度為 n, n-1, ..., 2, 1 + let nums = Array(repeating: 0, count: n) + print("遞迴 n = \(n) 中的 nums 長度 = \(nums.count)") + return quadraticRecur(n: n - 1) +} + +/* 指數階(建立滿二元樹) */ +func buildTree(n: Int) -> TreeNode? { + if n == 0 { + return nil + } + let root = TreeNode(x: 0) + root.left = buildTree(n: n - 1) + root.right = buildTree(n: n - 1) + return root +} + +@main +enum SpaceComplexity { + /* Driver Code */ + static func main() { + let n = 5 + // 常數階 + constant(n: n) + // 線性階 + linear(n: n) + linearRecur(n: n) + // 平方階 + quadratic(n: n) + quadraticRecur(n: n) + // 指數階 + let root = buildTree(n: n) + PrintUtil.printTree(root: root) + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/time_complexity.swift b/zh-hant/codes/swift/chapter_computational_complexity/time_complexity.swift new file mode 100644 index 0000000000..68a44b0753 --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -0,0 +1,172 @@ +/** + * File: time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 常數階 */ +func constant(n: Int) -> Int { + var count = 0 + let size = 100_000 + for _ in 0 ..< size { + count += 1 + } + return count +} + +/* 線性階 */ +func linear(n: Int) -> Int { + var count = 0 + for _ in 0 ..< n { + count += 1 + } + return count +} + +/* 線性階(走訪陣列) */ +func arrayTraversal(nums: [Int]) -> Int { + var count = 0 + // 迴圈次數與陣列長度成正比 + for _ in nums { + count += 1 + } + return count +} + +/* 平方階 */ +func quadratic(n: Int) -> Int { + var count = 0 + // 迴圈次數與資料大小 n 成平方關係 + for _ in 0 ..< n { + for _ in 0 ..< n { + count += 1 + } + } + return count +} + +/* 平方階(泡沫排序) */ +func bubbleSort(nums: inout [Int]) -> Int { + var count = 0 // 計數器 + // 外迴圈:未排序區間為 [0, i] + for i in nums.indices.dropFirst().reversed() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 // 元素交換包含 3 個單元操作 + } + } + } + return count +} + +/* 指數階(迴圈實現) */ +func exponential(n: Int) -> Int { + var count = 0 + var base = 1 + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for _ in 0 ..< n { + for _ in 0 ..< base { + count += 1 + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* 指數階(遞迴實現) */ +func expRecur(n: Int) -> Int { + if n == 1 { + return 1 + } + return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 +} + +/* 對數階(迴圈實現) */ +func logarithmic(n: Int) -> Int { + var count = 0 + var n = n + while n > 1 { + n = n / 2 + count += 1 + } + return count +} + +/* 對數階(遞迴實現) */ +func logRecur(n: Int) -> Int { + if n <= 1 { + return 0 + } + return logRecur(n: n / 2) + 1 +} + +/* 線性對數階 */ +func linearLogRecur(n: Int) -> Int { + if n <= 1 { + return 1 + } + var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) + for _ in stride(from: 0, to: n, by: 1) { + count += 1 + } + return count +} + +/* 階乘階(遞迴實現) */ +func factorialRecur(n: Int) -> Int { + if n == 0 { + return 1 + } + var count = 0 + // 從 1 個分裂出 n 個 + for _ in 0 ..< n { + count += factorialRecur(n: n - 1) + } + return count +} + +@main +enum TimeComplexity { + /* Driver Code */ + static func main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + let n = 8 + print("輸入資料大小 n = \(n)") + + var count = constant(n: n) + print("常數階的操作數量 = \(count)") + + count = linear(n: n) + print("線性階的操作數量 = \(count)") + count = arrayTraversal(nums: Array(repeating: 0, count: n)) + print("線性階(走訪陣列)的操作數量 = \(count)") + + count = quadratic(n: n) + print("平方階的操作數量 = \(count)") + var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] + count = bubbleSort(nums: &nums) + print("平方階(泡沫排序)的操作數量 = \(count)") + + count = exponential(n: n) + print("指數階(迴圈實現)的操作數量 = \(count)") + count = expRecur(n: n) + print("指數階(遞迴實現)的操作數量 = \(count)") + + count = logarithmic(n: n) + print("對數階(迴圈實現)的操作數量 = \(count)") + count = logRecur(n: n) + print("對數階(遞迴實現)的操作數量 = \(count)") + + count = linearLogRecur(n: n) + print("線性對數階(遞迴實現)的操作數量 = \(count)") + + count = factorialRecur(n: n) + print("階乘階(遞迴實現)的操作數量 = \(count)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/zh-hant/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift new file mode 100644 index 0000000000..0fc64f530f --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +func randomNumbers(n: Int) -> [Int] { + // 生成陣列 nums = { 1, 2, 3, ..., n } + var nums = Array(1 ... n) + // 隨機打亂陣列元素 + nums.shuffle() + return nums +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +func findOne(nums: [Int]) -> Int { + for i in nums.indices { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if nums[i] == 1 { + return i + } + } + return -1 +} + +@main +enum WorstBestTimeComplexity { + /* Driver Code */ + static func main() { + for _ in 0 ..< 10 { + let n = 100 + let nums = randomNumbers(n: n) + let index = findOne(nums: nums) + print("陣列 [ 1, 2, ..., n ] 被打亂後 = \(nums)") + print("數字 1 的索引為 \(index)") + } + } +} diff --git a/zh-hant/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift b/zh-hant/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift new file mode 100644 index 0000000000..a1dd279951 --- /dev/null +++ b/zh-hant/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift @@ -0,0 +1,44 @@ +/** + * File: binary_search_recur.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { + // 若區間為空,代表無目標元素,則返回 -1 + if i > j { + return -1 + } + // 計算中點索引 m + let m = (i + j) / 2 + if nums[m] < target { + // 遞迴子問題 f(m+1, j) + return dfs(nums: nums, target: target, i: m + 1, j: j) + } else if nums[m] > target { + // 遞迴子問題 f(i, m-1) + return dfs(nums: nums, target: target, i: i, j: m - 1) + } else { + // 找到目標元素,返回其索引 + return m + } +} + +/* 二分搜尋 */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 求解問題 f(0, n-1) + dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) +} + +@main +enum BinarySearchRecur { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + // 二分搜尋(雙閉區間) + let index = binarySearch(nums: nums, target: target) + print("目標元素 6 的索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_divide_and_conquer/build_tree.swift b/zh-hant/codes/swift/chapter_divide_and_conquer/build_tree.swift new file mode 100644 index 0000000000..5908a6f801 --- /dev/null +++ b/zh-hant/codes/swift/chapter_divide_and_conquer/build_tree.swift @@ -0,0 +1,47 @@ +/** + * File: build_tree.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 構建二元樹:分治 */ +func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { + // 子樹區間為空時終止 + if r - l < 0 { + return nil + } + // 初始化根節點 + let root = TreeNode(x: preorder[i]) + // 查詢 m ,從而劃分左右子樹 + let m = inorderMap[preorder[i]]! + // 子問題:構建左子樹 + root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) + // 子問題:構建右子樹 + root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) + // 返回根節點 + return root +} + +/* 構建二元樹 */ +func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } + return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) +} + +@main +enum BuildTree { + /* Driver Code */ + static func main() { + let preorder = [3, 9, 2, 1, 7] + let inorder = [9, 3, 1, 2, 7] + print("前序走訪 = \(preorder)") + print("中序走訪 = \(inorder)") + + let root = buildTree(preorder: preorder, inorder: inorder) + print("構建的二元樹為:") + PrintUtil.printTree(root: root) + } +} diff --git a/zh-hant/codes/swift/chapter_divide_and_conquer/hanota.swift b/zh-hant/codes/swift/chapter_divide_and_conquer/hanota.swift new file mode 100644 index 0000000000..550554b8a6 --- /dev/null +++ b/zh-hant/codes/swift/chapter_divide_and_conquer/hanota.swift @@ -0,0 +1,58 @@ +/** + * File: hanota.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 移動一個圓盤 */ +func move(src: inout [Int], tar: inout [Int]) { + // 從 src 頂部拿出一個圓盤 + let pan = src.popLast()! + // 將圓盤放入 tar 頂部 + tar.append(pan) +} + +/* 求解河內塔問題 f(i) */ +func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 { + move(src: &src, tar: &tar) + return + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src: &src, tar: &tar) + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) +} + +/* 求解河內塔問題 */ +func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { + let n = A.count + // 串列尾部是柱子頂部 + // 將 src 頂部 n 個圓盤藉助 B 移到 C + dfs(i: n, src: &A, buf: &B, tar: &C) +} + +@main +enum Hanota { + /* Driver Code */ + static func main() { + // 串列尾部是柱子頂部 + var A = [5, 4, 3, 2, 1] + var B: [Int] = [] + var C: [Int] = [] + print("初始狀態下:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + + solveHanota(A: &A, B: &B, C: &C) + + print("圓盤移動完成後:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift new file mode 100644 index 0000000000..7f4ce1f2b2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯 */ +func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { + // 當爬到第 n 階時,方案數量加 1 + if state == n { + res[0] += 1 + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:不允許越過第 n 階 + if state + choice > n { + continue + } + // 嘗試:做出選擇,更新狀態 + backtrack(choices: choices, state: state + choice, n: n, res: &res) + // 回退 + } +} + +/* 爬樓梯:回溯 */ +func climbingStairsBacktrack(n: Int) -> Int { + let choices = [1, 2] // 可選擇向上爬 1 階或 2 階 + let state = 0 // 從第 0 階開始爬 + var res: [Int] = [] + res.append(0) // 使用 res[0] 記錄方案數量 + backtrack(choices: choices, state: state, n: n, res: &res) + return res[0] +} + +@main +enum ClimbingStairsBacktrack { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsBacktrack(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift new file mode 100644 index 0000000000..8f4e6508f2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +func climbingStairsConstraintDP(n: Int) -> Int { + if n == 1 || n == 2 { + return 1 + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3 ... n { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +@main +enum ClimbingStairsConstraintDP { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsConstraintDP(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift new file mode 100644 index 0000000000..648ed22ea6 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 搜尋 */ +func dfs(i: Int) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1) + dfs(i: i - 2) + return count +} + +/* 爬樓梯:搜尋 */ +func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) +} + +@main +enum ClimbingStairsDFS { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFS(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift new file mode 100644 index 0000000000..04d274fa47 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dfs_mem.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 記憶化搜尋 */ +func dfs(i: Int, mem: inout [Int]) -> Int { + // 已知 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] + let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) + // 記錄 dp[i] + mem[i] = count + return count +} + +/* 爬樓梯:記憶化搜尋 */ +func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) +} + +@main +enum ClimbingStairsDFSMem { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFSMem(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift new file mode 100644 index 0000000000..7ddc627e6d --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬樓梯:動態規劃 */ +func climbingStairsDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = 1 + dp[2] = 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3 ... n { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +func climbingStairsDPComp(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + var a = 1 + var b = 2 + for _ in 3 ... n { + (a, b) = (b, a + b) + } + return b +} + +@main +enum ClimbingStairsDP { + /* Driver Code */ + static func main() { + let n = 9 + + var res = climbingStairsDP(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + + res = climbingStairsDPComp(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/coin_change.swift b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change.swift new file mode 100644 index 0000000000..2c4f9ffbc2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change.swift @@ -0,0 +1,69 @@ +/** + * File: coin_change.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零錢兌換:動態規劃 */ +func coinChangeDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 狀態轉移:首行首列 + for a in 1 ... amt { + dp[0][a] = MAX + } + // 狀態轉移:其餘行和列 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1 +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +func coinChangeDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: MAX, count: amt + 1) + dp[0] = 0 + // 狀態轉移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return dp[amt] != MAX ? dp[amt] : -1 +} + +@main +enum CoinChange { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 4 + + // 動態規劃 + var res = coinChangeDP(coins: coins, amt: amt) + print("湊到目標金額所需的最少硬幣數量為 \(res)") + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins: coins, amt: amt) + print("湊到目標金額所需的最少硬幣數量為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/coin_change_ii.swift b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change_ii.swift new file mode 100644 index 0000000000..ff5c3c9497 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change_ii.swift @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零錢兌換 II:動態規劃 */ +func coinChangeIIDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 初始化首列 + for i in 0 ... n { + dp[i][0] = 1 + } + // 狀態轉移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: amt + 1) + dp[0] = 1 + // 狀態轉移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +@main +enum CoinChangeII { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 5 + + // 動態規劃 + var res = coinChangeIIDP(coins: coins, amt: amt) + print("湊出目標金額的硬幣組合數量為 \(res)") + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins: coins, amt: amt) + print("湊出目標金額的硬幣組合數量為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/edit_distance.swift b/zh-hant/codes/swift/chapter_dynamic_programming/edit_distance.swift new file mode 100644 index 0000000000..7d990e4105 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/edit_distance.swift @@ -0,0 +1,147 @@ +/** + * File: edit_distance.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 編輯距離:暴力搜尋 */ +func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { + // 若 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.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 返回最少編輯步數 + return min(min(insert, delete), replace) + 1 +} + +/* 編輯距離:記憶化搜尋 */ +func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若 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.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 記錄並返回最少編輯步數 + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 編輯距離:動態規劃 */ +func editDistanceDP(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) + // 狀態轉移:首行首列 + for i in 1 ... n { + dp[i][0] = i + } + for j in 1 ... m { + dp[0][j] = j + } + // 狀態轉移:其餘行和列 + for i in 1 ... n { + for j in 1 ... m { + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +func editDistanceDPComp(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: 0, count: m + 1) + // 狀態轉移:首行 + for j in 1 ... m { + dp[j] = j + } + // 狀態轉移:其餘行 + for i in 1 ... n { + // 狀態轉移:首列 + var leftup = dp[0] // 暫存 dp[i-1, j-1] + dp[0] = i + // 狀態轉移:其餘列 + for j in 1 ... m { + let temp = dp[j] + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m] +} + +@main +enum EditDistance { + /* Driver Code */ + static func main() { + let s = "bag" + let t = "pack" + let n = s.utf8CString.count + let m = t.utf8CString.count + + // 暴力搜尋 + var res = editDistanceDFS(s: s, t: t, i: n, j: m) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + + // 記憶化搜尋 + var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) + res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + + // 動態規劃 + res = editDistanceDP(s: s, t: t) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s: s, t: t) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/knapsack.swift b/zh-hant/codes/swift/chapter_dynamic_programming/knapsack.swift new file mode 100644 index 0000000000..d4bd7e1db9 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/knapsack.swift @@ -0,0 +1,110 @@ +/** + * File: knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 0-1 背包:暴力搜尋 */ +func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c { + return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 返回兩種方案中價值更大的那一個 + return max(no, yes) +} + +/* 0-1 背包:記憶化搜尋 */ +func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 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: wgt, val: val, mem: &mem, i: i - 1, c: c) + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 狀態轉移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 狀態轉移 + for i in 1 ... n { + // 倒序走訪 + for c in (1 ... cap).reversed() { + if wgt[i - 1] <= c { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum Knapsack { + /* Driver Code */ + static func main() { + let wgt = [10, 20, 30, 40, 50] + let val = [50, 120, 150, 210, 240] + let cap = 50 + let n = wgt.count + + // 暴力搜尋 + var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 記憶化搜尋 + var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) + res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 動態規劃 + res = knapsackDP(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift b/zh-hant/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift new file mode 100644 index 0000000000..a192cbb69c --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬樓梯最小代價:動態規劃 */ +func minCostClimbingStairsDP(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3 ... n { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +func minCostClimbingStairsDPComp(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + var (a, b) = (cost[1], cost[2]) + for i in 3 ... n { + (a, b) = (b, min(a, b) + cost[i]) + } + return b +} + +@main +enum MinCostClimbingStairsDP { + /* Driver Code */ + static func main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print("輸入樓梯的代價串列為 \(cost)") + + var res = minCostClimbingStairsDP(cost: cost) + print("爬完樓梯的最低代價為 \(res)") + + res = minCostClimbingStairsDPComp(cost: cost) + print("爬完樓梯的最低代價為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/min_path_sum.swift b/zh-hant/codes/swift/chapter_dynamic_programming/min_path_sum.swift new file mode 100644 index 0000000000..ca3048e04e --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/min_path_sum.swift @@ -0,0 +1,123 @@ +/** + * File: min_path_sum.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最小路徑和:暴力搜尋 */ +func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { + // 若為左上角單元格,則終止搜尋 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return .max + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + let up = minPathSumDFS(grid: grid, i: i - 1, j: j) + let left = minPathSumDFS(grid: grid, i: i, j: j - 1) + // 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) + grid[i][j] +} + +/* 最小路徑和:記憶化搜尋 */ +func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若為左上角單元格,則終止搜尋 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return .max + } + // 若已有記錄,則直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左邊和上邊單元格的最小路徑代價 + let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) + let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* 最小路徑和:動態規劃 */ +func minPathSumDP(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: m), count: n) + dp[0][0] = grid[0][0] + // 狀態轉移:首行 + for j in 1 ..< m { + dp[0][j] = dp[0][j - 1] + grid[0][j] + } + // 狀態轉移:首列 + for i in 1 ..< n { + dp[i][0] = dp[i - 1][0] + grid[i][0] + } + // 狀態轉移:其餘行和列 + for i in 1 ..< n { + for j in 1 ..< m { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + } + } + return dp[n - 1][m - 1] +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +func minPathSumDPComp(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: m) + // 狀態轉移:首行 + dp[0] = grid[0][0] + for j in 1 ..< m { + dp[j] = dp[j - 1] + grid[0][j] + } + // 狀態轉移:其餘行 + for i in 1 ..< n { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0] + // 狀態轉移:其餘列 + for j in 1 ..< m { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + } + } + return dp[m - 1] +} + +@main +enum MinPathSum { + /* Driver Code */ + static func main() { + let grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ] + let n = grid.count + let m = grid[0].count + + // 暴力搜尋 + var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) + print("從左上角到右下角的最小路徑和為 \(res)") + + // 記憶化搜尋 + var mem = Array(repeating: Array(repeating: -1, count: m), count: n) + res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) + print("從左上角到右下角的最小路徑和為 \(res)") + + // 動態規劃 + res = minPathSumDP(grid: grid) + print("從左上角到右下角的最小路徑和為 \(res)") + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid: grid) + print("從左上角到右下角的最小路徑和為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift b/zh-hant/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift new file mode 100644 index 0000000000..3e591500c0 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 完全背包:動態規劃 */ +func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 狀態轉移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 完全背包:空間最佳化後的動態規劃 */ +func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 狀態轉移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum UnboundedKnapsack { + /* Driver Code */ + static func main() { + let wgt = [1, 2, 3] + let val = [5, 11, 15] + let cap = 4 + + // 動態規劃 + var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_graph/graph_adjacency_list.swift b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list.swift new file mode 100644 index 0000000000..65116f7c33 --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鄰接表實現的無向圖類別 */ +public class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + public private(set) var adjList: [Vertex: [Vertex]] + + /* 建構子 */ + public init(edges: [[Vertex]]) { + adjList = [:] + // 新增所有頂點和邊 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 獲取頂點數量 */ + public func size() -> Int { + adjList.count + } + + /* 新增邊 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 新增邊 vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 刪除邊 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 刪除邊 vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 新增頂點 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = [] + } + + /* 刪除頂點 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("參數錯誤") + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.removeValue(forKey: vet) + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 列印鄰接表 */ + public func print() { + Swift.print("鄰接表 =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(vet: v[1]) + print("\n刪除頂點 3 後,圖為") + graph.print() + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_graph/graph_adjacency_list_target.swift b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list_target.swift new file mode 100644 index 0000000000..65116f7c33 --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list_target.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鄰接表實現的無向圖類別 */ +public class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + public private(set) var adjList: [Vertex: [Vertex]] + + /* 建構子 */ + public init(edges: [[Vertex]]) { + adjList = [:] + // 新增所有頂點和邊 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 獲取頂點數量 */ + public func size() -> Int { + adjList.count + } + + /* 新增邊 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 新增邊 vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 刪除邊 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 刪除邊 vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 新增頂點 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = [] + } + + /* 刪除頂點 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("參數錯誤") + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.removeValue(forKey: vet) + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 列印鄰接表 */ + public func print() { + Swift.print("鄰接表 =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(vet: v[1]) + print("\n刪除頂點 3 後,圖為") + graph.print() + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_graph/graph_adjacency_matrix.swift b/zh-hant/codes/swift/chapter_graph/graph_adjacency_matrix.swift new file mode 100644 index 0000000000..c997da5ad6 --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_adjacency_matrix.swift @@ -0,0 +1,130 @@ +/** + * File: graph_adjacency_matrix.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + private var vertices: [Int] // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + private var adjMat: [[Int]] // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + init(vertices: [Int], edges: [[Int]]) { + self.vertices = [] + adjMat = [] + // 新增頂點 + for val in vertices { + addVertex(val: val) + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for e in edges { + addEdge(i: e[0], j: e[1]) + } + } + + /* 獲取頂點數量 */ + func size() -> Int { + vertices.count + } + + /* 新增頂點 */ + func addVertex(val: Int) { + let n = size() + // 向頂點串列中新增新頂點的值 + vertices.append(val) + // 在鄰接矩陣中新增一行 + let newRow = Array(repeating: 0, count: n) + adjMat.append(newRow) + // 在鄰接矩陣中新增一列 + for i in adjMat.indices { + adjMat[i].append(0) + } + } + + /* 刪除頂點 */ + func removeVertex(index: Int) { + if index >= size() { + fatalError("越界") + } + // 在頂點串列中移除索引 index 的頂點 + vertices.remove(at: index) + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.remove(at: index) + // 在鄰接矩陣中刪除索引 index 的列 + for i in adjMat.indices { + adjMat[i].remove(at: index) + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + func addEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + func removeEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 列印鄰接矩陣 */ + func print() { + Swift.print("頂點串列 = ", terminator: "") + Swift.print(vertices) + Swift.print("鄰接矩陣 =") + PrintUtil.printMatrix(matrix: adjMat) + } +} + +@main +enum GraphAdjacencyMatrix { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + let vertices = [1, 3, 2, 5, 4] + let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] + let graph = GraphAdjMat(vertices: vertices, edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(i: 0, j: 2) + print("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(i: 0, j: 1) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + graph.addVertex(val: 6) + print("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(index: 1) + print("\n刪除頂點 3 後,圖為") + graph.print() + } +} diff --git a/zh-hant/codes/swift/chapter_graph/graph_bfs.swift b/zh-hant/codes/swift/chapter_graph/graph_bfs.swift new file mode 100644 index 0000000000..63bcc0082d --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_bfs.swift @@ -0,0 +1,56 @@ +/** + * File: graph_bfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 頂點走訪序列 + var res: [Vertex] = [] + // 雜湊集合,用於記錄已被訪問過的頂點 + var visited: Set = [startVet] + // 佇列用於實現 BFS + var que: [Vertex] = [startVet] + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while !que.isEmpty { + let vet = que.removeFirst() // 佇列首頂點出隊 + res.append(vet) // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳過已被訪問的頂點 + } + que.append(adjVet) // 只入列未訪問的頂點 + visited.insert(adjVet) // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res +} + +@main +enum GraphBFS { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + let edges = [ + [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]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 廣度優先走訪 */ + let res = graphBFS(graph: graph, startVet: v[0]) + print("\n廣度優先走訪(BFS)頂點序列為") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/zh-hant/codes/swift/chapter_graph/graph_dfs.swift b/zh-hant/codes/swift/chapter_graph/graph_dfs.swift new file mode 100644 index 0000000000..308e05f6f9 --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_dfs.swift @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 深度優先走訪輔助函式 */ +func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // 記錄訪問頂點 + visited.insert(vet) // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 頂點走訪序列 + var res: [Vertex] = [] + // 雜湊集合,用於記錄已被訪問過的頂點 + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res +} + +@main +enum GraphDFS { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) + let edges = [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 深度優先走訪 */ + let res = graphDFS(graph: graph, startVet: v[0]) + print("\n深度優先走訪(DFS)頂點序列為") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/coin_change_greedy.swift b/zh-hant/codes/swift/chapter_greedy/coin_change_greedy.swift new file mode 100644 index 0000000000..75580c45f2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/coin_change_greedy.swift @@ -0,0 +1,54 @@ +/** + * File: coin_change_greedy.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零錢兌換:貪婪 */ +func coinChangeGreedy(coins: [Int], amt: Int) -> Int { + // 假設 coins 串列有序 + var i = coins.count - 1 + var count = 0 + var amt = amt + // 迴圈進行貪婪選擇,直到無剩餘金額 + while amt > 0 { + // 找到小於且最接近剩餘金額的硬幣 + while i > 0 && coins[i] > amt { + i -= 1 + } + // 選擇 coins[i] + amt -= coins[i] + count += 1 + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1 +} + +@main +enum CoinChangeGreedy { + /* Driver Code */ + static func main() { + // 貪婪:能夠保證找到全域性最優解 + var coins = [1, 5, 10, 20, 50, 100] + var amt = 186 + var res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50] + amt = 60 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") + print("實際上需要的最少數量為 3 ,即 20 + 20 + 20") + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50] + amt = 98 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") + print("實際上需要的最少數量為 2 ,即 49 + 49") + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/fractional_knapsack.swift b/zh-hant/codes/swift/chapter_greedy/fractional_knapsack.swift new file mode 100644 index 0000000000..9dd57b7070 --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/fractional_knapsack.swift @@ -0,0 +1,57 @@ +/** + * File: fractional_knapsack.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 物品 */ +class Item { + var w: Int // 物品重量 + var v: Int // 物品價值 + + init(w: Int, v: Int) { + self.w = w + self.v = v + } +} + +/* 分數背包:貪婪 */ +func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { + // 建立物品串列,包含兩個屬性:重量、價值 + var items = zip(wgt, val).map { Item(w: $0, v: $1) } + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } + // 迴圈貪婪選擇 + var res = 0.0 + var cap = cap + for item in items { + if item.w <= cap { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += Double(item.v) + cap -= item.w + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += Double(item.v) / Double(item.w) * Double(cap) + // 已無剩餘容量,因此跳出迴圈 + break + } + } + return res +} + +@main +enum FractionalKnapsack { + /* Driver Code */ + static func main() { + // 物品重量 + let wgt = [10, 20, 30, 40, 50] + // 物品價值 + let val = [50, 120, 150, 210, 240] + // 背包容量 + let cap = 50 + + // 貪婪演算法 + let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/max_capacity.swift b/zh-hant/codes/swift/chapter_greedy/max_capacity.swift new file mode 100644 index 0000000000..77169d8703 --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/max_capacity.swift @@ -0,0 +1,38 @@ +/** + * File: max_capacity.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最大容量:貪婪 */ +func maxCapacity(ht: [Int]) -> Int { + // 初始化 i, j,使其分列陣列兩端 + var i = ht.startIndex, j = ht.endIndex - 1 + // 初始最大容量為 0 + var res = 0 + // 迴圈貪婪選擇,直至兩板相遇 + while i < j { + // 更新最大容量 + let cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 向內移動短板 + if ht[i] < ht[j] { + i += 1 + } else { + j -= 1 + } + } + return res +} + +@main +enum MaxCapacity { + /* Driver Code */ + static func main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4] + + // 貪婪演算法 + let res = maxCapacity(ht: ht) + print("最大容量為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/max_product_cutting.swift b/zh-hant/codes/swift/chapter_greedy/max_product_cutting.swift new file mode 100644 index 0000000000..69f6d243bc --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/max_product_cutting.swift @@ -0,0 +1,43 @@ +/** + * File: max_product_cutting.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import Foundation + +func pow(_ x: Int, _ y: Int) -> Int { + Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) +} + +/* 最大切分乘積:貪婪 */ +func maxProductCutting(n: Int) -> Int { + // 當 n <= 3 時,必須切分出一個 1 + if n <= 3 { + return 1 * (n - 1) + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a = n / 3 + let 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) +} + +@main +enum MaxProductCutting { + static func main() { + let n = 58 + + // 貪婪演算法 + let res = maxProductCutting(n: n) + print("最大切分乘積為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/array_hash_map.swift b/zh-hant/codes/swift/chapter_hashing/array_hash_map.swift new file mode 100644 index 0000000000..61f373ca23 --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/array_hash_map.swift @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private var buckets: [Pair?] + + init() { + // 初始化陣列,包含 100 個桶 + buckets = Array(repeating: nil, count: 100) + } + + /* 雜湊函式 */ + private func hashFunc(key: Int) -> Int { + let index = key % 100 + return index + } + + /* 查詢操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let pair = buckets[index] + return pair?.val + } + + /* 新增操作 */ + func put(key: Int, val: String) { + let pair = Pair(key: key, val: val) + let index = hashFunc(key: key) + buckets[index] = pair + } + + /* 刪除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + // 置為 nil ,代表刪除 + buckets[index] = nil + } + + /* 獲取所有鍵值對 */ + func pairSet() -> [Pair] { + buckets.compactMap { $0 } + } + + /* 獲取所有鍵 */ + func keySet() -> [Int] { + buckets.compactMap { $0?.key } + } + + /* 獲取所有值 */ + func valueSet() -> [String] { + buckets.compactMap { $0?.val } + } + + /* 列印雜湊表 */ + func print() { + for pair in pairSet() { + Swift.print("\(pair.key) -> \(pair.val)") + } + } +} + +@main +enum _ArrayHashMap { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + let map = ArrayHashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小囉") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(key: 15937)! + print("\n輸入學號 15937 ,查詢到姓名 \(name)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(key: 10583) + print("\n刪除 10583 後,雜湊表為\nKey -> Value") + map.print() + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value") + for pair in map.pairSet() { + print("\(pair.key) -> \(pair.val)") + } + print("\n單獨走訪鍵 Key") + for key in map.keySet() { + print(key) + } + print("\n單獨走訪值 Value") + for val in map.valueSet() { + print(val) + } + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/built_in_hash.swift b/zh-hant/codes/swift/chapter_hashing/built_in_hash.swift new file mode 100644 index 0000000000..5e5871fcca --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/built_in_hash.swift @@ -0,0 +1,37 @@ +/** + * File: built_in_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BuiltInHash { + /* Driver Code */ + static func main() { + let num = 3 + let hashNum = num.hashValue + print("整數 \(num) 的雜湊值為 \(hashNum)") + + let bol = true + let hashBol = bol.hashValue + print("布林量 \(bol) 的雜湊值為 \(hashBol)") + + let dec = 3.14159 + let hashDec = dec.hashValue + print("小數 \(dec) 的雜湊值為 \(hashDec)") + + let str = "Hello 演算法" + let hashStr = str.hashValue + print("字串 \(str) 的雜湊值為 \(hashStr)") + + let arr = [AnyHashable(12836), AnyHashable("小哈")] + let hashTup = arr.hashValue + print("陣列 \(arr) 的雜湊值為 \(hashTup)") + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + print("節點物件 \(obj) 的雜湊值為 \(hashObj)") + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/hash_map.swift b/zh-hant/codes/swift/chapter_hashing/hash_map.swift new file mode 100644 index 0000000000..822cde8f3b --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/hash_map.swift @@ -0,0 +1,51 @@ +/** + * File: hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum HashMap { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + var map: [Int: String] = [:] + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + print("\n新增完成後,雜湊表為\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map[15937]! + print("\n輸入學號 15937 ,查詢到姓名 \(name)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.removeValue(forKey: 10583) + print("\n刪除 10583 後,雜湊表為\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value") + for (key, value) in map { + print("\(key) -> \(value)") + } + print("\n單獨走訪鍵 Key") + for key in map.keys { + print(key) + } + print("\n單獨走訪值 Value") + for value in map.values { + print(value) + } + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/hash_map_chaining.swift b/zh-hant/codes/swift/chapter_hashing/hash_map_chaining.swift new file mode 100644 index 0000000000..0e869936bd --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/hash_map_chaining.swift @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + var size: Int // 鍵值對數量 + var capacity: Int // 雜湊表容量 + var loadThres: Double // 觸發擴容的負載因子閾值 + var extendRatio: Int // 擴容倍數 + var buckets: [[Pair]] // 桶陣列 + + /* 建構子 */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: [], count: capacity) + } + + /* 雜湊函式 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 負載因子 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* 查詢操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 走訪桶,若找到 key ,則返回對應 val + for pair in bucket { + if pair.key == key { + return pair.val + } + } + // 若未找到 key ,則返回 nil + return nil + } + + /* 新增操作 */ + func put(key: Int, val: String) { + // 當負載因子超過閾值時,執行擴容 + if loadFactor() > loadThres { + extend() + } + let index = hashFunc(key: key) + let bucket = buckets[index] + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for pair in bucket { + if pair.key == key { + pair.val = val + return + } + } + // 若無該 key ,則將鍵值對新增至尾部 + let pair = Pair(key: key, val: val) + buckets[index].append(pair) + size += 1 + } + + /* 刪除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 走訪桶,從中刪除鍵值對 + for (pairIndex, pair) in bucket.enumerated() { + if pair.key == key { + buckets[index].remove(at: pairIndex) + size -= 1 + break + } + } + } + + /* 擴容雜湊表 */ + func extend() { + // 暫存原雜湊表 + let bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + buckets = Array(repeating: [], count: capacity) + size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for bucket in bucketsTmp { + for pair in bucket { + put(key: pair.key, val: pair.val) + } + } + } + + /* 列印雜湊表 */ + func print() { + for bucket in buckets { + let res = bucket.map { "\($0.key) -> \($0.val)" } + Swift.print(res) + } + } +} + +@main +enum _HashMapChaining { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + let map = HashMapChaining() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小囉") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(key: 13276) + print("\n輸入學號 13276 ,查詢到姓名 \(name!)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(key: 12836) + print("\n刪除 12836 後,雜湊表為\nKey -> Value") + map.print() + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/hash_map_open_addressing.swift b/zh-hant/codes/swift/chapter_hashing/hash_map_open_addressing.swift new file mode 100644 index 0000000000..390ab68230 --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/hash_map_open_addressing.swift @@ -0,0 +1,164 @@ +/** + * File: hash_map_open_addressing.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + var size: Int // 鍵值對數量 + var capacity: Int // 雜湊表容量 + var loadThres: Double // 觸發擴容的負載因子閾值 + var extendRatio: Int // 擴容倍數 + var buckets: [Pair?] // 桶陣列 + var TOMBSTONE: Pair // 刪除標記 + + /* 建構子 */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: nil, count: capacity) + TOMBSTONE = Pair(key: -1, val: "-1") + } + + /* 雜湊函式 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 負載因子 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* 搜尋 key 對應的桶索引 */ + func findBucket(key: Int) -> Int { + var index = hashFunc(key: key) + var firstTombstone = -1 + // 線性探查,當遇到空桶時跳出 + while buckets[index] != nil { + // 若遇到 key ,返回對應的桶索引 + if buckets[index]!.key == key { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if firstTombstone != -1 { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 返回移動後的桶索引 + } + return index // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if firstTombstone == -1 && buckets[index] == TOMBSTONE { + firstTombstone = index + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone + } + + /* 查詢操作 */ + func get(key: Int) -> String? { + // 搜尋 key 對應的桶索引 + let index = findBucket(key: key) + // 若找到鍵值對,則返回對應 val + if buckets[index] != nil, buckets[index] != TOMBSTONE { + return buckets[index]!.val + } + // 若鍵值對不存在,則返回 null + return nil + } + + /* 新增操作 */ + func put(key: Int, val: String) { + // 當負載因子超過閾值時,執行擴容 + if loadFactor() > loadThres { + extend() + } + // 搜尋 key 對應的桶索引 + let index = findBucket(key: key) + // 若找到鍵值對,則覆蓋 val 並返回 + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index]!.val = val + return + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = Pair(key: key, val: val) + size += 1 + } + + /* 刪除操作 */ + func remove(key: Int) { + // 搜尋 key 對應的桶索引 + let index = findBucket(key: key) + // 若找到鍵值對,則用刪除標記覆蓋它 + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index] = TOMBSTONE + size -= 1 + } + } + + /* 擴容雜湊表 */ + func extend() { + // 暫存原雜湊表 + let bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + buckets = Array(repeating: nil, count: capacity) + size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for pair in bucketsTmp { + if let pair, pair != TOMBSTONE { + put(key: pair.key, val: pair.val) + } + } + } + + /* 列印雜湊表 */ + func print() { + for pair in buckets { + if pair == nil { + Swift.print("null") + } else if pair == TOMBSTONE { + Swift.print("TOMBSTONE") + } else { + Swift.print("\(pair!.key) -> \(pair!.val)") + } + } + } +} + +@main +enum _HashMapOpenAddressing { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + let map = HashMapOpenAddressing() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小囉") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(key: 13276) + print("\n輸入學號 13276 ,查詢到姓名 \(name!)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(key: 16750) + print("\n刪除 16750 後,雜湊表為\nKey -> Value") + map.print() + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/simple_hash.swift b/zh-hant/codes/swift/chapter_hashing/simple_hash.swift new file mode 100644 index 0000000000..0b2188266b --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/simple_hash.swift @@ -0,0 +1,73 @@ +/** + * File: simple_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 加法雜湊 */ +func addHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* 乘法雜湊 */ +func mulHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (31 * hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* 互斥或雜湊 */ +func xorHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash ^= Int(scalar.value) + } + } + return hash & MODULUS +} + +/* 旋轉雜湊 */ +func rotHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS + } + } + return hash +} + +@main +enum SimpleHash { + /* Driver Code */ + static func main() { + let key = "Hello 演算法" + + var hash = addHash(key: key) + print("加法雜湊值為 \(hash)") + + hash = mulHash(key: key) + print("乘法雜湊值為 \(hash)") + + hash = xorHash(key: key) + print("互斥或雜湊值為 \(hash)") + + hash = rotHash(key: key) + print("旋轉雜湊值為 \(hash)") + } +} diff --git a/zh-hant/codes/swift/chapter_heap/heap.swift b/zh-hant/codes/swift/chapter_heap/heap.swift new file mode 100644 index 0000000000..c24e202533 --- /dev/null +++ b/zh-hant/codes/swift/chapter_heap/heap.swift @@ -0,0 +1,62 @@ +/** + * File: heap.swift + * Created Time: 2024-03-17 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +func testPush(heap: inout Heap, val: Int) { + heap.insert(val) + print("\n元素 \(val) 入堆積後\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +func testPop(heap: inout Heap) { + let val = heap.removeMax() + print("\n堆積頂元素 \(val) 出堆積後\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +@main +enum _Heap { + /* Driver Code */ + static func main() { + /* 初始化堆積 */ + // Swift 的 Heap 型別同時支持最大堆積和最小堆積 + var heap = Heap() + + /* 元素入堆積 */ + testPush(heap: &heap, val: 1) + testPush(heap: &heap, val: 3) + testPush(heap: &heap, val: 2) + testPush(heap: &heap, val: 5) + testPush(heap: &heap, val: 4) + + /* 獲取堆積頂元素 */ + let peek = heap.max() + print("\n堆積頂元素為 \(peek!)\n") + + /* 堆積頂元素出堆積 */ + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + + /* 獲取堆積大小 */ + let size = heap.count + print("\n堆積元素數量為 \(size)\n") + + /* 判斷堆積是否為空 */ + let isEmpty = heap.isEmpty + print("\n堆積是否為空 \(isEmpty)\n") + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + let heap2 = Heap([1, 3, 2, 5, 4]) + print("\n輸入串列並建立堆積後") + PrintUtil.printHeap(queue: heap2.unordered) + } +} diff --git a/zh-hant/codes/swift/chapter_heap/my_heap.swift b/zh-hant/codes/swift/chapter_heap/my_heap.swift new file mode 100644 index 0000000000..43f63a6180 --- /dev/null +++ b/zh-hant/codes/swift/chapter_heap/my_heap.swift @@ -0,0 +1,163 @@ +/** + * File: my_heap.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 大頂堆積 */ +class MaxHeap { + private var maxHeap: [Int] + + /* 建構子,根據輸入串列建堆積 */ + init(nums: [Int]) { + // 將串列元素原封不動新增進堆積 + maxHeap = nums + // 堆積化除葉節點以外的其他所有節點 + for i in (0 ... parent(i: size() - 1)).reversed() { + siftDown(i: i) + } + } + + /* 獲取左子節點的索引 */ + private func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 獲取右子節點的索引 */ + private func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 獲取父節點的索引 */ + private func parent(i: Int) -> Int { + (i - 1) / 2 // 向下整除 + } + + /* 交換元素 */ + private func swap(i: Int, j: Int) { + maxHeap.swapAt(i, j) + } + + /* 獲取堆積大小 */ + func size() -> Int { + maxHeap.count + } + + /* 判斷堆積是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 訪問堆積頂元素 */ + func peek() -> Int { + maxHeap[0] + } + + /* 元素入堆積 */ + func push(val: Int) { + // 新增節點 + maxHeap.append(val) + // 從底至頂堆積化 + siftUp(i: size() - 1) + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private func siftUp(i: Int) { + var i = i + while true { + // 獲取節點 i 的父節點 + let p = parent(i: i) + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if p < 0 || maxHeap[i] <= maxHeap[p] { + break + } + // 交換兩節點 + swap(i: i, j: p) + // 迴圈向上堆積化 + i = p + } + } + + /* 元素出堆積 */ + func pop() -> Int { + // 判空處理 + if isEmpty() { + fatalError("堆積為空") + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(i: 0, j: size() - 1) + // 刪除節點 + let val = maxHeap.remove(at: size() - 1) + // 從頂至底堆積化 + siftDown(i: 0) + // 返回堆積頂元素 + return val + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private func siftDown(i: Int) { + var i = i + while true { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = left(i: i) + let r = right(i: i) + var ma = i + if l < size(), maxHeap[l] > maxHeap[ma] { + ma = l + } + if r < size(), maxHeap[r] > maxHeap[ma] { + ma = r + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i { + break + } + // 交換兩節點 + swap(i: i, j: ma) + // 迴圈向下堆積化 + i = ma + } + } + + /* 列印堆積(二元樹) */ + func print() { + let queue = maxHeap + PrintUtil.printHeap(queue: queue) + } +} + +@main +enum MyHeap { + /* Driver Code */ + static func main() { + /* 初始化大頂堆積 */ + let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\n輸入串列並建堆積後") + maxHeap.print() + + /* 獲取堆積頂元素 */ + var peek = maxHeap.peek() + print("\n堆積頂元素為 \(peek)") + + /* 元素入堆積 */ + let val = 7 + maxHeap.push(val: val) + print("\n元素 \(val) 入堆積後") + maxHeap.print() + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop() + print("\n堆積頂元素 \(peek) 出堆積後") + maxHeap.print() + + /* 獲取堆積大小 */ + let size = maxHeap.size() + print("\n堆積元素數量為 \(size)") + + /* 判斷堆積是否為空 */ + let isEmpty = maxHeap.isEmpty() + print("\n堆積是否為空 \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_heap/top_k.swift b/zh-hant/codes/swift/chapter_heap/top_k.swift new file mode 100644 index 0000000000..36075ec3c7 --- /dev/null +++ b/zh-hant/codes/swift/chapter_heap/top_k.swift @@ -0,0 +1,36 @@ +/** + * File: top_k.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +func topKHeap(nums: [Int], k: Int) -> [Int] { + // 初始化一個小頂堆積,並將前 k 個元素建堆積 + var heap = Heap(nums.prefix(k)) + // 從第 k+1 個元素開始,保持堆積的長度為 k + for i in nums.indices.dropFirst(k) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if nums[i] > heap.min()! { + _ = heap.removeMin() + heap.insert(nums[i]) + } + } + return heap.unordered +} + +@main +enum TopK { + /* Driver Code */ + static func main() { + let nums = [1, 7, 6, 3, 2] + let k = 3 + + let res = topKHeap(nums: nums, k: k) + print("最大的 \(k) 個元素為") + PrintUtil.printHeap(queue: res) + } +} diff --git a/zh-hant/codes/swift/chapter_searching/binary_search.swift b/zh-hant/codes/swift/chapter_searching/binary_search.swift new file mode 100644 index 0000000000..c86860bea9 --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search.swift @@ -0,0 +1,62 @@ +/** + * File: binary_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋(雙閉區間) */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + var i = nums.startIndex + var j = nums.endIndex - 1 + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while i <= j { + let 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 +} + +/* 二分搜尋(左閉右開區間) */ +func binarySearchLCRO(nums: [Int], target: Int) -> Int { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + var i = nums.startIndex + var j = nums.endIndex + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while i < j { + let 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 +} + +@main +enum BinarySearch { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + /* 二分搜尋(雙閉區間) */ + var index = binarySearch(nums: nums, target: target) + print("目標元素 6 的索引 = \(index)") + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums: nums, target: target) + print("目標元素 6 的索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_searching/binary_search_edge.swift b/zh-hant/codes/swift/chapter_searching/binary_search_edge.swift new file mode 100644 index 0000000000..11f45fbf31 --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search_edge.swift @@ -0,0 +1,51 @@ +/** + * File: binary_search_edge.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import binary_search_insertion_target + +/* 二分搜尋最左一個 target */ +func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { + // 等價於查詢 target 的插入點 + let i = binarySearchInsertion(nums: nums, target: target) + // 未找到 target ,返回 -1 + if i == nums.endIndex || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分搜尋最右一個 target */ +func binarySearchRightEdge(nums: [Int], target: Int) -> Int { + // 轉化為查詢最左一個 target + 1 + let i = binarySearchInsertion(nums: nums, target: target + 1) + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + let j = i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j +} + +@main +enum BinarySearchEdge { + /* Driver Code */ + static func main() { + // 包含重複元素的陣列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n陣列 nums = \(nums)") + + // 二分搜尋左邊界和右邊界 + for target in [6, 7] { + var index = binarySearchLeftEdge(nums: nums, target: target) + print("最左一個元素 \(target) 的索引為 \(index)") + index = binarySearchRightEdge(nums: nums, target: target) + print("最右一個元素 \(target) 的索引為 \(index)") + } + } +} diff --git a/zh-hant/codes/swift/chapter_searching/binary_search_insertion.swift b/zh-hant/codes/swift/chapter_searching/binary_search_insertion.swift new file mode 100644 index 0000000000..49f2ce267a --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search_insertion.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let 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 +} + +/* 二分搜尋插入點(存在重複元素) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let 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 +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // 無重複元素的陣列 + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_searching/binary_search_insertion_target.swift b/zh-hant/codes/swift/chapter_searching/binary_search_insertion_target.swift new file mode 100644 index 0000000000..49f2ce267a --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search_insertion_target.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let 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 +} + +/* 二分搜尋插入點(存在重複元素) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let 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 +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // 無重複元素的陣列 + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_searching/hashing_search.swift b/zh-hant/codes/swift/chapter_searching/hashing_search.swift new file mode 100644 index 0000000000..4e14acc41c --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/hashing_search.swift @@ -0,0 +1,50 @@ +/** + * File: hashing_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 雜湊查詢(陣列) */ +func hashingSearchArray(map: [Int: Int], target: Int) -> Int { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map[target, default: -1] +} + +/* 雜湊查詢(鏈結串列) */ +func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map[target] +} + +@main +enum HashingSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* 雜湊查詢(陣列) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + // 初始化雜湊表 + var map: [Int: Int] = [:] + for i in nums.indices { + map[nums[i]] = i // key: 元素,value: 索引 + } + let index = hashingSearchArray(map: map, target: target) + print("目標元素 3 的索引 = \(index)") + + /* 雜湊查詢(鏈結串列) */ + var head = ListNode.arrToLinkedList(arr: nums) + // 初始化雜湊表 + var map1: [Int: ListNode] = [:] + while head != nil { + map1[head!.val] = head! // key: 節點值,value: 節點 + head = head?.next + } + let node = hashingSearchLinkedList(map: map1, target: target) + print("目標節點值 3 的對應節點物件為 \(node!)") + } +} diff --git a/zh-hant/codes/swift/chapter_searching/linear_search.swift b/zh-hant/codes/swift/chapter_searching/linear_search.swift new file mode 100644 index 0000000000..b018e855d9 --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/linear_search.swift @@ -0,0 +1,53 @@ +/** + * File: linear_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 線性查詢(陣列) */ +func linearSearchArray(nums: [Int], target: Int) -> Int { + // 走訪陣列 + for i in nums.indices { + // 找到目標元素,返回其索引 + if nums[i] == target { + return i + } + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 線性查詢(鏈結串列) */ +func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { + var head = head + // 走訪鏈結串列 + while head != nil { + // 找到目標節點,返回之 + if head?.val == target { + return head + } + head = head?.next + } + // 未找到目標節點,返回 null + return nil +} + +@main +enum LinearSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* 在陣列中執行線性查詢 */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + let index = linearSearchArray(nums: nums, target: target) + print("目標元素 3 的索引 = \(index)") + + /* 在鏈結串列中執行線性查詢 */ + let head = ListNode.arrToLinkedList(arr: nums) + let node = linearSearchLinkedList(head: head, target: target) + print("目標節點值 3 的對應節點物件為 \(node!)") + } +} diff --git a/zh-hant/codes/swift/chapter_searching/two_sum.swift b/zh-hant/codes/swift/chapter_searching/two_sum.swift new file mode 100644 index 0000000000..aca95b5dcf --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/two_sum.swift @@ -0,0 +1,49 @@ +/** + * File: two_sum.swift + * Created Time: 2023-01-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 方法一:暴力列舉 */ +func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { + // 兩層迴圈,時間複雜度為 O(n^2) + for i in nums.indices.dropLast() { + for j in nums.indices.dropFirst(i + 1) { + if nums[i] + nums[j] == target { + return [i, j] + } + } + } + return [0] +} + +/* 方法二:輔助雜湊表 */ +func twoSumHashTable(nums: [Int], target: Int) -> [Int] { + // 輔助雜湊表,空間複雜度為 O(n) + var dic: [Int: Int] = [:] + // 單層迴圈,時間複雜度為 O(n) + for i in nums.indices { + if let j = dic[target - nums[i]] { + return [j, i] + } + dic[nums[i]] = i + } + return [0] +} + +@main +enum LeetcodeTwoSum { + /* Driver Code */ + static func main() { + // ======= Test Case ======= + let nums = [2, 7, 11, 15] + let target = 13 + // ====== Driver Code ====== + // 方法一 + var res = twoSumBruteForce(nums: nums, target: target) + print("方法一 res = \(res)") + // 方法二 + res = twoSumHashTable(nums: nums, target: target) + print("方法二 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/bubble_sort.swift b/zh-hant/codes/swift/chapter_sorting/bubble_sort.swift new file mode 100644 index 0000000000..cea8db2856 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/bubble_sort.swift @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 泡沫排序 */ +func bubbleSort(nums: inout [Int]) { + // 外迴圈:未排序區間為 [0, i] + for i in nums.indices.dropFirst().reversed() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + nums.swapAt(j, j + 1) + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +func bubbleSortWithFlag(nums: inout [Int]) { + // 外迴圈:未排序區間為 [0, i] + for i in nums.indices.dropFirst().reversed() { + var flag = false // 初始化標誌位 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + nums.swapAt(j, j + 1) + flag = true // 記錄交換元素 + } + } + if !flag { // 此輪“冒泡”未交換任何元素,直接跳出 + break + } + } +} + +@main +enum BubbleSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + bubbleSort(nums: &nums) + print("泡沫排序完成後 nums = \(nums)") + + var nums1 = [4, 1, 3, 1, 5, 2] + bubbleSortWithFlag(nums: &nums1) + print("泡沫排序完成後 nums1 = \(nums1)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/bucket_sort.swift b/zh-hant/codes/swift/chapter_sorting/bucket_sort.swift new file mode 100644 index 0000000000..93fbffa641 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/bucket_sort.swift @@ -0,0 +1,43 @@ +/** + * File: bucket_sort.swift + * Created Time: 2023-03-27 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 桶排序 */ +func bucketSort(nums: inout [Double]) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + let k = nums.count / 2 + var buckets = (0 ..< k).map { _ in [Double]() } + // 1. 將陣列元素分配到各個桶中 + for num in nums { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + let i = Int(num * Double(k)) + // 將 num 新增進桶 i + buckets[i].append(num) + } + // 2. 對各個桶執行排序 + for i in buckets.indices { + // 使用內建排序函式,也可以替換成其他排序演算法 + buckets[i].sort() + } + // 3. 走訪桶合併結果 + var i = nums.startIndex + for bucket in buckets { + for num in bucket { + nums[i] = num + i += 1 + } + } +} + +@main +enum BucketSort { + /* Driver Code */ + static func main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucketSort(nums: &nums) + print("桶排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/counting_sort.swift b/zh-hant/codes/swift/chapter_sorting/counting_sort.swift new file mode 100644 index 0000000000..ff28a22ae2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/counting_sort.swift @@ -0,0 +1,70 @@ +/** + * File: counting_sort.swift + * Created Time: 2023-03-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +func countingSortNaive(nums: inout [Int]) { + // 1. 統計陣列最大元素 m + let m = nums.max()! + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + var i = 0 + for num in 0 ..< m + 1 { + for _ in 0 ..< counter[num] { + nums[i] = num + i += 1 + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +func countingSort(nums: inout [Int]) { + // 1. 統計陣列最大元素 m + let m = nums.max()! + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for i in 0 ..< m { + counter[i + 1] += counter[i] + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let num = nums[i] + res[counter[num] - 1] = num // 將 num 放置到對應索引處 + counter[num] -= 1 // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for i in nums.indices { + nums[i] = res[i] + } +} + +@main +enum CountingSort { + /* Driver Code */ + static func main() { + var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSortNaive(nums: &nums) + print("計數排序(無法排序物件)完成後 nums = \(nums)") + + var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSort(nums: &nums1) + print("計數排序完成後 nums1 = \(nums1)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/heap_sort.swift b/zh-hant/codes/swift/chapter_sorting/heap_sort.swift new file mode 100644 index 0000000000..fc709e1960 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/heap_sort.swift @@ -0,0 +1,55 @@ +/** + * File: heap_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +func siftDown(nums: inout [Int], n: Int, i: Int) { + var i = i + while true { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1 + let r = 2 * i + 2 + var 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 + } + // 交換兩節點 + nums.swapAt(i, ma) + // 迴圈向下堆積化 + i = ma + } +} + +/* 堆積排序 */ +func heapSort(nums: inout [Int]) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { + siftDown(nums: &nums, n: nums.count, i: i) + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for i in nums.indices.dropFirst().reversed() { + // 交換根節點與最右葉節點(交換首元素與尾元素) + nums.swapAt(0, i) + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums: &nums, n: i, i: 0) + } +} + +@main +enum HeapSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + heapSort(nums: &nums) + print("堆積排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/insertion_sort.swift b/zh-hant/codes/swift/chapter_sorting/insertion_sort.swift new file mode 100644 index 0000000000..869912f146 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/insertion_sort.swift @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 插入排序 */ +func insertionSort(nums: inout [Int]) { + // 外迴圈:已排序區間為 [0, i-1] + for i in nums.indices.dropFirst() { + let base = nums[i] + var j = i - 1 + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while j >= 0, nums[j] > base { + nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位 + j -= 1 + } + nums[j + 1] = base // 將 base 賦值到正確位置 + } +} + +@main +enum InsertionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + insertionSort(nums: &nums) + print("插入排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/merge_sort.swift b/zh-hant/codes/swift/chapter_sorting/merge_sort.swift new file mode 100644 index 0000000000..9784185898 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/merge_sort.swift @@ -0,0 +1,65 @@ +/** + * File: merge_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 合併左子陣列和右子陣列 */ +func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + var tmp = Array(repeating: 0, count: right - left + 1) + // 初始化左子陣列和右子陣列的起始索引 + var i = left, j = mid + 1, k = 0 + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while i <= mid, j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i += 1 + } else { + tmp[k] = nums[j] + j += 1 + } + k += 1 + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while i <= mid { + tmp[k] = nums[i] + i += 1 + k += 1 + } + while j <= right { + tmp[k] = nums[j] + j += 1 + k += 1 + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for k in tmp.indices { + nums[left + k] = tmp[k] + } +} + +/* 合併排序 */ +func mergeSort(nums: inout [Int], left: Int, right: Int) { + // 終止條件 + if left >= right { // 當子陣列長度為 1 時終止遞迴 + return + } + // 劃分階段 + let mid = left + (right - left) / 2 // 計算中點 + mergeSort(nums: &nums, left: left, right: mid) // 遞迴左子陣列 + mergeSort(nums: &nums, left: mid + 1, right: right) // 遞迴右子陣列 + // 合併階段 + merge(nums: &nums, left: left, mid: mid, right: right) +} + +@main +enum MergeSort { + /* Driver Code */ + static func main() { + /* 合併排序 */ + var nums = [7, 3, 2, 6, 0, 1, 5, 4] + mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("合併排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/quick_sort.swift b/zh-hant/codes/swift/chapter_sorting/quick_sort.swift new file mode 100644 index 0000000000..550b18bcd4 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/quick_sort.swift @@ -0,0 +1,114 @@ +/** + * File: quick_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 快速排序類別 */ +/* 哨兵劃分 */ +func partition(nums: inout [Int], left: Int, right: Int) -> Int { + // 以 nums[left] 為基準數 + var i = left + var j = right + while i < j { + while i < j, nums[j] >= nums[left] { + j -= 1 // 從右向左找首個小於基準數的元素 + } + while i < j, nums[i] <= nums[left] { + i += 1 // 從左向右找首個大於基準數的元素 + } + nums.swapAt(i, j) // 交換這兩個元素 + } + nums.swapAt(i, left) // 將基準數交換至兩子陣列的分界線 + return i // 返回基準數的索引 +} + +/* 快速排序 */ +func quickSort(nums: inout [Int], left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + let pivot = partition(nums: &nums, left: left, right: right) + // 遞迴左子陣列、右子陣列 + quickSort(nums: &nums, left: left, right: pivot - 1) + quickSort(nums: &nums, left: pivot + 1, right: right) +} + +/* 快速排序類別(中位基準數最佳化) */ +/* 選取三個候選元素的中位數 */ +func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { + let l = nums[left] + let m = nums[mid] + let 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 +} + +/* 哨兵劃分(三數取中值) */ +func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { + // 選取三個候選元素的中位數 + let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) + // 將中位數交換至陣列最左端 + nums.swapAt(left, med) + return partition(nums: &nums, left: left, right: right) +} + +/* 快速排序(中位基準數最佳化) */ +func quickSortMedian(nums: inout [Int], left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + let pivot = partitionMedian(nums: &nums, left: left, right: right) + // 遞迴左子陣列、右子陣列 + quickSortMedian(nums: &nums, left: left, right: pivot - 1) + quickSortMedian(nums: &nums, left: pivot + 1, right: right) +} + +/* 快速排序(尾遞迴最佳化) */ +func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { + var left = left + var right = right + // 子陣列長度為 1 時終止 + while left < right { + // 哨兵劃分操作 + let pivot = partition(nums: &nums, left: left, right: right) + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left) < (right - pivot) { + quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 遞迴排序左子陣列 + left = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 遞迴排序右子陣列 + right = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] + } + } +} + +@main +enum QuickSort { + /* Driver Code */ + static func main() { + /* 快速排序 */ + var nums = [2, 4, 1, 0, 3, 5] + quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("快速排序完成後 nums = \(nums)") + + /* 快速排序(中位基準數最佳化) */ + var nums1 = [2, 4, 1, 0, 3, 5] + quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) + print("快速排序(中位基準數最佳化)完成後 nums1 = \(nums1)") + + /* 快速排序(尾遞迴最佳化) */ + var nums2 = [2, 4, 1, 0, 3, 5] + quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) + print("快速排序(尾遞迴最佳化)完成後 nums2 = \(nums2)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/radix_sort.swift b/zh-hant/codes/swift/chapter_sorting/radix_sort.swift new file mode 100644 index 0000000000..15f9ba7f55 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/radix_sort.swift @@ -0,0 +1,79 @@ +/** + * File: radix_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +func digit(num: Int, exp: Int) -> Int { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + (num / exp) % 10 +} + +/* 計數排序(根據 nums 第 k 位排序) */ +func countingSortDigit(nums: inout [Int], exp: Int) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + var counter = Array(repeating: 0, count: 10) + // 統計 0~9 各數字的出現次數 + for i in nums.indices { + let d = digit(num: nums[i], exp: exp) // 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1 // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for i in 1 ..< 10 { + counter[i] += counter[i - 1] + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let d = digit(num: nums[i], exp: exp) + let j = counter[d] - 1 // 獲取 d 在陣列中的索引 j + res[j] = nums[i] // 將當前元素填入索引 j + counter[d] -= 1 // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for i in nums.indices { + nums[i] = res[i] + } +} + +/* 基數排序 */ +func radixSort(nums: inout [Int]) { + // 獲取陣列的最大元素,用於判斷最大位數 + var m = Int.min + for num in nums { + if num > m { + m = num + } + } + // 按照從低位到高位的順序走訪 + for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums: &nums, exp: exp) + } +} + +@main +enum RadixSort { + /* Driver Code */ + static func main() { + // 基數排序 + var nums = [ + 10_546_151, + 35_663_510, + 42_865_989, + 34_862_445, + 81_883_077, + 88_906_420, + 72_429_244, + 30_524_779, + 82_060_337, + 63_832_996, + ] + radixSort(nums: &nums) + print("基數排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/selection_sort.swift b/zh-hant/codes/swift/chapter_sorting/selection_sort.swift new file mode 100644 index 0000000000..d65833242c --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/selection_sort.swift @@ -0,0 +1,31 @@ +/** + * File: selection_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 選擇排序 */ +func selectionSort(nums: inout [Int]) { + // 外迴圈:未排序區間為 [i, n-1] + for i in nums.indices.dropLast() { + // 內迴圈:找到未排序區間內的最小元素 + var k = i + for j in nums.indices.dropFirst(i + 1) { + if nums[j] < nums[k] { + k = j // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + nums.swapAt(i, k) + } +} + +@main +enum SelectionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + selectionSort(nums: &nums) + print("選擇排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/array_deque.swift b/zh-hant/codes/swift/chapter_stack_and_queue/array_deque.swift new file mode 100644 index 0000000000..5152da047a --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/array_deque.swift @@ -0,0 +1,148 @@ +/** + * File: array_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private var nums: [Int] // 用於儲存雙向佇列元素的陣列 + private var front: Int // 佇列首指標,指向佇列首元素 + private var _size: Int // 雙向佇列長度 + + /* 建構子 */ + init(capacity: Int) { + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* 獲取雙向佇列的容量 */ + func capacity() -> Int { + nums.count + } + + /* 獲取雙向佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷雙向佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 計算環形陣列索引 */ + private func index(i: Int) -> Int { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + (i + capacity()) % capacity() + } + + /* 佇列首入列 */ + func pushFirst(num: Int) { + if size() == capacity() { + print("雙向佇列已滿") + return + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = index(i: front - 1) + // 將 num 新增至佇列首 + nums[front] = num + _size += 1 + } + + /* 佇列尾入列 */ + func pushLast(num: Int) { + if size() == capacity() { + print("雙向佇列已滿") + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + let rear = index(i: front + size()) + // 將 num 新增至佇列尾 + nums[rear] = num + _size += 1 + } + + /* 佇列首出列 */ + func popFirst() -> Int { + let num = peekFirst() + // 佇列首指標向後移動一位 + front = index(i: front + 1) + _size -= 1 + return num + } + + /* 佇列尾出列 */ + func popLast() -> Int { + let num = peekLast() + _size -= 1 + return num + } + + /* 訪問佇列首元素 */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + return nums[front] + } + + /* 訪問佇列尾元素 */ + func peekLast() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + // 計算尾元素索引 + let last = index(i: front + size() - 1) + return nums[last] + } + + /* 返回陣列用於列印 */ + func toArray() -> [Int] { + // 僅轉換有效長度範圍內的串列元素 + (front ..< front + size()).map { nums[index(i: $0)] } + } +} + +@main +enum _ArrayDeque { + /* Driver Code */ + static func main() { + /* 初始化雙向佇列 */ + let deque = ArrayDeque(capacity: 10) + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("雙向佇列 deque = \(deque.toArray())") + + /* 訪問元素 */ + let peekFirst = deque.peekFirst() + print("佇列首元素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("佇列尾元素 peekLast = \(peekLast)") + + /* 元素入列 */ + deque.pushLast(num: 4) + print("元素 4 佇列尾入列後 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("元素 1 佇列首入列後 deque = \(deque.toArray())") + + /* 元素出列 */ + let popLast = deque.popLast() + print("佇列尾出列元素 = \(popLast),佇列尾出列後 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("佇列首出列元素 = \(popFirst),佇列首出列後 deque = \(deque.toArray())") + + /* 獲取雙向佇列的長度 */ + let size = deque.size() + print("雙向佇列長度 size = \(size)") + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty() + print("雙向佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/array_queue.swift b/zh-hant/codes/swift/chapter_stack_and_queue/array_queue.swift new file mode 100644 index 0000000000..c7e86207e0 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/array_queue.swift @@ -0,0 +1,113 @@ +/** + * File: array_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private var nums: [Int] // 用於儲存佇列元素的陣列 + private var front: Int // 佇列首指標,指向佇列首元素 + private var _size: Int // 佇列長度 + + init(capacity: Int) { + // 初始化陣列 + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* 獲取佇列的容量 */ + func capacity() -> Int { + nums.count + } + + /* 獲取佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入列 */ + func push(num: Int) { + if size() == capacity() { + print("佇列已滿") + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + let rear = (front + size()) % capacity() + // 將 num 新增至佇列尾 + nums[rear] = num + _size += 1 + } + + /* 出列 */ + @discardableResult + func pop() -> Int { + let num = peek() + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % capacity() + _size -= 1 + return num + } + + /* 訪問佇列首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("佇列為空") + } + return nums[front] + } + + /* 返回陣列 */ + func toArray() -> [Int] { + // 僅轉換有效長度範圍內的串列元素 + (front ..< front + size()).map { nums[$0 % capacity()] } + } +} + +@main +enum _ArrayQueue { + /* Driver Code */ + static func main() { + /* 初始化佇列 */ + let capacity = 10 + let queue = ArrayQueue(capacity: capacity) + + /* 元素入列 */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("佇列 queue = \(queue.toArray())") + + /* 訪問佇列首元素 */ + let peek = queue.peek() + print("佇列首元素 peek = \(peek)") + + /* 元素出列 */ + let pop = queue.pop() + print("出列元素 pop = \(pop),出列後 queue = \(queue.toArray())") + + /* 獲取佇列的長度 */ + let size = queue.size() + print("佇列長度 size = \(size)") + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty() + print("佇列是否為空 = \(isEmpty)") + + /* 測試環形陣列 */ + for i in 0 ..< 10 { + queue.push(num: i) + queue.pop() + print("第 \(i) 輪入列 + 出列後 queue = \(queue.toArray())") + } + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/array_stack.swift b/zh-hant/codes/swift/chapter_stack_and_queue/array_stack.swift new file mode 100644 index 0000000000..5873ef3dc1 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/array_stack.swift @@ -0,0 +1,85 @@ +/** + * File: array_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private var stack: [Int] + + init() { + // 初始化串列(動態陣列) + stack = [] + } + + /* 獲取堆疊的長度 */ + func size() -> Int { + stack.count + } + + /* 判斷堆疊是否為空 */ + func isEmpty() -> Bool { + stack.isEmpty + } + + /* 入堆疊 */ + func push(num: Int) { + stack.append(num) + } + + /* 出堆疊 */ + @discardableResult + func pop() -> Int { + if isEmpty() { + fatalError("堆疊為空") + } + return stack.removeLast() + } + + /* 訪問堆疊頂元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("堆疊為空") + } + return stack.last! + } + + /* 將 List 轉化為 Array 並返回 */ + func toArray() -> [Int] { + stack + } +} + +@main +enum _ArrayStack { + /* Driver Code */ + static func main() { + /* 初始化堆疊 */ + let stack = ArrayStack() + + /* 元素入堆疊 */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("堆疊 stack = \(stack.toArray())") + + /* 訪問堆疊頂元素 */ + let peek = stack.peek() + print("堆疊頂元素 peek = \(peek)") + + /* 元素出堆疊 */ + let pop = stack.pop() + print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack.toArray())") + + /* 獲取堆疊的長度 */ + let size = stack.size() + print("堆疊的長度 size = \(size)") + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty() + print("堆疊是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/deque.swift b/zh-hant/codes/swift/chapter_stack_and_queue/deque.swift new file mode 100644 index 0000000000..fc6f6dcbc5 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/deque.swift @@ -0,0 +1,44 @@ +/** + * File: deque.swift + * Created Time: 2023-01-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Deque { + /* Driver Code */ + static func main() { + /* 初始化雙向佇列 */ + // Swift 沒有內建的雙向佇列類別,可以把 Array 當作雙向佇列來使用 + var deque: [Int] = [] + + /* 元素入列 */ + deque.append(2) + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) + deque.insert(1, at: 0) + print("雙向佇列 deque = \(deque)") + + /* 訪問元素 */ + let peekFirst = deque.first! + print("佇列首元素 peekFirst = \(peekFirst)") + let peekLast = deque.last! + print("佇列尾元素 peekLast = \(peekLast)") + + /* 元素出列 */ + // 使用 Array 模擬時 popFirst 的複雜度為 O(n) + let popFirst = deque.removeFirst() + print("佇列首出列元素 popFirst = \(popFirst),佇列首出列後 deque = \(deque)") + let popLast = deque.removeLast() + print("佇列尾出列元素 popLast = \(popLast),佇列尾出列後 deque = \(deque)") + + /* 獲取雙向佇列的長度 */ + let size = deque.count + print("雙向佇列長度 size = \(size)") + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty + print("雙向佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift new file mode 100644 index 0000000000..e3055ba31b --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift @@ -0,0 +1,180 @@ +/** + * File: linkedlist_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + var val: Int // 節點值 + var next: ListNode? // 後繼節點引用 + weak var prev: ListNode? // 前驅節點引用 + + init(val: Int) { + self.val = val + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private var front: ListNode? // 頭節點 front + private var rear: ListNode? // 尾節點 rear + private var _size: Int // 雙向佇列的長度 + + init() { + _size = 0 + } + + /* 獲取雙向佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷雙向佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入列操作 */ + private func push(num: Int, isFront: Bool) { + let node = ListNode(val: num) + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if isEmpty() { + front = node + rear = node + } + // 佇列首入列操作 + else if isFront { + // 將 node 新增至鏈結串列頭部 + front?.prev = node + node.next = front + front = node // 更新頭節點 + } + // 佇列尾入列操作 + else { + // 將 node 新增至鏈結串列尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾節點 + } + _size += 1 // 更新佇列長度 + } + + /* 佇列首入列 */ + func pushFirst(num: Int) { + push(num: num, isFront: true) + } + + /* 佇列尾入列 */ + func pushLast(num: Int) { + push(num: num, isFront: false) + } + + /* 出列操作 */ + private func pop(isFront: Bool) -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + let val: Int + // 佇列首出列操作 + if isFront { + val = front!.val // 暫存頭節點值 + // 刪除頭節點 + let fNext = front?.next + if fNext != nil { + fNext?.prev = nil + front?.next = nil + } + front = fNext // 更新頭節點 + } + // 佇列尾出列操作 + else { + val = rear!.val // 暫存尾節點值 + // 刪除尾節點 + let rPrev = rear?.prev + if rPrev != nil { + rPrev?.next = nil + rear?.prev = nil + } + rear = rPrev // 更新尾節點 + } + _size -= 1 // 更新佇列長度 + return val + } + + /* 佇列首出列 */ + func popFirst() -> Int { + pop(isFront: true) + } + + /* 佇列尾出列 */ + func popLast() -> Int { + pop(isFront: false) + } + + /* 訪問佇列首元素 */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + return front!.val + } + + /* 訪問佇列尾元素 */ + func peekLast() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + return rear!.val + } + + /* 返回陣列用於列印 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListDeque { + /* Driver Code */ + static func main() { + /* 初始化雙向佇列 */ + let deque = LinkedListDeque() + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("雙向佇列 deque = \(deque.toArray())") + + /* 訪問元素 */ + let peekFirst = deque.peekFirst() + print("佇列首元素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("佇列尾元素 peekLast = \(peekLast)") + + /* 元素入列 */ + deque.pushLast(num: 4) + print("元素 4 佇列尾入列後 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("元素 1 佇列首入列後 deque = \(deque.toArray())") + + /* 元素出列 */ + let popLast = deque.popLast() + print("佇列尾出列元素 = \(popLast),佇列尾出列後 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("佇列首出列元素 = \(popFirst),佇列首出列後 deque = \(deque.toArray())") + + /* 獲取雙向佇列的長度 */ + let size = deque.size() + print("雙向佇列長度 size = \(size)") + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty() + print("雙向佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift new file mode 100644 index 0000000000..b39c01c67f --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift @@ -0,0 +1,107 @@ +/** + * File: linkedlist_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private var front: ListNode? // 頭節點 + private var rear: ListNode? // 尾節點 + private var _size: Int + + init() { + _size = 0 + } + + /* 獲取佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入列 */ + func push(num: Int) { + // 在尾節點後新增 num + let node = ListNode(x: num) + // 如果佇列為空,則令頭、尾節點都指向該節點 + if front == nil { + front = node + rear = node + } + // 如果佇列不為空,則將該節點新增到尾節點後 + else { + rear?.next = node + rear = node + } + _size += 1 + } + + /* 出列 */ + @discardableResult + func pop() -> Int { + let num = peek() + // 刪除頭節點 + front = front?.next + _size -= 1 + return num + } + + /* 訪問佇列首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("佇列為空") + } + return front!.val + } + + /* 將鏈結串列轉化為 Array 並返回 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListQueue { + /* Driver Code */ + static func main() { + /* 初始化佇列 */ + let queue = LinkedListQueue() + + /* 元素入列 */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("佇列 queue = \(queue.toArray())") + + /* 訪問佇列首元素 */ + let peek = queue.peek() + print("佇列首元素 peek = \(peek)") + + /* 元素出列 */ + let pop = queue.pop() + print("出列元素 pop = \(pop),出列後 queue = \(queue.toArray())") + + /* 獲取佇列的長度 */ + let size = queue.size() + print("佇列長度 size = \(size)") + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty() + print("佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift new file mode 100644 index 0000000000..35eb412e23 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift @@ -0,0 +1,96 @@ +/** + * File: linkedlist_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private var _peek: ListNode? // 將頭節點作為堆疊頂 + private var _size: Int // 堆疊的長度 + + init() { + _size = 0 + } + + /* 獲取堆疊的長度 */ + func size() -> Int { + _size + } + + /* 判斷堆疊是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入堆疊 */ + func push(num: Int) { + let node = ListNode(x: num) + node.next = _peek + _peek = node + _size += 1 + } + + /* 出堆疊 */ + @discardableResult + func pop() -> Int { + let num = peek() + _peek = _peek?.next + _size -= 1 + return num + } + + /* 訪問堆疊頂元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("堆疊為空") + } + return _peek!.val + } + + /* 將 List 轉化為 Array 並返回 */ + func toArray() -> [Int] { + var node = _peek + var res = Array(repeating: 0, count: size()) + for i in res.indices.reversed() { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListStack { + /* Driver Code */ + static func main() { + /* 初始化堆疊 */ + let stack = LinkedListStack() + + /* 元素入堆疊 */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("堆疊 stack = \(stack.toArray())") + + /* 訪問堆疊頂元素 */ + let peek = stack.peek() + print("堆疊頂元素 peek = \(peek)") + + /* 元素出堆疊 */ + let pop = stack.pop() + print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack.toArray())") + + /* 獲取堆疊的長度 */ + let size = stack.size() + print("堆疊的長度 size = \(size)") + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty() + print("堆疊是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/queue.swift b/zh-hant/codes/swift/chapter_stack_and_queue/queue.swift new file mode 100644 index 0000000000..54d9d076ab --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/queue.swift @@ -0,0 +1,40 @@ +/** + * File: queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Queue { + /* Driver Code */ + static func main() { + /* 初始化佇列 */ + // Swift 沒有內建的佇列類別,可以把 Array 當作佇列來使用 + var queue: [Int] = [] + + /* 元素入列 */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + print("佇列 queue = \(queue)") + + /* 訪問佇列首元素 */ + let peek = queue.first! + print("佇列首元素 peek = \(peek)") + + /* 元素出列 */ + // 使用 Array 模擬時 pop 的複雜度為 O(n) + let pool = queue.removeFirst() + print("出列元素 pop = \(pool),出列後 queue = \(queue)") + + /* 獲取佇列的長度 */ + let size = queue.count + print("佇列長度 size = \(size)") + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty + print("佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/stack.swift b/zh-hant/codes/swift/chapter_stack_and_queue/stack.swift new file mode 100644 index 0000000000..cb6bd30744 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/stack.swift @@ -0,0 +1,39 @@ +/** + * File: stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Stack { + /* Driver Code */ + static func main() { + /* 初始化堆疊 */ + // Swift 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + var stack: [Int] = [] + + /* 元素入堆疊 */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("堆疊 stack = \(stack)") + + /* 訪問堆疊頂元素 */ + let peek = stack.last! + print("堆疊頂元素 peek = \(peek)") + + /* 元素出堆疊 */ + let pop = stack.removeLast() + print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack)") + + /* 獲取堆疊的長度 */ + let size = stack.count + print("堆疊的長度 size = \(size)") + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty + print("堆疊是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/array_binary_tree.swift b/zh-hant/codes/swift/chapter_tree/array_binary_tree.swift new file mode 100644 index 0000000000..93ddaba214 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/array_binary_tree.swift @@ -0,0 +1,141 @@ +/** + * File: array_binary_tree.swift + * Created Time: 2023-07-23 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + private var tree: [Int?] + + /* 建構子 */ + init(arr: [Int?]) { + tree = arr + } + + /* 串列容量 */ + func size() -> Int { + tree.count + } + + /* 獲取索引為 i 節點的值 */ + func val(i: Int) -> Int? { + // 若索引越界,則返回 null ,代表空位 + if i < 0 || i >= size() { + return nil + } + return tree[i] + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 獲取索引為 i 節點的父節點的索引 */ + func parent(i: Int) -> Int { + (i - 1) / 2 + } + + /* 層序走訪 */ + func levelOrder() -> [Int] { + var res: [Int] = [] + // 直接走訪陣列 + for i in 0 ..< size() { + if let val = val(i: i) { + res.append(val) + } + } + return res + } + + /* 深度優先走訪 */ + private func dfs(i: Int, order: String, res: inout [Int]) { + // 若為空位,則返回 + guard let val = val(i: i) else { + return + } + // 前序走訪 + if order == "pre" { + res.append(val) + } + dfs(i: left(i: i), order: order, res: &res) + // 中序走訪 + if order == "in" { + res.append(val) + } + dfs(i: right(i: i), order: order, res: &res) + // 後序走訪 + if order == "post" { + res.append(val) + } + } + + /* 前序走訪 */ + func preOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "pre", res: &res) + return res + } + + /* 中序走訪 */ + func inOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "in", res: &res) + return res + } + + /* 後序走訪 */ + func postOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "post", res: &res) + return res + } +} + +@main +enum _ArrayBinaryTree { + /* Driver Code */ + static func main() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + + let root = TreeNode.listToTree(arr: arr) + print("\n初始化二元樹\n") + print("二元樹的陣列表示:") + print(arr) + print("二元樹的鏈結串列表示:") + PrintUtil.printTree(root: root) + + // 陣列表示下的二元樹類別 + let abt = ArrayBinaryTree(arr: arr) + + // 訪問節點 + let i = 1 + let l = abt.left(i: i) + let r = abt.right(i: i) + let p = abt.parent(i: i) + print("\n當前節點的索引為 \(i) ,值為 \(abt.val(i: i) as Any)") + print("其左子節點的索引為 \(l) ,值為 \(abt.val(i: l) as Any)") + print("其右子節點的索引為 \(r) ,值為 \(abt.val(i: r) as Any)") + print("其父節點的索引為 \(p) ,值為 \(abt.val(i: p) as Any)") + + // 走訪樹 + var res = abt.levelOrder() + print("\n層序走訪為:\(res)") + res = abt.preOrder() + print("前序走訪為:\(res)") + res = abt.inOrder() + print("中序走訪為:\(res)") + res = abt.postOrder() + print("後序走訪為:\(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/avl_tree.swift b/zh-hant/codes/swift/chapter_tree/avl_tree.swift new file mode 100644 index 0000000000..b2ac8bf977 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/avl_tree.swift @@ -0,0 +1,230 @@ +/** + * File: avl_tree.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* AVL 樹 */ +class AVLTree { + fileprivate var root: TreeNode? // 根節點 + + init() {} + + /* 獲取節點高度 */ + func height(node: TreeNode?) -> Int { + // 空節點高度為 -1 ,葉節點高度為 0 + node?.height ?? -1 + } + + /* 更新節點高度 */ + private func updateHeight(node: TreeNode?) { + // 節點高度等於最高子樹高度 + 1 + node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 + } + + /* 獲取平衡因子 */ + func balanceFactor(node: TreeNode?) -> Int { + // 空節點平衡因子為 0 + guard let node = node else { return 0 } + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node: node.left) - height(node: node.right) + } + + /* 右旋操作 */ + private func rightRotate(node: TreeNode?) -> TreeNode? { + let child = node?.left + let grandChild = child?.right + // 以 child 為原點,將 node 向右旋轉 + child?.right = node + node?.left = grandChild + // 更新節點高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 左旋操作 */ + private func leftRotate(node: TreeNode?) -> TreeNode? { + let child = node?.right + let grandChild = child?.left + // 以 child 為原點,將 node 向左旋轉 + child?.left = node + node?.right = grandChild + // 更新節點高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private func rotate(node: TreeNode?) -> TreeNode? { + // 獲取節點 node 的平衡因子 + let balanceFactor = balanceFactor(node: node) + // 左偏樹 + if balanceFactor > 1 { + if self.balanceFactor(node: node?.left) >= 0 { + // 右旋 + return rightRotate(node: node) + } else { + // 先左旋後右旋 + node?.left = leftRotate(node: node?.left) + return rightRotate(node: node) + } + } + // 右偏樹 + if balanceFactor < -1 { + if self.balanceFactor(node: node?.right) <= 0 { + // 左旋 + return leftRotate(node: node) + } else { + // 先右旋後左旋 + node?.right = rightRotate(node: node?.right) + return leftRotate(node: node) + } + } + // 平衡樹,無須旋轉,直接返回 + return node + } + + /* 插入節點 */ + func insert(val: Int) { + root = insertHelper(node: root, val: val) + } + + /* 遞迴插入節點(輔助方法) */ + private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return TreeNode(x: val) + } + /* 1. 查詢插入位置並插入節點 */ + if val < node!.val { + node?.left = insertHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = insertHelper(node: node?.right, val: val) + } else { + return node // 重複節點不插入,直接返回 + } + updateHeight(node: node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node: node) + // 返回子樹的根節點 + return node + } + + /* 刪除節點 */ + func remove(val: Int) { + root = removeHelper(node: root, val: val) + } + + /* 遞迴刪除節點(輔助方法) */ + private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return nil + } + /* 1. 查詢節點並刪除 */ + if val < node!.val { + node?.left = removeHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = removeHelper(node: node?.right, val: val) + } else { + if node?.left == nil || node?.right == nil { + let child = node?.left ?? node?.right + // 子節點數量 = 0 ,直接刪除 node 並返回 + if child == nil { + return nil + } + // 子節點數量 = 1 ,直接刪除 node + else { + node = child + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + var temp = node?.right + while temp?.left != nil { + temp = temp?.left + } + node?.right = removeHelper(node: node?.right, val: temp!.val) + node?.val = temp!.val + } + } + updateHeight(node: node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node: node) + // 返回子樹的根節點 + return node + } + + /* 查詢節點 */ + func search(val: Int) -> TreeNode? { + var cur = root + while cur != nil { + // 目標節點在 cur 的右子樹中 + if cur!.val < val { + cur = cur?.right + } + // 目標節點在 cur 的左子樹中 + else if cur!.val > val { + cur = cur?.left + } + // 找到目標節點,跳出迴圈 + else { + break + } + } + // 返回目標節點 + return cur + } +} + +@main +enum _AVLTree { + static func testInsert(tree: AVLTree, val: Int) { + tree.insert(val: val) + print("\n插入節點 \(val) 後,AVL 樹為") + PrintUtil.printTree(root: tree.root) + } + + static func testRemove(tree: AVLTree, val: Int) { + tree.remove(val: val) + print("\n刪除節點 \(val) 後,AVL 樹為") + PrintUtil.printTree(root: tree.root) + } + + /* Driver Code */ + static func main() { + /* 初始化空 AVL 樹 */ + let avlTree = AVLTree() + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(tree: avlTree, val: 1) + testInsert(tree: avlTree, val: 2) + testInsert(tree: avlTree, val: 3) + testInsert(tree: avlTree, val: 4) + testInsert(tree: avlTree, val: 5) + testInsert(tree: avlTree, val: 8) + testInsert(tree: avlTree, val: 7) + testInsert(tree: avlTree, val: 9) + testInsert(tree: avlTree, val: 10) + testInsert(tree: avlTree, val: 6) + + /* 插入重複節點 */ + testInsert(tree: avlTree, val: 7) + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(tree: avlTree, val: 8) // 刪除度為 0 的節點 + testRemove(tree: avlTree, val: 5) // 刪除度為 1 的節點 + testRemove(tree: avlTree, val: 4) // 刪除度為 2 的節點 + + /* 查詢節點 */ + let node = avlTree.search(val: 7) + print("\n查詢到的節點物件為 \(node!),節點值 = \(node!.val)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_search_tree.swift b/zh-hant/codes/swift/chapter_tree/binary_search_tree.swift new file mode 100644 index 0000000000..0b54b324cc --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_search_tree.swift @@ -0,0 +1,173 @@ +/** + * File: binary_search_tree.swift + * Created Time: 2023-01-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 二元搜尋樹 */ +class BinarySearchTree { + private var root: TreeNode? + + /* 建構子 */ + init() { + // 初始化空樹 + root = nil + } + + /* 獲取二元樹根節點 */ + func getRoot() -> TreeNode? { + root + } + + /* 查詢節點 */ + func search(num: Int) -> TreeNode? { + var cur = root + // 迴圈查詢,越過葉節點後跳出 + while cur != nil { + // 目標節點在 cur 的右子樹中 + if cur!.val < num { + cur = cur?.right + } + // 目標節點在 cur 的左子樹中 + else if cur!.val > num { + cur = cur?.left + } + // 找到目標節點,跳出迴圈 + else { + break + } + } + // 返回目標節點 + return cur + } + + /* 插入節點 */ + func insert(num: Int) { + // 若樹為空,則初始化根節點 + if root == nil { + root = TreeNode(x: num) + return + } + var cur = root + var pre: TreeNode? + // 迴圈查詢,越過葉節點後跳出 + while cur != nil { + // 找到重複節點,直接返回 + if cur!.val == num { + return + } + pre = cur + // 插入位置在 cur 的右子樹中 + if cur!.val < num { + cur = cur?.right + } + // 插入位置在 cur 的左子樹中 + else { + cur = cur?.left + } + } + // 插入節點 + let node = TreeNode(x: num) + if pre!.val < num { + pre?.right = node + } else { + pre?.left = node + } + } + + /* 刪除節點 */ + func remove(num: Int) { + // 若樹為空,直接提前返回 + if root == nil { + return + } + var cur = root + var pre: TreeNode? + // 迴圈查詢,越過葉節點後跳出 + while cur != nil { + // 找到待刪除節點,跳出迴圈 + if cur!.val == num { + break + } + pre = cur + // 待刪除節點在 cur 的右子樹中 + if cur!.val < num { + cur = cur?.right + } + // 待刪除節點在 cur 的左子樹中 + else { + cur = cur?.left + } + } + // 若無待刪除節點,則直接返回 + if cur == nil { + return + } + // 子節點數量 = 0 or 1 + if cur?.left == nil || cur?.right == nil { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + let child = cur?.left ?? cur?.right + // 刪除節點 cur + if cur !== root { + if pre?.left === cur { + pre?.left = child + } else { + pre?.right = child + } + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + var tmp = cur?.right + while tmp?.left != nil { + tmp = tmp?.left + } + // 遞迴刪除節點 tmp + remove(num: tmp!.val) + // 用 tmp 覆蓋 cur + cur?.val = tmp!.val + } + } +} + +@main +enum _BinarySearchTree { + /* Driver Code */ + static func main() { + /* 初始化二元搜尋樹 */ + let bst = BinarySearchTree() + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + for num in nums { + bst.insert(num: num) + } + print("\n初始化的二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* 查詢節點 */ + let node = bst.search(num: 7) + print("\n查詢到的節點物件為 \(node!),節點值 = \(node!.val)") + + /* 插入節點 */ + bst.insert(num: 16) + print("\n插入節點 16 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* 刪除節點 */ + bst.remove(num: 1) + print("\n刪除節點 1 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 2) + print("\n刪除節點 2 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 4) + print("\n刪除節點 4 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_tree.swift b/zh-hant/codes/swift/chapter_tree/binary_tree.swift new file mode 100644 index 0000000000..65ba076978 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_tree.swift @@ -0,0 +1,40 @@ +/** + * File: binary_tree.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BinaryTree { + /* Driver Code */ + static func main() { + /* 初始化二元樹 */ + // 初始化節點 + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\n初始化二元樹\n") + PrintUtil.printTree(root: n1) + + /* 插入與刪除節點 */ + let P = TreeNode(x: 0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + print("\n插入節點 P 後\n") + PrintUtil.printTree(root: n1) + // 刪除節點 P + n1.left = n2 + print("\n刪除節點 P 後\n") + PrintUtil.printTree(root: n1) + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_tree_bfs.swift b/zh-hant/codes/swift/chapter_tree/binary_tree_bfs.swift new file mode 100644 index 0000000000..ea373bf1e6 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_tree_bfs.swift @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 層序走訪 */ +func levelOrder(root: TreeNode) -> [Int] { + // 初始化佇列,加入根節點 + var queue: [TreeNode] = [root] + // 初始化一個串列,用於儲存走訪序列 + var list: [Int] = [] + while !queue.isEmpty { + let node = queue.removeFirst() // 隊列出隊 + list.append(node.val) // 儲存節點值 + if let left = node.left { + queue.append(left) // 左子節點入列 + } + if let right = node.right { + queue.append(right) // 右子節點入列 + } + } + return list +} + +@main +enum BinaryTreeBFS { + /* Driver Code */ + static func main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n初始化二元樹\n") + PrintUtil.printTree(root: node) + + /* 層序走訪 */ + let list = levelOrder(root: node) + print("\n層序走訪的節點列印序列 = \(list)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_tree_dfs.swift b/zh-hant/codes/swift/chapter_tree/binary_tree_dfs.swift new file mode 100644 index 0000000000..342eff2ba2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_tree_dfs.swift @@ -0,0 +1,70 @@ +/** + * File: binary_tree_dfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +// 初始化串列,用於儲存走訪序列 +var list: [Int] = [] + +/* 前序走訪 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.append(root.val) + preOrder(root: root.left) + preOrder(root: root.right) +} + +/* 中序走訪 */ +func inOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root: root.left) + list.append(root.val) + inOrder(root: root.right) +} + +/* 後序走訪 */ +func postOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root: root.left) + postOrder(root: root.right) + list.append(root.val) +} + +@main +enum BinaryTreeDFS { + /* Driver Code */ + static func main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n初始化二元樹\n") + PrintUtil.printTree(root: root) + + /* 前序走訪 */ + list.removeAll() + preOrder(root: root) + print("\n前序走訪的節點列印序列 = \(list)") + + /* 中序走訪 */ + list.removeAll() + inOrder(root: root) + print("\n中序走訪的節點列印序列 = \(list)") + + /* 後序走訪 */ + list.removeAll() + postOrder(root: root) + print("\n後序走訪的節點列印序列 = \(list)") + } +} diff --git a/zh-hant/codes/swift/utils/ListNode.swift b/zh-hant/codes/swift/utils/ListNode.swift new file mode 100644 index 0000000000..ec4690d5e2 --- /dev/null +++ b/zh-hant/codes/swift/utils/ListNode.swift @@ -0,0 +1,33 @@ +/** + * File: ListNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public class ListNode: Hashable { + public var val: Int // 節點值 + public var next: ListNode? // 後繼節點引用 + + public init(x: Int) { + val = x + } + + public static func == (lhs: ListNode, rhs: ListNode) -> Bool { + lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + hasher.combine(next.map { ObjectIdentifier($0) }) + } + + public static func arrToLinkedList(arr: [Int]) -> ListNode? { + let dum = ListNode(x: 0) + var head: ListNode? = dum + for val in arr { + head?.next = ListNode(x: val) + head = head?.next + } + return dum.next + } +} diff --git a/zh-hant/codes/swift/utils/Pair.swift b/zh-hant/codes/swift/utils/Pair.swift new file mode 100644 index 0000000000..cfb57c5179 --- /dev/null +++ b/zh-hant/codes/swift/utils/Pair.swift @@ -0,0 +1,20 @@ +/** + * File: Pair.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 鍵值對 */ +public class Pair: Equatable { + public var key: Int + public var val: String + + public init(key: Int, val: String) { + self.key = key + self.val = val + } + + public static func == (lhs: Pair, rhs: Pair) -> Bool { + lhs.key == rhs.key && lhs.val == rhs.val + } +} diff --git a/zh-hant/codes/swift/utils/PrintUtil.swift b/zh-hant/codes/swift/utils/PrintUtil.swift new file mode 100644 index 0000000000..bfc8911c17 --- /dev/null +++ b/zh-hant/codes/swift/utils/PrintUtil.swift @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public enum PrintUtil { + private class Trunk { + var prev: Trunk? + var str: String + + init(prev: Trunk?, str: String) { + self.prev = prev + self.str = str + } + } + + public static func printLinkedList(head: ListNode) { + var head: ListNode? = head + var list: [String] = [] + while head != nil { + list.append("\(head!.val)") + head = head?.next + } + print(list.joined(separator: " -> ")) + } + + public static func printTree(root: TreeNode?) { + printTree(root: root, prev: nil, isRight: false) + } + + private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { + if root == nil { + return + } + + var prevStr = " " + let trunk = Trunk(prev: prev, str: prevStr) + + printTree(root: root?.right, prev: trunk, isRight: true) + + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev?.str = prevStr + } + + showTrunks(p: trunk) + print(" \(root!.val)") + + if prev != nil { + prev?.str = prevStr + } + trunk.str = " |" + + printTree(root: root?.left, prev: trunk, isRight: false) + } + + private static func showTrunks(p: Trunk?) { + if p == nil { + return + } + + showTrunks(p: p?.prev) + print(p!.str, terminator: "") + } + + public static func printHashMap(map: [K: V]) { + for (key, value) in map { + print("\(key) -> \(value)") + } + } + + public static func printHeap(queue: [Int]) { + print("堆積的陣列表示:", terminator: "") + print(queue) + print("堆積的樹狀表示:") + let root = TreeNode.listToTree(arr: queue) + printTree(root: root) + } + + public static func printMatrix(matrix: [[T]]) { + print("[") + for row in matrix { + print(" \(row),") + } + print("]") + } +} diff --git a/zh-hant/codes/swift/utils/TreeNode.swift b/zh-hant/codes/swift/utils/TreeNode.swift new file mode 100644 index 0000000000..f626148493 --- /dev/null +++ b/zh-hant/codes/swift/utils/TreeNode.swift @@ -0,0 +1,71 @@ +/** + * File: TreeNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二元樹節點類別 */ +public class TreeNode { + public var val: Int // 節點值 + public var height: Int // 節點高度 + public var left: TreeNode? // 左子節點引用 + public var right: TreeNode? // 右子節點引用 + + /* 建構子 */ + public init(x: Int) { + val = x + height = 0 + } + + // 序列化編碼規則請參考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二元樹的陣列表示: + // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + // 二元樹的鏈結串列表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 將串列反序列化為二元樹:遞迴 */ + private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { + if i < 0 || i >= arr.count || arr[i] == nil { + return nil + } + let root = TreeNode(x: arr[i]!) + root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) + root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) + return root + } + + /* 將串列反序列化為二元樹 */ + public static func listToTree(arr: [Int?]) -> TreeNode? { + listToTreeDFS(arr: arr, i: 0) + } + + /* 將二元樹序列化為串列:遞迴 */ + private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { + if root == nil { + return + } + while i >= res.count { + res.append(nil) + } + res[i] = root?.val + treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) + treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) + } + + /* 將二元樹序列化為串列 */ + public static func treeToList(root: TreeNode?) -> [Int?] { + var res: [Int?] = [] + treeToListDFS(root: root, i: 0, res: &res) + return res + } +} diff --git a/zh-hant/codes/swift/utils/Vertex.swift b/zh-hant/codes/swift/utils/Vertex.swift new file mode 100644 index 0000000000..87d0d6525e --- /dev/null +++ b/zh-hant/codes/swift/utils/Vertex.swift @@ -0,0 +1,32 @@ +/** + * File: Vertex.swift + * Created Time: 2023-02-19 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 頂點類別 */ +public class Vertex: Hashable { + public var val: Int + + public init(val: Int) { + self.val = val + } + + public static func == (lhs: Vertex, rhs: Vertex) -> Bool { + lhs.val == rhs.val + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static func valsToVets(vals: [Int]) -> [Vertex] { + vals.map { Vertex(val: $0) } + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static func vetsToVals(vets: [Vertex]) -> [Int] { + vets.map { $0.val } + } +} diff --git a/zh-hant/codes/typescript/.gitignore b/zh-hant/codes/typescript/.gitignore new file mode 100644 index 0000000000..201706994d --- /dev/null +++ b/zh-hant/codes/typescript/.gitignore @@ -0,0 +1,4 @@ +node_modules +out +package.json +package-lock.json diff --git a/zh-hant/codes/typescript/.prettierrc b/zh-hant/codes/typescript/.prettierrc new file mode 100644 index 0000000000..3f4aa8cb65 --- /dev/null +++ b/zh-hant/codes/typescript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/array.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/array.ts new file mode 100644 index 0000000000..47b4c5ce35 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/array.ts @@ -0,0 +1,101 @@ +/** + * File: array.ts + * Created Time: 2022-12-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 隨機訪問元素 */ +function randomAccess(nums: number[]): number { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + const random_index = Math.floor(Math.random() * nums.length); + // 獲取並返回隨機元素 + const random_num = nums[random_index]; + return random_num; +} + +/* 擴展陣列長度 */ +// 請注意,TypeScript 的 Array 是動態陣列,可以直接擴展 +// 為了方便學習,本函式將 Array 看作長度不可變的陣列 +function extend(nums: number[], enlarge: number): number[] { + // 初始化一個擴展長度後的陣列 + const res = new Array(nums.length + enlarge).fill(0); + // 將原陣列中的所有元素複製到新陣列 + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 num */ +function insert(nums: number[], num: number, index: number): void { + // 把索引 index 以及之後的所有元素向後移動一位 + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +function remove(nums: number[], index: number): void { + // 把索引 index 之後的所有元素向前移動一位 + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +function traverse(nums: number[]): void { + let count = 0; + // 透過索引走訪陣列 + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (const num of nums) { + count += num; + } +} + +/* 在陣列中查詢指定元素 */ +function find(nums: number[], target: number): number { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) { + return i; + } + } + return -1; +} + +/* Driver Code */ +/* 初始化陣列 */ +const arr: number[] = new Array(5).fill(0); +console.log('陣列 arr =', arr); +let nums: number[] = [1, 3, 2, 5, 4]; +console.log('陣列 nums =', nums); + +/* 隨機訪問 */ +let random_num = randomAccess(nums); +console.log('在 nums 中獲取隨機元素', random_num); + +/* 長度擴展 */ +nums = extend(nums, 3); +console.log('將陣列長度擴展至 8 ,得到 nums =', nums); + +/* 插入元素 */ +insert(nums, 6, 3); +console.log('在索引 3 處插入數字 6 ,得到 nums =', nums); + +/* 刪除元素 */ +remove(nums, 2); +console.log('刪除索引 2 處的元素,得到 nums =', nums); + +/* 走訪陣列 */ +traverse(nums); + +/* 查詢元素 */ +let index = find(nums, 3); +console.log('在 nums 中查詢元素 3 ,得到索引 =', index); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/linked_list.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/linked_list.ts new file mode 100644 index 0000000000..c68baa0535 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/linked_list.ts @@ -0,0 +1,86 @@ +/** + * File: linked_list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { printLinkedList } from '../modules/PrintUtil'; + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +function insert(n0: ListNode, P: ListNode): void { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +function remove(n0: ListNode): void { + if (!n0.next) { + return; + } + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +function access(head: ListNode | null, index: number): ListNode | null { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +function find(head: ListNode | null, target: number): number { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* 初始化鏈結串列 */ +// 初始化各個節點 +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// 構建節點之間的引用 +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('初始化的鏈結串列為'); +printLinkedList(n0); + +/* 插入節點 */ +insert(n0, new ListNode(0)); +console.log('插入節點後的鏈結串列為'); +printLinkedList(n0); + +/* 刪除節點 */ +remove(n0); +console.log('刪除節點後的鏈結串列為'); +printLinkedList(n0); + +/* 訪問節點 */ +const node = access(n0, 3); +console.log(`鏈結串列中索引 3 處的節點的值 = ${node?.val}`); + +/* 查詢節點 */ +const index = find(n0, 2); +console.log(`鏈結串列中值為 2 的節點的索引 = ${index}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/list.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/list.ts new file mode 100644 index 0000000000..dc6ba93f35 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/list.ts @@ -0,0 +1,59 @@ +/** + * File: list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 初始化串列 */ +const nums: number[] = [1, 3, 2, 5, 4]; +console.log(`串列 nums = ${nums}`); + +/* 訪問元素 */ +const num: number = nums[1]; +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums[1] = 0; +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums}`); + +/* 清空串列 */ +nums.length = 0; +console.log(`清空串列後 nums = ${nums}`); + +/* 在尾部新增元素 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`新增元素後 nums = ${nums}`); + +/* 在中間插入元素 */ +nums.splice(3, 0, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums}`); + +/* 刪除元素 */ +nums.splice(3, 1); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums}`); + +/* 透過索引走訪串列 */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* 直接走訪串列元素 */ +count = 0; +for (const x of nums) { + count += x; +} + +/* 拼接兩個串列 */ +const nums1: number[] = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`將串列 nums1 拼接到 nums 之後,得到 nums = ${nums}`); + +/* 排序串列 */ +nums.sort((a, b) => a - b); +console.log(`排序串列後 nums = ${nums}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/my_list.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/my_list.ts new file mode 100644 index 0000000000..eeaba05fe4 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/my_list.ts @@ -0,0 +1,141 @@ +/** + * File: my_list.ts + * Created Time: 2022-12-11 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 串列類別 */ +class MyList { + private arr: Array; // 陣列(儲存串列元素) + private _capacity: number = 10; // 串列容量 + private _size: number = 0; // 串列長度(當前元素數量) + private extendRatio: number = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + constructor() { + this.arr = new Array(this._capacity); + } + + /* 獲取串列長度(當前元素數量)*/ + public size(): number { + return this._size; + } + + /* 獲取串列容量 */ + public capacity(): number { + return this._capacity; + } + + /* 訪問元素 */ + public get(index: number): number { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= this._size) throw new Error('索引越界'); + return this.arr[index]; + } + + /* 更新元素 */ + public set(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('索引越界'); + this.arr[index] = num; + } + + /* 在尾部新增元素 */ + public add(num: number): void { + // 如果長度等於容量,則需要擴容 + if (this._size === this._capacity) this.extendCapacity(); + // 將新元素新增到串列尾部 + this.arr[this._size] = num; + this._size++; + } + + /* 在中間插入元素 */ + public insert(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('索引越界'); + // 元素數量超出容量時,觸發擴容機制 + if (this._size === this._capacity) { + this.extendCapacity(); + } + // 將索引 index 以及之後的元素都向後移動一位 + for (let j = this._size - 1; j >= index; j--) { + this.arr[j + 1] = this.arr[j]; + } + // 更新元素數量 + this.arr[index] = num; + this._size++; + } + + /* 刪除元素 */ + public remove(index: number): number { + if (index < 0 || index >= this._size) throw new Error('索引越界'); + let num = this.arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (let j = index; j < this._size - 1; j++) { + this.arr[j] = this.arr[j + 1]; + } + // 更新元素數量 + this._size--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + public extendCapacity(): void { + // 新建一個長度為 size 的陣列,並將原陣列複製到新陣列 + this.arr = this.arr.concat( + new Array(this.capacity() * (this.extendRatio - 1)) + ); + // 更新串列容量 + this._capacity = this.arr.length; + } + + /* 將串列轉換為陣列 */ + public toArray(): number[] { + let size = this.size(); + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* 初始化串列 */ +const nums = new MyList(); +/* 在尾部新增元素 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); + +/* 在中間插入元素 */ +nums.insert(3, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}`); + +/* 刪除元素 */ +nums.remove(3); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums.toArray()}`); + +/* 訪問元素 */ +const num = nums.get(1); +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums.set(1, 0); +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}`); + +/* 測試擴容機制 */ +for (let i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); +} +console.log( + `擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/n_queens.ts b/zh-hant/codes/typescript/chapter_backtracking/n_queens.ts new file mode 100644 index 0000000000..e3d6ef1b25 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/n_queens.ts @@ -0,0 +1,65 @@ +/** + * File: n_queens.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +function backtrack( + row: number, + n: number, + state: string[][], + res: string[][][], + cols: boolean[], + diags1: boolean[], + diags2: boolean[] +): void { + // 當放置完所有行時,記錄解 + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // 走訪所有列 + for (let col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + const diag1 = row - col + n - 1; + const diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +function nQueens(n: number): string[][][] { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 記錄列是否有皇后 + const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后 + const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后 + const res: string[][][] = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`輸入棋盤長寬為 ${n}`); +console.log(`皇后放置方案共有 ${res.length} 種`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/permutations_i.ts b/zh-hant/codes/typescript/chapter_backtracking/permutations_i.ts new file mode 100644 index 0000000000..990eecd176 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/permutations_i.ts @@ -0,0 +1,49 @@ +/** + * File: permutations_i.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 I */ +function permutationsI(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 3]; +const res: number[][] = permutationsI(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/permutations_ii.ts b/zh-hant/codes/typescript/chapter_backtracking/permutations_ii.ts new file mode 100644 index 0000000000..49ec2ecdcf --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/permutations_ii.ts @@ -0,0 +1,51 @@ +/** + * File: permutations_ii.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 II */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.has(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 II */ +function permutationsII(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 2]; +const res: number[][] = permutationsII(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts new file mode 100644 index 0000000000..7fd306e310 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts @@ -0,0 +1,36 @@ +/** + * File: preorder_traversal_i_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序走訪:例題一 */ +function preOrder(root: TreeNode | null, res: TreeNode[]): void { + if (root === null) { + return; + } + if (root.val === 7) { + // 記錄解 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const res: TreeNode[] = []; +preOrder(root, res); + +console.log('\n輸出所有值為 7 的節點'); +console.log(res.map((node) => node.val)); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts new file mode 100644 index 0000000000..8ec1f53894 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序走訪:例題二 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + if (root === null) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts new file mode 100644 index 0000000000..2e8acb14e5 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts @@ -0,0 +1,48 @@ +/** + * File: preorder_traversal_iii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序走訪:例題三 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + // 剪枝 + if (root === null || root.val === 3) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts new file mode 100644 index 0000000000..4aa8a96b94 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts @@ -0,0 +1,75 @@ +/** + * File: preorder_traversal_iii_template.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 判斷當前狀態是否為解 */ +function isSolution(state: TreeNode[]): boolean { + return state && state[state.length - 1]?.val === 7; +} + +/* 記錄解 */ +function recordSolution(state: TreeNode[], res: TreeNode[][]): void { + res.push([...state]); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +function isValid(state: TreeNode[], choice: TreeNode): boolean { + return choice !== null && choice.val !== 3; +} + +/* 更新狀態 */ +function makeChoice(state: TreeNode[], choice: TreeNode): void { + state.push(choice); +} + +/* 恢復狀態 */ +function undoChoice(state: TreeNode[]): void { + state.pop(); +} + +/* 回溯演算法:例題三 */ +function backtrack( + state: TreeNode[], + choices: TreeNode[], + res: TreeNode[][] +): void { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, [choice.left, choice.right], res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 回溯演算法 +const res: TreeNode[][] = []; +backtrack([], [root], res); + +console.log('\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i.ts b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i.ts new file mode 100644 index 0000000000..9799e534a8 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i.ts @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I */ +function subsetSumI(nums: number[], target: number): number[][] { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts new file mode 100644 index 0000000000..86f112d141 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts @@ -0,0 +1,52 @@ +/** + * File: subset_sum_i_naive.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack( + state: number[], + target: number, + total: number, + choices: number[], + res: number[][] +): void { + // 子集和等於 target 時,記錄解 + if (total === target) { + res.push([...state]); + return; + } + // 走訪所有選擇 + for (let i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I(包含重複子集) */ +function subsetSumINaive(nums: number[], target: number): number[][] { + const state = []; // 狀態(子集) + const total = 0; // 子集和 + const res = []; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); +console.log('請注意,該方法輸出的結果包含重複集合'); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/subset_sum_ii.ts b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_ii.ts new file mode 100644 index 0000000000..03de56ae44 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_ii.ts @@ -0,0 +1,59 @@ +/** + * File: subset_sum_ii.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 II */ +function subsetSumII(nums: number[], target: number): number[][] { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/iteration.ts b/zh-hant/codes/typescript/chapter_computational_complexity/iteration.ts new file mode 100644 index 0000000000..f6d0d3312f --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/iteration.ts @@ -0,0 +1,72 @@ +/** + * File: iteration.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for 迴圈 */ +function forLoop(n: number): number { + let res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +function whileLoop(n: number): number { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +function whileLoopII(n: number): number { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +function nestedForLoop(n: number): string { + let res = ''; + // 迴圈 i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = forLoop(n); +console.log(`for 迴圈的求和結果 res = ${res}`); + +res = whileLoop(n); +console.log(`while 迴圈的求和結果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while 迴圈(兩次更新)求和結果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`雙層 for 迴圈的走訪結果 ${resStr}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/recursion.ts b/zh-hant/codes/typescript/chapter_computational_complexity/recursion.ts new file mode 100644 index 0000000000..f653023410 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/recursion.ts @@ -0,0 +1,70 @@ +/** + * File: recursion.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 遞迴 */ +function recur(n: number): number { + // 終止條件 + if (n === 1) return 1; + // 遞:遞迴呼叫 + const res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +function forLoopRecur(n: number): number { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + const stack: number[] = []; + let res: number = 0; + // 遞:遞迴呼叫 + for (let i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (stack.length) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +function tailRecur(n: number, res: number): number { + // 終止條件 + if (n === 0) return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +function fib(n: number): number { + // 終止條件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = recur(n); +console.log(`遞迴函式的求和結果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`使用迭代模擬遞迴的求和結果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`尾遞迴函式的求和結果 res = ${res}`); + +res = fib(n); +console.log(`費波那契數列的第 ${n} 項為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/space_complexity.ts b/zh-hant/codes/typescript/chapter_computational_complexity/space_complexity.ts new file mode 100644 index 0000000000..5282ae565a --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/space_complexity.ts @@ -0,0 +1,103 @@ +/** + * File: space_complexity.ts + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 函式 */ +function constFunc(): number { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +function constant(n: number): void { + // 常數、變數、物件佔用 O(1) 空間 + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + const c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 線性階 */ +function linear(n: number): void { + // 長度為 n 的陣列佔用 O(n) 空間 + const nums = new Array(n); + // 長度為 n 的串列佔用 O(n) 空間 + const nodes: ListNode[] = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 線性階(遞迴實現) */ +function linearRecur(n: number): void { + console.log(`遞迴 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 平方階 */ +function quadratic(n: number): void { + // 矩陣佔用 O(n^2) 空間 + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二維串列佔用 O(n^2) 空間 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 平方階(遞迴實現) */ +function quadraticRecur(n: number): number { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`遞迴 n = ${n} 中的 nums 長度 = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +function buildTree(n: number): TreeNode | null { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 常數階 +constant(n); +// 線性階 +linear(n); +linearRecur(n); +// 平方階 +quadratic(n); +quadraticRecur(n); +// 指數階 +const root = buildTree(n); +printTree(root); diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/time_complexity.ts b/zh-hant/codes/typescript/chapter_computational_complexity/time_complexity.ts new file mode 100644 index 0000000000..d10d25e8d0 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/time_complexity.ts @@ -0,0 +1,157 @@ +/** + * File: time_complexity.ts + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 常數階 */ +function constant(n: number): number { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* 線性階 */ +function linear(n: number): number { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* 線性階(走訪陣列) */ +function arrayTraversal(nums: number[]): number { + let count = 0; + // 迴圈次數與陣列長度成正比 + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* 平方階 */ +function quadratic(n: number): number { + let count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +function bubbleSort(nums: number[]): number { + let count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +function exponential(n: number): number { + let count = 0, + base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +function expRecur(n: number): number { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +function logarithmic(n: number): number { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +function logRecur(n: number): number { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* 線性對數階 */ +function linearLogRecur(n: number): number { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +function factorialRecur(n: number): number { + if (n === 0) return 1; + let count = 0; + // 從 1 個分裂出 n 個 + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 +const n = 8; +console.log('輸入資料大小 n = ' + n); + +let count = constant(n); +console.log('常數階的操作數量 = ' + count); + +count = linear(n); +console.log('線性階的操作數量 = ' + count); +count = arrayTraversal(new Array(n)); +console.log('線性階(走訪陣列)的操作數量 = ' + count); + +count = quadratic(n); +console.log('平方階的操作數量 = ' + count); +var nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('平方階(泡沫排序)的操作數量 = ' + count); + +count = exponential(n); +console.log('指數階(迴圈實現)的操作數量 = ' + count); +count = expRecur(n); +console.log('指數階(遞迴實現)的操作數量 = ' + count); + +count = logarithmic(n); +console.log('對數階(迴圈實現)的操作數量 = ' + count); +count = logRecur(n); +console.log('對數階(遞迴實現)的操作數量 = ' + count); + +count = linearLogRecur(n); +console.log('線性對數階(遞迴實現)的操作數量 = ' + count); + +count = factorialRecur(n); +console.log('階乘階(遞迴實現)的操作數量 = ' + count); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts b/zh-hant/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts new file mode 100644 index 0000000000..4ba2038bd5 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.ts + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +function randomNumbers(n: number): number[] { + const nums = Array(n); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +function findOne(nums: number[]): number { + for (let i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n陣列 [ 1, 2, ..., n ] 被打亂後 = [' + nums.join(', ') + ']'); + console.log('數字 1 的索引為 ' + index); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts b/zh-hant/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts new file mode 100644 index 0000000000..6ae3755a32 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts @@ -0,0 +1,41 @@ +/** + * File: binary_search_recur.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +function dfs(nums: number[], target: number, i: number, j: number): number { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + const m = i + ((j - i) >> 1); + 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; + } +} + +/* 二分搜尋 */ +function binarySearch(nums: number[], target: number): number { + const n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分搜尋(雙閉區間) +const index = binarySearch(nums, target); +console.log(`目標元素 6 的索引 = ${index}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_divide_and_conquer/build_tree.ts b/zh-hant/codes/typescript/chapter_divide_and_conquer/build_tree.ts new file mode 100644 index 0000000000..585929d35c --- /dev/null +++ b/zh-hant/codes/typescript/chapter_divide_and_conquer/build_tree.ts @@ -0,0 +1,50 @@ +/** + * File: build_tree.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { printTree } from '../modules/PrintUtil'; +import { TreeNode } from '../modules/TreeNode'; + +/* 構建二元樹:分治 */ +function dfs( + preorder: number[], + inorderMap: Map, + i: number, + l: number, + r: number +): TreeNode | null { + // 子樹區間為空時終止 + if (r - l < 0) return null; + // 初始化根節點 + const root: TreeNode = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + const m = inorderMap.get(preorder[i]); + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前序走訪 = ' + JSON.stringify(preorder)); +console.log('中序走訪 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('構建的二元樹為:'); +printTree(root); diff --git a/zh-hant/codes/typescript/chapter_divide_and_conquer/hanota.ts b/zh-hant/codes/typescript/chapter_divide_and_conquer/hanota.ts new file mode 100644 index 0000000000..4b1732d823 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_divide_and_conquer/hanota.ts @@ -0,0 +1,52 @@ +/** + * File: hanota.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 移動一個圓盤 */ +function move(src: number[], tar: number[]): void { + // 從 src 頂部拿出一個圓盤 + const pan = src.pop(); + // 將圓盤放入 tar 頂部 + tar.push(pan); +} + +/* 求解河內塔問題 f(i) */ +function dfs(i: number, src: number[], buf: number[], tar: number[]): void { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i === 1) { + move(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解河內塔問題 */ +function solveHanota(A: number[], B: number[], C: number[]): void { + const n = A.length; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +// 串列尾部是柱子頂部 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初始狀態下:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('圓盤移動完成後:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts new file mode 100644 index 0000000000..0017da5771 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_backtrack.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯 */ +function backtrack( + choices: number[], + state: number, + n: number, + res: Map<0, any> +): void { + // 當爬到第 n 階時,方案數量加 1 + if (state === n) res.set(0, res.get(0) + 1); + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +function climbingStairsBacktrack(n: number): number { + const choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + const state = 0; // 從第 0 階開始爬 + const res = new Map(); + res.set(0, 0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts new file mode 100644 index 0000000000..9d6b0de54c --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_constraint_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +function climbingStairsConstraintDP(n: number): number { + if (n === 1 || n === 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = Array.from({ length: n + 1 }, () => new Array(3)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts new file mode 100644 index 0000000000..bc8c08b50e --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts @@ -0,0 +1,26 @@ +/** + * File: climbing_stairs_dfs.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 搜尋 */ +function dfs(i: number): number { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +function climbingStairsDFS(n: number): number { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts new file mode 100644 index 0000000000..5fe716ec10 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs_mem.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 記憶化搜尋 */ +function dfs(i: number, mem: number[]): number { + // 已知 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] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +function climbingStairsDFSMem(n: number): number { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts new file mode 100644 index 0000000000..5a47bf0efa --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_dp.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +function climbingStairsDP(n: number): number { + if (n === 1 || n === 2) return n; + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1).fill(-1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +function climbingStairsDPComp(n: number): number { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); +res = climbingStairsDPComp(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts new file mode 100644 index 0000000000..6fe7c251cf --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts @@ -0,0 +1,68 @@ +/** + * File: coin_change.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換:動態規劃 */ +function coinChangeDP(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 狀態轉移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +function coinChangeDPComp(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 動態規劃 +let res = coinChangeDP(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); + +// 空間最佳化後的動態規劃 +res = coinChangeDPComp(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts new file mode 100644 index 0000000000..fa41017a74 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換 II:動態規劃 */ +function coinChangeIIDP(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +function coinChangeIIDPComp(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 動態規劃 +let res = coinChangeIIDP(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); + +// 空間最佳化後的動態規劃 +res = coinChangeIIDPComp(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts new file mode 100644 index 0000000000..906d662aaa --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts @@ -0,0 +1,148 @@ +/** + * File: edit_distance.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 編輯距離:暴力搜尋 */ +function editDistanceDFS(s: string, t: string, i: number, j: number): number { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.min(insert, del, replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +function editDistanceDFSMem( + s: string, + t: string, + mem: Array>, + i: number, + j: number +): number { + // 若 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +function editDistanceDP(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => 0) + ); + // 狀態轉移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +function editDistanceDPComp(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 狀態轉移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (let i = 1; i <= n; i++) { + // 狀態轉移:首列 + let leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜尋 +let res = editDistanceDFS(s, t, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 記憶化搜尋 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => -1) +); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 動態規劃 +res = editDistanceDP(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 空間最佳化後的動態規劃 +res = editDistanceDPComp(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts new file mode 100644 index 0000000000..ea23a6cba8 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts @@ -0,0 +1,134 @@ +/** + * File: knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜尋 */ +function knapsackDFS( + wgt: Array, + val: Array, + i: number, + c: number +): number { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return Math.max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +function knapsackDFSMem( + wgt: Array, + val: Array, + mem: Array>, + i: number, + c: number +): number { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +function knapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +function knapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + // 倒序走訪 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜尋 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 動態規劃 +res = knapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 空間最佳化後的動態規劃 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts new file mode 100644 index 0000000000..bdeaca5963 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬樓梯最小代價:動態規劃 */ +function minCostClimbingStairsDP(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +function minCostClimbingStairsDPComp(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log(`輸入樓梯的代價串列為:${cost}`); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完樓梯的最低代價為:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完樓梯的最低代價為:${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts new file mode 100644 index 0000000000..5316095f62 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路徑和:暴力搜尋 */ +function minPathSumDFS( + grid: Array>, + i: number, + j: number +): number { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路徑和:記憶化搜尋 */ +function minPathSumDFSMem( + grid: Array>, + mem: Array>, + i: number, + j: number +): number { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +function minPathSumDP(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i < n; i++) { + for (let j: number = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +function minPathSumDPComp(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (let i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 暴力搜尋 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 動態規劃 +res = minPathSumDP(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 空間最佳化後的動態規劃 +res = minPathSumDPComp(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts new file mode 100644 index 0000000000..6f0f3ca316 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts @@ -0,0 +1,73 @@ +/** + * File: unbounded_knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:動態規劃 */ +function unboundedKnapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +function unboundedKnapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 動態規劃 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 空間最佳化後的動態規劃 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_graph/graph_adjacency_list.ts b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_list.ts new file mode 100644 index 0000000000..13c76a1e5a --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_list.ts @@ -0,0 +1,139 @@ +/** + * File: graph_adjacency_list.ts + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { Vertex } from '../modules/Vertex'; + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + adjList: Map; + + /* 建構子 */ + constructor(edges: Vertex[][]) { + this.adjList = new Map(); + // 新增所有頂點和邊 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + size(): number { + return this.adjList.size; + } + + /* 新增邊 */ + addEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 新增邊 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 刪除邊 */ + removeEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 刪除邊 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 新增頂點 */ + addVertex(vet: Vertex): void { + if (this.adjList.has(vet)) return; + // 在鄰接表中新增一個新鏈結串列 + this.adjList.set(vet, []); + } + + /* 刪除頂點 */ + removeVertex(vet: Vertex): void { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + this.adjList.delete(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (const set of this.adjList.values()) { + const index: number = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 列印鄰接表 */ + print(): void { + console.log('鄰接表 ='); + for (const [key, value] of this.adjList.entries()) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* 初始化無向圖 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初始化後,圖為'); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v0, v2 + graph.addEdge(v0, v2); + console.log('\n新增邊 1-2 後,圖為'); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v0, v1 + graph.removeEdge(v0, v1); + console.log('\n刪除邊 1-3 後,圖為'); + graph.print(); + + /* 新增頂點 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n新增頂點 6 後,圖為'); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v1 + graph.removeVertex(v1); + console.log('\n刪除頂點 3 後,圖為'); + graph.print(); +} + +export { GraphAdjList }; diff --git a/zh-hant/codes/typescript/chapter_graph/graph_adjacency_matrix.ts b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_matrix.ts new file mode 100644 index 0000000000..e191848815 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_matrix.ts @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.ts + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + vertices: number[]; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + adjMat: number[][]; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + constructor(vertices: number[], edges: number[][]) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + for (const val of vertices) { + this.addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + size(): number { + return this.vertices.length; + } + + /* 新增頂點 */ + addVertex(val: number): void { + const n: number = this.size(); + // 向頂點串列中新增新頂點的值 + this.vertices.push(val); + // 在鄰接矩陣中新增一行 + const newRow: number[] = []; + for (let j: number = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在鄰接矩陣中新增一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 刪除頂點 */ + removeVertex(index: number): void { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在頂點串列中移除索引 index 的頂點 + this.vertices.splice(index, 1); + + // 在鄰接矩陣中刪除索引 index 的行 + this.adjMat.splice(index, 1); + // 在鄰接矩陣中刪除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + addEdge(i: number, j: number): void { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + removeEdge(i: number, j: number): void { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + print(): void { + console.log('頂點串列 = ', this.vertices); + console.log('鄰接矩陣 =', this.adjMat); + } +} + +/* Driver Code */ +/* 初始化無向圖 */ +// 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 +const vertices: number[] = [1, 3, 2, 5, 4]; +const edges: number[][] = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 新增邊 */ +// 頂點 1, 2 的索引分別為 0, 2 +graph.addEdge(0, 2); +console.log('\n新增邊 1-2 後,圖為'); +graph.print(); + +/* 刪除邊 */ +// 頂點 1, 3 的索引分別為 0, 1 +graph.removeEdge(0, 1); +console.log('\n刪除邊 1-3 後,圖為'); +graph.print(); + +/* 新增頂點 */ +graph.addVertex(6); +console.log('\n新增頂點 6 後,圖為'); +graph.print(); + +/* 刪除頂點 */ +// 頂點 3 的索引為 1 +graph.removeVertex(1); +console.log('\n刪除頂點 3 後,圖為'); +graph.print(); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_graph/graph_bfs.ts b/zh-hant/codes/typescript/chapter_graph/graph_bfs.ts new file mode 100644 index 0000000000..916b4f1c40 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_bfs.ts @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { GraphAdjList } from './graph_adjacency_list'; +import { Vertex } from '../modules/Vertex'; + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 頂點走訪序列 + const res: Vertex[] = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + const visited: Set = new Set(); + visited.add(startVet); + // 佇列用於實現 BFS + const que = [startVet]; + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.length) { + const vet = que.shift(); // 佇列首頂點出隊 + res.push(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.push(adjVet); // 只入列未訪問 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 廣度優先走訪 */ +const res = graphBFS(graph, v[0]); +console.log('\n廣度優先走訪(BFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/typescript/chapter_graph/graph_dfs.ts b/zh-hant/codes/typescript/chapter_graph/graph_dfs.ts new file mode 100644 index 0000000000..846e212001 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_dfs.ts @@ -0,0 +1,58 @@ +/** + * File: graph_dfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { Vertex } from '../modules/Vertex'; +import { GraphAdjList } from './graph_adjacency_list'; + +/* 深度優先走訪輔助函式 */ +function dfs( + graph: GraphAdjList, + visited: Set, + res: Vertex[], + vet: Vertex +): void { + res.push(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 頂點走訪序列 + const res: Vertex[] = []; + // 雜湊集合,用於記錄已被訪問過的頂點 + const visited: Set = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 深度優先走訪 */ +const res = graphDFS(graph, v[0]); +console.log('\n深度優先走訪(DFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/typescript/chapter_greedy/coin_change_greedy.ts b/zh-hant/codes/typescript/chapter_greedy/coin_change_greedy.ts new file mode 100644 index 0000000000..d8a54c1bd9 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/coin_change_greedy.ts @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 零錢兌換:貪婪 */ +function coinChangeGreedy(coins: number[], amt: number): number { + // 假設 coins 陣列有序 + let i = coins.length - 1; + let count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 貪婪:能夠保證找到全域性最優解 +let coins: number[] = [1, 5, 10, 20, 50, 100]; +let amt: number = 186; +let res: number = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 3 ,即 20 + 20 + 20'); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 2 ,即 49 + 49'); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_greedy/fractional_knapsack.ts b/zh-hant/codes/typescript/chapter_greedy/fractional_knapsack.ts new file mode 100644 index 0000000000..fe7f142cac --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/fractional_knapsack.ts @@ -0,0 +1,50 @@ +/** + * File: fractional_knapsack.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 物品 */ +class Item { + w: number; // 物品重量 + v: number; // 物品價值 + + constructor(w: number, v: number) { + this.w = w; + this.v = v; + } +} + +/* 分數背包:貪婪 */ +function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { + // 建立物品串列,包含兩個屬性:重量、價值 + const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 迴圈貪婪選擇 + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (item.v / item.w) * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; +} + +/* Driver Code */ +const wgt: number[] = [10, 20, 30, 40, 50]; +const val: number[] = [50, 120, 150, 210, 240]; +const cap: number = 50; + +// 貪婪演算法 +const res: number = fractionalKnapsack(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_greedy/max_capacity.ts b/zh-hant/codes/typescript/chapter_greedy/max_capacity.ts new file mode 100644 index 0000000000..a835dfee67 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/max_capacity.ts @@ -0,0 +1,36 @@ +/** + * File: max_capacity.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:貪婪 */ +function maxCapacity(ht: number[]): number { + // 初始化 i, j,使其分列陣列兩端 + let i = 0, + j = ht.length - 1; + // 初始最大容量為 0 + let res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + const cap: number = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 貪婪演算法 +const res: number = maxCapacity(ht); +console.log(`最大容量為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_greedy/max_product_cutting.ts b/zh-hant/codes/typescript/chapter_greedy/max_product_cutting.ts new file mode 100644 index 0000000000..97ded2e8b6 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/max_product_cutting.ts @@ -0,0 +1,35 @@ +/** + * File: max_product_cutting.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切分乘積:貪婪 */ +function maxProductCutting(n: number): number { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a: number = Math.floor(n / 3); + let b: number = n % 3; + if (b === 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 當餘數為 2 時,不做處理 + return Math.pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return Math.pow(3, a); +} + +/* Driver Code */ +let n: number = 58; + +// 貪婪演算法 +let res: number = maxProductCutting(n); +console.log(`最大切分乘積為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/array_hash_map.ts b/zh-hant/codes/typescript/chapter_hashing/array_hash_map.ts new file mode 100644 index 0000000000..018b143bf9 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/array_hash_map.ts @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + public key: number; + public val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private readonly buckets: (Pair | null)[]; + + constructor() { + // 初始化陣列,包含 100 個桶 + this.buckets = new Array(100).fill(null); + } + + /* 雜湊函式 */ + private hashFunc(key: number): number { + return key % 100; + } + + /* 查詢操作 */ + public get(key: number): string | null { + let index = this.hashFunc(key); + let pair = this.buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* 新增操作 */ + public set(key: number, val: string) { + let index = this.hashFunc(key); + this.buckets[index] = new Pair(key, val); + } + + /* 刪除操作 */ + public delete(key: number) { + let index = this.hashFunc(key); + // 置為 null ,代表刪除 + this.buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + public entries(): (Pair | null)[] { + let arr: (Pair | null)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i]); + } + } + return arr; + } + + /* 獲取所有鍵 */ + public keys(): (number | undefined)[] { + let arr: (number | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].key); + } + } + return arr; + } + + /* 獲取所有值 */ + public values(): (string | undefined)[] { + let arr: (string | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].val); + } + } + return arr; + } + + /* 列印雜湊表 */ + public print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new ArrayHashMap(); +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +map.set(10583, '小鴨'); +console.info('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +let name = map.get(15937); +console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.delete(10583); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +map.print(); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\n單獨走訪鍵 Key'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\n單獨走訪值 Value'); +for (const val of map.values()) { + console.info(val); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/hash_map.ts b/zh-hant/codes/typescript/chapter_hashing/hash_map.ts new file mode 100644 index 0000000000..908451c088 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/hash_map.ts @@ -0,0 +1,46 @@ +/** + * File: hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new Map(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +map.set(10583, '小鴨'); +console.info('\n新增完成後,雜湊表為\nKey -> Value'); +console.info(map); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +let name = map.get(15937); +console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.delete(10583); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +console.info(map); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\n單獨走訪鍵 Key'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\n單獨走訪值 Value'); +for (const v of map.values()) { + console.info(v); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/hash_map_chaining.ts b/zh-hant/codes/typescript/chapter_hashing/hash_map_chaining.ts new file mode 100644 index 0000000000..a48c985e73 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/hash_map_chaining.ts @@ -0,0 +1,146 @@ +/** + * File: hash_map_chaining.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + key: number; + val: string; + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + #size: number; // 鍵值對數量 + #capacity: number; // 雜湊表容量 + #loadThres: number; // 觸發擴容的負載因子閾值 + #extendRatio: number; // 擴容倍數 + #buckets: Pair[][]; // 桶陣列 + + /* 建構子 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 雜湊函式 */ + #hashFunc(key: number): number { + return key % this.#capacity; + } + + /* 負載因子 */ + #loadFactor(): number { + return this.#size / this.#capacity; + } + + /* 查詢操作 */ + get(key: number): string | null { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若找到 key ,則返回對應 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + put(key: number, val: string): void { + // 當負載因子超過閾值時,執行擴容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 刪除操作 */ + remove(key: number): void { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* 擴容雜湊表 */ + #extend(): void { + // 暫存原雜湊表 + const bucketsTmp = this.#buckets; + // 初始化擴容後的新雜湊表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print(): void { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new HashMapChaining(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小囉'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +const name = map.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.remove(12836); +console.log('\n刪除 12836 後,雜湊表為\nKey -> Value'); +map.print(); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/hash_map_open_addressing.ts b/zh-hant/codes/typescript/chapter_hashing/hash_map_open_addressing.ts new file mode 100644 index 0000000000..e0bdf778fa --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/hash_map_open_addressing.ts @@ -0,0 +1,182 @@ +/** + * File: hash_map_open_addressing.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + key: number; + val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private size: number; // 鍵值對數量 + private capacity: number; // 雜湊表容量 + private loadThres: number; // 觸發擴容的負載因子閾值 + private extendRatio: number; // 擴容倍數 + private buckets: Array; // 桶陣列 + private TOMBSTONE: Pair; // 刪除標記 + + /* 建構子 */ + constructor() { + this.size = 0; // 鍵值對數量 + this.capacity = 4; // 雜湊表容量 + this.loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + this.extendRatio = 2; // 擴容倍數 + this.buckets = Array(this.capacity).fill(null); // 桶陣列 + this.TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 + } + + /* 雜湊函式 */ + private hashFunc(key: number): number { + return key % this.capacity; + } + + /* 負載因子 */ + private loadFactor(): number { + return this.size / this.capacity; + } + + /* 搜尋 key 對應的桶索引 */ + private findBucket(key: number): number { + let index = this.hashFunc(key); + let firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (this.buckets[index] !== null) { + // 若遇到 key ,返回對應的桶索引 + if (this.buckets[index]!.key === key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone !== -1) { + this.buckets[firstTombstone] = this.buckets[index]; + this.buckets[index] = this.TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if ( + firstTombstone === -1 && + this.buckets[index] === this.TOMBSTONE + ) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % this.capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + get(key: number): string | null { + // 搜尋 key 對應的桶索引 + const index = this.findBucket(key); + // 若找到鍵值對,則返回對應 val + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + return this.buckets[index]!.val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + put(key: number, val: string): void { + // 當負載因子超過閾值時,執行擴容 + if (this.loadFactor() > this.loadThres) { + this.extend(); + } + // 搜尋 key 對應的桶索引 + const index = this.findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index]!.val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + this.buckets[index] = new Pair(key, val); + this.size++; + } + + /* 刪除操作 */ + remove(key: number): void { + // 搜尋 key 對應的桶索引 + const index = this.findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index] = this.TOMBSTONE; + this.size--; + } + } + + /* 擴容雜湊表 */ + private extend(): void { + // 暫存原雜湊表 + const bucketsTmp = this.buckets; + // 初始化擴容後的新雜湊表 + this.capacity *= this.extendRatio; + this.buckets = Array(this.capacity).fill(null); + this.size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print(): void { + for (const pair of this.buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// 初始化雜湊表 +const hashmap = new HashMapOpenAddressing(); + +// 新增操作 +// 在雜湊表中新增鍵值對 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小囉'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +hashmap.print(); + +// 查詢操作 +// 向雜湊表中輸入鍵 key ,得到值 val +const name = hashmap.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +// 刪除操作 +// 在雜湊表中刪除鍵值對 (key, val) +hashmap.remove(16750); +console.log('\n刪除 16750 後,雜湊表為\nKey -> Value'); +hashmap.print(); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/simple_hash.ts b/zh-hant/codes/typescript/chapter_hashing/simple_hash.ts new file mode 100644 index 0000000000..5d615bee84 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/simple_hash.ts @@ -0,0 +1,60 @@ +/** + * File: simple_hash.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法雜湊 */ +function addHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法雜湊 */ +function mulHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 互斥或雜湊 */ +function xorHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* 旋轉雜湊 */ +function rotHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 演算法'; + +let hash = addHash(key); +console.log('加法雜湊值為 ' + hash); + +hash = mulHash(key); +console.log('乘法雜湊值為 ' + hash); + +hash = xorHash(key); +console.log('互斥或雜湊值為 ' + hash); + +hash = rotHash(key); +console.log('旋轉雜湊值為 ' + hash); diff --git a/zh-hant/codes/typescript/chapter_heap/my_heap.ts b/zh-hant/codes/typescript/chapter_heap/my_heap.ts new file mode 100644 index 0000000000..d0aab14dda --- /dev/null +++ b/zh-hant/codes/typescript/chapter_heap/my_heap.ts @@ -0,0 +1,155 @@ +/** + * File: my_heap.ts + * Created Time: 2023-02-07 + * Author: Justin (xiefahit@gmail.com) + */ + +import { printHeap } from '../modules/PrintUtil'; + +/* 最大堆積類別 */ +class MaxHeap { + private maxHeap: number[]; + /* 建構子,建立空堆積或根據輸入串列建堆積 */ + constructor(nums?: number[]) { + // 將串列元素原封不動新增進堆積 + this.maxHeap = nums === undefined ? [] : [...nums]; + // 堆積化除葉節點以外的其他所有節點 + for (let i = this.parent(this.size() - 1); i >= 0; i--) { + this.siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + private left(i: number): number { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + private right(i: number): number { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + private parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 交換元素 */ + private swap(i: number, j: number): void { + const tmp = this.maxHeap[i]; + this.maxHeap[i] = this.maxHeap[j]; + this.maxHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + public size(): number { + return this.maxHeap.length; + } + + /* 判斷堆積是否為空 */ + public isEmpty(): boolean { + return this.size() === 0; + } + + /* 訪問堆積頂元素 */ + public peek(): number { + return this.maxHeap[0]; + } + + /* 元素入堆積 */ + public push(val: number): void { + // 新增節點 + this.maxHeap.push(val); + // 從底至頂堆積化 + this.siftUp(this.size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private siftUp(i: number): void { + while (true) { + // 獲取節點 i 的父節點 + const p = this.parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; + // 交換兩節點 + this.swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + public pop(): number { + // 判空處理 + if (this.isEmpty()) throw new RangeError('Heap is empty.'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + this.swap(0, this.size() - 1); + // 刪除節點 + const val = this.maxHeap.pop(); + // 從頂至底堆積化 + this.siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private siftDown(i: number): void { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + const l = this.left(i), + r = this.right(i); + let ma = i; + if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; + if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma === i) break; + // 交換兩節點 + this.swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + public print(): void { + printHeap(this.maxHeap); + } + + /* 取出堆積中元素 */ + public getMaxHeap(): number[] { + return this.maxHeap; + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* 初始化大頂堆積 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\n輸入串列並建堆積後'); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + let peek = maxHeap.peek(); + console.log(`\n堆積頂元素為 ${peek}`); + + /* 元素入堆積 */ + const val = 7; + maxHeap.push(val); + console.log(`\n元素 ${val} 入堆積後`); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + console.log(`\n堆積頂元素 ${peek} 出堆積後`); + maxHeap.print(); + + /* 獲取堆積大小 */ + const size = maxHeap.size(); + console.log(`\n堆積元素數量為 ${size}`); + + /* 判斷堆積是否為空 */ + const isEmpty = maxHeap.isEmpty(); + console.log(`\n堆積是否為空 ${isEmpty}`); +} + +export { MaxHeap }; diff --git a/zh-hant/codes/typescript/chapter_heap/top_k.ts b/zh-hant/codes/typescript/chapter_heap/top_k.ts new file mode 100644 index 0000000000..5aa3ea4918 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_heap/top_k.ts @@ -0,0 +1,58 @@ +/** + * File: top_k.ts + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { MaxHeap } from './my_heap'; + +/* 元素入堆積 */ +function pushMinHeap(maxHeap: MaxHeap, val: number): void { + // 元素取反 + maxHeap.push(-val); +} + +/* 元素出堆積 */ +function popMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.pop(); +} + +/* 訪問堆積頂元素 */ +function peekMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.peek(); +} + +/* 取出堆積中元素 */ +function getMinHeap(maxHeap: MaxHeap): number[] { + // 元素取反 + return maxHeap.getMaxHeap().map((num: number) => -num); +} + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +function topKHeap(nums: number[], k: number): number[] { + // 初始化小頂堆積 + // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 + const maxHeap = new MaxHeap([]); + // 將陣列的前 k 個元素入堆積 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (let i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆積中元素 + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大的 ${k} 個元素為`, res); diff --git a/zh-hant/codes/typescript/chapter_searching/binary_search.ts b/zh-hant/codes/typescript/chapter_searching/binary_search.ts new file mode 100644 index 0000000000..83f37b08ed --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/binary_search.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search.ts + * Created Time: 2022-12-27 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* 二分搜尋(雙閉區間) */ +function binarySearch(nums: number[], target: number): number { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + let i = 0, + j = nums.length - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + // 計算中點索引 m + const m = Math.floor(i + (j - i) / 2); + 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; + } + } + return -1; // 未找到目標元素,返回 -1 +} + +/* 二分搜尋(左閉右開區間) */ +function binarySearchLCRO(nums: number[], target: number): number { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + let i = 0, + j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + // 計算中點索引 m + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + } else if (nums[m] > target) { + // 此情況說明 target 在區間 [i, m) 中 + j = m; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + return -1; // 未找到目標元素,返回 -1 +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* 二分搜尋(雙閉區間) */ +let index = binarySearch(nums, target); +console.info('目標元素 6 的索引 = %d', index); + +/* 二分搜尋(左閉右開區間) */ +index = binarySearchLCRO(nums, target); +console.info('目標元素 6 的索引 = %d', index); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/binary_search_edge.ts b/zh-hant/codes/typescript/chapter_searching/binary_search_edge.ts new file mode 100644 index 0000000000..a8b24f3bd8 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/binary_search_edge.ts @@ -0,0 +1,46 @@ +/** + * File: binary_search_edge.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ +import { binarySearchInsertion } from './binary_search_insertion'; + +/* 二分搜尋最左一個 target */ +function binarySearchLeftEdge(nums: Array, target: number): number { + // 等價於查詢 target 的插入點 + const i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +function binarySearchRightEdge(nums: Array, target: number): number { + // 轉化為查詢最左一個 target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + const j = i - 1; + // 未找到 target ,返回 -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +// 包含重複元素的陣列 +let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋左邊界和右邊界 +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('最左一個元素 ' + target + ' 的索引為 ' + index); + index = binarySearchRightEdge(nums, target); + console.log('最右一個元素 ' + target + ' 的索引為 ' + index); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/binary_search_insertion.ts b/zh-hant/codes/typescript/chapter_searching/binary_search_insertion.ts new file mode 100644 index 0000000000..e42bedc253 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/binary_search_insertion.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +function binarySearchInsertionSimple( + nums: Array, + target: number +): number { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* 二分搜尋插入點(存在重複元素) */ +function binarySearchInsertion(nums: Array, target: number): number { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + 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; +} + +/* Driver Code */ +// 無重複元素的陣列 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +// 包含重複元素的陣列 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +export { binarySearchInsertion }; diff --git a/zh-hant/codes/typescript/chapter_searching/hashing_search.ts b/zh-hant/codes/typescript/chapter_searching/hashing_search.ts new file mode 100644 index 0000000000..da7bd521bd --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/hashing_search.ts @@ -0,0 +1,50 @@ +/** + * File: hashing_search.ts + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* 雜湊查詢(陣列) */ +function hashingSearchArray(map: Map, target: number): number { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.has(target) ? (map.get(target) as number) : -1; +} + +/* 雜湊查詢(鏈結串列) */ +function hashingSearchLinkedList( + map: Map, + target: number +): ListNode | null { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.has(target) ? (map.get(target) as ListNode) : null; +} + +/* Driver Code */ +const target = 3; + +/* 雜湊查詢(陣列) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// 初始化雜湊表 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 元素,value: 索引 +} +const index = hashingSearchArray(map, target); +console.log('目標元素 3 的索引 = ' + index); + +/* 雜湊查詢(鏈結串列) */ +let head = arrToLinkedList(nums); +// 初始化雜湊表 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: 節點值,value: 節點 + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('目標節點值 3 的對應節點物件為', node); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/linear_search.ts b/zh-hant/codes/typescript/chapter_searching/linear_search.ts new file mode 100644 index 0000000000..9e2ec49f2c --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/linear_search.ts @@ -0,0 +1,52 @@ +/** + * File: linear_search.ts + * Created Time: 2023-01-07 + * Author: Daniel (better.sunjian@gmail.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* 線性查詢(陣列)*/ +function linearSearchArray(nums: number[], target: number): number { + // 走訪陣列 + for (let i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] === target) { + return i; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列)*/ +function linearSearchLinkedList( + head: ListNode | null, + target: number +): ListNode | null { + // 走訪鏈結串列 + while (head) { + // 找到目標節點,返回之 + if (head.val === target) { + return head; + } + head = head.next; + } + // 未找到目標節點,返回 null + return null; +} + +/* Driver Code */ +const target = 3; + +/* 在陣列中執行線性查詢 */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('目標元素 3 的索引 =', index); + +/* 在鏈結串列中執行線性查詢 */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('目標節點值 3 的對應節點物件為', node); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/two_sum.ts b/zh-hant/codes/typescript/chapter_searching/two_sum.ts new file mode 100644 index 0000000000..74670f5654 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/two_sum.ts @@ -0,0 +1,49 @@ +/** + * File: two_sum.ts + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法一:暴力列舉 */ +function twoSumBruteForce(nums: number[], target: number): number[] { + const n = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法二:輔助雜湊表 */ +function twoSumHashTable(nums: number[], target: number): number[] { + // 輔助雜湊表,空間複雜度為 O(n) + let m: Map = new Map(); + // 單層迴圈,時間複雜度為 O(n) + for (let i = 0; i < nums.length; i++) { + let index = m.get(target - nums[i]); + if (index !== undefined) { + return [index, i]; + } else { + m.set(nums[i], i); + } + } + return []; +} + +/* Driver Code */ +// 方法一 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法一 res = ', res); + +// 方法二 +res = twoSumHashTable(nums, target); +console.log('方法二 res = ', res); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/bubble_sort.ts b/zh-hant/codes/typescript/chapter_sorting/bubble_sort.ts new file mode 100644 index 0000000000..ef775d3bb0 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/bubble_sort.ts @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 泡沫排序 */ +function bubbleSort(nums: number[]): void { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +function bubbleSortWithFlag(nums: number[]): void { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('泡沫排序完成後 nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('泡沫排序完成後 nums =', nums1); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/bucket_sort.ts b/zh-hant/codes/typescript/chapter_sorting/bucket_sort.ts new file mode 100644 index 0000000000..c00f77fe44 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/bucket_sort.ts @@ -0,0 +1,41 @@ +/** + * File: bucket_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 桶排序 */ +function bucketSort(nums: number[]): void { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + const k = nums.length / 2; + const buckets: number[][] = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 將陣列元素分配到各個桶中 + for (const num of nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + const i = Math.floor(num * k); + // 將 num 新增進桶 i + buckets[i].push(num); + } + // 2. 對各個桶執行排序 + for (const bucket of buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort((a, b) => a - b); + } + // 3. 走訪桶合併結果 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('桶排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/counting_sort.ts b/zh-hant/codes/typescript/chapter_sorting/counting_sort.ts new file mode 100644 index 0000000000..db62710be1 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/counting_sort.ts @@ -0,0 +1,73 @@ +/** + * File: counting_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +function countingSortNaive(nums: number[]): void { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +function countingSort(nums: number[]): void { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + const n = nums.length; + const res: number[] = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('計數排序(無法排序物件)完成後 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('計數排序完成後 nums1 =', nums1); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/heap_sort.ts b/zh-hant/codes/typescript/chapter_sorting/heap_sort.ts new file mode 100644 index 0000000000..97414f529a --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/heap_sort.ts @@ -0,0 +1,51 @@ +/** + * File: heap_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +function siftDown(nums: number[], n: number, i: number): void { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let 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; + } + // 交換兩節點 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +function heapSort(nums: number[]): void { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (let i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('堆積排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/insertion_sort.ts b/zh-hant/codes/typescript/chapter_sorting/insertion_sort.ts new file mode 100644 index 0000000000..23081e29a6 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/insertion_sort.ts @@ -0,0 +1,27 @@ +/** + * File: insertion_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 插入排序 */ +function insertionSort(nums: number[]): void { + // 外迴圈:已排序區間為 [0, i-1] + for (let i = 1; i < nums.length; i++) { + const base = nums[i]; + let j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('插入排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/merge_sort.ts b/zh-hant/codes/typescript/chapter_sorting/merge_sort.ts new file mode 100644 index 0000000000..2696ab439f --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/merge_sort.ts @@ -0,0 +1,54 @@ +/** + * File: merge_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 合併左子陣列和右子陣列 */ +function merge(nums: number[], left: number, mid: number, right: number): void { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + const tmp = new Array(right - left + 1); + // 初始化左子陣列和右子陣列的起始索引 + let 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 < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +function mergeSort(nums: number[], left: number, right: number): void { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + let mid = Math.floor(left + (right - left) / 2); // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('合併排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/quick_sort.ts b/zh-hant/codes/typescript/chapter_sorting/quick_sort.ts new file mode 100644 index 0000000000..5fe31d6054 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/quick_sort.ts @@ -0,0 +1,180 @@ +/** + * File: quick_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums: number[], left: number, right: number): number { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // 從左向右找首個大於基準數的元素 + } + // 元素交換 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums: number[], left: number, right: number): void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) { + return; + } + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + medianThree( + nums: number[], + left: number, + mid: number, + right: number + ): number { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m 在 l 和 r 之間 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l 在 m 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* 哨兵劃分(三數取中值) */ + partition(nums: number[], left: number, right: number): number { + // 選取三個候選元素的中位數 + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // 將中位數交換至陣列最左端 + this.swap(nums, left, med); + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i++; // 從左向右找首個大於基準數的元素 + } + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums: number[], left: number, right: number): void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) { + return; + } + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums: number[], left: number, right: number): number { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i++; // 從左向右找首個大於基準數的元素 + } + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + quickSort(nums: number[], left: number, right: number): void { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + let pivot = this.partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* 快速排序 */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('快速排序完成後 nums =', nums); + +/* 快速排序(中位基準數最佳化) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1); + +/* 快速排序(尾遞迴最佳化) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('快速排序(尾遞迴最佳化)完成後 nums =', nums2); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/radix_sort.ts b/zh-hant/codes/typescript/chapter_sorting/radix_sort.ts new file mode 100644 index 0000000000..91ca29dd24 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/radix_sort.ts @@ -0,0 +1,68 @@ +/** + * File: radix_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +function digit(num: number, exp: number): number { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return Math.floor(num / exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +function countingSortDigit(nums: number[], exp: number): void { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + const counter = new Array(10).fill(0); + const n = nums.length; + // 統計 0~9 各數字的出現次數 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基數排序 */ +function radixSort(nums: number[]): void { + // 獲取陣列的最大元素,用於判斷最大位數 + let m = Number.MIN_VALUE; + for (const num of nums) { + if (num > m) { + m = num; + } + } + // 按照從低位到高位的順序走訪 + for (let exp = 1; exp <= m; exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基數排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/selection_sort.ts b/zh-hant/codes/typescript/chapter_sorting/selection_sort.ts new file mode 100644 index 0000000000..438d9a87c0 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/selection_sort.ts @@ -0,0 +1,29 @@ +/** + * File: selection_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 選擇排序 */ +function selectionSort(nums: number[]): void { + let n = nums.length; + // 外迴圈:未排序區間為 [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('選擇排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/array_deque.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/array_deque.ts new file mode 100644 index 0000000000..073cf3754e --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/array_deque.ts @@ -0,0 +1,158 @@ +/** + * File: array_deque.ts + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private nums: number[]; // 用於儲存雙向佇列元素的陣列 + private front: number; // 佇列首指標,指向佇列首元素 + private queSize: number; // 雙向佇列長度 + + /* 建構子 */ + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = 0; + this.queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + capacity(): number { + return this.nums.length; + } + + /* 獲取雙向佇列的長度 */ + size(): number { + return this.queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 計算環形陣列索引 */ + index(i: number): number { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + this.capacity()) % this.capacity(); + } + + /* 佇列首入列 */ + pushFirst(num: number): void { + if (this.queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + this.front = this.index(this.front - 1); + // 將 num 新增至佇列首 + this.nums[this.front] = num; + this.queSize++; + } + + /* 佇列尾入列 */ + pushLast(num: number): void { + if (this.queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + const rear: number = this.index(this.front + this.queSize); + // 將 num 新增至佇列尾 + this.nums[rear] = num; + this.queSize++; + } + + /* 佇列首出列 */ + popFirst(): number { + const num: number = this.peekFirst(); + // 佇列首指標向後移動一位 + this.front = this.index(this.front + 1); + this.queSize--; + return num; + } + + /* 佇列尾出列 */ + popLast(): number { + const num: number = this.peekLast(); + this.queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peekFirst(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.nums[this.front]; + } + + /* 訪問佇列尾元素 */ + peekLast(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 計算尾元素索引 + const last = this.index(this.front + this.queSize - 1); + return this.nums[last]; + } + + /* 返回陣列用於列印 */ + toArray(): number[] { + // 僅轉換有效長度範圍內的串列元素 + const res: number[] = []; + for (let i = 0, j = this.front; i < this.queSize; i++, j++) { + res[i] = this.nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const capacity = 5; +const deque: ArrayDeque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('雙向佇列 deque = [' + deque.toArray() + ']'); + +/* 訪問元素 */ +const peekFirst = deque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +deque.pushLast(4); +console.log('元素 4 佇列尾入列後 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('元素 1 佇列首入列後 deque = [' + deque.toArray() + ']'); + +/* 元素出列 */ +const popLast = deque.popLast(); +console.log( + '佇列尾出列元素 = ' + + popLast + + ',佇列尾出列後 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '佇列首出列元素 = ' + + popFirst + + ',佇列首出列後 deque = [' + + deque.toArray() + + ']' +); + +/* 獲取雙向佇列的長度 */ +const size = deque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = deque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/array_queue.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/array_queue.ts new file mode 100644 index 0000000000..86c28b4b4a --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/array_queue.ts @@ -0,0 +1,109 @@ +/** + * File: array_queue.ts + * Created Time: 2022-12-11 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private nums: number[]; // 用於儲存佇列元素的陣列 + private front: number; // 佇列首指標,指向佇列首元素 + private queSize: number; // 佇列長度 + + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = this.queSize = 0; + } + + /* 獲取佇列的容量 */ + get capacity(): number { + return this.nums.length; + } + + /* 獲取佇列的長度 */ + get size(): number { + return this.queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 入列 */ + push(num: number): void { + if (this.size === this.capacity) { + console.log('佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + const rear = (this.front + this.queSize) % this.capacity; + // 將 num 新增至佇列尾 + this.nums[rear] = num; + this.queSize++; + } + + /* 出列 */ + pop(): number { + const num = this.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + this.front = (this.front + 1) % this.capacity; + this.queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek(): number { + if (this.isEmpty()) throw new Error('佇列為空'); + return this.nums[this.front]; + } + + /* 返回 Array */ + toArray(): number[] { + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(this.size); + for (let i = 0, j = this.front; i < this.size; i++, j++) { + arr[i] = this.nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue =', queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); + +/* 測試環形陣列 */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 輪入列 + 出列後 queue =', queue.toArray()); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/array_stack.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/array_stack.ts new file mode 100644 index 0000000000..965c2d7c9d --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/array_stack.ts @@ -0,0 +1,77 @@ +/** + * File: array_stack.ts + * Created Time: 2022-12-08 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private stack: number[]; + constructor() { + this.stack = []; + } + + /* 獲取堆疊的長度 */ + get size(): number { + return this.stack.length; + } + + /* 判斷堆疊是否為空 */ + isEmpty(): boolean { + return this.stack.length === 0; + } + + /* 入堆疊 */ + push(num: number): void { + this.stack.push(num); + } + + /* 出堆疊 */ + pop(): number | undefined { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.stack.pop(); + } + + /* 訪問堆疊頂元素 */ + top(): number | undefined { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.stack[this.stack.length - 1]; + } + + /* 返回 Array */ + toArray() { + return this.stack; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new ArrayStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = '); +console.log(stack.toArray()); + +/* 訪問堆疊頂元素 */ +const top = stack.top(); +console.log('堆疊頂元素 top = ' + top); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = '); +console.log(stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/deque.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/deque.ts new file mode 100644 index 0000000000..3358c19215 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/deque.ts @@ -0,0 +1,46 @@ +/** + * File: deque.ts + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 初始化雙向佇列 */ +// TypeScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 +const deque: number[] = []; + +/* 元素入列 */ +deque.push(2); +deque.push(5); +deque.push(4); +// 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) +deque.unshift(3); +deque.unshift(1); +console.log('雙向佇列 deque = ', deque); + +/* 訪問元素 */ +const peekFirst: number = deque[0]; +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast: number = deque[deque.length - 1]; +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素出列 */ +// 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) +const popFront: number = deque.shift() as number; +console.log( + '佇列首出列元素 popFront = ' + popFront + ',佇列首出列後 deque = ' + deque +); +const popBack: number = deque.pop() as number; +console.log( + '佇列尾出列元素 popBack = ' + popBack + ',佇列尾出列後 deque = ' + deque +); + +/* 獲取雙向佇列的長度 */ +const size: number = deque.length; +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty: boolean = size === 0; +console.log('雙向佇列是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts new file mode 100644 index 0000000000..47973ca951 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.ts + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + prev: ListNode; // 前驅節點引用 (指標) + next: ListNode; // 後繼節點引用 (指標) + val: number; // 節點值 + + constructor(val: number) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private front: ListNode; // 頭節點 front + private rear: ListNode; // 尾節點 rear + private queSize: number; // 雙向佇列的長度 + + constructor() { + this.front = null; + this.rear = null; + this.queSize = 0; + } + + /* 佇列尾入列操作 */ + pushLast(val: number): void { + const node: ListNode = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 將 node 新增至鏈結串列尾部 + this.rear.next = node; + node.prev = this.rear; + this.rear = node; // 更新尾節點 + } + this.queSize++; + } + + /* 佇列首入列操作 */ + pushFirst(val: number): void { + const node: ListNode = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 將 node 新增至鏈結串列頭部 + this.front.prev = node; + node.next = this.front; + this.front = node; // 更新頭節點 + } + this.queSize++; + } + + /* 佇列尾出列操作 */ + popLast(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.rear.val; // 儲存尾節點值 + // 刪除尾節點 + let temp: ListNode = this.rear.prev; + if (temp !== null) { + temp.next = null; + this.rear.prev = null; + } + this.rear = temp; // 更新尾節點 + this.queSize--; + return value; + } + + /* 佇列首出列操作 */ + popFirst(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.front.val; // 儲存尾節點值 + // 刪除頭節點 + let temp: ListNode = this.front.next; + if (temp !== null) { + temp.prev = null; + this.front.next = null; + } + this.front = temp; // 更新頭節點 + this.queSize--; + return value; + } + + /* 訪問佇列尾元素 */ + peekLast(): number { + return this.queSize === 0 ? null : this.rear.val; + } + + /* 訪問佇列首元素 */ + peekFirst(): number { + return this.queSize === 0 ? null : this.front.val; + } + + /* 獲取雙向佇列的長度 */ + size(): number { + return this.queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 列印雙向佇列 */ + print(): void { + const arr: number[] = []; + let temp: ListNode = this.front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const linkedListDeque: LinkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('雙向佇列 linkedListDeque = '); +linkedListDeque.print(); + +/* 訪問元素 */ +const peekFirst: number = linkedListDeque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast: number = linkedListDeque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +linkedListDeque.pushLast(4); +console.log('元素 4 佇列尾入列後 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('元素 1 佇列首入列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 元素出列 */ +const popLast: number = linkedListDeque.popLast(); +console.log('佇列尾出列元素 = ' + popLast + ',佇列尾出列後 linkedListDeque = '); +linkedListDeque.print(); +const popFirst: number = linkedListDeque.popFirst(); +console.log('佇列首出列元素 = ' + popFirst + ',佇列首出列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 獲取雙向佇列的長度 */ +const size: number = linkedListDeque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty: boolean = linkedListDeque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts new file mode 100644 index 0000000000..bf2a2a348e --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts @@ -0,0 +1,102 @@ +/** + * File: linkedlist_queue.ts + * Created Time: 2022-12-19 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private front: ListNode | null; // 頭節點 front + private rear: ListNode | null; // 尾節點 rear + private queSize: number = 0; + + constructor() { + this.front = null; + this.rear = null; + } + + /* 獲取佇列的長度 */ + get size(): number { + return this.queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* 入列 */ + push(num: number): void { + // 在尾節點後新增 num + const node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (!this.front) { + this.front = node; + this.rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + this.rear!.next = node; + this.rear = node; + } + this.queSize++; + } + + /* 出列 */ + pop(): number { + const num = this.peek(); + if (!this.front) throw new Error('佇列為空'); + // 刪除頭節點 + this.front = this.front.next; + this.queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek(): number { + if (this.size === 0) throw new Error('佇列為空'); + return this.front!.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray(): number[] { + let node = this.front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const queue = new LinkedListQueue(); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue = ' + queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue = ' + queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts new file mode 100644 index 0000000000..eb9f9d6657 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts @@ -0,0 +1,91 @@ +/** + * File: linkedlist_stack.ts + * Created Time: 2022-12-21 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private stackPeek: ListNode | null; // 將頭節點作為堆疊頂 + private stkSize: number = 0; // 堆疊的長度 + + constructor() { + this.stackPeek = null; + } + + /* 獲取堆疊的長度 */ + get size(): number { + return this.stkSize; + } + + /* 判斷堆疊是否為空 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* 入堆疊 */ + push(num: number): void { + const node = new ListNode(num); + node.next = this.stackPeek; + this.stackPeek = node; + this.stkSize++; + } + + /* 出堆疊 */ + pop(): number { + const num = this.peek(); + if (!this.stackPeek) throw new Error('堆疊為空'); + this.stackPeek = this.stackPeek.next; + this.stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + peek(): number { + if (!this.stackPeek) throw new Error('堆疊為空'); + return this.stackPeek.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray(): number[] { + let node = this.stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new LinkedListStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = ' + stack.toArray()); + +/* 訪問堆疊頂元素 */ +const peek = stack.peek(); +console.log('堆疊頂元素 peek = ' + peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = ' + stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/queue.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/queue.ts new file mode 100644 index 0000000000..8880dcfd17 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/queue.ts @@ -0,0 +1,37 @@ +/** + * File: queue.ts + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化佇列 */ +// TypeScript 沒有內建的佇列,可以把 Array 當作佇列來使用 +const queue: number[] = []; + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue); + +/* 訪問佇列首元素 */ +const peek = queue[0]; +console.log('佇列首元素 peek =', peek); + +/* 元素出列 */ +// 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) +const pop = queue.shift(); +console.log('出列元素 pop =', pop, ',出列後 queue = ', queue); + +/* 獲取佇列的長度 */ +const size = queue.length; +console.log('佇列長度 size =', size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.length === 0; +console.log('佇列是否為空 = ', isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/stack.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/stack.ts new file mode 100644 index 0000000000..0008bfc137 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/stack.ts @@ -0,0 +1,37 @@ +/** + * File: stack.ts + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化堆疊 */ +// TypeScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 +const stack: number[] = []; + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack =', stack); + +/* 訪問堆疊頂元素 */ +const peek = stack[stack.length - 1]; +console.log('堆疊頂元素 peek =', peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop =', pop); +console.log('出堆疊後 stack =', stack); + +/* 獲取堆疊的長度 */ +const size = stack.length; +console.log('堆疊的長度 size =', size); + +/* 判斷是否為空 */ +const isEmpty = stack.length === 0; +console.log('堆疊是否為空 =', isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/array_binary_tree.ts b/zh-hant/codes/typescript/chapter_tree/array_binary_tree.ts new file mode 100644 index 0000000000..96059d5a2d --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/array_binary_tree.ts @@ -0,0 +1,151 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-09 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +type Order = 'pre' | 'in' | 'post'; + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + #tree: (number | null)[]; + + /* 建構子 */ + constructor(arr: (number | null)[]) { + this.#tree = arr; + } + + /* 串列容量 */ + size(): number { + return this.#tree.length; + } + + /* 獲取索引為 i 節點的值 */ + val(i: number): number | null { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + left(i: number): number { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + right(i: number): number { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 層序走訪 */ + levelOrder(): number[] { + let res = []; + // 直接走訪陣列 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度優先走訪 */ + #dfs(i: number, order: Order, res: (number | null)[]): void { + // 若為空位,則返回 + if (this.val(i) === null) return; + // 前序走訪 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序走訪 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 後序走訪 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序走訪 */ + preOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序走訪 */ + inOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 後序走訪 */ + postOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 初始化二元樹 +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n初始化二元樹\n'); +console.log('二元樹的陣列表示:'); +console.log(arr); +console.log('二元樹的鏈結串列表示:'); +printTree(root); + +// 陣列表示下的二元樹類別 +const abt = new ArrayBinaryTree(arr); + +// 訪問節點 +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n當前節點的索引為 ' + i + ' ,值為 ' + abt.val(i)); +console.log( + '其左子節點的索引為 ' + l + ' ,值為 ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + '其右子節點的索引為 ' + r + ' ,值為 ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + '其父節點的索引為 ' + p + ' ,值為 ' + (p === null ? 'null' : abt.val(p)) +); + +// 走訪樹 +let res = abt.levelOrder(); +console.log('\n層序走訪為:' + res); +res = abt.preOrder(); +console.log('前序走訪為:' + res); +res = abt.inOrder(); +console.log('中序走訪為:' + res); +res = abt.postOrder(); +console.log('後序走訪為:' + res); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/avl_tree.ts b/zh-hant/codes/typescript/chapter_tree/avl_tree.ts new file mode 100644 index 0000000000..4c1d8ea8c5 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/avl_tree.ts @@ -0,0 +1,222 @@ +/** + * File: avl_tree.ts + * Created Time: 2023-02-06 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* AVL 樹*/ +class AVLTree { + root: TreeNode; + /* 建構子 */ + constructor() { + this.root = null; //根節點 + } + + /* 獲取節點高度 */ + height(node: TreeNode): number { + // 空節點高度為 -1 ,葉節點高度為 0 + return node === null ? -1 : node.height; + } + + /* 更新節點高度 */ + private updateHeight(node: TreeNode): void { + // 節點高度等於最高子樹高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + balanceFactor(node: TreeNode): number { + // 空節點平衡因子為 0 + if (node === null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return this.height(node.left) - this.height(node.right); + } + + /* 右旋操作 */ + private rightRotate(node: TreeNode): TreeNode { + const child = node.left; + const grandChild = child.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + private leftRotate(node: TreeNode): TreeNode { + const child = node.right; + const grandChild = child.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private rotate(node: TreeNode): TreeNode { + // 獲取節點 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏樹 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.rightRotate(node); + } else { + // 先左旋後右旋 + node.left = this.leftRotate(node.left); + return this.rightRotate(node); + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.leftRotate(node); + } else { + // 先右旋後左旋 + node.right = this.rightRotate(node.right); + return this.leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + insert(val: number): void { + this.root = this.insertHelper(this.root, val); + } + + /* 遞迴插入節點(輔助方法) */ + private insertHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) { + node.left = this.insertHelper(node.left, val); + } else if (val > node.val) { + node.right = this.insertHelper(node.right, val); + } else { + return node; // 重複節點不插入,直接返回 + } + this.updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + remove(val: number): void { + this.root = this.removeHelper(this.root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + private removeHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) { + node.left = this.removeHelper(node.left, val); + } else if (val > node.val) { + node.right = this.removeHelper(node.right, val); + } else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child === null) { + return null; + } else { + // 子節點數量 = 1 ,直接刪除 node + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + search(val: number): TreeNode { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + if (cur.val < val) { + // 目標節點在 cur 的右子樹中 + cur = cur.right; + } else if (cur.val > val) { + // 目標節點在 cur 的左子樹中 + cur = cur.left; + } else { + // 找到目標節點,跳出迴圈 + break; + } + } + // 返回目標節點 + return cur; + } +} + +function testInsert(tree: AVLTree, val: number): void { + tree.insert(val); + console.log('\n插入節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +function testRemove(tree: AVLTree, val: number): void { + tree.remove(val); + console.log('\n刪除節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +/* Driver Code */ +/* 初始化空 AVL 樹 */ +const avlTree = new AVLTree(); +/* 插入節點 */ +// 請關注插入節點後,AVL 樹是如何保持平衡的 +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 插入重複節點 */ +testInsert(avlTree, 7); + +/* 刪除節點 */ +// 請關注刪除節點後,AVL 樹是如何保持平衡的 +testRemove(avlTree, 8); // 刪除度為 0 的節點 +testRemove(avlTree, 5); // 刪除度為 1 的節點 +testRemove(avlTree, 4); // 刪除度為 2 的節點 + +/* 查詢節點 */ +const node = avlTree.search(7); +console.log('\n查詢到的節點物件為', node, ',節點值 = ' + node.val); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_search_tree.ts b/zh-hant/codes/typescript/chapter_tree/binary_search_tree.ts new file mode 100644 index 0000000000..43c883fe5d --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_search_tree.ts @@ -0,0 +1,146 @@ +/** + * File: binary_search_tree.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 二元搜尋樹 */ +class BinarySearchTree { + private root: TreeNode | null; + + /* 建構子 */ + constructor() { + // 初始化空樹 + this.root = null; + } + + /* 獲取二元樹根節點 */ + getRoot(): TreeNode | null { + return this.root; + } + + /* 查詢節點 */ + search(num: number): TreeNode | null { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) cur = cur.left; + // 找到目標節點,跳出迴圈 + else break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + insert(num: number): void { + // 若樹為空,則初始化根節點 + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到重複節點,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子樹中 + else cur = cur.left; + } + // 插入節點 + const node = new TreeNode(num); + if (pre!.val < num) pre!.right = node; + else pre!.left = node; + } + + /* 刪除節點 */ + remove(num: number): void { + // 若樹為空,直接提前返回 + if (this.root === null) return; + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val === num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur === null) return; + // 子節點數量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + const child: TreeNode | null = + cur.left !== null ? cur.left : cur.right; + // 刪除節點 cur + if (cur !== this.root) { + if (pre!.left === cur) pre!.left = child; + else pre!.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + this.root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + let tmp: TreeNode | null = cur.right; + while (tmp!.left !== null) { + tmp = tmp!.left; + } + // 遞迴刪除節點 tmp + this.remove(tmp!.val); + // 用 tmp 覆蓋 cur + cur.val = tmp!.val; + } + } +} + +/* Driver Code */ +/* 初始化二元搜尋樹 */ +const bst = new BinarySearchTree(); +// 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\n初始化的二元樹為\n'); +printTree(bst.getRoot()); + +/* 查詢節點 */ +const node = bst.search(7); +console.log( + '\n查詢到的節點物件為 ' + node + ',節點值 = ' + (node ? node.val : 'null') +); + +/* 插入節點 */ +bst.insert(16); +console.log('\n插入節點 16 後,二元樹為\n'); +printTree(bst.getRoot()); + +/* 刪除節點 */ +bst.remove(1); +console.log('\n刪除節點 1 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\n刪除節點 2 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\n刪除節點 4 後,二元樹為\n'); +printTree(bst.getRoot()); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_tree.ts b/zh-hant/codes/typescript/chapter_tree/binary_tree.ts new file mode 100644 index 0000000000..5710049666 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_tree.ts @@ -0,0 +1,37 @@ +/** + * File: binary_tree.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 初始化二元樹 */ +// 初始化節點 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// 構建節點之間的引用(指標) +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\n初始化二元樹\n'); +printTree(n1); + +/* 插入與刪除節點 */ +const P = new TreeNode(0); +// 在 n1 -> n2 中間插入節點 P +n1.left = P; +P.left = n2; +console.log('\n插入節點 P 後\n'); +printTree(n1); +// 刪除節點 P +n1.left = n2; +console.log('\n刪除節點 P 後\n'); +printTree(n1); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_tree_bfs.ts b/zh-hant/codes/typescript/chapter_tree/binary_tree_bfs.ts new file mode 100644 index 0000000000..143a807679 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -0,0 +1,41 @@ +/** + * File: binary_tree_bfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 層序走訪 */ +function levelOrder(root: TreeNode | null): number[] { + // 初始化佇列,加入根節點 + const queue = [root]; + // 初始化一個串列,用於儲存走訪序列 + const list: number[] = []; + while (queue.length) { + let node = queue.shift() as TreeNode; // 隊列出隊 + list.push(node.val); // 儲存節點值 + if (node.left) { + queue.push(node.left); // 左子節點入列 + } + if (node.right) { + queue.push(node.right); // 右子節點入列 + } + } + return list; +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 層序走訪 */ +const list = levelOrder(root); +console.log('\n層序走訪的節點列印序列 = ' + list); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_tree_dfs.ts b/zh-hant/codes/typescript/chapter_tree/binary_tree_dfs.ts new file mode 100644 index 0000000000..f830bd5e45 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +// 初始化串列,用於儲存走訪序列 +const list: number[] = []; + +/* 前序走訪 */ +function preOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* 中序走訪 */ +function inOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* 後序走訪 */ +function postOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 前序走訪 */ +list.length = 0; +preOrder(root); +console.log('\n前序走訪的節點列印序列 = ' + list); + +/* 中序走訪 */ +list.length = 0; +inOrder(root); +console.log('\n中序走訪的節點列印序列 = ' + list); + +/* 後序走訪 */ +list.length = 0; +postOrder(root); +console.log('\n後序走訪的節點列印序列 = ' + list); + +export {}; diff --git a/zh-hant/codes/typescript/modules/ListNode.ts b/zh-hant/codes/typescript/modules/ListNode.ts new file mode 100644 index 0000000000..63c1f57e97 --- /dev/null +++ b/zh-hant/codes/typescript/modules/ListNode.ts @@ -0,0 +1,28 @@ +/** + * File: ListNode.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 鏈結串列節點 */ +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* 將陣列反序列化為鏈結串列 */ +function arrToLinkedList(arr: number[]): ListNode | null { + const dum: ListNode = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +export { ListNode, arrToLinkedList }; diff --git a/zh-hant/codes/typescript/modules/PrintUtil.ts b/zh-hant/codes/typescript/modules/PrintUtil.ts new file mode 100644 index 0000000000..caa01fbd5e --- /dev/null +++ b/zh-hant/codes/typescript/modules/PrintUtil.ts @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from './ListNode'; +import { TreeNode, arrToTree } from './TreeNode'; + +/* 列印鏈結串列 */ +function printLinkedList(head: ListNode | null): void { + const list: string[] = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +class Trunk { + prev: Trunk | null; + str: string; + + constructor(prev: Trunk | null, str: string) { + this.prev = prev; + this.str = str; + } +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root: TreeNode | null) { + printTreeHelper(root, null, false); +} + +/* 列印二元樹 */ +function printTreeHelper( + root: TreeNode | null, + prev: Trunk | null, + isRight: boolean +) { + if (root === null) { + return; + } + + let prev_str = ' '; + const trunk = new Trunk(prev, prev_str); + + printTreeHelper(root.right, trunk, true); + + if (prev === null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTreeHelper(root.left, trunk, false); +} + +function showTrunks(p: Trunk | null) { + if (p === null) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* 列印堆積 */ +function printHeap(arr: number[]): void { + console.log('堆積的陣列表示:'); + console.log(arr); + console.log('堆積的樹狀表示:'); + const root = arrToTree(arr); + printTree(root); +} + +export { printLinkedList, printTree, printHeap }; diff --git a/zh-hant/codes/typescript/modules/TreeNode.ts b/zh-hant/codes/typescript/modules/TreeNode.ts new file mode 100644 index 0000000000..ff004e78e1 --- /dev/null +++ b/zh-hant/codes/typescript/modules/TreeNode.ts @@ -0,0 +1,37 @@ +/** + * File: TreeNode.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 二元樹節點 */ +class TreeNode { + val: number; // 節點值 + height: number; // 節點高度 + left: TreeNode | null; // 左子節點指標 + right: TreeNode | null; // 右子節點指標 + constructor( + val?: number, + height?: number, + left?: TreeNode | null, + right?: TreeNode | null + ) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } +} + +/* 將陣列反序列化為二元樹 */ +function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +export { TreeNode, arrToTree }; diff --git a/zh-hant/codes/typescript/modules/Vertex.ts b/zh-hant/codes/typescript/modules/Vertex.ts new file mode 100644 index 0000000000..480acf7483 --- /dev/null +++ b/zh-hant/codes/typescript/modules/Vertex.ts @@ -0,0 +1,33 @@ +/** + * File: Vertex.ts + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 頂點類別 */ +class Vertex { + val: number; + constructor(val: number) { + this.val = val; + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static valsToVets(vals: number[]): Vertex[] { + const vets: Vertex[] = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static vetsToVals(vets: Vertex[]): number[] { + const vals: number[] = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +export { Vertex }; diff --git a/zh-hant/codes/typescript/tsconfig.json b/zh-hant/codes/typescript/tsconfig.json new file mode 100644 index 0000000000..954efb8e7f --- /dev/null +++ b/zh-hant/codes/typescript/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "esnext", + "moduleResolution": "node", + "types": ["@types/node"], + "noEmit": true, + "target": "esnext", + }, + "include": ["chapter_*/*.ts"], + "exclude": ["node_modules"] +} diff --git a/zh-hant/codes/zig/.gitignore b/zh-hant/codes/zig/.gitignore new file mode 100644 index 0000000000..8bc911a308 --- /dev/null +++ b/zh-hant/codes/zig/.gitignore @@ -0,0 +1,2 @@ +zig-out/ +zig-cache/ \ No newline at end of file diff --git a/zh-hant/codes/zig/build.zig b/zh-hant/codes/zig/build.zig new file mode 100644 index 0000000000..080f84166d --- /dev/null +++ b/zh-hant/codes/zig/build.zig @@ -0,0 +1,221 @@ +// File: build.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Zig Version: 0.11.0 +// Zig Build Command: zig build -Doptimize=ReleaseSafe +// Zig Run Command: zig build run_* -Doptimize=ReleaseSafe +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const group_name_path = .{ + // Source File: "chapter_computational_complexity/time_complexity.zig" + // Run Command: zig build run_time_complexity -Doptimize=ReleaseSafe + .{ .name = "time_complexity", .path = "chapter_computational_complexity/time_complexity.zig" }, + + // Source File: "chapter_computational_complexity/worst_best_time_complexity.zig" + // Run Command: zig build run_worst_best_time_complexity -Doptimize=ReleaseSafe + .{ .name = "worst_best_time_complexity", .path = "chapter_computational_complexity/worst_best_time_complexity.zig" }, + + // Source File: "chapter_computational_complexity/space_complexity.zig" + // Run Command: zig build run_space_complexity -Doptimize=ReleaseSafe + .{ .name = "space_complexity", .path = "chapter_computational_complexity/space_complexity.zig" }, + + // Source File: "chapter_computational_complexity/iteration.zig" + // Run Command: zig build run_iteration -Doptimize=ReleaseFast + .{ .name = "iteration", .path = "chapter_computational_complexity/iteration.zig" }, + + // Source File: "chapter_computational_complexity/recursion.zig" + // Run Command: zig build run_recursion -Doptimize=ReleaseFast + .{ .name = "recursion", .path = "chapter_computational_complexity/recursion.zig" }, + + // Source File: "chapter_array_and_linkedlist/array.zig" + // Run Command: zig build run_array -Doptimize=ReleaseSafe + .{ .name = "array", .path = "chapter_array_and_linkedlist/array.zig" }, + + // Source File: "chapter_array_and_linkedlist/linked_list.zig" + // Run Command: zig build run_linked_list -Doptimize=ReleaseSafe + .{ .name = "linked_list", .path = "chapter_array_and_linkedlist/linked_list.zig" }, + + // Source File: "chapter_array_and_linkedlist/list.zig" + // Run Command: zig build run_list -Doptimize=ReleaseSafe + .{ .name = "list", .path = "chapter_array_and_linkedlist/list.zig" }, + + // Source File: "chapter_array_and_linkedlist/my_list.zig" + // Run Command: zig build run_my_list -Doptimize=ReleaseSafe + .{ .name = "my_list", .path = "chapter_array_and_linkedlist/my_list.zig" }, + + // Source File: "chapter_stack_and_queue/stack.zig" + // Run Command: zig build run_stack -Doptimize=ReleaseSafe + .{ .name = "stack", .path = "chapter_stack_and_queue/stack.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_stack.zig" + // Run Command: zig build run_linkedlist_stack -Doptimize=ReleaseSafe + .{ .name = "linkedlist_stack", .path = "chapter_stack_and_queue/linkedlist_stack.zig" }, + + // Source File: "chapter_stack_and_queue/array_stack.zig" + // Run Command: zig build run_array_stack -Doptimize=ReleaseSafe + .{ .name = "array_stack", .path = "chapter_stack_and_queue/array_stack.zig" }, + + // Source File: "chapter_stack_and_queue/queue.zig" + // Run Command: zig build run_queue -Doptimize=ReleaseSafe + .{ .name = "queue", .path = "chapter_stack_and_queue/queue.zig" }, + + // Source File: "chapter_stack_and_queue/array_queue.zig" + // Run Command: zig build run_array_queue -Doptimize=ReleaseSafe + .{ .name = "array_queue", .path = "chapter_stack_and_queue/array_queue.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_queue.zig" + // Run Command: zig build run_linkedlist_queue -Doptimize=ReleaseSafe + .{ .name = "linkedlist_queue", .path = "chapter_stack_and_queue/linkedlist_queue.zig" }, + + // Source File: "chapter_stack_and_queue/deque.zig" + // Run Command: zig build run_deque -Doptimize=ReleaseSafe + .{ .name = "deque", .path = "chapter_stack_and_queue/deque.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_deque.zig" + // Run Command: zig build run_linkedlist_deque -Doptimize=ReleaseSafe + .{ .name = "linkedlist_deque", .path = "chapter_stack_and_queue/linkedlist_deque.zig" }, + + // Source File: "chapter_hashing/hash_map.zig" + // Run Command: zig build run_hash_map -Doptimize=ReleaseSafe + .{ .name = "hash_map", .path = "chapter_hashing/hash_map.zig" }, + + // Source File: "chapter_hashing/array_hash_map.zig" + // Run Command: zig build run_array_hash_map -Doptimize=ReleaseSafe + .{ .name = "array_hash_map", .path = "chapter_hashing/array_hash_map.zig" }, + + // Source File: "chapter_tree/binary_tree.zig" + // Run Command: zig build run_binary_tree -Doptimize=ReleaseSafe + .{ .name = "binary_tree", .path = "chapter_tree/binary_tree.zig" }, + + // Source File: "chapter_tree/binary_tree_bfs.zig" + // Run Command: zig build run_binary_tree_bfs -Doptimize=ReleaseSafe + .{ .name = "binary_tree_bfs", .path = "chapter_tree/binary_tree_bfs.zig" }, + + // Source File: "chapter_tree/binary_tree_dfs.zig" + // Run Command: zig build run_binary_tree_dfs -Doptimize=ReleaseSafe + .{ .name = "binary_tree_dfs", .path = "chapter_tree/binary_tree_dfs.zig" }, + + // Source File: "chapter_tree/binary_search_tree.zig" + // Run Command: zig build run_binary_search_tree -Doptimize=ReleaseSafe + .{ .name = "binary_search_tree", .path = "chapter_tree/binary_search_tree.zig" }, + + // Source File: "chapter_tree/avl_tree.zig" + // Run Command: zig build run_avl_tree -Doptimize=ReleaseSafe + .{ .name = "avl_tree", .path = "chapter_tree/avl_tree.zig" }, + + // Source File: "chapter_heap/heap.zig" + // Run Command: zig build run_heap -Doptimize=ReleaseSafe + .{ .name = "heap", .path = "chapter_heap/heap.zig" }, + + // Source File: "chapter_heap/my_heap.zig" + // Run Command: zig build run_my_heap -Doptimize=ReleaseSafe + .{ .name = "my_heap", .path = "chapter_heap/my_heap.zig" }, + + // Source File: "chapter_searching/linear_search.zig" + // Run Command: zig build run_linear_search -Doptimize=ReleaseSafe + .{ .name = "linear_search", .path = "chapter_searching/linear_search.zig" }, + + // Source File: "chapter_searching/binary_search.zig" + // Run Command: zig build run_binary_search -Doptimize=ReleaseSafe + .{ .name = "binary_search", .path = "chapter_searching/binary_search.zig" }, + + // Source File: "chapter_searching/hashing_search.zig" + // Run Command: zig build run_hashing_search -Doptimize=ReleaseSafe + .{ .name = "hashing_search", .path = "chapter_searching/hashing_search.zig" }, + + // Source File: "chapter_searching/two_sum.zig" + // Run Command: zig build run_two_sum -Doptimize=ReleaseSafe + .{ .name = "two_sum", .path = "chapter_searching/two_sum.zig" }, + + // Source File: "chapter_sorting/bubble_sort.zig" + // Run Command: zig build run_bubble_sort -Doptimize=ReleaseSafe + .{ .name = "bubble_sort", .path = "chapter_sorting/bubble_sort.zig" }, + + // Source File: "chapter_sorting/insertion_sort.zig" + // Run Command: zig build run_insertion_sort -Doptimize=ReleaseSafe + .{ .name = "insertion_sort", .path = "chapter_sorting/insertion_sort.zig" }, + + // Source File: "chapter_sorting/quick_sort.zig" + // Run Command: zig build run_quick_sort -Doptimize=ReleaseSafe + .{ .name = "quick_sort", .path = "chapter_sorting/quick_sort.zig" }, + + // Source File: "chapter_sorting/merge_sort.zig" + // Run Command: zig build run_merge_sort -Doptimize=ReleaseSafe + .{ .name = "merge_sort", .path = "chapter_sorting/merge_sort.zig" }, + + // Source File: "chapter_sorting/radix_sort.zig" + // Run Command: zig build run_radix_sort -Doptimize=ReleaseSafe + .{ .name = "radix_sort", .path = "chapter_sorting/radix_sort.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_backtrack.zig" + // Run Command: zig build run_climbing_stairs_backtrack -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_backtrack", .path = "chapter_dynamic_programming/climbing_stairs_backtrack.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_constraint_dp.zig" + // Run Command: zig build run_climbing_stairs_constraint_dp -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_constraint_dp", .path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dfs_mem.zig" + // Run Command: zig build run_climbing_stairs_dfs_mem -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dfs_mem", .path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dfs.zig" + // Run Command: zig build run_climbing_stairs_dfs -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dfs", .path = "chapter_dynamic_programming/climbing_stairs_dfs.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dp.zig" + // Run Command: zig build run_climbing_stairs_dp -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dp", .path = "chapter_dynamic_programming/climbing_stairs_dp.zig" }, + + // Source File: "chapter_dynamic_programming/coin_change_ii.zig" + // Run Command: zig build run_coin_change_ii -Doptimize=ReleaseSafe + .{ .name = "coin_change_ii", .path = "chapter_dynamic_programming/coin_change_ii.zig" }, + + // Source File: "chapter_dynamic_programming/coin_change.zig" + // Run Command: zig build run_coin_change -Doptimize=ReleaseSafe + .{ .name = "coin_change", .path = "chapter_dynamic_programming/coin_change.zig" }, + + // Source File: "chapter_dynamic_programming/edit_distance.zig" + // Run Command: zig build run_edit_distance -Doptimize=ReleaseSafe + .{ .name = "edit_distance", .path = "chapter_dynamic_programming/edit_distance.zig" }, + + // Source File: "chapter_dynamic_programming/knapsack.zig" + // Run Command: zig build run_knapsack -Doptimize=ReleaseSafe + .{ .name = "knapsack", .path = "chapter_dynamic_programming/knapsack.zig" }, + + // Source File: "chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig" + // Run Command: zig build run_min_cost_climbing_stairs_dp -Doptimize=ReleaseSafe + .{ .name = "min_cost_climbing_stairs_dp", .path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig" }, + + // Source File: "chapter_dynamic_programming/min_path_sum.zig" + // Run Command: zig build run_min_path_sum -Doptimize=ReleaseSafe + .{ .name = "min_path_sum", .path = "chapter_dynamic_programming/min_path_sum.zig" }, + + // Source File: "chapter_dynamic_programming/unbounded_knapsack.zig" + // Run Command: zig build run_unbounded_knapsack -Doptimize=ReleaseSafe + .{ .name = "unbounded_knapsack", .path = "chapter_dynamic_programming/unbounded_knapsack.zig" }, + }; + + inline for (group_name_path) |name_path| { + const exe = b.addExecutable(.{ + .name = name_path.name, + .root_source_file = .{ .path = name_path.path }, + .target = target, + .optimize = optimize, + }); + exe.addModule("include", b.addModule("", .{ + .source_file = .{ .path = "include/include.zig" }, + })); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + const run_step = b.step("run_" ++ name_path.name, "Run the app"); + run_step.dependOn(&run_cmd.step); + } +} diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/array.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/array.zig new file mode 100644 index 0000000000..25f2884dc6 --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/array.zig @@ -0,0 +1,117 @@ +// File: array.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 隨機訪問元素 +pub fn randomAccess(nums: []i32) i32 { + // 在區間 [0, nums.len) 中隨機抽取一個整數 + var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len); + // 獲取並返回隨機元素 + var randomNum = nums[randomIndex]; + return randomNum; +} + +// 擴展陣列長度 +pub fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 { + // 初始化一個擴展長度後的陣列 + var res = try mem_allocator.alloc(i32, nums.len + enlarge); + @memset(res, 0); + // 將原陣列中的所有元素複製到新陣列 + std.mem.copy(i32, res, nums); + // 返回擴展後的新陣列 + return res; +} + +// 在陣列的索引 index 處插入元素 num +pub fn insert(nums: []i32, num: i32, index: usize) void { + // 把索引 index 以及之後的所有元素向後移動一位 + var i = nums.len - 1; + while (i > index) : (i -= 1) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +// 刪除索引 index 處的元素 +pub fn remove(nums: []i32, index: usize) void { + // 把索引 index 之後的所有元素向前移動一位 + var i = index; + while (i < nums.len - 1) : (i += 1) { + nums[i] = nums[i + 1]; + } +} + +// 走訪陣列 +pub fn traverse(nums: []i32) void { + var count: i32 = 0; + // 透過索引走訪陣列 + var i: i32 = 0; + while (i < nums.len) : (i += 1) { + count += nums[i]; + } + count = 0; + // 直接走訪陣列元素 + for (nums) |num| { + count += num; + } +} + +// 在陣列中查詢指定元素 +pub fn find(nums: []i32, target: i32) i32 { + for (nums, 0..) |num, i| { + if (num == target) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化陣列 + var arr = [_]i32{0} ** 5; + std.debug.print("陣列 arr = ", .{}); + inc.PrintUtil.printArray(i32, &arr); + + var array = [_]i32{ 1, 3, 2, 5, 4 }; + var known_at_runtime_zero: usize = 0; + var nums = array[known_at_runtime_zero..]; + std.debug.print("\n陣列 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 隨機訪問 + var randomNum = randomAccess(nums); + std.debug.print("\n在 nums 中獲取隨機元素 {}", .{randomNum}); + + // 長度擴展 + nums = try extend(mem_allocator, nums, 3); + std.debug.print("\n將陣列長度擴展至 8 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 插入元素 + insert(nums, 6, 3); + std.debug.print("\n在索引 3 處插入數字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 刪除元素 + remove(nums, 2); + std.debug.print("\n刪除索引 2 處的元素,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 走訪陣列 + traverse(nums); + + // 查詢元素 + var index = find(nums, 3); + std.debug.print("\n在 nums 中查詢元素 3 ,得到索引 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/linked_list.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/linked_list.zig new file mode 100644 index 0000000000..2e255f10b6 --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/linked_list.zig @@ -0,0 +1,84 @@ +// File: linked_list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 在鏈結串列的節點 n0 之後插入節點 P +pub fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void { + var n1 = n0.?.next; + P.?.next = n1; + n0.?.next = P; +} + +// 刪除鏈結串列的節點 n0 之後的首個節點 +pub fn remove(n0: ?*inc.ListNode(i32)) void { + if (n0.?.next == null) return; + // n0 -> P -> n1 + var P = n0.?.next; + var n1 = P.?.next; + n0.?.next = n1; +} + +// 訪問鏈結串列中索引為 index 的節點 +pub fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) { + var head = node; + var i: i32 = 0; + while (i < index) : (i += 1) { + head = head.?.next; + if (head == null) return null; + } + return head; +} + +// 在鏈結串列中查詢值為 target 的首個節點 +pub fn find(node: ?*inc.ListNode(i32), target: i32) i32 { + var head = node; + var index: i32 = 0; + while (head != null) { + if (head.?.val == target) return index; + head = head.?.next; + index += 1; + } + return -1; +} + +// Driver Code +pub fn main() !void { + // 初始化鏈結串列 + // 初始化各個節點 + var n0 = inc.ListNode(i32){.val = 1}; + var n1 = inc.ListNode(i32){.val = 3}; + var n2 = inc.ListNode(i32){.val = 2}; + var n3 = inc.ListNode(i32){.val = 5}; + var n4 = inc.ListNode(i32){.val = 4}; + // 構建節點之間的引用 + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; + std.debug.print("初始化的鏈結串列為", .{}); + try inc.PrintUtil.printLinkedList(i32, &n0); + + // 插入節點 + var tmp = inc.ListNode(i32){.val = 0}; + insert(&n0, &tmp); + std.debug.print("插入節點後的鏈結串列為", .{}); + try inc.PrintUtil.printLinkedList(i32, &n0); + + // 刪除節點 + remove(&n0); + std.debug.print("刪除節點後的鏈結串列為", .{}); + try inc.PrintUtil.printLinkedList(i32, &n0); + + // 訪問節點 + var node = access(&n0, 3); + std.debug.print("鏈結串列中索引 3 處的節點的值 = {}\n", .{node.?.val}); + + // 查詢節點 + var index = find(&n0, 2); + std.debug.print("鏈結串列中值為 2 的節點的索引 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/list.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/list.zig new file mode 100644 index 0000000000..6e2b1401e7 --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/list.zig @@ -0,0 +1,78 @@ +// File: list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化串列 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("串列 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 訪問元素 + var num = nums.items[1]; + std.debug.print("\n訪問索引 1 處的元素,得到 num = {}", .{num}); + + // 更新元素 + nums.items[1] = 0; + std.debug.print("\n將索引 1 處的元素更新為 0 ,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 清空串列 + nums.clearRetainingCapacity(); + std.debug.print("\n清空串列後 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 在尾部新增元素 + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + std.debug.print("\n新增元素後 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 在中間插入元素 + try nums.insert(3, 6); + std.debug.print("\n在索引 3 處插入數字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 刪除元素 + _ = nums.orderedRemove(3); + std.debug.print("\n刪除索引 3 處的元素,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 透過索引走訪串列 + var count: i32 = 0; + var i: i32 = 0; + while (i < nums.items.len) : (i += 1) { + count += nums[i]; + } + // 直接走訪串列元素 + count = 0; + for (nums.items) |x| { + count += x; + } + + // 拼接兩個串列 + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); + std.debug.print("\n將串列 nums1 拼接到 nums 之後,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 排序串列 + std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + std.debug.print("\n排序串列後 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/my_list.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/my_list.zig new file mode 100644 index 0000000000..1e38998db3 --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/my_list.zig @@ -0,0 +1,173 @@ +// File: my_list.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 串列類別 +pub fn MyList(comptime T: type) type { + return struct { + const Self = @This(); + + arr: []T = undefined, // 陣列(儲存串列元素) + arrCapacity: usize = 10, // 串列容量 + numSize: usize = 0, // 串列長度(當前元素數量) + extendRatio: usize = 2, // 每次串列擴容的倍數 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化串列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.arr = try self.mem_allocator.alloc(T, self.arrCapacity); + @memset(self.arr, @as(T, 0)); + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取串列長度(當前元素數量) + pub fn size(self: *Self) usize { + return self.numSize; + } + + // 獲取串列容量 + pub fn capacity(self: *Self) usize { + return self.arrCapacity; + } + + // 訪問元素 + pub fn get(self: *Self, index: usize) T { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 or index >= self.size()) @panic("索引越界"); + return self.arr[index]; + } + + // 更新元素 + pub fn set(self: *Self, index: usize, num: T) void { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 or index >= self.size()) @panic("索引越界"); + self.arr[index] = num; + } + + // 在尾部新增元素 + pub fn add(self: *Self, num: T) !void { + // 元素數量超出容量時,觸發擴容機制 + if (self.size() == self.capacity()) try self.extendCapacity(); + self.arr[self.size()] = num; + // 更新元素數量 + self.numSize += 1; + } + + // 在中間插入元素 + pub fn insert(self: *Self, index: usize, num: T) !void { + if (index < 0 or index >= self.size()) @panic("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (self.size() == self.capacity()) try self.extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + var j = self.size() - 1; + while (j >= index) : (j -= 1) { + self.arr[j + 1] = self.arr[j]; + } + self.arr[index] = num; + // 更新元素數量 + self.numSize += 1; + } + + // 刪除元素 + pub fn remove(self: *Self, index: usize) T { + if (index < 0 or index >= self.size()) @panic("索引越界"); + var num = self.arr[index]; + // 將索引 index 之後的元素都向前移動一位 + var j = index; + while (j < self.size() - 1) : (j += 1) { + self.arr[j] = self.arr[j + 1]; + } + // 更新元素數量 + self.numSize -= 1; + // 返回被刪除的元素 + return num; + } + + // 串列擴容 + pub fn extendCapacity(self: *Self) !void { + // 新建一個長度為 size * extendRatio 的陣列,並將原陣列複製到新陣列 + var newCapacity = self.capacity() * self.extendRatio; + var extend = try self.mem_allocator.alloc(T, newCapacity); + @memset(extend, @as(T, 0)); + // 將原陣列中的所有元素複製到新陣列 + std.mem.copy(T, extend, self.arr); + self.arr = extend; + // 更新串列容量 + self.arrCapacity = newCapacity; + } + + // 將串列轉換為陣列 + pub fn toArray(self: *Self) ![]T { + // 僅轉換有效長度範圍內的串列元素 + var arr = try self.mem_allocator.alloc(T, self.size()); + @memset(arr, @as(T, 0)); + for (arr, 0..) |*num, i| { + num.* = self.get(i); + } + return arr; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化串列 + var nums = MyList(i32){}; + try nums.init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer nums.deinit(); + + // 在尾部新增元素 + try nums.add(1); + try nums.add(3); + try nums.add(2); + try nums.add(5); + try nums.add(4); + std.debug.print("串列 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + std.debug.print(" ,容量 = {} ,長度 = {}", .{nums.capacity(), nums.size()}); + + // 在中間插入元素 + try nums.insert(3, 6); + std.debug.print("\n在索引 3 處插入數字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + + // 刪除元素 + _ = nums.remove(3); + std.debug.print("\n刪除索引 3 處的元素,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + + // 訪問元素 + var num = nums.get(1); + std.debug.print("\n訪問索引 1 處的元素,得到 num = {}", .{num}); + + // 更新元素 + nums.set(1, 0); + std.debug.print("\n將索引 1 處的元素更新為 0 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + + // 測試擴容機制 + var i: i32 = 0; + while (i < 10) : (i += 1) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + try nums.add(i); + } + std.debug.print("\n擴容後的串列 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + std.debug.print(" ,容量 = {} ,長度 = {}\n", .{nums.capacity(), nums.size()}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_computational_complexity/iteration.zig b/zh-hant/codes/zig/chapter_computational_complexity/iteration.zig new file mode 100644 index 0000000000..b458becca4 --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/iteration.zig @@ -0,0 +1,77 @@ +// File: iteration.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com) + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +// for 迴圈 +fn forLoop(n: usize) i32 { + var res: i32 = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (1..n+1) |i| { + res = res + @as(i32, @intCast(i)); + } + return res; +} + +// while 迴圈 +fn whileLoop(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += @intCast(i); + i += 1; + } + return res; +} + +// while 迴圈(兩次更新) +fn whileLoopII(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += @intCast(i); + // 更新條件變數 + i += 1; + i *= 2; + } + return res; +} + +// 雙層 for 迴圈 +fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { + var res = std.ArrayList(u8).init(allocator); + defer res.deinit(); + var buffer: [20]u8 = undefined; + // 迴圈 i = 1, 2, ..., n-1, n + for (1..n+1) |i| { + // 迴圈 j = 1, 2, ..., n-1, n + for (1..n+1) |j| { + var _str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{i, j}); + try res.appendSlice(_str); + } + } + return res.toOwnedSlice(); +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + var res: i32 = 0; + + res = forLoop(n); + std.debug.print("\nfor 迴圈的求和結果 res = {}\n", .{res}); + + res = whileLoop(n); + std.debug.print("\nwhile 迴圈的求和結果 res = {}\n", .{res}); + + res = whileLoopII(n); + std.debug.print("\nwhile 迴圈(兩次更新)求和結果 res = {}\n", .{res}); + + const allocator = std.heap.page_allocator; + const resStr = try nestedForLoop(allocator, n); + std.debug.print("\n雙層 for 迴圈的走訪結果 {s}\n", .{resStr}); +} diff --git a/zh-hant/codes/zig/chapter_computational_complexity/recursion.zig b/zh-hant/codes/zig/chapter_computational_complexity/recursion.zig new file mode 100644 index 0000000000..de2b1d0993 --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/recursion.zig @@ -0,0 +1,78 @@ +// File: recursion.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com) + +const std = @import("std"); + +// 遞迴函式 +fn recur(n: i32) i32 { + // 終止條件 + if (n == 1) { + return 1; + } + // 遞:遞迴呼叫 + var res: i32 = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +// 使用迭代模擬遞迴 +fn forLoopRecur(comptime n: i32) i32 { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + var stack: [n]i32 = undefined; + var res: i32 = 0; + // 遞:遞迴呼叫 + var i: usize = n; + while (i > 0) { + stack[i - 1] = @intCast(i); + i -= 1; + } + // 迴:返回結果 + var index: usize = n; + while (index > 0) { + index -= 1; + res += stack[index]; + } + // res = 1+2+3+...+n + return res; +} + +// 尾遞迴函式 +fn tailRecur(n: i32, res: i32) i32 { + // 終止條件 + if (n == 0) { + return res; + } + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +// 費波那契數列 +fn fib(n: i32) i32 { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 or n == 2) { + return n - 1; + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + var res: i32 = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + var res: i32 = 0; + + res = recur(n); + std.debug.print("\n遞迴函式的求和結果 res = {}\n", .{recur(n)}); + + res = forLoopRecur(n); + std.debug.print("\n使用迭代模擬遞迴的求和結果 res = {}\n", .{forLoopRecur(n)}); + + res = tailRecur(n, 0); + std.debug.print("\n尾遞迴函式的求和結果 res = {}\n", .{tailRecur(n, 0)}); + + res = fib(n); + std.debug.print("\n費波那契數列的第 {} 項為 {}\n", .{n, fib(n)}); +} diff --git a/zh-hant/codes/zig/chapter_computational_complexity/space_complexity.zig b/zh-hant/codes/zig/chapter_computational_complexity/space_complexity.zig new file mode 100644 index 0000000000..9205cc759d --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/space_complexity.zig @@ -0,0 +1,124 @@ +// File: space_complexity.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 函式 +fn function() i32 { + // 執行某些操作 + return 0; +} + +// 常數階 +fn constant(n: i32) void { + // 常數、變數、物件佔用 O(1) 空間 + const a: i32 = 0; + var b: i32 = 0; + var nums = [_]i32{0}**10000; + var node = inc.ListNode(i32){.val = 0}; + var i: i32 = 0; + // 迴圈中的變數佔用 O(1) 空間 + while (i < n) : (i += 1) { + var c: i32 = 0; + _ = c; + } + // 迴圈中的函式佔用 O(1) 空間 + i = 0; + while (i < n) : (i += 1) { + _ = function(); + } + _ = a; + _ = b; + _ = nums; + _ = node; +} + +// 線性階 +fn linear(comptime n: i32) !void { + // 長度為 n 的陣列佔用 O(n) 空間 + var nums = [_]i32{0}**n; + // 長度為 n 的串列佔用 O(n) 空間 + var nodes = std.ArrayList(i32).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + try nodes.append(i); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); + defer map.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); + defer std.heap.page_allocator.free(string); + try map.put(i, string); + } + _ = nums; +} + +// 線性階(遞迴實現) +fn linearRecur(comptime n: i32) void { + std.debug.print("遞迴 n = {}\n", .{n}); + if (n == 1) return; + linearRecur(n - 1); +} + +// 平方階 +fn quadratic(n: i32) !void { + // 二維串列佔用 O(n^2) 空間 + var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + var tmp = std.ArrayList(i32).init(std.heap.page_allocator); + defer tmp.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + try tmp.append(0); + } + try nodes.append(tmp); + } +} + +// 平方階(遞迴實現) +fn quadraticRecur(comptime n: i32) i32 { + if (n <= 0) return 0; + var nums = [_]i32{0}**n; + std.debug.print("遞迴 n = {} 中的 nums 長度 = {}\n", .{n, nums.len}); + return quadraticRecur(n - 1); +} + +// 指數階(建立滿二元樹) +fn buildTree(mem_allocator: std.mem.Allocator, n: i32) !?*inc.TreeNode(i32) { + if (n == 0) return null; + const root = try mem_allocator.create(inc.TreeNode(i32)); + root.init(0); + root.left = try buildTree(mem_allocator, n - 1); + root.right = try buildTree(mem_allocator, n - 1); + return root; +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + // 常數階 + constant(n); + // 線性階 + try linear(n); + linearRecur(n); + // 平方階 + try quadratic(n); + _ = quadraticRecur(n); + // 指數階 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + var root = blk_root: { + const mem_allocator = mem_arena.allocator(); + break :blk_root try buildTree(mem_allocator, n); + }; + try inc.PrintUtil.printTree(root, null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_computational_complexity/time_complexity.zig b/zh-hant/codes/zig/chapter_computational_complexity/time_complexity.zig new file mode 100644 index 0000000000..3e592733d2 --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/time_complexity.zig @@ -0,0 +1,179 @@ +// File: time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 常數階 +fn constant(n: i32) i32 { + _ = n; + var count: i32 = 0; + const size: i32 = 100_000; + var i: i32 = 0; + while(i 0) : (i -= 1) { + var j: usize = 0; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +// 指數階(迴圈實現) +fn exponential(n: i32) i32 { + var count: i32 = 0; + var bas: i32 = 1; + var i: i32 = 0; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + while (i < n) : (i += 1) { + var j: i32 = 0; + while (j < bas) : (j += 1) { + count += 1; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +// 指數階(遞迴實現) +fn expRecur(n: i32) i32 { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +// 對數階(迴圈實現) +fn logarithmic(n: i32) i32 { + var count: i32 = 0; + var n_var = n; + while (n_var > 1) + { + n_var = n_var / 2; + count +=1; + } + return count; +} + +// 對數階(遞迴實現) +fn logRecur(n: i32) i32 { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +// 線性對數階 +fn linearLogRecur(n: i32) i32 { + if (n <= 1) return 1; + var count: i32 = linearLogRecur(n / 2) + linearLogRecur(n / 2); + var i: i32 = 0; + while (i < n) : (i += 1) { + count += 1; + } + return count; +} + +// 階乘階(遞迴實現) +fn factorialRecur(n: i32) i32 { + if (n == 0) return 1; + var count: i32 = 0; + var i: i32 = 0; + // 從 1 個分裂出 n 個 + while (i < n) : (i += 1) { + count += factorialRecur(n - 1); + } + return count; +} + +// Driver Code +pub fn main() !void { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + const n: i32 = 8; + std.debug.print("輸入資料大小 n = {}\n", .{n}); + + var count = constant(n); + std.debug.print("常數階的操作數量 = {}\n", .{count}); + + count = linear(n); + std.debug.print("線性階的操作數量 = {}\n", .{count}); + var nums = [_]i32{0}**n; + count = arrayTraversal(&nums); + std.debug.print("線性階(走訪陣列)的操作數量 = {}\n", .{count}); + + count = quadratic(n); + std.debug.print("平方階的操作數量 = {}\n", .{count}); + for (&nums, 0..) |*num, i| { + num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] + } + count = bubbleSort(&nums); + std.debug.print("平方階(泡沫排序)的操作數量 = {}\n", .{count}); + + count = exponential(n); + std.debug.print("指數階(迴圈實現)的操作數量 = {}\n", .{count}); + count = expRecur(n); + std.debug.print("指數階(遞迴實現)的操作數量 = {}\n", .{count}); + + count = logarithmic(n); + std.debug.print("對數階(迴圈實現)的操作數量 = {}\n", .{count}); + count = logRecur(n); + std.debug.print("對數階(遞迴實現)的操作數量 = {}\n", .{count}); + + count = linearLogRecur(n); + std.debug.print("線性對數階(遞迴實現)的操作數量 = {}\n", .{count}); + + count = factorialRecur(n); + std.debug.print("階乘階(遞迴實現)的操作數量 = {}\n", .{count}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig b/zh-hant/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig new file mode 100644 index 0000000000..8cd57d8470 --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig @@ -0,0 +1,45 @@ +// File: worst_best_time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 +pub fn randomNumbers(comptime n: usize) [n]i32 { + var nums: [n]i32 = undefined; + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (&nums, 0..) |*num, i| { + num.* = @as(i32, @intCast(i)) + 1; + } + // 隨機打亂陣列元素 + const rand = std.crypto.random; + rand.shuffle(i32, &nums); + return nums; +} + +// 查詢陣列 nums 中數字 1 所在索引 +pub fn findOne(nums: []i32) i32 { + for (nums, 0..) |num, i| { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (num == 1) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn main() !void { + var i: i32 = 0; + while (i < 10) : (i += 1) { + const n: usize = 100; + var nums = randomNumbers(n); + var index = findOne(&nums); + std.debug.print("\n陣列 [ 1, 2, ..., n ] 被打亂後 = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + std.debug.print("數字 1 的索引為 {}\n", .{index}); + } + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig new file mode 100644 index 0000000000..16423ed44b --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig @@ -0,0 +1,44 @@ +// File: climbing_stairs_backtrack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 回溯 +fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) { + res.items[0] = res.items[0] + 1; + } + // 走訪所有選擇 + for (choices) |choice| { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) { + continue; + } + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +// 爬樓梯:回溯 +fn climbingStairsBacktrack(n: usize) !i32 { + var choices = [_]i32{ 1, 2 }; // 可選擇向上爬 1 階或 2 階 + var state: i32 = 0; // 從第 0 階開始爬 + var res = std.ArrayList(i32).init(std.heap.page_allocator); + defer res.deinit(); + try res.append(0); // 使用 res[0] 記錄方案數量 + backtrack(&choices, state, @intCast(n), res); + return res.items[0]; +} + +// Driver Code +pub fn main() !void { + var n: usize = 9; + + var res = try climbingStairsBacktrack(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig new file mode 100644 index 0000000000..1213aaa7a1 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig @@ -0,0 +1,35 @@ +// File: climbing_stairs_constraint_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 帶約束爬樓梯:動態規劃 +fn climbingStairsConstraintDP(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsConstraintDP(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig new file mode 100644 index 0000000000..e9edfe4b03 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig @@ -0,0 +1,31 @@ +// File: climbing_stairs_dfs.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 搜尋 +fn dfs(i: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1) + dfs(i - 2); + return count; +} + +// 爬樓梯:搜尋 +fn climbingStairsDFS(comptime n: usize) i32 { + return dfs(n); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFS(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig new file mode 100644 index 0000000000..9faf633fe1 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig @@ -0,0 +1,39 @@ +// File: climbing_stairs_dfs_mem.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 記憶化搜尋 +fn dfs(i: usize, mem: []i32) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +// 爬樓梯:記憶化搜尋 +fn climbingStairsDFSMem(comptime n: usize) i32 { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + var mem = [_]i32{ -1 } ** (n + 1); + return dfs(n, &mem); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFSMem(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig new file mode 100644 index 0000000000..d63da34eae --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig @@ -0,0 +1,51 @@ +// File: climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 爬樓梯:動態規劃 +fn climbingStairsDP(comptime n: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (n == 1 or n == 2) { + return @intCast(n); + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +// 爬樓梯:空間最佳化後的動態規劃 +fn climbingStairsDPComp(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return @intCast(n); + } + var a: i32 = 1; + var b: i32 = 2; + for (3..n + 1) |_| { + var tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDP(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + res = climbingStairsDPComp(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/coin_change.zig b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change.zig new file mode 100644 index 0000000000..eb6b02e021 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change.zig @@ -0,0 +1,77 @@ +// File: coin_change.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 零錢兌換:動態規劃 +fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 狀態轉移:首行首列 + for (1..amt + 1) |a| { + dp[0][a] = max; + } + // 狀態轉移:其餘行和列 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[n][amt] != max) { + return @intCast(dp[n][amt]); + } else { + return -1; + } +} + +// 零錢兌換:空間最佳化後的動態規劃 +fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + @memset(&dp, max); + dp[0] = 0; + // 狀態轉移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[amt] != max) { + return @intCast(dp[amt]); + } else { + return -1; + } +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 4; + + // 動態規劃 + var res = coinChangeDP(&coins, amt); + std.debug.print("湊到目標金額所需的最少硬幣數量為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(&coins, amt); + std.debug.print("湊到目標金額所需的最少硬幣數量為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/coin_change_ii.zig b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change_ii.zig new file mode 100644 index 0000000000..e3d9b71031 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change_ii.zig @@ -0,0 +1,66 @@ +// File: coin_change_ii.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 零錢兌換 II:動態規劃 +fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 初始化首列 + for (0..n + 1) |i| { + dp[i][0] = 1; + } + // 狀態轉移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[n][amt]; +} + +// 零錢兌換 II:空間最佳化後的動態規劃 +fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + dp[0] = 1; + // 狀態轉移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[amt]; +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 5; + + // 動態規劃 + var res = coinChangeIIDP(&coins, amt); + std.debug.print("湊出目標金額的硬幣組合數量為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(&coins, amt); + std.debug.print("湊出目標金額的硬幣組合數量為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/edit_distance.zig b/zh-hant/codes/zig/chapter_dynamic_programming/edit_distance.zig new file mode 100644 index 0000000000..9b89290573 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/edit_distance.zig @@ -0,0 +1,146 @@ +// File: edit_distance.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 編輯距離:暴力搜尋 +fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 and j == 0) { + return 0; + } + // 若 s 為空,則返回 t 長度 + if (i == 0) { + return @intCast(j); + } + // 若 t 為空,則返回 s 長度 + if (j == 0) { + return @intCast(i); + } + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) { + return editDistanceDFS(s, t, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + var insert = editDistanceDFS(s, t, i, j - 1); + var delete = editDistanceDFS(s, t, i - 1, j); + var replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return @min(@min(insert, delete), replace) + 1; +} + +// 編輯距離:記憶化搜尋 +fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 and j == 0) { + return 0; + } + // 若 s 為空,則返回 t 長度 + if (i == 0) { + return @intCast(j); + } + // 若 t 為空,則返回 s 長度 + if (j == 0) { + return @intCast(i); + } + // 若已有記錄,則直接返回之 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) { + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + var insert = editDistanceDFSMem(s, t, mem, i, j - 1); + var delete = editDistanceDFSMem(s, t, mem, i - 1, j); + var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = @min(@min(insert, delete), replace) + 1; + return mem[i][j]; +} + +// 編輯距離:動態規劃 +fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); + // 狀態轉移:首行首列 + for (1..n + 1) |i| { + dp[i][0] = @intCast(i); + } + for (1..m + 1) |j| { + dp[0][j] = @intCast(j); + } + // 狀態轉移:其餘行和列 + for (1..n + 1) |i| { + for (1..m + 1) |j| { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +// 編輯距離:空間最佳化後的動態規劃 +fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_]i32{0} ** (m + 1); + // 狀態轉移:首行 + for (1..m + 1) |j| { + dp[j] = @intCast(j); + } + // 狀態轉移:其餘行 + for (1..n + 1) |i| { + // 狀態轉移:首列 + var leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = @intCast(i); + // 狀態轉移:其餘列 + for (1..m + 1) |j| { + var temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +// Driver Code +pub fn main() !void { + const s = "bag"; + const t = "pack"; + comptime var n = s.len; + comptime var m = t.len; + + // 暴力搜尋 + var res = editDistanceDFS(s, t, n, m); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + // 記憶搜尋 + var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); + res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + // 動態規劃 + res = editDistanceDP(s, t); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/knapsack.zig b/zh-hant/codes/zig/chapter_dynamic_programming/knapsack.zig new file mode 100644 index 0000000000..37fbfc15d5 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/knapsack.zig @@ -0,0 +1,110 @@ +// File: knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 0-1 背包:暴力搜尋 +fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + var no = knapsackDFS(wgt, val, i - 1, c); + var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return @max(no, yes); +} + +// 0-1 背包:記憶化搜尋 +fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + var no = knapsackDFSMem(wgt, val, mem, i - 1, c); + var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = @max(no, yes); + return mem[i][c]; +} + +// 0-1 背包:動態規劃 +fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 狀態轉移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 0-1 背包:空間最佳化後的動態規劃 +fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { + var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 狀態轉移 + for (1..n + 1) |i| { + // 倒序走訪 + var c = cap; + while (c > 0) : (c -= 1) { + if (wgt[i - 1] < c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; + comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; + comptime var cap = 50; + comptime var n = wgt.len; + + // 暴力搜尋 + var res = knapsackDFS(&wgt, &val, n, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 記憶搜尋 + var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); + res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 動態規劃 + res = knapsackDP(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig b/zh-hant/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig new file mode 100644 index 0000000000..97ea392750 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig @@ -0,0 +1,54 @@ +// File: min_cost_climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 爬樓梯最小代價:動態規劃 +fn minCostClimbingStairsDP(comptime cost: []i32) i32 { + comptime var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +// 爬樓梯最小代價:空間最佳化後的動態規劃 +fn minCostClimbingStairsDPComp(cost: []i32) i32 { + var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + var a = cost[1]; + var b = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + var tmp = b; + b = @min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + std.debug.print("輸入樓梯的代價串列為 {any}\n", .{cost}); + + var res = minCostClimbingStairsDP(&cost); + std.debug.print("輸入樓梯的代價串列為 {}\n", .{res}); + + res = minCostClimbingStairsDPComp(&cost); + std.debug.print("輸入樓梯的代價串列為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/min_path_sum.zig b/zh-hant/codes/zig/chapter_dynamic_programming/min_path_sum.zig new file mode 100644 index 0000000000..0c3e49dc83 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/min_path_sum.zig @@ -0,0 +1,122 @@ +// File: min_path_sum.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 最小路徑和:暴力搜尋 +fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { + // 若為左上角單元格,則終止搜尋 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + var up = minPathSumDFS(grid, i - 1, j); + var left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小路徑和:記憶化搜尋 +fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { + // 若為左上角單元格,則終止搜尋 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 若已有記錄,則直接返回 + if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + var up = minPathSumDFSMem(grid, mem, i - 1, j); + var left = minPathSumDFSMem(grid, mem, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小路徑和:動態規劃 +fn minPathSumDP(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_][m]i32{[_]i32{0} ** m} ** n; + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (1..m) |j| { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (1..n) |i| { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (1..n) |i| { + for (1..m) |j| { + dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +// 最小路徑和:空間最佳化後的動態規劃 +fn minPathSumDPComp(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_]i32{0} ** m; + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (1..m) |j| { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (1..n) |i| { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + for (1..m) |j| { + dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +// Driver Code +pub fn main() !void { + comptime var grid = [_][4]i32{ + [_]i32{ 1, 3, 1, 5 }, + [_]i32{ 2, 2, 4, 2 }, + [_]i32{ 5, 3, 2, 1 }, + [_]i32{ 4, 3, 5, 2 }, + }; + comptime var n = grid.len; + comptime var m = grid[0].len; + + // 暴力搜尋 + var res = minPathSumDFS(&grid, n - 1, m - 1); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + // 記憶化搜尋 + var mem = [_][m]i32{[_]i32{-1} ** m} ** n; + res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + // 動態規劃 + res = minPathSumDP(&grid); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(&grid); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig b/zh-hant/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig new file mode 100644 index 0000000000..a890a8071b --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig @@ -0,0 +1,62 @@ +// File: unbounded_knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 完全背包:動態規劃 +fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 狀態轉移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 完全背包:空間最佳化後的動態規劃 +fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 狀態轉移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 1, 2, 3 }; + comptime var val = [_]i32{ 5, 11, 15 }; + comptime var cap = 4; + + // 動態規劃 + var res = unboundedKnapsackDP(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_hashing/array_hash_map.zig b/zh-hant/codes/zig/chapter_hashing/array_hash_map.zig new file mode 100644 index 0000000000..e07fe3a34f --- /dev/null +++ b/zh-hant/codes/zig/chapter_hashing/array_hash_map.zig @@ -0,0 +1,162 @@ +// File: array_hash_map.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 鍵值對 +const Pair = struct { + key: usize = undefined, + val: []const u8 = undefined, + + pub fn init(key: usize, val: []const u8) Pair { + return Pair { + .key = key, + .val = val, + }; + } +}; + +// 基於陣列實現的雜湊表 +pub fn ArrayHashMap(comptime T: type) type { + return struct { + bucket: ?std.ArrayList(?T) = null, + mem_allocator: std.mem.Allocator = undefined, + + const Self = @This(); + + // 建構子 + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + self.mem_allocator = allocator; + // 初始化一個長度為 100 的桶(陣列) + self.bucket = std.ArrayList(?T).init(self.mem_allocator); + var i: i32 = 0; + while (i < 100) : (i += 1) { + try self.bucket.?.append(null); + } + } + + // 析構函式 + pub fn deinit(self: *Self) void { + if (self.bucket != null) self.bucket.?.deinit(); + } + + // 雜湊函式 + fn hashFunc(key: usize) usize { + var index = key % 100; + return index; + } + + // 查詢操作 + pub fn get(self: *Self, key: usize) []const u8 { + var index = hashFunc(key); + var pair = self.bucket.?.items[index]; + return pair.?.val; + } + + // 新增操作 + pub fn put(self: *Self, key: usize, val: []const u8) !void { + var pair = Pair.init(key, val); + var index = hashFunc(key); + self.bucket.?.items[index] = pair; + } + + // 刪除操作 + pub fn remove(self: *Self, key: usize) !void { + var index = hashFunc(key); + // 置為 null ,代表刪除 + self.bucket.?.items[index] = null; + } + + // 獲取所有鍵值對 + pub fn pairSet(self: *Self) !std.ArrayList(T) { + var entry_set = std.ArrayList(T).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try entry_set.append(item.?); + } + return entry_set; + } + + // 獲取所有鍵 + pub fn keySet(self: *Self) !std.ArrayList(usize) { + var key_set = std.ArrayList(usize).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try key_set.append(item.?.key); + } + return key_set; + } + + // 獲取所有值 + pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { + var value_set = std.ArrayList([]const u8).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try value_set.append(item.?.val); + } + return value_set; + } + + // 列印雜湊表 + pub fn print(self: *Self) !void { + var entry_set = try self.pairSet(); + defer entry_set.deinit(); + for (entry_set.items) |item| { + std.debug.print("{} -> {s}\n", .{item.key, item.val}); + } + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化雜湊表 + var map = ArrayHashMap(Pair){}; + try map.init(std.heap.page_allocator); + defer map.deinit(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, value) + try map.put(12836, "小哈"); + try map.put(15937, "小囉"); + try map.put(16750, "小算"); + try map.put(13276, "小法"); + try map.put(10583, "小鴨"); + std.debug.print("\n新增完成後,雜湊表為\nKey -> Value\n", .{}); + try map.print(); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 value + var name = map.get(15937); + std.debug.print("\n輸入學號 15937 ,查詢到姓名 {s}\n", .{name}); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, value) + try map.remove(10583); + std.debug.print("\n刪除 10583 後,雜湊表為\nKey -> Value\n", .{}); + try map.print(); + + // 走訪雜湊表 + std.debug.print("\n走訪鍵值對 Key->Value\n", .{}); + var entry_set = try map.pairSet(); + for (entry_set.items) |kv| { + std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); + } + defer entry_set.deinit(); + std.debug.print("\n單獨走訪鍵 Key\n", .{}); + var key_set = try map.keySet(); + for (key_set.items) |key| { + std.debug.print("{}\n", .{key}); + } + defer key_set.deinit(); + std.debug.print("\n單獨走訪值 value\n", .{}); + var value_set = try map.valueSet(); + for (value_set.items) |val| { + std.debug.print("{s}\n", .{val}); + } + defer value_set.deinit(); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_hashing/hash_map.zig b/zh-hant/codes/zig/chapter_hashing/hash_map.zig new file mode 100644 index 0000000000..7446fb3f9c --- /dev/null +++ b/zh-hant/codes/zig/chapter_hashing/hash_map.zig @@ -0,0 +1,54 @@ +// File: hash_map.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化雜湊表 + var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer map.deinit(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, value) + try map.put(12836, "小哈"); + try map.put(15937, "小囉"); + try map.put(16750, "小算"); + try map.put(13276, "小法"); + try map.put(10583, "小鴨"); + std.debug.print("\n新增完成後,雜湊表為\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 value + var name = map.get(15937).?; + std.debug.print("\n輸入學號 15937 ,查詢到姓名 {s}\n", .{name}); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, value) + _ = map.remove(10583); + std.debug.print("\n刪除 10583 後,雜湊表為\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // 走訪雜湊表 + std.debug.print("\n走訪鍵值對 Key->Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + std.debug.print("\n單獨走訪鍵 Key\n", .{}); + var it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{}\n", .{kv.key_ptr.*}); + } + + std.debug.print("\n單獨走訪值 value\n", .{}); + it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{s}\n", .{kv.value_ptr.*}); + } + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_heap/heap.zig b/zh-hant/codes/zig/chapter_heap/heap.zig new file mode 100644 index 0000000000..95e1115c20 --- /dev/null +++ b/zh-hant/codes/zig/chapter_heap/heap.zig @@ -0,0 +1,80 @@ +// File: heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +fn lessThan(context: void, a: i32, b: i32) std.math.Order { + _ = context; + return std.math.order(a, b); +} + +fn greaterThan(context: void, a: i32, b: i32) std.math.Order { + return lessThan(context, a, b).invert(); +} + +fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { + try heap.add(val); //元素入堆積 + std.debug.print("\n元素 {} 入堆積後\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { + var val = heap.remove(); //堆積頂元素出堆積 + std.debug.print("\n堆積頂元素 {} 出堆積後\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化堆積 + // 初始化小頂堆積 + const PQlt = std.PriorityQueue(i32, void, lessThan); + var min_heap = PQlt.init(std.heap.page_allocator, {}); + defer min_heap.deinit(); + // 初始化大頂堆積 + const PQgt = std.PriorityQueue(i32, void, greaterThan); + var max_heap = PQgt.init(std.heap.page_allocator, {}); + defer max_heap.deinit(); + + std.debug.print("\n以下測試樣例為大頂堆積", .{}); + + // 元素入堆積 + try testPush(i32, mem_allocator, &max_heap, 1); + try testPush(i32, mem_allocator, &max_heap, 3); + try testPush(i32, mem_allocator, &max_heap, 2); + try testPush(i32, mem_allocator, &max_heap, 5); + try testPush(i32, mem_allocator, &max_heap, 4); + + // 獲取堆積頂元素 + var peek = max_heap.peek().?; + std.debug.print("\n堆積頂元素為 {}\n", .{peek}); + + // 堆積頂元素出堆積 + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + + // 獲取堆積的大小 + var size = max_heap.len; + std.debug.print("\n堆積元素數量為 {}\n", .{size}); + + // 判斷堆積是否為空 + var is_empty = if (max_heap.len == 0) true else false; + std.debug.print("\n堆積是否為空 {}\n", .{is_empty}); + + // 輸入串列並建堆積 + try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("\n輸入串列並建立小頂堆積後\n", .{}); + try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_heap/my_heap.zig b/zh-hant/codes/zig/chapter_heap/my_heap.zig new file mode 100644 index 0000000000..e3118bd34d --- /dev/null +++ b/zh-hant/codes/zig/chapter_heap/my_heap.zig @@ -0,0 +1,186 @@ +// File: my_heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 堆積類別簡易實現 +pub fn MaxHeap(comptime T: type) type { + return struct { + const Self = @This(); + + max_heap: ?std.ArrayList(T) = null, // 使用串列而非陣列,這樣無須考慮擴容問題 + + // 建構子,根據輸入串列建堆積 + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { + if (self.max_heap != null) return; + self.max_heap = std.ArrayList(T).init(allocator); + // 將串列元素原封不動新增進堆積 + try self.max_heap.?.appendSlice(nums); + // 堆積化除葉節點以外的其他所有節點 + var i: usize = parent(self.size() - 1) + 1; + while (i > 0) : (i -= 1) { + try self.siftDown(i - 1); + } + } + + // 析構方法,釋放記憶體 + pub fn deinit(self: *Self) void { + if (self.max_heap != null) self.max_heap.?.deinit(); + } + + // 獲取左子節點的索引 + fn left(i: usize) usize { + return 2 * i + 1; + } + + // 獲取右子節點的索引 + fn right(i: usize) usize { + return 2 * i + 2; + } + + // 獲取父節點的索引 + fn parent(i: usize) usize { + // return (i - 1) / 2; // 向下整除 + return @divFloor(i - 1, 2); + } + + // 交換元素 + fn swap(self: *Self, i: usize, j: usize) !void { + var tmp = self.max_heap.?.items[i]; + try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); + try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); + } + + // 獲取堆積大小 + pub fn size(self: *Self) usize { + return self.max_heap.?.items.len; + } + + // 判斷堆積是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問堆積頂元素 + pub fn peek(self: *Self) T { + return self.max_heap.?.items[0]; + } + + // 元素入堆積 + pub fn push(self: *Self, val: T) !void { + // 新增節點 + try self.max_heap.?.append(val); + // 從底至頂堆積化 + try self.siftUp(self.size() - 1); + } + + // 從節點 i 開始,從底至頂堆積化 + fn siftUp(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 獲取節點 i 的父節點 + var p = parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; + // 交換兩節點 + try self.swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + // 元素出堆積 + pub fn pop(self: *Self) !T { + // 判斷處理 + if (self.isEmpty()) unreachable; + // 交換根節點與最右葉節點(交換首元素與尾元素) + try self.swap(0, self.size() - 1); + // 刪除節點 + var val = self.max_heap.?.pop(); + // 從頂至底堆積化 + try self.siftDown(0); + // 返回堆積頂元素 + return val; + } + + // 從節點 i 開始,從頂至底堆積化 + fn siftDown(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + var l = left(i); + var r = right(i); + var ma = i; + if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; + if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break; + // 交換兩節點 + try self.swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + fn lessThan(context: void, a: T, b: T) std.math.Order { + _ = context; + return std.math.order(a, b); + } + + fn greaterThan(context: void, a: T, b: T) std.math.Order { + return lessThan(context, a, b).invert(); + } + + // 列印堆積(二元樹) + pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { + const PQgt = std.PriorityQueue(T, void, greaterThan); + var queue = PQgt.init(std.heap.page_allocator, {}); + defer queue.deinit(); + try queue.addSlice(self.max_heap.?.items); + try inc.PrintUtil.printHeap(T, mem_allocator, queue); + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化大頂堆積 + var max_heap = MaxHeap(i32){}; + try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); + defer max_heap.deinit(); + std.debug.print("\n輸入串列並建堆積後\n", .{}); + try max_heap.print(mem_allocator); + + // 獲取堆積頂元素 + var peek = max_heap.peek(); + std.debug.print("\n堆積頂元素為 {}\n", .{peek}); + + // 元素入堆積 + const val = 7; + try max_heap.push(val); + std.debug.print("\n元素 {} 入堆積後\n", .{val}); + try max_heap.print(mem_allocator); + + // 堆積頂元素出堆積 + peek = try max_heap.pop(); + std.debug.print("\n堆積頂元素 {} 出堆積後\n", .{peek}); + try max_heap.print(mem_allocator); + + // 獲取堆積的大小 + var size = max_heap.size(); + std.debug.print("\n堆積元素數量為 {}", .{size}); + + // 判斷堆積是否為空 + var is_empty = max_heap.isEmpty(); + std.debug.print("\n堆積是否為空 {}\n", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_searching/binary_search.zig b/zh-hant/codes/zig/chapter_searching/binary_search.zig new file mode 100644 index 0000000000..4e1521e190 --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/binary_search.zig @@ -0,0 +1,64 @@ +// File: binary_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二分搜尋(雙閉區間) +fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + var i: usize = 0; + var j: usize = nums.items.len - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + var m = i + (j - i) / 2; // 計算中點索引 m + if (nums.items[m] < target) { // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + } else { // 找到目標元素,返回其索引 + return @intCast(m); + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +// 二分搜尋(左閉右開區間) +fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + var i: usize = 0; + var j: usize = nums.items.len; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i <= j) { + var m = i + (j - i) / 2; // 計算中點索引 m + if (nums.items[m] < target) { // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情況說明 target 在區間 [i, m) 中 + j = m; + } else { // 找到目標元素,返回其索引 + return @intCast(m); + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 6; + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); + + // 二分搜尋(雙閉區間) + var index = binarySearch(i32, nums, target); + std.debug.print("目標元素 6 的索引 = {}\n", .{index}); + + // 二分搜尋(左閉右開區間) + index = binarySearchLCRO(i32, nums, target); + std.debug.print("目標元素 6 的索引 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_searching/hashing_search.zig b/zh-hant/codes/zig/chapter_searching/hashing_search.zig new file mode 100644 index 0000000000..cc381782df --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/hashing_search.zig @@ -0,0 +1,57 @@ +// File: hashing_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 雜湊查詢(陣列) +fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if (map.getKey(target) == null) return -1; + return map.get(target).?; +} + +// 雜湊查詢(鏈結串列) +fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + if (map.getKey(target) == null) return null; + return map.get(target); +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // 雜湊查詢(陣列) + var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // 初始化雜湊表 + var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer map.deinit(); + for (nums, 0..) |num, i| { + try map.put(num, @as(i32, @intCast(i))); // key: 元素,value: 索引 + } + var index = hashingSearchArray(i32, map, target); + std.debug.print("目標元素 3 的索引 = {}\n", .{index}); + + // 雜湊查詢(鏈結串列) + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); + // 初始化雜湊表 + var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); + defer map1.deinit(); + while (head != null) { + try map1.put(head.?.val, head.?); + head = head.?.next; + } + var node = hashingSearchLinkedList(i32, map1, target); + std.debug.print("目標節點值 3 的對應節點物件為 ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_searching/linear_search.zig b/zh-hant/codes/zig/chapter_searching/linear_search.zig new file mode 100644 index 0000000000..b69123a255 --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/linear_search.zig @@ -0,0 +1,54 @@ +// File: linear_search.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 線性查詢(陣列) +fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 走訪陣列 + for (nums.items, 0..) |num, i| { + // 找到目標元素, 返回其索引 + if (num == target) { + return @intCast(i); + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +// 線性查詢(鏈結串列) +pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { + var head = node; + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.?.val == target) return head; + head = head.?.next; + } + return null; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // 在陣列中執行線性查詢 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); + var index = linearSearchArray(i32, nums, target); + std.debug.print("目標元素 3 的索引 = {}\n", .{index}); + + // 在鏈結串列中執行線性查詢 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); + var node = linearSearchLinkedList(i32, head, target); + std.debug.print("目標節點值 3 的對應節點物件為 ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_searching/two_sum.zig b/zh-hant/codes/zig/chapter_searching/two_sum.zig new file mode 100644 index 0000000000..30e4a82309 --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/two_sum.zig @@ -0,0 +1,58 @@ +// File: two_sum.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 方法一:暴力列舉 +pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { + var size: usize = nums.len; + var i: usize = 0; + // 兩層迴圈,時間複雜度為 O(n^2) + while (i < size - 1) : (i += 1) { + var j = i + 1; + while (j < size) : (j += 1) { + if (nums[i] + nums[j] == target) { + return [_]i32{@intCast(i), @intCast(j)}; + } + } + } + return null; +} + +// 方法二:輔助雜湊表 +pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { + var size: usize = nums.len; + // 輔助雜湊表,空間複雜度為 O(n) + var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer dic.deinit(); + var i: usize = 0; + // 單層迴圈,時間複雜度為 O(n) + while (i < size) : (i += 1) { + if (dic.contains(target - nums[i])) { + return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; + } + try dic.put(nums[i], @intCast(i)); + } + return null; +} + + +pub fn main() !void { + // ======= Test Case ======= + var nums = [_]i32{ 2, 7, 11, 15 }; + var target: i32 = 9; + + // ====== Driver Code ====== + // 方法一 + var res = twoSumBruteForce(&nums, target).?; + std.debug.print("方法一 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + // 方法二 + res = (try twoSumHashTable(&nums, target)).?; + std.debug.print("\n方法二 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_sorting/bubble_sort.zig b/zh-hant/codes/zig/chapter_sorting/bubble_sort.zig new file mode 100644 index 0000000000..95fa15d6ec --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/bubble_sort.zig @@ -0,0 +1,61 @@ +// File: bubble_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 泡沫排序 +fn bubbleSort(nums: []i32) void { + // 外迴圈:未排序區間為 [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +// 泡沫排序(標誌最佳化) +fn bubbleSortWithFlag(nums: []i32) void { + // 外迴圈:未排序區間為 [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var flag = false; // 初始化標誌位 + var j: usize = 0; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSort(&nums); + std.debug.print("泡沫排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(&nums1); + std.debug.print("\n泡沫排序完成後 nums1 = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_sorting/insertion_sort.zig b/zh-hant/codes/zig/chapter_sorting/insertion_sort.zig new file mode 100644 index 0000000000..4d34176e04 --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/insertion_sort.zig @@ -0,0 +1,31 @@ +// File: insertion_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 插入排序 +fn insertionSort(nums: []i32) void { + // 外迴圈:已排序區間為 [0, i-1] + var i: usize = 1; + while (i < nums.len) : (i += 1) { + var base = nums[i]; + var j: usize = i; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 1 and nums[j - 1] > base) : (j -= 1) { + nums[j] = nums[j - 1]; // 將 nums[j] 向右移動一位 + } + nums[j] = base; // 將 base 賦值到正確位置 + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + insertionSort(&nums); + std.debug.print("插入排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_sorting/merge_sort.zig b/zh-hant/codes/zig/chapter_sorting/merge_sort.zig new file mode 100644 index 0000000000..c60434fef3 --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/merge_sort.zig @@ -0,0 +1,67 @@ +// File: merge_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 合併左子陣列和右子陣列 +// 左子陣列區間 [left, mid] +// 右子陣列區間 [mid + 1, right] +fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { + // 初始化輔助陣列 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var tmp = try mem_allocator.alloc(i32, right + 1 - left); + std.mem.copy(i32, tmp, nums[left..right+1]); + // 左子陣列的起始索引和結束索引 + var leftStart = left - left; + var leftEnd = mid - left; + // 右子陣列的起始索引和結束索引 + var rightStart = mid + 1 - left; + var rightEnd = right - left; + // i, j 分別指向左子陣列、右子陣列的首元素 + var i = leftStart; + var j = rightStart; + // 透過覆蓋原陣列 nums 來合併左子陣列和右子陣列 + var k = left; + while (k <= right) : (k += 1) { + // 若“左子陣列已全部合併完”,則選取右子陣列元素,並且 j++ + if (i > leftEnd) { + nums[k] = tmp[j]; + j += 1; + // 否則,若“右子陣列已全部合併完”或“左子陣列元素 <= 右子陣列元素”,則選取左子陣列元素,並且 i++ + } else if (j > rightEnd or tmp[i] <= tmp[j]) { + nums[k] = tmp[i]; + i += 1; + // 否則,若“左右子陣列都未全部合併完”且“左子陣列元素 > 右子陣列元素”,則選取右子陣列元素,並且 j++ + } else { + nums[k] = tmp[j]; + j += 1; + } + } +} + +// 合併排序 +fn mergeSort(nums: []i32, left: usize, right: usize) !void { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + var mid = left + (right - left) / 2; // 計算中點 + try mergeSort(nums, left, mid); // 遞迴左子陣列 + try mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + try merge(nums, left, mid, right); +} + +// Driver Code +pub fn main() !void { + // 合併排序 + var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; + try mergeSort(&nums, 0, nums.len - 1); + std.debug.print("合併排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_sorting/quick_sort.zig b/zh-hant/codes/zig/chapter_sorting/quick_sort.zig new file mode 100644 index 0000000000..4c7bb59deb --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/quick_sort.zig @@ -0,0 +1,162 @@ +// File: quick_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 快速排序類別 +const QuickSort = struct { + + // 元素交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 哨兵劃分 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 以 nums[left] 為基準數 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + // 快速排序 + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + var pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// 快速排序類別(中位基準數最佳化) +const QuickSortMedian = struct { + + // 元素交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 選取三個候選元素的中位數 + pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { + var l = nums[left]; + var m = nums[mid]; + var 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; + } + + // 哨兵劃分(三數取中值) + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 選取三個候選元素的中位數 + var med = medianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + swap(nums, left, med); + // 以 nums[left] 為基準數 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + // 快速排序 + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + var pivot = partition(nums, left, right); + if (pivot == 0) return; + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// 快速排序類別(尾遞迴最佳化) +const QuickSortTailCall = struct { + + // 元素交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 哨兵劃分 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 以 nums[left] 為基準數 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + // 快速排序(尾遞迴最佳化) + pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { + var left = left_; + var right = right_; + // 子陣列長度為 1 時終止遞迴 + while (left < right) { + // 哨兵劃分操作 + var pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +}; + +// Driver Code +pub fn main() !void { + // 快速排序 + var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(&nums, 0, nums.len - 1); + std.debug.print("快速排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + // 快速排序(中位基準數最佳化) + var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); + std.debug.print("\n快速排序(中位基準數最佳化)完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + // 快速排序(尾遞迴最佳化) + var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); + std.debug.print("\n快速排序(尾遞迴最佳化)完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums2); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_sorting/radix_sort.zig b/zh-hant/codes/zig/chapter_sorting/radix_sort.zig new file mode 100644 index 0000000000..9f0424b582 --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/radix_sort.zig @@ -0,0 +1,77 @@ +// File: radix_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) +fn digit(num: i32, exp: i32) i32 { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return @mod(@divFloor(num, exp), 10); +} + +// 計數排序(根據 nums 第 k 位排序) +fn countingSortDigit(nums: []i32, exp: i32) !void { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var counter = try mem_allocator.alloc(usize, 10); + @memset(counter, 0); + var n = nums.len; + // 統計 0~9 各數字的出現次數 + for (nums) |num| { + var d: u32 = @bitCast(digit(num, exp)); // 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + var i: usize = 1; + while (i < 10) : (i += 1) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + var res = try mem_allocator.alloc(i32, n); + i = n - 1; + while (i >= 0) : (i -= 1) { + var d: u32 = @bitCast(digit(nums[i], exp)); + var j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d] -= 1; // 將 d 的數量減 1 + if (i == 0) break; + } + // 使用結果覆蓋原陣列 nums + i = 0; + while (i < n) : (i += 1) { + nums[i] = res[i]; + } +} + +// 基數排序 +fn radixSort(nums: []i32) !void { + // 獲取陣列的最大元素,用於判斷最大位數 + var m: i32 = std.math.minInt(i32); + for (nums) |num| { + if (num > m) m = num; + } + // 按照從低位到高位的順序走訪 + var exp: i32 = 1; + while (exp <= m) : (exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + try countingSortDigit(nums, exp); + } +} + +// Driver Code +pub fn main() !void { + // 基數排序 + var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; + try radixSort(&nums); + std.debug.print("基數排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/array_queue.zig b/zh-hant/codes/zig/chapter_stack_and_queue/array_queue.zig new file mode 100644 index 0000000000..323a0e3c18 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/array_queue.zig @@ -0,0 +1,140 @@ +// File: array_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於環形陣列實現的佇列 +pub fn ArrayQueue(comptime T: type) type { + return struct { + const Self = @This(); + + nums: []T = undefined, // 用於儲存佇列元素的陣列 + cap: usize = 0, // 佇列容量 + front: usize = 0, // 佇列首指標,指向佇列首元素 + queSize: usize = 0, // 尾指標,指向佇列尾 + 1 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化陣列) + pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.cap = cap; + self.nums = try self.mem_allocator.alloc(T, self.cap); + @memset(self.nums, @as(T, 0)); + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取佇列的容量 + pub fn capacity(self: *Self) usize { + return self.cap; + } + + // 獲取佇列的長度 + pub fn size(self: *Self) usize { + return self.queSize; + } + + // 判斷佇列是否為空 + pub fn isEmpty(self: *Self) bool { + return self.queSize == 0; + } + + // 入列 + pub fn push(self: *Self, num: T) !void { + if (self.size() == self.capacity()) { + std.debug.print("佇列已滿\n", .{}); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + var rear = (self.front + self.queSize) % self.capacity(); + // 在尾節點後新增 num + self.nums[rear] = num; + self.queSize += 1; + } + + // 出列 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + self.front = (self.front + 1) % self.capacity(); + self.queSize -= 1; + return num; + } + + // 訪問佇列首元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("佇列為空"); + return self.nums[self.front]; + } + + // 返回陣列 + pub fn toArray(self: *Self) ![]T { + // 僅轉換有效長度範圍內的串列元素 + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + var j: usize = self.front; + while (i < self.size()) : ({ i += 1; j += 1; }) { + res[i] = self.nums[j % self.capacity()]; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化佇列 + var capacity: usize = 10; + var queue = ArrayQueue(i32){}; + try queue.init(std.heap.page_allocator, capacity); + defer queue.deinit(); + + // 元素入列 + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("佇列 queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 訪問佇列首元素 + var peek = queue.peek(); + std.debug.print("\n佇列首元素 peek = {}", .{peek}); + + // 元素出列 + var pop = queue.pop(); + std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 獲取佇列的長度 + var size = queue.size(); + std.debug.print("\n佇列長度 size = {}", .{size}); + + // 判斷佇列是否為空 + var is_empty = queue.isEmpty(); + std.debug.print("\n佇列是否為空 = {}", .{is_empty}); + + // 測試環形陣列 + var i: i32 = 0; + while (i < 10) : (i += 1) { + try queue.push(i); + _ = queue.pop(); + std.debug.print("\n第 {} 輪入列 + 出列後 queue = ", .{i}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + } + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/array_stack.zig b/zh-hant/codes/zig/chapter_stack_and_queue/array_stack.zig new file mode 100644 index 0000000000..609cd1459e --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/array_stack.zig @@ -0,0 +1,97 @@ +// File: array_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於陣列實現的堆疊 +pub fn ArrayStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack: ?std.ArrayList(T) = null, + + // 建構子(分配記憶體+初始化堆疊) + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.stack == null) { + self.stack = std.ArrayList(T).init(allocator); + } + } + + // 析構方法(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.stack == null) return; + self.stack.?.deinit(); + } + + // 獲取堆疊的長度 + pub fn size(self: *Self) usize { + return self.stack.?.items.len; + } + + // 判斷堆疊是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問堆疊頂元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("堆疊為空"); + return self.stack.?.items[self.size() - 1]; + } + + // 入堆疊 + pub fn push(self: *Self, num: T) !void { + try self.stack.?.append(num); + } + + // 出堆疊 + pub fn pop(self: *Self) T { + var num = self.stack.?.pop(); + return num; + } + + // 返回 ArrayList + pub fn toList(self: *Self) std.ArrayList(T) { + return self.stack.?; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化堆疊 + var stack = ArrayStack(i32){}; + stack.init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer stack.deinit(); + + // 元素入堆疊 + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("堆疊 stack = ", .{}); + inc.PrintUtil.printList(i32, stack.toList()); + + // 訪問堆疊頂元素 + var peek = stack.peek(); + std.debug.print("\n堆疊頂元素 peek = {}", .{peek}); + + // 元素出堆疊 + var top = stack.pop(); + std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{top}); + inc.PrintUtil.printList(i32, stack.toList()); + + // 獲取堆疊的長度 + var size = stack.size(); + std.debug.print("\n堆疊的長度 size = {}", .{size}); + + // 判斷堆疊是否為空 + var is_empty = stack.isEmpty(); + std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/deque.zig b/zh-hant/codes/zig/chapter_stack_and_queue/deque.zig new file mode 100644 index 0000000000..a6ca594a12 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/deque.zig @@ -0,0 +1,51 @@ +// File: deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化雙向佇列 + const L = std.TailQueue(i32); + var deque = L{}; + + // 元素入列 + var node1 = L.Node{ .data = 2 }; + var node2 = L.Node{ .data = 5 }; + var node3 = L.Node{ .data = 4 }; + var node4 = L.Node{ .data = 3 }; + var node5 = L.Node{ .data = 1 }; + deque.append(&node1); // 新增至佇列尾 + deque.append(&node2); + deque.append(&node3); + deque.prepend(&node4); // 新增至佇列首 + deque.prepend(&node5); + std.debug.print("雙向佇列 deque = ", .{}); + inc.PrintUtil.printQueue(i32, deque); + + // 訪問元素 + var peek_first = deque.first.?.data; // 佇列首元素 + std.debug.print("\n佇列首元素 peek_first = {}", .{peek_first}); + var peek_last = deque.last.?.data; // 佇列尾元素 + std.debug.print("\n佇列尾元素 peek_last = {}", .{peek_last}); + + // 元素出列 + var pop_first = deque.popFirst().?.data; // 佇列首元素出列 + std.debug.print("\n佇列首出列元素 pop_first = {},佇列首出列後 deque = ", .{pop_first}); + inc.PrintUtil.printQueue(i32, deque); + var pop_last = deque.pop().?.data; // 佇列尾元素出列 + std.debug.print("\n佇列尾出列元素 pop_last = {},佇列尾出列後 deque = ", .{pop_last}); + inc.PrintUtil.printQueue(i32, deque); + + // 獲取雙向佇列的長度 + var size = deque.len; + std.debug.print("\n雙向佇列長度 size = {}", .{size}); + + // 判斷雙向佇列是否為空 + var is_empty = if (deque.len == 0) true else false; + std.debug.print("\n雙向佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig new file mode 100644 index 0000000000..45639b4378 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig @@ -0,0 +1,207 @@ +// File: linkedlist_deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 雙向鏈結串列節點 +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // 節點值 + next: ?*Self = null, // 後繼節點指標 + prev: ?*Self = null, // 前驅節點指標 + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; +} + +// 基於雙向鏈結串列實現的雙向佇列 +pub fn LinkedListDeque(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*ListNode(T) = null, // 頭節點 front + rear: ?*ListNode(T) = null, // 尾節點 rear + que_size: usize = 0, // 雙向佇列的長度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化佇列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取雙向佇列的長度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判斷雙向佇列是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 入列操作 + pub fn push(self: *Self, num: T, is_front: bool) !void { + var node = try self.mem_allocator.create(ListNode(T)); + node.init(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (self.isEmpty()) { + self.front = node; + self.rear = node; + // 佇列首入列操作 + } else if (is_front) { + // 將 node 新增至鏈結串列頭部 + self.front.?.prev = node; + node.next = self.front; + self.front = node; // 更新頭節點 + // 佇列尾入列操作 + } else { + // 將 node 新增至鏈結串列尾部 + self.rear.?.next = node; + node.prev = self.rear; + self.rear = node; // 更新尾節點 + } + self.que_size += 1; // 更新佇列長度 + } + + // 佇列首入列 + pub fn pushFirst(self: *Self, num: T) !void { + try self.push(num, true); + } + + // 佇列尾入列 + pub fn pushLast(self: *Self, num: T) !void { + try self.push(num, false); + } + + // 出列操作 + pub fn pop(self: *Self, is_front: bool) T { + if (self.isEmpty()) @panic("雙向佇列為空"); + var val: T = undefined; + // 佇列首出列操作 + if (is_front) { + val = self.front.?.val; // 暫存頭節點值 + // 刪除頭節點 + var fNext = self.front.?.next; + if (fNext != null) { + fNext.?.prev = null; + self.front.?.next = null; + } + self.front = fNext; // 更新頭節點 + // 佇列尾出列操作 + } else { + val = self.rear.?.val; // 暫存尾節點值 + // 刪除尾節點 + var rPrev = self.rear.?.prev; + if (rPrev != null) { + rPrev.?.next = null; + self.rear.?.prev = null; + } + self.rear = rPrev; // 更新尾節點 + } + self.que_size -= 1; // 更新佇列長度 + return val; + } + + // 佇列首出列 + pub fn popFirst(self: *Self) T { + return self.pop(true); + } + + // 佇列尾出列 + pub fn popLast(self: *Self) T { + return self.pop(false); + } + + // 訪問佇列首元素 + pub fn peekFirst(self: *Self) T { + if (self.isEmpty()) @panic("雙向佇列為空"); + return self.front.?.val; + } + + // 訪問佇列尾元素 + pub fn peekLast(self: *Self) T { + if (self.isEmpty()) @panic("雙向佇列為空"); + return self.rear.?.val; + } + + // 返回陣列用於列印 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化雙向佇列 + var deque = LinkedListDeque(i32){}; + try deque.init(std.heap.page_allocator); + defer deque.deinit(); + try deque.pushLast(3); + try deque.pushLast(2); + try deque.pushLast(5); + std.debug.print("雙向佇列 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 訪問元素 + var peek_first = deque.peekFirst(); + std.debug.print("\n佇列首元素 peek_first = {}", .{peek_first}); + var peek_last = deque.peekLast(); + std.debug.print("\n佇列尾元素 peek_last = {}", .{peek_last}); + + // 元素入列 + try deque.pushLast(4); + std.debug.print("\n元素 4 佇列尾入列後 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + try deque.pushFirst(1); + std.debug.print("\n元素 1 佇列首入列後 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 元素出列 + var pop_last = deque.popLast(); + std.debug.print("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", .{pop_last}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + var pop_first = deque.popFirst(); + std.debug.print("\n佇列首出列元素 = {},佇列首出列後 deque = ", .{pop_first}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 獲取雙向佇列的長度 + var size = deque.size(); + std.debug.print("\n雙向佇列長度 size = {}", .{size}); + + // 判斷雙向佇列是否為空 + var is_empty = deque.isEmpty(); + std.debug.print("\n雙向佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig new file mode 100644 index 0000000000..55c67877c4 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig @@ -0,0 +1,127 @@ +// File: linkedlist_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於鏈結串列實現的佇列 +pub fn LinkedListQueue(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*inc.ListNode(T) = null, // 頭節點 front + rear: ?*inc.ListNode(T) = null, // 尾節點 rear + que_size: usize = 0, // 佇列的長度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化佇列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取佇列的長度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判斷佇列是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問佇列首元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("佇列為空"); + return self.front.?.val; + } + + // 入列 + pub fn push(self: *Self, num: T) !void { + // 在尾節點後新增 num + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (self.front == null) { + self.front = node; + self.rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + self.rear.?.next = node; + self.rear = node; + } + self.que_size += 1; + } + + // 出列 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 刪除頭節點 + self.front = self.front.?.next; + self.que_size -= 1; + return num; + } + + // 將鏈結串列轉換為陣列 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化佇列 + var queue = LinkedListQueue(i32){}; + try queue.init(std.heap.page_allocator); + defer queue.deinit(); + + // 元素入列 + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("佇列 queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 訪問佇列首元素 + var peek = queue.peek(); + std.debug.print("\n佇列首元素 peek = {}", .{peek}); + + // 元素出列 + var pop = queue.pop(); + std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 獲取佇列的長度 + var size = queue.size(); + std.debug.print("\n佇列長度 size = {}", .{size}); + + // 判斷佇列是否為空 + var is_empty = queue.isEmpty(); + std.debug.print("\n佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig new file mode 100644 index 0000000000..22524ed156 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig @@ -0,0 +1,118 @@ +// File: linkedlist_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於鏈結串列實現的堆疊 +pub fn LinkedListStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack_top: ?*inc.ListNode(T) = null, // 將頭節點作為堆疊頂 + stk_size: usize = 0, // 堆疊的長度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化堆疊) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.stack_top = null; + self.stk_size = 0; + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取堆疊的長度 + pub fn size(self: *Self) usize { + return self.stk_size; + } + + // 判斷堆疊是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問堆疊頂元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("堆疊為空"); + return self.stack_top.?.val; + } + + // 入堆疊 + pub fn push(self: *Self, num: T) !void { + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + node.next = self.stack_top; + self.stack_top = node; + self.stk_size += 1; + } + + // 出堆疊 + pub fn pop(self: *Self) T { + var num = self.peek(); + self.stack_top = self.stack_top.?.next; + self.stk_size -= 1; + return num; + } + + // 將堆疊轉換為陣列 + pub fn toArray(self: *Self) ![]T { + var node = self.stack_top; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[res.len - i - 1] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化堆疊 + var stack = LinkedListStack(i32){}; + try stack.init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer stack.deinit(); + + // 元素入堆疊 + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("堆疊 stack = ", .{}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // 訪問堆疊頂元素 + var peek = stack.peek(); + std.debug.print("\n堆疊頂元素 top = {}", .{peek}); + + // 元素出堆疊 + var pop = stack.pop(); + std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{pop}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // 獲取堆疊的長度 + var size = stack.size(); + std.debug.print("\n堆疊的長度 size = {}", .{size}); + + // 判斷堆疊是否為空 + var is_empty = stack.isEmpty(); + std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/queue.zig b/zh-hant/codes/zig/chapter_stack_and_queue/queue.zig new file mode 100644 index 0000000000..2d2464e862 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/queue.zig @@ -0,0 +1,46 @@ +// File: queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化佇列 + const L = std.TailQueue(i32); + var queue = L{}; + + // 元素入列 + var node1 = L.Node{ .data = 1 }; + var node2 = L.Node{ .data = 3 }; + var node3 = L.Node{ .data = 2 }; + var node4 = L.Node{ .data = 5 }; + var node5 = L.Node{ .data = 4 }; + queue.append(&node1); + queue.append(&node2); + queue.append(&node3); + queue.append(&node4); + queue.append(&node5); + std.debug.print("佇列 queue = ", .{}); + inc.PrintUtil.printQueue(i32, queue); + + // 訪問佇列首元素 + var peek = queue.first.?.data; + std.debug.print("\n佇列首元素 peek = {}", .{peek}); + + // 元素出列 + var pop = queue.popFirst().?.data; + std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); + inc.PrintUtil.printQueue(i32, queue); + + // 獲取佇列的長度 + var size = queue.len; + std.debug.print("\n佇列長度 size = {}", .{size}); + + // 判斷佇列是否為空 + var is_empty = if (queue.len == 0) true else false; + std.debug.print("\n佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/stack.zig b/zh-hant/codes/zig/chapter_stack_and_queue/stack.zig new file mode 100644 index 0000000000..ffbaadbb71 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/stack.zig @@ -0,0 +1,43 @@ +// File: stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化堆疊 + // 在 zig 中,推薦將 ArrayList 當作堆疊來使用 + var stack = std.ArrayList(i32).init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer stack.deinit(); + + // 元素入堆疊 + try stack.append(1); + try stack.append(3); + try stack.append(2); + try stack.append(5); + try stack.append(4); + std.debug.print("堆疊 stack = ", .{}); + inc.PrintUtil.printList(i32, stack); + + // 訪問堆疊頂元素 + var peek = stack.items[stack.items.len - 1]; + std.debug.print("\n堆疊頂元素 peek = {}", .{peek}); + + // 元素出堆疊 + var pop = stack.pop(); + std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{pop}); + inc.PrintUtil.printList(i32, stack); + + // 獲取堆疊的長度 + var size = stack.items.len; + std.debug.print("\n堆疊的長度 size = {}", .{size}); + + // 判斷堆疊是否為空 + var is_empty = if (stack.items.len == 0) true else false; + std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_tree/avl_tree.zig b/zh-hant/codes/zig/chapter_tree/avl_tree.zig new file mode 100644 index 0000000000..11d48a8436 --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/avl_tree.zig @@ -0,0 +1,249 @@ +// File: avl_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// AVL 樹 +pub fn AVLTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, // 根節點 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子 + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + } + + // 析構方法 + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取節點高度 + fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { + _ = self; + // 空節點高度為 -1 ,葉節點高度為 0 + return if (node == null) -1 else node.?.height; + } + + // 更新節點高度 + fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { + // 節點高度等於最高子樹高度 + 1 + node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; + } + + // 獲取平衡因子 + fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { + // 空節點平衡因子為 0 + if (node == null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return self.height(node.?.left) - self.height(node.?.right); + } + + // 右旋操作 + fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.left; + var grandChild = child.?.right; + // 以 child 為原點,將 node 向右旋轉 + child.?.right = node; + node.?.left = grandChild; + // 更新節點高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + // 左旋操作 + fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.right; + var grandChild = child.?.left; + // 以 child 為原點,將 node 向左旋轉 + child.?.left = node; + node.?.right = grandChild; + // 更新節點高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + // 執行旋轉操作,使該子樹重新恢復平衡 + fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + // 獲取節點 node 的平衡因子 + var balance_factor = self.balanceFactor(node); + // 左偏樹 + if (balance_factor > 1) { + if (self.balanceFactor(node.?.left) >= 0) { + // 右旋 + return self.rightRotate(node); + } else { + // 先左旋後右旋 + node.?.left = self.leftRotate(node.?.left); + return self.rightRotate(node); + } + } + // 右偏樹 + if (balance_factor < -1) { + if (self.balanceFactor(node.?.right) <= 0) { + // 左旋 + return self.leftRotate(node); + } else { + // 先右旋後左旋 + node.?.right = self.rightRotate(node.?.right); + return self.leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + // 插入節點 + fn insert(self: *Self, val: T) !void { + self.root = (try self.insertHelper(self.root, val)).?; + } + + // 遞迴插入節點(輔助方法) + fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { + var node = node_; + if (node == null) { + var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); + tmp_node.init(val); + return tmp_node; + } + // 1. 查詢插入位置並插入節點 + if (val < node.?.val) { + node.?.left = try self.insertHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = try self.insertHelper(node.?.right, val); + } else { + return node; // 重複節點不插入,直接返回 + } + self.updateHeight(node); // 更新節點高度 + // 2. 執行旋轉操作,使該子樹重新恢復平衡 + node = self.rotate(node); + // 返回子樹的根節點 + return node; + } + + // 刪除節點 + fn remove(self: *Self, val: T) void { + self.root = self.removeHelper(self.root, val).?; + } + + // 遞迴刪除節點(輔助方法) + fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { + var node = node_; + if (node == null) return null; + // 1. 查詢節點並刪除 + if (val < node.?.val) { + node.?.left = self.removeHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = self.removeHelper(node.?.right, val); + } else { + if (node.?.left == null or node.?.right == null) { + var child = if (node.?.left != null) node.?.left else node.?.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) { + return null; + // 子節點數量 = 1 ,直接刪除 node + } else { + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + var temp = node.?.right; + while (temp.?.left != null) { + temp = temp.?.left; + } + node.?.right = self.removeHelper(node.?.right, temp.?.val); + node.?.val = temp.?.val; + } + } + self.updateHeight(node); // 更新節點高度 + // 2. 執行旋轉操作,使該子樹重新恢復平衡 + node = self.rotate(node); + // 返回子樹的根節點 + return node; + } + + // 查詢節點 + fn search(self: *Self, val: T) ?*inc.TreeNode(T) { + var cur = self.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.?.val < val) { + cur = cur.?.right; + // 目標節點在 cur 的左子樹中 + } else if (cur.?.val > val) { + cur = cur.?.left; + // 找到目標節點,跳出迴圈 + } else { + break; + } + } + // 返回目標節點 + return cur; + } + }; +} + +pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { + var tree = tree_; + try tree.insert(val); + std.debug.print("\n插入節點 {} 後,AVL 樹為\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { + var tree = tree_; + tree.remove(val); + std.debug.print("\n刪除節點 {} 後,AVL 樹為\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +// Driver Code +pub fn main() !void { + // 初始化空 AVL 樹 + var avl_tree = AVLTree(i32){}; + avl_tree.init(std.heap.page_allocator); + defer avl_tree.deinit(); + + // 插入節點 + // 請關注插入節點後,AVL 樹是如何保持平衡的 + try testInsert(i32, &avl_tree, 1); + try testInsert(i32, &avl_tree, 2); + try testInsert(i32, &avl_tree, 3); + try testInsert(i32, &avl_tree, 4); + try testInsert(i32, &avl_tree, 5); + try testInsert(i32, &avl_tree, 8); + try testInsert(i32, &avl_tree, 7); + try testInsert(i32, &avl_tree, 9); + try testInsert(i32, &avl_tree, 10); + try testInsert(i32, &avl_tree, 6); + + // 插入重複節點 + try testInsert(i32, &avl_tree, 7); + + // 刪除節點 + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(i32, &avl_tree, 8); // 刪除度為 0 的節點 + testRemove(i32, &avl_tree, 5); // 刪除度為 1 的節點 + testRemove(i32, &avl_tree, 4); // 刪除度為 2 的節點 + + // 查詢節點 + var node = avl_tree.search(7).?; + std.debug.print("\n查詢到的節點物件為 {any},節點值 = {}\n", .{node, node.val}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_tree/binary_search_tree.zig b/zh-hant/codes/zig/chapter_tree/binary_search_tree.zig new file mode 100644 index 0000000000..fb74c8df6c --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_search_tree.zig @@ -0,0 +1,182 @@ +// File: binary_search_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二元搜尋樹 +pub fn BinarySearchTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子 + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // 排序陣列 + self.root = try self.buildTree(nums, 0, nums.len - 1); // 構建二元搜尋樹 + } + + // 析構方法 + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 構建二元搜尋樹 + fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { + if (i > j) return null; + // 將陣列中間節點作為根節點 + var mid = i + (j - i) / 2; + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(nums[mid]); + // 遞迴建立左子樹和右子樹 + if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); + node.right = try self.buildTree(nums, mid + 1, j); + return node; + } + + // 獲取二元樹根節點 + fn getRoot(self: *Self) ?*inc.TreeNode(T) { + return self.root; + } + + // 查詢節點 + fn search(self: *Self, num: T) ?*inc.TreeNode(T) { + var cur = self.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.?.val < num) { + cur = cur.?.right; + // 目標節點在 cur 的左子樹中 + } else if (cur.?.val > num) { + cur = cur.?.left; + // 找到目標節點,跳出迴圈 + } else { + break; + } + } + // 返回目標節點 + return cur; + } + + // 插入節點 + fn insert(self: *Self, num: T) !void { + // 若樹為空,則初始化根節點 + if (self.root == null) { + self.root = try self.mem_allocator.create(inc.TreeNode(T)); + return; + } + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.?.val == num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.?.val < num) { + cur = cur.?.right; + // 插入位置在 cur 的左子樹中 + } else { + cur = cur.?.left; + } + } + // 插入節點 + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(num); + if (pre.?.val < num) { + pre.?.right = node; + } else { + pre.?.left = node; + } + } + + // 刪除節點 + fn remove(self: *Self, num: T) void { + // 若樹為空,直接提前返回 + if (self.root == null) return; + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.?.val == num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.?.val < num) { + cur = cur.?.right; + // 待刪除節點在 cur 的左子樹中 + } else { + cur = cur.?.left; + } + } + // 若無待刪除節點,則直接返回 + if (cur == null) return; + // 子節點數量 = 0 or 1 + if (cur.?.left == null or cur.?.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + var child = if (cur.?.left != null) cur.?.left else cur.?.right; + // 刪除節點 cur + if (pre.?.left == cur) { + pre.?.left = child; + } else { + pre.?.right = child; + } + // 子節點數量 = 2 + } else { + // 獲取中序走訪中 cur 的下一個節點 + var tmp = cur.?.right; + while (tmp.?.left != null) { + tmp = tmp.?.left; + } + var tmp_val = tmp.?.val; + // 遞迴刪除節點 tmp + self.remove(tmp.?.val); + // 用 tmp 覆蓋 cur + cur.?.val = tmp_val; + } + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化二元樹 + var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + var bst = BinarySearchTree(i32){}; + try bst.init(std.heap.page_allocator, &nums); + defer bst.deinit(); + std.debug.print("初始化的二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // 查詢節點 + var node = bst.search(7); + std.debug.print("\n查詢到的節點物件為 {any},節點值 = {}\n", .{node, node.?.val}); + + // 插入節點 + try bst.insert(16); + std.debug.print("\n插入節點 16 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // 刪除節點 + bst.remove(1); + std.debug.print("\n刪除節點 1 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(2); + std.debug.print("\n刪除節點 2 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(4); + std.debug.print("\n刪除節點 4 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_tree/binary_tree.zig b/zh-hant/codes/zig/chapter_tree/binary_tree.zig new file mode 100644 index 0000000000..4d4cf461d0 --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_tree.zig @@ -0,0 +1,39 @@ +// File: binary_tree.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化二元樹 + // 初始化節點 + var n1 = inc.TreeNode(i32){ .val = 1 }; + var n2 = inc.TreeNode(i32){ .val = 2 }; + var n3 = inc.TreeNode(i32){ .val = 3 }; + var n4 = inc.TreeNode(i32){ .val = 4 }; + var n5 = inc.TreeNode(i32){ .val = 5 }; + // 構建節點之間的引用(指標) + n1.left = &n2; + n1.right = &n3; + n2.left = &n4; + n2.right = &n5; + std.debug.print("初始化二元樹\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + // 插入與刪除節點 + var p = inc.TreeNode(i32){ .val = 0 }; + // 在 n1 -> n2 中間插入節點 P + n1.left = &p; + p.left = &n2; + std.debug.print("插入節點 P 後\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + // 刪除節點 + n1.left = &n2; + std.debug.print("刪除節點 P 後\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_tree/binary_tree_bfs.zig b/zh-hant/codes/zig/chapter_tree/binary_tree_bfs.zig new file mode 100644 index 0000000000..b4f4c1b394 --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_tree_bfs.zig @@ -0,0 +1,57 @@ +// File: binary_tree_bfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 層序走訪 +fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { + // 初始化佇列,加入根節點 + const L = std.TailQueue(*inc.TreeNode(T)); + var queue = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + queue.append(root_node); + // 初始化一個串列,用於儲存走訪序列 + var list = std.ArrayList(T).init(std.heap.page_allocator); + while (queue.len > 0) { + var queue_node = queue.popFirst().?; // 隊列出隊 + var node = queue_node.data; + try list.append(node.val); // 儲存節點值 + if (node.left != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + queue.append(tmp_node); // 左子節點入列 + } + if (node.right != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + queue.append(tmp_node); // 右子節點入列 + } + } + return list; +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("初始化二元樹\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // 層序走訪 + var list = try levelOrder(i32, mem_allocator, root.?); + defer list.deinit(); + std.debug.print("\n層序走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_tree/binary_tree_dfs.zig b/zh-hant/codes/zig/chapter_tree/binary_tree_dfs.zig new file mode 100644 index 0000000000..0a0a0771dd --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_tree_dfs.zig @@ -0,0 +1,70 @@ +// File: binary_tree_dfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +var list = std.ArrayList(i32).init(std.heap.page_allocator); + +// 前序走訪 +fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + try list.append(root.?.val); + try preOrder(T, root.?.left); + try preOrder(T, root.?.right); +} + +// 中序走訪 +fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + try inOrder(T, root.?.left); + try list.append(root.?.val); + try inOrder(T, root.?.right); +} + +// 後序走訪 +fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + try postOrder(T, root.?.left); + try postOrder(T, root.?.right); + try list.append(root.?.val); +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("初始化二元樹\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // 前序走訪 + list.clearRetainingCapacity(); + try preOrder(i32, root); + std.debug.print("\n前序走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 中序走訪 + list.clearRetainingCapacity(); + try inOrder(i32, root); + std.debug.print("\n中序走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 後序走訪 + list.clearRetainingCapacity(); + try postOrder(i32, root); + std.debug.print("\n後續走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/ListNode.zig b/zh-hant/codes/zig/include/ListNode.zig new file mode 100644 index 0000000000..580399dc36 --- /dev/null +++ b/zh-hant/codes/zig/include/ListNode.zig @@ -0,0 +1,49 @@ +// File: ListNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 鏈結串列節點 +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, + next: ?*Self = null, + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; +} + +// 將串列反序列化為鏈結串列 +pub fn listToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (list.items) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} + +// 將陣列反序列化為鏈結串列 +pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (arr) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/PrintUtil.zig b/zh-hant/codes/zig/include/PrintUtil.zig new file mode 100644 index 0000000000..c7444e99f1 --- /dev/null +++ b/zh-hant/codes/zig/include/PrintUtil.zig @@ -0,0 +1,132 @@ +// File: PrintUtil.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; + +// 列印陣列 +pub fn printArray(comptime T: type, nums: []T) void { + std.debug.print("[", .{}); + if (nums.len > 0) { + for (nums, 0..) |num, j| { + std.debug.print("{}{s}", .{num, if (j == nums.len-1) "]" else ", " }); + } + } else { + std.debug.print("]", .{}); + } +} + +// 列印串列 +pub fn printList(comptime T: type, list: std.ArrayList(T)) void { + std.debug.print("[", .{}); + if (list.items.len > 0) { + for (list.items, 0..) |value, i| { + std.debug.print("{}{s}", .{value, if (i == list.items.len-1) "]" else ", " }); + } + } else { + std.debug.print("]", .{}); + } +} + +// 列印鏈結串列 +pub fn printLinkedList(comptime T: type, node: ?*ListNode(T)) !void { + if (node == null) return; + var list = std.ArrayList(T).init(std.heap.page_allocator); + defer list.deinit(); + var head = node; + while (head != null) { + try list.append(head.?.val); + head = head.?.next; + } + for (list.items, 0..) |value, i| { + std.debug.print("{}{s}", .{value, if (i == list.items.len-1) "\n" else "->" }); + } +} + +// 列印佇列 +pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { + var node = queue.first; + std.debug.print("[", .{}); + var i: i32 = 0; + while (node != null) : (i += 1) { + var data = node.?.data; + std.debug.print("{}{s}", .{data, if (i == queue.len - 1) "]" else ", " }); + node = node.?.next; + } +} + +// 列印雜湊表 +pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { + var it = map.iterator(); + while (it.next()) |kv| { + var key = kv.key_ptr.*; + var value = kv.value_ptr.*; + std.debug.print("{} -> {s}\n", .{key, value}); + } +} + +// 列印堆積 +pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { + var arr = queue.items; + var len = queue.len; + std.debug.print("堆積的陣列表示:", .{}); + printArray(T, arr[0..len]); + std.debug.print("\n堆積的樹狀表示:\n", .{}); + var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); + try printTree(root, null, false); +} + +// 列印二元樹 +// This tree printer is borrowed from TECHIE DELIGHT +// https://www.techiedelight.com/c-program-print-binary-tree/ +const Trunk = struct { + prev: ?*Trunk = null, + str: []const u8 = undefined, + + pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { + self.prev = prev; + self.str = str; + } +}; + +pub fn showTrunks(p: ?*Trunk) void { + if (p == null) return; + showTrunks(p.?.prev); + std.debug.print("{s}", .{p.?.str}); +} + +// 列印二元樹 +pub fn printTree(root: ?*TreeNode(i32), prev: ?*Trunk, isRight: bool) !void { + if (root == null) { + return; + } + + var prev_str = " "; + var trunk = Trunk{.prev = prev, .str = prev_str}; + + try printTree(root.?.right, &trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.?.str = prev_str; + } + + showTrunks(&trunk); + std.debug.print(" {}\n", .{root.?.val}); + + if (prev) |_| { + prev.?.str = prev_str; + } + trunk.str = " |"; + + try printTree(root.?.left, &trunk, false); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/TreeNode.zig b/zh-hant/codes/zig/include/TreeNode.zig new file mode 100644 index 0000000000..39cdf330ef --- /dev/null +++ b/zh-hant/codes/zig/include/TreeNode.zig @@ -0,0 +1,63 @@ +// File: TreeNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 二元樹節點 +pub fn TreeNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // 節點值 + height: i32 = undefined, // 節點高度 + left: ?*Self = null, // 左子節點指標 + right: ?*Self = null, // 右子節點指標 + + // Initialize a tree node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.height = 0; + self.left = null; + self.right = null; + } + }; +} + +// 將陣列反序列化為二元樹 +pub fn arrToTree(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { + if (arr.len == 0) return null; + var root = try mem_allocator.create(TreeNode(T)); + root.init(arr[0]); + const L = std.TailQueue(*TreeNode(T)); + var que = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + que.append(root_node); + var index: usize = 0; + while (que.len > 0) { + var que_node = que.popFirst().?; + var node = que_node.data; + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try mem_allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.left = tmp; + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + que.append(tmp_node); + } + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try mem_allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.right = tmp; + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + que.append(tmp_node); + } + } + return root; +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/include.zig b/zh-hant/codes/zig/include/include.zig new file mode 100644 index 0000000000..cb98ffc0d3 --- /dev/null +++ b/zh-hant/codes/zig/include/include.zig @@ -0,0 +1,9 @@ +// File: include.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +pub const PrintUtil = @import("PrintUtil.zig"); +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; \ No newline at end of file diff --git a/zh-hant/docs/assets/covers/chapter_appendix.jpg b/zh-hant/docs/assets/covers/chapter_appendix.jpg new file mode 100644 index 0000000000..42262a225f Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_appendix.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_array_and_linkedlist.jpg b/zh-hant/docs/assets/covers/chapter_array_and_linkedlist.jpg new file mode 100644 index 0000000000..e6b0c12e05 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_array_and_linkedlist.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_backtracking.jpg b/zh-hant/docs/assets/covers/chapter_backtracking.jpg new file mode 100644 index 0000000000..b5e09ba501 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_backtracking.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_complexity_analysis.jpg b/zh-hant/docs/assets/covers/chapter_complexity_analysis.jpg new file mode 100644 index 0000000000..2a63d531fa Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_complexity_analysis.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_data_structure.jpg b/zh-hant/docs/assets/covers/chapter_data_structure.jpg new file mode 100644 index 0000000000..2511b64236 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_data_structure.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_divide_and_conquer.jpg b/zh-hant/docs/assets/covers/chapter_divide_and_conquer.jpg new file mode 100644 index 0000000000..56283b4c8e Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_divide_and_conquer.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_dynamic_programming.jpg b/zh-hant/docs/assets/covers/chapter_dynamic_programming.jpg new file mode 100644 index 0000000000..cf1c569e54 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_dynamic_programming.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_graph.jpg b/zh-hant/docs/assets/covers/chapter_graph.jpg new file mode 100644 index 0000000000..c70179dc53 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_graph.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_greedy.jpg b/zh-hant/docs/assets/covers/chapter_greedy.jpg new file mode 100644 index 0000000000..cbf74cf47d Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_greedy.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_hashing.jpg b/zh-hant/docs/assets/covers/chapter_hashing.jpg new file mode 100644 index 0000000000..cd1aa8c889 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_hashing.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_heap.jpg b/zh-hant/docs/assets/covers/chapter_heap.jpg new file mode 100644 index 0000000000..4672af4794 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_heap.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_hello_algo.jpg b/zh-hant/docs/assets/covers/chapter_hello_algo.jpg new file mode 100644 index 0000000000..8e347b3a4b Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_hello_algo.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_introduction.jpg b/zh-hant/docs/assets/covers/chapter_introduction.jpg new file mode 100644 index 0000000000..27d50a9645 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_introduction.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_preface.jpg b/zh-hant/docs/assets/covers/chapter_preface.jpg new file mode 100644 index 0000000000..3793703095 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_preface.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_searching.jpg b/zh-hant/docs/assets/covers/chapter_searching.jpg new file mode 100644 index 0000000000..f5f20a0ccf Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_searching.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_sorting.jpg b/zh-hant/docs/assets/covers/chapter_sorting.jpg new file mode 100644 index 0000000000..ea866f6b70 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_sorting.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_stack_and_queue.jpg b/zh-hant/docs/assets/covers/chapter_stack_and_queue.jpg new file mode 100644 index 0000000000..bcc187ce1b Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_stack_and_queue.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_tree.jpg b/zh-hant/docs/assets/covers/chapter_tree.jpg new file mode 100644 index 0000000000..4f40c5c878 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_tree.jpg differ diff --git a/zh-hant/docs/chapter_appendix/contribution.assets/edit_markdown.png b/zh-hant/docs/chapter_appendix/contribution.assets/edit_markdown.png new file mode 100644 index 0000000000..377d87967e Binary files /dev/null and b/zh-hant/docs/chapter_appendix/contribution.assets/edit_markdown.png differ diff --git a/zh-hant/docs/chapter_appendix/contribution.md b/zh-hant/docs/chapter_appendix/contribution.md new file mode 100644 index 0000000000..e5e000b6ee --- /dev/null +++ b/zh-hant/docs/chapter_appendix/contribution.md @@ -0,0 +1,47 @@ +# 一起參與創作 + +由於筆者能力有限,書中難免存在一些遺漏和錯誤,請您諒解。如果您發現了筆誤、連結失效、內容缺失、文字歧義、解釋不清晰或行文結構不合理等問題,請協助我們進行修正,以給讀者提供更優質的學習資源。 + +所有[撰稿人](https://github.com/krahets/hello-algo/graphs/contributors)的 GitHub ID 將在本書倉庫、網頁版和 PDF 版的主頁上進行展示,以感謝他們對開源社群的無私奉獻。 + +!!! success "開源的魅力" + + 紙質圖書的兩次印刷的間隔時間往往較久,內容更新非常不方便。 + + 而在本開源書中,內容更迭的時間被縮短至數日甚至幾個小時。 + +### 內容微調 + +如下圖所示,每個頁面的右上角都有“編輯圖示”。您可以按照以下步驟修改文字或程式碼。 + +1. 點選“編輯圖示”,如果遇到“需要 Fork 此倉庫”的提示,請同意該操作。 +2. 修改 Markdown 源檔案內容,檢查內容的正確性,並儘量保持排版格式的統一。 +3. 在頁面底部填寫修改說明,然後點選“Propose file change”按鈕。頁面跳轉後,點選“Create pull request”按鈕即可發起拉取請求。 + +![頁面編輯按鍵](contribution.assets/edit_markdown.png) + +圖片無法直接修改,需要透過新建 [Issue](https://github.com/krahets/hello-algo/issues) 或評論留言來描述問題,我們會盡快重新繪製並替換圖片。 + +### 內容創作 + +如果您有興趣參與此開源專案,包括將程式碼翻譯成其他程式語言、擴展文章內容等,那麼需要實施以下 Pull Request 工作流程。 + +1. 登入 GitHub ,將本書的[程式碼倉庫](https://github.com/krahets/hello-algo) Fork 到個人帳號下。 +2. 進入您的 Fork 倉庫網頁,使用 `git clone` 命令將倉庫克隆至本地。 +3. 在本地進行內容創作,並進行完整測試,驗證程式碼的正確性。 +4. 將本地所做更改 Commit ,然後 Push 至遠端倉庫。 +5. 重新整理倉庫網頁,點選“Create pull request”按鈕即可發起拉取請求。 + +### Docker 部署 + +在 `hello-algo` 根目錄下,執行以下 Docker 指令碼,即可在 `http://localhost:8000` 訪問本專案: + +```shell +docker-compose up -d +``` + +使用以下命令即可刪除部署: + +```shell +docker-compose down +``` diff --git a/zh-hant/docs/chapter_appendix/index.md b/zh-hant/docs/chapter_appendix/index.md new file mode 100644 index 0000000000..e9098b9c02 --- /dev/null +++ b/zh-hant/docs/chapter_appendix/index.md @@ -0,0 +1,3 @@ +# 附錄 + +![附錄](../assets/covers/chapter_appendix.jpg) diff --git a/zh-hant/docs/chapter_appendix/installation.assets/vscode_extension_installation.png b/zh-hant/docs/chapter_appendix/installation.assets/vscode_extension_installation.png new file mode 100644 index 0000000000..275f1a090e Binary files /dev/null and b/zh-hant/docs/chapter_appendix/installation.assets/vscode_extension_installation.png differ diff --git a/zh-hant/docs/chapter_appendix/installation.assets/vscode_installation.png b/zh-hant/docs/chapter_appendix/installation.assets/vscode_installation.png new file mode 100644 index 0000000000..b27257aee4 Binary files /dev/null and b/zh-hant/docs/chapter_appendix/installation.assets/vscode_installation.png differ diff --git a/zh-hant/docs/chapter_appendix/installation.md b/zh-hant/docs/chapter_appendix/installation.md new file mode 100644 index 0000000000..c6144ee867 --- /dev/null +++ b/zh-hant/docs/chapter_appendix/installation.md @@ -0,0 +1,68 @@ +# 程式設計環境安裝 + +## 安裝 IDE + +推薦使用開源、輕量的 VS Code 作為本地整合開發環境(IDE)。訪問 [VS Code 官網](https://code.visualstudio.com/),根據作業系統選擇相應版本的 VS Code 進行下載和安裝。 + +![從官網下載 VS Code](installation.assets/vscode_installation.png) + +VS Code 擁有強大的擴展包生態系統,支持大多數程式語言的執行和除錯。以 Python 為例,安裝“Python Extension Pack”擴展包之後,即可進行 Python 程式碼除錯。安裝步驟如下圖所示。 + +![安裝 VS Code 擴展包](installation.assets/vscode_extension_installation.png) + +## 安裝語言環境 + +### Python 環境 + +1. 下載並安裝 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) ,需要 Python 3.10 或更新版本。 +2. 在 VS Code 的擴充功能市場中搜索 `python` ,安裝 Python Extension Pack 。 +3. (可選)在命令列輸入 `pip install black` ,安裝程式碼格式化工具。 + +### C/C++ 環境 + +1. Windows 系統需要安裝 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)([配置教程](https://blog.csdn.net/qq_33698226/article/details/129031241));MacOS 自帶 Clang ,無須安裝。 +2. 在 VS Code 的擴充功能市場中搜索 `c++` ,安裝 C/C++ Extension Pack 。 +3. (可選)開啟 Settings 頁面,搜尋 `Clang_format_fallback Style` 程式碼格式化選項,設定為 `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` 。 + +### Java 環境 + +1. 下載並安裝 [OpenJDK](https://jdk.java.net/18/)(版本需滿足 > JDK 9)。 +2. 在 VS Code 的擴充功能市場中搜索 `java` ,安裝 Extension Pack for Java 。 + +### C# 環境 + +1. 下載並安裝 [.Net 8.0](https://dotnet.microsoft.com/en-us/download) 。 +2. 在 VS Code 的擴充功能市場中搜索 `C# Dev Kit` ,安裝 C# Dev Kit ([配置教程](https://code.visualstudio.com/docs/csharp/get-started))。 +3. 也可使用 Visual Studio([安裝教程](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 + +### Go 環境 + +1. 下載並安裝 [go](https://go.dev/dl/) 。 +2. 在 VS Code 的擴充功能市場中搜索 `go` ,安裝 Go 。 +3. 按快捷鍵 `Ctrl + Shift + P` 撥出命令欄,輸入 go ,選擇 `Go: Install/Update Tools` ,全部勾選並安裝即可。 + +### Swift 環境 + +1. 下載並安裝 [Swift](https://www.swift.org/download/) 。 +2. 在 VS Code 的擴充功能市場中搜索 `swift` ,安裝 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) 。 + +### JavaScript 環境 + +1. 下載並安裝 [Node.js](https://nodejs.org/en/) 。 +2. (可選)在 VS Code 的擴充功能市場中搜索 `Prettier` ,安裝程式碼格式化工具。 + +### TypeScript 環境 + +1. 同 JavaScript 環境安裝步驟。 +2. 安裝 [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation) 。 +3. 在 VS Code 的擴充功能市場中搜索 `typescript` ,安裝 [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) 。 + +### Dart 環境 + +1. 下載並安裝 [Dart](https://dart.dev/get-dart) 。 +2. 在 VS Code 的擴充功能市場中搜索 `dart` ,安裝 [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) 。 + +### Rust 環境 + +1. 下載並安裝 [Rust](https://www.rust-lang.org/tools/install) 。 +2. 在 VS Code 的擴充功能市場中搜索 `rust` ,安裝 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 。 diff --git a/zh-hant/docs/chapter_appendix/terminology.md b/zh-hant/docs/chapter_appendix/terminology.md new file mode 100644 index 0000000000..fd3d636f18 --- /dev/null +++ b/zh-hant/docs/chapter_appendix/terminology.md @@ -0,0 +1,137 @@ +# 術語表 + +下表列出了書中出現的重要術語,值得注意以下幾點。 + +- 建議記住名詞的英文叫法,以便閱讀英文文獻。 +- 部分名詞在簡體中文和繁體中文下的叫法不同。 + +

  資料結構與演算法的重要名詞

+ +| English | 簡體中文 | 繁體中文 | +| ------------------------------ | -------------- | -------------- | +| algorithm | 算法 | 演算法 | +| data structure | 数据结构 | 資料結構 | +| code | 代码 | 程式碼 | +| file | 文件 | 檔案 | +| function | 函数 | 函式 | +| method | 方法 | 方法 | +| variable | 变量 | 變數 | +| asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 | +| time complexity | 时间复杂度 | 時間複雜度 | +| space complexity | 空间复杂度 | 空間複雜度 | +| loop | 循环 | 迴圈 | +| iteration | 迭代 | 迭代 | +| recursion | 递归 | 遞迴 | +| tail recursion | 尾递归 | 尾遞迴 | +| recursion tree | 递归树 | 遞迴樹 | +| big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 | +| asymptotic upper bound | 渐近上界 | 漸近上界 | +| sign-magnitude | 原码 | 原碼 | +| 1’s complement | 反码 | 一補數 | +| 2’s complement | 补码 | 二補數 | +| array | 数组 | 陣列 | +| index | 索引 | 索引 | +| linked list | 链表 | 鏈結串列 | +| linked list node, list node | 链表节点 | 鏈結串列節點 | +| head node | 头节点 | 頭節點 | +| tail node | 尾节点 | 尾節點 | +| list | 列表 | 串列 | +| dynamic array | 动态数组 | 動態陣列 | +| hard disk | 硬盘 | 硬碟 | +| random-access memory (RAM) | 内存 | 記憶體 | +| cache memory | 缓存 | 快取 | +| cache miss | 缓存未命中 | 快取未命中 | +| cache hit rate | 缓存命中率 | 快取命中率 | +| stack | 栈 | 堆疊 | +| top of the stack | 栈顶 | 堆疊頂 | +| bottom of the stack | 栈底 | 堆疊底 | +| queue | 队列 | 佇列 | +| double-ended queue | 双向队列 | 雙向佇列 | +| front of the queue | 队首 | 佇列首 | +| rear of the queue | 队尾 | 佇列尾 | +| hash table | 哈希表 | 雜湊表 | +| hash set | 哈希集合 | 雜湊集合 | +| bucket | 桶 | 桶 | +| hash function | 哈希函数 | 雜湊函式 | +| hash collision | 哈希冲突 | 雜湊衝突 | +| load factor | 负载因子 | 負載因子 | +| separate chaining | 链式地址 | 鏈結位址 | +| open addressing | 开放寻址 | 開放定址 | +| linear probing | 线性探测 | 線性探查 | +| lazy deletion | 懒删除 | 懶刪除 | +| binary tree | 二叉树 | 二元樹 | +| tree node | 树节点 | 樹節點 | +| left-child node | 左子节点 | 左子節點 | +| right-child node | 右子节点 | 右子節點 | +| parent node | 父节点 | 父節點 | +| left subtree | 左子树 | 左子樹 | +| right subtree | 右子树 | 右子樹 | +| root node | 根节点 | 根節點 | +| leaf node | 叶节点 | 葉節點 | +| edge | 边 | 邊 | +| level | 层 | 層 | +| degree | 度 | 度 | +| height | 高度 | 高度 | +| depth | 深度 | 深度 | +| perfect binary tree | 完美二叉树 | 完美二元樹 | +| complete binary tree | 完全二叉树 | 完全二元樹 | +| full binary tree | 完满二叉树 | 完滿二元樹 | +| balanced binary tree | 平衡二叉树 | 平衡二元樹 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| AVL tree | AVL 树 | AVL 樹 | +| red-black tree | 红黑树 | 紅黑樹 | +| level-order traversal | 层序遍历 | 層序走訪 | +| breadth-first traversal | 广度优先遍历 | 廣度優先走訪 | +| depth-first traversal | 深度优先遍历 | 深度優先走訪 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 | +| balance factor | 平衡因子 | 平衡因子 | +| heap | 堆 | 堆積 | +| max heap | 大顶堆 | 大頂堆積 | +| min heap | 小顶堆 | 小頂堆積 | +| priority queue | 优先队列 | 優先佇列 | +| heapify | 堆化 | 堆積化 | +| top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 | +| graph | 图 | 圖 | +| vertex | 顶点 | 頂點 | +| undirected graph | 无向图 | 無向圖 | +| directed graph | 有向图 | 有向圖 | +| connected graph | 连通图 | 連通圖 | +| disconnected graph | 非连通图 | 非連通圖 | +| weighted graph | 有权图 | 有權圖 | +| adjacency | 邻接 | 鄰接 | +| path | 路径 | 路徑 | +| in-degree | 入度 | 入度 | +| out-degree | 出度 | 出度 | +| adjacency matrix | 邻接矩阵 | 鄰接矩陣 | +| adjacency list | 邻接表 | 鄰接表 | +| breadth-first search | 广度优先搜索 | 廣度優先搜尋 | +| depth-first search | 深度优先搜索 | 深度優先搜尋 | +| binary search | 二分查找 | 二分搜尋 | +| searching algorithm | 搜索算法 | 搜尋演算法 | +| sorting algorithm | 排序算法 | 排序演算法 | +| selection sort | 选择排序 | 選擇排序 | +| bubble sort | 冒泡排序 | 泡沫排序 | +| insertion sort | 插入排序 | 插入排序 | +| quick sort | 快速排序 | 快速排序 | +| merge sort | 归并排序 | 合併排序 | +| heap sort | 堆排序 | 堆積排序 | +| bucket sort | 桶排序 | 桶排序 | +| counting sort | 计数排序 | 計數排序 | +| radix sort | 基数排序 | 基數排序 | +| divide and conquer | 分治 | 分治 | +| hanota problem | 汉诺塔问题 | 河內塔問題 | +| backtracking algorithm | 回溯算法 | 回溯演算法 | +| constraint | 约束 | 約束 | +| solution | 解 | 解 | +| state | 状态 | 狀態 | +| pruning | 剪枝 | 剪枝 | +| permutations problem | 全排列问题 | 全排列問題 | +| subset-sum problem | 子集和问题 | 子集合問題 | +| $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 | +| dynamic programming | 动态规划 | 動態規劃 | +| initial state | 初始状态 | 初始狀態 | +| state-transition equation | 状态转移方程 | 狀態轉移方程 | +| knapsack problem | 背包问题 | 背包問題 | +| edit distance problem | 编辑距离问题 | 編輯距離問題 | +| greedy algorithm | 贪心算法 | 貪婪演算法 | diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_definition.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_definition.png new file mode 100644 index 0000000000..ea9589cbe7 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_definition.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png new file mode 100644 index 0000000000..8863838d85 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png new file mode 100644 index 0000000000..71f6cca204 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png new file mode 100644 index 0000000000..eb9c704cac Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.md b/zh-hant/docs/chapter_array_and_linkedlist/array.md new file mode 100755 index 0000000000..823d0103b8 --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/array.md @@ -0,0 +1,235 @@ +# 陣列 + +陣列(array)是一種線性資料結構,其將相同型別的元素儲存在連續的記憶體空間中。我們將元素在陣列中的位置稱為該元素的索引(index)。下圖展示了陣列的主要概念和儲存方式。 + +![陣列定義與儲存方式](array.assets/array_definition.png) + +## 陣列常用操作 + +### 初始化陣列 + +我們可以根據需求選用陣列的兩種初始化方式:無初始值、給定初始值。在未指定初始值的情況下,大多數程式語言會將陣列元素初始化為 $0$ : + +=== "Python" + + ```python title="array.py" + # 初始化陣列 + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* 初始化陣列 */ + // 儲存在堆疊上 + int arr[5]; + int nums[5] = { 1, 3, 2, 5, 4 }; + // 儲存在堆積上(需要手動釋放空間) + int* arr1 = new int[5]; + int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="array.java" + /* 初始化陣列 */ + int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } + int[] nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 初始化陣列 */ + int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] + int[] nums = [1, 3, 2, 5, 4]; + ``` + +=== "Go" + + ```go title="array.go" + /* 初始化陣列 */ + var arr [5]int + // 在 Go 中,指定長度時([5]int)為陣列,不指定長度時([]int)為切片 + // 由於 Go 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 + // 為了方便實現擴容 extend() 方法,以下將切片(Slice)看作陣列(Array) + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="array.swift" + /* 初始化陣列 */ + let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] + let nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="array.js" + /* 初始化陣列 */ + var arr = new Array(5).fill(0); + var nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="array.ts" + /* 初始化陣列 */ + let arr: number[] = new Array(5).fill(0); + let nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="array.dart" + /* 初始化陣列 */ + List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="array.rs" + /* 初始化陣列 */ + let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] + let slice: &[i32] = &[0; 5]; + // 在 Rust 中,指定長度時([i32; 5])為陣列,不指定長度時(&[i32])為切片 + // 由於 Rust 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 + // Vector 是 Rust 一般情況下用作動態陣列的型別 + // 為了方便實現擴容 extend() 方法,以下將 vector 看作陣列(array) + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="array.c" + /* 初始化陣列 */ + int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } + int nums[5] = { 1, 3, 2, 5, 4 }; + ``` + +=== "Kotlin" + + ```kotlin title="array.kt" + /* 初始化陣列 */ + var arr = IntArray(5) // { 0, 0, 0, 0, 0 } + var nums = intArrayOf(1, 3, 2, 5, 4) + ``` + +=== "Ruby" + + ```ruby title="array.rb" + # 初始化陣列 + arr = Array.new(5, 0) + nums = [1, 3, 2, 5, 4] + ``` + +=== "Zig" + + ```zig title="array.zig" + // 初始化陣列 + var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 } + var nums = [_]i32{ 1, 3, 2, 5, 4 }; + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0Aarr%20%3D%20%5B0%5D%20%2A%205%20%20%23%20%5B%200%2C%200%2C%200%2C%200%2C%200%20%5D%0Anums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 訪問元素 + +陣列元素被儲存在連續的記憶體空間中,這意味著計算陣列元素的記憶體位址非常容易。給定陣列記憶體位址(首元素記憶體位址)和某個元素的索引,我們可以使用下圖所示的公式計算得到該元素的記憶體位址,從而直接訪問該元素。 + +![陣列元素的記憶體位址計算](array.assets/array_memory_location_calculation.png) + +觀察上圖,我們發現陣列首個元素的索引為 $0$ ,這似乎有些反直覺,因為從 $1$ 開始計數會更自然。但從位址計算公式的角度看,**索引本質上是記憶體位址的偏移量**。首個元素的位址偏移量是 $0$ ,因此它的索引為 $0$ 是合理的。 + +在陣列中訪問元素非常高效,我們可以在 $O(1)$ 時間內隨機訪問陣列中的任意一個元素。 + +```src +[file]{array}-[class]{}-[func]{random_access} +``` + +### 插入元素 + +陣列元素在記憶體中是“緊挨著的”,它們之間沒有空間再存放任何資料。如下圖所示,如果想在陣列中間插入一個元素,則需要將該元素之後的所有元素都向後移動一位,之後再把元素賦值給該索引。 + +![陣列插入元素示例](array.assets/array_insert_element.png) + +值得注意的是,由於陣列的長度是固定的,因此插入一個元素必定會導致陣列尾部元素“丟失”。我們將這個問題的解決方案留在“串列”章節中討論。 + +```src +[file]{array}-[class]{}-[func]{insert} +``` + +### 刪除元素 + +同理,如下圖所示,若想刪除索引 $i$ 處的元素,則需要把索引 $i$ 之後的元素都向前移動一位。 + +![陣列刪除元素示例](array.assets/array_remove_element.png) + +請注意,刪除元素完成後,原先末尾的元素變得“無意義”了,所以我們無須特意去修改它。 + +```src +[file]{array}-[class]{}-[func]{remove} +``` + +總的來看,陣列的插入與刪除操作有以下缺點。 + +- **時間複雜度高**:陣列的插入和刪除的平均時間複雜度均為 $O(n)$ ,其中 $n$ 為陣列長度。 +- **丟失元素**:由於陣列的長度不可變,因此在插入元素後,超出陣列長度範圍的元素會丟失。 +- **記憶體浪費**:我們可以初始化一個比較長的陣列,只用前面一部分,這樣在插入資料時,丟失的末尾元素都是“無意義”的,但這樣做會造成部分記憶體空間浪費。 + +### 走訪陣列 + +在大多數程式語言中,我們既可以透過索引走訪陣列,也可以直接走訪獲取陣列中的每個元素: + +```src +[file]{array}-[class]{}-[func]{traverse} +``` + +### 查詢元素 + +在陣列中查詢指定元素需要走訪陣列,每輪判斷元素值是否匹配,若匹配則輸出對應索引。 + +因為陣列是線性資料結構,所以上述查詢操作被稱為“線性查詢”。 + +```src +[file]{array}-[class]{}-[func]{find} +``` + +### 擴容陣列 + +在複雜的系統環境中,程式難以保證陣列之後的記憶體空間是可用的,從而無法安全地擴展陣列容量。因此在大多數程式語言中,**陣列的長度是不可變的**。 + +如果我們希望擴容陣列,則需重新建立一個更大的陣列,然後把原陣列元素依次複製到新陣列。這是一個 $O(n)$ 的操作,在陣列很大的情況下非常耗時。程式碼如下所示: + +```src +[file]{array}-[class]{}-[func]{extend} +``` + +## 陣列的優點與侷限性 + +陣列儲存在連續的記憶體空間內,且元素型別相同。這種做法包含豐富的先驗資訊,系統可以利用這些資訊來最佳化資料結構的操作效率。 + +- **空間效率高**:陣列為資料分配了連續的記憶體塊,無須額外的結構開銷。 +- **支持隨機訪問**:陣列允許在 $O(1)$ 時間內訪問任何元素。 +- **快取區域性**:當訪問陣列元素時,計算機不僅會載入它,還會快取其周圍的其他資料,從而藉助高速快取來提升後續操作的執行速度。 + +連續空間儲存是一把雙刃劍,其存在以下侷限性。 + +- **插入與刪除效率低**:當陣列中元素較多時,插入與刪除操作需要移動大量的元素。 +- **長度不可變**:陣列在初始化後長度就固定了,擴容陣列需要將所有資料複製到新陣列,開銷很大。 +- **空間浪費**:如果陣列分配的大小超過實際所需,那麼多餘的空間就被浪費了。 + +## 陣列典型應用 + +陣列是一種基礎且常見的資料結構,既頻繁應用在各類演算法之中,也可用於實現各種複雜資料結構。 + +- **隨機訪問**:如果我們想隨機抽取一些樣本,那麼可以用陣列儲存,並生成一個隨機序列,根據索引實現隨機抽樣。 +- **排序和搜尋**:陣列是排序和搜尋演算法最常用的資料結構。快速排序、合併排序、二分搜尋等都主要在陣列上進行。 +- **查詢表**:當需要快速查詢一個元素或其對應關係時,可以使用陣列作為查詢表。假如我們想實現字元到 ASCII 碼的對映,則可以將字元的 ASCII 碼值作為索引,對應的元素存放在陣列中的對應位置。 +- **機器學習**:神經網路中大量使用了向量、矩陣、張量之間的線性代數運算,這些資料都是以陣列的形式構建的。陣列是神經網路程式設計中最常使用的資料結構。 +- **資料結構實現**:陣列可以用於實現堆疊、佇列、雜湊表、堆積、圖等資料結構。例如,圖的鄰接矩陣表示實際上是一個二維陣列。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/index.md b/zh-hant/docs/chapter_array_and_linkedlist/index.md new file mode 100644 index 0000000000..d5753ad47d --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/index.md @@ -0,0 +1,9 @@ +# 陣列與鏈結串列 + +![陣列與鏈結串列](../assets/covers/chapter_array_and_linkedlist.jpg) + +!!! abstract + + 資料結構的世界如同一堵厚實的磚牆。 + + 陣列的磚塊整齊排列,逐個緊貼。鏈結串列的磚塊分散各處,連線的藤蔓自由地穿梭於磚縫之間。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png new file mode 100644 index 0000000000..fc062c8ee4 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png new file mode 100644 index 0000000000..26de5e9662 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png new file mode 100644 index 0000000000..982194b138 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png new file mode 100644 index 0000000000..c297438562 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.md b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.md new file mode 100755 index 0000000000..218c96776e --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,761 @@ +# 鏈結串列 + +記憶體空間是所有程式的公共資源,在一個複雜的系統執行環境下,空閒的記憶體空間可能散落在記憶體各處。我們知道,儲存陣列的記憶體空間必須是連續的,而當陣列非常大時,記憶體可能無法提供如此大的連續空間。此時鏈結串列的靈活性優勢就體現出來了。 + +鏈結串列(linked list)是一種線性資料結構,其中的每個元素都是一個節點物件,各個節點透過“引用”相連線。引用記錄了下一個節點的記憶體位址,透過它可以從當前節點訪問到下一個節點。 + +鏈結串列的設計使得各個節點可以分散儲存在記憶體各處,它們的記憶體位址無須連續。 + +![鏈結串列定義與儲存方式](linked_list.assets/linkedlist_definition.png) + +觀察上圖,鏈結串列的組成單位是節點(node)物件。每個節點都包含兩項資料:節點的“值”和指向下一節點的“引用”。 + +- 鏈結串列的首個節點被稱為“頭節點”,最後一個節點被稱為“尾節點”。 +- 尾節點指向的是“空”,它在 Java、C++ 和 Python 中分別被記為 `null`、`nullptr` 和 `None` 。 +- 在 C、C++、Go 和 Rust 等支持指標的語言中,上述“引用”應被替換為“指標”。 + +如以下程式碼所示,鏈結串列節點 `ListNode` 除了包含值,還需額外儲存一個引用(指標)。因此在相同資料量下,**鏈結串列比陣列佔用更多的記憶體空間**。 + +=== "Python" + + ```python title="" + class ListNode: + """鏈結串列節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.next: ListNode | None = None # 指向下一節點的引用 + ``` + +=== "C++" + + ```cpp title="" + /* 鏈結串列節點結構體 */ + struct ListNode { + int val; // 節點值 + ListNode *next; // 指向下一節點的指標 + ListNode(int x) : val(x), next(nullptr) {} // 建構子 + }; + ``` + +=== "Java" + + ```java title="" + /* 鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode next; // 指向下一節點的引用 + ListNode(int x) { val = x; } // 建構子 + } + ``` + +=== "C#" + + ```csharp title="" + /* 鏈結串列節點類別 */ + class ListNode(int x) { //建構子 + int val = x; // 節點值 + ListNode? next; // 指向下一節點的引用 + } + ``` + +=== "Go" + + ```go title="" + /* 鏈結串列節點結構體 */ + type ListNode struct { + Val int // 節點值 + Next *ListNode // 指向下一節點的指標 + } + + // NewListNode 建構子,建立一個新的鏈結串列 + func NewListNode(val int) *ListNode { + return &ListNode{ + Val: val, + Next: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 鏈結串列節點類別 */ + class ListNode { + var val: Int // 節點值 + var next: ListNode? // 指向下一節點的引用 + + init(x: Int) { // 建構子 + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 鏈結串列節點類別 */ + class ListNode { + constructor(val, next) { + this.val = (val === undefined ? 0 : val); // 節點值 + this.next = (next === undefined ? null : next); // 指向下一節點的引用 + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 鏈結串列節點類別 */ + class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = next === undefined ? null : next; // 指向下一節點的引用 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode? next; // 指向下一節點的引用 + ListNode(this.val, [this.next]); // 建構子 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + /* 鏈結串列節點類別 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 節點值 + next: Option>>, // 指向下一節點的指標 + } + ``` + +=== "C" + + ```c title="" + /* 鏈結串列節點結構體 */ + typedef struct ListNode { + int val; // 節點值 + struct ListNode *next; // 指向下一節點的指標 + } ListNode; + + /* 建構子 */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 鏈結串列節點類別 */ + // 建構子 + class ListNode(x: Int) { + val _val: Int = x // 節點值 + val next: ListNode? = null // 指向下一個節點的引用 + } + ``` + +=== "Ruby" + + ```ruby title="" + # 鏈結串列節點類別 + class ListNode + attr_accessor :val # 節點值 + attr_accessor :next # 指向下一節點的引用 + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end + end + ``` + +=== "Zig" + + ```zig title="" + // 鏈結串列節點類別 + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, // 節點值 + next: ?*Self = null, // 指向下一節點的指標 + + // 建構子 + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; + } + ``` + +## 鏈結串列常用操作 + +### 初始化鏈結串列 + +建立鏈結串列分為兩步,第一步是初始化各個節點物件,第二步是構建節點之間的引用關係。初始化完成後,我們就可以從鏈結串列的頭節點出發,透過引用指向 `next` 依次訪問所有節點。 + +=== "Python" + + ```python title="linked_list.py" + # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 + # 初始化各個節點 + n0 = ListNode(1) + n1 = ListNode(3) + n2 = ListNode(2) + n3 = ListNode(5) + n4 = ListNode(4) + # 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "C++" + + ```cpp title="linked_list.cpp" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + ListNode* n0 = new ListNode(1); + ListNode* n1 = new ListNode(3); + ListNode* n2 = new ListNode(2); + ListNode* n3 = new ListNode(5); + ListNode* n4 = new ListNode(4); + // 構建節點之間的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + ``` + +=== "Java" + + ```java title="linked_list.java" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Go" + + ```go title="linked_list.go" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + // 構建節點之間的引用 + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + ``` + +=== "Swift" + + ```swift title="linked_list.swift" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "JS" + + ```javascript title="linked_list.js" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "TS" + + ```typescript title="linked_list.ts" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Dart" + + ```dart title="linked_list.dart" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */\ + // 初始化各個節點 + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Rust" + + ```rust title="linked_list.rs" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); + let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); + let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); + let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); + let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); + + // 構建節點之間的引用 + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + ``` + +=== "C" + + ```c title="linked_list.c" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + 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; + ``` + +=== "Kotlin" + + ```kotlin title="linked_list.kt" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + val n0 = ListNode(1) + val n1 = ListNode(3) + val n2 = ListNode(2) + val n3 = ListNode(5) + val n4 = ListNode(4) + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Ruby" + + ```ruby title="linked_list.rb" + # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 + # 初始化各個節點 + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "Zig" + + ```zig title="linked_list.zig" + // 初始化鏈結串列 + // 初始化各個節點 + var n0 = inc.ListNode(i32){.val = 1}; + var n1 = inc.ListNode(i32){.val = 3}; + var n2 = inc.ListNode(i32){.val = 2}; + var n3 = inc.ListNode(i32){.val = 5}; + var n4 = inc.ListNode(i32){.val = 4}; + // 構建節點之間的引用 + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E5%80%8B%E7%AF%80%E9%BB%9E%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +陣列整體是一個變數,比如陣列 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而鏈結串列是由多個獨立的節點物件組成的。**我們通常將頭節點當作鏈結串列的代稱**,比如以上程式碼中的鏈結串列可記作鏈結串列 `n0` 。 + +### 插入節點 + +在鏈結串列中插入節點非常容易。如下圖所示,假設我們想在相鄰的兩個節點 `n0` 和 `n1` 之間插入一個新節點 `P` ,**則只需改變兩個節點引用(指標)即可**,時間複雜度為 $O(1)$ 。 + +相比之下,在陣列中插入元素的時間複雜度為 $O(n)$ ,在大資料量下的效率較低。 + +![鏈結串列插入節點示例](linked_list.assets/linkedlist_insert_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{insert} +``` + +### 刪除節點 + +如下圖所示,在鏈結串列中刪除節點也非常方便,**只需改變一個節點的引用(指標)即可**。 + +請注意,儘管在刪除操作完成後節點 `P` 仍然指向 `n1` ,但實際上走訪此鏈結串列已經無法訪問到 `P` ,這意味著 `P` 已經不再屬於該鏈結串列了。 + +![鏈結串列刪除節點](linked_list.assets/linkedlist_remove_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{remove} +``` + +### 訪問節點 + +**在鏈結串列中訪問節點的效率較低**。如上一節所述,我們可以在 $O(1)$ 時間下訪問陣列中的任意元素。鏈結串列則不然,程式需要從頭節點出發,逐個向後走訪,直至找到目標節點。也就是說,訪問鏈結串列的第 $i$ 個節點需要迴圈 $i - 1$ 輪,時間複雜度為 $O(n)$ 。 + +```src +[file]{linked_list}-[class]{}-[func]{access} +``` + +### 查詢節點 + +走訪鏈結串列,查詢其中值為 `target` 的節點,輸出該節點在鏈結串列中的索引。此過程也屬於線性查詢。程式碼如下所示: + +```src +[file]{linked_list}-[class]{}-[func]{find} +``` + +## 陣列 vs. 鏈結串列 + +下表總結了陣列和鏈結串列的各項特點並對比了操作效率。由於它們採用兩種相反的儲存策略,因此各種性質和操作效率也呈現對立的特點。 + +

  陣列與鏈結串列的效率對比

+ +| | 陣列 | 鏈結串列 | +| -------- | ------------------------------ | -------------- | +| 儲存方式 | 連續記憶體空間 | 分散記憶體空間 | +| 容量擴展 | 長度不可變 | 可靈活擴展 | +| 記憶體效率 | 元素佔用記憶體少、但可能浪費空間 | 元素佔用記憶體多 | +| 訪問元素 | $O(1)$ | $O(n)$ | +| 新增元素 | $O(n)$ | $O(1)$ | +| 刪除元素 | $O(n)$ | $O(1)$ | + +## 常見鏈結串列型別 + +如下圖所示,常見的鏈結串列型別包括三種。 + +- **單向鏈結串列**:即前面介紹的普通鏈結串列。單向鏈結串列的節點包含值和指向下一節點的引用兩項資料。我們將首個節點稱為頭節點,將最後一個節點稱為尾節點,尾節點指向空 `None` 。 +- **環形鏈結串列**:如果我們令單向鏈結串列的尾節點指向頭節點(首尾相接),則得到一個環形鏈結串列。在環形鏈結串列中,任意節點都可以視作頭節點。 +- **雙向鏈結串列**:與單向鏈結串列相比,雙向鏈結串列記錄了兩個方向的引用。雙向鏈結串列的節點定義同時包含指向後繼節點(下一個節點)和前驅節點(上一個節點)的引用(指標)。相較於單向鏈結串列,雙向鏈結串列更具靈活性,可以朝兩個方向走訪鏈結串列,但相應地也需要佔用更多的記憶體空間。 + +=== "Python" + + ```python title="" + class ListNode: + """雙向鏈結串列節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.next: ListNode | None = None # 指向後繼節點的引用 + self.prev: ListNode | None = None # 指向前驅節點的引用 + ``` + +=== "C++" + + ```cpp title="" + /* 雙向鏈結串列節點結構體 */ + struct ListNode { + int val; // 節點值 + ListNode *next; // 指向後繼節點的指標 + ListNode *prev; // 指向前驅節點的指標 + ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 建構子 + }; + ``` + +=== "Java" + + ```java title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode next; // 指向後繼節點的引用 + ListNode prev; // 指向前驅節點的引用 + ListNode(int x) { val = x; } // 建構子 + } + ``` + +=== "C#" + + ```csharp title="" + /* 雙向鏈結串列節點類別 */ + class ListNode(int x) { // 建構子 + int val = x; // 節點值 + ListNode next; // 指向後繼節點的引用 + ListNode prev; // 指向前驅節點的引用 + } + ``` + +=== "Go" + + ```go title="" + /* 雙向鏈結串列節點結構體 */ + type DoublyListNode struct { + Val int // 節點值 + Next *DoublyListNode // 指向後繼節點的指標 + Prev *DoublyListNode // 指向前驅節點的指標 + } + + // NewDoublyListNode 初始化 + func NewDoublyListNode(val int) *DoublyListNode { + return &DoublyListNode{ + Val: val, + Next: nil, + Prev: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + var val: Int // 節點值 + var next: ListNode? // 指向後繼節點的引用 + var prev: ListNode? // 指向前驅節點的引用 + + init(x: Int) { // 建構子 + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + constructor(val, next, prev) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = next === undefined ? null : next; // 指向後繼節點的引用 + this.prev = prev === undefined ? null : prev; // 指向前驅節點的引用 + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + val: number; + next: ListNode | null; + prev: ListNode | null; + constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = next === undefined ? null : next; // 指向後繼節點的引用 + this.prev = prev === undefined ? null : prev; // 指向前驅節點的引用 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode? next; // 指向後繼節點的引用 + ListNode? prev; // 指向前驅節點的引用 + ListNode(this.val, [this.next, this.prev]); // 建構子 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 雙向鏈結串列節點型別 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 節點值 + next: Option>>, // 指向後繼節點的指標 + prev: Option>>, // 指向前驅節點的指標 + } + + /* 建構子 */ + impl ListNode { + fn new(val: i32) -> Self { + ListNode { + val, + next: None, + prev: None, + } + } + } + ``` + +=== "C" + + ```c title="" + /* 雙向鏈結串列節點結構體 */ + typedef struct ListNode { + int val; // 節點值 + struct ListNode *next; // 指向後繼節點的指標 + struct ListNode *prev; // 指向前驅節點的指標 + } ListNode; + + /* 建構子 */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + node->prev = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 雙向鏈結串列節點類別 */ + // 建構子 + class ListNode(x: Int) { + val _val: Int = x // 節點值 + val next: ListNode? = null // 指向後繼節點的引用 + val prev: ListNode? = null // 指向前驅節點的引用 + } + ``` + +=== "Ruby" + + ```ruby title="" + # 雙向鏈結串列節點類別 + class ListNode + attr_accessor :val # 節點值 + attr_accessor :next # 指向後繼節點的引用 + attr_accessor :prev # 指向前驅節點的引用 + + def initialize(val=0, next_node=nil, prev_node=nil) + @val = val + @next = next_node + @prev = prev_node + end + end + ``` + +=== "Zig" + + ```zig title="" + // 雙向鏈結串列節點類別 + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, // 節點值 + next: ?*Self = null, // 指向後繼節點的指標 + prev: ?*Self = null, // 指向前驅節點的指標 + + // 建構子 + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; + } + ``` + +![常見鏈結串列種類](linked_list.assets/linkedlist_common_types.png) + +## 鏈結串列典型應用 + +單向鏈結串列通常用於實現堆疊、佇列、雜湊表和圖等資料結構。 + +- **堆疊與佇列**:當插入和刪除操作都在鏈結串列的一端進行時,它表現的特性為先進後出,對應堆疊;當插入操作在鏈結串列的一端進行,刪除操作在鏈結串列的另一端進行,它表現的特性為先進先出,對應佇列。 +- **雜湊表**:鏈式位址是解決雜湊衝突的主流方案之一,在該方案中,所有衝突的元素都會被放到一個鏈結串列中。 +- **圖**:鄰接表是表示圖的一種常用方式,其中圖的每個頂點都與一個鏈結串列相關聯,鏈結串列中的每個元素都代表與該頂點相連的其他頂點。 + +雙向鏈結串列常用於需要快速查詢前一個和後一個元素的場景。 + +- **高階資料結構**:比如在紅黑樹、B 樹中,我們需要訪問節點的父節點,這可以透過在節點中儲存一個指向父節點的引用來實現,類似於雙向鏈結串列。 +- **瀏覽器歷史**:在網頁瀏覽器中,當用戶點選前進或後退按鈕時,瀏覽器需要知道使用者訪問過的前一個和後一個網頁。雙向鏈結串列的特性使得這種操作變得簡單。 +- **LRU 演算法**:在快取淘汰(LRU)演算法中,我們需要快速找到最近最少使用的資料,以及支持快速新增和刪除節點。這時候使用雙向鏈結串列就非常合適。 + +環形鏈結串列常用於需要週期性操作的場景,比如作業系統的資源排程。 + +- **時間片輪轉排程演算法**:在作業系統中,時間片輪轉排程演算法是一種常見的 CPU 排程演算法,它需要對一組程序進行迴圈。每個程序被賦予一個時間片,當時間片用完時,CPU 將切換到下一個程序。這種迴圈操作可以透過環形鏈結串列來實現。 +- **資料緩衝區**:在某些資料緩衝區的實現中,也可能會使用環形鏈結串列。比如在音訊、影片播放器中,資料流可能會被分成多個緩衝塊並放入一個環形鏈結串列,以便實現無縫播放。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/list.md b/zh-hant/docs/chapter_array_and_linkedlist/list.md new file mode 100755 index 0000000000..5ce01314fb --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/list.md @@ -0,0 +1,1034 @@ +# 串列 + +串列(list)是一個抽象的資料結構概念,它表示元素的有序集合,支持元素訪問、修改、新增、刪除和走訪等操作,無須使用者考慮容量限制的問題。串列可以基於鏈結串列或陣列實現。 + +- 鏈結串列天然可以看作一個串列,其支持元素增刪查改操作,並且可以靈活動態擴容。 +- 陣列也支持元素增刪查改,但由於其長度不可變,因此只能看作一個具有長度限制的串列。 + +當使用陣列實現串列時,**長度不可變的性質會導致串列的實用性降低**。這是因為我們通常無法事先確定需要儲存多少資料,從而難以選擇合適的串列長度。若長度過小,則很可能無法滿足使用需求;若長度過大,則會造成記憶體空間浪費。 + +為解決此問題,我們可以使用動態陣列(dynamic array)來實現串列。它繼承了陣列的各項優點,並且可以在程式執行過程中進行動態擴容。 + +實際上,**許多程式語言中的標準庫提供的串列是基於動態陣列實現的**,例如 Python 中的 `list` 、Java 中的 `ArrayList` 、C++ 中的 `vector` 和 C# 中的 `List` 等。在接下來的討論中,我們將把“串列”和“動態陣列”視為等同的概念。 + +## 串列常用操作 + +### 初始化串列 + +我們通常使用“無初始值”和“有初始值”這兩種初始化方法: + +=== "Python" + + ```python title="list.py" + # 初始化串列 + # 無初始值 + nums1: list[int] = [] + # 有初始值 + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 初始化串列 */ + // 需注意,C++ 中 vector 即是本文描述的 nums + // 無初始值 + vector nums1; + // 有初始值 + vector nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="list.java" + /* 初始化串列 */ + // 無初始值 + List nums1 = new ArrayList<>(); + // 有初始值(注意陣列的元素型別需為 int[] 的包裝類別 Integer[]) + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 初始化串列 */ + // 無初始值 + List nums1 = []; + // 有初始值 + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + ``` + +=== "Go" + + ```go title="list_test.go" + /* 初始化串列 */ + // 無初始值 + nums1 := []int{} + // 有初始值 + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 初始化串列 */ + // 無初始值 + let nums1: [Int] = [] + // 有初始值 + var nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="list.js" + /* 初始化串列 */ + // 無初始值 + const nums1 = []; + // 有初始值 + const nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 初始化串列 */ + // 無初始值 + const nums1: number[] = []; + // 有初始值 + const nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 初始化串列 */ + // 無初始值 + List nums1 = []; + // 有初始值 + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 初始化串列 */ + // 無初始值 + let nums1: Vec = Vec::new(); + // 有初始值 + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 初始化串列 */ + // 無初始值 + var nums1 = listOf() + // 有初始值 + var numbers = arrayOf(1, 3, 2, 5, 4) + var nums = numbers.toMutableList() + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 初始化串列 + # 無初始值 + nums1 = [] + # 有初始值 + nums = [1, 3, 2, 5, 4] + ``` + +=== "Zig" + + ```zig title="list.zig" + // 初始化串列 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20%23%20%E7%84%A1%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 訪問元素 + +串列本質上是陣列,因此可以在 $O(1)$ 時間內訪問和更新元素,效率很高。 + +=== "Python" + + ```python title="list.py" + # 訪問元素 + num: int = nums[1] # 訪問索引 1 處的元素 + + # 更新元素 + nums[1] = 0 # 將索引 1 處的元素更新為 0 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 訪問元素 */ + int num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Java" + + ```java title="list.java" + /* 訪問元素 */ + int num = nums.get(1); // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums.set(1, 0); // 將索引 1 處的元素更新為 0 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 訪問元素 */ + int num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Go" + + ```go title="list_test.go" + /* 訪問元素 */ + num := nums[1] // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 訪問元素 */ + let num = nums[1] // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 訪問元素 */ + const num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 訪問元素 */ + const num: number = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 訪問元素 */ + int num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 訪問元素 */ + let num: i32 = nums[1]; // 訪問索引 1 處的元素 + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 訪問元素 */ + val num = nums[1] // 訪問索引 1 處的元素 + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 訪問元素 + num = nums[1] # 訪問索引 1 處的元素 + # 更新元素 + nums[1] = 0 # 將索引 1 處的元素更新為 0 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 訪問元素 + var num = nums.items[1]; // 訪問索引 1 處的元素 + + // 更新元素 + nums.items[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%A8%AA%E5%95%8F%E7%B4%A2%E5%BC%95%201%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%87%E7%B4%A2%E5%BC%95%201%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E7%82%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 插入與刪除元素 + +相較於陣列,串列可以自由地新增與刪除元素。在串列尾部新增元素的時間複雜度為 $O(1)$ ,但插入和刪除元素的效率仍與陣列相同,時間複雜度為 $O(n)$ 。 + +=== "Python" + + ```python title="list.py" + # 清空串列 + nums.clear() + + # 在尾部新增元素 + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + # 在中間插入元素 + nums.insert(3, 6) # 在索引 3 處插入數字 6 + + # 刪除元素 + nums.pop(3) # 刪除索引 3 處的元素 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + + /* 在中間插入元素 */ + nums.insert(nums.begin() + 3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.erase(nums.begin() + 3); // 刪除索引 3 處的元素 + ``` + +=== "Java" + + ```java title="list.java" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* 在中間插入元素 */ + nums.add(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(3); // 刪除索引 3 處的元素 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 清空串列 */ + nums.Clear(); + + /* 在尾部新增元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + + /* 在中間插入元素 */ + nums.Insert(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.RemoveAt(3); // 刪除索引 3 處的元素 + ``` + +=== "Go" + + ```go title="list_test.go" + /* 清空串列 */ + nums = nil + + /* 在尾部新增元素 */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + + /* 在中間插入元素 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums = append(nums[:3], nums[4:]...) // 刪除索引 3 處的元素 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 清空串列 */ + nums.removeAll() + + /* 在尾部新增元素 */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + /* 在中間插入元素 */ + nums.insert(6, at: 3) // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(at: 3) // 刪除索引 3 處的元素 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 清空串列 */ + nums.length = 0; + + /* 在尾部新增元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* 在中間插入元素 */ + nums.splice(3, 0, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.splice(3, 1); // 刪除索引 3 處的元素 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 清空串列 */ + nums.length = 0; + + /* 在尾部新增元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* 在中間插入元素 */ + nums.splice(3, 0, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.splice(3, 1); // 刪除索引 3 處的元素 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* 在中間插入元素 */ + nums.insert(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.removeAt(3); // 刪除索引 3 處的元素 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* 在中間插入元素 */ + nums.insert(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(3); // 刪除索引 3 處的元素 + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* 在中間插入元素 */ + nums.add(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(3); // 刪除索引 3 處的元素 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 清空串列 + nums.clear + + # 在尾部新增元素 + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + + # 在中間插入元素 + nums.insert(3, 6) # 在索引 3 處插入數字 6 + + # 刪除元素 + nums.delete_at(3) # 刪除索引 3 處的元素 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 清空串列 + nums.clearRetainingCapacity(); + + // 在尾部新增元素 + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + + // 在中間插入元素 + try nums.insert(3, 6); // 在索引 3 處插入數字 6 + + // 刪除元素 + _ = nums.orderedRemove(3); // 刪除索引 3 處的元素 + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E4%B8%B2%E5%88%97%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%96%B0%E5%A2%9E%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283%2C%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E6%8F%92%E5%85%A5%E6%95%B8%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E8%99%95%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 走訪串列 + +與陣列一樣,串列可以根據索引走訪,也可以直接走訪各元素。 + +=== "Python" + + ```python title="list.py" + # 透過索引走訪串列 + count = 0 + for i in range(len(nums)): + count += nums[i] + + # 直接走訪串列元素 + for num in nums: + count += num + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (int num : nums) { + count += num; + } + ``` + +=== "Java" + + ```java title="list.java" + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + + /* 直接走訪串列元素 */ + for (int num : nums) { + count += num; + } + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + foreach (int num in nums) { + count += num; + } + ``` + +=== "Go" + + ```go title="list_test.go" + /* 透過索引走訪串列 */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + + /* 直接走訪串列元素 */ + count = 0 + for _, num := range nums { + count += num + } + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 透過索引走訪串列 */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + + /* 直接走訪串列元素 */ + count = 0 + for num in nums { + count += num + } + ``` + +=== "JS" + + ```javascript title="list.js" + /* 透過索引走訪串列 */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 透過索引走訪串列 */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 透過索引走訪串列 */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (var num in nums) { + count += num; + } + ``` + +=== "Rust" + + ```rust title="list.rs" + // 透過索引走訪串列 + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + + // 直接走訪串列元素 + _count = 0; + for num in &nums { + _count += num; + } + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 透過索引走訪串列 */ + var count = 0 + for (i in nums.indices) { + count += nums[i] + } + + /* 直接走訪串列元素 */ + for (num in nums) { + count += num + } + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 透過索引走訪串列 + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # 直接走訪串列元素 + count = 0 + for num in nums + count += num + end + ``` + +=== "Zig" + + ```zig title="list.zig" + // 透過索引走訪串列 + var count: i32 = 0; + var i: i32 = 0; + while (i < nums.items.len) : (i += 1) { + count += nums[i]; + } + + // 直接走訪串列元素 + count = 0; + for (nums.items) |num| { + count += num; + } + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%8F%E9%81%8E%E7%B4%A2%E5%BC%95%E8%B5%B0%E8%A8%AA%E4%B8%B2%E5%88%97%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E8%B5%B0%E8%A8%AA%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 拼接串列 + +給定一個新串列 `nums1` ,我們可以將其拼接到原串列的尾部。 + +=== "Python" + + ```python title="list.py" + # 拼接兩個串列 + nums1: list[int] = [6, 8, 7, 10, 9] + nums += nums1 # 將串列 nums1 拼接到 nums 之後 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 拼接兩個串列 */ + vector nums1 = { 6, 8, 7, 10, 9 }; + // 將串列 nums1 拼接到 nums 之後 + nums.insert(nums.end(), nums1.begin(), nums1.end()); + ``` + +=== "Java" + + ```java title="list.java" + /* 拼接兩個串列 */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Go" + + ```go title="list_test.go" + /* 拼接兩個串列 */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 拼接兩個串列 */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 拼接兩個串列 */ + const nums1 = [6, 8, 7, 10, 9]; + nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 拼接兩個串列 */ + const nums1: number[] = [6, 8, 7, 10, 9]; + nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 拼接兩個串列 */ + let nums1: Vec = vec![6, 8, 7, 10, 9]; + nums.extend(nums1); + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 拼接兩個串列 */ + val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() + nums.addAll(nums1) // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 拼接兩個串列 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 拼接兩個串列 + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); // 將串列 nums1 拼接到 nums 之後 + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E5%85%A9%E5%80%8B%E4%B8%B2%E5%88%97%0A%20%20%20%20nums1%20%3D%20%5B6%2C%208%2C%207%2C%2010%2C%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%87%E4%B8%B2%E5%88%97%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%BE%8C&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 排序串列 + +完成串列排序後,我們便可以使用在陣列類別演算法題中經常考查的“二分搜尋”和“雙指標”演算法。 + +=== "Python" + + ```python title="list.py" + # 排序串列 + nums.sort() # 排序後,串列元素從小到大排列 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 排序串列 */ + sort(nums.begin(), nums.end()); // 排序後,串列元素從小到大排列 + ``` + +=== "Java" + + ```java title="list.java" + /* 排序串列 */ + Collections.sort(nums); // 排序後,串列元素從小到大排列 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 排序串列 */ + nums.Sort(); // 排序後,串列元素從小到大排列 + ``` + +=== "Go" + + ```go title="list_test.go" + /* 排序串列 */ + sort.Ints(nums) // 排序後,串列元素從小到大排列 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 排序串列 */ + nums.sort() // 排序後,串列元素從小到大排列 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 排序串列 */ + nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 排序串列 */ + nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 排序串列 */ + nums.sort(); // 排序後,串列元素從小到大排列 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 排序串列 */ + nums.sort(); // 排序後,串列元素從小到大排列 + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 排序串列 */ + nums.sort() // 排序後,串列元素從小到大排列 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 排序串列 + nums = nums.sort { |a, b| a <=> b } # 排序後,串列元素從小到大排列 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 排序串列 + std.sort.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%B2%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E4%B8%B2%E5%88%97%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%BE%8C%EF%BC%8C%E4%B8%B2%E5%88%97%E5%85%83%E7%B4%A0%E5%BE%9E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 串列實現 + +許多程式語言內建了串列,例如 Java、C++、Python 等。它們的實現比較複雜,各個參數的設定也非常考究,例如初始容量、擴容倍數等。感興趣的讀者可以查閱原始碼進行學習。 + +為了加深對串列工作原理的理解,我們嘗試實現一個簡易版串列,包括以下三個重點設計。 + +- **初始容量**:選取一個合理的陣列初始容量。在本示例中,我們選擇 10 作為初始容量。 +- **數量記錄**:宣告一個變數 `size` ,用於記錄串列當前元素數量,並隨著元素插入和刪除實時更新。根據此變數,我們可以定位串列尾部,以及判斷是否需要擴容。 +- **擴容機制**:若插入元素時串列容量已滿,則需要進行擴容。先根據擴容倍數建立一個更大的陣列,再將當前陣列的所有元素依次移動至新陣列。在本示例中,我們規定每次將陣列擴容至之前的 2 倍。 + +```src +[file]{my_list}-[class]{my_list}-[func]{} +``` diff --git a/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png new file mode 100644 index 0000000000..390961738e Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png new file mode 100644 index 0000000000..436a509d70 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.md b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.md new file mode 100644 index 0000000000..8f315c5ddc --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -0,0 +1,71 @@ +# 記憶體與快取 * + +在本章的前兩節中,我們探討了陣列和鏈結串列這兩種基礎且重要的資料結構,它們分別代表了“連續儲存”和“分散儲存”兩種物理結構。 + +實際上,**物理結構在很大程度上決定了程式對記憶體和快取的使用效率**,進而影響演算法程式的整體效能。 + +## 計算機儲存裝置 + +計算機中包括三種類型的儲存裝置:硬碟(hard disk)記憶體(random-access memory, RAM)快取(cache memory)。下表展示了它們在計算機系統中的不同角色和效能特點。 + +

  計算機的儲存裝置

+ +| | 硬碟 | 記憶體 | 快取 | +| ------ | ---------------------------------------- | -------------------------------------- | ------------------------------------------------- | +| 用途 | 長期儲存資料,包括作業系統、程式、檔案等 | 臨時儲存當前執行的程式和正在處理的資料 | 儲存經常訪問的資料和指令,減少 CPU 訪問記憶體的次數 | +| 易失性 | 斷電後資料不會丟失 | 斷電後資料會丟失 | 斷電後資料會丟失 | +| 容量 | 較大,TB 級別 | 較小,GB 級別 | 非常小,MB 級別 | +| 速度 | 較慢,幾百到幾千 MB/s | 較快,幾十 GB/s | 非常快,幾十到幾百 GB/s | +| 價格 | 較便宜,幾毛到幾元 / GB | 較貴,幾十到幾百元 / GB | 非常貴,隨 CPU 打包計價 | + +我們可以將計算機儲存系統想象為下圖所示的金字塔結構。越靠近金字塔頂端的儲存裝置的速度越快、容量越小、成本越高。這種多層級的設計並非偶然,而是計算機科學家和工程師們經過深思熟慮的結果。 + +- **硬碟難以被記憶體取代**。首先,記憶體中的資料在斷電後會丟失,因此它不適合長期儲存資料;其次,記憶體的成本是硬碟的幾十倍,這使得它難以在消費者市場普及。 +- **快取的大容量和高速度難以兼得**。隨著 L1、L2、L3 快取的容量逐步增大,其物理尺寸會變大,與 CPU 核心之間的物理距離會變遠,從而導致資料傳輸時間增加,元素訪問延遲變高。在當前技術下,多層級的快取結構是容量、速度和成本之間的最佳平衡點。 + +![計算機儲存系統](ram_and_cache.assets/storage_pyramid.png) + +!!! tip + + 計算機的儲存層次結構體現了速度、容量和成本三者之間的精妙平衡。實際上,這種權衡普遍存在於所有工業領域,它要求我們在不同的優勢和限制之間找到最佳平衡點。 + +總的來說,**硬碟用於長期儲存大量資料,記憶體用於臨時儲存程式執行中正在處理的資料,而快取則用於儲存經常訪問的資料和指令**,以提高程式執行效率。三者共同協作,確保計算機系統高效執行。 + +如下圖所示,在程式執行時,資料會從硬碟中被讀取到記憶體中,供 CPU 計算使用。快取可以看作 CPU 的一部分,**它透過智慧地從記憶體載入資料**,給 CPU 提供高速的資料讀取,從而顯著提升程式的執行效率,減少對較慢的記憶體的依賴。 + +![硬碟、記憶體和快取之間的資料流通](ram_and_cache.assets/computer_storage_devices.png) + +## 資料結構的記憶體效率 + +在記憶體空間利用方面,陣列和鏈結串列各自具有優勢和侷限性。 + +一方面,**記憶體是有限的,且同一塊記憶體不能被多個程式共享**,因此我們希望資料結構能夠儘可能高效地利用空間。陣列的元素緊密排列,不需要額外的空間來儲存鏈結串列節點間的引用(指標),因此空間效率更高。然而,陣列需要一次性分配足夠的連續記憶體空間,這可能導致記憶體浪費,陣列擴容也需要額外的時間和空間成本。相比之下,鏈結串列以“節點”為單位進行動態記憶體分配和回收,提供了更大的靈活性。 + +另一方面,在程式執行時,**隨著反覆申請與釋放記憶體,空閒記憶體的碎片化程度會越來越高**,從而導致記憶體的利用效率降低。陣列由於其連續的儲存方式,相對不容易導致記憶體碎片化。相反,鏈結串列的元素是分散儲存的,在頻繁的插入與刪除操作中,更容易導致記憶體碎片化。 + +## 資料結構的快取效率 + +快取雖然在空間容量上遠小於記憶體,但它比記憶體快得多,在程式執行速度上起著至關重要的作用。由於快取的容量有限,只能儲存一小部分頻繁訪問的資料,因此當 CPU 嘗試訪問的資料不在快取中時,就會發生快取未命中(cache miss),此時 CPU 不得不從速度較慢的記憶體中載入所需資料。 + +顯然,**“快取未命中”越少,CPU 讀寫資料的效率就越高**,程式效能也就越好。我們將 CPU 從快取中成功獲取資料的比例稱為快取命中率(cache hit rate),這個指標通常用來衡量快取效率。 + +為了儘可能達到更高的效率,快取會採取以下資料載入機制。 + +- **快取行**:快取不是單個位元組地儲存與載入資料,而是以快取行為單位。相比於單個位元組的傳輸,快取行的傳輸形式更加高效。 +- **預取機制**:處理器會嘗試預測資料訪問模式(例如順序訪問、固定步長跳躍訪問等),並根據特定模式將資料載入至快取之中,從而提升命中率。 +- **空間區域性**:如果一個數據被訪問,那麼它附近的資料可能近期也會被訪問。因此,快取在載入某一資料時,也會載入其附近的資料,以提高命中率。 +- **時間區域性**:如果一個數據被訪問,那麼它在不久的將來很可能再次被訪問。快取利用這一原理,透過保留最近訪問過的資料來提高命中率。 + +實際上,**陣列和鏈結串列對快取的利用效率是不同的**,主要體現在以下幾個方面。 + +- **佔用空間**:鏈結串列元素比陣列元素佔用空間更多,導致快取中容納的有效資料量更少。 +- **快取行**:鏈結串列資料分散在記憶體各處,而快取是“按行載入”的,因此載入到無效資料的比例更高。 +- **預取機制**:陣列比鏈結串列的資料訪問模式更具“可預測性”,即系統更容易猜出即將被載入的資料。 +- **空間區域性**:陣列被儲存在集中的記憶體空間中,因此被載入資料附近的資料更有可能即將被訪問。 + +總體而言,**陣列具有更高的快取命中率,因此它在操作效率上通常優於鏈結串列**。這使得在解決演算法問題時,基於陣列實現的資料結構往往更受歡迎。 + +需要注意的是,**高快取效率並不意味著陣列在所有情況下都優於鏈結串列**。實際應用中選擇哪種資料結構,應根據具體需求來決定。例如,陣列和鏈結串列都可以實現“堆疊”資料結構(下一章會詳細介紹),但它們適用於不同場景。 + +- 在做演算法題時,我們會傾向於選擇基於陣列實現的堆疊,因為它提供了更高的操作效率和隨機訪問的能力,代價僅是需要預先為陣列分配一定的記憶體空間。 +- 如果資料量非常大、動態性很高、堆疊的預期大小難以估計,那麼基於鏈結串列實現的堆疊更加合適。鏈結串列能夠將大量資料分散儲存於記憶體的不同部分,並且避免了陣列擴容產生的額外開銷。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/summary.md b/zh-hant/docs/chapter_array_and_linkedlist/summary.md new file mode 100644 index 0000000000..7df5b5e74d --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/summary.md @@ -0,0 +1,86 @@ +# 小結 + +### 重點回顧 + +- 陣列和鏈結串列是兩種基本的資料結構,分別代表資料在計算機記憶體中的兩種儲存方式:連續空間儲存和分散空間儲存。兩者的特點呈現出互補的特性。 +- 陣列支持隨機訪問、佔用記憶體較少;但插入和刪除元素效率低,且初始化後長度不可變。 +- 鏈結串列透過更改引用(指標)實現高效的節點插入與刪除,且可以靈活調整長度;但節點訪問效率低、佔用記憶體較多。常見的鏈結串列型別包括單向鏈結串列、環形鏈結串列、雙向鏈結串列。 +- 串列是一種支持增刪查改的元素有序集合,通常基於動態陣列實現。它保留了陣列的優勢,同時可以靈活調整長度。 +- 串列的出現大幅提高了陣列的實用性,但可能導致部分記憶體空間浪費。 +- 程式執行時,資料主要儲存在記憶體中。陣列可提供更高的記憶體空間效率,而鏈結串列則在記憶體使用上更加靈活。 +- 快取透過快取行、預取機制以及空間區域性和時間區域性等資料載入機制,為 CPU 提供快速資料訪問,顯著提升程式的執行效率。 +- 由於陣列具有更高的快取命中率,因此它通常比鏈結串列更高效。在選擇資料結構時,應根據具體需求和場景做出恰當選擇。 + +### Q & A + +**Q**:陣列儲存在堆疊上和儲存在堆積上,對時間效率和空間效率是否有影響? + +儲存在堆疊上和堆積上的陣列都被儲存在連續記憶體空間內,資料操作效率基本一致。然而,堆疊和堆積具有各自的特點,從而導致以下不同點。 + +1. 分配和釋放效率:堆疊是一塊較小的記憶體,分配由編譯器自動完成;而堆積記憶體相對更大,可以在程式碼中動態分配,更容易碎片化。因此,堆積上的分配和釋放操作通常比堆疊上的慢。 +2. 大小限制:堆疊記憶體相對較小,堆積的大小一般受限於可用記憶體。因此堆積更加適合儲存大型陣列。 +3. 靈活性:堆疊上的陣列的大小需要在編譯時確定,而堆積上的陣列的大小可以在執行時動態確定。 + +**Q**:為什麼陣列要求相同型別的元素,而在鏈結串列中卻沒有強調相同型別呢? + +鏈結串列由節點組成,節點之間透過引用(指標)連線,各個節點可以儲存不同型別的資料,例如 `int`、`double`、`string`、`object` 等。 + +相對地,陣列元素則必須是相同型別的,這樣才能透過計算偏移量來獲取對應元素位置。例如,陣列同時包含 `int` 和 `long` 兩種型別,單個元素分別佔用 4 位元組和 8 位元組 ,此時就不能用以下公式計算偏移量了,因為陣列中包含了兩種“元素長度”。 + +```shell +# 元素記憶體位址 = 陣列記憶體位址(首元素記憶體位址) + 元素長度 * 元素索引 +``` + +**Q**:刪除節點 `P` 後,是否需要把 `P.next` 設為 `None` 呢? + +不修改 `P.next` 也可以。從該鏈結串列的角度看,從頭節點走訪到尾節點已經不會遇到 `P` 了。這意味著節點 `P` 已經從鏈結串列中刪除了,此時節點 `P` 指向哪裡都不會對該鏈結串列產生影響。 + +從資料結構與演算法(做題)的角度看,不斷開沒有關係,只要保證程式的邏輯是正確的就行。從標準庫的角度看,斷開更加安全、邏輯更加清晰。如果不斷開,假設被刪除節點未被正常回收,那麼它會影響後繼節點的記憶體回收。 + +**Q**:在鏈結串列中插入和刪除操作的時間複雜度是 $O(1)$ 。但是增刪之前都需要 $O(n)$ 的時間查詢元素,那為什麼時間複雜度不是 $O(n)$ 呢? + +如果是先查詢元素、再刪除元素,時間複雜度確實是 $O(n)$ 。然而,鏈結串列的 $O(1)$ 增刪的優勢可以在其他應用上得到體現。例如,雙向佇列適合使用鏈結串列實現,我們維護一個指標變數始終指向頭節點、尾節點,每次插入與刪除操作都是 $O(1)$ 。 + +**Q**:圖“鏈結串列定義與儲存方式”中,淺藍色的儲存節點指標是佔用一塊記憶體位址嗎?還是和節點值各佔一半呢? + +該示意圖只是定性表示,定量表示需要根據具體情況進行分析。 + +- 不同型別的節點值佔用的空間是不同的,比如 `int`、`long`、`double` 和例項物件等。 +- 指標變數佔用的記憶體空間大小根據所使用的作業系統及編譯環境而定,大多為 8 位元組或 4 位元組。 + +**Q**:在串列末尾新增元素是否時時刻刻都為 $O(1)$ ? + +如果新增元素時超出串列長度,則需要先擴容串列再新增。系統會申請一塊新的記憶體,並將原串列的所有元素搬運過去,這時候時間複雜度就會是 $O(n)$ 。 + +**Q**:“串列的出現極大地提高了陣列的實用性,但可能導致部分記憶體空間浪費”,這裡的空間浪費是指額外增加的變數如容量、長度、擴容倍數所佔的記憶體嗎? + +這裡的空間浪費主要有兩方面含義:一方面,串列都會設定一個初始長度,我們不一定需要用這麼多;另一方面,為了防止頻繁擴容,擴容一般會乘以一個係數,比如 $\times 1.5$ 。這樣一來,也會出現很多空位,我們通常不能完全填滿它們。 + +**Q**:在 Python 中初始化 `n = [1, 2, 3]` 後,這 3 個元素的位址是相連的,但是初始化 `m = [2, 1, 3]` 會發現它們每個元素的 id 並不是連續的,而是分別跟 `n` 中的相同。這些元素的位址不連續,那麼 `m` 還是陣列嗎? + +假如把串列元素換成鏈結串列節點 `n = [n1, n2, n3, n4, n5]` ,通常情況下這 5 個節點物件也分散儲存在記憶體各處。然而,給定一個串列索引,我們仍然可以在 $O(1)$ 時間內獲取節點記憶體位址,從而訪問到對應的節點。這是因為陣列中儲存的是節點的引用,而非節點本身。 + +與許多語言不同,Python 中的數字也被包裝為物件,串列中儲存的不是數字本身,而是對數字的引用。因此,我們會發現兩個陣列中的相同數字擁有同一個 id ,並且這些數字的記憶體位址無須連續。 + +**Q**:C++ STL 裡面的 `std::list` 已經實現了雙向鏈結串列,但好像一些演算法書上不怎麼直接使用它,是不是因為有什麼侷限性呢? + +一方面,我們往往更青睞使用陣列實現演算法,而只在必要時才使用鏈結串列,主要有兩個原因。 + +- 空間開銷:由於每個元素需要兩個額外的指標(一個用於前一個元素,一個用於後一個元素),所以 `std::list` 通常比 `std::vector` 更佔用空間。 +- 快取不友好:由於資料不是連續存放的,因此 `std::list` 對快取的利用率較低。一般情況下,`std::vector` 的效能會更好。 + +另一方面,必要使用鏈結串列的情況主要是二元樹和圖。堆疊和佇列往往會使用程式語言提供的 `stack` 和 `queue` ,而非鏈結串列。 + +**Q**:操作 `res = [[0]] * n` 生成了一個二維串列,其中每一個 `[0]` 都是獨立的嗎? + +不是獨立的。此二維串列中,所有的 `[0]` 實際上是同一個物件的引用。如果我們修改其中一個元素,會發現所有的對應元素都會隨之改變。 + +如果希望二維串列中的每個 `[0]` 都是獨立的,可以使用 `res = [[0] for _ in range(n)]` 來實現。這種方式的原理是初始化了 $n$ 個獨立的 `[0]` 串列物件。 + +**Q**:操作 `res = [0] * n` 生成了一個串列,其中每一個整數 0 都是獨立的嗎? + +在該串列中,所有整數 0 都是同一個物件的引用。這是因為 Python 對小整數(通常是 -5 到 256)採用了快取池機制,以便最大化物件複用,從而提升效能。 + +雖然它們指向同一個物件,但我們仍然可以獨立修改串列中的每個元素,這是因為 Python 的整數是“不可變物件”。當我們修改某個元素時,實際上是切換為另一個物件的引用,而不是改變原有物件本身。 + +然而,當串列元素是“可變物件”時(例如串列、字典或類別例項等),修改某個元素會直接改變該物件本身,所有引用該物件的元素都會產生相同變化。 diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png new file mode 100644 index 0000000000..2b9ffd5f77 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png new file mode 100644 index 0000000000..eb5408121d Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png new file mode 100644 index 0000000000..af2f3486f6 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png new file mode 100644 index 0000000000..c1e921bc4e Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png new file mode 100644 index 0000000000..7e6abf1083 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png new file mode 100644 index 0000000000..e40e5feec6 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png new file mode 100644 index 0000000000..482724d7f0 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png new file mode 100644 index 0000000000..fb24940ee1 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png new file mode 100644 index 0000000000..1ff8ac2926 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png new file mode 100644 index 0000000000..ae5f90f57b Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png new file mode 100644 index 0000000000..83dc013c0f Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png new file mode 100644 index 0000000000..84771b5f01 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png new file mode 100644 index 0000000000..7061dfc7ba Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png new file mode 100644 index 0000000000..49d0c7b953 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md new file mode 100644 index 0000000000..56cbc9cc78 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md @@ -0,0 +1,509 @@ +# 回溯演算法 + +回溯演算法(backtracking algorithm)是一種透過窮舉來解決問題的方法,它的核心思想是從一個初始狀態出發,暴力搜尋所有可能的解決方案,當遇到正確的解則將其記錄,直到找到解或者嘗試了所有可能的選擇都無法找到解為止。 + +回溯演算法通常採用“深度優先搜尋”來走訪解空間。在“二元樹”章節中,我們提到前序、中序和後序走訪都屬於深度優先搜尋。接下來,我們利用前序走訪構造一個回溯問題,逐步瞭解回溯演算法的工作原理。 + +!!! question "例題一" + + 給定一棵二元樹,搜尋並記錄所有值為 $7$ 的節點,請返回節點串列。 + +對於此題,我們前序走訪這棵樹,並判斷當前節點的值是否為 $7$ ,若是,則將該節點的值加入結果串列 `res` 之中。相關過程實現如下圖和以下程式碼所示: + +```src +[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} +``` + +![在前序走訪中搜索節點](backtracking_algorithm.assets/preorder_find_nodes.png) + +## 嘗試與回退 + +**之所以稱之為回溯演算法,是因為該演算法在搜尋解空間時會採用“嘗試”與“回退”的策略**。當演算法在搜尋過程中遇到某個狀態無法繼續前進或無法得到滿足條件的解時,它會撤銷上一步的選擇,退回到之前的狀態,並嘗試其他可能的選擇。 + +對於例題一,訪問每個節點都代表一次“嘗試”,而越過葉節點或返回父節點的 `return` 則表示“回退”。 + +值得說明的是,**回退並不僅僅包括函式返回**。為解釋這一點,我們對例題一稍作拓展。 + +!!! question "例題二" + + 在二元樹中搜索所有值為 $7$ 的節點,**請返回根節點到這些節點的路徑**。 + +在例題一程式碼的基礎上,我們需要藉助一個串列 `path` 記錄訪問過的節點路徑。當訪問到值為 $7$ 的節點時,則複製 `path` 並新增進結果串列 `res` 。走訪完成後,`res` 中儲存的就是所有的解。程式碼如下所示: + +```src +[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} +``` + +在每次“嘗試”中,我們透過將當前節點新增進 `path` 來記錄路徑;而在“回退”前,我們需要將該節點從 `path` 中彈出,**以恢復本次嘗試之前的狀態**。 + +觀察下圖所示的過程,**我們可以將嘗試和回退理解為“前進”與“撤銷”**,兩個操作互為逆向。 + +=== "<1>" + ![嘗試與回退](backtracking_algorithm.assets/preorder_find_paths_step1.png) + +=== "<2>" + ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) + +=== "<3>" + ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) + +=== "<4>" + ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) + +=== "<5>" + ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) + +=== "<6>" + ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) + +=== "<7>" + ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) + +=== "<8>" + ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) + +=== "<9>" + ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) + +=== "<10>" + ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) + +=== "<11>" + ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) + +## 剪枝 + +複雜的回溯問題通常包含一個或多個約束條件,**約束條件通常可用於“剪枝”**。 + +!!! question "例題三" + + 在二元樹中搜索所有值為 $7$ 的節點,請返回根節點到這些節點的路徑,**並要求路徑中不包含值為 $3$ 的節點**。 + +為了滿足以上約束條件,**我們需要新增剪枝操作**:在搜尋過程中,若遇到值為 $3$ 的節點,則提前返回,不再繼續搜尋。程式碼如下所示: + +```src +[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} +``` + +“剪枝”是一個非常形象的名詞。如下圖所示,在搜尋過程中,**我們“剪掉”了不滿足約束條件的搜尋分支**,避免許多無意義的嘗試,從而提高了搜尋效率。 + +![根據約束條件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png) + +## 框架程式碼 + +接下來,我們嘗試將回溯的“嘗試、回退、剪枝”的主體框架提煉出來,提升程式碼的通用性。 + +在以下框架程式碼中,`state` 表示問題的當前狀態,`choices` 表示當前狀態下可以做出的選擇: + +=== "Python" + + ```python title="" + def backtrack(state: State, choices: list[choice], res: list[state]): + """回溯演算法框架""" + # 判斷是否為解 + if is_solution(state): + # 記錄解 + record_solution(state, res) + # 不再繼續搜尋 + return + # 走訪所有選擇 + for choice in choices: + # 剪枝:判斷選擇是否合法 + if is_valid(state, choice): + # 嘗試:做出選擇,更新狀態 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice) + ``` + +=== "C++" + + ```cpp title="" + /* 回溯演算法框架 */ + void backtrack(State *state, vector &choices, vector &res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (Choice choice : choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "Java" + + ```java title="" + /* 回溯演算法框架 */ + void backtrack(State state, List choices, List res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (Choice choice : choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + /* 回溯演算法框架 */ + void Backtrack(State state, List choices, List res) { + // 判斷是否為解 + if (IsSolution(state)) { + // 記錄解 + RecordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + foreach (Choice choice in choices) { + // 剪枝:判斷選擇是否合法 + if (IsValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + MakeChoice(state, choice); + Backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + UndoChoice(state, choice); + } + } + } + ``` + +=== "Go" + + ```go title="" + /* 回溯演算法框架 */ + func backtrack(state *State, choices []Choice, res *[]State) { + // 判斷是否為解 + if isSolution(state) { + // 記錄解 + recordSolution(state, res) + // 不再繼續搜尋 + return + } + // 走訪所有選擇 + for _, choice := range choices { + // 剪枝:判斷選擇是否合法 + if isValid(state, choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + backtrack(state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 回溯演算法框架 */ + func backtrack(state: inout State, choices: [Choice], res: inout [State]) { + // 判斷是否為解 + if isSolution(state: state) { + // 記錄解 + recordSolution(state: state, res: &res) + // 不再繼續搜尋 + return + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:判斷選擇是否合法 + if isValid(state: state, choice: choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state: &state, choice: choice) + backtrack(state: &state, choices: choices, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state: &state, choice: choice) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 回溯演算法框架 */ + function backtrack(state, choices, res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (let choice of choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 回溯演算法框架 */ + function backtrack(state: State, choices: Choice[], res: State[]): void { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (let choice of choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 回溯演算法框架 */ + void backtrack(State state, List, List res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (Choice choice in choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + /* 回溯演算法框架 */ + fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { + // 判斷是否為解 + if is_solution(state) { + // 記錄解 + record_solution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:判斷選擇是否合法 + if is_valid(state, choice) { + // 嘗試:做出選擇,更新狀態 + make_choice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice); + } + } + } + ``` + +=== "C" + + ```c title="" + /* 回溯演算法框架 */ + void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res, numRes); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (int i = 0; i < numChoices; i++) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, &choices[i])) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, &choices[i]); + backtrack(state, choices, numChoices, res, numRes); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, &choices[i]); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 回溯演算法框架 */ + fun backtrack(state: State?, choices: List, res: List?) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res) + // 不再繼續搜尋 + return + } + // 走訪所有選擇 + for (choice in choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + backtrack(state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } + } + ``` + +=== "Ruby" + + ```ruby title="" + ### 回溯演算法框架 ### + def backtrack(state, choices, res) + # 判斷是否為解 + if is_solution?(state) + # 記錄解 + record_solution(state, res) + return + end + + # 走訪所有選擇 + for choice in choices + # 剪枝:判斷選擇是否合法 + if is_valid?(state, choice) + # 嘗試:做出選擇,更新狀態 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice) + end + end + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +接下來,我們基於框架程式碼來解決例題三。狀態 `state` 為節點走訪路徑,選擇 `choices` 為當前節點的左子節點和右子節點,結果 `res` 是路徑串列: + +```src +[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} +``` + +根據題意,我們在找到值為 $7$ 的節點後應該繼續搜尋,**因此需要將記錄解之後的 `return` 語句刪除**。下圖對比了保留或刪除 `return` 語句的搜尋過程。 + +![保留與刪除 return 的搜尋過程對比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) + +相比基於前序走訪的程式碼實現,基於回溯演算法框架的程式碼實現雖然顯得囉唆,但通用性更好。實際上,**許多回溯問題可以在該框架下解決**。我們只需根據具體問題來定義 `state` 和 `choices` ,並實現框架中的各個方法即可。 + +## 常用術語 + +為了更清晰地分析演算法問題,我們總結一下回溯演算法中常用術語的含義,並對照例題三給出對應示例,如下表所示。 + +

  常見的回溯演算法術語

+ +| 名詞 | 定義 | 例題三 | +| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| 解(solution) | 解是滿足問題特定條件的答案,可能有一個或多個 | 根節點到節點 $7$ 的滿足約束條件的所有路徑 | +| 約束條件(constraint) | 約束條件是問題中限制解的可行性的條件,通常用於剪枝 | 路徑中不包含節點 $3$ | +| 狀態(state) | 狀態表示問題在某一時刻的情況,包括已經做出的選擇 | 當前已訪問的節點路徑,即 `path` 節點串列 | +| 嘗試(attempt) | 嘗試是根據可用選擇來探索解空間的過程,包括做出選擇,更新狀態,檢查是否為解 | 遞迴訪問左(右)子節點,將節點新增進 `path` ,判斷節點的值是否為 $7$ | +| 回退(backtracking) | 回退指遇到不滿足約束條件的狀態時,撤銷前面做出的選擇,回到上一個狀態 | 當越過葉節點、結束節點訪問、遇到值為 $3$ 的節點時終止搜尋,函式返回 | +| 剪枝(pruning) | 剪枝是根據問題特性和約束條件避免無意義的搜尋路徑的方法,可提高搜尋效率 | 當遇到值為 $3$ 的節點時,則不再繼續搜尋 | + +!!! tip + + 問題、解、狀態等概念是通用的,在分治、回溯、動態規劃、貪婪等演算法中都有涉及。 + +## 優點與侷限性 + +回溯演算法本質上是一種深度優先搜尋演算法,它嘗試所有可能的解決方案直到找到滿足條件的解。這種方法的優點在於能夠找到所有可能的解決方案,而且在合理的剪枝操作下,具有很高的效率。 + +然而,在處理大規模或者複雜問題時,**回溯演算法的執行效率可能難以接受**。 + +- **時間**:回溯演算法通常需要走訪狀態空間的所有可能,時間複雜度可以達到指數階或階乘階。 +- **空間**:在遞迴呼叫中需要儲存當前的狀態(例如路徑、用於剪枝的輔助變數等),當深度很大時,空間需求可能會變得很大。 + +即便如此,**回溯演算法仍然是某些搜尋問題和約束滿足問題的最佳解決方案**。對於這些問題,由於無法預測哪些選擇可生成有效的解,因此我們必須對所有可能的選擇進行走訪。在這種情況下,**關鍵是如何最佳化效率**,常見的效率最佳化方法有兩種。 + +- **剪枝**:避免搜尋那些肯定不會產生解的路徑,從而節省時間和空間。 +- **啟發式搜尋**:在搜尋過程中引入一些策略或者估計值,從而優先搜尋最有可能產生有效解的路徑。 + +## 回溯典型例題 + +回溯演算法可用於解決許多搜尋問題、約束滿足問題和組合最佳化問題。 + +**搜尋問題**:這類問題的目標是找到滿足特定條件的解決方案。 + +- 全排列問題:給定一個集合,求出其所有可能的排列組合。 +- 子集和問題:給定一個集合和一個目標和,找到集合中所有和為目標和的子集。 +- 河內塔問題:給定三根柱子和一系列大小不同的圓盤,要求將所有圓盤從一根柱子移動到另一根柱子,每次只能移動一個圓盤,且不能將大圓盤放在小圓盤上。 + +**約束滿足問題**:這類問題的目標是找到滿足所有約束條件的解。 + +- $n$ 皇后:在 $n \times n$ 的棋盤上放置 $n$ 個皇后,使得它們互不攻擊。 +- 數獨:在 $9 \times 9$ 的網格中填入數字 $1$ ~ $9$ ,使得每行、每列和每個 $3 \times 3$ 子網格中的數字不重複。 +- 圖著色問題:給定一個無向圖,用最少的顏色給圖的每個頂點著色,使得相鄰頂點顏色不同。 + +**組合最佳化問題**:這類問題的目標是在一個組合空間中找到滿足某些條件的最優解。 + +- 0-1 背包問題:給定一組物品和一個背包,每個物品有一定的價值和重量,要求在背包容量限制內,選擇物品使得總價值最大。 +- 旅行商問題:在一個圖中,從一個點出發,訪問所有其他點恰好一次後返回起點,求最短路徑。 +- 最大團問題:給定一個無向圖,找到最大的完全子圖,即子圖中的任意兩個頂點之間都有邊相連。 + +請注意,對於許多組合最佳化問題,回溯不是最優解決方案。 + +- 0-1 背包問題通常使用動態規劃解決,以達到更高的時間效率。 +- 旅行商是一個著名的 NP-Hard 問題,常用解法有遺傳演算法和蟻群演算法等。 +- 最大團問題是圖論中的一個經典問題,可用貪婪演算法等啟發式演算法來解決。 diff --git a/zh-hant/docs/chapter_backtracking/index.md b/zh-hant/docs/chapter_backtracking/index.md new file mode 100644 index 0000000000..cb547d1e2d --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/index.md @@ -0,0 +1,9 @@ +# 回溯 + +![回溯](../assets/covers/chapter_backtracking.jpg) + +!!! abstract + + 我們如同迷宮中的探索者,在前進的道路上可能會遇到困難。 + + 回溯的力量讓我們能夠重新開始,不斷嘗試,最終找到通往光明的出口。 diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png new file mode 100644 index 0000000000..74a2b01fcb Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png new file mode 100644 index 0000000000..cb0e41e270 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png new file mode 100644 index 0000000000..dd97d9a2f0 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png new file mode 100644 index 0000000000..1e841201a8 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.md b/zh-hant/docs/chapter_backtracking/n_queens_problem.md new file mode 100644 index 0000000000..daebf27075 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/n_queens_problem.md @@ -0,0 +1,53 @@ +# n 皇后問題 + +!!! question + + 根據國際象棋的規則,皇后可以攻擊與同處一行、一列或一條斜線上的棋子。給定 $n$ 個皇后和一個 $n \times n$ 大小的棋盤,尋找使得所有皇后之間無法相互攻擊的擺放方案。 + +如下圖所示,當 $n = 4$ 時,共可以找到兩個解。從回溯演算法的角度看,$n \times n$ 大小的棋盤共有 $n^2$ 個格子,給出了所有的選擇 `choices` 。在逐個放置皇后的過程中,棋盤狀態在不斷地變化,每個時刻的棋盤就是狀態 `state` 。 + +![4 皇后問題的解](n_queens_problem.assets/solution_4_queens.png) + +下圖展示了本題的三個約束條件:**多個皇后不能在同一行、同一列、同一條對角線上**。值得注意的是,對角線分為主對角線 `\` 和次對角線 `/` 兩種。 + +![n 皇后問題的約束條件](n_queens_problem.assets/n_queens_constraints.png) + +### 逐行放置策略 + +皇后的數量和棋盤的行數都為 $n$ ,因此我們容易得到一個推論:**棋盤每行都允許且只允許放置一個皇后**。 + +也就是說,我們可以採取逐行放置策略:從第一行開始,在每行放置一個皇后,直至最後一行結束。 + +下圖所示為 4 皇后問題的逐行放置過程。受畫幅限制,下圖僅展開了第一行的其中一個搜尋分支,並且將不滿足列約束和對角線約束的方案都進行了剪枝。 + +![逐行放置策略](n_queens_problem.assets/n_queens_placing.png) + +從本質上看,**逐行放置策略起到了剪枝的作用**,它避免了同一行出現多個皇后的所有搜尋分支。 + +### 列與對角線剪枝 + +為了滿足列約束,我們可以利用一個長度為 $n$ 的布林型陣列 `cols` 記錄每一列是否有皇后。在每次決定放置前,我們透過 `cols` 將已有皇后的列進行剪枝,並在回溯中動態更新 `cols` 的狀態。 + +!!! tip + + 請注意,矩陣的起點位於左上角,其中行索引從上到下增加,列索引從左到右增加。 + +那麼,如何處理對角線約束呢?設棋盤中某個格子的行列索引為 $(row, col)$ ,選定矩陣中的某條主對角線,我們發現該對角線上所有格子的行索引減列索引都相等,**即主對角線上所有格子的 $row - col$ 為恆定值**。 + +也就是說,如果兩個格子滿足 $row_1 - col_1 = row_2 - col_2$ ,則它們一定處在同一條主對角線上。利用該規律,我們可以藉助下圖所示的陣列 `diags1` 記錄每條主對角線上是否有皇后。 + +同理,**次對角線上的所有格子的 $row + col$ 是恆定值**。我們同樣也可以藉助陣列 `diags2` 來處理次對角線約束。 + +![處理列約束和對角線約束](n_queens_problem.assets/n_queens_cols_diagonals.png) + +### 程式碼實現 + +請注意,$n$ 維方陣中 $row - col$ 的範圍是 $[-n + 1, n - 1]$ ,$row + col$ 的範圍是 $[0, 2n - 2]$ ,所以主對角線和次對角線的數量都為 $2n - 1$ ,即陣列 `diags1` 和 `diags2` 的長度都為 $2n - 1$ 。 + +```src +[file]{n_queens}-[class]{}-[func]{n_queens} +``` + +逐行放置 $n$ 次,考慮列約束,則從第一行到最後一行分別有 $n$、$n-1$、$\dots$、$2$、$1$ 個選擇,使用 $O(n!)$ 時間。當記錄解時,需要複製矩陣 `state` 並新增進 `res` ,複製操作使用 $O(n^2)$ 時間。因此,**總體時間複雜度為 $O(n! \cdot n^2)$** 。實際上,根據對角線約束的剪枝也能夠大幅縮小搜尋空間,因而搜尋效率往往優於以上時間複雜度。 + +陣列 `state` 使用 $O(n^2)$ 空間,陣列 `cols`、`diags1` 和 `diags2` 皆使用 $O(n)$ 空間。最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。因此,**空間複雜度為 $O(n^2)$** 。 diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png new file mode 100644 index 0000000000..5f82d19158 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png new file mode 100644 index 0000000000..8032c5559d Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png new file mode 100644 index 0000000000..892a67116e Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png new file mode 100644 index 0000000000..7661d83af9 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png new file mode 100644 index 0000000000..b948ccdb4c Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.md b/zh-hant/docs/chapter_backtracking/permutations_problem.md new file mode 100644 index 0000000000..58fac34f77 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/permutations_problem.md @@ -0,0 +1,95 @@ +# 全排列問題 + +全排列問題是回溯演算法的一個典型應用。它的定義是在給定一個集合(如一個陣列或字串)的情況下,找出其中元素的所有可能的排列。 + +下表列舉了幾個示例資料,包括輸入陣列和對應的所有排列。 + +

  全排列示例

+ +| 輸入陣列 | 所有排列 | +| :---------- | :----------------------------------------------------------------- | +| $[1]$ | $[1]$ | +| $[1, 2]$ | $[1, 2], [2, 1]$ | +| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | + +## 無相等元素的情況 + +!!! question + + 輸入一個整數陣列,其中不包含重複元素,返回所有可能的排列。 + +從回溯演算法的角度看,**我們可以把生成排列的過程想象成一系列選擇的結果**。假設輸入陣列為 $[1, 2, 3]$ ,如果我們先選擇 $1$ ,再選擇 $3$ ,最後選擇 $2$ ,則獲得排列 $[1, 3, 2]$ 。回退表示撤銷一個選擇,之後繼續嘗試其他選擇。 + +從回溯程式碼的角度看,候選集合 `choices` 是輸入陣列中的所有元素,狀態 `state` 是直至目前已被選擇的元素。請注意,每個元素只允許被選擇一次,**因此 `state` 中的所有元素都應該是唯一的**。 + +如下圖所示,我們可以將搜尋過程展開成一棵遞迴樹,樹中的每個節點代表當前狀態 `state` 。從根節點開始,經過三輪選擇後到達葉節點,每個葉節點都對應一個排列。 + +![全排列的遞迴樹](permutations_problem.assets/permutations_i.png) + +### 重複選擇剪枝 + +為了實現每個元素只被選擇一次,我們考慮引入一個布林型陣列 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被選擇,並基於它實現以下剪枝操作。 + +- 在做出選擇 `choice[i]` 後,我們就將 `selected[i]` 賦值為 $\text{True}$ ,代表它已被選擇。 +- 走訪選擇串列 `choices` 時,跳過所有已被選擇的節點,即剪枝。 + +如下圖所示,假設我們第一輪選擇 1 ,第二輪選擇 3 ,第三輪選擇 2 ,則需要在第二輪剪掉元素 1 的分支,在第三輪剪掉元素 1 和元素 3 的分支。 + +![全排列剪枝示例](permutations_problem.assets/permutations_i_pruning.png) + +觀察上圖發現,該剪枝操作將搜尋空間大小從 $O(n^n)$ 減小至 $O(n!)$ 。 + +### 程式碼實現 + +想清楚以上資訊之後,我們就可以在框架程式碼中做“完形填空”了。為了縮短整體程式碼,我們不單獨實現框架程式碼中的各個函式,而是將它們展開在 `backtrack()` 函式中: + +```src +[file]{permutations_i}-[class]{}-[func]{permutations_i} +``` + +## 考慮相等元素的情況 + +!!! question + + 輸入一個整數陣列,**陣列中可能包含重複元素**,返回所有不重複的排列。 + +假設輸入陣列為 $[1, 1, 2]$ 。為了方便區分兩個重複元素 $1$ ,我們將第二個 $1$ 記為 $\hat{1}$ 。 + +如下圖所示,上述方法生成的排列有一半是重複的。 + +![重複排列](permutations_problem.assets/permutations_ii.png) + +那麼如何去除重複的排列呢?最直接地,考慮藉助一個雜湊集合,直接對排列結果進行去重。然而這樣做不夠優雅,**因為生成重複排列的搜尋分支沒有必要,應當提前識別並剪枝**,這樣可以進一步提升演算法效率。 + +### 相等元素剪枝 + +觀察下圖,在第一輪中,選擇 $1$ 或選擇 $\hat{1}$ 是等價的,在這兩個選擇之下生成的所有排列都是重複的。因此應該把 $\hat{1}$ 剪枝。 + +同理,在第一輪選擇 $2$ 之後,第二輪選擇中的 $1$ 和 $\hat{1}$ 也會產生重複分支,因此也應將第二輪的 $\hat{1}$ 剪枝。 + +從本質上看,**我們的目標是在某一輪選擇中,保證多個相等的元素僅被選擇一次**。 + +![重複排列剪枝](permutations_problem.assets/permutations_ii_pruning.png) + +### 程式碼實現 + +在上一題的程式碼的基礎上,我們考慮在每一輪選擇中開啟一個雜湊集合 `duplicated` ,用於記錄該輪中已經嘗試過的元素,並將重複元素剪枝: + +```src +[file]{permutations_ii}-[class]{}-[func]{permutations_ii} +``` + +假設元素兩兩之間互不相同,則 $n$ 個元素共有 $n!$ 種排列(階乘);在記錄結果時,需要複製長度為 $n$ 的串列,使用 $O(n)$ 時間。**因此時間複雜度為 $O(n!n)$** 。 + +最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。`selected` 使用 $O(n)$ 空間。同一時刻最多共有 $n$ 個 `duplicated` ,使用 $O(n^2)$ 空間。**因此空間複雜度為 $O(n^2)$** 。 + +### 兩種剪枝對比 + +請注意,雖然 `selected` 和 `duplicated` 都用於剪枝,但兩者的目標不同。 + +- **重複選擇剪枝**:整個搜尋過程中只有一個 `selected` 。它記錄的是當前狀態中包含哪些元素,其作用是避免某個元素在 `state` 中重複出現。 +- **相等元素剪枝**:每輪選擇(每個呼叫的 `backtrack` 函式)都包含一個 `duplicated` 。它記錄的是在本輪走訪(`for` 迴圈)中哪些元素已被選擇過,其作用是保證相等元素只被選擇一次。 + +下圖展示了兩個剪枝條件的生效範圍。注意,樹中的每個節點代表一個選擇,從根節點到葉節點的路徑上的各個節點構成一個排列。 + +![兩種剪枝條件的作用範圍](permutations_problem.assets/permutations_ii_pruning_summary.png) diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png new file mode 100644 index 0000000000..c330f95d64 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png new file mode 100644 index 0000000000..80d1d85f00 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png new file mode 100644 index 0000000000..a9880a643b Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png new file mode 100644 index 0000000000..660f346c87 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png new file mode 100644 index 0000000000..650028f569 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.md b/zh-hant/docs/chapter_backtracking/subset_sum_problem.md new file mode 100644 index 0000000000..f6418f836d --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/subset_sum_problem.md @@ -0,0 +1,95 @@ +# 子集和問題 + +## 無重複元素的情況 + +!!! question + + 給定一個正整數陣列 `nums` 和一個目標正整數 `target` ,請找出所有可能的組合,使得組合中的元素和等於 `target` 。給定陣列無重複元素,每個元素可以被選取多次。請以串列形式返回這些組合,串列中不應包含重複組合。 + +例如,輸入集合 $\{3, 4, 5\}$ 和目標整數 $9$ ,解為 $\{3, 3, 3\}, \{4, 5\}$ 。需要注意以下兩點。 + +- 輸入集合中的元素可以被無限次重複選取。 +- 子集不區分元素順序,比如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一個子集。 + +### 參考全排列解法 + +類似於全排列問題,我們可以把子集的生成過程想象成一系列選擇的結果,並在選擇過程中實時更新“元素和”,當元素和等於 `target` 時,就將子集記錄至結果串列。 + +而與全排列問題不同的是,**本題集合中的元素可以被無限次選取**,因此無須藉助 `selected` 布林串列來記錄元素是否已被選擇。我們可以對全排列程式碼進行小幅修改,初步得到解題程式碼: + +```src +[file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} +``` + +向以上程式碼輸入陣列 $[3, 4, 5]$ 和目標元素 $9$ ,輸出結果為 $[3, 3, 3], [4, 5], [5, 4]$ 。**雖然成功找出了所有和為 $9$ 的子集,但其中存在重複的子集 $[4, 5]$ 和 $[5, 4]$** 。 + +這是因為搜尋過程是區分選擇順序的,然而子集不區分選擇順序。如下圖所示,先選 $4$ 後選 $5$ 與先選 $5$ 後選 $4$ 是不同的分支,但對應同一個子集。 + +![子集搜尋與越界剪枝](subset_sum_problem.assets/subset_sum_i_naive.png) + +為了去除重複子集,**一種直接的思路是對結果串列進行去重**。但這個方法效率很低,有兩方面原因。 + +- 當陣列元素較多,尤其是當 `target` 較大時,搜尋過程會產生大量的重複子集。 +- 比較子集(陣列)的異同非常耗時,需要先排序陣列,再比較陣列中每個元素的異同。 + +### 重複子集剪枝 + +**我們考慮在搜尋過程中透過剪枝進行去重**。觀察下圖,重複子集是在以不同順序選擇陣列元素時產生的,例如以下情況。 + +1. 當第一輪和第二輪分別選擇 $3$ 和 $4$ 時,會生成包含這兩個元素的所有子集,記為 $[3, 4, \dots]$ 。 +2. 之後,當第一輪選擇 $4$ 時,**則第二輪應該跳過 $3$** ,因為該選擇產生的子集 $[4, 3, \dots]$ 和第 `1.` 步中生成的子集完全重複。 + +在搜尋過程中,每一層的選擇都是從左到右被逐個嘗試的,因此越靠右的分支被剪掉的越多。 + +1. 前兩輪選擇 $3$ 和 $5$ ,生成子集 $[3, 5, \dots]$ 。 +2. 前兩輪選擇 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。 +3. 若第一輪選擇 $5$ ,**則第二輪應該跳過 $3$ 和 $4$** ,因為子集 $[5, 3, \dots]$ 和 $[5, 4, \dots]$ 與第 `1.` 步和第 `2.` 步中描述的子集完全重複。 + +![不同選擇順序導致的重複子集](subset_sum_problem.assets/subset_sum_i_pruning.png) + +總結來看,給定輸入陣列 $[x_1, x_2, \dots, x_n]$ ,設搜尋過程中的選擇序列為 $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ ,則該選擇序列需要滿足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,**不滿足該條件的選擇序列都會造成重複,應當剪枝**。 + +### 程式碼實現 + +為實現該剪枝,我們初始化變數 `start` ,用於指示走訪起始點。**當做出選擇 $x_{i}$ 後,設定下一輪從索引 $i$ 開始走訪**。這樣做就可以讓選擇序列滿足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,從而保證子集唯一。 + +除此之外,我們還對程式碼進行了以下兩項最佳化。 + +- 在開啟搜尋前,先將陣列 `nums` 排序。在走訪所有選擇時,**當子集和超過 `target` 時直接結束迴圈**,因為後邊的元素更大,其子集和一定超過 `target` 。 +- 省去元素和變數 `total` ,**透過在 `target` 上執行減法來統計元素和**,當 `target` 等於 $0$ 時記錄解。 + +```src +[file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} +``` + +下圖所示為將陣列 $[3, 4, 5]$ 和目標元素 $9$ 輸入以上程式碼後的整體回溯過程。 + +![子集和 I 回溯過程](subset_sum_problem.assets/subset_sum_i.png) + +## 考慮重複元素的情況 + +!!! question + + 給定一個正整數陣列 `nums` 和一個目標正整數 `target` ,請找出所有可能的組合,使得組合中的元素和等於 `target` 。**給定陣列可能包含重複元素,每個元素只可被選擇一次**。請以串列形式返回這些組合,串列中不應包含重複組合。 + +相比於上題,**本題的輸入陣列可能包含重複元素**,這引入了新的問題。例如,給定陣列 $[4, \hat{4}, 5]$ 和目標元素 $9$ ,則現有程式碼的輸出結果為 $[4, 5], [\hat{4}, 5]$ ,出現了重複子集。 + +**造成這種重複的原因是相等元素在某輪中被多次選擇**。在下圖中,第一輪共有三個選擇,其中兩個都為 $4$ ,會產生兩個重複的搜尋分支,從而輸出重複子集;同理,第二輪的兩個 $4$ 也會產生重複子集。 + +![相等元素導致的重複子集](subset_sum_problem.assets/subset_sum_ii_repeat.png) + +### 相等元素剪枝 + +為解決此問題,**我們需要限制相等元素在每一輪中只能被選擇一次**。實現方式比較巧妙:由於陣列是已排序的,因此相等元素都是相鄰的。這意味著在某輪選擇中,若當前元素與其左邊元素相等,則說明它已經被選擇過,因此直接跳過當前元素。 + +與此同時,**本題規定每個陣列元素只能被選擇一次**。幸運的是,我們也可以利用變數 `start` 來滿足該約束:當做出選擇 $x_{i}$ 後,設定下一輪從索引 $i + 1$ 開始向後走訪。這樣既能去除重複子集,也能避免重複選擇元素。 + +### 程式碼實現 + +```src +[file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} +``` + +下圖展示了陣列 $[4, 4, 5]$ 和目標元素 $9$ 的回溯過程,共包含四種剪枝操作。請你將圖示與程式碼註釋相結合,理解整個搜尋過程,以及每種剪枝操作是如何工作的。 + +![子集和 II 回溯過程](subset_sum_problem.assets/subset_sum_ii.png) diff --git a/zh-hant/docs/chapter_backtracking/summary.md b/zh-hant/docs/chapter_backtracking/summary.md new file mode 100644 index 0000000000..13e081dde9 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/summary.md @@ -0,0 +1,23 @@ +# 小結 + +### 重點回顧 + +- 回溯演算法本質是窮舉法,透過對解空間進行深度優先走訪來尋找符合條件的解。在搜尋過程中,遇到滿足條件的解則記錄,直至找到所有解或走訪完成後結束。 +- 回溯演算法的搜尋過程包括嘗試與回退兩個部分。它透過深度優先搜尋來嘗試各種選擇,當遇到不滿足約束條件的情況時,則撤銷上一步的選擇,退回到之前的狀態,並繼續嘗試其他選擇。嘗試與回退是兩個方向相反的操作。 +- 回溯問題通常包含多個約束條件,它們可用於實現剪枝操作。剪枝可以提前結束不必要的搜尋分支,大幅提升搜尋效率。 +- 回溯演算法主要可用於解決搜尋問題和約束滿足問題。組合最佳化問題雖然可以用回溯演算法解決,但往往存在效率更高或效果更好的解法。 +- 全排列問題旨在搜尋給定集合元素的所有可能的排列。我們藉助一個陣列來記錄每個元素是否被選擇,剪掉重複選擇同一元素的搜尋分支,確保每個元素只被選擇一次。 +- 在全排列問題中,如果集合中存在重複元素,則最終結果會出現重複排列。我們需要約束相等元素在每輪中只能被選擇一次,這通常藉助一個雜湊集合來實現。 +- 子集和問題的目標是在給定集合中找到和為目標值的所有子集。集合不區分元素順序,而搜尋過程會輸出所有順序的結果,產生重複子集。我們在回溯前將資料進行排序,並設定一個變數來指示每一輪的走訪起始點,從而將生成重複子集的搜尋分支進行剪枝。 +- 對於子集和問題,陣列中的相等元素會產生重複集合。我們利用陣列已排序的前置條件,透過判斷相鄰元素是否相等實現剪枝,從而確保相等元素在每輪中只能被選中一次。 +- $n$ 皇后問題旨在尋找將 $n$ 個皇后放置到 $n \times n$ 尺寸棋盤上的方案,要求所有皇后兩兩之間無法攻擊對方。該問題的約束條件有行約束、列約束、主對角線和次對角線約束。為滿足行約束,我們採用按行放置的策略,保證每一行放置一個皇后。 +- 列約束和對角線約束的處理方式類似。對於列約束,我們利用一個陣列來記錄每一列是否有皇后,從而指示選中的格子是否合法。對於對角線約束,我們藉助兩個陣列來分別記錄該主、次對角線上是否存在皇后;難點在於找出處在同一主(副)對角線上的格子所滿足的行列索引規律。 + +### Q & A + +**Q**:怎麼理解回溯和遞迴的關係? + +總的來看,回溯是一種“演算法策略”,而遞迴更像是一個“工具”。 + +- 回溯演算法通常基於遞迴實現。然而,回溯是遞迴的應用場景之一,是遞迴在搜尋問題中的應用。 +- 遞迴的結構體現了“子問題分解”的解題範式,常用於解決分治、回溯、動態規劃(記憶化遞迴)等問題。 diff --git a/zh-hant/docs/chapter_computational_complexity/index.md b/zh-hant/docs/chapter_computational_complexity/index.md new file mode 100644 index 0000000000..a8fdf4e13a --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/index.md @@ -0,0 +1,9 @@ +# 複雜度分析 + +![複雜度分析](../assets/covers/chapter_complexity_analysis.jpg) + +!!! abstract + + 複雜度分析猶如浩瀚的演算法宇宙中的時空嚮導。 + + 它帶領我們在時間與空間這兩個維度上深入探索,尋找更優雅的解決方案。 diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png new file mode 100644 index 0000000000..f4e8df549a Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png new file mode 100644 index 0000000000..f314c50e38 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png new file mode 100644 index 0000000000..114ae717b7 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png new file mode 100644 index 0000000000..e8e794b3f4 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png new file mode 100644 index 0000000000..40b1f8a5c3 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png new file mode 100644 index 0000000000..3d42607994 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.md b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.md new file mode 100644 index 0000000000..66c4b1ba73 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -0,0 +1,194 @@ +# 迭代與遞迴 + +在演算法中,重複執行某個任務是很常見的,它與複雜度分析息息相關。因此,在介紹時間複雜度和空間複雜度之前,我們先來了解如何在程式中實現重複執行任務,即兩種基本的程式控制結構:迭代、遞迴。 + +## 迭代 + +迭代(iteration)是一種重複執行某個任務的控制結構。在迭代中,程式會在滿足一定的條件下重複執行某段程式碼,直到這個條件不再滿足。 + +### for 迴圈 + +`for` 迴圈是最常見的迭代形式之一,**適合在預先知道迭代次數時使用**。 + +以下函式基於 `for` 迴圈實現了求和 $1 + 2 + \dots + n$ ,求和結果使用變數 `res` 記錄。需要注意的是,Python 中 `range(a, b)` 對應的區間是“左閉右開”的,對應的走訪範圍為 $a, a + 1, \dots, b-1$ : + +```src +[file]{iteration}-[class]{}-[func]{for_loop} +``` + +下圖是該求和函式的流程框圖。 + +![求和函式的流程框圖](iteration_and_recursion.assets/iteration.png) + +此求和函式的操作數量與輸入資料大小 $n$ 成正比,或者說成“線性關係”。實際上,**時間複雜度描述的就是這個“線性關係”**。相關內容將會在下一節中詳細介紹。 + +### while 迴圈 + +與 `for` 迴圈類似,`while` 迴圈也是一種實現迭代的方法。在 `while` 迴圈中,程式每輪都會先檢查條件,如果條件為真,則繼續執行,否則就結束迴圈。 + +下面我們用 `while` 迴圈來實現求和 $1 + 2 + \dots + n$ : + +```src +[file]{iteration}-[class]{}-[func]{while_loop} +``` + +**`while` 迴圈比 `for` 迴圈的自由度更高**。在 `while` 迴圈中,我們可以自由地設計條件變數的初始化和更新步驟。 + +例如在以下程式碼中,條件變數 $i$ 每輪進行兩次更新,這種情況就不太方便用 `for` 迴圈實現: + +```src +[file]{iteration}-[class]{}-[func]{while_loop_ii} +``` + +總的來說,**`for` 迴圈的程式碼更加緊湊,`while` 迴圈更加靈活**,兩者都可以實現迭代結構。選擇使用哪一個應該根據特定問題的需求來決定。 + +### 巢狀迴圈 + +我們可以在一個迴圈結構內巢狀另一個迴圈結構,下面以 `for` 迴圈為例: + +```src +[file]{iteration}-[class]{}-[func]{nested_for_loop} +``` + +下圖是該巢狀迴圈的流程框圖。 + +![巢狀迴圈的流程框圖](iteration_and_recursion.assets/nested_iteration.png) + +在這種情況下,函式的操作數量與 $n^2$ 成正比,或者說演算法執行時間和輸入資料大小 $n$ 成“平方關係”。 + +我們可以繼續新增巢狀迴圈,每一次巢狀都是一次“升維”,將會使時間複雜度提高至“立方關係”“四次方關係”,以此類推。 + +## 遞迴 + + 遞迴(recursion)是一種演算法策略,透過函式呼叫自身來解決問題。它主要包含兩個階段。 + +1. **遞**:程式不斷深入地呼叫自身,通常傳入更小或更簡化的參數,直到達到“終止條件”。 +2. **迴**:觸發“終止條件”後,程式從最深層的遞迴函式開始逐層返回,匯聚每一層的結果。 + +而從實現的角度看,遞迴程式碼主要包含三個要素。 + +1. **終止條件**:用於決定什麼時候由“遞”轉“迴”。 +2. **遞迴呼叫**:對應“遞”,函式呼叫自身,通常輸入更小或更簡化的參數。 +3. **返回結果**:對應“迴”,將當前遞迴層級的結果返回至上一層。 + +觀察以下程式碼,我們只需呼叫函式 `recur(n)` ,就可以完成 $1 + 2 + \dots + n$ 的計算: + +```src +[file]{recursion}-[class]{}-[func]{recur} +``` + +下圖展示了該函式的遞迴過程。 + +![求和函式的遞迴過程](iteration_and_recursion.assets/recursion_sum.png) + +雖然從計算角度看,迭代與遞迴可以得到相同的結果,**但它們代表了兩種完全不同的思考和解決問題的範式**。 + +- **迭代**:“自下而上”地解決問題。從最基礎的步驟開始,然後不斷重複或累加這些步驟,直到任務完成。 +- **遞迴**:“自上而下”地解決問題。將原問題分解為更小的子問題,這些子問題和原問題具有相同的形式。接下來將子問題繼續分解為更小的子問題,直到基本情況時停止(基本情況的解是已知的)。 + +以上述求和函式為例,設問題 $f(n) = 1 + 2 + \dots + n$ 。 + +- **迭代**:在迴圈中模擬求和過程,從 $1$ 走訪到 $n$ ,每輪執行求和操作,即可求得 $f(n)$ 。 +- **遞迴**:將問題分解為子問題 $f(n) = n + f(n-1)$ ,不斷(遞迴地)分解下去,直至基本情況 $f(1) = 1$ 時終止。 + +### 呼叫堆疊 + +遞迴函式每次呼叫自身時,系統都會為新開啟的函式分配記憶體,以儲存區域性變數、呼叫位址和其他資訊等。這將導致兩方面的結果。 + +- 函式的上下文資料都儲存在稱為“堆疊幀空間”的記憶體區域中,直至函式返回後才會被釋放。因此,**遞迴通常比迭代更加耗費記憶體空間**。 +- 遞迴呼叫函式會產生額外的開銷。**因此遞迴通常比迴圈的時間效率更低**。 + +如下圖所示,在觸發終止條件前,同時存在 $n$ 個未返回的遞迴函式,**遞迴深度為 $n$** 。 + +![遞迴呼叫深度](iteration_and_recursion.assets/recursion_sum_depth.png) + +在實際中,程式語言允許的遞迴深度通常是有限的,過深的遞迴可能導致堆疊溢位錯誤。 + +### 尾遞迴 + +有趣的是,**如果函式在返回前的最後一步才進行遞迴呼叫**,則該函式可以被編譯器或直譯器最佳化,使其在空間效率上與迭代相當。這種情況被稱為尾遞迴(tail recursion)。 + +- **普通遞迴**:當函式返回到上一層級的函式後,需要繼續執行程式碼,因此系統需要儲存上一層呼叫的上下文。 +- **尾遞迴**:遞迴呼叫是函式返回前的最後一個操作,這意味著函式返回到上一層級後,無須繼續執行其他操作,因此系統無須儲存上一層函式的上下文。 + +以計算 $1 + 2 + \dots + n$ 為例,我們可以將結果變數 `res` 設為函式參數,從而實現尾遞迴: + +```src +[file]{recursion}-[class]{}-[func]{tail_recur} +``` + +尾遞迴的執行過程如下圖所示。對比普通遞迴和尾遞迴,兩者的求和操作的執行點是不同的。 + +- **普通遞迴**:求和操作是在“迴”的過程中執行的,每層返回後都要再執行一次求和操作。 +- **尾遞迴**:求和操作是在“遞”的過程中執行的,“迴”的過程只需層層返回。 + +![尾遞迴過程](iteration_and_recursion.assets/tail_recursion_sum.png) + +!!! tip + + 請注意,許多編譯器或直譯器並不支持尾遞迴最佳化。例如,Python 預設不支持尾遞迴最佳化,因此即使函式是尾遞迴形式,仍然可能會遇到堆疊溢位問題。 + +### 遞迴樹 + +當處理與“分治”相關的演算法問題時,遞迴往往比迭代的思路更加直觀、程式碼更加易讀。以“費波那契數列”為例。 + +!!! question + + 給定一個費波那契數列 $0, 1, 1, 2, 3, 5, 8, 13, \dots$ ,求該數列的第 $n$ 個數字。 + +設費波那契數列的第 $n$ 個數字為 $f(n)$ ,易得兩個結論。 + +- 數列的前兩個數字為 $f(1) = 0$ 和 $f(2) = 1$ 。 +- 數列中的每個數字是前兩個數字的和,即 $f(n) = f(n - 1) + f(n - 2)$ 。 + +按照遞推關係進行遞迴呼叫,將前兩個數字作為終止條件,便可寫出遞迴程式碼。呼叫 `fib(n)` 即可得到費波那契數列的第 $n$ 個數字: + +```src +[file]{recursion}-[class]{}-[func]{fib} +``` + +觀察以上程式碼,我們在函式內遞迴呼叫了兩個函式,**這意味著從一個呼叫產生了兩個呼叫分支**。如下圖所示,這樣不斷遞迴呼叫下去,最終將產生一棵層數為 $n$ 的遞迴樹(recursion tree)。 + +![費波那契數列的遞迴樹](iteration_and_recursion.assets/recursion_tree.png) + +從本質上看,遞迴體現了“將問題分解為更小子問題”的思維範式,這種分治策略至關重要。 + +- 從演算法角度看,搜尋、排序、回溯、分治、動態規劃等許多重要演算法策略直接或間接地應用了這種思維方式。 +- 從資料結構角度看,遞迴天然適合處理鏈結串列、樹和圖的相關問題,因為它們非常適合用分治思想進行分析。 + +## 兩者對比 + +總結以上內容,如下表所示,迭代和遞迴在實現、效能和適用性上有所不同。 + +

  迭代與遞迴特點對比

+ +| | 迭代 | 遞迴 | +| -------- | -------------------------------------- | ------------------------------------------------------------ | +| 實現方式 | 迴圈結構 | 函式呼叫自身 | +| 時間效率 | 效率通常較高,無函式呼叫開銷 | 每次函式呼叫都會產生開銷 | +| 記憶體使用 | 通常使用固定大小的記憶體空間 | 累積函式呼叫可能使用大量的堆疊幀空間 | +| 適用問題 | 適用於簡單迴圈任務,程式碼直觀、可讀性好 | 適用於子問題分解,如樹、圖、分治、回溯等,程式碼結構簡潔、清晰 | + +!!! tip + + 如果感覺以下內容理解困難,可以在讀完“堆疊”章節後再來複習。 + +那麼,迭代和遞迴具有什麼內在關聯呢?以上述遞迴函式為例,求和操作在遞迴的“迴”階段進行。這意味著最初被呼叫的函式實際上是最後完成其求和操作的,**這種工作機制與堆疊的“先入後出”原則異曲同工**。 + +事實上,“呼叫堆疊”和“堆疊幀空間”這類遞迴術語已經暗示了遞迴與堆疊之間的密切關係。 + +1. **遞**:當函式被呼叫時,系統會在“呼叫堆疊”上為該函式分配新的堆疊幀,用於儲存函式的區域性變數、參數、返回位址等資料。 +2. **迴**:當函式完成執行並返回時,對應的堆疊幀會被從“呼叫堆疊”上移除,恢復之前函式的執行環境。 + +因此,**我們可以使用一個顯式的堆疊來模擬呼叫堆疊的行為**,從而將遞迴轉化為迭代形式: + +```src +[file]{recursion}-[class]{}-[func]{for_loop_recur} +``` + +觀察以上程式碼,當遞迴轉化為迭代後,程式碼變得更加複雜了。儘管迭代和遞迴在很多情況下可以互相轉化,但不一定值得這樣做,有以下兩點原因。 + +- 轉化後的程式碼可能更加難以理解,可讀性更差。 +- 對於某些複雜問題,模擬系統呼叫堆疊的行為可能非常困難。 + +總之,**選擇迭代還是遞迴取決於特定問題的性質**。在程式設計實踐中,權衡兩者的優劣並根據情境選擇合適的方法至關重要。 diff --git a/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md b/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md new file mode 100644 index 0000000000..b97c7a3252 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md @@ -0,0 +1,49 @@ +# 演算法效率評估 + +在演算法設計中,我們先後追求以下兩個層面的目標。 + +1. **找到問題解法**:演算法需要在規定的輸入範圍內可靠地求得問題的正確解。 +2. **尋求最優解法**:同一個問題可能存在多種解法,我們希望找到儘可能高效的演算法。 + +也就是說,在能夠解決問題的前提下,演算法效率已成為衡量演算法優劣的主要評價指標,它包括以下兩個維度。 + +- **時間效率**:演算法執行時間的長短。 +- **空間效率**:演算法佔用記憶體空間的大小。 + +簡而言之,**我們的目標是設計“既快又省”的資料結構與演算法**。而有效地評估演算法效率至關重要,因為只有這樣,我們才能將各種演算法進行對比,進而指導演算法設計與最佳化過程。 + +效率評估方法主要分為兩種:實際測試、理論估算。 + +## 實際測試 + +假設我們現在有演算法 `A` 和演算法 `B` ,它們都能解決同一問題,現在需要對比這兩個演算法的效率。最直接的方法是找一臺計算機,執行這兩個演算法,並監控記錄它們的執行時間和記憶體佔用情況。這種評估方式能夠反映真實情況,但也存在較大的侷限性。 + +一方面,**難以排除測試環境的干擾因素**。硬體配置會影響演算法的效能表現。比如一個演算法的並行度較高,那麼它就更適合在多核 CPU 上執行,一個演算法的記憶體操作密集,那麼它在高效能記憶體上的表現就會更好。也就是說,演算法在不同的機器上的測試結果可能是不一致的。這意味著我們需要在各種機器上進行測試,統計平均效率,而這是不現實的。 + +另一方面,**展開完整測試非常耗費資源**。隨著輸入資料量的變化,演算法會表現出不同的效率。例如,在輸入資料量較小時,演算法 `A` 的執行時間比演算法 `B` 短;而在輸入資料量較大時,測試結果可能恰恰相反。因此,為了得到有說服力的結論,我們需要測試各種規模的輸入資料,而這需要耗費大量的計算資源。 + +## 理論估算 + +由於實際測試具有較大的侷限性,我們可以考慮僅透過一些計算來評估演算法的效率。這種估算方法被稱為漸近複雜度分析(asymptotic complexity analysis),簡稱複雜度分析。 + +複雜度分析能夠體現演算法執行所需的時間和空間資源與輸入資料大小之間的關係。**它描述了隨著輸入資料大小的增加,演算法執行所需時間和空間的增長趨勢**。這個定義有些拗口,我們可以將其分為三個重點來理解。 + +- “時間和空間資源”分別對應時間複雜度(time complexity)空間複雜度(space complexity)。 +- “隨著輸入資料大小的增加”意味著複雜度反映了演算法執行效率與輸入資料體量之間的關係。 +- “時間和空間的增長趨勢”表示複雜度分析關注的不是執行時間或佔用空間的具體值,而是時間或空間增長的“快慢”。 + +**複雜度分析克服了實際測試方法的弊端**,體現在以下幾個方面。 + +- 它無需實際執行程式碼,更加綠色節能。 +- 它獨立於測試環境,分析結果適用於所有執行平臺。 +- 它可以體現不同資料量下的演算法效率,尤其是在大資料量下的演算法效能。 + +!!! tip + + 如果你仍對複雜度的概念感到困惑,無須擔心,我們會在後續章節中詳細介紹。 + +複雜度分析為我們提供了一把評估演算法效率的“標尺”,使我們可以衡量執行某個演算法所需的時間和空間資源,對比不同演算法之間的效率。 + +複雜度是個數學概念,對於初學者可能比較抽象,學習難度相對較高。從這個角度看,複雜度分析可能不太適合作為最先介紹的內容。然而,當我們討論某個資料結構或演算法的特點時,難以避免要分析其執行速度和空間使用情況。 + +綜上所述,建議你在深入學習資料結構與演算法之前,**先對複雜度分析建立初步的瞭解,以便能夠完成簡單演算法的複雜度分析**。 diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png new file mode 100644 index 0000000000..bb65e10a2b Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png new file mode 100644 index 0000000000..aa1172b919 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png new file mode 100644 index 0000000000..99f30fe22d Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png new file mode 100644 index 0000000000..7710951fd8 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_types.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_types.png new file mode 100644 index 0000000000..21cda71ead Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_types.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.md b/zh-hant/docs/chapter_computational_complexity/space_complexity.md new file mode 100755 index 0000000000..0a56ff7aa5 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,898 @@ +# 空間複雜度 + +空間複雜度(space complexity)用於衡量演算法佔用記憶體空間隨著資料量變大時的增長趨勢。這個概念與時間複雜度非常類似,只需將“執行時間”替換為“佔用記憶體空間”。 + +## 演算法相關空間 + +演算法在執行過程中使用的記憶體空間主要包括以下幾種。 + +- **輸入空間**:用於儲存演算法的輸入資料。 +- **暫存空間**:用於儲存演算法在執行過程中的變數、物件、函式上下文等資料。 +- **輸出空間**:用於儲存演算法的輸出資料。 + +一般情況下,空間複雜度的統計範圍是“暫存空間”加上“輸出空間”。 + +暫存空間可以進一步劃分為三個部分。 + +- **暫存資料**:用於儲存演算法執行過程中的各種常數、變數、物件等。 +- **堆疊幀空間**:用於儲存呼叫函式的上下文資料。系統在每次呼叫函式時都會在堆疊頂部建立一個堆疊幀,函式返回後,堆疊幀空間會被釋放。 +- **指令空間**:用於儲存編譯後的程式指令,在實際統計中通常忽略不計。 + +在分析一段程式的空間複雜度時,**我們通常統計暫存資料、堆疊幀空間和輸出資料三部分**,如下圖所示。 + +![演算法使用的相關空間](space_complexity.assets/space_types.png) + +相關程式碼如下: + +=== "Python" + + ```python title="" + class Node: + """類別""" + def __init__(self, x: int): + self.val: int = x # 節點值 + self.next: Node | None = None # 指向下一節點的引用 + + def function() -> int: + """函式""" + # 執行某些操作... + return 0 + + def algorithm(n) -> int: # 輸入資料 + A = 0 # 暫存資料(常數,一般用大寫字母表示) + b = 0 # 暫存資料(變數) + node = Node(0) # 暫存資料(物件) + c = function() # 堆疊幀空間(呼叫函式) + return A + b + c # 輸出資料 + ``` + +=== "C++" + + ```cpp title="" + /* 結構體 */ + struct Node { + int val; + Node *next; + Node(int x) : val(x), next(nullptr) {} + }; + + /* 函式 */ + int func() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node* node = new Node(0); // 暫存資料(物件) + int c = func(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Java" + + ```java title="" + /* 類別 */ + class Node { + int val; + Node next; + Node(int x) { val = x; } + } + + /* 函式 */ + int function() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + final int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node node = new Node(0); // 暫存資料(物件) + int c = function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "C#" + + ```csharp title="" + /* 類別 */ + class Node(int x) { + int val = x; + Node next; + } + + /* 函式 */ + int Function() { + // 執行某些操作... + return 0; + } + + int Algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node node = new(0); // 暫存資料(物件) + int c = Function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Go" + + ```go title="" + /* 結構體 */ + type node struct { + val int + next *node + } + + /* 建立 node 結構體 */ + func newNode(val int) *node { + return &node{val: val} + } + + /* 函式 */ + func function() int { + // 執行某些操作... + return 0 + } + + func algorithm(n int) int { // 輸入資料 + const a = 0 // 暫存資料(常數) + b := 0 // 暫存資料(變數) + newNode(0) // 暫存資料(物件) + c := function() // 堆疊幀空間(呼叫函式) + return a + b + c // 輸出資料 + } + ``` + +=== "Swift" + + ```swift title="" + /* 類別 */ + class Node { + var val: Int + var next: Node? + + init(x: Int) { + val = x + } + } + + /* 函式 */ + func function() -> Int { + // 執行某些操作... + return 0 + } + + func algorithm(n: Int) -> Int { // 輸入資料 + let a = 0 // 暫存資料(常數) + var b = 0 // 暫存資料(變數) + let node = Node(x: 0) // 暫存資料(物件) + let c = function() // 堆疊幀空間(呼叫函式) + return a + b + c // 輸出資料 + } + ``` + +=== "JS" + + ```javascript title="" + /* 類別 */ + class Node { + val; + next; + constructor(val) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = null; // 指向下一節點的引用 + } + } + + /* 函式 */ + function constFunc() { + // 執行某些操作 + return 0; + } + + function algorithm(n) { // 輸入資料 + const a = 0; // 暫存資料(常數) + let b = 0; // 暫存資料(變數) + const node = new Node(0); // 暫存資料(物件) + const c = constFunc(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "TS" + + ```typescript title="" + /* 類別 */ + class Node { + val: number; + next: Node | null; + constructor(val?: number) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = null; // 指向下一節點的引用 + } + } + + /* 函式 */ + function constFunc(): number { + // 執行某些操作 + return 0; + } + + function algorithm(n: number): number { // 輸入資料 + const a = 0; // 暫存資料(常數) + let b = 0; // 暫存資料(變數) + const node = new Node(0); // 暫存資料(物件) + const c = constFunc(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Dart" + + ```dart title="" + /* 類別 */ + class Node { + int val; + Node next; + Node(this.val, [this.next]); + } + + /* 函式 */ + int function() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node node = Node(0); // 暫存資料(物件) + int c = function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 結構體 */ + struct Node { + val: i32, + next: Option>>, + } + + /* 建立 Node 結構體 */ + impl Node { + fn new(val: i32) -> Self { + Self { val: val, next: None } + } + } + + /* 函式 */ + fn function() -> i32 { + // 執行某些操作... + return 0; + } + + fn algorithm(n: i32) -> i32 { // 輸入資料 + const a: i32 = 0; // 暫存資料(常數) + let mut b = 0; // 暫存資料(變數) + let node = Node::new(0); // 暫存資料(物件) + let c = function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "C" + + ```c title="" + /* 函式 */ + int func() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + int c = func(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 類別 */ + class Node(var _val: Int) { + var next: Node? = null + } + + /* 函式 */ + fun function(): Int { + // 執行某些操作... + return 0 + } + + fun algorithm(n: Int): Int { // 輸入資料 + val a = 0 // 暫存資料(常數) + var b = 0 // 暫存資料(變數) + val node = Node(0) // 暫存資料(物件) + val c = function() // 堆疊幀空間(呼叫函式) + return a + b + c // 輸出資料 + } + ``` + +=== "Ruby" + + ```ruby title="" + ### 類別 ### + class Node + attr_accessor :val # 節點值 + attr_accessor :next # 指向下一節點的引用 + + def initialize(x) + @val = x + end + end + + ### 函式 ### + def function + # 執行某些操作... + 0 + end + + ### 演算法 ### + def algorithm(n) # 輸入資料 + a = 0 # 暫存資料(常數) + b = 0 # 暫存資料(變數) + node = Node.new(0) # 暫存資料(物件) + c = function # 堆疊幀空間(呼叫函式) + a + b + c # 輸出資料 + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +## 推算方法 + +空間複雜度的推算方法與時間複雜度大致相同,只需將統計物件從“操作數量”轉為“使用空間大小”。 + +而與時間複雜度不同的是,**我們通常只關注最差空間複雜度**。這是因為記憶體空間是一項硬性要求,我們必須確保在所有輸入資料下都有足夠的記憶體空間預留。 + +觀察以下程式碼,最差空間複雜度中的“最差”有兩層含義。 + +1. **以最差輸入資料為準**:當 $n < 10$ 時,空間複雜度為 $O(1)$ ;但當 $n > 10$ 時,初始化的陣列 `nums` 佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 +2. **以演算法執行中的峰值記憶體為準**:例如,程式在執行最後一行之前,佔用 $O(1)$ 空間;當初始化陣列 `nums` 時,程式佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 0 # O(1) + b = [0] * 10000 # O(1) + if n > 10: + nums = [0] * n # O(n) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 0; // O(1) + vector b(10000); // O(1) + if (n > 10) + vector nums(n); // O(n) + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) + int[] nums = new int[n]; // O(n) + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) { + int[] nums = new int[n]; // O(n) + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 0 // O(1) + b := make([]int, 10000) // O(1) + var nums []int + if n > 10 { + nums := make([]int, n) // O(n) + } + fmt.Println(a, b, nums) + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + let a = 0 // O(1) + let b = Array(repeating: 0, count: 10000) // O(1) + if n > 10 { + let nums = Array(repeating: 0, count: n) // O(n) + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 0; // O(1) + List b = List.filled(10000, 0); // O(1) + if (n > 10) { + List nums = List.filled(n, 0); // O(n) + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let a = 0; // O(1) + let b = [0; 10000]; // O(1) + if n > 10 { + let nums = vec![0; n as usize]; // O(n) + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 0; // O(1) + int b[10000]; // O(1) + if (n > 10) + int nums[n] = {0}; // O(n) + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + val a = 0 // O(1) + val b = IntArray(10000) // O(1) + if (n > 10) { + val nums = IntArray(n) // O(n) + } + } + ``` + +=== "Ruby" + + ```ruby title="" + def algorithm(n) + a = 0 # O(1) + b = Array.new(10000) # O(1) + nums = Array.new(n) if n > 10 # O(n) + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +**在遞迴函式中,需要注意統計堆疊幀空間**。觀察以下程式碼: + +=== "Python" + + ```python title="" + def function() -> int: + # 執行某些操作 + return 0 + + def loop(n: int): + """迴圈的空間複雜度為 O(1)""" + for _ in range(n): + function() + + def recur(n: int): + """遞迴的空間複雜度為 O(n)""" + if n == 1: + return + return recur(n - 1) + ``` + +=== "C++" + + ```cpp title="" + int func() { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Java" + + ```java title="" + int function() { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "C#" + + ```csharp title="" + int Function() { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + void Loop(int n) { + for (int i = 0; i < n; i++) { + Function(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + int Recur(int n) { + if (n == 1) return 1; + return Recur(n - 1); + } + ``` + +=== "Go" + + ```go title="" + func function() int { + // 執行某些操作 + return 0 + } + + /* 迴圈的空間複雜度為 O(1) */ + func loop(n int) { + for i := 0; i < n; i++ { + function() + } + } + + /* 遞迴的空間複雜度為 O(n) */ + func recur(n int) { + if n == 1 { + return + } + recur(n - 1) + } + ``` + +=== "Swift" + + ```swift title="" + @discardableResult + func function() -> Int { + // 執行某些操作 + return 0 + } + + /* 迴圈的空間複雜度為 O(1) */ + func loop(n: Int) { + for _ in 0 ..< n { + function() + } + } + + /* 遞迴的空間複雜度為 O(n) */ + func recur(n: Int) { + if n == 1 { + return + } + recur(n: n - 1) + } + ``` + +=== "JS" + + ```javascript title="" + function constFunc() { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + function loop(n) { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + function recur(n) { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "TS" + + ```typescript title="" + function constFunc(): number { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + function loop(n: number): void { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + function recur(n: number): void { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "Dart" + + ```dart title="" + int function() { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Rust" + + ```rust title="" + fn function() -> i32 { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + fn loop(n: i32) { + for i in 0..n { + function(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + fn recur(n: i32) { + if n == 1 { + return; + } + recur(n - 1); + } + ``` + +=== "C" + + ```c title="" + int func() { + // 執行某些操作 + return 0; + } + /* 迴圈的空間複雜度為 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* 遞迴的空間複雜度為 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun function(): Int { + // 執行某些操作 + return 0 + } + /* 迴圈的空間複雜度為 O(1) */ + fun loop(n: Int) { + for (i in 0..函式(function)可以被獨立執行,所有參數都以顯式傳遞。方法(method)與一個物件關聯,被隱式傳遞給呼叫它的物件,能夠對類別的例項中包含的資料進行操作。 + +下面以幾種常見的程式語言為例來說明。 + +- C 語言是程序式程式設計語言,沒有物件導向的概念,所以只有函式。但我們可以透過建立結構體(struct)來模擬物件導向程式設計,與結構體相關聯的函式就相當於其他程式語言中的方法。 +- Java 和 C# 是物件導向的程式語言,程式碼塊(方法)通常作為某個類別的一部分。靜態方法的行為類似於函式,因為它被繫結在類別上,不能訪問特定的例項變數。 +- C++ 和 Python 既支持程序式程式設計(函式),也支持物件導向程式設計(方法)。 + +**Q**:圖解“常見的空間複雜度型別”反映的是否是佔用空間的絕對大小? + +不是,該圖展示的是空間複雜度,其反映的是增長趨勢,而不是佔用空間的絕對大小。 + +假設取 $n = 8$ ,你可能會發現每條曲線的值與函式對應不上。這是因為每條曲線都包含一個常數項,用於將取值範圍壓縮到一個視覺舒適的範圍內。 + +在實際中,因為我們通常不知道每個方法的“常數項”複雜度是多少,所以一般無法僅憑複雜度來選擇 $n = 8$ 之下的最優解法。但對於 $n = 8^5$ 就很好選了,這時增長趨勢已經佔主導了。 diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png new file mode 100644 index 0000000000..753d70772c Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png new file mode 100644 index 0000000000..3e1e36fd17 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png new file mode 100644 index 0000000000..1234ae7dc4 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png new file mode 100644 index 0000000000..1630b19f76 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png new file mode 100644 index 0000000000..0a400b3ef6 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png new file mode 100644 index 0000000000..e3134370ef Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png new file mode 100644 index 0000000000..6802b6dd68 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png new file mode 100644 index 0000000000..3bd4127d50 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.md b/zh-hant/docs/chapter_computational_complexity/time_complexity.md new file mode 100755 index 0000000000..a8506b7aa0 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,1224 @@ +# 時間複雜度 + +執行時間可以直觀且準確地反映演算法的效率。如果我們想準確預估一段程式碼的執行時間,應該如何操作呢? + +1. **確定執行平臺**,包括硬體配置、程式語言、系統環境等,這些因素都會影響程式碼的執行效率。 +2. **評估各種計算操作所需的執行時間**,例如加法操作 `+` 需要 1 ns ,乘法操作 `*` 需要 10 ns ,列印操作 `print()` 需要 5 ns 等。 +3. **統計程式碼中所有的計算操作**,並將所有操作的執行時間求和,從而得到執行時間。 + +例如在以下程式碼中,輸入資料大小為 $n$ : + +=== "Python" + + ```python title="" + # 在某執行平臺下 + def algorithm(n: int): + a = 2 # 1 ns + a = a + 1 # 1 ns + a = a * 2 # 10 ns + # 迴圈 n 次 + for _ in range(n): # 1 ns + print(0) # 5 ns + ``` + +=== "C++" + + ```cpp title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns + cout << 0 << endl; // 5 ns + } + } + ``` + +=== "Java" + + ```java title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns + System.out.println(0); // 5 ns + } + } + ``` + +=== "C#" + + ```csharp title="" + // 在某執行平臺下 + void Algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns + Console.WriteLine(0); // 5 ns + } + } + ``` + +=== "Go" + + ```go title="" + // 在某執行平臺下 + func algorithm(n int) { + a := 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // 迴圈 n 次 + for i := 0; i < n; i++ { // 1 ns + fmt.Println(a) // 5 ns + } + } + ``` + +=== "Swift" + + ```swift title="" + // 在某執行平臺下 + func algorithm(n: Int) { + var a = 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // 迴圈 n 次 + for _ in 0 ..< n { // 1 ns + print(0) // 5 ns + } + } + ``` + +=== "JS" + + ```javascript title="" + // 在某執行平臺下 + function algorithm(n) { + var a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for(let i = 0; i < n; i++) { // 1 ns + console.log(0); // 5 ns + } + } + ``` + +=== "TS" + + ```typescript title="" + // 在某執行平臺下 + function algorithm(n: number): void { + var a: number = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for(let i = 0; i < n; i++) { // 1 ns + console.log(0); // 5 ns + } + } + ``` + +=== "Dart" + + ```dart title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns + print(0); // 5 ns + } + } + ``` + +=== "Rust" + + ```rust title="" + // 在某執行平臺下 + fn algorithm(n: i32) { + let mut a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for _ in 0..n { // 1 ns + println!("{}", 0); // 5 ns + } + } + ``` + +=== "C" + + ```c title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns + printf("%d", 0); // 5 ns + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + // 在某執行平臺下 + fun algorithm(n: Int) { + var a = 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // 迴圈 n 次 + for (i in 0.. 1$ 時比演算法 `A` 更慢,在 $n > 1000000$ 時比演算法 `C` 更慢。事實上,只要輸入資料大小 $n$ 足夠大,複雜度為“常數階”的演算法一定優於“線性階”的演算法,這正是時間增長趨勢的含義。 +- **時間複雜度的推算方法更簡便**。顯然,執行平臺和計算操作型別都與演算法執行時間的增長趨勢無關。因此在時間複雜度分析中,我們可以簡單地將所有計算操作的執行時間視為相同的“單位時間”,從而將“計算操作執行時間統計”簡化為“計算操作數量統計”,這樣一來估算難度就大大降低了。 +- **時間複雜度也存在一定的侷限性**。例如,儘管演算法 `A` 和 `C` 的時間複雜度相同,但實際執行時間差別很大。同樣,儘管演算法 `B` 的時間複雜度比 `C` 高,但在輸入資料大小 $n$ 較小時,演算法 `B` 明顯優於演算法 `C` 。對於此類情況,我們時常難以僅憑時間複雜度判斷演算法效率的高低。當然,儘管存在上述問題,複雜度分析仍然是評判演算法效率最有效且常用的方法。 + +## 函式漸近上界 + +給定一個輸入大小為 $n$ 的函式: + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +1 + a = a + 1 # +1 + a = a * 2 # +1 + # 迴圈 n 次 + for i in range(n): # +1 + print(0) # +1 + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + cout << 0 << endl; // +1 + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + System.out.println(0); // +1 + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + Console.WriteLine(0); // +1 + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // 迴圈 n 次 + for i := 0; i < n; i++ { // +1 + fmt.Println(a) // +1 + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // 迴圈 n 次 + for _ in 0 ..< n { // +1 + print(0) // +1 + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + var a = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // 迴圈 n 次 + for(let i = 0; i < n; i++){ // +1(每輪都執行 i ++) + console.log(0); // +1 + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void{ + var a: number = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // 迴圈 n 次 + for(let i = 0; i < n; i++){ // +1(每輪都執行 i ++) + console.log(0); // +1 + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + print(0); // +1 + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + + // 迴圈 n 次 + for _ in 0..n { // +1(每輪都執行 i ++) + println!("{}", 0); // +1 + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + printf("%d", 0); // +1 + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // 迴圈 n 次 + for (i in 0..大 $O$ 記號(big-$O$ notation),表示函式 $T(n)$ 的漸近上界(asymptotic upper bound)。 + +時間複雜度分析本質上是計算“操作數量 $T(n)$”的漸近上界,它具有明確的數學定義。 + +!!! note "函式漸近上界" + + 若存在正實數 $c$ 和實數 $n_0$ ,使得對於所有的 $n > n_0$ ,均有 $T(n) \leq c \cdot f(n)$ ,則可認為 $f(n)$ 給出了 $T(n)$ 的一個漸近上界,記為 $T(n) = O(f(n))$ 。 + +如下圖所示,計算漸近上界就是尋找一個函式 $f(n)$ ,使得當 $n$ 趨向於無窮大時,$T(n)$ 和 $f(n)$ 處於相同的增長級別,僅相差一個常數項 $c$ 的倍數。 + +![函式的漸近上界](time_complexity.assets/asymptotic_upper_bound.png) + +## 推算方法 + +漸近上界的數學味兒有點重,如果你感覺沒有完全理解,也無須擔心。我們可以先掌握推算方法,在不斷的實踐中,就可以逐漸領悟其數學意義。 + +根據定義,確定 $f(n)$ 之後,我們便可得到時間複雜度 $O(f(n))$ 。那麼如何確定漸近上界 $f(n)$ 呢?總體分為兩步:首先統計操作數量,然後判斷漸近上界。 + +### 第一步:統計操作數量 + +針對程式碼,逐行從上到下計算即可。然而,由於上述 $c \cdot f(n)$ 中的常數項 $c$ 可以取任意大小,**因此操作數量 $T(n)$ 中的各種係數、常數項都可以忽略**。根據此原則,可以總結出以下計數簡化技巧。 + +1. **忽略 $T(n)$ 中的常數項**。因為它們都與 $n$ 無關,所以對時間複雜度不產生影響。 +2. **省略所有係數**。例如,迴圈 $2n$ 次、$5n + 1$ 次等,都可以簡化記為 $n$ 次,因為 $n$ 前面的係數對時間複雜度沒有影響。 +3. **迴圈巢狀時使用乘法**。總操作數量等於外層迴圈和內層迴圈操作數量之積,每一層迴圈依然可以分別套用第 `1.` 點和第 `2.` 點的技巧。 + +給定一個函式,我們可以用上述技巧來統計操作數量: + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +0(技巧 1) + a = a + n # +0(技巧 1) + # +n(技巧 2) + for i in range(5 * n + 1): + print(0) + # +n*n(技巧 3) + for i in range(2 * n): + for j in range(n + 1): + print(0) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + cout << 0 << endl; + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + cout << 0 << endl; + } + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + System.out.println(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + System.out.println(0); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +0(技巧 1) + a = a + n // +0(技巧 1) + // +n(技巧 2) + for i := 0; i < 5 * n + 1; i++ { + fmt.Println(0) + } + // +n*n(技巧 3) + for i := 0; i < 2 * n; i++ { + for j := 0; j < n + 1; j++ { + fmt.Println(0) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +0(技巧 1) + a = a + n // +0(技巧 1) + // +n(技巧 2) + for _ in 0 ..< (5 * n + 1) { + print(0) + } + // +n*n(技巧 3) + for _ in 0 ..< (2 * n) { + for _ in 0 ..< (n + 1) { + print(0) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + let a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n(技巧 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + let a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n(技巧 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + print(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + print(0); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + + // +n(技巧 2) + for i in 0..(5 * n + 1) { + println!("{}", 0); + } + + // +n*n(技巧 3) + for i in 0..(2 * n) { + for j in 0..(n + 1) { + println!("{}", 0); + } + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + printf("%d", 0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + printf("%d", 0); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + var a = 1 // +0(技巧 1) + a = a + n // +0(技巧 1) + // +n(技巧 2) + for (i in 0..<5 * n + 1) { + println(0) + } + // +n*n(技巧 3) + for (i in 0..<2 * n) { + for (j in 0..   不同操作數量對應的時間複雜度

+ +| 操作數量 $T(n)$ | 時間複雜度 $O(f(n))$ | +| ---------------------- | -------------------- | +| $100000$ | $O(1)$ | +| $3n + 2$ | $O(n)$ | +| $2n^2 + 3n + 2$ | $O(n^2)$ | +| $n^3 + 10000n^2$ | $O(n^3)$ | +| $2^n + 10000n^{10000}$ | $O(2^n)$ | + +## 常見型別 + +設輸入資料大小為 $n$ ,常見的時間複雜度型別如下圖所示(按照從低到高的順序排列)。 + +$$ +\begin{aligned} +O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline +\text{常數階} < \text{對數階} < \text{線性階} < \text{線性對數階} < \text{平方階} < \text{指數階} < \text{階乘階} +\end{aligned} +$$ + +![常見的時間複雜度型別](time_complexity.assets/time_complexity_common_types.png) + +### 常數階 $O(1)$ + +常數階的操作數量與輸入資料大小 $n$ 無關,即不隨著 $n$ 的變化而變化。 + +在以下函式中,儘管操作數量 `size` 可能很大,但由於其與輸入資料大小 $n$ 無關,因此時間複雜度仍為 $O(1)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{constant} +``` + +### 線性階 $O(n)$ + +線性階的操作數量相對於輸入資料大小 $n$ 以線性級別增長。線性階通常出現在單層迴圈中: + +```src +[file]{time_complexity}-[class]{}-[func]{linear} +``` + +走訪陣列和走訪鏈結串列等操作的時間複雜度均為 $O(n)$ ,其中 $n$ 為陣列或鏈結串列的長度: + +```src +[file]{time_complexity}-[class]{}-[func]{array_traversal} +``` + +值得注意的是,**輸入資料大小 $n$ 需根據輸入資料的型別來具體確定**。比如在第一個示例中,變數 $n$ 為輸入資料大小;在第二個示例中,陣列長度 $n$ 為資料大小。 + +### 平方階 $O(n^2)$ + +平方階的操作數量相對於輸入資料大小 $n$ 以平方級別增長。平方階通常出現在巢狀迴圈中,外層迴圈和內層迴圈的時間複雜度都為 $O(n)$ ,因此總體的時間複雜度為 $O(n^2)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{quadratic} +``` + +下圖對比了常數階、線性階和平方階三種時間複雜度。 + +![常數階、線性階和平方階的時間複雜度](time_complexity.assets/time_complexity_constant_linear_quadratic.png) + +以泡沫排序為例,外層迴圈執行 $n - 1$ 次,內層迴圈執行 $n-1$、$n-2$、$\dots$、$2$、$1$ 次,平均為 $n / 2$ 次,因此時間複雜度為 $O((n - 1) n / 2) = O(n^2)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{bubble_sort} +``` + +### 指數階 $O(2^n)$ + +生物學的“細胞分裂”是指數階增長的典型例子:初始狀態為 $1$ 個細胞,分裂一輪後變為 $2$ 個,分裂兩輪後變為 $4$ 個,以此類推,分裂 $n$ 輪後有 $2^n$ 個細胞。 + +下圖和以下程式碼模擬了細胞分裂的過程,時間複雜度為 $O(2^n)$ 。請注意,輸入 $n$ 表示分裂輪數,返回值 `count` 表示總分裂次數。 + +```src +[file]{time_complexity}-[class]{}-[func]{exponential} +``` + +![指數階的時間複雜度](time_complexity.assets/time_complexity_exponential.png) + +在實際演算法中,指數階常出現於遞迴函式中。例如在以下程式碼中,其遞迴地一分為二,經過 $n$ 次分裂後停止: + +```src +[file]{time_complexity}-[class]{}-[func]{exp_recur} +``` + +指數階增長非常迅速,在窮舉法(暴力搜尋、回溯等)中比較常見。對於資料規模較大的問題,指數階是不可接受的,通常需要使用動態規劃或貪婪演算法等來解決。 + +### 對數階 $O(\log n)$ + +與指數階相反,對數階反映了“每輪縮減到一半”的情況。設輸入資料大小為 $n$ ,由於每輪縮減到一半,因此迴圈次數是 $\log_2 n$ ,即 $2^n$ 的反函式。 + +下圖和以下程式碼模擬了“每輪縮減到一半”的過程,時間複雜度為 $O(\log_2 n)$ ,簡記為 $O(\log n)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{logarithmic} +``` + +![對數階的時間複雜度](time_complexity.assets/time_complexity_logarithmic.png) + +與指數階類似,對數階也常出現於遞迴函式中。以下程式碼形成了一棵高度為 $\log_2 n$ 的遞迴樹: + +```src +[file]{time_complexity}-[class]{}-[func]{log_recur} +``` + +對數階常出現於基於分治策略的演算法中,體現了“一分為多”和“化繁為簡”的演算法思想。它增長緩慢,是僅次於常數階的理想的時間複雜度。 + +!!! tip "$O(\log n)$ 的底數是多少?" + + 準確來說,“一分為 $m$”對應的時間複雜度是 $O(\log_m n)$ 。而透過對數換底公式,我們可以得到具有不同底數、相等的時間複雜度: + + $$ + O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) + $$ + + 也就是說,底數 $m$ 可以在不影響複雜度的前提下轉換。因此我們通常會省略底數 $m$ ,將對數階直接記為 $O(\log n)$ 。 + +### 線性對數階 $O(n \log n)$ + +線性對數階常出現於巢狀迴圈中,兩層迴圈的時間複雜度分別為 $O(\log n)$ 和 $O(n)$ 。相關程式碼如下: + +```src +[file]{time_complexity}-[class]{}-[func]{linear_log_recur} +``` + +下圖展示了線性對數階的生成方式。二元樹的每一層的操作總數都為 $n$ ,樹共有 $\log_2 n + 1$ 層,因此時間複雜度為 $O(n \log n)$ 。 + +![線性對數階的時間複雜度](time_complexity.assets/time_complexity_logarithmic_linear.png) + +主流排序演算法的時間複雜度通常為 $O(n \log n)$ ,例如快速排序、合併排序、堆積排序等。 + +### 階乘階 $O(n!)$ + +階乘階對應數學上的“全排列”問題。給定 $n$ 個互不重複的元素,求其所有可能的排列方案,方案數量為: + +$$ +n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 +$$ + +階乘通常使用遞迴實現。如下圖和以下程式碼所示,第一層分裂出 $n$ 個,第二層分裂出 $n - 1$ 個,以此類推,直至第 $n$ 層時停止分裂: + +```src +[file]{time_complexity}-[class]{}-[func]{factorial_recur} +``` + +![階乘階的時間複雜度](time_complexity.assets/time_complexity_factorial.png) + +請注意,因為當 $n \geq 4$ 時恆有 $n! > 2^n$ ,所以階乘階比指數階增長得更快,在 $n$ 較大時也是不可接受的。 + +## 最差、最佳、平均時間複雜度 + +**演算法的時間效率往往不是固定的,而是與輸入資料的分佈有關**。假設輸入一個長度為 $n$ 的陣列 `nums` ,其中 `nums` 由從 $1$ 至 $n$ 的數字組成,每個數字只出現一次;但元素順序是隨機打亂的,任務目標是返回元素 $1$ 的索引。我們可以得出以下結論。 + +- 當 `nums = [?, ?, ..., 1]` ,即當末尾元素是 $1$ 時,需要完整走訪陣列,**達到最差時間複雜度 $O(n)$** 。 +- 當 `nums = [1, ?, ?, ...]` ,即當首個元素為 $1$ 時,無論陣列多長都不需要繼續走訪,**達到最佳時間複雜度 $\Omega(1)$** 。 + +“最差時間複雜度”對應函式漸近上界,使用大 $O$ 記號表示。相應地,“最佳時間複雜度”對應函式漸近下界,用 $\Omega$ 記號表示: + +```src +[file]{worst_best_time_complexity}-[class]{}-[func]{find_one} +``` + +值得說明的是,我們在實際中很少使用最佳時間複雜度,因為通常只有在很小機率下才能達到,可能會帶來一定的誤導性。**而最差時間複雜度更為實用,因為它給出了一個效率安全值**,讓我們可以放心地使用演算法。 + +從上述示例可以看出,最差時間複雜度和最佳時間複雜度只出現於“特殊的資料分佈”,這些情況的出現機率可能很小,並不能真實地反映演算法執行效率。相比之下,**平均時間複雜度可以體現演算法在隨機輸入資料下的執行效率**,用 $\Theta$ 記號來表示。 + +對於部分演算法,我們可以簡單地推算出隨機資料分佈下的平均情況。比如上述示例,由於輸入陣列是被打亂的,因此元素 $1$ 出現在任意索引的機率都是相等的,那麼演算法的平均迴圈次數就是陣列長度的一半 $n / 2$ ,平均時間複雜度為 $\Theta(n / 2) = \Theta(n)$ 。 + +但對於較為複雜的演算法,計算平均時間複雜度往往比較困難,因為很難分析出在資料分佈下的整體數學期望。在這種情況下,我們通常使用最差時間複雜度作為演算法效率的評判標準。 + +!!! question "為什麼很少看到 $\Theta$ 符號?" + + 可能由於 $O$ 符號過於朗朗上口,因此我們常常使用它來表示平均時間複雜度。但從嚴格意義上講,這種做法並不規範。在本書和其他資料中,若遇到類似“平均時間複雜度 $O(n)$”的表述,請將其直接理解為 $\Theta(n)$ 。 diff --git a/zh-hant/docs/chapter_data_structure/basic_data_types.md b/zh-hant/docs/chapter_data_structure/basic_data_types.md new file mode 100644 index 0000000000..dea80ea1fe --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/basic_data_types.md @@ -0,0 +1,181 @@ +# 基本資料型別 + +當談及計算機中的資料時,我們會想到文字、圖片、影片、語音、3D 模型等各種形式。儘管這些資料的組織形式各異,但它們都由各種基本資料型別構成。 + +**基本資料型別是 CPU 可以直接進行運算的型別**,在演算法中直接被使用,主要包括以下幾種。 + +- 整數型別 `byte`、`short`、`int`、`long` 。 +- 浮點數型別 `float`、`double` ,用於表示小數。 +- 字元型別 `char` ,用於表示各種語言的字母、標點符號甚至表情符號等。 +- 布林型別 `bool` ,用於表示“是”與“否”判斷。 + +**基本資料型別以二進位制的形式儲存在計算機中**。一個二進位制位即為 $1$ 位元。在絕大多數現代作業系統中,$1$ 位元組(byte)由 $8$ 位元(bit)組成。 + +基本資料型別的取值範圍取決於其佔用的空間大小。下面以 Java 為例。 + +- 整數型別 `byte` 佔用 $1$ 位元組 = $8$ 位元 ,可以表示 $2^{8}$ 個數字。 +- 整數型別 `int` 佔用 $4$ 位元組 = $32$ 位元 ,可以表示 $2^{32}$ 個數字。 + +下表列舉了 Java 中各種基本資料型別的佔用空間、取值範圍和預設值。此表格無須死記硬背,大致理解即可,需要時可以透過查表來回憶。 + +

  基本資料型別的佔用空間和取值範圍

+ +| 型別 | 符號 | 佔用空間 | 最小值 | 最大值 | 預設值 | +| ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- | +| 整數 | `byte` | 1 位元組 | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | +| | `short` | 2 位元組 | $-2^{15}$ | $2^{15} - 1$ | $0$ | +| | `int` | 4 位元組 | $-2^{31}$ | $2^{31} - 1$ | $0$ | +| | `long` | 8 位元組 | $-2^{63}$ | $2^{63} - 1$ | $0$ | +| 浮點數 | `float` | 4 位元組 | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | +| | `double` | 8 位元組 | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | +| 字元 | `char` | 2 位元組 | $0$ | $2^{16} - 1$ | $0$ | +| 布林 | `bool` | 1 位元組 | $\text{false}$ | $\text{true}$ | $\text{false}$ | + +請注意,上表針對的是 Java 的基本資料型別的情況。每種程式語言都有各自的資料型別定義,它們的佔用空間、取值範圍和預設值可能會有所不同。 + +- 在 Python 中,整數型別 `int` 可以是任意大小,只受限於可用記憶體;浮點數 `float` 是雙精度 64 位;沒有 `char` 型別,單個字元實際上是長度為 1 的字串 `str` 。 +- C 和 C++ 未明確規定基本資料型別的大小,而因實現和平臺各異。上表遵循 LP64 [資料模型](https://en.cppreference.com/w/cpp/language/types#Properties),其用於包括 Linux 和 macOS 在內的 Unix 64 位作業系統。 +- 字元 `char` 的大小在 C 和 C++ 中為 1 位元組,在大多數程式語言中取決於特定的字元編碼方法,詳見“字元編碼”章節。 +- 即使表示布林量僅需 1 位($0$ 或 $1$),它在記憶體中通常也儲存為 1 位元組。這是因為現代計算機 CPU 通常將 1 位元組作為最小定址記憶體單元。 + +那麼,基本資料型別與資料結構之間有什麼關聯呢?我們知道,資料結構是在計算機中組織與儲存資料的方式。這句話的主語是“結構”而非“資料”。 + +如果想表示“一排數字”,我們自然會想到使用陣列。這是因為陣列的線性結構可以表示數字的相鄰關係和順序關係,但至於儲存的內容是整數 `int`、小數 `float` 還是字元 `char` ,則與“資料結構”無關。 + +換句話說,**基本資料型別提供了資料的“內容型別”,而資料結構提供了資料的“組織方式”**。例如以下程式碼,我們用相同的資料結構(陣列)來儲存與表示不同的基本資料型別,包括 `int`、`float`、`char`、`bool` 等。 + +=== "Python" + + ```python title="" + # 使用多種基本資料型別來初始化陣列 + numbers: list[int] = [0] * 5 + decimals: list[float] = [0.0] * 5 + # Python 的字元實際上是長度為 1 的字串 + characters: list[str] = ['0'] * 5 + bools: list[bool] = [False] * 5 + # Python 的串列可以自由儲存各種基本資料型別和物件引用 + data = [0, 0.0, 'a', False, ListNode(0)] + ``` + +=== "C++" + + ```cpp title="" + // 使用多種基本資料型別來初始化陣列 + int numbers[5]; + float decimals[5]; + char characters[5]; + bool bools[5]; + ``` + +=== "Java" + + ```java title="" + // 使用多種基本資料型別來初始化陣列 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + boolean[] bools = new boolean[5]; + ``` + +=== "C#" + + ```csharp title="" + // 使用多種基本資料型別來初始化陣列 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + bool[] bools = new bool[5]; + ``` + +=== "Go" + + ```go title="" + // 使用多種基本資料型別來初始化陣列 + var numbers = [5]int{} + var decimals = [5]float64{} + var characters = [5]byte{} + var bools = [5]bool{} + ``` + +=== "Swift" + + ```swift title="" + // 使用多種基本資料型別來初始化陣列 + let numbers = Array(repeating: 0, count: 5) + let decimals = Array(repeating: 0.0, count: 5) + let characters: [Character] = Array(repeating: "a", count: 5) + let bools = Array(repeating: false, count: 5) + ``` + +=== "JS" + + ```javascript title="" + // JavaScript 的陣列可以自由儲存各種基本資料型別和物件 + const array = [0, 0.0, 'a', false]; + ``` + +=== "TS" + + ```typescript title="" + // 使用多種基本資料型別來初始化陣列 + const numbers: number[] = []; + const characters: string[] = []; + const bools: boolean[] = []; + ``` + +=== "Dart" + + ```dart title="" + // 使用多種基本資料型別來初始化陣列 + List numbers = List.filled(5, 0); + List decimals = List.filled(5, 0.0); + List characters = List.filled(5, 'a'); + List bools = List.filled(5, false); + ``` + +=== "Rust" + + ```rust title="" + // 使用多種基本資料型別來初始化陣列 + let numbers: Vec = vec![0; 5]; + let decimals: Vec = vec![0.0; 5]; + let characters: Vec = vec!['0'; 5]; + let bools: Vec = vec![false; 5]; + ``` + +=== "C" + + ```c title="" + // 使用多種基本資料型別來初始化陣列 + int numbers[10]; + float decimals[10]; + char characters[10]; + bool bools[10]; + ``` + +=== "Kotlin" + + ```kotlin title="" + // 使用多種基本資料型別來初始化陣列 + val numbers = IntArray(5) + val decinals = FloatArray(5) + val characters = CharArray(5) + val bools = BooleanArray(5) + ``` + +=== "Ruby" + + ```ruby title="" + # Ruby 的串列可以自由儲存各種基本資料型別和物件引用 + data = [0, 0.0, 'a', false, ListNode(0)] + ``` + +=== "Zig" + + ```zig title="" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A8%AE%E5%9F%BA%E6%9C%AC%E8%B3%87%E6%96%99%E5%9E%8B%E5%88%A5%E4%BE%86%E5%88%9D%E5%A7%8B%E5%8C%96%E9%99%A3%E5%88%97%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20%2A%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20%2A%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E5%85%83%E5%AF%A6%E9%9A%9B%E4%B8%8A%E6%98%AF%E9%95%B7%E5%BA%A6%E7%82%BA%201%20%E7%9A%84%E5%AD%97%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B%270%27%5D%20%2A%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20%2A%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E4%B8%B2%E5%88%97%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%84%B2%E5%AD%98%E5%90%84%E7%A8%AE%E5%9F%BA%E6%9C%AC%E8%B3%87%E6%96%99%E5%9E%8B%E5%88%A5%E5%92%8C%E7%89%A9%E4%BB%B6%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0%2C%200.0%2C%20%27a%27%2C%20False%2C%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.assets/ascii_table.png b/zh-hant/docs/chapter_data_structure/character_encoding.assets/ascii_table.png new file mode 100644 index 0000000000..bef8d18128 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/character_encoding.assets/ascii_table.png differ diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png b/zh-hant/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png new file mode 100644 index 0000000000..d219c35517 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png differ diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png b/zh-hant/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png new file mode 100644 index 0000000000..7029dab793 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png differ diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.md b/zh-hant/docs/chapter_data_structure/character_encoding.md new file mode 100644 index 0000000000..3358828212 --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/character_encoding.md @@ -0,0 +1,87 @@ +# 字元編碼 * + +在計算機中,所有資料都是以二進位制數的形式儲存的,字元 `char` 也不例外。為了表示字元,我們需要建立一套“字符集”,規定每個字元和二進位制數之間的一一對應關係。有了字符集之後,計算機就可以透過查表完成二進位制數到字元的轉換。 + +## ASCII 字符集 + +ASCII 碼是最早出現的字符集,其全稱為 American Standard Code for Information Interchange(美國標準資訊交換程式碼)。它使用 7 位二進位制數(一個位元組的低 7 位)表示一個字元,最多能夠表示 128 個不同的字元。如下圖所示,ASCII 碼包括英文字母的大小寫、數字 0 ~ 9、一些標點符號,以及一些控制字元(如換行符和製表符)。 + +![ASCII 碼](character_encoding.assets/ascii_table.png) + +然而,**ASCII 碼僅能夠表示英文**。隨著計算機的全球化,誕生了一種能夠表示更多語言的 EASCII 字符集。它在 ASCII 的 7 位基礎上擴展到 8 位,能夠表示 256 個不同的字元。 + +在世界範圍內,陸續出現了一批適用於不同地區的 EASCII 字符集。這些字符集的前 128 個字元統一為 ASCII 碼,後 128 個字元定義不同,以適應不同語言的需求。 + +## GBK 字符集 + +後來人們發現,**EASCII 碼仍然無法滿足許多語言的字元數量要求**。比如漢字有近十萬個,光日常使用的就有幾千個。中國國家標準總局於 1980 年釋出了 GB2312 字符集,其收錄了 6763 個漢字,基本滿足了漢字的計算機處理需要。 + +然而,GB2312 無法處理部分罕見字和繁體字。GBK 字符集是在 GB2312 的基礎上擴展得到的,它共收錄了 21886 個漢字。在 GBK 的編碼方案中,ASCII 字元使用一個位元組表示,漢字使用兩個位元組表示。 + +## Unicode 字符集 + +隨著計算機技術的蓬勃發展,字符集與編碼標準百花齊放,而這帶來了許多問題。一方面,這些字符集一般只定義了特定語言的字元,無法在多語言環境下正常工作。另一方面,同一種語言存在多種字符集標準,如果兩臺計算機使用的是不同的編碼標準,則在資訊傳遞時就會出現亂碼。 + +那個時代的研究人員就在想:**如果推出一個足夠完整的字符集,將世界範圍內的所有語言和符號都收錄其中,不就可以解決跨語言環境和亂碼問題了嗎**?在這種想法的驅動下,一個大而全的字符集 Unicode 應運而生。 + +Unicode 的中文名稱為“統一碼”,理論上能容納 100 多萬個字元。它致力於將全球範圍內的字元納入統一的字符集之中,提供一種通用的字符集來處理和顯示各種語言文字,減少因為編碼標準不同而產生的亂碼問題。 + +自 1991 年釋出以來,Unicode 不斷擴充新的語言與字元。截至 2022 年 9 月,Unicode 已經包含 149186 個字元,包括各種語言的字元、符號甚至表情符號等。在龐大的 Unicode 字符集中,常用的字元佔用 2 位元組,有些生僻的字元佔用 3 位元組甚至 4 位元組。 + +Unicode 是一種通用字符集,本質上是給每個字元分配一個編號(稱為“碼點”),**但它並沒有規定在計算機中如何儲存這些字元碼點**。我們不禁會問:當多種長度的 Unicode 碼點同時出現在一個文字中時,系統如何解析字元?例如給定一個長度為 2 位元組的編碼,系統如何確認它是一個 2 位元組的字元還是兩個 1 位元組的字元? + +對於以上問題,**一種直接的解決方案是將所有字元儲存為等長的編碼**。如下圖所示,“Hello”中的每個字元佔用 1 位元組,“演算法”中的每個字元佔用 2 位元組。我們可以透過高位填 0 將“Hello 演算法”中的所有字元都編碼為 2 位元組長度。這樣系統就可以每隔 2 位元組解析一個字元,恢復這個短語的內容了。 + +![Unicode 編碼示例](character_encoding.assets/unicode_hello_algo.png) + +然而 ASCII 碼已經向我們證明,編碼英文只需 1 位元組。若採用上述方案,英文文字佔用空間的大小將會是 ASCII 編碼下的兩倍,非常浪費記憶體空間。因此,我們需要一種更加高效的 Unicode 編碼方法。 + +## UTF-8 編碼 + +目前,UTF-8 已成為國際上使用最廣泛的 Unicode 編碼方法。**它是一種可變長度的編碼**,使用 1 到 4 位元組來表示一個字元,根據字元的複雜性而變。ASCII 字元只需 1 位元組,拉丁字母和希臘字母需要 2 位元組,常用的中文字元需要 3 位元組,其他的一些生僻字元需要 4 位元組。 + +UTF-8 的編碼規則並不複雜,分為以下兩種情況。 + +- 對於長度為 1 位元組的字元,將最高位設定為 $0$ ,其餘 7 位設定為 Unicode 碼點。值得注意的是,ASCII 字元在 Unicode 字符集中佔據了前 128 個碼點。也就是說,**UTF-8 編碼可以向下相容 ASCII 碼**。這意味著我們可以使用 UTF-8 來解析年代久遠的 ASCII 碼文字。 +- 對於長度為 $n$ 位元組的字元(其中 $n > 1$),將首個位元組的高 $n$ 位都設定為 $1$ ,第 $n + 1$ 位設定為 $0$ ;從第二個位元組開始,將每個位元組的高 2 位都設定為 $10$ ;其餘所有位用於填充字元的 Unicode 碼點。 + +下圖展示了“Hello演算法”對應的 UTF-8 編碼。觀察發現,由於最高 $n$ 位都設定為 $1$ ,因此系統可以透過讀取最高位 $1$ 的個數來解析出字元的長度為 $n$ 。 + +但為什麼要將其餘所有位元組的高 2 位都設定為 $10$ 呢?實際上,這個 $10$ 能夠起到校驗符的作用。假設系統從一個錯誤的位元組開始解析文字,位元組頭部的 $10$ 能夠幫助系統快速判斷出異常。 + +之所以將 $10$ 當作校驗符,是因為在 UTF-8 編碼規則下,不可能有字元的最高兩位是 $10$ 。這個結論可以用反證法來證明:假設一個字元的最高兩位是 $10$ ,說明該字元的長度為 $1$ ,對應 ASCII 碼。而 ASCII 碼的最高位應該是 $0$ ,與假設矛盾。 + +![UTF-8 編碼示例](character_encoding.assets/utf-8_hello_algo.png) + +除了 UTF-8 之外,常見的編碼方式還包括以下兩種。 + +- **UTF-16 編碼**:使用 2 或 4 位元組來表示一個字元。所有的 ASCII 字元和常用的非英文字元,都用 2 位元組表示;少數字符需要用到 4 位元組表示。對於 2 位元組的字元,UTF-16 編碼與 Unicode 碼點相等。 +- **UTF-32 編碼**:每個字元都使用 4 位元組。這意味著 UTF-32 比 UTF-8 和 UTF-16 更佔用空間,特別是對於 ASCII 字元佔比較高的文字。 + +從儲存空間佔用的角度看,使用 UTF-8 表示英文字元非常高效,因為它僅需 1 位元組;使用 UTF-16 編碼某些非英文字元(例如中文)會更加高效,因為它僅需 2 位元組,而 UTF-8 可能需要 3 位元組。 + +從相容性的角度看,UTF-8 的通用性最佳,許多工具和庫優先支持 UTF-8 。 + +## 程式語言的字元編碼 + +對於以往的大多數程式語言,程式執行中的字串都採用 UTF-16 或 UTF-32 這類等長編碼。在等長編碼下,我們可以將字串看作陣列來處理,這種做法具有以下優點。 + +- **隨機訪問**:UTF-16 編碼的字串可以很容易地進行隨機訪問。UTF-8 是一種變長編碼,要想找到第 $i$ 個字元,我們需要從字串的開始處走訪到第 $i$ 個字元,這需要 $O(n)$ 的時間。 +- **字元計數**:與隨機訪問類似,計算 UTF-16 編碼的字串的長度也是 $O(1)$ 的操作。但是,計算 UTF-8 編碼的字串的長度需要走訪整個字串。 +- **字串操作**:在 UTF-16 編碼的字串上,很多字串操作(如分割、連線、插入、刪除等)更容易進行。在 UTF-8 編碼的字串上,進行這些操作通常需要額外的計算,以確保不會產生無效的 UTF-8 編碼。 + +實際上,程式語言的字元編碼方案設計是一個很有趣的話題,涉及許多因素。 + +- Java 的 `String` 型別使用 UTF-16 編碼,每個字元佔用 2 位元組。這是因為 Java 語言設計之初,人們認為 16 位足以表示所有可能的字元。然而,這是一個不正確的判斷。後來 Unicode 規範擴展到了超過 16 位,所以 Java 中的字元現在可能由一對 16 位的值(稱為“代理對”)表示。 +- JavaScript 和 TypeScript 的字串使用 UTF-16 編碼的原因與 Java 類似。當 1995 年 Netscape 公司首次推出 JavaScript 語言時,Unicode 還處於發展早期,那時候使用 16 位的編碼就足以表示所有的 Unicode 字元了。 +- C# 使用 UTF-16 編碼,主要是因為 .NET 平臺是由 Microsoft 設計的,而 Microsoft 的很多技術(包括 Windows 作業系統)都廣泛使用 UTF-16 編碼。 + +由於以上程式語言對字元數量的低估,它們不得不採取“代理對”的方式來表示超過 16 位長度的 Unicode 字元。這是一個不得已為之的無奈之舉。一方面,包含代理對的字串中,一個字元可能佔用 2 位元組或 4 位元組,從而喪失了等長編碼的優勢。另一方面,處理代理對需要額外增加程式碼,這提高了程式設計的複雜性和除錯難度。 + +出於以上原因,部分程式語言提出了一些不同的編碼方案。 + +- Python 中的 `str` 使用 Unicode 編碼,並採用一種靈活的字串表示,儲存的字元長度取決於字串中最大的 Unicode 碼點。若字串中全部是 ASCII 字元,則每個字元佔用 1 位元組;如果有字元超出了 ASCII 範圍,但全部在基本多語言平面(BMP)內,則每個字元佔用 2 位元組;如果有超出 BMP 的字元,則每個字元佔用 4 位元組。 +- Go 語言的 `string` 型別在內部使用 UTF-8 編碼。Go 語言還提供了 `rune` 型別,它用於表示單個 Unicode 碼點。 +- Rust 語言的 `str` 和 `String` 型別在內部使用 UTF-8 編碼。Rust 也提供了 `char` 型別,用於表示單個 Unicode 碼點。 + +需要注意的是,以上討論的都是字串在程式語言中的儲存方式,**這和字串如何在檔案中儲存或在網路中傳輸是不同的問題**。在檔案儲存或網路傳輸中,我們通常會將字串編碼為 UTF-8 格式,以達到最優的相容性和空間效率。 diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png new file mode 100644 index 0000000000..7454732574 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png differ diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png new file mode 100644 index 0000000000..a9f1014878 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png differ diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png new file mode 100644 index 0000000000..aafa9e33a1 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png differ diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.md b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.md new file mode 100644 index 0000000000..c6861fdb59 --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.md @@ -0,0 +1,48 @@ +# 資料結構分類 + +常見的資料結構包括陣列、鏈結串列、堆疊、佇列、雜湊表、樹、堆積、圖,它們可以從“邏輯結構”和“物理結構”兩個維度進行分類。 + +## 邏輯結構:線性與非線性 + +**邏輯結構揭示了資料元素之間的邏輯關係**。在陣列和鏈結串列中,資料按照一定順序排列,體現了資料之間的線性關係;而在樹中,資料從頂部向下按層次排列,表現出“祖先”與“後代”之間的派生關係;圖則由節點和邊構成,反映了複雜的網路關係。 + +如下圖所示,邏輯結構可分為“線性”和“非線性”兩大類。線性結構比較直觀,指資料在邏輯關係上呈線性排列;非線性結構則相反,呈非線性排列。 + +- **線性資料結構**:陣列、鏈結串列、堆疊、佇列、雜湊表,元素之間是一對一的順序關係。 +- **非線性資料結構**:樹、堆積、圖、雜湊表。 + +非線性資料結構可以進一步劃分為樹形結構和網狀結構。 + +- **樹形結構**:樹、堆積、雜湊表,元素之間是一對多的關係。 +- **網狀結構**:圖,元素之間是多對多的關係。 + +![線性資料結構與非線性資料結構](classification_of_data_structure.assets/classification_logic_structure.png) + +## 物理結構:連續與分散 + +**當演算法程式執行時,正在處理的資料主要儲存在記憶體中**。下圖展示了一個計算機記憶體條,其中每個黑色方塊都包含一塊記憶體空間。我們可以將記憶體想象成一個巨大的 Excel 表格,其中每個單元格都可以儲存一定大小的資料。 + +**系統透過記憶體位址來訪問目標位置的資料**。如下圖所示,計算機根據特定規則為表格中的每個單元格分配編號,確保每個記憶體空間都有唯一的記憶體位址。有了這些位址,程式便可以訪問記憶體中的資料。 + +![記憶體條、記憶體空間、記憶體位址](classification_of_data_structure.assets/computer_memory_location.png) + +!!! tip + + 值得說明的是,將記憶體比作 Excel 表格是一個簡化的類比,實際記憶體的工作機制比較複雜,涉及位址空間、記憶體管理、快取機制、虛擬記憶體和物理記憶體等概念。 + +記憶體是所有程式的共享資源,當某塊記憶體被某個程式佔用時,則通常無法被其他程式同時使用了。**因此在資料結構與演算法的設計中,記憶體資源是一個重要的考慮因素**。比如,演算法所佔用的記憶體峰值不應超過系統剩餘空閒記憶體;如果缺少連續大塊的記憶體空間,那麼所選用的資料結構必須能夠儲存在分散的記憶體空間內。 + +如下圖所示,**物理結構反映了資料在計算機記憶體中的儲存方式**,可分為連續空間儲存(陣列)和分散空間儲存(鏈結串列)。物理結構從底層決定了資料的訪問、更新、增刪等操作方法,兩種物理結構在時間效率和空間效率方面呈現出互補的特點。 + +![連續空間儲存與分散空間儲存](classification_of_data_structure.assets/classification_phisical_structure.png) + +值得說明的是,**所有資料結構都是基於陣列、鏈結串列或二者的組合實現的**。例如,堆疊和佇列既可以使用陣列實現,也可以使用鏈結串列實現;而雜湊表的實現可能同時包含陣列和鏈結串列。 + +- **基於陣列可實現**:堆疊、佇列、雜湊表、樹、堆積、圖、矩陣、張量(維度 $\geq 3$ 的陣列)等。 +- **基於鏈結串列可實現**:堆疊、佇列、雜湊表、樹、堆積、圖等。 + +鏈結串列在初始化後,仍可以在程式執行過程中對其長度進行調整,因此也稱“動態資料結構”。陣列在初始化後長度不可變,因此也稱“靜態資料結構”。值得注意的是,陣列可透過重新分配記憶體實現長度變化,從而具備一定的“動態性”。 + +!!! tip + + 如果你感覺物理結構理解起來有困難,建議先閱讀下一章,然後再回顧本節內容。 diff --git a/zh-hant/docs/chapter_data_structure/index.md b/zh-hant/docs/chapter_data_structure/index.md new file mode 100644 index 0000000000..ad7b8ec0de --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/index.md @@ -0,0 +1,9 @@ +# 資料結構 + +![資料結構](../assets/covers/chapter_data_structure.jpg) + +!!! abstract + + 資料結構如同一副穩固而多樣的框架。 + + 它為資料的有序組織提供了藍圖,演算法得以在此基礎上生動起來。 diff --git a/zh-hant/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png b/zh-hant/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png new file mode 100644 index 0000000000..f07dcfa401 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png differ diff --git a/zh-hant/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png b/zh-hant/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png new file mode 100644 index 0000000000..c0a3210d65 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png differ diff --git a/zh-hant/docs/chapter_data_structure/number_encoding.md b/zh-hant/docs/chapter_data_structure/number_encoding.md new file mode 100644 index 0000000000..ae64c83a1e --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/number_encoding.md @@ -0,0 +1,150 @@ +# 數字編碼 * + +!!! tip + + 在本書中,標題帶有 * 符號的是選讀章節。如果你時間有限或感到理解困難,可以先跳過,等學完必讀章節後再單獨攻克。 + +## 原碼、一補數和二補數 + +在上一節的表格中我們發現,所有整數型別能夠表示的負數都比正數多一個,例如 `byte` 的取值範圍是 $[-128, 127]$ 。這個現象比較反直覺,它的內在原因涉及原碼、一補數、二補數的相關知識。 + +首先需要指出,**數字是以“二補數”的形式儲存在計算機中的**。在分析這樣做的原因之前,首先給出三者的定義。 + +- **原碼**:我們將數字的二進位制表示的最高位視為符號位,其中 $0$ 表示正數,$1$ 表示負數,其餘位表示數字的值。 +- **一補數**:正數的一補數與其原碼相同,負數的一補數是對其原碼除符號位外的所有位取反。 +- **二補數**:正數的二補數與其原碼相同,負數的二補數是在其一補數的基礎上加 $1$ 。 + +下圖展示了原碼、一補數和二補數之間的轉換方法。 + +![原碼、一補數與二補數之間的相互轉換](number_encoding.assets/1s_2s_complement.png) + +原碼(sign-magnitude)雖然最直觀,但存在一些侷限性。一方面,**負數的原碼不能直接用於運算**。例如在原碼下計算 $1 + (-2)$ ,得到的結果是 $-3$ ,這顯然是不對的。 + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline +& = 1000 \; 0011 \newline +& \rightarrow -3 +\end{aligned} +$$ + +為了解決此問題,計算機引入了一補數(1's complement)。如果我們先將原碼轉換為一補數,並在一補數下計算 $1 + (-2)$ ,最後將結果從一補數轉換回原碼,則可得到正確結果 $-1$ 。 + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 \; \text{(原碼)} + 1000 \; 0010 \; \text{(原碼)} \newline +& = 0000 \; 0001 \; \text{(一補數)} + 1111 \; 1101 \; \text{(一補數)} \newline +& = 1111 \; 1110 \; \text{(一補數)} \newline +& = 1000 \; 0001 \; \text{(原碼)} \newline +& \rightarrow -1 +\end{aligned} +$$ + +另一方面,**數字零的原碼有 $+0$ 和 $-0$ 兩種表示方式**。這意味著數字零對應兩個不同的二進位制編碼,這可能會帶來歧義。比如在條件判斷中,如果沒有區分正零和負零,則可能會導致判斷結果出錯。而如果我們想處理正零和負零歧義,則需要引入額外的判斷操作,這可能會降低計算機的運算效率。 + +$$ +\begin{aligned} ++0 & \rightarrow 0000 \; 0000 \newline +-0 & \rightarrow 1000 \; 0000 +\end{aligned} +$$ + +與原碼一樣,一補數也存在正負零歧義問題,因此計算機進一步引入了二補數(2's complement)。我們先來觀察一下負零的原碼、一補數、二補數的轉換過程: + +$$ +\begin{aligned} +-0 \rightarrow \; & 1000 \; 0000 \; \text{(原碼)} \newline += \; & 1111 \; 1111 \; \text{(一補數)} \newline += 1 \; & 0000 \; 0000 \; \text{(二補數)} \newline +\end{aligned} +$$ + +在負零的一補數基礎上加 $1$ 會產生進位,但 `byte` 型別的長度只有 8 位,因此溢位到第 9 位的 $1$ 會被捨棄。也就是說,**負零的二補數為 $0000 \; 0000$ ,與正零的二補數相同**。這意味著在二補數表示中只存在一個零,正負零歧義從而得到解決。 + +還剩最後一個疑惑:`byte` 型別的取值範圍是 $[-128, 127]$ ,多出來的一個負數 $-128$ 是如何得到的呢?我們注意到,區間 $[-127, +127]$ 內的所有整數都有對應的原碼、一補數和二補數,並且原碼和二補數之間可以互相轉換。 + +然而,**二補數 $1000 \; 0000$ 是一個例外,它並沒有對應的原碼**。根據轉換方法,我們得到該二補數的原碼為 $0000 \; 0000$ 。這顯然是矛盾的,因為該原碼表示數字 $0$ ,它的二補數應該是自身。計算機規定這個特殊的二補數 $1000 \; 0000$ 代表 $-128$ 。實際上,$(-1) + (-127)$ 在二補數下的計算結果就是 $-128$ 。 + +$$ +\begin{aligned} +& (-127) + (-1) \newline +& \rightarrow 1111 \; 1111 \; \text{(原碼)} + 1000 \; 0001 \; \text{(原碼)} \newline +& = 1000 \; 0000 \; \text{(一補數)} + 1111 \; 1110 \; \text{(一補數)} \newline +& = 1000 \; 0001 \; \text{(二補數)} + 1111 \; 1111 \; \text{(二補數)} \newline +& = 1000 \; 0000 \; \text{(二補數)} \newline +& \rightarrow -128 +\end{aligned} +$$ + +你可能已經發現了,上述所有計算都是加法運算。這暗示著一個重要事實:**計算機內部的硬體電路主要是基於加法運算設計的**。這是因為加法運算相對於其他運算(比如乘法、除法和減法)來說,硬體實現起來更簡單,更容易進行並行化處理,運算速度更快。 + +請注意,這並不意味著計算機只能做加法。**透過將加法與一些基本邏輯運算結合,計算機能夠實現各種其他的數學運算**。例如,計算減法 $a - b$ 可以轉換為計算加法 $a + (-b)$ ;計算乘法和除法可以轉換為計算多次加法或減法。 + +現在我們可以總結出計算機使用二補數的原因:基於二補數表示,計算機可以用同樣的電路和操作來處理正數和負數的加法,不需要設計特殊的硬體電路來處理減法,並且無須特別處理正負零的歧義問題。這大大簡化了硬體設計,提高了運算效率。 + +二補數的設計非常精妙,因篇幅關係我們就先介紹到這裡,建議有興趣的讀者進一步深入瞭解。 + +## 浮點數編碼 + +細心的你可能會發現:`int` 和 `float` 長度相同,都是 4 位元組 ,但為什麼 `float` 的取值範圍遠大於 `int` ?這非常反直覺,因為按理說 `float` 需要表示小數,取值範圍應該變小才對。 + +實際上,**這是因為浮點數 `float` 採用了不同的表示方式**。記一個 32 位元長度的二進位制數為: + +$$ +b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 +$$ + +根據 IEEE 754 標準,32-bit 長度的 `float` 由以下三個部分構成。 + +- 符號位 $\mathrm{S}$ :佔 1 位 ,對應 $b_{31}$ 。 +- 指數位 $\mathrm{E}$ :佔 8 位 ,對應 $b_{30} b_{29} \ldots b_{23}$ 。 +- 分數位 $\mathrm{N}$ :佔 23 位 ,對應 $b_{22} b_{21} \ldots b_0$ 。 + +二進位制數 `float` 對應值的計算方法為: + +$$ +\text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 +$$ + +轉化到十進位制下的計算公式為: + +$$ +\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) +$$ + +其中各項的取值範圍為: + +$$ +\begin{aligned} +\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline +(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] +\end{aligned} +$$ + +![IEEE 754 標準下的 float 的計算示例](number_encoding.assets/ieee_754_float.png) + +觀察上圖,給定一個示例資料 $\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,則有: + +$$ +\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 +$$ + +現在我們可以回答最初的問題:**`float` 的表示方式包含指數位,導致其取值範圍遠大於 `int`** 。根據以上計算,`float` 可表示的最大正數為 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切換符號位便可得到最小負數。 + +**儘管浮點數 `float` 擴展了取值範圍,但其副作用是犧牲了精度**。整數型別 `int` 將全部 32 位元用於表示數字,數字是均勻分佈的;而由於指數位的存在,浮點數 `float` 的數值越大,相鄰兩個數字之間的差值就會趨向越大。 + +如下表所示,指數位 $\mathrm{E} = 0$ 和 $\mathrm{E} = 255$ 具有特殊含義,**用於表示零、無窮大、$\mathrm{NaN}$ 等**。 + +

  指數位含義

+ +| 指數位 E | 分數位 $\mathrm{N} = 0$ | 分數位 $\mathrm{N} \ne 0$ | 計算公式 | +| ------------------ | ----------------------- | ------------------------- | ---------------------------------------------------------------------- | +| $0$ | $\pm 0$ | 次正規數 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | +| $1, 2, \dots, 254$ | 正規數 | 正規數 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | +| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | + +值得說明的是,次正規數顯著提升了浮點數的精度。最小正正規數為 $2^{-126}$ ,最小正次正規數為 $2^{-126} \times 2^{-23}$ 。 + +雙精度 `double` 也採用類似於 `float` 的表示方法,在此不做贅述。 diff --git a/zh-hant/docs/chapter_data_structure/summary.md b/zh-hant/docs/chapter_data_structure/summary.md new file mode 100644 index 0000000000..03f326648c --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/summary.md @@ -0,0 +1,66 @@ +# 小結 + +### 重點回顧 + +- 資料結構可以從邏輯結構和物理結構兩個角度進行分類。邏輯結構描述了資料元素之間的邏輯關係,而物理結構描述了資料在計算機記憶體中的儲存方式。 +- 常見的邏輯結構包括線性、樹狀和網狀等。通常我們根據邏輯結構將資料結構分為線性(陣列、鏈結串列、堆疊、佇列)和非線性(樹、圖、堆積)兩種。雜湊表的實現可能同時包含線性資料結構和非線性資料結構。 +- 當程式執行時,資料被儲存在計算機記憶體中。每個記憶體空間都擁有對應的記憶體位址,程式透過這些記憶體位址訪問資料。 +- 物理結構主要分為連續空間儲存(陣列)和分散空間儲存(鏈結串列)。所有資料結構都是由陣列、鏈結串列或兩者的組合實現的。 +- 計算機中的基本資料型別包括整數 `byte`、`short`、`int`、`long` ,浮點數 `float`、`double` ,字元 `char` 和布林 `bool` 。它們的取值範圍取決於佔用空間大小和表示方式。 +- 原碼、一補數和二補數是在計算機中編碼數字的三種方法,它們之間可以相互轉換。整數的原碼的最高位是符號位,其餘位是數字的值。 +- 整數在計算機中是以二補數的形式儲存的。在二補數表示下,計算機可以對正數和負數的加法一視同仁,不需要為減法操作單獨設計特殊的硬體電路,並且不存在正負零歧義的問題。 +- 浮點數的編碼由 1 位符號位、8 位指數位和 23 位分數位構成。由於存在指數位,因此浮點數的取值範圍遠大於整數,代價是犧牲了精度。 +- ASCII 碼是最早出現的英文字符集,長度為 1 位元組,共收錄 127 個字元。GBK 字符集是常用的中文字符集,共收錄兩萬多個漢字。Unicode 致力於提供一個完整的字符集標準,收錄世界上各種語言的字元,從而解決由於字元編碼方法不一致而導致的亂碼問題。 +- UTF-8 是最受歡迎的 Unicode 編碼方法,通用性非常好。它是一種變長的編碼方法,具有很好的擴展性,有效提升了儲存空間的使用效率。UTF-16 和 UTF-32 是等長的編碼方法。在編碼中文時,UTF-16 佔用的空間比 UTF-8 更小。Java 和 C# 等程式語言預設使用 UTF-16 編碼。 + +### Q & A + +**Q**:為什麼雜湊表同時包含線性資料結構和非線性資料結構? + +雜湊表底層是陣列,而為了解決雜湊衝突,我們可能會使用“鏈式位址”(後續“雜湊衝突”章節會講):陣列中每個桶指向一個鏈結串列,當鏈結串列長度超過一定閾值時,又可能被轉化為樹(通常為紅黑樹)。 + +從儲存的角度來看,雜湊表的底層是陣列,其中每一個桶槽位可能包含一個值,也可能包含一個鏈結串列或一棵樹。因此,雜湊表可能同時包含線性資料結構(陣列、鏈結串列)和非線性資料結構(樹)。 + +**Q**:`char` 型別的長度是 1 位元組嗎? + +`char` 型別的長度由程式語言採用的編碼方法決定。例如,Java、JavaScript、TypeScript、C# 都採用 UTF-16 編碼(儲存 Unicode 碼點),因此 `char` 型別的長度為 2 位元組。 + +**Q**:基於陣列實現的資料結構也稱“靜態資料結構” 是否有歧義?堆疊也可以進行出堆疊和入堆疊等操作,這些操作都是“動態”的。 + +堆疊確實可以實現動態的資料操作,但資料結構仍然是“靜態”(長度不可變)的。儘管基於陣列的資料結構可以動態地新增或刪除元素,但它們的容量是固定的。如果資料量超出了預分配的大小,就需要建立一個新的更大的陣列,並將舊陣列的內容複製到新陣列中。 + +**Q**:在構建堆疊(佇列)的時候,未指定它的大小,為什麼它們是“靜態資料結構”呢? + +在高階程式語言中,我們無須人工指定堆疊(佇列)的初始容量,這個工作由類別內部自動完成。例如,Java 的 `ArrayList` 的初始容量通常為 10。另外,擴容操作也是自動實現的。詳見後續的“串列”章節。 + +**Q**:原碼轉二補數的方法是“先取反後加 1”,那麼二補數轉原碼應該是逆運算“先減 1 後取反”,而二補數轉原碼也一樣可以透過“先取反後加 1”得到,這是為什麼呢? + +這是因為原碼和二補數的相互轉換實際上是計算“補數”的過程。我們先給出補數的定義:假設 $a + b = c$ ,那麼我們稱 $a$ 是 $b$ 到 $c$ 的補數,反之也稱 $b$ 是 $a$ 到 $c$ 的補數。 + +給定一個 $n = 4$ 位長度的二進位制數 $0010$ ,如果將這個數字看作原碼(不考慮符號位),那麼它的二補數需透過“先取反後加 1”得到: + +$$ +0010 \rightarrow 1101 \rightarrow 1110 +$$ + +我們會發現,原碼和二補數的和是 $0010 + 1110 = 10000$ ,也就是說,二補數 $1110$ 是原碼 $0010$ 到 $10000$ 的“補數”。**這意味著上述“先取反後加 1”實際上是計算到 $10000$ 的補數的過程**。 + +那麼,二補數 $1110$ 到 $10000$ 的“補數”是多少呢?我們依然可以用“先取反後加 1”得到它: + +$$ +1110 \rightarrow 0001 \rightarrow 0010 +$$ + +換句話說,原碼和二補數互為對方到 $10000$ 的“補數”,因此“原碼轉二補數”和“二補數轉原碼”可以用相同的操作(先取反後加 1 )實現。 + +當然,我們也可以用逆運算來求二補數 $1110$ 的原碼,即“先減 1 後取反”: + +$$ +1110 \rightarrow 1101 \rightarrow 0010 +$$ + +總結來看,“先取反後加 1”和“先減 1 後取反”這兩種運算都是在計算到 $10000$ 的補數,它們是等價的。 + +本質上看,“取反”操作實際上是求到 $1111$ 的補數(因為恆有 `原碼 + 一補數 = 1111`);而在一補數基礎上再加 1 得到的二補數,就是到 $10000$ 的補數。 + +上述以 $n = 4$ 為例,其可被推廣至任意位數的二進位制數。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png new file mode 100644 index 0000000000..3649ce1569 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.md b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 0000000000..5f55ac4e80 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,45 @@ +# 分治搜尋策略 + +我們已經學過,搜尋演算法分為兩大類。 + +- **暴力搜尋**:它透過走訪資料結構實現,時間複雜度為 $O(n)$ 。 +- **自適應搜尋**:它利用特有的資料組織形式或先驗資訊,時間複雜度可達到 $O(\log n)$ 甚至 $O(1)$ 。 + +實際上,**時間複雜度為 $O(\log n)$ 的搜尋演算法通常是基於分治策略實現的**,例如二分搜尋和樹。 + +- 二分搜尋的每一步都將問題(在陣列中搜索目標元素)分解為一個小問題(在陣列的一半中搜索目標元素),這個過程一直持續到陣列為空或找到目標元素為止。 +- 樹是分治思想的代表,在二元搜尋樹、AVL 樹、堆積等資料結構中,各種操作的時間複雜度皆為 $O(\log n)$ 。 + +二分搜尋的分治策略如下所示。 + +- **問題可以分解**:二分搜尋遞迴地將原問題(在陣列中進行查詢)分解為子問題(在陣列的一半中進行查詢),這是透過比較中間元素和目標元素來實現的。 +- **子問題是獨立的**:在二分搜尋中,每輪只處理一個子問題,它不受其他子問題的影響。 +- **子問題的解無須合併**:二分搜尋旨在查詢一個特定元素,因此不需要將子問題的解進行合併。當子問題得到解決時,原問題也會同時得到解決。 + +分治能夠提升搜尋效率,本質上是因為暴力搜尋每輪只能排除一個選項,**而分治搜尋每輪可以排除一半選項**。 + +### 基於分治實現二分搜尋 + +在之前的章節中,二分搜尋是基於遞推(迭代)實現的。現在我們基於分治(遞迴)來實現它。 + +!!! question + + 給定一個長度為 $n$ 的有序陣列 `nums` ,其中所有元素都是唯一的,請查詢元素 `target` 。 + +從分治角度,我們將搜尋區間 $[i, j]$ 對應的子問題記為 $f(i, j)$ 。 + +以原問題 $f(0, n-1)$ 為起始點,透過以下步驟進行二分搜尋。 + +1. 計算搜尋區間 $[i, j]$ 的中點 $m$ ,根據它排除一半搜尋區間。 +2. 遞迴求解規模減小一半的子問題,可能為 $f(i, m-1)$ 或 $f(m+1, j)$ 。 +3. 迴圈第 `1.` 步和第 `2.` 步,直至找到 `target` 或區間為空時返回。 + +下圖展示了在陣列中二分搜尋元素 $6$ 的分治過程。 + +![二分搜尋的分治過程](binary_search_recur.assets/binary_search_recur.png) + +在實現程式碼中,我們宣告一個遞迴函式 `dfs()` 來求解問題 $f(i, j)$ : + +```src +[file]{binary_search_recur}-[class]{}-[func]{binary_search} +``` diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png new file mode 100644 index 0000000000..1e9e450b0d Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png new file mode 100644 index 0000000000..5b0bfce47e Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png new file mode 100644 index 0000000000..da91ab1a43 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png new file mode 100644 index 0000000000..45baa3434f Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png new file mode 100644 index 0000000000..bb4d4701f3 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png new file mode 100644 index 0000000000..59c77c25cc Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png new file mode 100644 index 0000000000..08f6b9e389 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png new file mode 100644 index 0000000000..de9dd0dcbf Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png new file mode 100644 index 0000000000..eb0f07c0fa Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png new file mode 100644 index 0000000000..230f6248b2 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png new file mode 100644 index 0000000000..d62db2631f Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png new file mode 100644 index 0000000000..1c31d7ad72 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png new file mode 100644 index 0000000000..db9bba0226 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.md new file mode 100644 index 0000000000..88917b9bd5 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -0,0 +1,99 @@ +# 構建二元樹問題 + +!!! question + + 給定一棵二元樹的前序走訪 `preorder` 和中序走訪 `inorder` ,請從中構建二元樹,返回二元樹的根節點。假設二元樹中沒有值重複的節點(如下圖所示)。 + +![構建二元樹的示例資料](build_binary_tree_problem.assets/build_tree_example.png) + +### 判斷是否為分治問題 + +原問題定義為從 `preorder` 和 `inorder` 構建二元樹,是一個典型的分治問題。 + +- **問題可以分解**:從分治的角度切入,我們可以將原問題劃分為兩個子問題:構建左子樹、構建右子樹,加上一步操作:初始化根節點。而對於每棵子樹(子問題),我們仍然可以複用以上劃分方法,將其劃分為更小的子樹(子問題),直至達到最小子問題(空子樹)時終止。 +- **子問題是獨立的**:左子樹和右子樹是相互獨立的,它們之間沒有交集。在構建左子樹時,我們只需關注中序走訪和前序走訪中與左子樹對應的部分。右子樹同理。 +- **子問題的解可以合併**:一旦得到了左子樹和右子樹(子問題的解),我們就可以將它們連結到根節點上,得到原問題的解。 + +### 如何劃分子樹 + +根據以上分析,這道題可以使用分治來求解,**但如何透過前序走訪 `preorder` 和中序走訪 `inorder` 來劃分左子樹和右子樹呢**? + +根據定義,`preorder` 和 `inorder` 都可以劃分為三個部分。 + +- 前序走訪:`[ 根節點 | 左子樹 | 右子樹 ]` ,例如上圖的樹對應 `[ 3 | 9 | 2 1 7 ]` 。 +- 中序走訪:`[ 左子樹 | 根節點 | 右子樹 ]` ,例如上圖的樹對應 `[ 9 | 3 | 1 2 7 ]` 。 + +以上圖資料為例,我們可以透過下圖所示的步驟得到劃分結果。 + +1. 前序走訪的首元素 3 是根節點的值。 +2. 查詢根節點 3 在 `inorder` 中的索引,利用該索引可將 `inorder` 劃分為 `[ 9 | 3 | 1 2 7 ]` 。 +3. 根據 `inorder` 的劃分結果,易得左子樹和右子樹的節點數量分別為 1 和 3 ,從而可將 `preorder` 劃分為 `[ 3 | 9 | 2 1 7 ]` 。 + +![在前序走訪和中序走訪中劃分子樹](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) + +### 基於變數描述子樹區間 + +根據以上劃分方法,**我們已經得到根節點、左子樹、右子樹在 `preorder` 和 `inorder` 中的索引區間**。而為了描述這些索引區間,我們需要藉助幾個指標變數。 + +- 將當前樹的根節點在 `preorder` 中的索引記為 $i$ 。 +- 將當前樹的根節點在 `inorder` 中的索引記為 $m$ 。 +- 將當前樹在 `inorder` 中的索引區間記為 $[l, r]$ 。 + +如下表所示,透過以上變數即可表示根節點在 `preorder` 中的索引,以及子樹在 `inorder` 中的索引區間。 + +

  根節點和子樹在前序走訪和中序走訪中的索引

+ +| | 根節點在 `preorder` 中的索引 | 子樹在 `inorder` 中的索引區間 | +| ------ | ---------------------------- | ----------------------------- | +| 當前樹 | $i$ | $[l, r]$ | +| 左子樹 | $i + 1$ | $[l, m-1]$ | +| 右子樹 | $i + 1 + (m - l)$ | $[m+1, r]$ | + +請注意,右子樹根節點索引中的 $(m-l)$ 的含義是“左子樹的節點數量”,建議結合下圖理解。 + +![根節點和左右子樹的索引區間表示](build_binary_tree_problem.assets/build_tree_division_pointers.png) + +### 程式碼實現 + +為了提升查詢 $m$ 的效率,我們藉助一個雜湊表 `hmap` 來儲存陣列 `inorder` 中元素到索引的對映: + +```src +[file]{build_tree}-[class]{}-[func]{build_tree} +``` + +下圖展示了構建二元樹的遞迴過程,各個節點是在向下“遞”的過程中建立的,而各條邊(引用)是在向上“迴”的過程中建立的。 + +=== "<1>" + ![構建二元樹的遞迴過程](build_binary_tree_problem.assets/built_tree_step1.png) + +=== "<2>" + ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) + +=== "<3>" + ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) + +=== "<4>" + ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) + +=== "<5>" + ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) + +=== "<6>" + ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) + +=== "<7>" + ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) + +=== "<8>" + ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) + +=== "<9>" + ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) + +每個遞迴函式內的前序走訪 `preorder` 和中序走訪 `inorder` 的劃分結果如下圖所示。 + +![每個遞迴函式中的劃分結果](build_binary_tree_problem.assets/built_tree_overall.png) + +設樹的節點數量為 $n$ ,初始化每一個節點(執行一個遞迴函式 `dfs()` )使用 $O(1)$ 時間。**因此總體時間複雜度為 $O(n)$** 。 + +雜湊表儲存 `inorder` 元素到索引的對映,空間複雜度為 $O(n)$ 。在最差情況下,即二元樹退化為鏈結串列時,遞迴深度達到 $n$ ,使用 $O(n)$ 的堆疊幀空間。**因此總體空間複雜度為 $O(n)$** 。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png new file mode 100644 index 0000000000..12452f66ca Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png new file mode 100644 index 0000000000..d82d0d95f6 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png new file mode 100644 index 0000000000..0f1a414491 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md new file mode 100644 index 0000000000..d6ce7d2746 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md @@ -0,0 +1,91 @@ +# 分治演算法 + +分治(divide and conquer),全稱分而治之,是一種非常重要且常見的演算法策略。分治通常基於遞迴實現,包括“分”和“治”兩個步驟。 + +1. **分(劃分階段)**:遞迴地將原問題分解為兩個或多個子問題,直至到達最小子問題時終止。 +2. **治(合併階段)**:從已知解的最小子問題開始,從底至頂地將子問題的解進行合併,從而構建出原問題的解。 + +如下圖所示,“合併排序”是分治策略的典型應用之一。 + +1. **分**:遞迴地將原陣列(原問題)劃分為兩個子陣列(子問題),直到子陣列只剩一個元素(最小子問題)。 +2. **治**:從底至頂地將有序的子陣列(子問題的解)進行合併,從而得到有序的原陣列(原問題的解)。 + +![合併排序的分治策略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) + +## 如何判斷分治問題 + +一個問題是否適合使用分治解決,通常可以參考以下幾個判斷依據。 + +1. **問題可以分解**:原問題可以分解成規模更小、類似的子問題,以及能夠以相同方式遞迴地進行劃分。 +2. **子問題是獨立的**:子問題之間沒有重疊,互不依賴,可以獨立解決。 +3. **子問題的解可以合併**:原問題的解透過合併子問題的解得來。 + +顯然,合併排序滿足以上三個判斷依據。 + +1. **問題可以分解**:遞迴地將陣列(原問題)劃分為兩個子陣列(子問題)。 +2. **子問題是獨立的**:每個子陣列都可以獨立地進行排序(子問題可以獨立進行求解)。 +3. **子問題的解可以合併**:兩個有序子陣列(子問題的解)可以合併為一個有序陣列(原問題的解)。 + +## 透過分治提升效率 + +**分治不僅可以有效地解決演算法問題,往往還可以提升演算法效率**。在排序演算法中,快速排序、合併排序、堆積排序相較於選擇、冒泡、插入排序更快,就是因為它們應用了分治策略。 + +那麼,我們不禁發問:**為什麼分治可以提升演算法效率,其底層邏輯是什麼**?換句話說,將大問題分解為多個子問題、解決子問題、將子問題的解合併為原問題的解,這幾步的效率為什麼比直接解決原問題的效率更高?這個問題可以從操作數量和平行計算兩方面來討論。 + +### 操作數量最佳化 + +以“泡沫排序”為例,其處理一個長度為 $n$ 的陣列需要 $O(n^2)$ 時間。假設我們按照下圖所示的方式,將陣列從中點處分為兩個子陣列,則劃分需要 $O(n)$ 時間,排序每個子陣列需要 $O((n / 2)^2)$ 時間,合併兩個子陣列需要 $O(n)$ 時間,總體時間複雜度為: + +$$ +O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) +$$ + +![劃分陣列前後的泡沫排序](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) + +接下來,我們計算以下不等式,其左邊和右邊分別為劃分前和劃分後的操作總數: + +$$ +\begin{aligned} +n^2 & > \frac{n^2}{2} + 2n \newline +n^2 - \frac{n^2}{2} - 2n & > 0 \newline +n(n - 4) & > 0 +\end{aligned} +$$ + +**這意味著當 $n > 4$ 時,劃分後的操作數量更少,排序效率應該更高**。請注意,劃分後的時間複雜度仍然是平方階 $O(n^2)$ ,只是複雜度中的常數項變小了。 + +進一步想,**如果我們把子陣列不斷地再從中點處劃分為兩個子陣列**,直至子陣列只剩一個元素時停止劃分呢?這種思路實際上就是“合併排序”,時間複雜度為 $O(n \log n)$ 。 + +再思考,**如果我們多設定幾個劃分點**,將原陣列平均劃分為 $k$ 個子陣列呢?這種情況與“桶排序”非常類似,它非常適合排序海量資料,理論上時間複雜度可以達到 $O(n + k)$ 。 + +### 平行計算最佳化 + +我們知道,分治生成的子問題是相互獨立的,**因此通常可以並行解決**。也就是說,分治不僅可以降低演算法的時間複雜度,**還有利於作業系統的並行最佳化**。 + +並行最佳化在多核或多處理器的環境中尤其有效,因為系統可以同時處理多個子問題,更加充分地利用計算資源,從而顯著減少總體的執行時間。 + +比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可將所有桶的排序任務分散到各個計算單元,完成後再合併結果。 + +![桶排序的平行計算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) + +## 分治常見應用 + +一方面,分治可以用來解決許多經典演算法問題。 + +- **尋找最近點對**:該演算法首先將點集分成兩部分,然後分別找出兩部分中的最近點對,最後找出跨越兩部分的最近點對。 +- **大整數乘法**:例如 Karatsuba 演算法,它將大整數乘法分解為幾個較小的整數的乘法和加法。 +- **矩陣乘法**:例如 Strassen 演算法,它將大矩陣乘法分解為多個小矩陣的乘法和加法。 +- **河內塔問題**:河內塔問題可以透過遞迴解決,這是典型的分治策略應用。 +- **求解逆序對**:在一個序列中,如果前面的數字大於後面的數字,那麼這兩個數字構成一個逆序對。求解逆序對問題可以利用分治的思想,藉助合併排序進行求解。 + +另一方面,分治在演算法和資料結構的設計中應用得非常廣泛。 + +- **二分搜尋**:二分搜尋是將有序陣列從中點索引處分為兩部分,然後根據目標值與中間元素值比較結果,決定排除哪一半區間,並在剩餘區間執行相同的二分操作。 +- **合併排序**:本節開頭已介紹,不再贅述。 +- **快速排序**:快速排序是選取一個基準值,然後把陣列分為兩個子陣列,一個子陣列的元素比基準值小,另一子陣列的元素比基準值大,再對這兩部分進行相同的劃分操作,直至子陣列只剩下一個元素。 +- **桶排序**:桶排序的基本思想是將資料分散到多個桶,然後對每個桶內的元素進行排序,最後將各個桶的元素依次取出,從而得到一個有序陣列。 +- **樹**:例如二元搜尋樹、AVL 樹、紅黑樹、B 樹、B+ 樹等,它們的查詢、插入和刪除等操作都可以視為分治策略的應用。 +- **堆積**:堆積是一種特殊的完全二元樹,其各種操作,如插入、刪除和堆積化,實際上都隱含了分治的思想。 +- **雜湊表**:雖然雜湊表並不直接應用分治,但某些雜湊衝突解決方案間接應用了分治策略,例如,鏈式位址中的長鏈結串列會被轉化為紅黑樹,以提升查詢效率。 + +可以看出,**分治是一種“潤物細無聲”的演算法思想**,隱含在各種演算法與資料結構之中。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png new file mode 100644 index 0000000000..a939dc66af Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png new file mode 100644 index 0000000000..07792388a8 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png new file mode 100644 index 0000000000..5c56a65748 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png new file mode 100644 index 0000000000..94659a290e Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png new file mode 100644 index 0000000000..8e3c1c42fd Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png new file mode 100644 index 0000000000..31ec6b8e61 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png new file mode 100644 index 0000000000..6770dcdcf3 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png new file mode 100644 index 0000000000..6f6114faf9 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png new file mode 100644 index 0000000000..226c867c33 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png new file mode 100644 index 0000000000..5555414c27 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png new file mode 100644 index 0000000000..eac277f7de Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png new file mode 100644 index 0000000000..7f20e7a6a5 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png new file mode 100644 index 0000000000..4955f42f75 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.md b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.md new file mode 100644 index 0000000000..32d94e2a39 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.md @@ -0,0 +1,97 @@ +# 河內塔問題 + +在合併排序和構建二元樹中,我們都是將原問題分解為兩個規模為原問題一半的子問題。然而對於河內塔問題,我們採用不同的分解策略。 + +!!! question + + 給定三根柱子,記為 `A`、`B` 和 `C` 。起始狀態下,柱子 `A` 上套著 $n$ 個圓盤,它們從上到下按照從小到大的順序排列。我們的任務是要把這 $n$ 個圓盤移到柱子 `C` 上,並保持它們的原有順序不變(如下圖所示)。在移動圓盤的過程中,需要遵守以下規則。 + + 1. 圓盤只能從一根柱子頂部拿出,從另一根柱子頂部放入。 + 2. 每次只能移動一個圓盤。 + 3. 小圓盤必須時刻位於大圓盤之上。 + +![河內塔問題示例](hanota_problem.assets/hanota_example.png) + +**我們將規模為 $i$ 的河內塔問題記作 $f(i)$** 。例如 $f(3)$ 代表將 $3$ 個圓盤從 `A` 移動至 `C` 的河內塔問題。 + +### 考慮基本情況 + +如下圖所示,對於問題 $f(1)$ ,即當只有一個圓盤時,我們將它直接從 `A` 移動至 `C` 即可。 + +=== "<1>" + ![規模為 1 的問題的解](hanota_problem.assets/hanota_f1_step1.png) + +=== "<2>" + ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) + +如下圖所示,對於問題 $f(2)$ ,即當有兩個圓盤時,**由於要時刻滿足小圓盤在大圓盤之上,因此需要藉助 `B` 來完成移動**。 + +1. 先將上面的小圓盤從 `A` 移至 `B` 。 +2. 再將大圓盤從 `A` 移至 `C` 。 +3. 最後將小圓盤從 `B` 移至 `C` 。 + +=== "<1>" + ![規模為 2 的問題的解](hanota_problem.assets/hanota_f2_step1.png) + +=== "<2>" + ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) + +=== "<3>" + ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) + +=== "<4>" + ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) + +解決問題 $f(2)$ 的過程可總結為:**將兩個圓盤藉助 `B` 從 `A` 移至 `C`** 。其中,`C` 稱為目標柱、`B` 稱為緩衝柱。 + +### 子問題分解 + +對於問題 $f(3)$ ,即當有三個圓盤時,情況變得稍微複雜了一些。 + +因為已知 $f(1)$ 和 $f(2)$ 的解,所以我們可從分治角度思考,**將 `A` 頂部的兩個圓盤看作一個整體**,執行下圖所示的步驟。這樣三個圓盤就被順利地從 `A` 移至 `C` 了。 + +1. 令 `B` 為目標柱、`C` 為緩衝柱,將兩個圓盤從 `A` 移至 `B` 。 +2. 將 `A` 中剩餘的一個圓盤從 `A` 直接移動至 `C` 。 +3. 令 `C` 為目標柱、`A` 為緩衝柱,將兩個圓盤從 `B` 移至 `C` 。 + +=== "<1>" + ![規模為 3 的問題的解](hanota_problem.assets/hanota_f3_step1.png) + +=== "<2>" + ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) + +=== "<3>" + ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) + +=== "<4>" + ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) + +從本質上看,**我們將問題 $f(3)$ 劃分為兩個子問題 $f(2)$ 和一個子問題 $f(1)$** 。按順序解決這三個子問題之後,原問題隨之得到解決。這說明子問題是獨立的,而且解可以合併。 + +至此,我們可總結出下圖所示的解決河內塔問題的分治策略:將原問題 $f(n)$ 劃分為兩個子問題 $f(n-1)$ 和一個子問題 $f(1)$ ,並按照以下順序解決這三個子問題。 + +1. 將 $n-1$ 個圓盤藉助 `C` 從 `A` 移至 `B` 。 +2. 將剩餘 $1$ 個圓盤從 `A` 直接移至 `C` 。 +3. 將 $n-1$ 個圓盤藉助 `A` 從 `B` 移至 `C` 。 + +對於這兩個子問題 $f(n-1)$ ,**可以透過相同的方式進行遞迴劃分**,直至達到最小子問題 $f(1)$ 。而 $f(1)$ 的解是已知的,只需一次移動操作即可。 + +![解決河內塔問題的分治策略](hanota_problem.assets/hanota_divide_and_conquer.png) + +### 程式碼實現 + +在程式碼中,我們宣告一個遞迴函式 `dfs(i, src, buf, tar)` ,它的作用是將柱 `src` 頂部的 $i$ 個圓盤藉助緩衝柱 `buf` 移動至目標柱 `tar` : + +```src +[file]{hanota}-[class]{}-[func]{solve_hanota} +``` + +如下圖所示,河內塔問題形成一棵高度為 $n$ 的遞迴樹,每個節點代表一個子問題,對應一個開啟的 `dfs()` 函式,**因此時間複雜度為 $O(2^n)$ ,空間複雜度為 $O(n)$** 。 + +![河內塔問題的遞迴樹](hanota_problem.assets/hanota_recursive_tree.png) + +!!! quote + + 河內塔問題源自一個古老的傳說。在古印度的一個寺廟裡,僧侶們有三根高大的鑽石柱子,以及 $64$ 個大小不一的金圓盤。僧侶們不斷地移動圓盤,他們相信在最後一個圓盤被正確放置的那一刻,這個世界就會結束。 + + 然而,即使僧侶們每秒鐘移動一次,總共需要大約 $2^{64} \approx 1.84×10^{19}$ 秒,合約 $5850$ 億年,遠遠超過了現在對宇宙年齡的估計。所以,倘若這個傳說是真的,我們應該不需要擔心世界末日的到來。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/index.md b/zh-hant/docs/chapter_divide_and_conquer/index.md new file mode 100644 index 0000000000..64b3009f22 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/index.md @@ -0,0 +1,9 @@ +# 分治 + +![分治](../assets/covers/chapter_divide_and_conquer.jpg) + +!!! abstract + + 難題被逐層拆解,每一次的拆解都使它變得更為簡單。 + + 分而治之揭示了一個重要的事實:從簡單做起,一切都不再複雜。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/summary.md b/zh-hant/docs/chapter_divide_and_conquer/summary.md new file mode 100644 index 0000000000..8d544ce44d --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/summary.md @@ -0,0 +1,11 @@ +# 小結 + +- 分治是一種常見的演算法設計策略,包括分(劃分)和治(合併)兩個階段,通常基於遞迴實現。 +- 判斷是否是分治演算法問題的依據包括:問題能否分解、子問題是否獨立、子問題能否合併。 +- 合併排序是分治策略的典型應用,其遞迴地將陣列劃分為等長的兩個子陣列,直到只剩一個元素時開始逐層合併,從而完成排序。 +- 引入分治策略往往可以提升演算法效率。一方面,分治策略減少了操作數量;另一方面,分治後有利於系統的並行最佳化。 +- 分治既可以解決許多演算法問題,也廣泛應用於資料結構與演算法設計中,處處可見其身影。 +- 相較於暴力搜尋,自適應搜尋效率更高。時間複雜度為 $O(\log n)$ 的搜尋演算法通常是基於分治策略實現的。 +- 二分搜尋是分治策略的另一個典型應用,它不包含將子問題的解進行合併的步驟。我們可以透過遞迴分治實現二分搜尋。 +- 在構建二元樹的問題中,構建樹(原問題)可以劃分為構建左子樹和右子樹(子問題),這可以透過劃分前序走訪和中序走訪的索引區間來實現。 +- 在河內塔問題中,一個規模為 $n$ 的問題可以劃分為兩個規模為 $n-1$ 的子問題和一個規模為 $1$ 的子問題。按順序解決這三個子問題後,原問題隨之得到解決。 diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png new file mode 100644 index 0000000000..10e3f57109 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png new file mode 100644 index 0000000000..6f0c6825d0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png new file mode 100644 index 0000000000..4eb5f0b436 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png new file mode 100644 index 0000000000..73c9f224ca Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.md b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.md new file mode 100644 index 0000000000..feda346f27 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.md @@ -0,0 +1,101 @@ +# 動態規劃問題特性 + +在上一節中,我們學習了動態規劃是如何透過子問題分解來求解原問題的。實際上,子問題分解是一種通用的演算法思路,在分治、動態規劃、回溯中的側重點不同。 + +- 分治演算法遞迴地將原問題劃分為多個相互獨立的子問題,直至最小子問題,並在回溯中合併子問題的解,最終得到原問題的解。 +- 動態規劃也對問題進行遞迴分解,但與分治演算法的主要區別是,動態規劃中的子問題是相互依賴的,在分解過程中會出現許多重疊子問題。 +- 回溯演算法在嘗試和回退中窮舉所有可能的解,並透過剪枝避免不必要的搜尋分支。原問題的解由一系列決策步驟構成,我們可以將每個決策步驟之前的子序列看作一個子問題。 + +實際上,動態規劃常用來求解最最佳化問題,它們不僅包含重疊子問題,還具有另外兩大特性:最優子結構、無後效性。 + +## 最優子結構 + +我們對爬樓梯問題稍作改動,使之更加適合展示最優子結構概念。 + +!!! question "爬樓梯最小代價" + + 給定一個樓梯,你每步可以上 $1$ 階或者 $2$ 階,每一階樓梯上都貼有一個非負整數,表示你在該臺階所需要付出的代價。給定一個非負整數陣列 $cost$ ,其中 $cost[i]$ 表示在第 $i$ 個臺階需要付出的代價,$cost[0]$ 為地面(起始點)。請計算最少需要付出多少代價才能到達頂部? + +如下圖所示,若第 $1$、$2$、$3$ 階的代價分別為 $1$、$10$、$1$ ,則從地面爬到第 $3$ 階的最小代價為 $2$ 。 + +![爬到第 3 階的最小代價](dp_problem_features.assets/min_cost_cs_example.png) + +設 $dp[i]$ 為爬到第 $i$ 階累計付出的代價,由於第 $i$ 階只可能從 $i - 1$ 階或 $i - 2$ 階走來,因此 $dp[i]$ 只可能等於 $dp[i - 1] + cost[i]$ 或 $dp[i - 2] + cost[i]$ 。為了儘可能減少代價,我們應該選擇兩者中較小的那一個: + +$$ +dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] +$$ + +這便可以引出最優子結構的含義:**原問題的最優解是從子問題的最優解構建得來的**。 + +本題顯然具有最優子結構:我們從兩個子問題最優解 $dp[i-1]$ 和 $dp[i-2]$ 中挑選出較優的那一個,並用它構建出原問題 $dp[i]$ 的最優解。 + +那麼,上一節的爬樓梯題目有沒有最優子結構呢?它的目標是求解方案數量,看似是一個計數問題,但如果換一種問法:“求解最大方案數量”。我們意外地發現,**雖然題目修改前後是等價的,但最優子結構浮現出來了**:第 $n$ 階最大方案數量等於第 $n-1$ 階和第 $n-2$ 階最大方案數量之和。所以說,最優子結構的解釋方式比較靈活,在不同問題中會有不同的含義。 + +根據狀態轉移方程,以及初始狀態 $dp[1] = cost[1]$ 和 $dp[2] = cost[2]$ ,我們就可以得到動態規劃程式碼: + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} +``` + +下圖展示了以上程式碼的動態規劃過程。 + +![爬樓梯最小代價的動態規劃過程](dp_problem_features.assets/min_cost_cs_dp.png) + +本題也可以進行空間最佳化,將一維壓縮至零維,使得空間複雜度從 $O(n)$ 降至 $O(1)$ : + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} +``` + +## 無後效性 + +無後效性是動態規劃能夠有效解決問題的重要特性之一,其定義為:**給定一個確定的狀態,它的未來發展只與當前狀態有關,而與過去經歷的所有狀態無關**。 + +以爬樓梯問題為例,給定狀態 $i$ ,它會發展出狀態 $i+1$ 和狀態 $i+2$ ,分別對應跳 $1$ 步和跳 $2$ 步。在做出這兩種選擇時,我們無須考慮狀態 $i$ 之前的狀態,它們對狀態 $i$ 的未來沒有影響。 + +然而,如果我們給爬樓梯問題新增一個約束,情況就不一樣了。 + +!!! question "帶約束爬樓梯" + + 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階,**但不能連續兩輪跳 $1$ 階**,請問有多少種方案可以爬到樓頂? + +如下圖所示,爬上第 $3$ 階僅剩 $2$ 種可行方案,其中連續三次跳 $1$ 階的方案不滿足約束條件,因此被捨棄。 + +![帶約束爬到第 3 階的方案數量](dp_problem_features.assets/climbing_stairs_constraint_example.png) + +在該問題中,如果上一輪是跳 $1$ 階上來的,那麼下一輪就必須跳 $2$ 階。這意味著,**下一步選擇不能由當前狀態(當前所在樓梯階數)獨立決定,還和前一個狀態(上一輪所在樓梯階數)有關**。 + +不難發現,此問題已不滿足無後效性,狀態轉移方程 $dp[i] = dp[i-1] + dp[i-2]$ 也失效了,因為 $dp[i-1]$ 代表本輪跳 $1$ 階,但其中包含了許多“上一輪是跳 $1$ 階上來的”方案,而為了滿足約束,我們就不能將 $dp[i-1]$ 直接計入 $dp[i]$ 中。 + +為此,我們需要擴展狀態定義:**狀態 $[i, j]$ 表示處在第 $i$ 階並且上一輪跳了 $j$ 階**,其中 $j \in \{1, 2\}$ 。此狀態定義有效地區分了上一輪跳了 $1$ 階還是 $2$ 階,我們可以據此判斷當前狀態是從何而來的。 + +- 當上一輪跳了 $1$ 階時,上上一輪只能選擇跳 $2$ 階,即 $dp[i, 1]$ 只能從 $dp[i-1, 2]$ 轉移過來。 +- 當上一輪跳了 $2$ 階時,上上一輪可選擇跳 $1$ 階或跳 $2$ 階,即 $dp[i, 2]$ 可以從 $dp[i-2, 1]$ 或 $dp[i-2, 2]$ 轉移過來。 + +如下圖所示,在該定義下,$dp[i, j]$ 表示狀態 $[i, j]$ 對應的方案數。此時狀態轉移方程為: + +$$ +\begin{cases} +dp[i, 1] = dp[i-1, 2] \\ +dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] +\end{cases} +$$ + +![考慮約束下的遞推關係](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) + +最終,返回 $dp[n, 1] + dp[n, 2]$ 即可,兩者之和代表爬到第 $n$ 階的方案總數: + +```src +[file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} +``` + +在上面的案例中,由於僅需多考慮前面一個狀態,因此我們仍然可以透過擴展狀態定義,使得問題重新滿足無後效性。然而,某些問題具有非常嚴重的“有後效性”。 + +!!! question "爬樓梯與障礙生成" + + 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階。**規定當爬到第 $i$ 階時,系統自動會在第 $2i$ 階上放上障礙物,之後所有輪都不允許跳到第 $2i$ 階上**。例如,前兩輪分別跳到了第 $2$、$3$ 階上,則之後就不能跳到第 $4$、$6$ 階上。請問有多少種方案可以爬到樓頂? + +在這個問題中,下次跳躍依賴過去所有的狀態,因為每一次跳躍都會在更高的階梯上設定障礙,並影響未來的跳躍。對於這類問題,動態規劃往往難以解決。 + +實際上,許多複雜的組合最佳化問題(例如旅行商問題)不滿足無後效性。對於這類問題,我們通常會選擇使用其他方法,例如啟發式搜尋、遺傳演算法、強化學習等,從而在有限時間內得到可用的區域性最優解。 diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png new file mode 100644 index 0000000000..f9288d066e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png new file mode 100644 index 0000000000..b1c27ede5f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png new file mode 100644 index 0000000000..27e581a2ce Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png new file mode 100644 index 0000000000..b6e8baf10d Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png new file mode 100644 index 0000000000..e8cc80a926 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png new file mode 100644 index 0000000000..7d9f2f2054 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png new file mode 100644 index 0000000000..503899880f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png new file mode 100644 index 0000000000..c774683468 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png new file mode 100644 index 0000000000..f7a016a126 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png new file mode 100644 index 0000000000..914eb37ec0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png new file mode 100644 index 0000000000..7dc5f0b6d4 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png new file mode 100644 index 0000000000..d0ae5cd09a Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png new file mode 100644 index 0000000000..1028b5da0f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png new file mode 100644 index 0000000000..58dd94c860 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png new file mode 100644 index 0000000000..1ffb42f8e7 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png new file mode 100644 index 0000000000..557d9a5595 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png new file mode 100644 index 0000000000..083a3e9ef6 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png new file mode 100644 index 0000000000..efec741982 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.md new file mode 100644 index 0000000000..777a8b7514 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -0,0 +1,183 @@ +# 動態規劃解題思路 + +上兩節介紹了動態規劃問題的主要特徵,接下來我們一起探究兩個更加實用的問題。 + +1. 如何判斷一個問題是不是動態規劃問題? +2. 求解動態規劃問題該從何處入手,完整步驟是什麼? + +## 問題判斷 + +總的來說,如果一個問題包含重疊子問題、最優子結構,並滿足無後效性,那麼它通常適合用動態規劃求解。然而,我們很難從問題描述中直接提取出這些特性。因此我們通常會放寬條件,**先觀察問題是否適合使用回溯(窮舉)解決**。 + +**適合用回溯解決的問題通常滿足“決策樹模型”**,這種問題可以使用樹形結構來描述,其中每一個節點代表一個決策,每一條路徑代表一個決策序列。 + +換句話說,如果問題包含明確的決策概念,並且解是透過一系列決策產生的,那麼它就滿足決策樹模型,通常可以使用回溯來解決。 + +在此基礎上,動態規劃問題還有一些判斷的“加分項”。 + +- 問題包含最大(小)或最多(少)等最最佳化描述。 +- 問題的狀態能夠使用一個串列、多維矩陣或樹來表示,並且一個狀態與其周圍的狀態存在遞推關係。 + +相應地,也存在一些“減分項”。 + +- 問題的目標是找出所有可能的解決方案,而不是找出最優解。 +- 問題描述中有明顯的排列組合的特徵,需要返回具體的多個方案。 + +如果一個問題滿足決策樹模型,並具有較為明顯的“加分項”,我們就可以假設它是一個動態規劃問題,並在求解過程中驗證它。 + +## 問題求解步驟 + +動態規劃的解題流程會因問題的性質和難度而有所不同,但通常遵循以下步驟:描述決策,定義狀態,建立 $dp$ 表,推導狀態轉移方程,確定邊界條件等。 + +為了更形象地展示解題步驟,我們使用一個經典問題“最小路徑和”來舉例。 + +!!! question + + 給定一個 $n \times m$ 的二維網格 `grid` ,網格中的每個單元格包含一個非負整數,表示該單元格的代價。機器人以左上角單元格為起始點,每次只能向下或者向右移動一步,直至到達右下角單元格。請返回從左上角到右下角的最小路徑和。 + +下圖展示了一個例子,給定網格的最小路徑和為 $13$ 。 + +![最小路徑和示例資料](dp_solution_pipeline.assets/min_path_sum_example.png) + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +本題的每一輪的決策就是從當前格子向下或向右走一步。設當前格子的行列索引為 $[i, j]$ ,則向下或向右走一步後,索引變為 $[i+1, j]$ 或 $[i, j+1]$ 。因此,狀態應包含行索引和列索引兩個變數,記為 $[i, j]$ 。 + +狀態 $[i, j]$ 對應的子問題為:從起始點 $[0, 0]$ 走到 $[i, j]$ 的最小路徑和,解記為 $dp[i, j]$ 。 + +至此,我們就得到了下圖所示的二維 $dp$ 矩陣,其尺寸與輸入網格 $grid$ 相同。 + +![狀態定義與 dp 表](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) + +!!! note + + 動態規劃和回溯過程可以描述為一個決策序列,而狀態由所有決策變數構成。它應當包含描述解題進度的所有變數,其包含了足夠的資訊,能夠用來推導出下一個狀態。 + + 每個狀態都對應一個子問題,我們會定義一個 $dp$ 表來儲存所有子問題的解,狀態的每個獨立變數都是 $dp$ 表的一個維度。從本質上看,$dp$ 表是狀態和子問題的解之間的對映。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +對於狀態 $[i, j]$ ,它只能從上邊格子 $[i-1, j]$ 和左邊格子 $[i, j-1]$ 轉移而來。因此最優子結構為:到達 $[i, j]$ 的最小路徑和由 $[i, j-1]$ 的最小路徑和與 $[i-1, j]$ 的最小路徑和中較小的那一個決定。 + +根據以上分析,可推出下圖所示的狀態轉移方程: + +$$ +dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] +$$ + +![最優子結構與狀態轉移方程](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) + +!!! note + + 根據定義好的 $dp$ 表,思考原問題和子問題的關係,找出透過子問題的最優解來構造原問題的最優解的方法,即最優子結構。 + + 一旦我們找到了最優子結構,就可以使用它來構建出狀態轉移方程。 + +**第三步:確定邊界條件和狀態轉移順序** + +在本題中,處在首行的狀態只能從其左邊的狀態得來,處在首列的狀態只能從其上邊的狀態得來,因此首行 $i = 0$ 和首列 $j = 0$ 是邊界條件。 + +如下圖所示,由於每個格子是由其左方格子和上方格子轉移而來,因此我們使用迴圈來走訪矩陣,外迴圈走訪各行,內迴圈走訪各列。 + +![邊界條件與狀態轉移順序](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) + +!!! note + + 邊界條件在動態規劃中用於初始化 $dp$ 表,在搜尋中用於剪枝。 + + 狀態轉移順序的核心是要保證在計算當前問題的解時,所有它依賴的更小子問題的解都已經被正確地計算出來。 + +根據以上分析,我們已經可以直接寫出動態規劃程式碼。然而子問題分解是一種從頂至底的思想,因此按照“暴力搜尋 $\rightarrow$ 記憶化搜尋 $\rightarrow$ 動態規劃”的順序實現更加符合思維習慣。 + +### 方法一:暴力搜尋 + +從狀態 $[i, j]$ 開始搜尋,不斷分解為更小的狀態 $[i-1, j]$ 和 $[i, j-1]$ ,遞迴函式包括以下要素。 + +- **遞迴參數**:狀態 $[i, j]$ 。 +- **返回值**:從 $[0, 0]$ 到 $[i, j]$ 的最小路徑和 $dp[i, j]$ 。 +- **終止條件**:當 $i = 0$ 且 $j = 0$ 時,返回代價 $grid[0, 0]$ 。 +- **剪枝**:當 $i < 0$ 時或 $j < 0$ 時索引越界,此時返回代價 $+\infty$ ,代表不可行。 + +實現程式碼如下: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} +``` + +下圖給出了以 $dp[2, 1]$ 為根節點的遞迴樹,其中包含一些重疊子問題,其數量會隨著網格 `grid` 的尺寸變大而急劇增多。 + +從本質上看,造成重疊子問題的原因為:**存在多條路徑可以從左上角到達某一單元格**。 + +![暴力搜尋遞迴樹](dp_solution_pipeline.assets/min_path_sum_dfs.png) + +每個狀態都有向下和向右兩種選擇,從左上角走到右下角總共需要 $m + n - 2$ 步,所以最差時間複雜度為 $O(2^{m + n})$ ,其中 $n$ 和 $m$ 分別為網格的行數和列數。請注意,這種計算方式未考慮臨近網格邊界的情況,當到達網路邊界時只剩下一種選擇,因此實際的路徑數量會少一些。 + +### 方法二:記憶化搜尋 + +我們引入一個和網格 `grid` 相同尺寸的記憶串列 `mem` ,用於記錄各個子問題的解,並將重疊子問題進行剪枝: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} +``` + +如下圖所示,在引入記憶化後,所有子問題的解只需計算一次,因此時間複雜度取決於狀態總數,即網格尺寸 $O(nm)$ 。 + +![記憶化搜尋遞迴樹](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) + +### 方法三:動態規劃 + +基於迭代實現動態規劃解法,程式碼如下所示: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} +``` + +下圖展示了最小路徑和的狀態轉移過程,其走訪了整個網格,**因此時間複雜度為 $O(nm)$** 。 + +陣列 `dp` 大小為 $n \times m$ ,**因此空間複雜度為 $O(nm)$** 。 + +=== "<1>" + ![最小路徑和的動態規劃過程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) + +=== "<2>" + ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) + +=== "<3>" + ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) + +=== "<4>" + ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) + +=== "<5>" + ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) + +=== "<6>" + ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) + +=== "<7>" + ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) + +=== "<8>" + ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) + +=== "<9>" + ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) + +=== "<10>" + ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) + +=== "<11>" + ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) + +=== "<12>" + ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) + +### 空間最佳化 + +由於每個格子只與其左邊和上邊的格子有關,因此我們可以只用一個單行陣列來實現 $dp$ 表。 + +請注意,因為陣列 `dp` 只能表示一行的狀態,所以我們無法提前初始化首列狀態,而是在走訪每行時更新它: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} +``` diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png new file mode 100644 index 0000000000..e189cae548 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png new file mode 100644 index 0000000000..8c4e7f640c Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png new file mode 100644 index 0000000000..31cb6b93ad Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png new file mode 100644 index 0000000000..3fa1113dfb Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png new file mode 100644 index 0000000000..e7eb675382 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png new file mode 100644 index 0000000000..5ae7bde84f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png new file mode 100644 index 0000000000..e19015e9fc Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png new file mode 100644 index 0000000000..6cada51c54 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png new file mode 100644 index 0000000000..40131d274d Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png new file mode 100644 index 0000000000..21e3eeac10 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png new file mode 100644 index 0000000000..0e63fd17f4 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png new file mode 100644 index 0000000000..ad34d1377a Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png new file mode 100644 index 0000000000..210941f582 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png new file mode 100644 index 0000000000..521dda5eb6 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png new file mode 100644 index 0000000000..1de27bbdaf Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png new file mode 100644 index 0000000000..37b5c04241 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png new file mode 100644 index 0000000000..4e5b9071da Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png new file mode 100644 index 0000000000..cb34b6a3db Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.md b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.md new file mode 100644 index 0000000000..d8d8e407ab --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -0,0 +1,129 @@ +# 編輯距離問題 + +編輯距離,也稱 Levenshtein 距離,指兩個字串之間互相轉換的最少修改次數,通常用於在資訊檢索和自然語言處理中度量兩個序列的相似度。 + +!!! question + + 輸入兩個字串 $s$ 和 $t$ ,返回將 $s$ 轉換為 $t$ 所需的最少編輯步數。 + + 你可以在一個字串中進行三種編輯操作:插入一個字元、刪除一個字元、將字元替換為任意一個字元。 + +如下圖所示,將 `kitten` 轉換為 `sitting` 需要編輯 3 步,包括 2 次替換操作與 1 次新增操作;將 `hello` 轉換為 `algo` 需要 3 步,包括 2 次替換操作和 1 次刪除操作。 + +![編輯距離的示例資料](edit_distance_problem.assets/edit_distance_example.png) + +**編輯距離問題可以很自然地用決策樹模型來解釋**。字串對應樹節點,一輪決策(一次編輯操作)對應樹的一條邊。 + +如下圖所示,在不限制操作的情況下,每個節點都可以派生出許多條邊,每條邊對應一種操作,這意味著從 `hello` 轉換到 `algo` 有許多種可能的路徑。 + +從決策樹的角度看,本題的目標是求解節點 `hello` 和節點 `algo` 之間的最短路徑。 + +![基於決策樹模型表示編輯距離問題](edit_distance_problem.assets/edit_distance_decision_tree.png) + +### 動態規劃思路 + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +每一輪的決策是對字串 $s$ 進行一次編輯操作。 + +我們希望在編輯操作的過程中,問題的規模逐漸縮小,這樣才能構建子問題。設字串 $s$ 和 $t$ 的長度分別為 $n$ 和 $m$ ,我們先考慮兩字串尾部的字元 $s[n-1]$ 和 $t[m-1]$ 。 + +- 若 $s[n-1]$ 和 $t[m-1]$ 相同,我們可以跳過它們,直接考慮 $s[n-2]$ 和 $t[m-2]$ 。 +- 若 $s[n-1]$ 和 $t[m-1]$ 不同,我們需要對 $s$ 進行一次編輯(插入、刪除、替換),使得兩字串尾部的字元相同,從而可以跳過它們,考慮規模更小的問題。 + +也就是說,我們在字串 $s$ 中進行的每一輪決策(編輯操作),都會使得 $s$ 和 $t$ 中剩餘的待匹配字元發生變化。因此,狀態為當前在 $s$ 和 $t$ 中考慮的第 $i$ 和第 $j$ 個字元,記為 $[i, j]$ 。 + +狀態 $[i, j]$ 對應的子問題:**將 $s$ 的前 $i$ 個字元更改為 $t$ 的前 $j$ 個字元所需的最少編輯步數**。 + +至此,得到一個尺寸為 $(i+1) \times (j+1)$ 的二維 $dp$ 表。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +考慮子問題 $dp[i, j]$ ,其對應的兩個字串的尾部字元為 $s[i-1]$ 和 $t[j-1]$ ,可根據不同編輯操作分為下圖所示的三種情況。 + +1. 在 $s[i-1]$ 之後新增 $t[j-1]$ ,則剩餘子問題 $dp[i, j-1]$ 。 +2. 刪除 $s[i-1]$ ,則剩餘子問題 $dp[i-1, j]$ 。 +3. 將 $s[i-1]$ 替換為 $t[j-1]$ ,則剩餘子問題 $dp[i-1, j-1]$ 。 + +![編輯距離的狀態轉移](edit_distance_problem.assets/edit_distance_state_transfer.png) + +根據以上分析,可得最優子結構:$dp[i, j]$ 的最少編輯步數等於 $dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ 三者中的最少編輯步數,再加上本次的編輯步數 $1$ 。對應的狀態轉移方程為: + +$$ +dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 +$$ + +請注意,**當 $s[i-1]$ 和 $t[j-1]$ 相同時,無須編輯當前字元**,這種情況下的狀態轉移方程為: + +$$ +dp[i, j] = dp[i-1, j-1] +$$ + +**第三步:確定邊界條件和狀態轉移順序** + +當兩字串都為空時,編輯步數為 $0$ ,即 $dp[0, 0] = 0$ 。當 $s$ 為空但 $t$ 不為空時,最少編輯步數等於 $t$ 的長度,即首行 $dp[0, j] = j$ 。當 $s$ 不為空但 $t$ 為空時,最少編輯步數等於 $s$ 的長度,即首列 $dp[i, 0] = i$ 。 + +觀察狀態轉移方程,解 $dp[i, j]$ 依賴左方、上方、左上方的解,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。 + +### 程式碼實現 + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp} +``` + +如下圖所示,編輯距離問題的狀態轉移過程與背包問題非常類似,都可以看作填寫一個二維網格的過程。 + +=== "<1>" + ![編輯距離的動態規劃過程](edit_distance_problem.assets/edit_distance_dp_step1.png) + +=== "<2>" + ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) + +=== "<3>" + ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) + +=== "<4>" + ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) + +=== "<5>" + ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) + +=== "<6>" + ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) + +=== "<7>" + ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) + +=== "<8>" + ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) + +=== "<9>" + ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) + +=== "<10>" + ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) + +=== "<11>" + ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) + +=== "<12>" + ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) + +=== "<13>" + ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) + +=== "<14>" + ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) + +=== "<15>" + ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) + +### 空間最佳化 + +由於 $dp[i,j]$ 是由上方 $dp[i-1, j]$、左方 $dp[i, j-1]$、左上方 $dp[i-1, j-1]$ 轉移而來的,而正序走訪會丟失左上方 $dp[i-1, j-1]$ ,倒序走訪無法提前構建 $dp[i, j-1]$ ,因此兩種走訪順序都不可取。 + +為此,我們可以使用一個變數 `leftup` 來暫存左上方的解 $dp[i-1, j-1]$ ,從而只需考慮左方和上方的解。此時的情況與完全背包問題相同,可使用正序走訪。程式碼如下所示: + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} +``` diff --git a/zh-hant/docs/chapter_dynamic_programming/index.md b/zh-hant/docs/chapter_dynamic_programming/index.md new file mode 100644 index 0000000000..3df762bd88 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/index.md @@ -0,0 +1,9 @@ +# 動態規劃 + +![動態規劃](../assets/covers/chapter_dynamic_programming.jpg) + +!!! abstract + + 小溪匯入河流,江河匯入大海。 + + 動態規劃將小問題的解彙集成大問題的答案,一步步引領我們走向解決問題的彼岸。 diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png new file mode 100644 index 0000000000..e474e18e71 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png new file mode 100644 index 0000000000..e0a42d33f3 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png new file mode 100644 index 0000000000..c28c296708 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png new file mode 100644 index 0000000000..dbeab88672 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png new file mode 100644 index 0000000000..1efe073e71 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md new file mode 100644 index 0000000000..8f4370f501 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -0,0 +1,110 @@ +# 初探動態規劃 + +動態規劃(dynamic programming)是一個重要的演算法範式,它將一個問題分解為一系列更小的子問題,並透過儲存子問題的解來避免重複計算,從而大幅提升時間效率。 + +在本節中,我們從一個經典例題入手,先給出它的暴力回溯解法,觀察其中包含的重疊子問題,再逐步導出更高效的動態規劃解法。 + +!!! question "爬樓梯" + + 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階,請問有多少種方案可以爬到樓頂? + +如下圖所示,對於一個 $3$ 階樓梯,共有 $3$ 種方案可以爬到樓頂。 + +![爬到第 3 階的方案數量](intro_to_dynamic_programming.assets/climbing_stairs_example.png) + +本題的目標是求解方案數量,**我們可以考慮透過回溯來窮舉所有可能性**。具體來說,將爬樓梯想象為一個多輪選擇的過程:從地面出發,每輪選擇上 $1$ 階或 $2$ 階,每當到達樓梯頂部時就將方案數量加 $1$ ,當越過樓梯頂部時就將其剪枝。程式碼如下所示: + +```src +[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} +``` + +## 方法一:暴力搜尋 + +回溯演算法通常並不顯式地對問題進行拆解,而是將求解問題看作一系列決策步驟,透過試探和剪枝,搜尋所有可能的解。 + +我們可以嘗試從問題分解的角度分析這道題。設爬到第 $i$ 階共有 $dp[i]$ 種方案,那麼 $dp[i]$ 就是原問題,其子問題包括: + +$$ +dp[i-1], dp[i-2], \dots, dp[2], dp[1] +$$ + +由於每輪只能上 $1$ 階或 $2$ 階,因此當我們站在第 $i$ 階樓梯上時,上一輪只可能站在第 $i - 1$ 階或第 $i - 2$ 階上。換句話說,我們只能從第 $i -1$ 階或第 $i - 2$ 階邁向第 $i$ 階。 + +由此便可得出一個重要推論:**爬到第 $i - 1$ 階的方案數加上爬到第 $i - 2$ 階的方案數就等於爬到第 $i$ 階的方案數**。公式如下: + +$$ +dp[i] = dp[i-1] + dp[i-2] +$$ + +這意味著在爬樓梯問題中,各個子問題之間存在遞推關係,**原問題的解可以由子問題的解構建得來**。下圖展示了該遞推關係。 + +![方案數量遞推關係](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) + +我們可以根據遞推公式得到暴力搜尋解法。以 $dp[n]$ 為起始點,**遞迴地將一個較大問題拆解為兩個較小問題的和**,直至到達最小子問題 $dp[1]$ 和 $dp[2]$ 時返回。其中,最小子問題的解是已知的,即 $dp[1] = 1$、$dp[2] = 2$ ,表示爬到第 $1$、$2$ 階分別有 $1$、$2$ 種方案。 + +觀察以下程式碼,它和標準回溯程式碼都屬於深度優先搜尋,但更加簡潔: + +```src +[file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} +``` + +下圖展示了暴力搜尋形成的遞迴樹。對於問題 $dp[n]$ ,其遞迴樹的深度為 $n$ ,時間複雜度為 $O(2^n)$ 。指數階屬於爆炸式增長,如果我們輸入一個比較大的 $n$ ,則會陷入漫長的等待之中。 + +![爬樓梯對應遞迴樹](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) + +觀察上圖,**指數階的時間複雜度是“重疊子問題”導致的**。例如 $dp[9]$ 被分解為 $dp[8]$ 和 $dp[7]$ ,$dp[8]$ 被分解為 $dp[7]$ 和 $dp[6]$ ,兩者都包含子問題 $dp[7]$ 。 + +以此類推,子問題中包含更小的重疊子問題,子子孫孫無窮盡也。絕大部分計算資源都浪費在這些重疊的子問題上。 + +## 方法二:記憶化搜尋 + +為了提升演算法效率,**我們希望所有的重疊子問題都只被計算一次**。為此,我們宣告一個陣列 `mem` 來記錄每個子問題的解,並在搜尋過程中將重疊子問題剪枝。 + +1. 當首次計算 $dp[i]$ 時,我們將其記錄至 `mem[i]` ,以便之後使用。 +2. 當再次需要計算 $dp[i]$ 時,我們便可直接從 `mem[i]` 中獲取結果,從而避免重複計算該子問題。 + +程式碼如下所示: + +```src +[file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} +``` + +觀察下圖,**經過記憶化處理後,所有重疊子問題都只需計算一次,時間複雜度最佳化至 $O(n)$** ,這是一個巨大的飛躍。 + +![記憶化搜尋對應遞迴樹](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) + +## 方法三:動態規劃 + +**記憶化搜尋是一種“從頂至底”的方法**:我們從原問題(根節點)開始,遞迴地將較大子問題分解為較小子問題,直至解已知的最小子問題(葉節點)。之後,透過回溯逐層收集子問題的解,構建出原問題的解。 + +與之相反,**動態規劃是一種“從底至頂”的方法**:從最小子問題的解開始,迭代地構建更大子問題的解,直至得到原問題的解。 + +由於動態規劃不包含回溯過程,因此只需使用迴圈迭代實現,無須使用遞迴。在以下程式碼中,我們初始化一個陣列 `dp` 來儲存子問題的解,它起到了與記憶化搜尋中陣列 `mem` 相同的記錄作用: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} +``` + +下圖模擬了以上程式碼的執行過程。 + +![爬樓梯的動態規劃過程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) + +與回溯演算法一樣,動態規劃也使用“狀態”概念來表示問題求解的特定階段,每個狀態都對應一個子問題以及相應的區域性最優解。例如,爬樓梯問題的狀態定義為當前所在樓梯階數 $i$ 。 + +根據以上內容,我們可以總結出動態規劃的常用術語。 + +- 將陣列 `dp` 稱為 dp 表,$dp[i]$ 表示狀態 $i$ 對應子問題的解。 +- 將最小子問題對應的狀態(第 $1$ 階和第 $2$ 階樓梯)稱為初始狀態。 +- 將遞推公式 $dp[i] = dp[i-1] + dp[i-2]$ 稱為狀態轉移方程。 + +## 空間最佳化 + +細心的讀者可能發現了,**由於 $dp[i]$ 只與 $dp[i-1]$ 和 $dp[i-2]$ 有關,因此我們無須使用一個陣列 `dp` 來儲存所有子問題的解**,而只需兩個變數滾動前進即可。程式碼如下所示: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} +``` + +觀察以上程式碼,由於省去了陣列 `dp` 佔用的空間,因此空間複雜度從 $O(n)$ 降至 $O(1)$ 。 + +在動態規劃問題中,當前狀態往往僅與前面有限個狀態有關,這時我們可以只保留必要的狀態,透過“降維”來節省記憶體空間。**這種空間最佳化技巧被稱為“滾動變數”或“滾動陣列”**。 diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png new file mode 100644 index 0000000000..76f5af03d1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png new file mode 100644 index 0000000000..581f603232 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png new file mode 100644 index 0000000000..a762f516ae Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png new file mode 100644 index 0000000000..ad1a3693f2 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png new file mode 100644 index 0000000000..964c636afc Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png new file mode 100644 index 0000000000..c355a40f68 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png new file mode 100644 index 0000000000..9f538bd01f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png new file mode 100644 index 0000000000..50d1cd839c Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png new file mode 100644 index 0000000000..79ed911b63 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png new file mode 100644 index 0000000000..db6030a148 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png new file mode 100644 index 0000000000..f10490b9e2 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png new file mode 100644 index 0000000000..3f232b8e3c Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png new file mode 100644 index 0000000000..47631d1715 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png new file mode 100644 index 0000000000..625c819514 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png new file mode 100644 index 0000000000..dc1f4e608e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png new file mode 100644 index 0000000000..0bf06fa9ae Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png new file mode 100644 index 0000000000..65d0eac388 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png new file mode 100644 index 0000000000..c7c9bc95f7 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png new file mode 100644 index 0000000000..e357f84537 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png new file mode 100644 index 0000000000..ec085886e9 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png new file mode 100644 index 0000000000..18986975c0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png new file mode 100644 index 0000000000..ef8af28432 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png new file mode 100644 index 0000000000..6cb1f7c6b1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.md b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.md new file mode 100644 index 0000000000..bb675fac1d --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.md @@ -0,0 +1,168 @@ +# 0-1 背包問題 + +背包問題是一個非常好的動態規劃入門題目,是動態規劃中最常見的問題形式。其具有很多變種,例如 0-1 背包問題、完全背包問題、多重背包問題等。 + +在本節中,我們先來求解最常見的 0-1 背包問題。 + +!!! question + + 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。每個物品只能選擇一次,問在限定背包容量下能放入物品的最大價值。 + +觀察下圖,由於物品編號 $i$ 從 $1$ 開始計數,陣列索引從 $0$ 開始計數,因此物品 $i$ 對應重量 $wgt[i-1]$ 和價值 $val[i-1]$ 。 + +![0-1 背包的示例資料](knapsack_problem.assets/knapsack_example.png) + +我們可以將 0-1 背包問題看作一個由 $n$ 輪決策組成的過程,對於每個物體都有不放入和放入兩種決策,因此該問題滿足決策樹模型。 + +該問題的目標是求解“在限定背包容量下能放入物品的最大價值”,因此較大機率是一個動態規劃問題。 + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +對於每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號 $i$ 和背包容量 $c$ ,記為 $[i, c]$ 。 + +狀態 $[i, c]$ 對應的子問題為:**前 $i$ 個物品在容量為 $c$ 的背包中的最大價值**,記為 $dp[i, c]$ 。 + +待求解的是 $dp[n, cap]$ ,因此需要一個尺寸為 $(n+1) \times (cap+1)$ 的二維 $dp$ 表。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +當我們做出物品 $i$ 的決策後,剩餘的是前 $i-1$ 個物品決策的子問題,可分為以下兩種情況。 + +- **不放入物品 $i$** :背包容量不變,狀態變化為 $[i-1, c]$ 。 +- **放入物品 $i$** :背包容量減少 $wgt[i-1]$ ,價值增加 $val[i-1]$ ,狀態變化為 $[i-1, c-wgt[i-1]]$ 。 + +上述分析向我們揭示了本題的最優子結構:**最大價值 $dp[i, c]$ 等於不放入物品 $i$ 和放入物品 $i$ 兩種方案中價值更大的那一個**。由此可推導出狀態轉移方程: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) +$$ + +需要注意的是,若當前物品重量 $wgt[i - 1]$ 超出剩餘背包容量 $c$ ,則只能選擇不放入背包。 + +**第三步:確定邊界條件和狀態轉移順序** + +當無物品或背包容量為 $0$ 時最大價值為 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等於 $0$ 。 + +當前狀態 $[i, c]$ 從上方的狀態 $[i-1, c]$ 和左上方的狀態 $[i-1, c-wgt[i-1]]$ 轉移而來,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。 + +根據以上分析,我們接下來按順序實現暴力搜尋、記憶化搜尋、動態規劃解法。 + +### 方法一:暴力搜尋 + +搜尋程式碼包含以下要素。 + +- **遞迴參數**:狀態 $[i, c]$ 。 +- **返回值**:子問題的解 $dp[i, c]$ 。 +- **終止條件**:當物品編號越界 $i = 0$ 或背包剩餘容量為 $0$ 時,終止遞迴並返回價值 $0$ 。 +- **剪枝**:若當前物品重量超出背包剩餘容量,則只能選擇不放入背包。 + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs} +``` + +如下圖所示,由於每個物品都會產生不選和選兩條搜尋分支,因此時間複雜度為 $O(2^n)$ 。 + +觀察遞迴樹,容易發現其中存在重疊子問題,例如 $dp[1, 10]$ 等。而當物品較多、背包容量較大,尤其是相同重量的物品較多時,重疊子問題的數量將會大幅增多。 + +![0-1 背包問題的暴力搜尋遞迴樹](knapsack_problem.assets/knapsack_dfs.png) + +### 方法二:記憶化搜尋 + +為了保證重疊子問題只被計算一次,我們藉助記憶串列 `mem` 來記錄子問題的解,其中 `mem[i][c]` 對應 $dp[i, c]$ 。 + +引入記憶化之後,**時間複雜度取決於子問題數量**,也就是 $O(n \times cap)$ 。實現程式碼如下: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} +``` + +下圖展示了在記憶化搜尋中被剪掉的搜尋分支。 + +![0-1 背包問題的記憶化搜尋遞迴樹](knapsack_problem.assets/knapsack_dfs_mem.png) + +### 方法三:動態規劃 + +動態規劃實質上就是在狀態轉移中填充 $dp$ 表的過程,程式碼如下所示: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp} +``` + +如下圖所示,時間複雜度和空間複雜度都由陣列 `dp` 大小決定,即 $O(n \times cap)$ 。 + +=== "<1>" + ![0-1 背包問題的動態規劃過程](knapsack_problem.assets/knapsack_dp_step1.png) + +=== "<2>" + ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) + +=== "<3>" + ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) + +=== "<4>" + ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) + +=== "<5>" + ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) + +=== "<6>" + ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) + +=== "<7>" + ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) + +=== "<8>" + ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) + +=== "<9>" + ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) + +=== "<10>" + ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) + +=== "<11>" + ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) + +=== "<12>" + ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) + +=== "<13>" + ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) + +=== "<14>" + ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) + +### 空間最佳化 + +由於每個狀態都只與其上一行的狀態有關,因此我們可以使用兩個陣列滾動前進,將空間複雜度從 $O(n^2)$ 降至 $O(n)$ 。 + +進一步思考,我們能否僅用一個陣列實現空間最佳化呢?觀察可知,每個狀態都是由正上方或左上方的格子轉移過來的。假設只有一個陣列,當開始走訪第 $i$ 行時,該陣列儲存的仍然是第 $i-1$ 行的狀態。 + +- 如果採取正序走訪,那麼走訪到 $dp[i, j]$ 時,左上方 $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ 值可能已經被覆蓋,此時就無法得到正確的狀態轉移結果。 +- 如果採取倒序走訪,則不會發生覆蓋問題,狀態轉移可以正確進行。 + +下圖展示了在單個陣列下從第 $i = 1$ 行轉換至第 $i = 2$ 行的過程。請思考正序走訪和倒序走訪的區別。 + +=== "<1>" + ![0-1 背包的空間最佳化後的動態規劃過程](knapsack_problem.assets/knapsack_dp_comp_step1.png) + +=== "<2>" + ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) + +=== "<3>" + ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) + +=== "<4>" + ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) + +=== "<5>" + ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) + +=== "<6>" + ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) + +在程式碼實現中,我們僅需將陣列 `dp` 的第一維 $i$ 直接刪除,並且把內迴圈更改為倒序走訪即可: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} +``` diff --git a/zh-hant/docs/chapter_dynamic_programming/summary.md b/zh-hant/docs/chapter_dynamic_programming/summary.md new file mode 100644 index 0000000000..582768c2a2 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/summary.md @@ -0,0 +1,23 @@ +# 小結 + +- 動態規劃對問題進行分解,並透過儲存子問題的解來規避重複計算,提高計算效率。 +- 不考慮時間的前提下,所有動態規劃問題都可以用回溯(暴力搜尋)進行求解,但遞迴樹中存在大量的重疊子問題,效率極低。透過引入記憶化串列,可以儲存所有計算過的子問題的解,從而保證重疊子問題只被計算一次。 +- 記憶化搜尋是一種從頂至底的遞迴式解法,而與之對應的動態規劃是一種從底至頂的遞推式解法,其如同“填寫表格”一樣。由於當前狀態僅依賴某些區域性狀態,因此我們可以消除 $dp$ 表的一個維度,從而降低空間複雜度。 +- 子問題分解是一種通用的演算法思路,在分治、動態規劃、回溯中具有不同的性質。 +- 動態規劃問題有三大特性:重疊子問題、最優子結構、無後效性。 +- 如果原問題的最優解可以從子問題的最優解構建得來,則它就具有最優子結構。 +- 無後效性指對於一個狀態,其未來發展只與該狀態有關,而與過去經歷的所有狀態無關。許多組合最佳化問題不具有無後效性,無法使用動態規劃快速求解。 + +**背包問題** + +- 背包問題是最典型的動態規劃問題之一,具有 0-1 背包、完全背包、多重背包等變種。 +- 0-1 背包的狀態定義為前 $i$ 個物品在容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。 +- 完全背包問題的每種物品的選取數量無限制,因此選擇放入物品的狀態轉移與 0-1 背包問題不同。由於狀態依賴正上方和正左方的狀態,因此在空間最佳化中應當正序走訪。 +- 零錢兌換問題是完全背包問題的一個變種。它從求“最大”價值變為求“最小”硬幣數量,因此狀態轉移方程中的 $\max()$ 應改為 $\min()$ 。從追求“不超過”背包容量到追求“恰好”湊出目標金額,因此使用 $amt + 1$ 來表示“無法湊出目標金額”的無效解。 +- 零錢兌換問題 II 從求“最少硬幣數量”改為求“硬幣組合數量”,狀態轉移方程相應地從 $\min()$ 改為求和運算子。 + +**編輯距離問題** + +- 編輯距離(Levenshtein 距離)用於衡量兩個字串之間的相似度,其定義為從一個字串到另一個字串的最少編輯步數,編輯操作包括新增、刪除、替換。 +- 編輯距離問題的狀態定義為將 $s$ 的前 $i$ 個字元更改為 $t$ 的前 $j$ 個字元所需的最少編輯步數。當 $s[i] \ne t[j]$ 時,具有三種決策:新增、刪除、替換,它們都有相應的剩餘子問題。據此便可以找出最優子結構與構建狀態轉移方程。而當 $s[i] = t[j]$ 時,無須編輯當前字元。 +- 在編輯距離中,狀態依賴其正上方、正左方、左上方的狀態,因此空間最佳化後正序或倒序走訪都無法正確地進行狀態轉移。為此,我們利用一個變數暫存左上方狀態,從而轉化到與完全背包問題等價的情況,可以在空間最佳化後進行正序走訪。 diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png new file mode 100644 index 0000000000..e37343d3c0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png new file mode 100644 index 0000000000..88709590b1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png new file mode 100644 index 0000000000..d9e769cdb2 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png new file mode 100644 index 0000000000..fd99be484e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png new file mode 100644 index 0000000000..4bb8520221 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png new file mode 100644 index 0000000000..f9760edcce Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png new file mode 100644 index 0000000000..6927b05591 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png new file mode 100644 index 0000000000..11b9e05e19 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png new file mode 100644 index 0000000000..0b270e3a9e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png new file mode 100644 index 0000000000..a4a16579cc Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png new file mode 100644 index 0000000000..dbef584fd0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png new file mode 100644 index 0000000000..b0dd79fd6f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png new file mode 100644 index 0000000000..68a96cd1d8 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png new file mode 100644 index 0000000000..38f81b8152 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png new file mode 100644 index 0000000000..f7980b1bce Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png new file mode 100644 index 0000000000..a6a7932017 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png new file mode 100644 index 0000000000..4256b9158b Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png new file mode 100644 index 0000000000..5b02470772 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png new file mode 100644 index 0000000000..1c85ac5e55 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png new file mode 100644 index 0000000000..dba8427fc7 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png new file mode 100644 index 0000000000..87b19cbf8f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png new file mode 100644 index 0000000000..93ee151246 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png new file mode 100644 index 0000000000..dc19cd8c1e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png new file mode 100644 index 0000000000..3eec7bcbfe Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md new file mode 100644 index 0000000000..490facc15a --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -0,0 +1,207 @@ +# 完全背包問題 + +在本節中,我們先求解另一個常見的背包問題:完全背包,再瞭解它的一種特例:零錢兌換。 + +## 完全背包問題 + +!!! question + + 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。**每個物品可以重複選取**,問在限定背包容量下能放入物品的最大價值。示例如下圖所示。 + +![完全背包問題的示例資料](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) + +### 動態規劃思路 + +完全背包問題和 0-1 背包問題非常相似,**區別僅在於不限制物品的選擇次數**。 + +- 在 0-1 背包問題中,每種物品只有一個,因此將物品 $i$ 放入背包後,只能從前 $i-1$ 個物品中選擇。 +- 在完全背包問題中,每種物品的數量是無限的,因此將物品 $i$ 放入背包後,**仍可以從前 $i$ 個物品中選擇**。 + +在完全背包問題的規定下,狀態 $[i, c]$ 的變化分為兩種情況。 + +- **不放入物品 $i$** :與 0-1 背包問題相同,轉移至 $[i-1, c]$ 。 +- **放入物品 $i$** :與 0-1 背包問題不同,轉移至 $[i, c-wgt[i-1]]$ 。 + +從而狀態轉移方程變為: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) +$$ + +### 程式碼實現 + +對比兩道題目的程式碼,狀態轉移中有一處從 $i-1$ 變為 $i$ ,其餘完全一致: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} +``` + +### 空間最佳化 + +由於當前狀態是從左邊和上邊的狀態轉移而來的,**因此空間最佳化後應該對 $dp$ 表中的每一行進行正序走訪**。 + +這個走訪順序與 0-1 背包正好相反。請藉助下圖來理解兩者的區別。 + +=== "<1>" + ![完全背包問題在空間最佳化後的動態規劃過程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) + +=== "<2>" + ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) + +=== "<3>" + ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) + +=== "<4>" + ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) + +=== "<5>" + ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) + +=== "<6>" + ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) + +程式碼實現比較簡單,僅需將陣列 `dp` 的第一維刪除: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} +``` + +## 零錢兌換問題 + +背包問題是一大類動態規劃問題的代表,其擁有很多變種,例如零錢兌換問題。 + +!!! question + + 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,**每種硬幣可以重複選取**,問能夠湊出目標金額的最少硬幣數量。如果無法湊出目標金額,則返回 $-1$ 。示例如下圖所示。 + +![零錢兌換問題的示例資料](unbounded_knapsack_problem.assets/coin_change_example.png) + +### 動態規劃思路 + +**零錢兌換可以看作完全背包問題的一種特殊情況**,兩者具有以下關聯與不同點。 + +- 兩道題可以相互轉換,“物品”對應“硬幣”、“物品重量”對應“硬幣面值”、“背包容量”對應“目標金額”。 +- 最佳化目標相反,完全背包問題是要最大化物品價值,零錢兌換問題是要最小化硬幣數量。 +- 完全背包問題是求“不超過”背包容量下的解,零錢兌換是求“恰好”湊到目標金額的解。 + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +狀態 $[i, a]$ 對應的子問題為:**前 $i$ 種硬幣能夠湊出金額 $a$ 的最少硬幣數量**,記為 $dp[i, a]$ 。 + +二維 $dp$ 表的尺寸為 $(n+1) \times (amt+1)$ 。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +本題與完全背包問題的狀態轉移方程存在以下兩點差異。 + +- 本題要求最小值,因此需將運算子 $\max()$ 更改為 $\min()$ 。 +- 最佳化主體是硬幣數量而非商品價值,因此在選中硬幣時執行 $+1$ 即可。 + +$$ +dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) +$$ + +**第三步:確定邊界條件和狀態轉移順序** + +當目標金額為 $0$ 時,湊出它的最少硬幣數量為 $0$ ,即首列所有 $dp[i, 0]$ 都等於 $0$ 。 + +當無硬幣時,**無法湊出任意 $> 0$ 的目標金額**,即是無效解。為使狀態轉移方程中的 $\min()$ 函式能夠識別並過濾無效解,我們考慮使用 $+ \infty$ 來表示它們,即令首行所有 $dp[0, a]$ 都等於 $+ \infty$ 。 + +### 程式碼實現 + +大多數程式語言並未提供 $+ \infty$ 變數,只能使用整型 `int` 的最大值來代替。而這又會導致大數越界:狀態轉移方程中的 $+ 1$ 操作可能發生溢位。 + +為此,我們採用數字 $amt + 1$ 來表示無效解,因為湊出 $amt$ 的硬幣數量最多為 $amt$ 。最後返回前,判斷 $dp[n, amt]$ 是否等於 $amt + 1$ ,若是則返回 $-1$ ,代表無法湊出目標金額。程式碼如下所示: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp} +``` + +下圖展示了零錢兌換的動態規劃過程,和完全背包問題非常相似。 + +=== "<1>" + ![零錢兌換問題的動態規劃過程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) + +=== "<2>" + ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) + +=== "<3>" + ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) + +=== "<4>" + ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) + +=== "<5>" + ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) + +=== "<6>" + ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) + +=== "<7>" + ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) + +=== "<8>" + ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) + +=== "<9>" + ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) + +=== "<10>" + ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) + +=== "<11>" + ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) + +=== "<12>" + ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) + +=== "<13>" + ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) + +=== "<14>" + ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) + +=== "<15>" + ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) + +### 空間最佳化 + +零錢兌換的空間最佳化的處理方式和完全背包問題一致: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} +``` + +## 零錢兌換問題 II + +!!! question + + 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,每種硬幣可以重複選取,**問湊出目標金額的硬幣組合數量**。示例如下圖所示。 + +![零錢兌換問題 II 的示例資料](unbounded_knapsack_problem.assets/coin_change_ii_example.png) + +### 動態規劃思路 + +相比於上一題,本題目標是求組合數量,因此子問題變為:**前 $i$ 種硬幣能夠湊出金額 $a$ 的組合數量**。而 $dp$ 表仍然是尺寸為 $(n+1) \times (amt + 1)$ 的二維矩陣。 + +當前狀態的組合數量等於不選當前硬幣與選當前硬幣這兩種決策的組合數量之和。狀態轉移方程為: + +$$ +dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] +$$ + +當目標金額為 $0$ 時,無須選擇任何硬幣即可湊出目標金額,因此應將首列所有 $dp[i, 0]$ 都初始化為 $1$ 。當無硬幣時,無法湊出任何 $>0$ 的目標金額,因此首行所有 $dp[0, a]$ 都等於 $0$ 。 + +### 程式碼實現 + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} +``` + +### 空間最佳化 + +空間最佳化處理方式相同,刪除硬幣維度即可: + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} +``` diff --git a/zh-hant/docs/chapter_graph/graph.assets/adjacency_list.png b/zh-hant/docs/chapter_graph/graph.assets/adjacency_list.png new file mode 100644 index 0000000000..812abc68e4 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/adjacency_list.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/adjacency_matrix.png b/zh-hant/docs/chapter_graph/graph.assets/adjacency_matrix.png new file mode 100644 index 0000000000..3ac2436171 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/adjacency_matrix.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/connected_graph.png b/zh-hant/docs/chapter_graph/graph.assets/connected_graph.png new file mode 100644 index 0000000000..312a2b4017 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/connected_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/directed_graph.png b/zh-hant/docs/chapter_graph/graph.assets/directed_graph.png new file mode 100644 index 0000000000..93bf23fa75 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/directed_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png b/zh-hant/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png new file mode 100644 index 0000000000..ab99b4926b Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/weighted_graph.png b/zh-hant/docs/chapter_graph/graph.assets/weighted_graph.png new file mode 100644 index 0000000000..9ebf859571 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/weighted_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.md b/zh-hant/docs/chapter_graph/graph.md new file mode 100644 index 0000000000..61a33c5973 --- /dev/null +++ b/zh-hant/docs/chapter_graph/graph.md @@ -0,0 +1,83 @@ +# 圖 + +圖(graph)是一種非線性資料結構,由頂點(vertex)邊(edge)組成。我們可以將圖 $G$ 抽象地表示為一組頂點 $V$ 和一組邊 $E$ 的集合。以下示例展示了一個包含 5 個頂點和 7 條邊的圖。 + +$$ +\begin{aligned} +V & = \{ 1, 2, 3, 4, 5 \} \newline +E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline +G & = \{ V, E \} \newline +\end{aligned} +$$ + +如果將頂點看作節點,將邊看作連線各個節點的引用(指標),我們就可以將圖看作一種從鏈結串列拓展而來的資料結構。如下圖所示,**相較於線性關係(鏈結串列)和分治關係(樹),網路關係(圖)的自由度更高**,因而更為複雜。 + +![鏈結串列、樹、圖之間的關係](graph.assets/linkedlist_tree_graph.png) + +## 圖的常見型別與術語 + +根據邊是否具有方向,可分為無向圖(undirected graph)有向圖(directed graph),如下圖所示。 + +- 在無向圖中,邊表示兩頂點之間的“雙向”連線關係,例如微信或 QQ 中的“好友關係”。 +- 在有向圖中,邊具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 兩個方向的邊是相互獨立的,例如微博或抖音上的“關注”與“被關注”關係。 + +![有向圖與無向圖](graph.assets/directed_graph.png) + +根據所有頂點是否連通,可分為連通圖(connected graph)非連通圖(disconnected graph),如下圖所示。 + +- 對於連通圖,從某個頂點出發,可以到達其餘任意頂點。 +- 對於非連通圖,從某個頂點出發,至少有一個頂點無法到達。 + +![連通圖與非連通圖](graph.assets/connected_graph.png) + +我們還可以為邊新增“權重”變數,從而得到如下圖所示的有權圖(weighted graph)。例如在《王者榮耀》等手遊中,系統會根據共同遊戲時間來計算玩家之間的“親密度”,這種親密度網路就可以用有權圖來表示。 + +![有權圖與無權圖](graph.assets/weighted_graph.png) + +圖資料結構包含以下常用術語。 + +- 鄰接(adjacency):當兩頂點之間存在邊相連時,稱這兩頂點“鄰接”。在上圖中,頂點 1 的鄰接頂點為頂點 2、3、5。 +- 路徑(path):從頂點 A 到頂點 B 經過的邊構成的序列被稱為從 A 到 B 的“路徑”。在上圖中,邊序列 1-5-2-4 是頂點 1 到頂點 4 的一條路徑。 +- 度(degree):一個頂點擁有的邊數。對於有向圖,入度(in-degree)表示有多少條邊指向該頂點,出度(out-degree)表示有多少條邊從該頂點指出。 + +## 圖的表示 + +圖的常用表示方式包括“鄰接矩陣”和“鄰接表”。以下使用無向圖進行舉例。 + +### 鄰接矩陣 + +設圖的頂點數量為 $n$ ,鄰接矩陣(adjacency matrix)使用一個 $n \times n$ 大小的矩陣來表示圖,每一行(列)代表一個頂點,矩陣元素代表邊,用 $1$ 或 $0$ 表示兩個頂點之間是否存在邊。 + +如下圖所示,設鄰接矩陣為 $M$、頂點串列為 $V$ ,那麼矩陣元素 $M[i, j] = 1$ 表示頂點 $V[i]$ 到頂點 $V[j]$ 之間存在邊,反之 $M[i, j] = 0$ 表示兩頂點之間無邊。 + +![圖的鄰接矩陣表示](graph.assets/adjacency_matrix.png) + +鄰接矩陣具有以下特性。 + +- 在簡單圖中,頂點不能與自身相連,此時鄰接矩陣主對角線元素沒有意義。 +- 對於無向圖,兩個方向的邊等價,此時鄰接矩陣關於主對角線對稱。 +- 將鄰接矩陣的元素從 $1$ 和 $0$ 替換為權重,則可表示有權圖。 + +使用鄰接矩陣表示圖時,我們可以直接訪問矩陣元素以獲取邊,因此增刪查改操作的效率很高,時間複雜度均為 $O(1)$ 。然而,矩陣的空間複雜度為 $O(n^2)$ ,記憶體佔用較多。 + +### 鄰接表 + +鄰接表(adjacency list)使用 $n$ 個鏈結串列來表示圖,鏈結串列節點表示頂點。第 $i$ 個鏈結串列對應頂點 $i$ ,其中儲存了該頂點的所有鄰接頂點(與該頂點相連的頂點)。下圖展示了一個使用鄰接表儲存的圖的示例。 + +![圖的鄰接表表示](graph.assets/adjacency_list.png) + +鄰接表僅儲存實際存在的邊,而邊的總數通常遠小於 $n^2$ ,因此它更加節省空間。然而,在鄰接表中需要透過走訪鏈結串列來查詢邊,因此其時間效率不如鄰接矩陣。 + +觀察上圖,**鄰接表結構與雜湊表中的“鏈式位址”非常相似,因此我們也可以採用類似的方法來最佳化效率**。比如當鏈結串列較長時,可以將鏈結串列轉化為 AVL 樹或紅黑樹,從而將時間效率從 $O(n)$ 最佳化至 $O(\log n)$ ;還可以把鏈結串列轉換為雜湊表,從而將時間複雜度降至 $O(1)$ 。 + +## 圖的常見應用 + +如下表所示,許多現實系統可以用圖來建模,相應的問題也可以約化為圖計算問題。 + +

  現實生活中常見的圖

+ +| | 頂點 | 邊 | 圖計算問題 | +| -------- | ---- | -------------------- | ------------ | +| 社交網路 | 使用者 | 好友關係 | 潛在好友推薦 | +| 地鐵線路 | 站點 | 站點間的連通性 | 最短路線推薦 | +| 太陽系 | 星體 | 星體間的萬有引力作用 | 行星軌道計算 | diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png new file mode 100644 index 0000000000..292fc83ee8 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png new file mode 100644 index 0000000000..38537819d4 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png new file mode 100644 index 0000000000..b6b8e4c636 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png new file mode 100644 index 0000000000..26d5830ef8 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png new file mode 100644 index 0000000000..786deee7b5 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png new file mode 100644 index 0000000000..fbb5148b94 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png new file mode 100644 index 0000000000..91992ceab7 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png new file mode 100644 index 0000000000..6c9058d6ed Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png new file mode 100644 index 0000000000..c2d5d1f803 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png new file mode 100644 index 0000000000..aadacfd346 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.md b/zh-hant/docs/chapter_graph/graph_operations.md new file mode 100644 index 0000000000..08d9640486 --- /dev/null +++ b/zh-hant/docs/chapter_graph/graph_operations.md @@ -0,0 +1,86 @@ +# 圖的基礎操作 + +圖的基礎操作可分為對“邊”的操作和對“頂點”的操作。在“鄰接矩陣”和“鄰接表”兩種表示方法下,實現方式有所不同。 + +## 基於鄰接矩陣的實現 + +給定一個頂點數量為 $n$ 的無向圖,則各種操作的實現方式如下圖所示。 + +- **新增或刪除邊**:直接在鄰接矩陣中修改指定的邊即可,使用 $O(1)$ 時間。而由於是無向圖,因此需要同時更新兩個方向的邊。 +- **新增頂點**:在鄰接矩陣的尾部新增一行一列,並全部填 $0$ 即可,使用 $O(n)$ 時間。 +- **刪除頂點**:在鄰接矩陣中刪除一行一列。當刪除首行首列時達到最差情況,需要將 $(n-1)^2$ 個元素“向左上移動”,從而使用 $O(n^2)$ 時間。 +- **初始化**:傳入 $n$ 個頂點,初始化長度為 $n$ 的頂點串列 `vertices` ,使用 $O(n)$ 時間;初始化 $n \times n$ 大小的鄰接矩陣 `adjMat` ,使用 $O(n^2)$ 時間。 + +=== "初始化鄰接矩陣" + ![鄰接矩陣的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_matrix_step1_initialization.png) + +=== "新增邊" + ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) + +=== "刪除邊" + ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) + +=== "新增頂點" + ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) + +=== "刪除頂點" + ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) + +以下是基於鄰接矩陣表示圖的實現程式碼: + +```src +[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} +``` + +## 基於鄰接表的實現 + +設無向圖的頂點總數為 $n$、邊總數為 $m$ ,則可根據下圖所示的方法實現各種操作。 + +- **新增邊**:在頂點對應鏈結串列的末尾新增邊即可,使用 $O(1)$ 時間。因為是無向圖,所以需要同時新增兩個方向的邊。 +- **刪除邊**:在頂點對應鏈結串列中查詢並刪除指定邊,使用 $O(m)$ 時間。在無向圖中,需要同時刪除兩個方向的邊。 +- **新增頂點**:在鄰接表中新增一個鏈結串列,並將新增頂點作為鏈結串列頭節點,使用 $O(1)$ 時間。 +- **刪除頂點**:需走訪整個鄰接表,刪除包含指定頂點的所有邊,使用 $O(n + m)$ 時間。 +- **初始化**:在鄰接表中建立 $n$ 個頂點和 $2m$ 條邊,使用 $O(n + m)$ 時間。 + +=== "初始化鄰接表" + ![鄰接表的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_list_step1_initialization.png) + +=== "新增邊" + ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) + +=== "刪除邊" + ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) + +=== "新增頂點" + ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) + +=== "刪除頂點" + ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) + +以下是鄰接表的程式碼實現。對比上圖,實際程式碼有以下不同。 + +- 為了方便新增與刪除頂點,以及簡化程式碼,我們使用串列(動態陣列)來代替鏈結串列。 +- 使用雜湊表來儲存鄰接表,`key` 為頂點例項,`value` 為該頂點的鄰接頂點串列(鏈結串列)。 + +另外,我們在鄰接表中使用 `Vertex` 類別來表示頂點,這樣做的原因是:如果與鄰接矩陣一樣,用串列索引來區分不同頂點,那麼假設要刪除索引為 $i$ 的頂點,則需走訪整個鄰接表,將所有大於 $i$ 的索引全部減 $1$ ,效率很低。而如果每個頂點都是唯一的 `Vertex` 例項,刪除某一頂點之後就無須改動其他頂點了。 + +```src +[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} +``` + +## 效率對比 + +設圖中共有 $n$ 個頂點和 $m$ 條邊,下表對比了鄰接矩陣和鄰接表的時間效率和空間效率。請注意,鄰接表(鏈結串列)對應本文實現,而鄰接表(雜湊表)專指將所有鏈結串列替換為雜湊表後的實現。 + +

  鄰接矩陣與鄰接表對比

+ +| | 鄰接矩陣 | 鄰接表(鏈結串列) | 鄰接表(雜湊表) | +| ------------ | -------- | -------------- | ---------------- | +| 判斷是否鄰接 | $O(1)$ | $O(n)$ | $O(1)$ | +| 新增邊 | $O(1)$ | $O(1)$ | $O(1)$ | +| 刪除邊 | $O(1)$ | $O(n)$ | $O(1)$ | +| 新增頂點 | $O(n)$ | $O(1)$ | $O(1)$ | +| 刪除頂點 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| 記憶體空間佔用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | + +觀察上表,似乎鄰接表(雜湊表)的時間效率與空間效率最優。但實際上,在鄰接矩陣中操作邊的效率更高,只需一次陣列訪問或賦值操作即可。綜合來看,鄰接矩陣體現了“以空間換時間”的原則,而鄰接表體現了“以時間換空間”的原則。 diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs.png new file mode 100644 index 0000000000..7d2445ac8b Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png new file mode 100644 index 0000000000..0783afc4ad Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png new file mode 100644 index 0000000000..4b95ad656a Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png new file mode 100644 index 0000000000..870aa72cbc Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png new file mode 100644 index 0000000000..e51dc1eb22 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png new file mode 100644 index 0000000000..ffe803b2c2 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png new file mode 100644 index 0000000000..cea942ecc0 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png new file mode 100644 index 0000000000..8d478ec199 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png new file mode 100644 index 0000000000..32b378b616 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png new file mode 100644 index 0000000000..3306e4a477 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png new file mode 100644 index 0000000000..73125af18e Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png new file mode 100644 index 0000000000..004bc9a7f8 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs.png new file mode 100644 index 0000000000..3a38547188 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png new file mode 100644 index 0000000000..dfb21abef4 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png new file mode 100644 index 0000000000..e673acd45b Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png new file mode 100644 index 0000000000..3fe5562ffc Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png new file mode 100644 index 0000000000..9ed006c00d Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png new file mode 100644 index 0000000000..e6b5aaeefe Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png new file mode 100644 index 0000000000..3dff7eca29 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png new file mode 100644 index 0000000000..df609a51b6 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png new file mode 100644 index 0000000000..4c3e40edf9 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png new file mode 100644 index 0000000000..3c6d20ec75 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png new file mode 100644 index 0000000000..590bdc7033 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png new file mode 100644 index 0000000000..81d2359cc1 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.md b/zh-hant/docs/chapter_graph/graph_traversal.md new file mode 100644 index 0000000000..4b855bc918 --- /dev/null +++ b/zh-hant/docs/chapter_graph/graph_traversal.md @@ -0,0 +1,140 @@ +# 圖的走訪 + +樹代表的是“一對多”的關係,而圖則具有更高的自由度,可以表示任意的“多對多”關係。因此,我們可以把樹看作圖的一種特例。顯然,**樹的走訪操作也是圖的走訪操作的一種特例**。 + +圖和樹都需要應用搜索演算法來實現走訪操作。圖的走訪方式也可分為兩種:廣度優先走訪深度優先走訪。 + +## 廣度優先走訪 + +**廣度優先走訪是一種由近及遠的走訪方式,從某個節點出發,始終優先訪問距離最近的頂點,並一層層向外擴張**。如下圖所示,從左上角頂點出發,首先走訪該頂點的所有鄰接頂點,然後走訪下一個頂點的所有鄰接頂點,以此類推,直至所有頂點訪問完畢。 + +![圖的廣度優先走訪](graph_traversal.assets/graph_bfs.png) + +### 演算法實現 + +BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入先出”的性質,這與 BFS 的“由近及遠”的思想異曲同工。 + +1. 將走訪起始頂點 `startVet` 加入佇列,並開啟迴圈。 +2. 在迴圈的每輪迭代中,彈出佇列首頂點並記錄訪問,然後將該頂點的所有鄰接頂點加入到佇列尾部。 +3. 迴圈步驟 `2.` ,直到所有頂點被訪問完畢後結束。 + +為了防止重複走訪頂點,我們需要藉助一個雜湊集合 `visited` 來記錄哪些節點已被訪問。 + +!!! tip + + 雜湊集合可以看作一個只儲存 `key` 而不儲存 `value` 的雜湊表,它可以在 $O(1)$ 時間複雜度下進行 `key` 的增刪查改操作。根據 `key` 的唯一性,雜湊集合通常用於資料去重等場景。 + +```src +[file]{graph_bfs}-[class]{}-[func]{graph_bfs} +``` + +程式碼相對抽象,建議對照下圖來加深理解。 + +=== "<1>" + ![圖的廣度優先走訪步驟](graph_traversal.assets/graph_bfs_step1.png) + +=== "<2>" + ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) + +=== "<3>" + ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) + +=== "<4>" + ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) + +=== "<5>" + ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) + +=== "<6>" + ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) + +=== "<7>" + ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) + +=== "<8>" + ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) + +=== "<9>" + ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) + +=== "<10>" + ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) + +=== "<11>" + ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) + +!!! question "廣度優先走訪的序列是否唯一?" + + 不唯一。廣度優先走訪只要求按“由近及遠”的順序走訪,**而多個相同距離的頂點的走訪順序允許被任意打亂**。以上圖為例,頂點 $1$、$3$ 的訪問順序可以交換,頂點 $2$、$4$、$6$ 的訪問順序也可以任意交換。 + +### 複雜度分析 + +**時間複雜度**:所有頂點都會入列並出隊一次,使用 $O(|V|)$ 時間;在走訪鄰接頂點的過程中,由於是無向圖,因此所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。 + +**空間複雜度**:串列 `res` ,雜湊集合 `visited` ,佇列 `que` 中的頂點數量最多為 $|V|$ ,使用 $O(|V|)$ 空間。 + +## 深度優先走訪 + +**深度優先走訪是一種優先走到底、無路可走再回頭的走訪方式**。如下圖所示,從左上角頂點出發,訪問當前頂點的某個鄰接頂點,直到走到盡頭時返回,再繼續走到盡頭並返回,以此類推,直至所有頂點走訪完成。 + +![圖的深度優先走訪](graph_traversal.assets/graph_dfs.png) + +### 演算法實現 + +這種“走到盡頭再返回”的演算法範式通常基於遞迴來實現。與廣度優先走訪類似,在深度優先走訪中,我們也需要藉助一個雜湊集合 `visited` 來記錄已被訪問的頂點,以避免重複訪問頂點。 + +```src +[file]{graph_dfs}-[class]{}-[func]{graph_dfs} +``` + +深度優先走訪的演算法流程如下圖所示。 + +- **直虛線代表向下遞推**,表示開啟了一個新的遞迴方法來訪問新頂點。 +- **曲虛線代表向上回溯**,表示此遞迴方法已經返回,回溯到了開啟此方法的位置。 + +為了加深理解,建議將下圖與程式碼結合起來,在腦中模擬(或者用筆畫下來)整個 DFS 過程,包括每個遞迴方法何時開啟、何時返回。 + +=== "<1>" + ![圖的深度優先走訪步驟](graph_traversal.assets/graph_dfs_step1.png) + +=== "<2>" + ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) + +=== "<3>" + ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) + +=== "<4>" + ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) + +=== "<5>" + ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) + +=== "<6>" + ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) + +=== "<7>" + ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) + +=== "<8>" + ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) + +=== "<9>" + ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) + +=== "<10>" + ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) + +=== "<11>" + ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) + +!!! question "深度優先走訪的序列是否唯一?" + + 與廣度優先走訪類似,深度優先走訪序列的順序也不是唯一的。給定某頂點,先往哪個方向探索都可以,即鄰接頂點的順序可以任意打亂,都是深度優先走訪。 + + 以樹的走訪為例,“根 $\rightarrow$ 左 $\rightarrow$ 右”“左 $\rightarrow$ 根 $\rightarrow$ 右”“左 $\rightarrow$ 右 $\rightarrow$ 根”分別對應前序、中序、後序走訪,它們展示了三種走訪優先順序,然而這三者都屬於深度優先走訪。 + +### 複雜度分析 + +**時間複雜度**:所有頂點都會被訪問 $1$ 次,使用 $O(|V|)$ 時間;所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。 + +**空間複雜度**:串列 `res` ,雜湊集合 `visited` 頂點數量最多為 $|V|$ ,遞迴深度最大為 $|V|$ ,因此使用 $O(|V|)$ 空間。 diff --git a/zh-hant/docs/chapter_graph/index.md b/zh-hant/docs/chapter_graph/index.md new file mode 100644 index 0000000000..e82ad1b86c --- /dev/null +++ b/zh-hant/docs/chapter_graph/index.md @@ -0,0 +1,9 @@ +# 圖 + +![圖](../assets/covers/chapter_graph.jpg) + +!!! abstract + + 在生命旅途中,我們就像是一個個節點,被無數看不見的邊相連。 + + 每一次的相識與相離,都在這張巨大的網路圖中留下獨特的印記。 diff --git a/zh-hant/docs/chapter_graph/summary.md b/zh-hant/docs/chapter_graph/summary.md new file mode 100644 index 0000000000..5eed0e19d5 --- /dev/null +++ b/zh-hant/docs/chapter_graph/summary.md @@ -0,0 +1,31 @@ +# 小結 + +### 重點回顧 + +- 圖由頂點和邊組成,可以表示為一組頂點和一組邊構成的集合。 +- 相較於線性關係(鏈結串列)和分治關係(樹),網路關係(圖)具有更高的自由度,因而更為複雜。 +- 有向圖的邊具有方向性,連通圖中的任意頂點均可達,有權圖的每條邊都包含權重變數。 +- 鄰接矩陣利用矩陣來表示圖,每一行(列)代表一個頂點,矩陣元素代表邊,用 $1$ 或 $0$ 表示兩個頂點之間有邊或無邊。鄰接矩陣在增刪查改操作上效率很高,但空間佔用較多。 +- 鄰接表使用多個鏈結串列來表示圖,第 $i$ 個鏈結串列對應頂點 $i$ ,其中儲存了該頂點的所有鄰接頂點。鄰接表相對於鄰接矩陣更加節省空間,但由於需要走訪鏈結串列來查詢邊,因此時間效率較低。 +- 當鄰接表中的鏈結串列過長時,可以將其轉換為紅黑樹或雜湊表,從而提升查詢效率。 +- 從演算法思想的角度分析,鄰接矩陣體現了“以空間換時間”,鄰接表體現了“以時間換空間”。 +- 圖可用於建模各類現實系統,如社交網路、地鐵線路等。 +- 樹是圖的一種特例,樹的走訪也是圖的走訪的一種特例。 +- 圖的廣度優先走訪是一種由近及遠、層層擴張的搜尋方式,通常藉助佇列實現。 +- 圖的深度優先走訪是一種優先走到底、無路可走時再回溯的搜尋方式,常基於遞迴來實現。 + +### Q & A + +**Q**:路徑的定義是頂點序列還是邊序列? + +維基百科上不同語言版本的定義不一致:英文版是“路徑是一個邊序列”,而中文版是“路徑是一個頂點序列”。以下是英文版原文:In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. + +在本文中,路徑被視為一個邊序列,而不是一個頂點序列。這是因為兩個頂點之間可能存在多條邊連線,此時每條邊都對應一條路徑。 + +**Q**:非連通圖中是否會有無法走訪到的點? + +在非連通圖中,從某個頂點出發,至少有一個頂點無法到達。走訪非連通圖需要設定多個起點,以走訪到圖的所有連通分量。 + +**Q**:在鄰接表中,“與該頂點相連的所有頂點”的頂點順序是否有要求? + +可以是任意順序。但在實際應用中,可能需要按照指定規則來排序,比如按照頂點新增的次序,或者按照頂點值大小的順序等,這樣有助於快速查詢“帶有某種極值”的頂點。 diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png new file mode 100644 index 0000000000..07ed4b86ba Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png new file mode 100644 index 0000000000..fa06064496 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png new file mode 100644 index 0000000000..1841a1c167 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png new file mode 100644 index 0000000000..b4d995e687 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.md b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.md new file mode 100644 index 0000000000..5c4f8f5f3f --- /dev/null +++ b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.md @@ -0,0 +1,52 @@ +# 分數背包問題 + +!!! question + + 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。每個物品只能選擇一次,**但可以選擇物品的一部分,價值根據選擇的重量比例計算**,問在限定背包容量下背包中物品的最大價值。示例如下圖所示。 + +![分數背包問題的示例資料](fractional_knapsack_problem.assets/fractional_knapsack_example.png) + +分數背包問題和 0-1 背包問題整體上非常相似,狀態包含當前物品 $i$ 和容量 $c$ ,目標是求限定背包容量下的最大價值。 + +不同點在於,本題允許只選擇物品的一部分。如下圖所示,**我們可以對物品任意地進行切分,並按照重量比例來計算相應價值**。 + +1. 對於物品 $i$ ,它在單位重量下的價值為 $val[i-1] / wgt[i-1]$ ,簡稱單位價值。 +2. 假設放入一部分物品 $i$ ,重量為 $w$ ,則背包增加的價值為 $w \times val[i-1] / wgt[i-1]$ 。 + +![物品在單位重量下的價值](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) + +### 貪婪策略確定 + +最大化背包內物品總價值,**本質上是最大化單位重量下的物品價值**。由此便可推理出下圖所示的貪婪策略。 + +1. 將物品按照單位價值從高到低進行排序。 +2. 走訪所有物品,**每輪貪婪地選擇單位價值最高的物品**。 +3. 若剩餘背包容量不足,則使用當前物品的一部分填滿背包。 + +![分數背包問題的貪婪策略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) + +### 程式碼實現 + +我們建立了一個物品類別 `Item` ,以便將物品按照單位價值進行排序。迴圈進行貪婪選擇,當背包已滿時跳出並返回解: + +```src +[file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} +``` + +內建排序演算法的時間複雜度通常為 $O(\log n)$ ,空間複雜度通常為 $O(\log n)$ 或 $O(n)$ ,取決於程式語言的具體實現。 + +除排序之外,在最差情況下,需要走訪整個物品串列,**因此時間複雜度為 $O(n)$** ,其中 $n$ 為物品數量。 + +由於初始化了一個 `Item` 物件串列,**因此空間複雜度為 $O(n)$** 。 + +### 正確性證明 + +採用反證法。假設物品 $x$ 是單位價值最高的物品,使用某演算法求得最大價值為 `res` ,但該解中不包含物品 $x$ 。 + +現在從背包中拿出單位重量的任意物品,並替換為單位重量的物品 $x$ 。由於物品 $x$ 的單位價值最高,因此替換後的總價值一定大於 `res` 。**這與 `res` 是最優解矛盾,說明最優解中必須包含物品 $x$** 。 + +對於該解中的其他物品,我們也可以構建出上述矛盾。總而言之,**單位價值更大的物品總是更優選擇**,這說明貪婪策略是有效的。 + +如下圖所示,如果將物品重量和物品單位價值分別看作一張二維圖表的橫軸和縱軸,則分數背包問題可轉化為“求在有限橫軸區間下圍成的最大面積”。這個類比可以幫助我們從幾何角度理解貪婪策略的有效性。 + +![分數背包問題的幾何表示](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) diff --git a/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png new file mode 100644 index 0000000000..a166d8c0e5 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png differ diff --git a/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png new file mode 100644 index 0000000000..065cb64f1d Binary files /dev/null and b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png differ diff --git a/zh-hant/docs/chapter_greedy/greedy_algorithm.md b/zh-hant/docs/chapter_greedy/greedy_algorithm.md new file mode 100644 index 0000000000..f4a5a349fe --- /dev/null +++ b/zh-hant/docs/chapter_greedy/greedy_algorithm.md @@ -0,0 +1,94 @@ +# 貪婪演算法 + +貪婪演算法(greedy algorithm)是一種常見的解決最佳化問題的演算法,其基本思想是在問題的每個決策階段,都選擇當前看起來最優的選擇,即貪婪地做出區域性最優的決策,以期獲得全域性最優解。貪婪演算法簡潔且高效,在許多實際問題中有著廣泛的應用。 + +貪婪演算法和動態規劃都常用於解決最佳化問題。它們之間存在一些相似之處,比如都依賴最優子結構性質,但工作原理不同。 + +- 動態規劃會根據之前階段的所有決策來考慮當前決策,並使用過去子問題的解來構建當前子問題的解。 +- 貪婪演算法不會考慮過去的決策,而是一路向前地進行貪婪選擇,不斷縮小問題範圍,直至問題被解決。 + +我們先透過例題“零錢兌換”瞭解貪婪演算法的工作原理。這道題已經在“完全背包問題”章節中介紹過,相信你對它並不陌生。 + +!!! question + + 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,每種硬幣可以重複選取,問能夠湊出目標金額的最少硬幣數量。如果無法湊出目標金額,則返回 $-1$ 。 + +本題採取的貪婪策略如下圖所示。給定目標金額,**我們貪婪地選擇不大於且最接近它的硬幣**,不斷迴圈該步驟,直至湊出目標金額為止。 + +![零錢兌換的貪婪策略](greedy_algorithm.assets/coin_change_greedy_strategy.png) + +實現程式碼如下所示: + +```src +[file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} +``` + +你可能會不由地發出感嘆:So clean !貪婪演算法僅用約十行程式碼就解決了零錢兌換問題。 + +## 貪婪演算法的優點與侷限性 + +**貪婪演算法不僅操作直接、實現簡單,而且通常效率也很高**。在以上程式碼中,記硬幣最小面值為 $\min(coins)$ ,則貪婪選擇最多迴圈 $amt / \min(coins)$ 次,時間複雜度為 $O(amt / \min(coins))$ 。這比動態規劃解法的時間複雜度 $O(n \times amt)$ 小了一個數量級。 + +然而,**對於某些硬幣面值組合,貪婪演算法並不能找到最優解**。下圖給出了兩個示例。 + +- **正例 $coins = [1, 5, 10, 20, 50, 100]$**:在該硬幣組合下,給定任意 $amt$ ,貪婪演算法都可以找到最優解。 +- **反例 $coins = [1, 20, 50]$**:假設 $amt = 60$ ,貪婪演算法只能找到 $50 + 1 \times 10$ 的兌換組合,共計 $11$ 枚硬幣,但動態規劃可以找到最優解 $20 + 20 + 20$ ,僅需 $3$ 枚硬幣。 +- **反例 $coins = [1, 49, 50]$**:假設 $amt = 98$ ,貪婪演算法只能找到 $50 + 1 \times 48$ 的兌換組合,共計 $49$ 枚硬幣,但動態規劃可以找到最優解 $49 + 49$ ,僅需 $2$ 枚硬幣。 + +![貪婪演算法無法找出最優解的示例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) + +也就是說,對於零錢兌換問題,貪婪演算法無法保證找到全域性最優解,並且有可能找到非常差的解。它更適合用動態規劃解決。 + +一般情況下,貪婪演算法的適用情況分以下兩種。 + +1. **可以保證找到最優解**:貪婪演算法在這種情況下往往是最優選擇,因為它往往比回溯、動態規劃更高效。 +2. **可以找到近似最優解**:貪婪演算法在這種情況下也是可用的。對於很多複雜問題來說,尋找全域性最優解非常困難,能以較高效率找到次優解也是非常不錯的。 + +## 貪婪演算法特性 + +那麼問題來了,什麼樣的問題適合用貪婪演算法求解呢?或者說,貪婪演算法在什麼情況下可以保證找到最優解? + +相較於動態規劃,貪婪演算法的使用條件更加苛刻,其主要關注問題的兩個性質。 + +- **貪婪選擇性質**:只有當局部最優選擇始終可以導致全域性最優解時,貪婪演算法才能保證得到最優解。 +- **最優子結構**:原問題的最優解包含子問題的最優解。 + +最優子結構已經在“動態規劃”章節中介紹過,這裡不再贅述。值得注意的是,一些問題的最優子結構並不明顯,但仍然可使用貪婪演算法解決。 + +我們主要探究貪婪選擇性質的判斷方法。雖然它的描述看上去比較簡單,**但實際上對於許多問題,證明貪婪選擇性質並非易事**。 + +例如零錢兌換問題,我們雖然能夠容易地舉出反例,對貪婪選擇性質進行證偽,但證實的難度較大。如果問:**滿足什麼條件的硬幣組合可以使用貪婪演算法求解**?我們往往只能憑藉直覺或舉例子來給出一個模稜兩可的答案,而難以給出嚴謹的數學證明。 + +!!! quote + + 有一篇論文給出了一個 $O(n^3)$ 時間複雜度的演算法,用於判斷一個硬幣組合能否使用貪婪演算法找出任意金額的最優解。 + + Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. + +## 貪婪演算法解題步驟 + +貪婪問題的解決流程大體可分為以下三步。 + +1. **問題分析**:梳理與理解問題特性,包括狀態定義、最佳化目標和約束條件等。這一步在回溯和動態規劃中都有涉及。 +2. **確定貪婪策略**:確定如何在每一步中做出貪婪選擇。這個策略能夠在每一步減小問題的規模,並最終解決整個問題。 +3. **正確性證明**:通常需要證明問題具有貪婪選擇性質和最優子結構。這個步驟可能需要用到數學證明,例如歸納法或反證法等。 + +確定貪婪策略是求解問題的核心步驟,但實施起來可能並不容易,主要有以下原因。 + +- **不同問題的貪婪策略的差異較大**。對於許多問題來說,貪婪策略比較淺顯,我們透過一些大概的思考與嘗試就能得出。而對於一些複雜問題,貪婪策略可能非常隱蔽,這種情況就非常考驗個人的解題經驗與演算法能力了。 +- **某些貪婪策略具有較強的迷惑性**。當我們滿懷信心設計好貪婪策略,寫出解題程式碼並提交執行,很可能發現部分測試樣例無法透過。這是因為設計的貪婪策略只是“部分正確”的,上文介紹的零錢兌換就是一個典型案例。 + +為了保證正確性,我們應該對貪婪策略進行嚴謹的數學證明,**通常需要用到反證法或數學歸納法**。 + +然而,正確性證明也很可能不是一件易事。如若沒有頭緒,我們通常會選擇面向測試用例進行程式碼除錯,一步步修改與驗證貪婪策略。 + +## 貪婪演算法典型例題 + +貪婪演算法常常應用在滿足貪婪選擇性質和最優子結構的最佳化問題中,以下列舉了一些典型的貪婪演算法問題。 + +- **硬幣找零問題**:在某些硬幣組合下,貪婪演算法總是可以得到最優解。 +- **區間排程問題**:假設你有一些任務,每個任務在一段時間內進行,你的目標是完成儘可能多的任務。如果每次都選擇結束時間最早的任務,那麼貪婪演算法就可以得到最優解。 +- **分數背包問題**:給定一組物品和一個載重量,你的目標是選擇一組物品,使得總重量不超過載重量,且總價值最大。如果每次都選擇價效比最高(價值 / 重量)的物品,那麼貪婪演算法在一些情況下可以得到最優解。 +- **股票買賣問題**:給定一組股票的歷史價格,你可以進行多次買賣,但如果你已經持有股票,那麼在賣出之前不能再買,目標是獲取最大利潤。 +- **霍夫曼編碼**:霍夫曼編碼是一種用於無損資料壓縮的貪婪演算法。透過構建霍夫曼樹,每次選擇出現頻率最低的兩個節點合併,最後得到的霍夫曼樹的帶權路徑長度(編碼長度)最小。 +- **Dijkstra 演算法**:它是一種解決給定源頂點到其餘各頂點的最短路徑問題的貪婪演算法。 diff --git a/zh-hant/docs/chapter_greedy/index.md b/zh-hant/docs/chapter_greedy/index.md new file mode 100644 index 0000000000..7b06ea7899 --- /dev/null +++ b/zh-hant/docs/chapter_greedy/index.md @@ -0,0 +1,9 @@ +# 貪婪 + +![貪婪](../assets/covers/chapter_greedy.jpg) + +!!! abstract + + 向日葵朝著太陽轉動,時刻追求自身成長的最大可能。 + + 貪婪策略在一輪輪的簡單選擇中,逐步導向最佳答案。 diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png new file mode 100644 index 0000000000..8b53883e56 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png new file mode 100644 index 0000000000..742469096f Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png new file mode 100644 index 0000000000..83c5fb9442 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png new file mode 100644 index 0000000000..ac149881aa Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png new file mode 100644 index 0000000000..c5e758b06a Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png new file mode 100644 index 0000000000..858db53aac Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png new file mode 100644 index 0000000000..2c0a6fc0bc Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png new file mode 100644 index 0000000000..ec02379986 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png new file mode 100644 index 0000000000..60bb1e9e5e Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png new file mode 100644 index 0000000000..5e7815a3e2 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png new file mode 100644 index 0000000000..6a6947c7df Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png new file mode 100644 index 0000000000..197f157689 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png new file mode 100644 index 0000000000..02a600e751 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png new file mode 100644 index 0000000000..99b025d55b Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.md b/zh-hant/docs/chapter_greedy/max_capacity_problem.md new file mode 100644 index 0000000000..8b3ac4dabb --- /dev/null +++ b/zh-hant/docs/chapter_greedy/max_capacity_problem.md @@ -0,0 +1,99 @@ +# 最大容量問題 + +!!! question + + 輸入一個陣列 $ht$ ,其中的每個元素代表一個垂直隔板的高度。陣列中的任意兩個隔板,以及它們之間的空間可以組成一個容器。 + + 容器的容量等於高度和寬度的乘積(面積),其中高度由較短的隔板決定,寬度是兩個隔板的陣列索引之差。 + + 請在陣列中選擇兩個隔板,使得組成的容器的容量最大,返回最大容量。示例如下圖所示。 + +![最大容量問題的示例資料](max_capacity_problem.assets/max_capacity_example.png) + +容器由任意兩個隔板圍成,**因此本題的狀態為兩個隔板的索引,記為 $[i, j]$** 。 + +根據題意,容量等於高度乘以寬度,其中高度由短板決定,寬度是兩隔板的陣列索引之差。設容量為 $cap[i, j]$ ,則可得計算公式: + +$$ +cap[i, j] = \min(ht[i], ht[j]) \times (j - i) +$$ + +設陣列長度為 $n$ ,兩個隔板的組合數量(狀態總數)為 $C_n^2 = \frac{n(n - 1)}{2}$ 個。最直接地,**我們可以窮舉所有狀態**,從而求得最大容量,時間複雜度為 $O(n^2)$ 。 + +### 貪婪策略確定 + +這道題還有更高效率的解法。如下圖所示,現選取一個狀態 $[i, j]$ ,其滿足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 為短板、$j$ 為長板。 + +![初始狀態](max_capacity_problem.assets/max_capacity_initial_state.png) + +如下圖所示,**若此時將長板 $j$ 向短板 $i$ 靠近,則容量一定變小**。 + +這是因為在移動長板 $j$ 後,寬度 $j-i$ 肯定變小;而高度由短板決定,因此高度只可能不變( $i$ 仍為短板)或變小(移動後的 $j$ 成為短板)。 + +![向內移動長板後的狀態](max_capacity_problem.assets/max_capacity_moving_long_board.png) + +反向思考,**我們只有向內收縮短板 $i$ ,才有可能使容量變大**。因為雖然寬度一定變小,**但高度可能會變大**(移動後的短板 $i$ 可能會變長)。例如在下圖中,移動短板後面積變大。 + +![向內移動短板後的狀態](max_capacity_problem.assets/max_capacity_moving_short_board.png) + +由此便可推出本題的貪婪策略:初始化兩指標,使其分列容器兩端,每輪向內收縮短板對應的指標,直至兩指標相遇。 + +下圖展示了貪婪策略的執行過程。 + +1. 初始狀態下,指標 $i$ 和 $j$ 分列陣列兩端。 +2. 計算當前狀態的容量 $cap[i, j]$ ,並更新最大容量。 +3. 比較板 $i$ 和板 $j$ 的高度,並將短板向內移動一格。 +4. 迴圈執行第 `2.` 步和第 `3.` 步,直至 $i$ 和 $j$ 相遇時結束。 + +=== "<1>" + ![最大容量問題的貪婪過程](max_capacity_problem.assets/max_capacity_greedy_step1.png) + +=== "<2>" + ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) + +=== "<3>" + ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) + +=== "<4>" + ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) + +=== "<5>" + ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) + +=== "<6>" + ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) + +=== "<7>" + ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) + +=== "<8>" + ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) + +=== "<9>" + ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) + +### 程式碼實現 + +程式碼迴圈最多 $n$ 輪,**因此時間複雜度為 $O(n)$** 。 + +變數 $i$、$j$、$res$ 使用常數大小的額外空間,**因此空間複雜度為 $O(1)$** 。 + +```src +[file]{max_capacity}-[class]{}-[func]{max_capacity} +``` + +### 正確性證明 + +之所以貪婪比窮舉更快,是因為每輪的貪婪選擇都會“跳過”一些狀態。 + +比如在狀態 $cap[i, j]$ 下,$i$ 為短板、$j$ 為長板。若貪婪地將短板 $i$ 向內移動一格,會導致下圖所示的狀態被“跳過”。**這意味著之後無法驗證這些狀態的容量大小**。 + +$$ +cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] +$$ + +![移動短板導致被跳過的狀態](max_capacity_problem.assets/max_capacity_skipped_states.png) + +觀察發現,**這些被跳過的狀態實際上就是將長板 $j$ 向內移動的所有狀態**。前面我們已經證明內移長板一定會導致容量變小。也就是說,被跳過的狀態都不可能是最優解,**跳過它們不會導致錯過最優解**。 + +以上分析說明,移動短板的操作是“安全”的,貪婪策略是有效的。 diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png new file mode 100644 index 0000000000..cddf154f20 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png new file mode 100644 index 0000000000..79522773f6 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png new file mode 100644 index 0000000000..7d571a88f8 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png new file mode 100644 index 0000000000..8ccae074f3 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.md b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.md new file mode 100644 index 0000000000..1504b23c0d --- /dev/null +++ b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.md @@ -0,0 +1,85 @@ +# 最大切分乘積問題 + +!!! question + + 給定一個正整數 $n$ ,將其切分為至少兩個正整數的和,求切分後所有整數的乘積最大是多少,如下圖所示。 + +![最大切分乘積的問題定義](max_product_cutting_problem.assets/max_product_cutting_definition.png) + +假設我們將 $n$ 切分為 $m$ 個整數因子,其中第 $i$ 個因子記為 $n_i$ ,即 + +$$ +n = \sum_{i=1}^{m}n_i +$$ + +本題的目標是求得所有整數因子的最大乘積,即 + +$$ +\max(\prod_{i=1}^{m}n_i) +$$ + +我們需要思考的是:切分數量 $m$ 應該多大,每個 $n_i$ 應該是多少? + +### 貪婪策略確定 + +根據經驗,兩個整數的乘積往往比它們的加和更大。假設從 $n$ 中分出一個因子 $2$ ,則它們的乘積為 $2(n-2)$ 。我們將該乘積與 $n$ 作比較: + +$$ +\begin{aligned} +2(n-2) & \geq n \newline +2n - n - 4 & \geq 0 \newline +n & \geq 4 +\end{aligned} +$$ + +如下圖所示,當 $n \geq 4$ 時,切分出一個 $2$ 後乘積會變大,**這說明大於等於 $4$ 的整數都應該被切分**。 + +**貪婪策略一**:如果切分方案中包含 $\geq 4$ 的因子,那麼它就應該被繼續切分。最終的切分方案只應出現 $1$、$2$、$3$ 這三種因子。 + +![切分導致乘積變大](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) + +接下來思考哪個因子是最優的。在 $1$、$2$、$3$ 這三個因子中,顯然 $1$ 是最差的,因為 $1 \times (n-1) < n$ 恆成立,即切分出 $1$ 反而會導致乘積減小。 + +如下圖所示,當 $n = 6$ 時,有 $3 \times 3 > 2 \times 2 \times 2$ 。**這意味著切分出 $3$ 比切分出 $2$ 更優**。 + +**貪婪策略二**:在切分方案中,最多隻應存在兩個 $2$ 。因為三個 $2$ 總是可以替換為兩個 $3$ ,從而獲得更大的乘積。 + +![最優切分因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) + +綜上所述,可推理出以下貪婪策略。 + +1. 輸入整數 $n$ ,從其不斷地切分出因子 $3$ ,直至餘數為 $0$、$1$、$2$ 。 +2. 當餘數為 $0$ 時,代表 $n$ 是 $3$ 的倍數,因此不做任何處理。 +3. 當餘數為 $2$ 時,不繼續劃分,保留。 +4. 當餘數為 $1$ 時,由於 $2 \times 2 > 1 \times 3$ ,因此應將最後一個 $3$ 替換為 $2$ 。 + +### 程式碼實現 + +如下圖所示,我們無須透過迴圈來切分整數,而可以利用向下整除運算得到 $3$ 的個數 $a$ ,用取模運算得到餘數 $b$ ,此時有: + +$$ +n = 3 a + b +$$ + +請注意,對於 $n \leq 3$ 的邊界情況,必須拆分出一個 $1$ ,乘積為 $1 \times (n - 1)$ 。 + +```src +[file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} +``` + +![最大切分乘積的計算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) + +**時間複雜度取決於程式語言的冪運算的實現方法**。以 Python 為例,常用的冪計算函式有三種。 + +- 運算子 `**` 和函式 `pow()` 的時間複雜度均為 $O(\log⁡ a)$ 。 +- 函式 `math.pow()` 內部呼叫 C 語言庫的 `pow()` 函式,其執行浮點取冪,時間複雜度為 $O(1)$ 。 + +變數 $a$ 和 $b$ 使用常數大小的額外空間,**因此空間複雜度為 $O(1)$** 。 + +### 正確性證明 + +使用反證法,只分析 $n \geq 3$ 的情況。 + +1. **所有因子 $\leq 3$** :假設最優切分方案中存在 $\geq 4$ 的因子 $x$ ,那麼一定可以將其繼續劃分為 $2(x-2)$ ,從而獲得更大的乘積。這與假設矛盾。 +2. **切分方案不包含 $1$** :假設最優切分方案中存在一個因子 $1$ ,那麼它一定可以合併入另外一個因子中,以獲得更大的乘積。這與假設矛盾。 +3. **切分方案最多包含兩個 $2$** :假設最優切分方案中包含三個 $2$ ,那麼一定可以替換為兩個 $3$ ,乘積更大。這與假設矛盾。 diff --git a/zh-hant/docs/chapter_greedy/summary.md b/zh-hant/docs/chapter_greedy/summary.md new file mode 100644 index 0000000000..c312a04c56 --- /dev/null +++ b/zh-hant/docs/chapter_greedy/summary.md @@ -0,0 +1,12 @@ +# 小結 + +- 貪婪演算法通常用於解決最最佳化問題,其原理是在每個決策階段都做出區域性最優的決策,以期獲得全域性最優解。 +- 貪婪演算法會迭代地做出一個又一個的貪婪選擇,每輪都將問題轉化成一個規模更小的子問題,直到問題被解決。 +- 貪婪演算法不僅實現簡單,還具有很高的解題效率。相比於動態規劃,貪婪演算法的時間複雜度通常更低。 +- 在零錢兌換問題中,對於某些硬幣組合,貪婪演算法可以保證找到最優解;對於另外一些硬幣組合則不然,貪婪演算法可能找到很差的解。 +- 適合用貪婪演算法求解的問題具有兩大性質:貪婪選擇性質和最優子結構。貪婪選擇性質代表貪婪策略的有效性。 +- 對於某些複雜問題,貪婪選擇性質的證明並不簡單。相對來說,證偽更加容易,例如零錢兌換問題。 +- 求解貪婪問題主要分為三步:問題分析、確定貪婪策略、正確性證明。其中,確定貪婪策略是核心步驟,正確性證明往往是難點。 +- 分數背包問題在 0-1 背包的基礎上,允許選擇物品的一部分,因此可使用貪婪演算法求解。貪婪策略的正確性可以使用反證法來證明。 +- 最大容量問題可使用窮舉法求解,時間複雜度為 $O(n^2)$ 。透過設計貪婪策略,每輪向內移動短板,可將時間複雜度最佳化至 $O(n)$ 。 +- 在最大切分乘積問題中,我們先後推理出兩個貪婪策略:$\geq 4$ 的整數都應該繼續切分,最優切分因子為 $3$ 。程式碼中包含冪運算,時間複雜度取決於冪運算實現方法,通常為 $O(1)$ 或 $O(\log n)$ 。 diff --git a/zh-hant/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png b/zh-hant/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png new file mode 100644 index 0000000000..4aa9564de3 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_algorithm.md b/zh-hant/docs/chapter_hashing/hash_algorithm.md new file mode 100644 index 0000000000..50c6f73694 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/hash_algorithm.md @@ -0,0 +1,416 @@ +# 雜湊演算法 + +前兩節介紹了雜湊表的工作原理和雜湊衝突的處理方法。然而無論是開放定址還是鏈式位址,**它們只能保證雜湊表可以在發生衝突時正常工作,而無法減少雜湊衝突的發生**。 + +如果雜湊衝突過於頻繁,雜湊表的效能則會急劇劣化。如下圖所示,對於鏈式位址雜湊表,理想情況下鍵值對均勻分佈在各個桶中,達到最佳查詢效率;最差情況下所有鍵值對都儲存到同一個桶中,時間複雜度退化至 $O(n)$ 。 + +![雜湊衝突的最佳情況與最差情況](hash_algorithm.assets/hash_collision_best_worst_condition.png) + +**鍵值對的分佈情況由雜湊函式決定**。回憶雜湊函式的計算步驟,先計算雜湊值,再對陣列長度取模: + +```shell +index = hash(key) % capacity +``` + +觀察以上公式,當雜湊表容量 `capacity` 固定時,**雜湊演算法 `hash()` 決定了輸出值**,進而決定了鍵值對在雜湊表中的分佈情況。 + +這意味著,為了降低雜湊衝突的發生機率,我們應當將注意力集中在雜湊演算法 `hash()` 的設計上。 + +## 雜湊演算法的目標 + +為了實現“既快又穩”的雜湊表資料結構,雜湊演算法應具備以下特點。 + +- **確定性**:對於相同的輸入,雜湊演算法應始終產生相同的輸出。這樣才能確保雜湊表是可靠的。 +- **效率高**:計算雜湊值的過程應該足夠快。計算開銷越小,雜湊表的實用性越高。 +- **均勻分佈**:雜湊演算法應使得鍵值對均勻分佈在雜湊表中。分佈越均勻,雜湊衝突的機率就越低。 + +實際上,雜湊演算法除了可以用於實現雜湊表,還廣泛應用於其他領域中。 + +- **密碼儲存**:為了保護使用者密碼的安全,系統通常不會直接儲存使用者的明文密碼,而是儲存密碼的雜湊值。當用戶輸入密碼時,系統會對輸入的密碼計算雜湊值,然後與儲存的雜湊值進行比較。如果兩者匹配,那麼密碼就被視為正確。 +- **資料完整性檢查**:資料傳送方可以計算資料的雜湊值並將其一同傳送;接收方可以重新計算接收到的資料的雜湊值,並與接收到的雜湊值進行比較。如果兩者匹配,那麼資料就被視為完整。 + +對於密碼學的相關應用,為了防止從雜湊值推導出原始密碼等逆向工程,雜湊演算法需要具備更高等級的安全特性。 + +- **單向性**:無法透過雜湊值反推出關於輸入資料的任何資訊。 +- **抗碰撞性**:應當極難找到兩個不同的輸入,使得它們的雜湊值相同。 +- **雪崩效應**:輸入的微小變化應當導致輸出的顯著且不可預測的變化。 + +請注意,**“均勻分佈”與“抗碰撞性”是兩個獨立的概念**,滿足均勻分佈不一定滿足抗碰撞性。例如,在隨機輸入 `key` 下,雜湊函式 `key % 100` 可以產生均勻分佈的輸出。然而該雜湊演算法過於簡單,所有後兩位相等的 `key` 的輸出都相同,因此我們可以很容易地從雜湊值反推出可用的 `key` ,從而破解密碼。 + +## 雜湊演算法的設計 + +雜湊演算法的設計是一個需要考慮許多因素的複雜問題。然而對於某些要求不高的場景,我們也能設計一些簡單的雜湊演算法。 + +- **加法雜湊**:對輸入的每個字元的 ASCII 碼進行相加,將得到的總和作為雜湊值。 +- **乘法雜湊**:利用乘法的不相關性,每輪乘以一個常數,將各個字元的 ASCII 碼累積到雜湊值中。 +- **互斥或雜湊**:將輸入資料的每個元素透過互斥或操作累積到一個雜湊值中。 +- **旋轉雜湊**:將每個字元的 ASCII 碼累積到一個雜湊值中,每次累積之前都會對雜湊值進行旋轉操作。 + +```src +[file]{simple_hash}-[class]{}-[func]{rot_hash} +``` + +觀察發現,每種雜湊演算法的最後一步都是對大質數 $1000000007$ 取模,以確保雜湊值在合適的範圍內。值得思考的是,為什麼要強調對質數取模,或者說對合數取模的弊端是什麼?這是一個有趣的問題。 + +先給出結論:**使用大質數作為模數,可以最大化地保證雜湊值的均勻分佈**。因為質數不與其他數字存在公約數,可以減少因取模操作而產生的週期性模式,從而避免雜湊衝突。 + +舉個例子,假設我們選擇合數 $9$ 作為模數,它可以被 $3$ 整除,那麼所有可以被 $3$ 整除的 `key` 都會被對映到 $0$、$3$、$6$ 這三個雜湊值。 + +$$ +\begin{aligned} +\text{modulus} & = 9 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} +\end{aligned} +$$ + +如果輸入 `key` 恰好滿足這種等差數列的資料分佈,那麼雜湊值就會出現聚堆積,從而加重雜湊衝突。現在,假設將 `modulus` 替換為質數 $13$ ,由於 `key` 和 `modulus` 之間不存在公約數,因此輸出的雜湊值的均勻性會明顯提升。 + +$$ +\begin{aligned} +\text{modulus} & = 13 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} +\end{aligned} +$$ + +值得說明的是,如果能夠保證 `key` 是隨機均勻分佈的,那麼選擇質數或者合數作為模數都可以,它們都能輸出均勻分佈的雜湊值。而當 `key` 的分佈存在某種週期性時,對合數取模更容易出現聚集現象。 + +總而言之,我們通常選取質數作為模數,並且這個質數最好足夠大,以儘可能消除週期性模式,提升雜湊演算法的穩健性。 + +## 常見雜湊演算法 + +不難發現,以上介紹的簡單雜湊演算法都比較“脆弱”,遠遠沒有達到雜湊演算法的設計目標。例如,由於加法和互斥或滿足交換律,因此加法雜湊和互斥或雜湊無法區分內容相同但順序不同的字串,這可能會加劇雜湊衝突,並引起一些安全問題。 + +在實際中,我們通常會用一些標準雜湊演算法,例如 MD5、SHA-1、SHA-2 和 SHA-3 等。它們可以將任意長度的輸入資料對映到恆定長度的雜湊值。 + +近一個世紀以來,雜湊演算法處在不斷升級與最佳化的過程中。一部分研究人員努力提升雜湊演算法的效能,另一部分研究人員和駭客則致力於尋找雜湊演算法的安全性問題。下表展示了在實際應用中常見的雜湊演算法。 + +- MD5 和 SHA-1 已多次被成功攻擊,因此它們被各類安全應用棄用。 +- SHA-2 系列中的 SHA-256 是最安全的雜湊演算法之一,仍未出現成功的攻擊案例,因此常用在各類安全應用與協議中。 +- SHA-3 相較 SHA-2 的實現開銷更低、計算效率更高,但目前使用覆蓋度不如 SHA-2 系列。 + +

  常見的雜湊演算法

+ +| | MD5 | SHA-1 | SHA-2 | SHA-3 | +| -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | +| 推出時間 | 1992 | 1995 | 2002 | 2008 | +| 輸出長度 | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | +| 雜湊衝突 | 較多 | 較多 | 很少 | 很少 | +| 安全等級 | 低,已被成功攻擊 | 低,已被成功攻擊 | 高 | 高 | +| 應用 | 已被棄用,仍用於資料完整性檢查 | 已被棄用 | 加密貨幣交易驗證、數字簽名等 | 可用於替代 SHA-2 | + +## 資料結構的雜湊值 + +我們知道,雜湊表的 `key` 可以是整數、小數或字串等資料型別。程式語言通常會為這些資料型別提供內建的雜湊演算法,用於計算雜湊表中的桶索引。以 Python 為例,我們可以呼叫 `hash()` 函式來計算各種資料型別的雜湊值。 + +- 整數和布林量的雜湊值就是其本身。 +- 浮點數和字串的雜湊值計算較為複雜,有興趣的讀者請自行學習。 +- 元組的雜湊值是對其中每一個元素進行雜湊,然後將這些雜湊值組合起來,得到單一的雜湊值。 +- 物件的雜湊值基於其記憶體位址生成。透過重寫物件的雜湊方法,可實現基於內容生成雜湊值。 + +!!! tip + + 請注意,不同程式語言的內建雜湊值計算函式的定義和方法不同。 + +=== "Python" + + ```python title="built_in_hash.py" + num = 3 + hash_num = hash(num) + # 整數 3 的雜湊值為 3 + + bol = True + hash_bol = hash(bol) + # 布林量 True 的雜湊值為 1 + + dec = 3.14159 + hash_dec = hash(dec) + # 小數 3.14159 的雜湊值為 326484311674566659 + + str = "Hello 演算法" + hash_str = hash(str) + # 字串“Hello 演算法”的雜湊值為 4617003410720528961 + + tup = (12836, "小哈") + hash_tup = hash(tup) + # 元組 (12836, '小哈') 的雜湊值為 1029005403108185979 + + obj = ListNode(0) + hash_obj = hash(obj) + # 節點物件 的雜湊值為 274267521 + ``` + +=== "C++" + + ```cpp title="built_in_hash.cpp" + int num = 3; + size_t hashNum = hash()(num); + // 整數 3 的雜湊值為 3 + + bool bol = true; + size_t hashBol = hash()(bol); + // 布林量 1 的雜湊值為 1 + + double dec = 3.14159; + size_t hashDec = hash()(dec); + // 小數 3.14159 的雜湊值為 4614256650576692846 + + string str = "Hello 演算法"; + size_t hashStr = hash()(str); + // 字串“Hello 演算法”的雜湊值為 15466937326284535026 + + // 在 C++ 中,內建 std:hash() 僅提供基本資料型別的雜湊值計算 + // 陣列、物件的雜湊值計算需要自行實現 + ``` + +=== "Java" + + ```java title="built_in_hash.java" + int num = 3; + int hashNum = Integer.hashCode(num); + // 整數 3 的雜湊值為 3 + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + // 布林量 true 的雜湊值為 1231 + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + // 小數 3.14159 的雜湊值為 -1340954729 + + String str = "Hello 演算法"; + int hashStr = str.hashCode(); + // 字串“Hello 演算法”的雜湊值為 -727081396 + + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + // 陣列 [12836, 小哈] 的雜湊值為 1151158 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + // 節點物件 utils.ListNode@7dc5e7b4 的雜湊值為 2110121908 + ``` + +=== "C#" + + ```csharp title="built_in_hash.cs" + int num = 3; + int hashNum = num.GetHashCode(); + // 整數 3 的雜湊值為 3; + + bool bol = true; + int hashBol = bol.GetHashCode(); + // 布林量 true 的雜湊值為 1; + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + // 小數 3.14159 的雜湊值為 -1340954729; + + string str = "Hello 演算法"; + int hashStr = str.GetHashCode(); + // 字串“Hello 演算法”的雜湊值為 -586107568; + + object[] arr = [12836, "小哈"]; + int hashTup = arr.GetHashCode(); + // 陣列 [12836, 小哈] 的雜湊值為 42931033; + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + // 節點物件 0 的雜湊值為 39053774; + ``` + +=== "Go" + + ```go title="built_in_hash.go" + // Go 未提供內建 hash code 函式 + ``` + +=== "Swift" + + ```swift title="built_in_hash.swift" + let num = 3 + let hashNum = num.hashValue + // 整數 3 的雜湊值為 9047044699613009734 + + let bol = true + let hashBol = bol.hashValue + // 布林量 true 的雜湊值為 -4431640247352757451 + + let dec = 3.14159 + let hashDec = dec.hashValue + // 小數 3.14159 的雜湊值為 -2465384235396674631 + + let str = "Hello 演算法" + let hashStr = str.hashValue + // 字串“Hello 演算法”的雜湊值為 -7850626797806988787 + + let arr = [AnyHashable(12836), AnyHashable("小哈")] + let hashTup = arr.hashValue + // 陣列 [AnyHashable(12836), AnyHashable("小哈")] 的雜湊值為 -2308633508154532996 + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + // 節點物件 utils.ListNode 的雜湊值為 -2434780518035996159 + ``` + +=== "JS" + + ```javascript title="built_in_hash.js" + // JavaScript 未提供內建 hash code 函式 + ``` + +=== "TS" + + ```typescript title="built_in_hash.ts" + // TypeScript 未提供內建 hash code 函式 + ``` + +=== "Dart" + + ```dart title="built_in_hash.dart" + int num = 3; + int hashNum = num.hashCode; + // 整數 3 的雜湊值為 34803 + + bool bol = true; + int hashBol = bol.hashCode; + // 布林值 true 的雜湊值為 1231 + + double dec = 3.14159; + int hashDec = dec.hashCode; + // 小數 3.14159 的雜湊值為 2570631074981783 + + String str = "Hello 演算法"; + int hashStr = str.hashCode; + // 字串“Hello 演算法”的雜湊值為 468167534 + + List arr = [12836, "小哈"]; + int hashArr = arr.hashCode; + // 陣列 [12836, 小哈] 的雜湊值為 976512528 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + // 節點物件 Instance of 'ListNode' 的雜湊值為 1033450432 + ``` + +=== "Rust" + + ```rust title="built_in_hash.rs" + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + // 整數 3 的雜湊值為 568126464209439262 + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + // 布林量 true 的雜湊值為 4952851536318644461 + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + // 小數 3.14159 的雜湊值為 2566941990314602357 + + let str = "Hello 演算法"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + // 字串“Hello 演算法”的雜湊值為 16092673739211250988 + + let arr = (&12836, &"小哈"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + // 元組 (12836, "小哈") 的雜湊值為 1885128010422702749 + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + // 節點物件 RefCell { value: ListNode { val: 42, next: None } } 的雜湊值為15387811073369036852 + ``` + +=== "C" + + ```c title="built_in_hash.c" + // C 未提供內建 hash code 函式 + ``` + +=== "Kotlin" + + ```kotlin title="built_in_hash.kt" + val num = 3 + val hashNum = num.hashCode() + // 整數 3 的雜湊值為 3 + + val bol = true + val hashBol = bol.hashCode() + // 布林量 true 的雜湊值為 1231 + + val dec = 3.14159 + val hashDec = dec.hashCode() + // 小數 3.14159 的雜湊值為 -1340954729 + + val str = "Hello 演算法" + val hashStr = str.hashCode() + // 字串“Hello 演算法”的雜湊值為 -727081396 + + val arr = arrayOf(12836, "小哈") + val hashTup = arr.hashCode() + // 陣列 [12836, 小哈] 的雜湊值為 189568618 + + val obj = ListNode(0) + val hashObj = obj.hashCode() + // 節點物件 utils.ListNode@1d81eb93 的雜湊值為 495053715 + ``` + +=== "Ruby" + + ```ruby title="built_in_hash.rb" + num = 3 + hash_num = num.hash + # 整數 3 的雜湊值為 -4385856518450339636 + + bol = true + hash_bol = bol.hash + # 布林量 true 的雜湊值為 -1617938112149317027 + + dec = 3.14159 + hash_dec = dec.hash + # 小數 3.14159 的雜湊值為 -1479186995943067893 + + str = "Hello 演算法" + hash_str = str.hash + # 字串“Hello 演算法”的雜湊值為 -4075943250025831763 + + tup = [12836, '小哈'] + hash_tup = tup.hash + # 元組 (12836, '小哈') 的雜湊值為 1999544809202288822 + + obj = ListNode.new(0) + hash_obj = obj.hash + # 節點物件 # 的雜湊值為 4302940560806366381 + ``` + +=== "Zig" + + ```zig title="built_in_hash.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B9%BC%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B8%203%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E6%9E%97%E9%87%8F%20True%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B8%203.14159%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E6%BC%94%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E4%B8%B2%E2%80%9CHello%20%E6%BC%94%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836%2C%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%B5%84%20%2812836%2C%20%27%E5%B0%8F%E5%93%88%27%29%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E7%89%A9%E4%BB%B6%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E9%9B%9C%E6%B9%8A%E5%80%BC%E7%82%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +在許多程式語言中,**只有不可變物件才可作為雜湊表的 `key`** 。假如我們將串列(動態陣列)作為 `key` ,當串列的內容發生變化時,它的雜湊值也隨之改變,我們就無法在雜湊表中查詢到原先的 `value` 了。 + +雖然自定義物件(比如鏈結串列節點)的成員變數是可變的,但它是可雜湊的。**這是因為物件的雜湊值通常是基於記憶體位址生成的**,即使物件的內容發生了變化,但它的記憶體位址不變,雜湊值仍然是不變的。 + +細心的你可能發現在不同控制檯中執行程式時,輸出的雜湊值是不同的。**這是因為 Python 直譯器在每次啟動時,都會為字串雜湊函式加入一個隨機的鹽(salt)值**。這種做法可以有效防止 HashDoS 攻擊,提升雜湊演算法的安全性。 diff --git a/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png new file mode 100644 index 0000000000..f8dd2f3592 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png new file mode 100644 index 0000000000..895fb6881a Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png new file mode 100644 index 0000000000..cac56df8d3 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_collision.md b/zh-hant/docs/chapter_hashing/hash_collision.md new file mode 100644 index 0000000000..1939cd24c1 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/hash_collision.md @@ -0,0 +1,108 @@ +# 雜湊衝突 + +上一節提到,**通常情況下雜湊函式的輸入空間遠大於輸出空間**,因此理論上雜湊衝突是不可避免的。比如,輸入空間為全體整數,輸出空間為陣列容量大小,則必然有多個整數對映至同一桶索引。 + +雜湊衝突會導致查詢結果錯誤,嚴重影響雜湊表的可用性。為了解決該問題,每當遇到雜湊衝突時,我們就進行雜湊表擴容,直至衝突消失為止。此方法簡單粗暴且有效,但效率太低,因為雜湊表擴容需要進行大量的資料搬運與雜湊值計算。為了提升效率,我們可以採用以下策略。 + +1. 改良雜湊表資料結構,**使得雜湊表可以在出現雜湊衝突時正常工作**。 +2. 僅在必要時,即當雜湊衝突比較嚴重時,才執行擴容操作。 + +雜湊表的結構改良方法主要包括“鏈式位址”和“開放定址”。 + +## 鏈式位址 + +在原始雜湊表中,每個桶僅能儲存一個鍵值對。鏈式位址(separate chaining)將單個元素轉換為鏈結串列,將鍵值對作為鏈結串列節點,將所有發生衝突的鍵值對都儲存在同一鏈結串列中。下圖展示了一個鏈式位址雜湊表的例子。 + +![鏈式位址雜湊表](hash_collision.assets/hash_table_chaining.png) + +基於鏈式位址實現的雜湊表的操作方法發生了以下變化。 + +- **查詢元素**:輸入 `key` ,經過雜湊函式得到桶索引,即可訪問鏈結串列頭節點,然後走訪鏈結串列並對比 `key` 以查詢目標鍵值對。 +- **新增元素**:首先透過雜湊函式訪問鏈結串列頭節點,然後將節點(鍵值對)新增到鏈結串列中。 +- **刪除元素**:根據雜湊函式的結果訪問鏈結串列頭部,接著走訪鏈結串列以查詢目標節點並將其刪除。 + +鏈式位址存在以下侷限性。 + +- **佔用空間增大**:鏈結串列包含節點指標,它相比陣列更加耗費記憶體空間。 +- **查詢效率降低**:因為需要線性走訪鏈結串列來查詢對應元素。 + +以下程式碼給出了鏈式位址雜湊表的簡單實現,需要注意兩點。 + +- 使用串列(動態陣列)代替鏈結串列,從而簡化程式碼。在這種設定下,雜湊表(陣列)包含多個桶,每個桶都是一個串列。 +- 以下實現包含雜湊表擴容方法。當負載因子超過 $\frac{2}{3}$ 時,我們將雜湊表擴容至原先的 $2$ 倍。 + +```src +[file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} +``` + +值得注意的是,當鏈結串列很長時,查詢效率 $O(n)$ 很差。**此時可以將鏈結串列轉換為“AVL 樹”或“紅黑樹”**,從而將查詢操作的時間複雜度最佳化至 $O(\log n)$ 。 + +## 開放定址 + +開放定址(open addressing)不引入額外的資料結構,而是透過“多次探測”來處理雜湊衝突,探測方式主要包括線性探查、平方探測和多次雜湊等。 + +下面以線性探查為例,介紹開放定址雜湊表的工作機制。 + +### 線性探查 + +線性探查採用固定步長的線性搜尋來進行探測,其操作方法與普通雜湊表有所不同。 + +- **插入元素**:透過雜湊函式計算桶索引,若發現桶內已有元素,則從衝突位置向後線性走訪(步長通常為 $1$ ),直至找到空桶,將元素插入其中。 +- **查詢元素**:若發現雜湊衝突,則使用相同步長向後進行線性走訪,直到找到對應元素,返回 `value` 即可;如果遇到空桶,說明目標元素不在雜湊表中,返回 `None` 。 + +下圖展示了開放定址(線性探查)雜湊表的鍵值對分佈。根據此雜湊函式,最後兩位相同的 `key` 都會被對映到相同的桶。而透過線性探查,它們被依次儲存在該桶以及之下的桶中。 + +![開放定址(線性探查)雜湊表的鍵值對分佈](hash_collision.assets/hash_table_linear_probing.png) + +然而,**線性探查容易產生“聚集現象”**。具體來說,陣列中連續被佔用的位置越長,這些連續位置發生雜湊衝突的可能性越大,從而進一步促使該位置的聚堆積生長,形成惡性迴圈,最終導致增刪查改操作效率劣化。 + +值得注意的是,**我們不能在開放定址雜湊表中直接刪除元素**。這是因為刪除元素會在陣列內產生一個空桶 `None` ,而當查詢元素時,線性探查到該空桶就會返回,因此在該空桶之下的元素都無法再被訪問到,程式可能誤判這些元素不存在,如下圖所示。 + +![在開放定址中刪除元素導致的查詢問題](hash_collision.assets/hash_table_open_addressing_deletion.png) + +為了解決該問題,我們可以採用懶刪除(lazy deletion)機制:它不直接從雜湊表中移除元素,**而是利用一個常數 `TOMBSTONE` 來標記這個桶**。在該機制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置鍵值對。但不同的是,線性探查到 `TOMBSTONE` 時應該繼續走訪,因為其之下可能還存在鍵值對。 + +然而,**懶刪除可能會加速雜湊表的效能退化**。這是因為每次刪除操作都會產生一個刪除標記,隨著 `TOMBSTONE` 的增加,搜尋時間也會增加,因為線性探查可能需要跳過多個 `TOMBSTONE` 才能找到目標元素。 + +為此,考慮在線性探查中記錄遇到的首個 `TOMBSTONE` 的索引,並將搜尋到的目標元素與該 `TOMBSTONE` 交換位置。這樣做的好處是當每次查詢或新增元素時,元素會被移動至距離理想位置(探測起始點)更近的桶,從而最佳化查詢效率。 + +以下程式碼實現了一個包含懶刪除的開放定址(線性探查)雜湊表。為了更加充分地使用雜湊表的空間,我們將雜湊表看作一個“環形陣列”,當越過陣列尾部時,回到頭部繼續走訪。 + +```src +[file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} +``` + +### 平方探測 + +平方探測與線性探查類似,都是開放定址的常見策略之一。當發生衝突時,平方探測不是簡單地跳過一個固定的步數,而是跳過“探測次數的平方”的步數,即 $1, 4, 9, \dots$ 步。 + +平方探測主要具有以下優勢。 + +- 平方探測透過跳過探測次數平方的距離,試圖緩解線性探查的聚集效應。 +- 平方探測會跳過更大的距離來尋找空位置,有助於資料分佈得更加均勻。 + +然而,平方探測並不是完美的。 + +- 仍然存在聚集現象,即某些位置比其他位置更容易被佔用。 +- 由於平方的增長,平方探測可能不會探測整個雜湊表,這意味著即使雜湊表中有空桶,平方探測也可能無法訪問到它。 + +### 多次雜湊 + +顧名思義,多次雜湊方法使用多個雜湊函式 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 進行探測。 + +- **插入元素**:若雜湊函式 $f_1(x)$ 出現衝突,則嘗試 $f_2(x)$ ,以此類推,直到找到空位後插入元素。 +- **查詢元素**:在相同的雜湊函式順序下進行查詢,直到找到目標元素時返回;若遇到空位或已嘗試所有雜湊函式,說明雜湊表中不存在該元素,則返回 `None` 。 + +與線性探查相比,多次雜湊方法不易產生聚集,但多個雜湊函式會帶來額外的計算量。 + +!!! tip + + 請注意,開放定址(線性探查、平方探測和多次雜湊)雜湊表都存在“不能直接刪除元素”的問題。 + +## 程式語言的選擇 + +各種程式語言採取了不同的雜湊表實現策略,下面舉幾個例子。 + +- Python 採用開放定址。字典 `dict` 使用偽隨機數進行探測。 +- Java 採用鏈式位址。自 JDK 1.8 以來,當 `HashMap` 內陣列長度達到 64 且鏈結串列長度達到 8 時,鏈結串列會轉換為紅黑樹以提升查詢效能。 +- Go 採用鏈式位址。Go 規定每個桶最多儲存 8 個鍵值對,超出容量則連線一個溢位桶;當溢位桶過多時,會執行一次特殊的等量擴容操作,以確保效能。 diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_collision.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_collision.png new file mode 100644 index 0000000000..0301ef9aff Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_collision.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_function.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_function.png new file mode 100644 index 0000000000..6f7ff5f654 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_function.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png new file mode 100644 index 0000000000..c05342c71d Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png new file mode 100644 index 0000000000..9b226b72e2 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.md b/zh-hant/docs/chapter_hashing/hash_map.md new file mode 100755 index 0000000000..b785fe178c --- /dev/null +++ b/zh-hant/docs/chapter_hashing/hash_map.md @@ -0,0 +1,603 @@ +# 雜湊表 + +雜湊表(hash table),又稱散列表,它透過建立鍵 `key` 與值 `value` 之間的對映,實現高效的元素查詢。具體而言,我們向雜湊表中輸入一個鍵 `key` ,則可以在 $O(1)$ 時間內獲取對應的值 `value` 。 + +如下圖所示,給定 $n$ 個學生,每個學生都有“姓名”和“學號”兩項資料。假如我們希望實現“輸入一個學號,返回對應的姓名”的查詢功能,則可以採用下圖所示的雜湊表來實現。 + +![雜湊表的抽象表示](hash_map.assets/hash_table_lookup.png) + +除雜湊表外,陣列和鏈結串列也可以實現查詢功能,它們的效率對比如下表所示。 + +- **新增元素**:僅需將元素新增至陣列(鏈結串列)的尾部即可,使用 $O(1)$ 時間。 +- **查詢元素**:由於陣列(鏈結串列)是亂序的,因此需要走訪其中的所有元素,使用 $O(n)$ 時間。 +- **刪除元素**:需要先查詢到元素,再從陣列(鏈結串列)中刪除,使用 $O(n)$ 時間。 + +

  元素查詢效率對比

+ +| | 陣列 | 鏈結串列 | 雜湊表 | +| -------- | ------ | ------ | ------ | +| 查詢元素 | $O(n)$ | $O(n)$ | $O(1)$ | +| 新增元素 | $O(1)$ | $O(1)$ | $O(1)$ | +| 刪除元素 | $O(n)$ | $O(n)$ | $O(1)$ | + +觀察發現,**在雜湊表中進行增刪查改的時間複雜度都是 $O(1)$** ,非常高效。 + +## 雜湊表常用操作 + +雜湊表的常見操作包括:初始化、查詢操作、新增鍵值對和刪除鍵值對等,示例程式碼如下: + +=== "Python" + + ```python title="hash_map.py" + # 初始化雜湊表 + hmap: dict = {} + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name: str = hmap[15937] + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hmap.pop(10583) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* 初始化雜湊表 */ + unordered_map map; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.erase(10583); + ``` + +=== "Java" + + ```java title="hash_map.java" + /* 初始化雜湊表 */ + Map map = new HashMap<>(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map.get(15937); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* 初始化雜湊表 */ + Dictionary map = new() { + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + { 12836, "小哈" }, + { 15937, "小囉" }, + { 16750, "小算" }, + { 13276, "小法" }, + { 10583, "小鴨" } + }; + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(10583); + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* 初始化雜湊表 */ + hmap := make(map[int]string) + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap[15937] + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + delete(hmap, 10583) + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* 初始化雜湊表 */ + var map: [Int: String] = [:] + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map[15937]! + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.removeValue(forKey: 10583) + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* 初始化雜湊表 */ + const map = new Map(); + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.set(12836, '小哈'); + map.set(15937, '小囉'); + map.set(16750, '小算'); + map.set(13276, '小法'); + map.set(10583, '小鴨'); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(15937); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.delete(10583); + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* 初始化雜湊表 */ + const map = new Map(); + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.set(12836, '小哈'); + map.set(15937, '小囉'); + map.set(16750, '小算'); + map.set(13276, '小法'); + map.set(10583, '小鴨'); + console.info('\n新增完成後,雜湊表為\nKey -> Value'); + console.info(map); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(15937); + console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.delete(10583); + console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); + console.info(map); + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* 初始化雜湊表 */ + Map map = {}; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map[15937]; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + use std::collections::HashMap; + + /* 初始化雜湊表 */ + let mut map: HashMap = HashMap::new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.insert(12836, "小哈".to_string()); + map.insert(15937, "小囉".to_string()); + map.insert(16750, "小算".to_string()); + map.insert(13279, "小法".to_string()); + map.insert(10583, "小鴨".to_string()); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let _name: Option<&String> = map.get(&15937); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + let _removed_value: Option = map.remove(&10583); + ``` + +=== "C" + + ```c title="hash_map.c" + // C 未提供內建雜湊表 + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + /* 初始化雜湊表 */ + val map = HashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name = map[15937] + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583) + ``` + +=== "Ruby" + + ```ruby title="hash_map.rb" + # 初始化雜湊表 + hmap = {} + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name = hmap[15937] + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hmap.delete(10583) + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E6%96%B0%E5%A2%9E%E9%8D%B5%E5%80%BC%E5%B0%8D%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%9B%89%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B4%A8%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%A9%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E8%BC%B8%E5%85%A5%E9%8D%B5%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E5%88%AA%E9%99%A4%E9%8D%B5%E5%80%BC%E5%B0%8D%20%28key%2C%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +雜湊表有三種常用的走訪方式:走訪鍵值對、走訪鍵和走訪值。示例程式碼如下: + +=== "Python" + + ```python title="hash_map.py" + # 走訪雜湊表 + # 走訪鍵值對 key->value + for key, value in hmap.items(): + print(key, "->", value) + # 單獨走訪鍵 key + for key in hmap.keys(): + print(key) + # 單獨走訪值 value + for value in hmap.values(): + print(value) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for (auto kv: map) { + cout << kv.first << " -> " << kv.second << endl; + } + // 使用迭代器走訪 key->value + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + ``` + +=== "Java" + + ```java title="hash_map.java" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for (Map.Entry kv: map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + // 單獨走訪鍵 key + for (int key: map.keySet()) { + System.out.println(key); + } + // 單獨走訪值 value + for (String val: map.values()) { + System.out.println(val); + } + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + // 單獨走訪鍵 key + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + // 單獨走訪值 value + foreach (string val in map.Values) { + Console.WriteLine(val); + } + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // 單獨走訪鍵 key + for key := range hmap { + fmt.Println(key) + } + // 單獨走訪值 value + for _, value := range hmap { + fmt.Println(value) + } + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + for (key, value) in map { + print("\(key) -> \(value)") + } + // 單獨走訪鍵 Key + for key in map.keys { + print(key) + } + // 單獨走訪值 Value + for value in map.values { + print(value) + } + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* 走訪雜湊表 */ + console.info('\n走訪鍵值對 Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\n單獨走訪鍵 Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\n單獨走訪值 Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* 走訪雜湊表 */ + console.info('\n走訪鍵值對 Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\n單獨走訪鍵 Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\n單獨走訪值 Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + map.forEach((key, value) { + print('$key -> $value'); + }); + + // 單獨走訪鍵 Key + map.keys.forEach((key) { + print(key); + }); + + // 單獨走訪值 Value + map.values.forEach((value) { + print(value); + }); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + for (key, value) in &map { + println!("{key} -> {value}"); + } + + // 單獨走訪鍵 Key + for key in map.keys() { + println!("{key}"); + } + + // 單獨走訪值 Value + for value in map.values() { + println!("{value}"); + } + ``` + +=== "C" + + ```c title="hash_map.c" + // C 未提供內建雜湊表 + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for ((key, value) in map) { + println("$key -> $value") + } + // 單獨走訪鍵 key + for (key in map.keys) { + println(key) + } + // 單獨走訪值 value + for (_val in map.values) { + println(_val) + } + ``` + +=== "Ruby" + + ```ruby title="hash_map.rb" + # 走訪雜湊表 + # 走訪鍵值對 key->value + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } + + # 單獨走訪鍵 key + hmap.keys.each { |key| puts key } + + # 單獨走訪值 value + hmap.values.each { |val| puts val } + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%96%B0%E5%A2%9E%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E9%9B%9C%E6%B9%8A%E8%A1%A8%E4%B8%AD%E6%96%B0%E5%A2%9E%E9%8D%B5%E5%80%BC%E5%B0%8D%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%9B%89%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B4%A8%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%9B%9C%E6%B9%8A%E8%A1%A8%0A%20%20%20%20%23%20%E8%B5%B0%E8%A8%AA%E9%8D%B5%E5%80%BC%E5%B0%8D%20key-%3Evalue%0A%20%20%20%20for%20key%2C%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%2C%20%22-%3E%22%2C%20value%29%0A%20%20%20%20%23%20%E5%96%AE%E7%8D%A8%E8%B5%B0%E8%A8%AA%E9%8D%B5%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%96%AE%E7%8D%A8%E8%B5%B0%E8%A8%AA%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 雜湊表簡單實現 + +我們先考慮最簡單的情況,**僅用一個陣列來實現雜湊表**。在雜湊表中,我們將陣列中的每個空位稱為桶(bucket),每個桶可儲存一個鍵值對。因此,查詢操作就是找到 `key` 對應的桶,並在桶中獲取 `value` 。 + +那麼,如何基於 `key` 定位對應的桶呢?這是透過雜湊函式(hash function)實現的。雜湊函式的作用是將一個較大的輸入空間對映到一個較小的輸出空間。在雜湊表中,輸入空間是所有 `key` ,輸出空間是所有桶(陣列索引)。換句話說,輸入一個 `key` ,**我們可以透過雜湊函式得到該 `key` 對應的鍵值對在陣列中的儲存位置**。 + +輸入一個 `key` ,雜湊函式的計算過程分為以下兩步。 + +1. 透過某種雜湊演算法 `hash()` 計算得到雜湊值。 +2. 將雜湊值對桶數量(陣列長度)`capacity` 取模,從而獲取該 `key` 對應的陣列索引 `index` 。 + +```shell +index = hash(key) % capacity +``` + +隨後,我們就可以利用 `index` 在雜湊表中訪問對應的桶,從而獲取 `value` 。 + +設陣列長度 `capacity = 100`、雜湊演算法 `hash(key) = key` ,易得雜湊函式為 `key % 100` 。下圖以 `key` 學號和 `value` 姓名為例,展示了雜湊函式的工作原理。 + +![雜湊函式工作原理](hash_map.assets/hash_function.png) + +以下程式碼實現了一個簡單雜湊表。其中,我們將 `key` 和 `value` 封裝成一個類別 `Pair` ,以表示鍵值對。 + +```src +[file]{array_hash_map}-[class]{array_hash_map}-[func]{} +``` + +## 雜湊衝突與擴容 + +從本質上看,雜湊函式的作用是將所有 `key` 構成的輸入空間對映到陣列所有索引構成的輸出空間,而輸入空間往往遠大於輸出空間。因此,**理論上一定存在“多個輸入對應相同輸出”的情況**。 + +對於上述示例中的雜湊函式,當輸入的 `key` 後兩位相同時,雜湊函式的輸出結果也相同。例如,查詢學號為 12836 和 20336 的兩個學生時,我們得到: + +```shell +12836 % 100 = 36 +20336 % 100 = 36 +``` + +如下圖所示,兩個學號指向了同一個姓名,這顯然是不對的。我們將這種多個輸入對應同一輸出的情況稱為雜湊衝突(hash collision)。 + +![雜湊衝突示例](hash_map.assets/hash_collision.png) + +容易想到,雜湊表容量 $n$ 越大,多個 `key` 被分配到同一個桶中的機率就越低,衝突就越少。因此,**我們可以透過擴容雜湊表來減少雜湊衝突**。 + +如下圖所示,擴容前鍵值對 `(136, A)` 和 `(236, D)` 發生衝突,擴容後衝突消失。 + +![雜湊表擴容](hash_map.assets/hash_table_reshash.png) + +類似於陣列擴容,雜湊表擴容需將所有鍵值對從原雜湊表遷移至新雜湊表,非常耗時;並且由於雜湊表容量 `capacity` 改變,我們需要透過雜湊函式來重新計算所有鍵值對的儲存位置,這進一步增加了擴容過程的計算開銷。為此,程式語言通常會預留足夠大的雜湊表容量,防止頻繁擴容。 + +負載因子(load factor)是雜湊表的一個重要概念,其定義為雜湊表的元素數量除以桶數量,用於衡量雜湊衝突的嚴重程度,**也常作為雜湊表擴容的觸發條件**。例如在 Java 中,當負載因子超過 $0.75$ 時,系統會將雜湊表擴容至原先的 $2$ 倍。 diff --git a/zh-hant/docs/chapter_hashing/index.md b/zh-hant/docs/chapter_hashing/index.md new file mode 100644 index 0000000000..f1f2fa24e1 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/index.md @@ -0,0 +1,9 @@ +# 雜湊表 + +![雜湊表](../assets/covers/chapter_hashing.jpg) + +!!! abstract + + 在計算機世界中,雜湊表如同一位聰慧的圖書管理員。 + + 他知道如何計算索書號,從而可以快速找到目標圖書。 diff --git a/zh-hant/docs/chapter_hashing/summary.md b/zh-hant/docs/chapter_hashing/summary.md new file mode 100644 index 0000000000..c80102b9d8 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/summary.md @@ -0,0 +1,47 @@ +# 小結 + +### 重點回顧 + +- 輸入 `key` ,雜湊表能夠在 $O(1)$ 時間內查詢到 `value` ,效率非常高。 +- 常見的雜湊表操作包括查詢、新增鍵值對、刪除鍵值對和走訪雜湊表等。 +- 雜湊函式將 `key` 對映為陣列索引,從而訪問對應桶並獲取 `value` 。 +- 兩個不同的 `key` 可能在經過雜湊函式後得到相同的陣列索引,導致查詢結果出錯,這種現象被稱為雜湊衝突。 +- 雜湊表容量越大,雜湊衝突的機率就越低。因此可以透過擴容雜湊表來緩解雜湊衝突。與陣列擴容類似,雜湊表擴容操作的開銷很大。 +- 負載因子定義為雜湊表中元素數量除以桶數量,反映了雜湊衝突的嚴重程度,常用作觸發雜湊表擴容的條件。 +- 鏈式位址透過將單個元素轉化為鏈結串列,將所有衝突元素儲存在同一個鏈結串列中。然而,鏈結串列過長會降低查詢效率,可以透過進一步將鏈結串列轉換為紅黑樹來提高效率。 +- 開放定址透過多次探測來處理雜湊衝突。線性探查使用固定步長,缺點是不能刪除元素,且容易產生聚集。多次雜湊使用多個雜湊函式進行探測,相較線性探查更不易產生聚集,但多個雜湊函式增加了計算量。 +- 不同程式語言採取了不同的雜湊表實現。例如,Java 的 `HashMap` 使用鏈式位址,而 Python 的 `Dict` 採用開放定址。 +- 在雜湊表中,我們希望雜湊演算法具有確定性、高效率和均勻分佈的特點。在密碼學中,雜湊演算法還應該具備抗碰撞性和雪崩效應。 +- 雜湊演算法通常採用大質數作為模數,以最大化地保證雜湊值均勻分佈,減少雜湊衝突。 +- 常見的雜湊演算法包括 MD5、SHA-1、SHA-2 和 SHA-3 等。MD5 常用於校驗檔案完整性,SHA-2 常用於安全應用與協議。 +- 程式語言通常會為資料型別提供內建雜湊演算法,用於計算雜湊表中的桶索引。通常情況下,只有不可變物件是可雜湊的。 + +### Q & A + +**Q**:雜湊表的時間複雜度在什麼情況下是 $O(n)$ ? + +當雜湊衝突比較嚴重時,雜湊表的時間複雜度會退化至 $O(n)$ 。當雜湊函式設計得比較好、容量設定比較合理、衝突比較平均時,時間複雜度是 $O(1)$ 。我們使用程式語言內建的雜湊表時,通常認為時間複雜度是 $O(1)$ 。 + +**Q**:為什麼不使用雜湊函式 $f(x) = x$ 呢?這樣就不會有衝突了。 + +在 $f(x) = x$ 雜湊函式下,每個元素對應唯一的桶索引,這與陣列等價。然而,輸入空間通常遠大於輸出空間(陣列長度),因此雜湊函式的最後一步往往是對陣列長度取模。換句話說,雜湊表的目標是將一個較大的狀態空間對映到一個較小的空間,並提供 $O(1)$ 的查詢效率。 + +**Q**:雜湊表底層實現是陣列、鏈結串列、二元樹,但為什麼效率可以比它們更高呢? + +首先,雜湊表的時間效率變高,但空間效率變低了。雜湊表有相當一部分記憶體未使用。 + +其次,只是在特定使用場景下時間效率變高了。如果一個功能能夠在相同的時間複雜度下使用陣列或鏈結串列實現,那麼通常比雜湊表更快。這是因為雜湊函式計算需要開銷,時間複雜度的常數項更大。 + +最後,雜湊表的時間複雜度可能發生劣化。例如在鏈式位址中,我們採取在鏈結串列或紅黑樹中執行查詢操作,仍然有退化至 $O(n)$ 時間的風險。 + +**Q**:多次雜湊有不能直接刪除元素的缺陷嗎?標記為已刪除的空間還能再次使用嗎? + +多次雜湊是開放定址的一種,開放定址法都有不能直接刪除元素的缺陷,需要透過標記刪除。標記為已刪除的空間可以再次使用。當將新元素插入雜湊表,並且透過雜湊函式找到標記為已刪除的位置時,該位置可以被新元素使用。這樣做既能保持雜湊表的探測序列不變,又能保證雜湊表的空間使用率。 + +**Q**:為什麼在線性探查中,查詢元素的時候會出現雜湊衝突呢? + +查詢的時候透過雜湊函式找到對應的桶和鍵值對,發現 `key` 不匹配,這就代表有雜湊衝突。因此,線性探查法會根據預先設定的步長依次向下查詢,直至找到正確的鍵值對或無法找到跳出為止。 + +**Q**:為什麼雜湊表擴容能夠緩解雜湊衝突? + +雜湊函式的最後一步往往是對陣列長度 $n$ 取模(取餘),讓輸出值落在陣列索引範圍內;在擴容後,陣列長度 $n$ 發生變化,而 `key` 對應的索引也可能發生變化。原先落在同一個桶的多個 `key` ,在擴容後可能會被分配到多個桶中,從而實現雜湊衝突的緩解。 diff --git a/zh-hant/docs/chapter_heap/build_heap.assets/heapify_operations_count.png b/zh-hant/docs/chapter_heap/build_heap.assets/heapify_operations_count.png new file mode 100644 index 0000000000..d66535520d Binary files /dev/null and b/zh-hant/docs/chapter_heap/build_heap.assets/heapify_operations_count.png differ diff --git a/zh-hant/docs/chapter_heap/build_heap.md b/zh-hant/docs/chapter_heap/build_heap.md new file mode 100644 index 0000000000..5b6c51049a --- /dev/null +++ b/zh-hant/docs/chapter_heap/build_heap.md @@ -0,0 +1,74 @@ +# 建堆積操作 + +在某些情況下,我們希望使用一個串列的所有元素來構建一個堆積,這個過程被稱為“建堆積操作”。 + +## 藉助入堆積操作實現 + +我們首先建立一個空堆積,然後走訪串列,依次對每個元素執行“入堆積操作”,即先將元素新增至堆積的尾部,再對該元素執行“從底至頂”堆積化。 + +每當一個元素入堆積,堆積的長度就加一。由於節點是從頂到底依次被新增進二元樹的,因此堆積是“自上而下”構建的。 + +設元素數量為 $n$ ,每個元素的入堆積操作使用 $O(\log{n})$ 時間,因此該建堆積方法的時間複雜度為 $O(n \log n)$ 。 + +## 透過走訪堆積化實現 + +實際上,我們可以實現一種更為高效的建堆積方法,共分為兩步。 + +1. 將串列所有元素原封不動地新增到堆積中,此時堆積的性質尚未得到滿足。 +2. 倒序走訪堆積(層序走訪的倒序),依次對每個非葉節點執行“從頂至底堆積化”。 + +**每當堆積化一個節點後,以該節點為根節點的子樹就形成一個合法的子堆積**。而由於是倒序走訪,因此堆積是“自下而上”構建的。 + +之所以選擇倒序走訪,是因為這樣能夠保證當前節點之下的子樹已經是合法的子堆積,這樣堆積化當前節點才是有效的。 + +值得說明的是,**由於葉節點沒有子節點,因此它們天然就是合法的子堆積,無須堆積化**。如以下程式碼所示,最後一個非葉節點是最後一個節點的父節點,我們從它開始倒序走訪並執行堆積化: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{__init__} +``` + +## 複雜度分析 + +下面,我們來嘗試推算第二種建堆積方法的時間複雜度。 + +- 假設完全二元樹的節點數量為 $n$ ,則葉節點數量為 $(n + 1) / 2$ ,其中 $/$ 為向下整除。因此需要堆積化的節點數量為 $(n - 1) / 2$ 。 +- 在從頂至底堆積化的過程中,每個節點最多堆積化到葉節點,因此最大迭代次數為二元樹高度 $\log n$ 。 + +將上述兩者相乘,可得到建堆積過程的時間複雜度為 $O(n \log n)$ 。**但這個估算結果並不準確,因為我們沒有考慮到二元樹底層節點數量遠多於頂層節點的性質**。 + +接下來我們來進行更為準確的計算。為了降低計算難度,假設給定一個節點數量為 $n$ 、高度為 $h$ 的“完美二元樹”,該假設不會影響計算結果的正確性。 + +![完美二元樹的各層節點數量](build_heap.assets/heapify_operations_count.png) + +如上圖所示,節點“從頂至底堆積化”的最大迭代次數等於該節點到葉節點的距離,而該距離正是“節點高度”。因此,我們可以對各層的“節點數量 $\times$ 節點高度”求和,**得到所有節點的堆積化迭代次數的總和**。 + +$$ +T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 +$$ + +化簡上式需要藉助中學的數列知識,先將 $T(h)$ 乘以 $2$ ,得到: + +$$ +\begin{aligned} +T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline +2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline +\end{aligned} +$$ + +使用錯位相減法,用下式 $2 T(h)$ 減去上式 $T(h)$ ,可得: + +$$ +2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h +$$ + +觀察上式,發現 $T(h)$ 是一個等比數列,可直接使用求和公式,得到時間複雜度為: + +$$ +\begin{aligned} +T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline +& = 2^{h+1} - h - 2 \newline +& = O(2^h) +\end{aligned} +$$ + +進一步,高度為 $h$ 的完美二元樹的節點數量為 $n = 2^{h+1} - 1$ ,易得複雜度為 $O(2^h) = O(n)$ 。以上推算表明,**輸入串列並建堆積的時間複雜度為 $O(n)$ ,非常高效**。 diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step1.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step1.png new file mode 100644 index 0000000000..a463650320 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step1.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step10.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step10.png new file mode 100644 index 0000000000..1b48802dcb Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step10.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step2.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step2.png new file mode 100644 index 0000000000..4c16c4af8e Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step2.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step3.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step3.png new file mode 100644 index 0000000000..75859bc15e Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step3.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step4.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step4.png new file mode 100644 index 0000000000..e68a264a64 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step4.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step5.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step5.png new file mode 100644 index 0000000000..7c1823d439 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step5.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step6.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step6.png new file mode 100644 index 0000000000..4cbe77a277 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step6.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step7.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step7.png new file mode 100644 index 0000000000..9c212207c3 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step7.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step8.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step8.png new file mode 100644 index 0000000000..128d72dc3d Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step8.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step9.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step9.png new file mode 100644 index 0000000000..f5728c2e52 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step9.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step1.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step1.png new file mode 100644 index 0000000000..8fe5a49818 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step1.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step2.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step2.png new file mode 100644 index 0000000000..2bdd7cd862 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step2.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step3.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step3.png new file mode 100644 index 0000000000..e648063902 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step3.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step4.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step4.png new file mode 100644 index 0000000000..e4d82cabf7 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step4.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step5.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step5.png new file mode 100644 index 0000000000..ca87ddbeb9 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step5.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step6.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step6.png new file mode 100644 index 0000000000..1c74a7aaa8 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step6.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step7.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step7.png new file mode 100644 index 0000000000..93ec1df5a1 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step7.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step8.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step8.png new file mode 100644 index 0000000000..7612c005aa Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step8.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step9.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step9.png new file mode 100644 index 0000000000..4ce6361650 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step9.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png b/zh-hant/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png new file mode 100644 index 0000000000..e3ece96abc Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/representation_of_heap.png b/zh-hant/docs/chapter_heap/heap.assets/representation_of_heap.png new file mode 100644 index 0000000000..37853ada59 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/representation_of_heap.png differ diff --git a/zh-hant/docs/chapter_heap/heap.md b/zh-hant/docs/chapter_heap/heap.md new file mode 100644 index 0000000000..00a321b4f3 --- /dev/null +++ b/zh-hant/docs/chapter_heap/heap.md @@ -0,0 +1,538 @@ +# 堆積 + +堆積(heap)是一種滿足特定條件的完全二元樹,主要可分為兩種型別,如下圖所示。 + +- 小頂堆積(min heap):任意節點的值 $\leq$ 其子節點的值。 +- 大頂堆積(max heap):任意節點的值 $\geq$ 其子節點的值。 + +![小頂堆積與大頂堆積](heap.assets/min_heap_and_max_heap.png) + +堆積作為完全二元樹的一個特例,具有以下特性。 + +- 最底層節點靠左填充,其他層的節點都被填滿。 +- 我們將二元樹的根節點稱為“堆積頂”,將底層最靠右的節點稱為“堆積底”。 +- 對於大頂堆積(小頂堆積),堆積頂元素(根節點)的值是最大(最小)的。 + +## 堆積的常用操作 + +需要指出的是,許多程式語言提供的是優先佇列(priority queue),這是一種抽象的資料結構,定義為具有優先順序排序的佇列。 + +實際上,**堆積通常用於實現優先佇列,大頂堆積相當於元素按從大到小的順序出列的優先佇列**。從使用角度來看,我們可以將“優先佇列”和“堆積”看作等價的資料結構。因此,本書對兩者不做特別區分,統一稱作“堆積”。 + +堆積的常用操作見下表,方法名需要根據程式語言來確定。 + +

  堆積的操作效率

+ +| 方法名 | 描述 | 時間複雜度 | +| ----------- | ------------------------------------------------ | ----------- | +| `push()` | 元素入堆積 | $O(\log n)$ | +| `pop()` | 堆積頂元素出堆積 | $O(\log n)$ | +| `peek()` | 訪問堆積頂元素(對於大 / 小頂堆積分別為最大 / 小值) | $O(1)$ | +| `size()` | 獲取堆積的元素數量 | $O(1)$ | +| `isEmpty()` | 判斷堆積是否為空 | $O(1)$ | + +在實際應用中,我們可以直接使用程式語言提供的堆積類別(或優先佇列類別)。 + +類似於排序演算法中的“從小到大排列”和“從大到小排列”,我們可以透過設定一個 `flag` 或修改 `Comparator` 實現“小頂堆積”與“大頂堆積”之間的轉換。程式碼如下所示: + +=== "Python" + + ```python title="heap.py" + # 初始化小頂堆積 + min_heap, flag = [], 1 + # 初始化大頂堆積 + max_heap, flag = [], -1 + + # Python 的 heapq 模組預設實現小頂堆積 + # 考慮將“元素取負”後再入堆積,這樣就可以將大小關係顛倒,從而實現大頂堆積 + # 在本示例中,flag = 1 時對應小頂堆積,flag = -1 時對應大頂堆積 + + # 元素入堆積 + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + # 獲取堆積頂元素 + peek: int = flag * max_heap[0] # 5 + + # 堆積頂元素出堆積 + # 出堆積元素會形成一個從大到小的序列 + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 + + # 獲取堆積大小 + size: int = len(max_heap) + + # 判斷堆積是否為空 + is_empty: bool = not max_heap + + # 輸入串列並建堆積 + min_heap: list[int] = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + ``` + +=== "C++" + + ```cpp title="heap.cpp" + /* 初始化堆積 */ + // 初始化小頂堆積 + priority_queue, greater> minHeap; + // 初始化大頂堆積 + priority_queue, less> maxHeap; + + /* 元素入堆積 */ + maxHeap.push(1); + maxHeap.push(3); + maxHeap.push(2); + maxHeap.push(5); + maxHeap.push(4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.top(); // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + maxHeap.pop(); // 5 + maxHeap.pop(); // 4 + maxHeap.pop(); // 3 + maxHeap.pop(); // 2 + maxHeap.pop(); // 1 + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.empty(); + + /* 輸入串列並建堆積 */ + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + ``` + +=== "Java" + + ```java title="heap.java" + /* 初始化堆積 */ + // 初始化小頂堆積 + Queue minHeap = new PriorityQueue<>(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + /* 元素入堆積 */ + maxHeap.offer(1); + maxHeap.offer(3); + maxHeap.offer(2); + maxHeap.offer(5); + maxHeap.offer(4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + peek = maxHeap.poll(); // 5 + peek = maxHeap.poll(); // 4 + peek = maxHeap.poll(); // 3 + peek = maxHeap.poll(); // 2 + peek = maxHeap.poll(); // 1 + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + + /* 判斷堆積是否為空 */ + boolean isEmpty = maxHeap.isEmpty(); + + /* 輸入串列並建堆積 */ + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + ``` + +=== "C#" + + ```csharp title="heap.cs" + /* 初始化堆積 */ + // 初始化小頂堆積 + PriorityQueue minHeap = new(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparer 即可) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); + + /* 元素入堆積 */ + maxHeap.Enqueue(1, 1); + maxHeap.Enqueue(3, 3); + maxHeap.Enqueue(2, 2); + maxHeap.Enqueue(5, 5); + maxHeap.Enqueue(4, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.Peek();//5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + peek = maxHeap.Dequeue(); // 5 + peek = maxHeap.Dequeue(); // 4 + peek = maxHeap.Dequeue(); // 3 + peek = maxHeap.Dequeue(); // 2 + peek = maxHeap.Dequeue(); // 1 + + /* 獲取堆積大小 */ + int size = maxHeap.Count; + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.Count == 0; + + /* 輸入串列並建堆積 */ + minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); + ``` + +=== "Go" + + ```go title="heap.go" + // Go 語言中可以透過實現 heap.Interface 來構建整數大頂堆積 + // 實現 heap.Interface 需要同時實現 sort.Interface + type intHeap []any + + // Push heap.Interface 的方法,實現推入元素到堆積 + func (h *intHeap) Push(x any) { + // Push 和 Pop 使用 pointer receiver 作為參數 + // 因為它們不僅會對切片的內容進行調整,還會修改切片的長度。 + *h = append(*h, x.(int)) + } + + // Pop heap.Interface 的方法,實現彈出堆積頂元素 + func (h *intHeap) Pop() any { + // 待出堆積元素存放在最後 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last + } + + // Len sort.Interface 的方法 + func (h *intHeap) Len() int { + return len(*h) + } + + // Less sort.Interface 的方法 + func (h *intHeap) Less(i, j int) bool { + // 如果實現小頂堆積,則需要調整為小於號 + return (*h)[i].(int) > (*h)[j].(int) + } + + // Swap sort.Interface 的方法 + func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + } + + // Top 獲取堆積頂元素 + func (h *intHeap) Top() any { + return (*h)[0] + } + + /* Driver Code */ + func TestHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 元素入堆積 */ + // 呼叫 heap.Interface 的方法,來新增元素 + heap.Push(maxHeap, 1) + heap.Push(maxHeap, 3) + heap.Push(maxHeap, 2) + heap.Push(maxHeap, 4) + heap.Push(maxHeap, 5) + + /* 獲取堆積頂元素 */ + top := maxHeap.Top() + fmt.Printf("堆積頂元素為 %d\n", top) + + /* 堆積頂元素出堆積 */ + // 呼叫 heap.Interface 的方法,來移除元素 + heap.Pop(maxHeap) // 5 + heap.Pop(maxHeap) // 4 + heap.Pop(maxHeap) // 3 + heap.Pop(maxHeap) // 2 + heap.Pop(maxHeap) // 1 + + /* 獲取堆積大小 */ + size := len(*maxHeap) + fmt.Printf("堆積元素數量為 %d\n", size) + + /* 判斷堆積是否為空 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("堆積是否為空 %t\n", isEmpty) + } + ``` + +=== "Swift" + + ```swift title="heap.swift" + /* 初始化堆積 */ + // Swift 的 Heap 型別同時支持最大堆積和最小堆積,且需要引入 swift-collections + var heap = Heap() + + /* 元素入堆積 */ + heap.insert(1) + heap.insert(3) + heap.insert(2) + heap.insert(5) + heap.insert(4) + + /* 獲取堆積頂元素 */ + var peek = heap.max()! + + /* 堆積頂元素出堆積 */ + peek = heap.removeMax() // 5 + peek = heap.removeMax() // 4 + peek = heap.removeMax() // 3 + peek = heap.removeMax() // 2 + peek = heap.removeMax() // 1 + + /* 獲取堆積大小 */ + let size = heap.count + + /* 判斷堆積是否為空 */ + let isEmpty = heap.isEmpty + + /* 輸入串列並建堆積 */ + let heap2 = Heap([1, 3, 2, 5, 4]) + ``` + +=== "JS" + + ```javascript title="heap.js" + // JavaScript 未提供內建 Heap 類別 + ``` + +=== "TS" + + ```typescript title="heap.ts" + // TypeScript 未提供內建 Heap 類別 + ``` + +=== "Dart" + + ```dart title="heap.dart" + // Dart 未提供內建 Heap 類別 + ``` + +=== "Rust" + + ```rust title="heap.rs" + use std::collections::BinaryHeap; + use std::cmp::Reverse; + + /* 初始化堆積 */ + // 初始化小頂堆積 + let mut min_heap = BinaryHeap::>::new(); + // 初始化大頂堆積 + let mut max_heap = BinaryHeap::new(); + + /* 元素入堆積 */ + max_heap.push(1); + max_heap.push(3); + max_heap.push(2); + max_heap.push(5); + max_heap.push(4); + + /* 獲取堆積頂元素 */ + let peek = max_heap.peek().unwrap(); // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + let peek = max_heap.pop().unwrap(); // 5 + let peek = max_heap.pop().unwrap(); // 4 + let peek = max_heap.pop().unwrap(); // 3 + let peek = max_heap.pop().unwrap(); // 2 + let peek = max_heap.pop().unwrap(); // 1 + + /* 獲取堆積大小 */ + let size = max_heap.len(); + + /* 判斷堆積是否為空 */ + let is_empty = max_heap.is_empty(); + + /* 輸入串列並建堆積 */ + let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); + ``` + +=== "C" + + ```c title="heap.c" + // C 未提供內建 Heap 類別 + ``` + +=== "Kotlin" + + ```kotlin title="heap.kt" + /* 初始化堆積 */ + // 初始化小頂堆積 + var minHeap = PriorityQueue() + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + /* 元素入堆積 */ + maxHeap.offer(1) + maxHeap.offer(3) + maxHeap.offer(2) + maxHeap.offer(5) + maxHeap.offer(4) + + /* 獲取堆積頂元素 */ + var peek = maxHeap.peek() // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + peek = maxHeap.poll() // 5 + peek = maxHeap.poll() // 4 + peek = maxHeap.poll() // 3 + peek = maxHeap.poll() // 2 + peek = maxHeap.poll() // 1 + + /* 獲取堆積大小 */ + val size = maxHeap.size + + /* 判斷堆積是否為空 */ + val isEmpty = maxHeap.isEmpty() + + /* 輸入串列並建堆積 */ + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + ``` + +=== "Ruby" + + ```ruby title="heap.rb" + # Ruby 未提供內建 Heap 類別 + ``` + +=== "Zig" + + ```zig title="heap.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20min_heap%2C%20flag%20%3D%20%5B%5D%2C%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20max_heap%2C%20flag%20%3D%20%5B%5D%2C%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E7%B5%84%E9%A0%90%E8%A8%AD%E5%AF%A6%E7%8F%BE%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E8%80%83%E6%85%AE%E5%B0%87%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B2%A0%E2%80%9D%E5%BE%8C%E5%86%8D%E5%85%A5%E5%A0%86%E7%A9%8D%EF%BC%8C%E9%80%99%E6%A8%A3%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%87%E5%A4%A7%E5%B0%8F%E9%97%9C%E4%BF%82%E9%A1%9B%E5%80%92%EF%BC%8C%E5%BE%9E%E8%80%8C%E5%AF%A6%E7%8F%BE%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%99%82%E5%B0%8D%E6%87%89%E5%B0%8F%E9%A0%82%E5%A0%86%E7%A9%8D%EF%BC%8Cflag%20%3D%20-1%20%E6%99%82%E5%B0%8D%E6%87%89%E5%A4%A7%E9%A0%82%E5%A0%86%E7%A9%8D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%A9%8D%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%201%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%203%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%202%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%205%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20%2A%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E7%A9%8D%E9%A0%82%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%A9%8D%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E7%A9%8D%E5%85%83%E7%B4%A0%E6%9C%83%E5%BD%A2%E6%88%90%E4%B8%80%E5%80%8B%E5%BE%9E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%A9%8D%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E5%A0%86%E7%A9%8D%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BC%B8%E5%85%A5%E4%B8%B2%E5%88%97%E4%B8%A6%E5%BB%BA%E5%A0%86%E7%A9%8D%0A%20%20%20%20min_heap%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 堆積的實現 + +下文實現的是大頂堆積。若要將其轉換為小頂堆積,只需將所有大小邏輯判斷進行逆轉(例如,將 $\geq$ 替換為 $\leq$ )。感興趣的讀者可以自行實現。 + +### 堆積的儲存與表示 + +“二元樹”章節講過,完全二元樹非常適合用陣列來表示。由於堆積正是一種完全二元樹,**因此我們將採用陣列來儲存堆積**。 + +當使用陣列表示二元樹時,元素代表節點值,索引代表節點在二元樹中的位置。**節點指標透過索引對映公式來實現**。 + +如下圖所示,給定索引 $i$ ,其左子節點的索引為 $2i + 1$ ,右子節點的索引為 $2i + 2$ ,父節點的索引為 $(i - 1) / 2$(向下整除)。當索引越界時,表示空節點或節點不存在。 + +![堆積的表示與儲存](heap.assets/representation_of_heap.png) + +我們可以將索引對映公式封裝成函式,方便後續使用: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{parent} +``` + +### 訪問堆積頂元素 + +堆積頂元素即為二元樹的根節點,也就是串列的首個元素: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{peek} +``` + +### 元素入堆積 + +給定元素 `val` ,我們首先將其新增到堆積底。新增之後,由於 `val` 可能大於堆積中其他元素,堆積的成立條件可能已被破壞,**因此需要修復從插入節點到根節點的路徑上的各個節點**,這個操作被稱為堆積化(heapify)。 + +考慮從入堆積節點開始,**從底至頂執行堆積化**。如下圖所示,我們比較插入節點與其父節點的值,如果插入節點更大,則將它們交換。然後繼續執行此操作,從底至頂修復堆積中的各個節點,直至越過根節點或遇到無須交換的節點時結束。 + +=== "<1>" + ![元素入堆積步驟](heap.assets/heap_push_step1.png) + +=== "<2>" + ![heap_push_step2](heap.assets/heap_push_step2.png) + +=== "<3>" + ![heap_push_step3](heap.assets/heap_push_step3.png) + +=== "<4>" + ![heap_push_step4](heap.assets/heap_push_step4.png) + +=== "<5>" + ![heap_push_step5](heap.assets/heap_push_step5.png) + +=== "<6>" + ![heap_push_step6](heap.assets/heap_push_step6.png) + +=== "<7>" + ![heap_push_step7](heap.assets/heap_push_step7.png) + +=== "<8>" + ![heap_push_step8](heap.assets/heap_push_step8.png) + +=== "<9>" + ![heap_push_step9](heap.assets/heap_push_step9.png) + +設節點總數為 $n$ ,則樹的高度為 $O(\log n)$ 。由此可知,堆積化操作的迴圈輪數最多為 $O(\log n)$ ,**元素入堆積操作的時間複雜度為 $O(\log n)$** 。程式碼如下所示: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_up} +``` + +### 堆積頂元素出堆積 + +堆積頂元素是二元樹的根節點,即串列首元素。如果我們直接從串列中刪除首元素,那麼二元樹中所有節點的索引都會發生變化,這將使得後續使用堆積化進行修復變得困難。為了儘量減少元素索引的變動,我們採用以下操作步驟。 + +1. 交換堆積頂元素與堆積底元素(交換根節點與最右葉節點)。 +2. 交換完成後,將堆積底從串列中刪除(注意,由於已經交換,因此實際上刪除的是原來的堆積頂元素)。 +3. 從根節點開始,**從頂至底執行堆積化**。 + +如下圖所示,**“從頂至底堆積化”的操作方向與“從底至頂堆積化”相反**,我們將根節點的值與其兩個子節點的值進行比較,將最大的子節點與根節點交換。然後迴圈執行此操作,直到越過葉節點或遇到無須交換的節點時結束。 + +=== "<1>" + ![堆積頂元素出堆積步驟](heap.assets/heap_pop_step1.png) + +=== "<2>" + ![heap_pop_step2](heap.assets/heap_pop_step2.png) + +=== "<3>" + ![heap_pop_step3](heap.assets/heap_pop_step3.png) + +=== "<4>" + ![heap_pop_step4](heap.assets/heap_pop_step4.png) + +=== "<5>" + ![heap_pop_step5](heap.assets/heap_pop_step5.png) + +=== "<6>" + ![heap_pop_step6](heap.assets/heap_pop_step6.png) + +=== "<7>" + ![heap_pop_step7](heap.assets/heap_pop_step7.png) + +=== "<8>" + ![heap_pop_step8](heap.assets/heap_pop_step8.png) + +=== "<9>" + ![heap_pop_step9](heap.assets/heap_pop_step9.png) + +=== "<10>" + ![heap_pop_step10](heap.assets/heap_pop_step10.png) + +與元素入堆積操作相似,堆積頂元素出堆積操作的時間複雜度也為 $O(\log n)$ 。程式碼如下所示: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_down} +``` + +## 堆積的常見應用 + +- **優先佇列**:堆積通常作為實現優先佇列的首選資料結構,其入列和出列操作的時間複雜度均為 $O(\log n)$ ,而建堆積操作為 $O(n)$ ,這些操作都非常高效。 +- **堆積排序**:給定一組資料,我們可以用它們建立一個堆積,然後不斷地執行元素出堆積操作,從而得到有序資料。然而,我們通常會使用一種更優雅的方式實現堆積排序,詳見“堆積排序”章節。 +- **獲取最大的 $k$ 個元素**:這是一個經典的演算法問題,同時也是一種典型應用,例如選擇熱度前 10 的新聞作為微博熱搜,選取銷量前 10 的商品等。 diff --git a/zh-hant/docs/chapter_heap/index.md b/zh-hant/docs/chapter_heap/index.md new file mode 100644 index 0000000000..af380781a0 --- /dev/null +++ b/zh-hant/docs/chapter_heap/index.md @@ -0,0 +1,9 @@ +# 堆積 + +![堆積](../assets/covers/chapter_heap.jpg) + +!!! abstract + + 堆積就像是山嶽峰巒,層疊起伏、形態各異。 + + 座座山峰高低錯落,而最高的山峰總是最先映入眼簾。 diff --git a/zh-hant/docs/chapter_heap/summary.md b/zh-hant/docs/chapter_heap/summary.md new file mode 100644 index 0000000000..621abde81e --- /dev/null +++ b/zh-hant/docs/chapter_heap/summary.md @@ -0,0 +1,17 @@ +# 小結 + +### 重點回顧 + +- 堆積是一棵完全二元樹,根據成立條件可分為大頂堆積和小頂堆積。大(小)頂堆積的堆積頂元素是最大(小)的。 +- 優先佇列的定義是具有出列優先順序的佇列,通常使用堆積來實現。 +- 堆積的常用操作及其對應的時間複雜度包括:元素入堆積 $O(\log n)$、堆積頂元素出堆積 $O(\log n)$ 和訪問堆積頂元素 $O(1)$ 等。 +- 完全二元樹非常適合用陣列表示,因此我們通常使用陣列來儲存堆積。 +- 堆積化操作用於維護堆積的性質,在入堆積和出堆積操作中都會用到。 +- 輸入 $n$ 個元素並建堆積的時間複雜度可以最佳化至 $O(n)$ ,非常高效。 +- Top-k 是一個經典演算法問題,可以使用堆積資料結構高效解決,時間複雜度為 $O(n \log k)$ 。 + +### Q & A + +**Q**:資料結構的“堆積”與記憶體管理的“堆積”是同一個概念嗎? + +兩者不是同一個概念,只是碰巧都叫“堆積”。計算機系統記憶體中的堆積是動態記憶體分配的一部分,程式在執行時可以使用它來儲存資料。程式可以請求一定量的堆積記憶體,用於儲存如物件和陣列等複雜結構。當這些資料不再需要時,程式需要釋放這些記憶體,以防止記憶體流失。相較於堆疊記憶體,堆積記憶體的管理和使用需要更謹慎,使用不當可能會導致記憶體流失和野指標等問題。 diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step1.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step1.png new file mode 100644 index 0000000000..f54f3a0262 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step1.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step2.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step2.png new file mode 100644 index 0000000000..9a5e5d1ce2 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step2.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step3.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step3.png new file mode 100644 index 0000000000..ffc21fab7c Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step3.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step4.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step4.png new file mode 100644 index 0000000000..4a4517c22a Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step4.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step5.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step5.png new file mode 100644 index 0000000000..4c879e9b58 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step5.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step6.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step6.png new file mode 100644 index 0000000000..767d184329 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step6.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step7.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step7.png new file mode 100644 index 0000000000..1855eabb3e Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step7.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step8.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step8.png new file mode 100644 index 0000000000..441f5910ad Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step8.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step9.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step9.png new file mode 100644 index 0000000000..afc762da19 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step9.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_sorting.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_sorting.png new file mode 100644 index 0000000000..5cc63713d6 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_sorting.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_traversal.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_traversal.png new file mode 100644 index 0000000000..a02bd4abc0 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_traversal.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.md b/zh-hant/docs/chapter_heap/top_k.md new file mode 100644 index 0000000000..beee1bb78d --- /dev/null +++ b/zh-hant/docs/chapter_heap/top_k.md @@ -0,0 +1,73 @@ +# Top-k 問題 + +!!! question + + 給定一個長度為 $n$ 的無序陣列 `nums` ,請返回陣列中最大的 $k$ 個元素。 + +對於該問題,我們先介紹兩種思路比較直接的解法,再介紹效率更高的堆積解法。 + +## 方法一:走訪選擇 + +我們可以進行下圖所示的 $k$ 輪走訪,分別在每輪中提取第 $1$、$2$、$\dots$、$k$ 大的元素,時間複雜度為 $O(nk)$ 。 + +此方法只適用於 $k \ll n$ 的情況,因為當 $k$ 與 $n$ 比較接近時,其時間複雜度趨向於 $O(n^2)$ ,非常耗時。 + +![走訪尋找最大的 k 個元素](top_k.assets/top_k_traversal.png) + +!!! tip + + 當 $k = n$ 時,我們可以得到完整的有序序列,此時等價於“選擇排序”演算法。 + +## 方法二:排序 + +如下圖所示,我們可以先對陣列 `nums` 進行排序,再返回最右邊的 $k$ 個元素,時間複雜度為 $O(n \log n)$ 。 + +顯然,該方法“超額”完成任務了,因為我們只需找出最大的 $k$ 個元素即可,而不需要排序其他元素。 + +![排序尋找最大的 k 個元素](top_k.assets/top_k_sorting.png) + +## 方法三:堆積 + +我們可以基於堆積更加高效地解決 Top-k 問題,流程如下圖所示。 + +1. 初始化一個小頂堆積,其堆積頂元素最小。 +2. 先將陣列的前 $k$ 個元素依次入堆積。 +3. 從第 $k + 1$ 個元素開始,若當前元素大於堆積頂元素,則將堆積頂元素出堆積,並將當前元素入堆積。 +4. 走訪完成後,堆積中儲存的就是最大的 $k$ 個元素。 + +=== "<1>" + ![基於堆積尋找最大的 k 個元素](top_k.assets/top_k_heap_step1.png) + +=== "<2>" + ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) + +=== "<3>" + ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) + +=== "<4>" + ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) + +=== "<5>" + ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) + +=== "<6>" + ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) + +=== "<7>" + ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) + +=== "<8>" + ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) + +=== "<9>" + ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) + +示例程式碼如下: + +```src +[file]{top_k}-[class]{}-[func]{top_k_heap} +``` + +總共執行了 $n$ 輪入堆積和出堆積,堆積的最大長度為 $k$ ,因此時間複雜度為 $O(n \log k)$ 。該方法的效率很高,當 $k$ 較小時,時間複雜度趨向 $O(n)$ ;當 $k$ 較大時,時間複雜度不會超過 $O(n \log n)$ 。 + +另外,該方法適用於動態資料流的使用場景。在不斷加入資料時,我們可以持續維護堆積內的元素,從而實現最大的 $k$ 個元素的動態更新。 diff --git a/zh-hant/docs/chapter_hello_algo/index.md b/zh-hant/docs/chapter_hello_algo/index.md new file mode 100644 index 0000000000..ac04b57697 --- /dev/null +++ b/zh-hant/docs/chapter_hello_algo/index.md @@ -0,0 +1,30 @@ +--- +comments: true +icon: material/rocket-launch-outline +--- + +# 序 + +幾年前,我在力扣上分享了“劍指 Offer”系列題解,受到了許多讀者的鼓勵和支持。在與讀者交流期間,我最常被問的一個問題是“如何入門演算法”。逐漸地,我對這個問題產生了濃厚的興趣。 + +兩眼一抹黑地刷題似乎是最受歡迎的方法,簡單、直接且有效。然而刷題就如同玩“掃雷”遊戲,自學能力強的人能夠順利將地雷逐個排掉,而基礎不足的人很可能被炸得滿頭是包,並在挫折中步步退縮。通讀教材也是一種常見做法,但對於面向求職的人來說,畢業論文、投遞簡歷、準備筆試和面試已經消耗了大部分精力,啃厚重的書往往變成了一項艱鉅的挑戰。 + +如果你也面臨類似的困擾,那麼很幸運這本書“找”到了你。本書是我對這個問題給出的答案,即使不是最優解,也至少是一次積極的嘗試。本書雖然不足以讓你直接拿到 Offer,但會引導你探索資料結構與演算法的“知識地圖”,帶你瞭解不同“地雷”的形狀、大小和分佈位置,讓你掌握各種“排雷方法”。有了這些本領,相信你可以更加自如地刷題和閱讀文獻,逐步構建起完整的知識體系。 + +我深深贊同費曼教授所言:“Knowledge isn't free. You have to pay attention.”從這個意義上看,這本書並非完全“免費”。為了不辜負你為本書所付出的寶貴“注意力”,我會竭盡所能,投入最大的“注意力”來完成本書的創作。 + +本人自知學疏才淺,書中內容雖然已經過一段時間的打磨,但一定仍有許多錯誤,懇請各位老師和同學批評指正。 + +![Hello 演算法](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" } + +
+

Hello,演算法!

+
+ +計算機的出現給世界帶來了巨大變革,它憑藉高速的計算能力和出色的可程式設計性,成為了執行演算法與處理資料的理想媒介。無論是電子遊戲的逼真畫面、自動駕駛的智慧決策,還是 AlphaGo 的精彩棋局、ChatGPT 的自然互動,這些應用都是演算法在計算機上的精妙演繹。 + +事實上,在計算機問世之前,演算法和資料結構就已經存在於世界的各個角落。早期的演算法相對簡單,例如古代的計數方法和工具製作步驟等。隨著文明的進步,演算法逐漸變得更加精細和複雜。從巧奪天工的匠人技藝、到解放生產力的工業產品、再到宇宙執行的科學規律,幾乎每一件平凡或令人驚歎的事物背後,都隱藏著精妙的演算法思想。 + +同樣,資料結構無處不在:大到社會網路,小到地鐵線路,許多系統都可以建模為“圖”;大到一個國家,小到一個家庭,社會的主要組織形式呈現出“樹”的特徵;冬天的衣服就像“堆疊”,最先穿上的最後才能脫下;羽毛球筒則如同“佇列”,一端放入、另一端取出;字典就像一個“雜湊表”,能夠快速查詢目標詞條。 + +本書旨在透過清晰易懂的動畫圖解和可執行的程式碼示例,使讀者理解演算法和資料結構的核心概念,並能夠透過程式設計來實現它們。在此基礎上,本書致力於揭示演算法在複雜世界中的生動體現,展現演算法之美。希望本書能夠幫助到你! diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png new file mode 100644 index 0000000000..949e308653 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png new file mode 100644 index 0000000000..e05f3d974e Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png new file mode 100644 index 0000000000..1fbc259a64 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png new file mode 100644 index 0000000000..c85f41eddb Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png new file mode 100644 index 0000000000..b458e8a7d6 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png new file mode 100644 index 0000000000..1316116dc9 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png new file mode 100644 index 0000000000..a659087ab4 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.md b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.md new file mode 100644 index 0000000000..6072b0376b --- /dev/null +++ b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.md @@ -0,0 +1,56 @@ +# 演算法無處不在 + +當我們聽到“演算法”這個詞時,很自然地會想到數學。然而實際上,許多演算法並不涉及複雜數學,而是更多地依賴基本邏輯,這些邏輯在我們的日常生活中處處可見。 + +在正式探討演算法之前,有一個有趣的事實值得分享:**你已經在不知不覺中學會了許多演算法,並習慣將它們應用到日常生活中了**。下面我將舉幾個具體的例子來證實這一點。 + +**例一:查字典**。在字典裡,每個漢字都對應一個拼音,而字典是按照拼音字母順序排列的。假設我們需要查詢一個拼音首字母為 $r$ 的字,通常會按照下圖所示的方式實現。 + +1. 翻開字典約一半的頁數,檢視該頁的首字母是什麼,假設首字母為 $m$ 。 +2. 由於在拼音字母表中 $r$ 位於 $m$ 之後,所以排除字典前半部分,查詢範圍縮小到後半部分。 +3. 不斷重複步驟 `1.` 和步驟 `2.` ,直至找到拼音首字母為 $r$ 的頁碼為止。 + +=== "<1>" + ![查字典步驟](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) + +=== "<2>" + ![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) + +=== "<3>" + ![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) + +=== "<4>" + ![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) + +=== "<5>" + ![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) + +查字典這個小學生必備技能,實際上就是著名的“二分搜尋”演算法。從資料結構的角度,我們可以把字典視為一個已排序的“陣列”;從演算法的角度,我們可以將上述查字典的一系列操作看作“二分搜尋”。 + +**例二:整理撲克**。我們在打牌時,每局都需要整理手中的撲克牌,使其從小到大排列,實現流程如下圖所示。 + +1. 將撲克牌劃分為“有序”和“無序”兩部分,並假設初始狀態下最左 1 張撲克牌已經有序。 +2. 在無序部分抽出一張撲克牌,插入至有序部分的正確位置;完成後最左 2 張撲克已經有序。 +3. 不斷迴圈步驟 `2.` ,每一輪將一張撲克牌從無序部分插入至有序部分,直至所有撲克牌都有序。 + +![撲克排序步驟](algorithms_are_everywhere.assets/playing_cards_sorting.png) + +上述整理撲克牌的方法本質上是“插入排序”演算法,它在處理小型資料集時非常高效。許多程式語言的排序庫函式中都有插入排序的身影。 + +**例三:貨幣找零**。假設我們在超市購買了 $69$ 元的商品,給了收銀員 $100$ 元,則收銀員需要找我們 $31$ 元。他會很自然地完成如下圖所示的思考。 + +1. 可選項是比 $31$ 元面值更小的貨幣,包括 $1$ 元、$5$ 元、$10$ 元、$20$ 元。 +2. 從可選項中拿出最大的 $20$ 元,剩餘 $31 - 20 = 11$ 元。 +3. 從剩餘可選項中拿出最大的 $10$ 元,剩餘 $11 - 10 = 1$ 元。 +4. 從剩餘可選項中拿出最大的 $1$ 元,剩餘 $1 - 1 = 0$ 元。 +5. 完成找零,方案為 $20 + 10 + 1 = 31$ 元。 + +![貨幣找零過程](algorithms_are_everywhere.assets/greedy_change.png) + +在以上步驟中,我們每一步都採取當前看來最好的選擇(儘可能用大面額的貨幣),最終得到了可行的找零方案。從資料結構與演算法的角度看,這種方法本質上是“貪婪”演算法。 + +小到烹飪一道菜,大到星際航行,幾乎所有問題的解決都離不開演算法。計算機的出現使得我們能夠透過程式設計將資料結構儲存在記憶體中,同時編寫程式碼呼叫 CPU 和 GPU 執行演算法。這樣一來,我們就能把生活中的問題轉移到計算機上,以更高效的方式解決各種複雜問題。 + +!!! tip + + 如果你對資料結構、演算法、陣列和二分搜尋等概念仍感到一知半解,請繼續往下閱讀,本書將引導你邁入資料結構與演算法的知識殿堂。 diff --git a/zh-hant/docs/chapter_introduction/index.md b/zh-hant/docs/chapter_introduction/index.md new file mode 100644 index 0000000000..f9257ddd74 --- /dev/null +++ b/zh-hant/docs/chapter_introduction/index.md @@ -0,0 +1,9 @@ +# 初識演算法 + +![初識演算法](../assets/covers/chapter_introduction.jpg) + +!!! abstract + + 一位少女翩翩起舞,與資料交織在一起,裙襬上飄揚著演算法的旋律。 + + 她邀請你共舞,請緊跟她的步伐,踏入充滿邏輯與美感的演算法世界。 diff --git a/zh-hant/docs/chapter_introduction/summary.md b/zh-hant/docs/chapter_introduction/summary.md new file mode 100644 index 0000000000..fb68076a16 --- /dev/null +++ b/zh-hant/docs/chapter_introduction/summary.md @@ -0,0 +1,22 @@ +# 小結 + +- 演算法在日常生活中無處不在,並不是遙不可及的高深知識。實際上,我們已經在不知不覺中學會了許多演算法,用以解決生活中的大小問題。 +- 查字典的原理與二分搜尋演算法相一致。二分搜尋演算法體現了分而治之的重要演算法思想。 +- 整理撲克的過程與插入排序演算法非常類似。插入排序演算法適合排序小型資料集。 +- 貨幣找零的步驟本質上是貪婪演算法,每一步都採取當前看來最好的選擇。 +- 演算法是在有限時間內解決特定問題的一組指令或操作步驟,而資料結構是計算機中組織和儲存資料的方式。 +- 資料結構與演算法緊密相連。資料結構是演算法的基石,而演算法為資料結構注入生命力。 +- 我們可以將資料結構與演算法類比為拼裝積木,積木代表資料,積木的形狀和連線方式等代表資料結構,拼裝積木的步驟則對應演算法。 + +### Q & A + +**Q**:作為一名程式設計師,我在日常工作中從未用演算法解決過問題,常用演算法都被程式語言封裝好了,直接用就可以了;這是否意味著我們工作中的問題還沒有到達需要演算法的程度? + +如果把具體的工作技能比作是武功的“招式”的話,那麼基礎科目應該更像是“內功”。 + +我認為學演算法(以及其他基礎科目)的意義不是在於在工作中從零實現它,而是基於學到的知識,在解決問題時能夠作出專業的反應和判斷,從而提升工作的整體質量。舉一個簡單例子,每種程式語言都內建了排序函式: + +- 如果我們沒有學過資料結構與演算法,那麼給定任何資料,我們可能都塞給這個排序函式去做了。執行順暢、效能不錯,看上去並沒有什麼問題。 +- 但如果學過演算法,我們就會知道內建排序函式的時間複雜度是 $O(n \log n)$ ;而如果給定的資料是固定位數的整數(例如學號),那麼我們就可以用效率更高的“基數排序”來做,將時間複雜度降為 $O(nk)$ ,其中 $k$ 為位數。當資料體量很大時,節省出來的執行時間就能創造較大價值(成本降低、體驗變好等)。 + +在工程領域中,大量問題是難以達到最優解的,許多問題只是被“差不多”地解決了。問題的難易程度一方面取決於問題本身的性質,另一方面也取決於觀測問題的人的知識儲備。人的知識越完備、經驗越多,分析問題就會越深入,問題就能被解決得更優雅。 diff --git a/zh-hant/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png new file mode 100644 index 0000000000..5f7ac5fe3e Binary files /dev/null and b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png differ diff --git a/zh-hant/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png new file mode 100644 index 0000000000..c3e3b599e7 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png differ diff --git a/zh-hant/docs/chapter_introduction/what_is_dsa.md b/zh-hant/docs/chapter_introduction/what_is_dsa.md new file mode 100644 index 0000000000..5f135eaa6f --- /dev/null +++ b/zh-hant/docs/chapter_introduction/what_is_dsa.md @@ -0,0 +1,53 @@ +# 演算法是什麼 + +## 演算法定義 + +演算法(algorithm)是在有限時間內解決特定問題的一組指令或操作步驟,它具有以下特性。 + +- 問題是明確的,包含清晰的輸入和輸出定義。 +- 具有可行性,能夠在有限步驟、時間和記憶體空間下完成。 +- 各步驟都有確定的含義,在相同的輸入和執行條件下,輸出始終相同。 + +## 資料結構定義 + +資料結構(data structure)是組織和儲存資料的方式,涵蓋資料內容、資料之間關係和資料操作方法,它具有以下設計目標。 + +- 空間佔用儘量少,以節省計算機記憶體。 +- 資料操作儘可能快速,涵蓋資料訪問、新增、刪除、更新等。 +- 提供簡潔的資料表示和邏輯資訊,以便演算法高效執行。 + +**資料結構設計是一個充滿權衡的過程**。如果想在某方面取得提升,往往需要在另一方面作出妥協。下面舉兩個例子。 + +- 鏈結串列相較於陣列,在資料新增和刪除操作上更加便捷,但犧牲了資料訪問速度。 +- 圖相較於鏈結串列,提供了更豐富的邏輯資訊,但需要佔用更大的記憶體空間。 + +## 資料結構與演算法的關係 + +如下圖所示,資料結構與演算法高度相關、緊密結合,具體表現在以下三個方面。 + +- 資料結構是演算法的基石。資料結構為演算法提供了結構化儲存的資料,以及操作資料的方法。 +- 演算法為資料結構注入生命力。資料結構本身僅儲存資料資訊,結合演算法才能解決特定問題。 +- 演算法通常可以基於不同的資料結構實現,但執行效率可能相差很大,選擇合適的資料結構是關鍵。 + +![資料結構與演算法的關係](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) + +資料結構與演算法猶如下圖所示的拼裝積木。一套積木,除了包含許多零件之外,還附有詳細的組裝說明書。我們按照說明書一步步操作,就能組裝出精美的積木模型。 + +![拼裝積木](what_is_dsa.assets/assembling_blocks.png) + +兩者的詳細對應關係如下表所示。 + +

  將資料結構與演算法類比為拼裝積木

+ +| 資料結構與演算法 | 拼裝積木 | +| -------------- | ---------------------------------------- | +| 輸入資料 | 未拼裝的積木 | +| 資料結構 | 積木組織形式,包括形狀、大小、連線方式等 | +| 演算法 | 把積木拼成目標形態的一系列操作步驟 | +| 輸出資料 | 積木模型 | + +值得說明的是,資料結構與演算法是獨立於程式語言的。正因如此,本書得以提供基於多種程式語言的實現。 + +!!! tip "約定俗成的簡稱" + + 在實際討論時,我們通常會將“資料結構與演算法”簡稱為“演算法”。比如眾所周知的 LeetCode 演算法題目,實際上同時考查資料結構和演算法兩方面的知識。 diff --git a/zh-hant/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png b/zh-hant/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png new file mode 100644 index 0000000000..893be60231 Binary files /dev/null and b/zh-hant/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png differ diff --git a/zh-hant/docs/chapter_preface/about_the_book.md b/zh-hant/docs/chapter_preface/about_the_book.md new file mode 100644 index 0000000000..a1ccfb8b97 --- /dev/null +++ b/zh-hant/docs/chapter_preface/about_the_book.md @@ -0,0 +1,52 @@ +# 關於本書 + +本專案旨在建立一本開源、免費、對新手友好的資料結構與演算法入門教程。 + +- 全書採用動畫圖解,內容清晰易懂、學習曲線平滑,引導初學者探索資料結構與演算法的知識地圖。 +- 源程式碼可一鍵執行,幫助讀者在練習中提升程式設計技能,瞭解演算法工作原理和資料結構底層實現。 +- 提倡讀者互助學習,歡迎大家在評論區提出問題與分享見解,在交流討論中共同進步。 + +## 目標讀者 + +若你是演算法初學者,從未接觸過演算法,或者已經有一些刷題經驗,對資料結構與演算法有模糊的認識,在會與不會之間反覆橫跳,那麼本書正是為你量身定製的! + +如果你已經積累一定的刷題量,熟悉大部分題型,那麼本書可助你回顧與梳理演算法知識體系,倉庫源程式碼可以當作“刷題工具庫”或“演算法字典”來使用。 + +若你是演算法“大神”,我們期待收到你的寶貴建議,或者[一起參與創作](https://www.hello-algo.com/chapter_appendix/contribution/)。 + +!!! success "前置條件" + + 你需要至少具備任一語言的程式設計基礎,能夠閱讀和編寫簡單程式碼。 + +## 內容結構 + +本書的主要內容如下圖所示。 + +- **複雜度分析**:資料結構和演算法的評價維度與方法。時間複雜度和空間複雜度的推算方法、常見型別、示例等。 +- **資料結構**:基本資料型別和資料結構的分類方法。陣列、鏈結串列、堆疊、佇列、雜湊表、樹、堆積、圖等資料結構的定義、優缺點、常用操作、常見型別、典型應用、實現方法等。 +- **演算法**:搜尋、排序、分治、回溯、動態規劃、貪婪等演算法的定義、優缺點、效率、應用場景、解題步驟和示例問題等。 + +![本書主要內容](about_the_book.assets/hello_algo_mindmap.png) + +## 致謝 + +本書在開源社群眾多貢獻者的共同努力下不斷完善。感謝每一位投入時間與精力的撰稿人,他們是(按照 GitHub 自動生成的順序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、msk397、gvenusleo、khoaxuantu、RiverTwilight、rongyi、gyt95、zhuoqinyue、K3v123、Zuoxun、mingXta、hello-ikun、FangYuan33、GN-Yu、yuelinxin、longsizhuo、Cathay-Chen、guowei-gong、xBLACKICEx、IsChristina、JoseHung、qualifier1024、QiLOL、pengchzn、Guanngxu、L-Super、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、theNefelibatas、longranger2、cy-by-side、xiongsp、JeffersonHuang、Transmigration-zhou、magentaqin、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、Shyam-Chen、nanlei、hongyun-robot、Phoenix0415、MolDuM、Nigh、he-weilai、junminhong、mgisr、iron-irax、yd-j、XiaChuerwu、XC-Zero、seven1240、SamJin98、wodray、reeswell、NI-SW、Horbin-Magician、Enlightenus、xjr7670、YangXuanyi、DullSword、boloboloda、iStig、qq909244296、jiaxianhua、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、liuxjerry、lucaswangdev、lyl625760、hts0000、gledfish、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、luluxia、xb534、bitsmi、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、steventimes、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、ZhongGuanbin、shanghai-Jerry、JackYang-hellobobo、Javesun99、lipusheng、BlindTerran、ShiMaRing、FreddieLi、FloranceYeh、iFleey、fanchenggang、gltianwen、goerll、Dr-XYZ、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、fanenr、eagleanurag、LifeGoesOnionOnionOnion、52coder、foursevenlove、KorsChen、hezhizhen、linzeyan、ZJKung、GaochaoZhu、hopkings2008、yang-le、Evilrabbit520、Turing-1024-Lee、thomasq0、Suremotoo、Allen-Scai、Risuntsy、Richard-Zhang1019、qingpeng9802、primexiao、nidhoggfgg、1ch0、MwumLi、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai 和 0130w。 + +本書的程式碼審閱工作由 coderonion、curtishd、Gonglja、gvenusleo、hpstory、justin-tse、khoaxuantu、krahets、night-cruise、nuomi1、Reanon 和 rongyi 完成(按照首字母順序排列)。感謝他們付出的時間與精力,正是他們確保了各語言程式碼的規範與統一。 + +本書的繁體中文版由 Shyam-Chen 和 Dr-XYZ 審閱,英文版由 yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0 和 magentaqin 審閱。正是因為他們的持續貢獻,這本書才能夠服務於更廣泛的讀者群體,感謝他們。 + +在本書的創作過程中,我得到了許多人的幫助。 + +- 感謝我在公司的導師李汐博士,在一次暢談中你鼓勵我“快行動起來”,堅定了我寫這本書的決心; +- 感謝我的女朋友泡泡作為本書的首位讀者,從演算法小白的角度提出許多寶貴建議,使得本書更適合新手閱讀; +- 感謝騰寶、琦寶、飛寶為本書起了一個富有創意的名字,喚起大家寫下第一行程式碼“Hello World!”的美好回憶; +- 感謝校銓在智慧財產權方面提供的專業幫助,這對本開源書的完善起到了重要作用; +- 感謝蘇潼為本書設計了精美的封面和 logo ,並在我的強迫症的驅使下多次耐心修改; +- 感謝 @squidfunk 提供的排版建議,以及他開發的開源文件主題 [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) 。 + +在寫作過程中,我閱讀了許多關於資料結構與演算法的教材和文章。這些作品為本書提供了優秀的範本,確保了本書內容的準確性與品質。在此感謝所有老師和前輩的傑出貢獻! + +本書倡導手腦並用的學習方式,在這一點上我深受[《動手學深度學習》](https://github.com/d2l-ai/d2l-zh)的啟發。在此向各位讀者強烈推薦這本優秀的著作。 + +**衷心感謝我的父母,正是你們一直以來的支持與鼓勵,讓我有機會做這件富有趣味的事**。 diff --git a/zh-hant/docs/chapter_preface/index.md b/zh-hant/docs/chapter_preface/index.md new file mode 100644 index 0000000000..ad378bf014 --- /dev/null +++ b/zh-hant/docs/chapter_preface/index.md @@ -0,0 +1,9 @@ +# 前言 + +![前言](../assets/covers/chapter_preface.jpg) + +!!! abstract + + 演算法猶如美妙的交響樂,每一行程式碼都像韻律般流淌。 + + 願這本書在你的腦海中輕輕響起,留下獨特而深刻的旋律。 diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/code_md_to_repo.png b/zh-hant/docs/chapter_preface/suggestions.assets/code_md_to_repo.png new file mode 100644 index 0000000000..9ce80ff761 Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/code_md_to_repo.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/download_code.png b/zh-hant/docs/chapter_preface/suggestions.assets/download_code.png new file mode 100644 index 0000000000..c80230ebe7 Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/download_code.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/learning_route.png b/zh-hant/docs/chapter_preface/suggestions.assets/learning_route.png new file mode 100644 index 0000000000..884bc4846d Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/learning_route.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/pythontutor_example.png b/zh-hant/docs/chapter_preface/suggestions.assets/pythontutor_example.png new file mode 100644 index 0000000000..9a169d8719 Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/pythontutor_example.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.md b/zh-hant/docs/chapter_preface/suggestions.md new file mode 100644 index 0000000000..9fc04dd126 --- /dev/null +++ b/zh-hant/docs/chapter_preface/suggestions.md @@ -0,0 +1,252 @@ +# 如何使用本書 + +!!! tip + + 為了獲得最佳的閱讀體驗,建議你通讀本節內容。 + +## 行文風格約定 + +- 標題後標註 `*` 的是選讀章節,內容相對困難。如果你的時間有限,可以先跳過。 +- 專業術語會使用黑體(紙質版和 PDF 版)或新增下劃線(網頁版),例如陣列(array)。建議記住它們,以便閱讀文獻。 +- 重點內容和總結性語句會 **加粗**,這類文字值得特別關注。 +- 有特指含義的詞句會使用“引號”標註,以避免歧義。 +- 當涉及程式語言之間不一致的名詞時,本書均以 Python 為準,例如使用 `None` 來表示“空”。 +- 本書部分放棄了程式語言的註釋規範,以換取更加緊湊的內容排版。註釋主要分為三種類型:標題註釋、內容註釋、多行註釋。 + +=== "Python" + + ```python title="" + """標題註釋,用於標註函式、類別、測試樣例等""" + + # 內容註釋,用於詳解程式碼 + + """ + 多行 + 註釋 + """ + ``` + +=== "C++" + + ```cpp title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Java" + + ```java title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "C#" + + ```csharp title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Go" + + ```go title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Swift" + + ```swift title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "JS" + + ```javascript title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "TS" + + ```typescript title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Dart" + + ```dart title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Rust" + + ```rust title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "C" + + ```c title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Ruby" + + ```ruby title="" + ### 標題註釋,用於標註函式、類別、測試樣例等 ### + + # 內容註釋,用於詳解程式碼 + + # 多行 + # 註釋 + ``` + +=== "Zig" + + ```zig title="" + // 標題註釋,用於標註函式、類別、測試樣例等 + + // 內容註釋,用於詳解程式碼 + + // 多行 + // 註釋 + ``` + +## 在動畫圖解中高效學習 + +相較於文字,影片和圖片具有更高的資訊密度和結構化程度,更易於理解。在本書中,**重點和難點知識將主要透過動畫以圖解形式展示**,而文字則作為解釋與補充。 + +如果你在閱讀本書時,發現某段內容提供瞭如下圖所示的動畫圖解,**請以圖為主、以文字為輔**,綜合兩者來理解內容。 + +![動畫圖解示例](../index.assets/animation.gif) + +## 在程式碼實踐中加深理解 + +本書的配套程式碼託管在 [GitHub 倉庫](https://github.com/krahets/hello-algo)。如下圖所示,**源程式碼附有測試樣例,可一鍵執行**。 + +如果時間允許,**建議你參照程式碼自行敲一遍**。如果學習時間有限,請至少通讀並執行所有程式碼。 + +與閱讀程式碼相比,編寫程式碼的過程往往能帶來更多收穫。**動手學,才是真的學**。 + +![執行程式碼示例](../index.assets/running_code.gif) + +執行程式碼的前置工作主要分為三步。 + +**第一步:安裝本地程式設計環境**。請參照附錄所示的[教程](https://www.hello-algo.com/chapter_appendix/installation/)進行安裝,如果已安裝,則可跳過此步驟。 + +**第二步:克隆或下載程式碼倉庫**。前往 [GitHub 倉庫](https://github.com/krahets/hello-algo)。如果已經安裝 [Git](https://git-scm.com/downloads) ,可以透過以下命令克隆本倉庫: + +```shell +git clone https://github.com/krahets/hello-algo.git +``` + +當然,你也可以在下圖所示的位置,點選“Download ZIP”按鈕直接下載程式碼壓縮包,然後在本地解壓即可。 + +![克隆倉庫與下載程式碼](suggestions.assets/download_code.png) + +**第三步:執行源程式碼**。如下圖所示,對於頂部標有檔案名稱的程式碼塊,我們可以在倉庫的 `codes` 檔案夾內找到對應的源程式碼檔案。源程式碼檔案可一鍵執行,將幫助你節省不必要的除錯時間,讓你能夠專注於學習內容。 + +![程式碼塊與對應的源程式碼檔案](suggestions.assets/code_md_to_repo.png) + +除了本地執行程式碼,**網頁版還支持 Python 程式碼的視覺化執行**(基於 [pythontutor](https://pythontutor.com/) 實現)。如下圖所示,你可以點選程式碼塊下方的“視覺化執行”來展開檢視,觀察演算法程式碼的執行過程;也可以點選“全屏觀看”,以獲得更好的閱覽體驗。 + +![Python 程式碼的視覺化執行](suggestions.assets/pythontutor_example.png) + +## 在提問討論中共同成長 + +在閱讀本書時,請不要輕易跳過那些沒學明白的知識點。**歡迎在評論區提出你的問題**,我和小夥伴們將竭誠為你解答,一般情況下可在兩天內回覆。 + +如下圖所示,網頁版每個章節的底部都配有評論區。希望你能多關注評論區的內容。一方面,你可以瞭解大家遇到的問題,從而查漏補缺,激發更深入的思考。另一方面,期待你能慷慨地回答其他小夥伴的問題,分享你的見解,幫助他人進步。 + +![評論區示例](../index.assets/comment.gif) + +## 演算法學習路線 + +從總體上看,我們可以將學習資料結構與演算法的過程劃分為三個階段。 + +1. **階段一:演算法入門**。我們需要熟悉各種資料結構的特點和用法,學習不同演算法的原理、流程、用途和效率等方面的內容。 +2. **階段二:刷演算法題**。建議從熱門題目開刷,先積累至少 100 道題目,熟悉主流的演算法問題。初次刷題時,“知識遺忘”可能是一個挑戰,但請放心,這是很正常的。我們可以按照“艾賓浩斯遺忘曲線”來複習題目,通常在進行 3~5 輪的重複後,就能將其牢記在心。推薦的題單和刷題計劃請見此 [GitHub 倉庫](https://github.com/krahets/LeetCode-Book)。 +3. **階段三:搭建知識體系**。在學習方面,我們可以閱讀演算法專欄文章、解題框架和演算法教材,以不斷豐富知識體系。在刷題方面,可以嘗試採用進階刷題策略,如按專題分類、一題多解、一解多題等,相關的刷題心得可以在各個社群找到。 + +如下圖所示,本書內容主要涵蓋“階段一”,旨在幫助你更高效地展開階段二和階段三的學習。 + +![演算法學習路線](suggestions.assets/learning_route.png) diff --git a/zh-hant/docs/chapter_preface/summary.md b/zh-hant/docs/chapter_preface/summary.md new file mode 100644 index 0000000000..03d3c60984 --- /dev/null +++ b/zh-hant/docs/chapter_preface/summary.md @@ -0,0 +1,8 @@ +# 小結 + +- 本書的主要受眾是演算法初學者。如果你已有一定基礎,本書能幫助你系統回顧演算法知識,書中源程式碼也可作為“刷題工具庫”使用。 +- 書中內容主要包括複雜度分析、資料結構和演算法三部分,涵蓋了該領域的大部分主題。 +- 對於演算法新手,在初學階段閱讀一本入門書至關重要,可以少走許多彎路。 +- 書中的動畫圖解通常用於介紹重點和難點知識。閱讀本書時,應給予這些內容更多關注。 +- 實踐乃學習程式設計之最佳途徑。強烈建議執行源程式碼並親自敲程式碼。 +- 本書網頁版的每個章節都設有評論區,歡迎隨時分享你的疑惑與見解。 diff --git a/zh-hant/docs/chapter_reference/index.md b/zh-hant/docs/chapter_reference/index.md new file mode 100644 index 0000000000..b75d606cb0 --- /dev/null +++ b/zh-hant/docs/chapter_reference/index.md @@ -0,0 +1,25 @@ +--- +icon: material/bookshelf +--- + +# 參考文獻 + +[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). + +[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). + +[3] Robert Sedgewick, et al. Algorithms (4th Edition). + +[4] 嚴蔚敏. 資料結構(C 語言版). + +[5] 鄧俊輝. 資料結構(C++ 語言版,第三版). + +[6] 馬克 艾倫 維斯著,陳越譯. 資料結構與演算法分析:Java語言描述(第三版). + +[7] 程傑. 大話資料結構. + +[8] 王爭. 資料結構與演算法之美. + +[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). + +[10] Aston Zhang, et al. Dive into Deep Learning. diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_example.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_example.png new file mode 100644 index 0000000000..73a096adc9 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_example.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_ranges.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_ranges.png new file mode 100644 index 0000000000..432977cab4 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_ranges.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step1.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step1.png new file mode 100644 index 0000000000..e0ac766321 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step1.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step2.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step2.png new file mode 100644 index 0000000000..4cde1b576f Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step2.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step3.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step3.png new file mode 100644 index 0000000000..f1040637a2 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step3.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step4.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step4.png new file mode 100644 index 0000000000..886f333404 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step4.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step5.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step5.png new file mode 100644 index 0000000000..da2a058d82 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step5.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step6.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step6.png new file mode 100644 index 0000000000..0cc481cb29 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step6.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step7.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step7.png new file mode 100644 index 0000000000..243af9ce7f Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step7.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.md b/zh-hant/docs/chapter_searching/binary_search.md new file mode 100755 index 0000000000..be2b6dd544 --- /dev/null +++ b/zh-hant/docs/chapter_searching/binary_search.md @@ -0,0 +1,83 @@ +# 二分搜尋 + +二分搜尋(binary search)是一種基於分治策略的高效搜尋演算法。它利用資料的有序性,每輪縮小一半搜尋範圍,直至找到目標元素或搜尋區間為空為止。 + +!!! question + + 給定一個長度為 $n$ 的陣列 `nums` ,元素按從小到大的順序排列且不重複。請查詢並返回元素 `target` 在該陣列中的索引。若陣列不包含該元素,則返回 $-1$ 。示例如下圖所示。 + +![二分搜尋示例資料](binary_search.assets/binary_search_example.png) + +如下圖所示,我們先初始化指標 $i = 0$ 和 $j = n - 1$ ,分別指向陣列首元素和尾元素,代表搜尋區間 $[0, n - 1]$ 。請注意,中括號表示閉區間,其包含邊界值本身。 + +接下來,迴圈執行以下兩步。 + +1. 計算中點索引 $m = \lfloor {(i + j) / 2} \rfloor$ ,其中 $\lfloor \: \rfloor$ 表示向下取整操作。 +2. 判斷 `nums[m]` 和 `target` 的大小關係,分為以下三種情況。 + 1. 當 `nums[m] < target` 時,說明 `target` 在區間 $[m + 1, j]$ 中,因此執行 $i = m + 1$ 。 + 2. 當 `nums[m] > target` 時,說明 `target` 在區間 $[i, m - 1]$ 中,因此執行 $j = m - 1$ 。 + 3. 當 `nums[m] = target` 時,說明找到 `target` ,因此返回索引 $m$ 。 + +若陣列不包含目標元素,搜尋區間最終會縮小為空。此時返回 $-1$ 。 + +=== "<1>" + ![二分搜尋流程](binary_search.assets/binary_search_step1.png) + +=== "<2>" + ![binary_search_step2](binary_search.assets/binary_search_step2.png) + +=== "<3>" + ![binary_search_step3](binary_search.assets/binary_search_step3.png) + +=== "<4>" + ![binary_search_step4](binary_search.assets/binary_search_step4.png) + +=== "<5>" + ![binary_search_step5](binary_search.assets/binary_search_step5.png) + +=== "<6>" + ![binary_search_step6](binary_search.assets/binary_search_step6.png) + +=== "<7>" + ![binary_search_step7](binary_search.assets/binary_search_step7.png) + +值得注意的是,由於 $i$ 和 $j$ 都是 `int` 型別,**因此 $i + j$ 可能會超出 `int` 型別的取值範圍**。為了避免大數越界,我們通常採用公式 $m = \lfloor {i + (j - i) / 2} \rfloor$ 來計算中點。 + +程式碼如下所示: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search} +``` + +**時間複雜度為 $O(\log n)$** :在二分迴圈中,區間每輪縮小一半,因此迴圈次數為 $\log_2 n$ 。 + +**空間複雜度為 $O(1)$** :指標 $i$ 和 $j$ 使用常數大小空間。 + +## 區間表示方法 + +除了上述雙閉區間外,常見的區間表示還有“左閉右開”區間,定義為 $[0, n)$ ,即左邊界包含自身,右邊界不包含自身。在該表示下,區間 $[i, j)$ 在 $i = j$ 時為空。 + +我們可以基於該表示實現具有相同功能的二分搜尋演算法: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search_lcro} +``` + +如下圖所示,在兩種區間表示下,二分搜尋演算法的初始化、迴圈條件和縮小區間操作皆有所不同。 + +由於“雙閉區間”表示中的左右邊界都被定義為閉區間,因此透過指標 $i$ 和指標 $j$ 縮小區間的操作也是對稱的。這樣更不容易出錯,**因此一般建議採用“雙閉區間”的寫法**。 + +![兩種區間定義](binary_search.assets/binary_search_ranges.png) + +## 優點與侷限性 + +二分搜尋在時間和空間方面都有較好的效能。 + +- 二分搜尋的時間效率高。在大資料量下,對數階的時間複雜度具有顯著優勢。例如,當資料大小 $n = 2^{20}$ 時,線性查詢需要 $2^{20} = 1048576$ 輪迴圈,而二分搜尋僅需 $\log_2 2^{20} = 20$ 輪迴圈。 +- 二分搜尋無須額外空間。相較於需要藉助額外空間的搜尋演算法(例如雜湊查詢),二分搜尋更加節省空間。 + +然而,二分搜尋並非適用於所有情況,主要有以下原因。 + +- 二分搜尋僅適用於有序資料。若輸入資料無序,為了使用二分搜尋而專門進行排序,得不償失。因為排序演算法的時間複雜度通常為 $O(n \log n)$ ,比線性查詢和二分搜尋都更高。對於頻繁插入元素的場景,為保持陣列有序性,需要將元素插入到特定位置,時間複雜度為 $O(n)$ ,也是非常昂貴的。 +- 二分搜尋僅適用於陣列。二分搜尋需要跳躍式(非連續地)訪問元素,而在鏈結串列中執行跳躍式訪問的效率較低,因此不適合應用在鏈結串列或基於鏈結串列實現的資料結構。 +- 小資料量下,線性查詢效能更佳。線上性查詢中,每輪只需 1 次判斷操作;而在二分搜尋中,需要 1 次加法、1 次除法、1 ~ 3 次判斷操作、1 次加法(減法),共 4 ~ 6 個單元操作;因此,當資料量 $n$ 較小時,線性查詢反而比二分搜尋更快。 diff --git a/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png new file mode 100644 index 0000000000..cc379b5050 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png new file mode 100644 index 0000000000..c77758dc4c Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_edge.md b/zh-hant/docs/chapter_searching/binary_search_edge.md new file mode 100644 index 0000000000..b015e0cef3 --- /dev/null +++ b/zh-hant/docs/chapter_searching/binary_search_edge.md @@ -0,0 +1,56 @@ +# 二分搜尋邊界 + +## 查詢左邊界 + +!!! question + + 給定一個長度為 $n$ 的有序陣列 `nums` ,其中可能包含重複元素。請返回陣列中最左一個元素 `target` 的索引。若陣列中不包含該元素,則返回 $-1$ 。 + +回憶二分搜尋插入點的方法,搜尋完成後 $i$ 指向最左一個 `target` ,**因此查詢插入點本質上是在查詢最左一個 `target` 的索引**。 + +考慮透過查詢插入點的函式實現查詢左邊界。請注意,陣列中可能不包含 `target` ,這種情況可能導致以下兩種結果。 + +- 插入點的索引 $i$ 越界。 +- 元素 `nums[i]` 與 `target` 不相等。 + +當遇到以上兩種情況時,直接返回 $-1$ 即可。程式碼如下所示: + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} +``` + +## 查詢右邊界 + +那麼如何查詢最右一個 `target` 呢?最直接的方式是修改程式碼,替換在 `nums[m] == target` 情況下的指標收縮操作。程式碼在此省略,有興趣的讀者可以自行實現。 + +下面我們介紹兩種更加取巧的方法。 + +### 複用查詢左邊界 + +實際上,我們可以利用查詢最左元素的函式來查詢最右元素,具體方法為:**將查詢最右一個 `target` 轉化為查詢最左一個 `target + 1`**。 + +如下圖所示,查詢完成後,指標 $i$ 指向最左一個 `target + 1`(如果存在),而 $j$ 指向最右一個 `target` ,**因此返回 $j$ 即可**。 + +![將查詢右邊界轉化為查詢左邊界](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) + +請注意,返回的插入點是 $i$ ,因此需要將其減 $1$ ,從而獲得 $j$ : + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} +``` + +### 轉化為查詢元素 + +我們知道,當陣列不包含 `target` 時,最終 $i$ 和 $j$ 會分別指向首個大於、小於 `target` 的元素。 + +因此,如下圖所示,我們可以構造一個陣列中不存在的元素,用於查詢左右邊界。 + +- 查詢最左一個 `target` :可以轉化為查詢 `target - 0.5` ,並返回指標 $i$ 。 +- 查詢最右一個 `target` :可以轉化為查詢 `target + 0.5` ,並返回指標 $j$ 。 + +![將查詢邊界轉化為查詢元素](binary_search_edge.assets/binary_search_edge_by_element.png) + +程式碼在此省略,以下兩點值得注意。 + +- 給定陣列不包含小數,這意味著我們無須關心如何處理相等的情況。 +- 因為該方法引入了小數,所以需要將函式中的變數 `target` 改為浮點數型別(Python 無須改動)。 diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png new file mode 100644 index 0000000000..1f4dc50eb3 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png new file mode 100644 index 0000000000..1b07b3cfd3 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png new file mode 100644 index 0000000000..9522685d5d Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png new file mode 100644 index 0000000000..f0d790e125 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png new file mode 100644 index 0000000000..0a3122bd91 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png new file mode 100644 index 0000000000..6892f0e41f Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png new file mode 100644 index 0000000000..422839eb15 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png new file mode 100644 index 0000000000..b8abf6d291 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png new file mode 100644 index 0000000000..06080b0c84 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png new file mode 100644 index 0000000000..5a2d2ced70 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.md b/zh-hant/docs/chapter_searching/binary_search_insertion.md new file mode 100644 index 0000000000..bfc0d51817 --- /dev/null +++ b/zh-hant/docs/chapter_searching/binary_search_insertion.md @@ -0,0 +1,91 @@ +# 二分搜尋插入點 + +二分搜尋不僅可用於搜尋目標元素,還可用於解決許多變種問題,比如搜尋目標元素的插入位置。 + +## 無重複元素的情況 + +!!! question + + 給定一個長度為 $n$ 的有序陣列 `nums` 和一個元素 `target` ,陣列不存在重複元素。現將 `target` 插入陣列 `nums` 中,並保持其有序性。若陣列中已存在元素 `target` ,則插入到其左方。請返回插入後 `target` 在陣列中的索引。示例如下圖所示。 + +![二分搜尋插入點示例資料](binary_search_insertion.assets/binary_search_insertion_example.png) + +如果想複用上一節的二分搜尋程式碼,則需要回答以下兩個問題。 + +**問題一**:當陣列中包含 `target` 時,插入點的索引是否是該元素的索引? + +題目要求將 `target` 插入到相等元素的左邊,這意味著新插入的 `target` 替換了原來 `target` 的位置。也就是說,**當陣列包含 `target` 時,插入點的索引就是該 `target` 的索引**。 + +**問題二**:當陣列中不存在 `target` 時,插入點是哪個元素的索引? + +進一步思考二分搜尋過程:當 `nums[m] < target` 時 $i$ 移動,這意味著指標 $i$ 在向大於等於 `target` 的元素靠近。同理,指標 $j$ 始終在向小於等於 `target` 的元素靠近。 + +因此二分結束時一定有:$i$ 指向首個大於 `target` 的元素,$j$ 指向首個小於 `target` 的元素。**易得當陣列不包含 `target` 時,插入索引為 $i$** 。程式碼如下所示: + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} +``` + +## 存在重複元素的情況 + +!!! question + + 在上一題的基礎上,規定陣列可能包含重複元素,其餘不變。 + +假設陣列中存在多個 `target` ,則普通二分搜尋只能返回其中一個 `target` 的索引,**而無法確定該元素的左邊和右邊還有多少 `target`**。 + +題目要求將目標元素插入到最左邊,**所以我們需要查詢陣列中最左一個 `target` 的索引**。初步考慮透過下圖所示的步驟實現。 + +1. 執行二分搜尋,得到任意一個 `target` 的索引,記為 $k$ 。 +2. 從索引 $k$ 開始,向左進行線性走訪,當找到最左邊的 `target` 時返回。 + +![線性查詢重複元素的插入點](binary_search_insertion.assets/binary_search_insertion_naive.png) + +此方法雖然可用,但其包含線性查詢,因此時間複雜度為 $O(n)$ 。當陣列中存在很多重複的 `target` 時,該方法效率很低。 + +現考慮拓展二分搜尋程式碼。如下圖所示,整體流程保持不變,每輪先計算中點索引 $m$ ,再判斷 `target` 和 `nums[m]` 的大小關係,分為以下幾種情況。 + +- 當 `nums[m] < target` 或 `nums[m] > target` 時,說明還沒有找到 `target` ,因此採用普通二分搜尋的縮小區間操作,**從而使指標 $i$ 和 $j$ 向 `target` 靠近**。 +- 當 `nums[m] == target` 時,說明小於 `target` 的元素在區間 $[i, m - 1]$ 中,因此採用 $j = m - 1$ 來縮小區間,**從而使指標 $j$ 向小於 `target` 的元素靠近**。 + +迴圈完成後,$i$ 指向最左邊的 `target` ,$j$ 指向首個小於 `target` 的元素,**因此索引 $i$ 就是插入點**。 + +=== "<1>" + ![二分搜尋重複元素的插入點的步驟](binary_search_insertion.assets/binary_search_insertion_step1.png) + +=== "<2>" + ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) + +=== "<3>" + ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) + +=== "<4>" + ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) + +=== "<5>" + ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) + +=== "<6>" + ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) + +=== "<7>" + ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) + +=== "<8>" + ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) + +觀察以下程式碼,判斷分支 `nums[m] > target` 和 `nums[m] == target` 的操作相同,因此兩者可以合併。 + +即便如此,我們仍然可以將判斷條件保持展開,因為其邏輯更加清晰、可讀性更好。 + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} +``` + +!!! tip + + 本節的程式碼都是“雙閉區間”寫法。有興趣的讀者可以自行實現“左閉右開”寫法。 + +總的來看,二分搜尋無非就是給指標 $i$ 和 $j$ 分別設定搜尋目標,目標可能是一個具體的元素(例如 `target` ),也可能是一個元素範圍(例如小於 `target` 的元素)。 + +在不斷的迴圈二分中,指標 $i$ 和 $j$ 都逐漸逼近預先設定的目標。最終,它們或是成功找到答案,或是越過邊界後停止。 diff --git a/zh-hant/docs/chapter_searching/index.md b/zh-hant/docs/chapter_searching/index.md new file mode 100644 index 0000000000..83a2e62eca --- /dev/null +++ b/zh-hant/docs/chapter_searching/index.md @@ -0,0 +1,9 @@ +# 搜尋 + +![搜尋](../assets/covers/chapter_searching.jpg) + +!!! abstract + + 搜尋是一場未知的冒險,我們或許需要走遍神秘空間的每個角落,又或許可以快速鎖定目標。 + + 在這場尋覓之旅中,每一次探索都可能得到一個未曾料想的答案。 diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png new file mode 100644 index 0000000000..985012db71 Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png new file mode 100644 index 0000000000..209ae5b2c5 Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png new file mode 100644 index 0000000000..dd0d0f4554 Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png new file mode 100644 index 0000000000..cac6df1e4c Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.md b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.md new file mode 100755 index 0000000000..b59cb3534b --- /dev/null +++ b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.md @@ -0,0 +1,47 @@ +# 雜湊最佳化策略 + +在演算法題中,**我們常透過將線性查詢替換為雜湊查詢來降低演算法的時間複雜度**。我們藉助一個演算法題來加深理解。 + +!!! question + + 給定一個整數陣列 `nums` 和一個目標元素 `target` ,請在陣列中搜索“和”為 `target` 的兩個元素,並返回它們的陣列索引。返回任意一個解即可。 + +## 線性查詢:以時間換空間 + +考慮直接走訪所有可能的組合。如下圖所示,我們開啟一個兩層迴圈,在每輪中判斷兩個整數的和是否為 `target` ,若是,則返回它們的索引。 + +![線性查詢求解兩數之和](replace_linear_by_hashing.assets/two_sum_brute_force.png) + +程式碼如下所示: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_brute_force} +``` + +此方法的時間複雜度為 $O(n^2)$ ,空間複雜度為 $O(1)$ ,在大資料量下非常耗時。 + +## 雜湊查詢:以空間換時間 + +考慮藉助一個雜湊表,鍵值對分別為陣列元素和元素索引。迴圈走訪陣列,每輪執行下圖所示的步驟。 + +1. 判斷數字 `target - nums[i]` 是否在雜湊表中,若是,則直接返回這兩個元素的索引。 +2. 將鍵值對 `nums[i]` 和索引 `i` 新增進雜湊表。 + +=== "<1>" + ![輔助雜湊表求解兩數之和](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + +=== "<2>" + ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) + +=== "<3>" + ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) + +實現程式碼如下所示,僅需單層迴圈即可: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_hash_table} +``` + +此方法透過雜湊查詢將時間複雜度從 $O(n^2)$ 降至 $O(n)$ ,大幅提升執行效率。 + +由於需要維護一個額外的雜湊表,因此空間複雜度為 $O(n)$ 。**儘管如此,該方法的整體時空效率更為均衡,因此它是本題的最優解法**。 diff --git a/zh-hant/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png new file mode 100644 index 0000000000..de6e20869f Binary files /dev/null and b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png differ diff --git a/zh-hant/docs/chapter_searching/searching_algorithm_revisited.md b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.md new file mode 100644 index 0000000000..0ce25d7220 --- /dev/null +++ b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.md @@ -0,0 +1,84 @@ +# 重識搜尋演算法 + +搜尋演算法(searching algorithm)用於在資料結構(例如陣列、鏈結串列、樹或圖)中搜索一個或一組滿足特定條件的元素。 + +搜尋演算法可根據實現思路分為以下兩類。 + +- **透過走訪資料結構來定位目標元素**,例如陣列、鏈結串列、樹和圖的走訪等。 +- **利用資料組織結構或資料包含的先驗資訊,實現高效元素查詢**,例如二分搜尋、雜湊查詢和二元搜尋樹查詢等。 + +不難發現,這些知識點都已在前面的章節中介紹過,因此搜尋演算法對於我們來說並不陌生。在本節中,我們將從更加系統的視角切入,重新審視搜尋演算法。 + +## 暴力搜尋 + +暴力搜尋透過走訪資料結構的每個元素來定位目標元素。 + +- “線性搜尋”適用於陣列和鏈結串列等線性資料結構。它從資料結構的一端開始,逐個訪問元素,直到找到目標元素或到達另一端仍沒有找到目標元素為止。 +- “廣度優先搜尋”和“深度優先搜尋”是圖和樹的兩種走訪策略。廣度優先搜尋從初始節點開始逐層搜尋,由近及遠地訪問各個節點。深度優先搜尋從初始節點開始,沿著一條路徑走到頭,再回溯並嘗試其他路徑,直到走訪完整個資料結構。 + +暴力搜尋的優點是簡單且通用性好,**無須對資料做預處理和藉助額外的資料結構**。 + +然而,**此類演算法的時間複雜度為 $O(n)$** ,其中 $n$ 為元素數量,因此在資料量較大的情況下效能較差。 + +## 自適應搜尋 + +自適應搜尋利用資料的特有屬性(例如有序性)來最佳化搜尋過程,從而更高效地定位目標元素。 + +- “二分搜尋”利用資料的有序性實現高效查詢,僅適用於陣列。 +- “雜湊查詢”利用雜湊表將搜尋資料和目標資料建立為鍵值對對映,從而實現查詢操作。 +- “樹查詢”在特定的樹結構(例如二元搜尋樹)中,基於比較節點值來快速排除節點,從而定位目標元素。 + +此類演算法的優點是效率高,**時間複雜度可達到 $O(\log n)$ 甚至 $O(1)$** 。 + +然而,**使用這些演算法往往需要對資料進行預處理**。例如,二分搜尋需要預先對陣列進行排序,雜湊查詢和樹查詢都需要藉助額外的資料結構,維護這些資料結構也需要額外的時間和空間開銷。 + +!!! tip + + 自適應搜尋演算法常被稱為查詢演算法,**主要用於在特定資料結構中快速檢索目標元素**。 + +## 搜尋方法選取 + +給定大小為 $n$ 的一組資料,我們可以使用線性搜尋、二分搜尋、樹查詢、雜湊查詢等多種方法從中搜索目標元素。各個方法的工作原理如下圖所示。 + +![多種搜尋策略](searching_algorithm_revisited.assets/searching_algorithms.png) + +上述幾種方法的操作效率與特性如下表所示。 + +

  查詢演算法效率對比

+ +| | 線性搜尋 | 二分搜尋 | 樹查詢 | 雜湊查詢 | +| ------------ | -------- | ------------------ | ------------------ | --------------- | +| 查詢元素 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | +| 插入元素 | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 刪除元素 | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 額外空間 | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | +| 資料預處理 | / | 排序 $O(n \log n)$ | 建樹 $O(n \log n)$ | 建雜湊表 $O(n)$ | +| 資料是否有序 | 無序 | 有序 | 有序 | 無序 | + +搜尋演算法的選擇還取決於資料體量、搜尋效能要求、資料查詢與更新頻率等。 + +**線性搜尋** + +- 通用性較好,無須任何資料預處理操作。假如我們僅需查詢一次資料,那麼其他三種方法的資料預處理的時間比線性搜尋的時間還要更長。 +- 適用於體量較小的資料,此情況下時間複雜度對效率影響較小。 +- 適用於資料更新頻率較高的場景,因為該方法不需要對資料進行任何額外維護。 + +**二分搜尋** + +- 適用於大資料量的情況,效率表現穩定,最差時間複雜度為 $O(\log n)$ 。 +- 資料量不能過大,因為儲存陣列需要連續的記憶體空間。 +- 不適用於高頻增刪資料的場景,因為維護有序陣列的開銷較大。 + +**雜湊查詢** + +- 適合對查詢效能要求很高的場景,平均時間複雜度為 $O(1)$ 。 +- 不適合需要有序資料或範圍查詢的場景,因為雜湊表無法維護資料的有序性。 +- 對雜湊函式和雜湊衝突處理策略的依賴性較高,具有較大的效能劣化風險。 +- 不適合資料量過大的情況,因為雜湊表需要額外空間來最大程度地減少衝突,從而提供良好的查詢效能。 + +**樹查詢** + +- 適用於海量資料,因為樹節點在記憶體中是分散儲存的。 +- 適合需要維護有序資料或範圍查詢的場景。 +- 在持續增刪節點的過程中,二元搜尋樹可能產生傾斜,時間複雜度劣化至 $O(n)$ 。 +- 若使用 AVL 樹或紅黑樹,則各項操作可在 $O(\log n)$ 效率下穩定執行,但維護樹平衡的操作會增加額外的開銷。 diff --git a/zh-hant/docs/chapter_searching/summary.md b/zh-hant/docs/chapter_searching/summary.md new file mode 100644 index 0000000000..964158c9ab --- /dev/null +++ b/zh-hant/docs/chapter_searching/summary.md @@ -0,0 +1,8 @@ +# 小結 + +- 二分搜尋依賴資料的有序性,透過迴圈逐步縮減一半搜尋區間來進行查詢。它要求輸入資料有序,且僅適用於陣列或基於陣列實現的資料結構。 +- 暴力搜尋透過走訪資料結構來定位資料。線性搜尋適用於陣列和鏈結串列,廣度優先搜尋和深度優先搜尋適用於圖和樹。此類演算法通用性好,無須對資料進行預處理,但時間複雜度 $O(n)$ 較高。 +- 雜湊查詢、樹查詢和二分搜尋屬於高效搜尋方法,可在特定資料結構中快速定位目標元素。此類演算法效率高,時間複雜度可達 $O(\log n)$ 甚至 $O(1)$ ,但通常需要藉助額外資料結構。 +- 實際中,我們需要對資料體量、搜尋效能要求、資料查詢和更新頻率等因素進行具體分析,從而選擇合適的搜尋方法。 +- 線性搜尋適用於小型或頻繁更新的資料;二分搜尋適用於大型、排序的資料;雜湊查詢適用於對查詢效率要求較高且無須範圍查詢的資料;樹查詢適用於需要維護順序和支持範圍查詢的大型動態資料。 +- 用雜湊查詢替換線性查詢是一種常用的最佳化執行時間的策略,可將時間複雜度從 $O(n)$ 降至 $O(1)$ 。 diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png new file mode 100644 index 0000000000..e2698e78fd Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png new file mode 100644 index 0000000000..380f218788 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png new file mode 100644 index 0000000000..f6a158ca70 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png new file mode 100644 index 0000000000..bcef7a3f39 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png new file mode 100644 index 0000000000..7b1ce1e93a Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png new file mode 100644 index 0000000000..44896afd98 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png new file mode 100644 index 0000000000..69d37434c2 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png new file mode 100644 index 0000000000..bd44347ff8 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.md b/zh-hant/docs/chapter_sorting/bubble_sort.md new file mode 100755 index 0000000000..162394d5d5 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/bubble_sort.md @@ -0,0 +1,59 @@ +# 泡沫排序 + +泡沫排序(bubble sort)透過連續地比較與交換相鄰元素實現排序。這個過程就像氣泡從底部升到頂部一樣,因此得名泡沫排序。 + +如下圖所示,冒泡過程可以利用元素交換操作來模擬:從陣列最左端開始向右走訪,依次比較相鄰元素大小,如果“左元素 > 右元素”就交換二者。走訪完成後,最大的元素會被移動到陣列的最右端。 + +=== "<1>" + ![利用元素交換操作模擬冒泡](bubble_sort.assets/bubble_operation_step1.png) + +=== "<2>" + ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) + +=== "<3>" + ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) + +=== "<4>" + ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) + +=== "<5>" + ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) + +=== "<6>" + ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) + +=== "<7>" + ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) + +## 演算法流程 + +設陣列的長度為 $n$ ,泡沫排序的步驟如下圖所示。 + +1. 首先,對 $n$ 個元素執行“冒泡”,**將陣列的最大元素交換至正確位置**。 +2. 接下來,對剩餘 $n - 1$ 個元素執行“冒泡”,**將第二大元素交換至正確位置**。 +3. 以此類推,經過 $n - 1$ 輪“冒泡”後,**前 $n - 1$ 大的元素都被交換至正確位置**。 +4. 僅剩的一個元素必定是最小元素,無須排序,因此陣列排序完成。 + +![泡沫排序流程](bubble_sort.assets/bubble_sort_overview.png) + +示例程式碼如下: + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort} +``` + +## 效率最佳化 + +我們發現,如果某輪“冒泡”中沒有執行任何交換操作,說明陣列已經完成排序,可直接返回結果。因此,可以增加一個標誌位 `flag` 來監測這種情況,一旦出現就立即返回。 + +經過最佳化,泡沫排序的最差時間複雜度和平均時間複雜度仍為 $O(n^2)$ ;但當輸入陣列完全有序時,可達到最佳時間複雜度 $O(n)$ 。 + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n^2)$、自適應排序**:各輪“冒泡”走訪的陣列長度依次為 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,總和為 $(n - 1) n / 2$ 。在引入 `flag` 最佳化後,最佳時間複雜度可達到 $O(n)$ 。 +- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 +- **穩定排序**:由於在“冒泡”中遇到相等元素不交換。 diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png b/zh-hant/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png new file mode 100644 index 0000000000..d486cd2d16 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png new file mode 100644 index 0000000000..3f1cdff1ab Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png differ diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png new file mode 100644 index 0000000000..95905513cb Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png differ diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.md b/zh-hant/docs/chapter_sorting/bucket_sort.md new file mode 100644 index 0000000000..e38cb49854 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/bucket_sort.md @@ -0,0 +1,45 @@ +# 桶排序 + +前述幾種排序演算法都屬於“基於比較的排序演算法”,它們透過比較元素間的大小來實現排序。此類排序演算法的時間複雜度無法超越 $O(n \log n)$ 。接下來,我們將探討幾種“非比較排序演算法”,它們的時間複雜度可以達到線性階。 + +桶排序(bucket sort)是分治策略的一個典型應用。它透過設定一些具有大小順序的桶,每個桶對應一個數據範圍,將資料平均分配到各個桶中;然後,在每個桶內部分別執行排序;最終按照桶的順序將所有資料合併。 + +## 演算法流程 + +考慮一個長度為 $n$ 的陣列,其元素是範圍 $[0, 1)$ 內的浮點數。桶排序的流程如下圖所示。 + +1. 初始化 $k$ 個桶,將 $n$ 個元素分配到 $k$ 個桶中。 +2. 對每個桶分別執行排序(這裡採用程式語言的內建排序函式)。 +3. 按照桶從小到大的順序合併結果。 + +![桶排序演算法流程](bucket_sort.assets/bucket_sort_overview.png) + +程式碼如下所示: + +```src +[file]{bucket_sort}-[class]{}-[func]{bucket_sort} +``` + +## 演算法特性 + +桶排序適用於處理體量很大的資料。例如,輸入資料包含 100 萬個元素,由於空間限制,系統記憶體無法一次性載入所有資料。此時,可以將資料分成 1000 個桶,然後分別對每個桶進行排序,最後將結果合併。 + +- **時間複雜度為 $O(n + k)$** :假設元素在各個桶內平均分佈,那麼每個桶內的元素數量為 $\frac{n}{k}$ 。假設排序單個桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 時間,則排序所有桶使用 $O(n \log\frac{n}{k})$ 時間。**當桶數量 $k$ 比較大時,時間複雜度則趨向於 $O(n)$** 。合併結果時需要走訪所有桶和元素,花費 $O(n + k)$ 時間。在最差情況下,所有資料被分配到一個桶中,且排序該桶使用 $O(n^2)$ 時間。 +- **空間複雜度為 $O(n + k)$、非原地排序**:需要藉助 $k$ 個桶和總共 $n$ 個元素的額外空間。 +- 桶排序是否穩定取決於排序桶內元素的演算法是否穩定。 + +## 如何實現平均分配 + +桶排序的時間複雜度理論上可以達到 $O(n)$ ,**關鍵在於將元素均勻分配到各個桶中**,因為實際資料往往不是均勻分佈的。例如,我們想要將淘寶上的所有商品按價格範圍平均分配到 10 個桶中,但商品價格分佈不均,低於 100 元的非常多,高於 1000 元的非常少。若將價格區間平均劃分為 10 個,各個桶中的商品數量差距會非常大。 + +為實現平均分配,我們可以先設定一條大致的分界線,將資料粗略地分到 3 個桶中。**分配完畢後,再將商品較多的桶繼續劃分為 3 個桶,直至所有桶中的元素數量大致相等**。 + +如下圖所示,這種方法本質上是建立一棵遞迴樹,目標是讓葉節點的值儘可能平均。當然,不一定要每輪將資料劃分為 3 個桶,具體劃分方式可根據資料特點靈活選擇。 + +![遞迴劃分桶](bucket_sort.assets/scatter_in_buckets_recursively.png) + +如果我們提前知道商品價格的機率分佈,**則可以根據資料機率分佈設定每個桶的價格分界線**。值得注意的是,資料分佈並不一定需要特意統計,也可以根據資料特點採用某種機率模型進行近似。 + +如下圖所示,我們假設商品價格服從正態分佈,這樣就可以合理地設定價格區間,從而將商品平均分配到各個桶中。 + +![根據機率分佈劃分桶](bucket_sort.assets/scatter_in_buckets_distribution.png) diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png new file mode 100644 index 0000000000..4111234a65 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png new file mode 100644 index 0000000000..cb986162cf Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png new file mode 100644 index 0000000000..4a51c2f728 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png new file mode 100644 index 0000000000..ef7402409e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png new file mode 100644 index 0000000000..f626b95d58 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png new file mode 100644 index 0000000000..337b991173 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png new file mode 100644 index 0000000000..10bc31956b Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png new file mode 100644 index 0000000000..2b942ffd5f Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png new file mode 100644 index 0000000000..8e92bde542 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.md b/zh-hant/docs/chapter_sorting/counting_sort.md new file mode 100644 index 0000000000..aaeeecaae2 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/counting_sort.md @@ -0,0 +1,84 @@ +# 計數排序 + +計數排序(counting sort)透過統計元素數量來實現排序,通常應用於整數陣列。 + +## 簡單實現 + +先來看一個簡單的例子。給定一個長度為 $n$ 的陣列 `nums` ,其中的元素都是“非負整數”,計數排序的整體流程如下圖所示。 + +1. 走訪陣列,找出其中的最大數字,記為 $m$ ,然後建立一個長度為 $m + 1$ 的輔助陣列 `counter` 。 +2. **藉助 `counter` 統計 `nums` 中各數字的出現次數**,其中 `counter[num]` 對應數字 `num` 的出現次數。統計方法很簡單,只需走訪 `nums`(設當前數字為 `num`),每輪將 `counter[num]` 增加 $1$ 即可。 +3. **由於 `counter` 的各個索引天然有序,因此相當於所有數字已經排序好了**。接下來,我們走訪 `counter` ,根據各數字出現次數從小到大的順序填入 `nums` 即可。 + +![計數排序流程](counting_sort.assets/counting_sort_overview.png) + +程式碼如下所示: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort_naive} +``` + +!!! note "計數排序與桶排序的關聯" + + 從桶排序的角度看,我們可以將計數排序中的計數陣列 `counter` 的每個索引視為一個桶,將統計數量的過程看作將各個元素分配到對應的桶中。本質上,計數排序是桶排序在整型資料下的一個特例。 + +## 完整實現 + +細心的讀者可能發現了,**如果輸入資料是物件,上述步驟 `3.` 就失效了**。假設輸入資料是商品物件,我們想按照商品價格(類別的成員變數)對商品進行排序,而上述演算法只能給出價格的排序結果。 + +那麼如何才能得到原資料的排序結果呢?我們首先計算 `counter` 的“前綴和”。顧名思義,索引 `i` 處的前綴和 `prefix[i]` 等於陣列前 `i` 個元素之和: + +$$ +\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} +$$ + +**前綴和具有明確的意義,`prefix[num] - 1` 代表元素 `num` 在結果陣列 `res` 中最後一次出現的索引**。這個資訊非常關鍵,因為它告訴我們各個元素應該出現在結果陣列的哪個位置。接下來,我們倒序走訪原陣列 `nums` 的每個元素 `num` ,在每輪迭代中執行以下兩步。 + +1. 將 `num` 填入陣列 `res` 的索引 `prefix[num] - 1` 處。 +2. 令前綴和 `prefix[num]` 減小 $1$ ,從而得到下次放置 `num` 的索引。 + +走訪完成後,陣列 `res` 中就是排序好的結果,最後使用 `res` 覆蓋原陣列 `nums` 即可。下圖展示了完整的計數排序流程。 + +=== "<1>" + ![計數排序步驟](counting_sort.assets/counting_sort_step1.png) + +=== "<2>" + ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) + +=== "<3>" + ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) + +=== "<4>" + ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) + +=== "<5>" + ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) + +=== "<6>" + ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) + +=== "<7>" + ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) + +=== "<8>" + ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) + +計數排序的實現程式碼如下所示: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n + m)$、非自適應排序** :涉及走訪 `nums` 和走訪 `counter` ,都使用線性時間。一般情況下 $n \gg m$ ,時間複雜度趨於 $O(n)$ 。 +- **空間複雜度為 $O(n + m)$、非原地排序**:藉助了長度分別為 $n$ 和 $m$ 的陣列 `res` 和 `counter` 。 +- **穩定排序**:由於向 `res` 中填充元素的順序是“從右向左”的,因此倒序走訪 `nums` 可以避免改變相等元素之間的相對位置,從而實現穩定排序。實際上,正序走訪 `nums` 也可以得到正確的排序結果,但結果是非穩定的。 + +## 侷限性 + +看到這裡,你也許會覺得計數排序非常巧妙,僅透過統計數量就可以實現高效的排序。然而,使用計數排序的前置條件相對較為嚴格。 + +**計數排序只適用於非負整數**。若想將其用於其他型別的資料,需要確保這些資料可以轉換為非負整數,並且在轉換過程中不能改變各個元素之間的相對大小關係。例如,對於包含負數的整數陣列,可以先給所有數字加上一個常數,將全部數字轉化為正數,排序完成後再轉換回去。 + +**計數排序適用於資料量大但資料範圍較小的情況**。比如,在上述示例中 $m$ 不能太大,否則會佔用過多空間。而當 $n \ll m$ 時,計數排序使用 $O(m)$ 時間,可能比 $O(n \log n)$ 的排序演算法還要慢。 diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png new file mode 100644 index 0000000000..eb99ebd607 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png new file mode 100644 index 0000000000..95889ba995 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png new file mode 100644 index 0000000000..7ad2c919fc Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png new file mode 100644 index 0000000000..c24d2e77ea Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png new file mode 100644 index 0000000000..c50b7a18cd Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png new file mode 100644 index 0000000000..f2b94f1f69 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png new file mode 100644 index 0000000000..4323531cd7 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png new file mode 100644 index 0000000000..183d66bf32 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png new file mode 100644 index 0000000000..9f2ab89d14 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png new file mode 100644 index 0000000000..64fdf38105 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png new file mode 100644 index 0000000000..4c3739d2ab Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png new file mode 100644 index 0000000000..3bfb7001e1 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.md b/zh-hant/docs/chapter_sorting/heap_sort.md new file mode 100644 index 0000000000..638c7d3fb0 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/heap_sort.md @@ -0,0 +1,73 @@ +# 堆積排序 + +!!! tip + + 閱讀本節前,請確保已學完“堆積”章節。 + +堆積排序(heap sort)是一種基於堆積資料結構實現的高效排序演算法。我們可以利用已經學過的“建堆積操作”和“元素出堆積操作”實現堆積排序。 + +1. 輸入陣列並建立小頂堆積,此時最小元素位於堆積頂。 +2. 不斷執行出堆積操作,依次記錄出堆積元素,即可得到從小到大排序的序列。 + +以上方法雖然可行,但需要藉助一個額外陣列來儲存彈出的元素,比較浪費空間。在實際中,我們通常使用一種更加優雅的實現方式。 + +## 演算法流程 + +設陣列的長度為 $n$ ,堆積排序的流程如下圖所示。 + +1. 輸入陣列並建立大頂堆積。完成後,最大元素位於堆積頂。 +2. 將堆積頂元素(第一個元素)與堆積底元素(最後一個元素)交換。完成交換後,堆積的長度減 $1$ ,已排序元素數量加 $1$ 。 +3. 從堆積頂元素開始,從頂到底執行堆積化操作(sift down)。完成堆積化後,堆積的性質得到修復。 +4. 迴圈執行第 `2.` 步和第 `3.` 步。迴圈 $n - 1$ 輪後,即可完成陣列排序。 + +!!! tip + + 實際上,元素出堆積操作中也包含第 `2.` 步和第 `3.` 步,只是多了一個彈出元素的步驟。 + +=== "<1>" + ![堆積排序步驟](heap_sort.assets/heap_sort_step1.png) + +=== "<2>" + ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) + +=== "<3>" + ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) + +=== "<4>" + ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) + +=== "<5>" + ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) + +=== "<6>" + ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) + +=== "<7>" + ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) + +=== "<8>" + ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) + +=== "<9>" + ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) + +=== "<10>" + ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) + +=== "<11>" + ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) + +=== "<12>" + ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) + +在程式碼實現中,我們使用了與“堆積”章節相同的從頂至底堆積化 `sift_down()` 函式。值得注意的是,由於堆積的長度會隨著提取最大元素而減小,因此我們需要給 `sift_down()` 函式新增一個長度參數 $n$ ,用於指定堆積的當前有效長度。程式碼如下所示: + +```src +[file]{heap_sort}-[class]{}-[func]{heap_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n \log n)$、非自適應排序**:建堆積操作使用 $O(n)$ 時間。從堆積中提取最大元素的時間複雜度為 $O(\log n)$ ,共迴圈 $n - 1$ 輪。 +- **空間複雜度為 $O(1)$、原地排序**:幾個指標變數使用 $O(1)$ 空間。元素交換和堆積化操作都是在原陣列上進行的。 +- **非穩定排序**:在交換堆積頂元素和堆積底元素時,相等元素的相對位置可能發生變化。 diff --git a/zh-hant/docs/chapter_sorting/index.md b/zh-hant/docs/chapter_sorting/index.md new file mode 100644 index 0000000000..a125ec36ad --- /dev/null +++ b/zh-hant/docs/chapter_sorting/index.md @@ -0,0 +1,9 @@ +# 排序 + +![排序](../assets/covers/chapter_sorting.jpg) + +!!! abstract + + 排序猶如一把將混亂變為秩序的魔法鑰匙,使我們能以更高效的方式理解與處理資料。 + + 無論是簡單的升序,還是複雜的分類排列,排序都向我們展示了資料的和諧美感。 diff --git a/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png new file mode 100644 index 0000000000..09fee6899e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png differ diff --git a/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png new file mode 100644 index 0000000000..cc2bc81be7 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/insertion_sort.md b/zh-hant/docs/chapter_sorting/insertion_sort.md new file mode 100755 index 0000000000..ab1573e9bb --- /dev/null +++ b/zh-hant/docs/chapter_sorting/insertion_sort.md @@ -0,0 +1,46 @@ +# 插入排序 + +插入排序(insertion sort)是一種簡單的排序演算法,它的工作原理與手動整理一副牌的過程非常相似。 + +具體來說,我們在未排序區間選擇一個基準元素,將該元素與其左側已排序區間的元素逐一比較大小,並將該元素插入到正確的位置。 + +下圖展示了陣列插入元素的操作流程。設基準元素為 `base` ,我們需要將從目標索引到 `base` 之間的所有元素向右移動一位,然後將 `base` 賦值給目標索引。 + +![單次插入操作](insertion_sort.assets/insertion_operation.png) + +## 演算法流程 + +插入排序的整體流程如下圖所示。 + +1. 初始狀態下,陣列的第 1 個元素已完成排序。 +2. 選取陣列的第 2 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 2 個元素已排序**。 +3. 選取第 3 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 3 個元素已排序**。 +4. 以此類推,在最後一輪中,選取最後一個元素作為 `base` ,將其插入到正確位置後,**所有元素均已排序**。 + +![插入排序流程](insertion_sort.assets/insertion_sort_overview.png) + +示例程式碼如下: + +```src +[file]{insertion_sort}-[class]{}-[func]{insertion_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n^2)$、自適應排序**:在最差情況下,每次插入操作分別需要迴圈 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此時間複雜度為 $O(n^2)$ 。在遇到有序資料時,插入操作會提前終止。當輸入陣列完全有序時,插入排序達到最佳時間複雜度 $O(n)$ 。 +- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 +- **穩定排序**:在插入操作過程中,我們會將元素插入到相等元素的右側,不會改變它們的順序。 + +## 插入排序的優勢 + +插入排序的時間複雜度為 $O(n^2)$ ,而我們即將學習的快速排序的時間複雜度為 $O(n \log n)$ 。儘管插入排序的時間複雜度更高,**但在資料量較小的情況下,插入排序通常更快**。 + +這個結論與線性查詢和二分搜尋的適用情況的結論類似。快速排序這類 $O(n \log n)$ 的演算法屬於基於分治策略的排序演算法,往往包含更多單元計算操作。而在資料量較小時,$n^2$ 和 $n \log n$ 的數值比較接近,複雜度不佔主導地位,每輪中的單元操作數量起到決定性作用。 + +實際上,許多程式語言(例如 Java)的內建排序函式採用了插入排序,大致思路為:對於長陣列,採用基於分治策略的排序演算法,例如快速排序;對於短陣列,直接使用插入排序。 + +雖然泡沫排序、選擇排序和插入排序的時間複雜度都為 $O(n^2)$ ,但在實際情況中,**插入排序的使用頻率顯著高於泡沫排序和選擇排序**,主要有以下原因。 + +- 泡沫排序基於元素交換實現,需要藉助一個臨時變數,共涉及 3 個單元操作;插入排序基於元素賦值實現,僅需 1 個單元操作。因此,**泡沫排序的計算開銷通常比插入排序更高**。 +- 選擇排序在任何情況下的時間複雜度都為 $O(n^2)$ 。**如果給定一組部分有序的資料,插入排序通常比選擇排序效率更高**。 +- 選擇排序不穩定,無法應用於多級排序。 diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png new file mode 100644 index 0000000000..439e569ec5 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png new file mode 100644 index 0000000000..d0ab5ab65e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png new file mode 100644 index 0000000000..6ee5527c21 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png new file mode 100644 index 0000000000..21a187cfc8 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png new file mode 100644 index 0000000000..40bfbb6852 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png new file mode 100644 index 0000000000..ea45368b5f Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png new file mode 100644 index 0000000000..261a2c8a6f Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png new file mode 100644 index 0000000000..eaa1be589b Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png new file mode 100644 index 0000000000..aebe4ab104 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png new file mode 100644 index 0000000000..b58e6b7272 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png new file mode 100644 index 0000000000..ecfd554c82 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.md b/zh-hant/docs/chapter_sorting/merge_sort.md new file mode 100755 index 0000000000..bdfca92f98 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/merge_sort.md @@ -0,0 +1,73 @@ +# 合併排序 + +合併排序(merge sort)是一種基於分治策略的排序演算法,包含下圖所示的“劃分”和“合併”階段。 + +1. **劃分階段**:透過遞迴不斷地將陣列從中點處分開,將長陣列的排序問題轉換為短陣列的排序問題。 +2. **合併階段**:當子陣列長度為 1 時終止劃分,開始合併,持續地將左右兩個較短的有序陣列合併為一個較長的有序陣列,直至結束。 + +![合併排序的劃分與合併階段](merge_sort.assets/merge_sort_overview.png) + +## 演算法流程 + +如下圖所示,“劃分階段”從頂至底遞迴地將陣列從中點切分為兩個子陣列。 + +1. 計算陣列中點 `mid` ,遞迴劃分左子陣列(區間 `[left, mid]` )和右子陣列(區間 `[mid + 1, right]` )。 +2. 遞迴執行步驟 `1.` ,直至子陣列區間長度為 1 時終止。 + +“合併階段”從底至頂地將左子陣列和右子陣列合併為一個有序陣列。需要注意的是,從長度為 1 的子陣列開始合併,合併階段中的每個子陣列都是有序的。 + +=== "<1>" + ![合併排序步驟](merge_sort.assets/merge_sort_step1.png) + +=== "<2>" + ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) + +=== "<3>" + ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) + +=== "<4>" + ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) + +=== "<5>" + ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) + +=== "<6>" + ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) + +=== "<7>" + ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) + +=== "<8>" + ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) + +=== "<9>" + ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) + +=== "<10>" + ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) + +觀察發現,合併排序與二元樹後序走訪的遞迴順序是一致的。 + +- **後序走訪**:先遞迴左子樹,再遞迴右子樹,最後處理根節點。 +- **合併排序**:先遞迴左子陣列,再遞迴右子陣列,最後處理合併。 + +合併排序的實現如以下程式碼所示。請注意,`nums` 的待合併區間為 `[left, right]` ,而 `tmp` 的對應區間為 `[0, right - left]` 。 + +```src +[file]{merge_sort}-[class]{}-[func]{merge_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n \log n)$、非自適應排序**:劃分產生高度為 $\log n$ 的遞迴樹,每層合併的總操作數量為 $n$ ,因此總體時間複雜度為 $O(n \log n)$ 。 +- **空間複雜度為 $O(n)$、非原地排序**:遞迴深度為 $\log n$ ,使用 $O(\log n)$ 大小的堆疊幀空間。合併操作需要藉助輔助陣列實現,使用 $O(n)$ 大小的額外空間。 +- **穩定排序**:在合併過程中,相等元素的次序保持不變。 + +## 鏈結串列排序 + +對於鏈結串列,合併排序相較於其他排序演算法具有顯著優勢,**可以將鏈結串列排序任務的空間複雜度最佳化至 $O(1)$** 。 + +- **劃分階段**:可以使用“迭代”替代“遞迴”來實現鏈結串列劃分工作,從而省去遞迴使用的堆疊幀空間。 +- **合併階段**:在鏈結串列中,節點增刪操作僅需改變引用(指標)即可實現,因此合併階段(將兩個短有序鏈結串列合併為一個長有序鏈結串列)無須建立額外鏈結串列。 + +具體實現細節比較複雜,有興趣的讀者可以查閱相關資料進行學習。 diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png new file mode 100644 index 0000000000..50b597542e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png new file mode 100644 index 0000000000..44f2b48652 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png new file mode 100644 index 0000000000..d93be6c87d Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png new file mode 100644 index 0000000000..f4dd86752e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png new file mode 100644 index 0000000000..9db1e6ba69 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png new file mode 100644 index 0000000000..b3ef604dc1 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png new file mode 100644 index 0000000000..a86aa0b624 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png new file mode 100644 index 0000000000..671379e6bc Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png new file mode 100644 index 0000000000..7464c18761 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png new file mode 100644 index 0000000000..f3855fab55 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.md b/zh-hant/docs/chapter_sorting/quick_sort.md new file mode 100755 index 0000000000..2202c00206 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/quick_sort.md @@ -0,0 +1,100 @@ +# 快速排序 + +快速排序(quick sort)是一種基於分治策略的排序演算法,執行高效,應用廣泛。 + +快速排序的核心操作是“哨兵劃分”,其目標是:選擇陣列中的某個元素作為“基準數”,將所有小於基準數的元素移到其左側,而大於基準數的元素移到其右側。具體來說,哨兵劃分的流程如下圖所示。 + +1. 選取陣列最左端元素作為基準數,初始化兩個指標 `i` 和 `j` 分別指向陣列的兩端。 +2. 設定一個迴圈,在每輪中使用 `i`(`j`)分別尋找第一個比基準數大(小)的元素,然後交換這兩個元素。 +3. 迴圈執行步驟 `2.` ,直到 `i` 和 `j` 相遇時停止,最後將基準數交換至兩個子陣列的分界線。 + +=== "<1>" + ![哨兵劃分步驟](quick_sort.assets/pivot_division_step1.png) + +=== "<2>" + ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) + +=== "<3>" + ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) + +=== "<4>" + ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) + +=== "<5>" + ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) + +=== "<6>" + ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) + +=== "<7>" + ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) + +=== "<8>" + ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) + +=== "<9>" + ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) + +哨兵劃分完成後,原陣列被劃分成三部分:左子陣列、基準數、右子陣列,且滿足“左子陣列任意元素 $\leq$ 基準數 $\leq$ 右子陣列任意元素”。因此,我們接下來只需對這兩個子陣列進行排序。 + +!!! note "快速排序的分治策略" + + 哨兵劃分的實質是將一個較長陣列的排序問題簡化為兩個較短陣列的排序問題。 + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{partition} +``` + +## 演算法流程 + +快速排序的整體流程如下圖所示。 + +1. 首先,對原陣列執行一次“哨兵劃分”,得到未排序的左子陣列和右子陣列。 +2. 然後,對左子陣列和右子陣列分別遞迴執行“哨兵劃分”。 +3. 持續遞迴,直至子陣列長度為 1 時終止,從而完成整個陣列的排序。 + +![快速排序流程](quick_sort.assets/quick_sort_overview.png) + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n \log n)$、非自適應排序**:在平均情況下,哨兵劃分的遞迴層數為 $\log n$ ,每層中的總迴圈數為 $n$ ,總體使用 $O(n \log n)$ 時間。在最差情況下,每輪哨兵劃分操作都將長度為 $n$ 的陣列劃分為長度為 $0$ 和 $n - 1$ 的兩個子陣列,此時遞迴層數達到 $n$ ,每層中的迴圈數為 $n$ ,總體使用 $O(n^2)$ 時間。 +- **空間複雜度為 $O(n)$、原地排序**:在輸入陣列完全倒序的情況下,達到最差遞迴深度 $n$ ,使用 $O(n)$ 堆疊幀空間。排序操作是在原陣列上進行的,未藉助額外陣列。 +- **非穩定排序**:在哨兵劃分的最後一步,基準數可能會被交換至相等元素的右側。 + +## 快速排序為什麼快 + +從名稱上就能看出,快速排序在效率方面應該具有一定的優勢。儘管快速排序的平均時間複雜度與“合併排序”和“堆積排序”相同,但通常快速排序的效率更高,主要有以下原因。 + +- **出現最差情況的機率很低**:雖然快速排序的最差時間複雜度為 $O(n^2)$ ,沒有合併排序穩定,但在絕大多數情況下,快速排序能在 $O(n \log n)$ 的時間複雜度下執行。 +- **快取使用效率高**:在執行哨兵劃分操作時,系統可將整個子陣列載入到快取,因此訪問元素的效率較高。而像“堆積排序”這類演算法需要跳躍式訪問元素,從而缺乏這一特性。 +- **複雜度的常數係數小**:在上述三種演算法中,快速排序的比較、賦值、交換等操作的總數量最少。這與“插入排序”比“泡沫排序”更快的原因類似。 + +## 基準數最佳化 + +**快速排序在某些輸入下的時間效率可能降低**。舉一個極端例子,假設輸入陣列是完全倒序的,由於我們選擇最左端元素作為基準數,那麼在哨兵劃分完成後,基準數被交換至陣列最右端,導致左子陣列長度為 $n - 1$、右子陣列長度為 $0$ 。如此遞迴下去,每輪哨兵劃分後都有一個子陣列的長度為 $0$ ,分治策略失效,快速排序退化為“泡沫排序”的近似形式。 + +為了儘量避免這種情況發生,**我們可以最佳化哨兵劃分中的基準數的選取策略**。例如,我們可以隨機選取一個元素作為基準數。然而,如果運氣不佳,每次都選到不理想的基準數,效率仍然不盡如人意。 + +需要注意的是,程式語言通常生成的是“偽隨機數”。如果我們針對偽隨機數序列構建一個特定的測試樣例,那麼快速排序的效率仍然可能劣化。 + +為了進一步改進,我們可以在陣列中選取三個候選元素(通常為陣列的首、尾、中點元素),**並將這三個候選元素的中位數作為基準數**。這樣一來,基準數“既不太小也不太大”的機率將大幅提升。當然,我們還可以選取更多候選元素,以進一步提高演算法的穩健性。採用這種方法後,時間複雜度劣化至 $O(n^2)$ 的機率大大降低。 + +示例程式碼如下: + +```src +[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} +``` + +## 尾遞迴最佳化 + +**在某些輸入下,快速排序可能佔用空間較多**。以完全有序的輸入陣列為例,設遞迴中的子陣列長度為 $m$ ,每輪哨兵劃分操作都將產生長度為 $0$ 的左子陣列和長度為 $m - 1$ 的右子陣列,這意味著每一層遞迴呼叫減少的問題規模非常小(只減少一個元素),遞迴樹的高度會達到 $n - 1$ ,此時需要佔用 $O(n)$ 大小的堆疊幀空間。 + +為了防止堆疊幀空間的累積,我們可以在每輪哨兵排序完成後,比較兩個子陣列的長度,**僅對較短的子陣列進行遞迴**。由於較短子陣列的長度不會超過 $n / 2$ ,因此這種方法能確保遞迴深度不超過 $\log n$ ,從而將最差空間複雜度最佳化至 $O(\log n)$ 。程式碼如下所示: + +```src +[file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} +``` diff --git a/zh-hant/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png b/zh-hant/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png new file mode 100644 index 0000000000..c255e2c5c5 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/radix_sort.md b/zh-hant/docs/chapter_sorting/radix_sort.md new file mode 100644 index 0000000000..502fb7dc4d --- /dev/null +++ b/zh-hant/docs/chapter_sorting/radix_sort.md @@ -0,0 +1,41 @@ +# 基數排序 + +上一節介紹了計數排序,它適用於資料量 $n$ 較大但資料範圍 $m$ 較小的情況。假設我們需要對 $n = 10^6$ 個學號進行排序,而學號是一個 $8$ 位數字,這意味著資料範圍 $m = 10^8$ 非常大,使用計數排序需要分配大量記憶體空間,而基數排序可以避免這種情況。 + +基數排序(radix sort)的核心思想與計數排序一致,也透過統計個數來實現排序。在此基礎上,基數排序利用數字各位之間的遞進關係,依次對每一位進行排序,從而得到最終的排序結果。 + +## 演算法流程 + +以學號資料為例,假設數字的最低位是第 $1$ 位,最高位是第 $8$ 位,基數排序的流程如下圖所示。 + +1. 初始化位數 $k = 1$ 。 +2. 對學號的第 $k$ 位執行“計數排序”。完成後,資料會根據第 $k$ 位從小到大排序。 +3. 將 $k$ 增加 $1$ ,然後返回步驟 `2.` 繼續迭代,直到所有位都排序完成後結束。 + +![基數排序演算法流程](radix_sort.assets/radix_sort_overview.png) + +下面剖析程式碼實現。對於一個 $d$ 進位制的數字 $x$ ,要獲取其第 $k$ 位 $x_k$ ,可以使用以下計算公式: + +$$ +x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d +$$ + +其中 $\lfloor a \rfloor$ 表示對浮點數 $a$ 向下取整,而 $\bmod \: d$ 表示對 $d$ 取模(取餘)。對於學號資料,$d = 10$ 且 $k \in [1, 8]$ 。 + +此外,我們需要小幅改動計數排序程式碼,使之可以根據數字的第 $k$ 位進行排序: + +```src +[file]{radix_sort}-[class]{}-[func]{radix_sort} +``` + +!!! question "為什麼從最低位開始排序?" + + 在連續的排序輪次中,後一輪排序會覆蓋前一輪排序的結果。舉例來說,如果第一輪排序結果 $a < b$ ,而第二輪排序結果 $a > b$ ,那麼第二輪的結果將取代第一輪的結果。由於數字的高位優先順序高於低位,因此應該先排序低位再排序高位。 + +## 演算法特性 + +相較於計數排序,基數排序適用於數值範圍較大的情況,**但前提是資料必須可以表示為固定位數的格式,且位數不能過大**。例如,浮點數不適合使用基數排序,因為其位數 $k$ 過大,可能導致時間複雜度 $O(nk) \gg O(n^2)$ 。 + +- **時間複雜度為 $O(nk)$、非自適應排序**:設資料量為 $n$、資料為 $d$ 進位制、最大位數為 $k$ ,則對某一位執行計數排序使用 $O(n + d)$ 時間,排序所有 $k$ 位使用 $O((n + d)k)$ 時間。通常情況下,$d$ 和 $k$ 都相對較小,時間複雜度趨向 $O(n)$ 。 +- **空間複雜度為 $O(n + d)$、非原地排序**:與計數排序相同,基數排序需要藉助長度為 $n$ 和 $d$ 的陣列 `res` 和 `counter` 。 +- **穩定排序**:當計數排序穩定時,基數排序也穩定;當計數排序不穩定時,基數排序無法保證得到正確的排序結果。 diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png new file mode 100644 index 0000000000..f39d178e19 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png new file mode 100644 index 0000000000..d876a15a68 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png new file mode 100644 index 0000000000..708a61399b Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png new file mode 100644 index 0000000000..0d04add4ba Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png new file mode 100644 index 0000000000..bfd957dcd8 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png new file mode 100644 index 0000000000..f995b78ff5 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png new file mode 100644 index 0000000000..fb37ba0e16 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png new file mode 100644 index 0000000000..3eba595435 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png new file mode 100644 index 0000000000..87d072f5e6 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png new file mode 100644 index 0000000000..96d9249597 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png new file mode 100644 index 0000000000..c704af4702 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png new file mode 100644 index 0000000000..4ab1047382 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.md b/zh-hant/docs/chapter_sorting/selection_sort.md new file mode 100644 index 0000000000..911a9b4762 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/selection_sort.md @@ -0,0 +1,58 @@ +# 選擇排序 + +選擇排序(selection sort)的工作原理非常簡單:開啟一個迴圈,每輪從未排序區間選擇最小的元素,將其放到已排序區間的末尾。 + +設陣列的長度為 $n$ ,選擇排序的演算法流程如下圖所示。 + +1. 初始狀態下,所有元素未排序,即未排序(索引)區間為 $[0, n-1]$ 。 +2. 選取區間 $[0, n-1]$ 中的最小元素,將其與索引 $0$ 處的元素交換。完成後,陣列前 1 個元素已排序。 +3. 選取區間 $[1, n-1]$ 中的最小元素,將其與索引 $1$ 處的元素交換。完成後,陣列前 2 個元素已排序。 +4. 以此類推。經過 $n - 1$ 輪選擇與交換後,陣列前 $n - 1$ 個元素已排序。 +5. 僅剩的一個元素必定是最大元素,無須排序,因此陣列排序完成。 + +=== "<1>" + ![選擇排序步驟](selection_sort.assets/selection_sort_step1.png) + +=== "<2>" + ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) + +=== "<3>" + ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) + +=== "<4>" + ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) + +=== "<5>" + ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) + +=== "<6>" + ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) + +=== "<7>" + ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) + +=== "<8>" + ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) + +=== "<9>" + ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) + +=== "<10>" + ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) + +=== "<11>" + ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) + +在程式碼中,我們用 $k$ 來記錄未排序區間內的最小元素: + +```src +[file]{selection_sort}-[class]{}-[func]{selection_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n^2)$、非自適應排序**:外迴圈共 $n - 1$ 輪,第一輪的未排序區間長度為 $n$ ,最後一輪的未排序區間長度為 $2$ ,即各輪外迴圈分別包含 $n$、$n - 1$、$\dots$、$3$、$2$ 輪內迴圈,求和為 $\frac{(n - 1)(n + 2)}{2}$ 。 +- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 +- **非穩定排序**:如下圖所示,元素 `nums[i]` 有可能被交換至與其相等的元素的右邊,導致兩者的相對順序發生改變。 + +![選擇排序非穩定示例](selection_sort.assets/selection_sort_instability.png) diff --git a/zh-hant/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png b/zh-hant/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png new file mode 100644 index 0000000000..62b8daf640 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png differ diff --git a/zh-hant/docs/chapter_sorting/sorting_algorithm.md b/zh-hant/docs/chapter_sorting/sorting_algorithm.md new file mode 100644 index 0000000000..5e292648ff --- /dev/null +++ b/zh-hant/docs/chapter_sorting/sorting_algorithm.md @@ -0,0 +1,46 @@ +# 排序演算法 + +排序演算法(sorting algorithm)用於對一組資料按照特定順序進行排列。排序演算法有著廣泛的應用,因為有序資料通常能夠被更高效地查詢、分析和處理。 + +如下圖所示,排序演算法中的資料型別可以是整數、浮點數、字元或字串等。排序的判斷規則可根據需求設定,如數字大小、字元 ASCII 碼順序或自定義規則。 + +![資料型別和判斷規則示例](sorting_algorithm.assets/sorting_examples.png) + +## 評價維度 + +**執行效率**:我們期望排序演算法的時間複雜度儘量低,且總體操作數量較少(時間複雜度中的常數項變小)。對於大資料量的情況,執行效率顯得尤為重要。 + +**就地性**:顧名思義,原地排序透過在原陣列上直接操作實現排序,無須藉助額外的輔助陣列,從而節省記憶體。通常情況下,原地排序的資料搬運操作較少,執行速度也更快。 + +**穩定性**:穩定排序在完成排序後,相等元素在陣列中的相對順序不發生改變。 + +穩定排序是多級排序場景的必要條件。假設我們有一個儲存學生資訊的表格,第 1 列和第 2 列分別是姓名和年齡。在這種情況下,非穩定排序可能導致輸入資料的有序性喪失: + +```shell +# 輸入資料是按照姓名排序好的 +# (name, age) + ('A', 19) + ('B', 18) + ('C', 21) + ('D', 19) + ('E', 23) + +# 假設使用非穩定排序演算法按年齡排序串列, +# 結果中 ('D', 19) 和 ('A', 19) 的相對位置改變, +# 輸入資料按姓名排序的性質丟失 + ('B', 18) + ('D', 19) + ('A', 19) + ('C', 21) + ('E', 23) +``` + +**自適應性**:自適應排序能夠利用輸入資料已有的順序資訊來減少計算量,達到更優的時間效率。自適應排序演算法的最佳時間複雜度通常優於平均時間複雜度。 + +**是否基於比較**:基於比較的排序依賴比較運算子($<$、$=$、$>$)來判斷元素的相對順序,從而排序整個陣列,理論最優時間複雜度為 $O(n \log n)$ 。而非比較排序不使用比較運算子,時間複雜度可達 $O(n)$ ,但其通用性相對較差。 + +## 理想排序演算法 + +**執行快、原地、穩定、自適應、通用性好**。顯然,迄今為止尚未發現兼具以上所有特性的排序演算法。因此,在選擇排序演算法時,需要根據具體的資料特點和問題需求來決定。 + +接下來,我們將共同學習各種排序演算法,並基於上述評價維度對各個排序演算法的優缺點進行分析。 diff --git a/zh-hant/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png b/zh-hant/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png new file mode 100644 index 0000000000..1fd5338ff5 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png differ diff --git a/zh-hant/docs/chapter_sorting/summary.md b/zh-hant/docs/chapter_sorting/summary.md new file mode 100644 index 0000000000..969b37a5f4 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/summary.md @@ -0,0 +1,47 @@ +# 小結 + +### 重點回顧 + +- 泡沫排序透過交換相鄰元素來實現排序。透過新增一個標誌位來實現提前返回,我們可以將泡沫排序的最佳時間複雜度最佳化到 $O(n)$ 。 +- 插入排序每輪將未排序區間內的元素插入到已排序區間的正確位置,從而完成排序。雖然插入排序的時間複雜度為 $O(n^2)$ ,但由於單元操作相對較少,因此在小資料量的排序任務中非常受歡迎。 +- 快速排序基於哨兵劃分操作實現排序。在哨兵劃分中,有可能每次都選取到最差的基準數,導致時間複雜度劣化至 $O(n^2)$ 。引入中位數基準數或隨機基準數可以降低這種劣化的機率。尾遞迴方法可以有效地減少遞迴深度,將空間複雜度最佳化到 $O(\log n)$ 。 +- 合併排序包括劃分和合並兩個階段,典型地體現了分治策略。在合併排序中,排序陣列需要建立輔助陣列,空間複雜度為 $O(n)$ ;然而排序鏈結串列的空間複雜度可以最佳化至 $O(1)$ 。 +- 桶排序包含三個步驟:資料分桶、桶內排序和合並結果。它同樣體現了分治策略,適用於資料體量很大的情況。桶排序的關鍵在於對資料進行平均分配。 +- 計數排序是桶排序的一個特例,它透過統計資料出現的次數來實現排序。計數排序適用於資料量大但資料範圍有限的情況,並且要求資料能夠轉換為正整數。 +- 基數排序透過逐位排序來實現資料排序,要求資料能夠表示為固定位數的數字。 +- 總的來說,我們希望找到一種排序演算法,具有高效率、穩定、原地以及自適應性等優點。然而,正如其他資料結構和演算法一樣,沒有一種排序演算法能夠同時滿足所有這些條件。在實際應用中,我們需要根據資料的特性來選擇合適的排序演算法。 +- 下圖對比了主流排序演算法的效率、穩定性、就地性和自適應性等。 + +![排序演算法對比](summary.assets/sorting_algorithms_comparison.png) + +### Q & A + +**Q**:排序演算法穩定性在什麼情況下是必需的? + +在現實中,我們有可能基於物件的某個屬性進行排序。例如,學生有姓名和身高兩個屬性,我們希望實現一個多級排序:先按照姓名進行排序,得到 `(A, 180) (B, 185) (C, 170) (D, 170)` ;再對身高進行排序。由於排序演算法不穩定,因此可能得到 `(D, 170) (C, 170) (A, 180) (B, 185)` 。 + +可以發現,學生 D 和 C 的位置發生了交換,姓名的有序性被破壞了,而這是我們不希望看到的。 + +**Q**:哨兵劃分中“從右往左查詢”與“從左往右查詢”的順序可以交換嗎? + +不行,當我們以最左端元素為基準數時,必須先“從右往左查詢”再“從左往右查詢”。這個結論有些反直覺,我們來剖析一下原因。 + +哨兵劃分 `partition()` 的最後一步是交換 `nums[left]` 和 `nums[i]` 。完成交換後,基準數左邊的元素都 `<=` 基準數,**這就要求最後一步交換前 `nums[left] >= nums[i]` 必須成立**。假設我們先“從左往右查詢”,那麼如果找不到比基準數更大的元素,**則會在 `i == j` 時跳出迴圈,此時可能 `nums[j] == nums[i] > nums[left]`**。也就是說,此時最後一步交換操作會把一個比基準數更大的元素交換至陣列最左端,導致哨兵劃分失敗。 + +舉個例子,給定陣列 `[0, 0, 0, 0, 1]` ,如果先“從左向右查詢”,哨兵劃分後陣列為 `[1, 0, 0, 0, 0]` ,這個結果是不正確的。 + +再深入思考一下,如果我們選擇 `nums[right]` 為基準數,那麼正好反過來,必須先“從左往右查詢”。 + +**Q**:關於尾遞迴最佳化,為什麼選短的陣列能保證遞迴深度不超過 $\log n$ ? + +遞迴深度就是當前未返回的遞迴方法的數量。每輪哨兵劃分我們將原陣列劃分為兩個子陣列。在尾遞迴最佳化後,向下遞迴的子陣列長度最大為原陣列長度的一半。假設最差情況,一直為一半長度,那麼最終的遞迴深度就是 $\log n$ 。 + +回顧原始的快速排序,我們有可能會連續地遞迴長度較大的陣列,最差情況下為 $n$、$n - 1$、$\dots$、$2$、$1$ ,遞迴深度為 $n$ 。尾遞迴最佳化可以避免這種情況出現。 + +**Q**:當陣列中所有元素都相等時,快速排序的時間複雜度是 $O(n^2)$ 嗎?該如何處理這種退化情況? + +是的。對於這種情況,可以考慮透過哨兵劃分將陣列劃分為三個部分:小於、等於、大於基準數。僅向下遞迴小於和大於的兩部分。在該方法下,輸入元素全部相等的陣列,僅一輪哨兵劃分即可完成排序。 + +**Q**:桶排序的最差時間複雜度為什麼是 $O(n^2)$ ? + +最差情況下,所有元素被分至同一個桶中。如果我們採用一個 $O(n^2)$ 演算法來排序這些元素,則時間複雜度為 $O(n^2)$ 。 diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png new file mode 100644 index 0000000000..1051d04816 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png new file mode 100644 index 0000000000..83da5e2174 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png new file mode 100644 index 0000000000..68d1a5d2a3 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png new file mode 100644 index 0000000000..a32871bb24 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png new file mode 100644 index 0000000000..50b2ab9a11 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/deque_operations.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/deque_operations.png new file mode 100644 index 0000000000..c18ba3281a Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/deque_operations.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png new file mode 100644 index 0000000000..6f8decd53d Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png new file mode 100644 index 0000000000..67095493fa Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png new file mode 100644 index 0000000000..b773d6d05c Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png new file mode 100644 index 0000000000..7639acb33f Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png new file mode 100644 index 0000000000..85c53881a5 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.md b/zh-hant/docs/chapter_stack_and_queue/deque.md new file mode 100644 index 0000000000..f82e045443 --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/deque.md @@ -0,0 +1,458 @@ +# 雙向佇列 + +在佇列中,我們僅能刪除頭部元素或在尾部新增元素。如下圖所示,雙向佇列(double-ended queue)提供了更高的靈活性,允許在頭部和尾部執行元素的新增或刪除操作。 + +![雙向佇列的操作](deque.assets/deque_operations.png) + +## 雙向佇列常用操作 + +雙向佇列的常用操作如下表所示,具體的方法名稱需要根據所使用的程式語言來確定。 + +

  雙向佇列操作效率

+ +| 方法名 | 描述 | 時間複雜度 | +| -------------- | ---------------- | ---------- | +| `push_first()` | 將元素新增至佇列首 | $O(1)$ | +| `push_last()` | 將元素新增至佇列尾 | $O(1)$ | +| `pop_first()` | 刪除佇列首元素 | $O(1)$ | +| `pop_last()` | 刪除佇列尾元素 | $O(1)$ | +| `peek_first()` | 訪問佇列首元素 | $O(1)$ | +| `peek_last()` | 訪問佇列尾元素 | $O(1)$ | + +同樣地,我們可以直接使用程式語言中已實現的雙向佇列類別: + +=== "Python" + + ```python title="deque.py" + from collections import deque + + # 初始化雙向佇列 + deq: deque[int] = deque() + + # 元素入列 + deq.append(2) # 新增至佇列尾 + deq.append(5) + deq.append(4) + deq.appendleft(3) # 新增至佇列首 + deq.appendleft(1) + + # 訪問元素 + front: int = deq[0] # 佇列首元素 + rear: int = deq[-1] # 佇列尾元素 + + # 元素出列 + pop_front: int = deq.popleft() # 佇列首元素出列 + pop_rear: int = deq.pop() # 佇列尾元素出列 + + # 獲取雙向佇列的長度 + size: int = len(deq) + + # 判斷雙向佇列是否為空 + is_empty: bool = len(deq) == 0 + ``` + +=== "C++" + + ```cpp title="deque.cpp" + /* 初始化雙向佇列 */ + deque deque; + + /* 元素入列 */ + deque.push_back(2); // 新增至佇列尾 + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // 新增至佇列首 + deque.push_front(1); + + /* 訪問元素 */ + int front = deque.front(); // 佇列首元素 + int back = deque.back(); // 佇列尾元素 + + /* 元素出列 */ + deque.pop_front(); // 佇列首元素出列 + deque.pop_back(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + + /* 判斷雙向佇列是否為空 */ + bool empty = deque.empty(); + ``` + +=== "Java" + + ```java title="deque.java" + /* 初始化雙向佇列 */ + Deque deque = new LinkedList<>(); + + /* 元素入列 */ + deque.offerLast(2); // 新增至佇列尾 + deque.offerLast(5); + deque.offerLast(4); + deque.offerFirst(3); // 新增至佇列首 + deque.offerFirst(1); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); // 佇列首元素 + int peekLast = deque.peekLast(); // 佇列尾元素 + + /* 元素出列 */ + int popFirst = deque.pollFirst(); // 佇列首元素出列 + int popLast = deque.pollLast(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + ``` + +=== "C#" + + ```csharp title="deque.cs" + /* 初始化雙向佇列 */ + // 在 C# 中,將鏈結串列 LinkedList 看作雙向佇列來使用 + LinkedList deque = new(); + + /* 元素入列 */ + deque.AddLast(2); // 新增至佇列尾 + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 新增至佇列首 + deque.AddFirst(1); + + /* 訪問元素 */ + int peekFirst = deque.First.Value; // 佇列首元素 + int peekLast = deque.Last.Value; // 佇列尾元素 + + /* 元素出列 */ + deque.RemoveFirst(); // 佇列首元素出列 + deque.RemoveLast(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.Count; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.Count == 0; + ``` + +=== "Go" + + ```go title="deque_test.go" + /* 初始化雙向佇列 */ + // 在 Go 中,將 list 作為雙向佇列使用 + deque := list.New() + + /* 元素入列 */ + deque.PushBack(2) // 新增至佇列尾 + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) // 新增至佇列首 + deque.PushFront(1) + + /* 訪問元素 */ + front := deque.Front() // 佇列首元素 + rear := deque.Back() // 佇列尾元素 + + /* 元素出列 */ + deque.Remove(front) // 佇列首元素出列 + deque.Remove(rear) // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + size := deque.Len() + + /* 判斷雙向佇列是否為空 */ + isEmpty := deque.Len() == 0 + ``` + +=== "Swift" + + ```swift title="deque.swift" + /* 初始化雙向佇列 */ + // Swift 沒有內建的雙向佇列類別,可以把 Array 當作雙向佇列來使用 + var deque: [Int] = [] + + /* 元素入列 */ + deque.append(2) // 新增至佇列尾 + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) // 新增至佇列首 + deque.insert(1, at: 0) + + /* 訪問元素 */ + let peekFirst = deque.first! // 佇列首元素 + let peekLast = deque.last! // 佇列尾元素 + + /* 元素出列 */ + // 使用 Array 模擬時 popFirst 的複雜度為 O(n) + let popFirst = deque.removeFirst() // 佇列首元素出列 + let popLast = deque.removeLast() // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + let size = deque.count + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty + ``` + +=== "JS" + + ```javascript title="deque.js" + /* 初始化雙向佇列 */ + // JavaScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 + const deque = []; + + /* 元素入列 */ + deque.push(2); + deque.push(5); + deque.push(4); + // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) + deque.unshift(3); + deque.unshift(1); + + /* 訪問元素 */ + const peekFirst = deque[0]; + const peekLast = deque[deque.length - 1]; + + /* 元素出列 */ + // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) + const popFront = deque.shift(); + const popBack = deque.pop(); + + /* 獲取雙向佇列的長度 */ + const size = deque.length; + + /* 判斷雙向佇列是否為空 */ + const isEmpty = size === 0; + ``` + +=== "TS" + + ```typescript title="deque.ts" + /* 初始化雙向佇列 */ + // TypeScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 + const deque: number[] = []; + + /* 元素入列 */ + deque.push(2); + deque.push(5); + deque.push(4); + // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) + deque.unshift(3); + deque.unshift(1); + + /* 訪問元素 */ + const peekFirst: number = deque[0]; + const peekLast: number = deque[deque.length - 1]; + + /* 元素出列 */ + // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) + const popFront: number = deque.shift() as number; + const popBack: number = deque.pop() as number; + + /* 獲取雙向佇列的長度 */ + const size: number = deque.length; + + /* 判斷雙向佇列是否為空 */ + const isEmpty: boolean = size === 0; + ``` + +=== "Dart" + + ```dart title="deque.dart" + /* 初始化雙向佇列 */ + // 在 Dart 中,Queue 被定義為雙向佇列 + Queue deque = Queue(); + + /* 元素入列 */ + deque.addLast(2); // 新增至佇列尾 + deque.addLast(5); + deque.addLast(4); + deque.addFirst(3); // 新增至佇列首 + deque.addFirst(1); + + /* 訪問元素 */ + int peekFirst = deque.first; // 佇列首元素 + int peekLast = deque.last; // 佇列尾元素 + + /* 元素出列 */ + int popFirst = deque.removeFirst(); // 佇列首元素出列 + int popLast = deque.removeLast(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.length; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.isEmpty; + ``` + +=== "Rust" + + ```rust title="deque.rs" + /* 初始化雙向佇列 */ + let mut deque: VecDeque = VecDeque::new(); + + /* 元素入列 */ + deque.push_back(2); // 新增至佇列尾 + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // 新增至佇列首 + deque.push_front(1); + + /* 訪問元素 */ + if let Some(front) = deque.front() { // 佇列首元素 + } + if let Some(rear) = deque.back() { // 佇列尾元素 + } + + /* 元素出列 */ + if let Some(pop_front) = deque.pop_front() { // 佇列首元素出列 + } + if let Some(pop_rear) = deque.pop_back() { // 佇列尾元素出列 + } + + /* 獲取雙向佇列的長度 */ + let size = deque.len(); + + /* 判斷雙向佇列是否為空 */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="deque.c" + // C 未提供內建雙向佇列 + ``` + +=== "Kotlin" + + ```kotlin title="deque.kt" + /* 初始化雙向佇列 */ + val deque = LinkedList() + + /* 元素入列 */ + deque.offerLast(2) // 新增至佇列尾 + deque.offerLast(5) + deque.offerLast(4) + deque.offerFirst(3) // 新增至佇列首 + deque.offerFirst(1) + + /* 訪問元素 */ + val peekFirst = deque.peekFirst() // 佇列首元素 + val peekLast = deque.peekLast() // 佇列尾元素 + + /* 元素出列 */ + val popFirst = deque.pollFirst() // 佇列首元素出列 + val popLast = deque.pollLast() // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + val size = deque.size + + /* 判斷雙向佇列是否為空 */ + val isEmpty = deque.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="deque.rb" + # 初始化雙向佇列 + # Ruby 沒有內直的雙端佇列,只能把 Array 當作雙端佇列來使用 + deque = [] + + # 元素如隊 + deque << 2 + deque << 5 + deque << 4 + # 請注意,由於是陣列,Array#unshift 方法的時間複雜度為 O(n) + deque.unshift(3) + deque.unshift(1) + + # 訪問元素 + peek_first = deque.first + peek_last = deque.last + + # 元素出列 + # 請注意,由於是陣列, Array#shift 方法的時間複雜度為 O(n) + pop_front = deque.shift + pop_back = deque.pop + + # 獲取雙向佇列的長度 + size = deque.length + + # 判斷雙向佇列是否為空 + is_empty = size.zero? + ``` + +=== "Zig" + + ```zig title="deque.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%96%B0%E5%A2%9E%E8%87%B3%E4%BD%87%E5%88%97%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%96%B0%E5%A2%9E%E8%87%B3%E4%BD%87%E5%88%97%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%20deque%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22%2C%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E4%BD%87%E5%88%97%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22%2C%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22%2C%20pop_front%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%87%BA%E5%88%97%E5%BE%8C%20deque%20%3D%22%2C%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E4%BD%87%E5%88%97%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E5%B0%BE%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22%2C%20pop_rear%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E5%B0%BE%E5%87%BA%E5%88%97%E5%BE%8C%20deque%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 雙向佇列實現 * + +雙向佇列的實現與佇列類似,可以選擇鏈結串列或陣列作為底層資料結構。 + +### 基於雙向鏈結串列的實現 + +回顧上一節內容,我們使用普通單向鏈結串列來實現佇列,因為它可以方便地刪除頭節點(對應出列操作)和在尾節點後新增新節點(對應入列操作)。 + +對於雙向佇列而言,頭部和尾部都可以執行入列和出列操作。換句話說,雙向佇列需要實現另一個對稱方向的操作。為此,我們採用“雙向鏈結串列”作為雙向佇列的底層資料結構。 + +如下圖所示,我們將雙向鏈結串列的頭節點和尾節點視為雙向佇列的佇列首和佇列尾,同時實現在兩端新增和刪除節點的功能。 + +=== "LinkedListDeque" + ![基於鏈結串列實現雙向佇列的入列出列操作](deque.assets/linkedlist_deque_step1.png) + +=== "push_last()" + ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) + +=== "push_first()" + ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) + +=== "pop_last()" + ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) + +=== "pop_first()" + ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) + +實現程式碼如下所示: + +```src +[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} +``` + +### 基於陣列的實現 + +如下圖所示,與基於陣列實現佇列類似,我們也可以使用環形陣列來實現雙向佇列。 + +=== "ArrayDeque" + ![基於陣列實現雙向佇列的入列出列操作](deque.assets/array_deque_step1.png) + +=== "push_last()" + ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) + +=== "push_first()" + ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) + +=== "pop_last()" + ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) + +=== "pop_first()" + ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) + +在佇列的實現基礎上,僅需增加“佇列首入列”和“佇列尾出列”的方法: + +```src +[file]{array_deque}-[class]{array_deque}-[func]{} +``` + +## 雙向佇列應用 + +雙向佇列兼具堆疊與佇列的邏輯,**因此它可以實現這兩者的所有應用場景,同時提供更高的自由度**。 + +我們知道,軟體的“撤銷”功能通常使用堆疊來實現:系統將每次更改操作 `push` 到堆疊中,然後透過 `pop` 實現撤銷。然而,考慮到系統資源的限制,軟體通常會限制撤銷的步數(例如僅允許儲存 $50$ 步)。當堆疊的長度超過 $50$ 時,軟體需要在堆疊底(佇列首)執行刪除操作。**但堆疊無法實現該功能,此時就需要使用雙向佇列來替代堆疊**。請注意,“撤銷”的核心邏輯仍然遵循堆疊的先入後出原則,只是雙向佇列能夠更加靈活地實現一些額外邏輯。 diff --git a/zh-hant/docs/chapter_stack_and_queue/index.md b/zh-hant/docs/chapter_stack_and_queue/index.md new file mode 100644 index 0000000000..298a5ec20b --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/index.md @@ -0,0 +1,9 @@ +# 堆疊與佇列 + +![堆疊與佇列](../assets/covers/chapter_stack_and_queue.jpg) + +!!! abstract + + 堆疊如同疊貓貓,而佇列就像貓貓排隊。 + + 兩者分別代表先入後出和先入先出的邏輯關係。 diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png new file mode 100644 index 0000000000..72d87004e1 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png new file mode 100644 index 0000000000..95808d0bf6 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png new file mode 100644 index 0000000000..e6433ea85c Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png new file mode 100644 index 0000000000..07203e8f1c Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png new file mode 100644 index 0000000000..713d952b5d Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png new file mode 100644 index 0000000000..b99ce45a5e Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/queue_operations.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/queue_operations.png new file mode 100644 index 0000000000..ceba5de684 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/queue_operations.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.md b/zh-hant/docs/chapter_stack_and_queue/queue.md new file mode 100755 index 0000000000..61b10b8782 --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/queue.md @@ -0,0 +1,429 @@ +# 佇列 + +佇列(queue)是一種遵循先入先出規則的線性資料結構。顧名思義,佇列模擬了排隊現象,即新來的人不斷加入佇列尾部,而位於佇列頭部的人逐個離開。 + +如下圖所示,我們將佇列頭部稱為“佇列首”,尾部稱為“佇列尾”,將把元素加入列尾的操作稱為“入列”,刪除佇列首元素的操作稱為“出列”。 + +![佇列的先入先出規則](queue.assets/queue_operations.png) + +## 佇列常用操作 + +佇列的常見操作如下表所示。需要注意的是,不同程式語言的方法名稱可能會有所不同。我們在此採用與堆疊相同的方法命名。 + +

  佇列操作效率

+ +| 方法名 | 描述 | 時間複雜度 | +| -------- | ---------------------------- | ---------- | +| `push()` | 元素入列,即將元素新增至佇列尾 | $O(1)$ | +| `pop()` | 佇列首元素出列 | $O(1)$ | +| `peek()` | 訪問佇列首元素 | $O(1)$ | + +我們可以直接使用程式語言中現成的佇列類別: + +=== "Python" + + ```python title="queue.py" + from collections import deque + + # 初始化佇列 + # 在 Python 中,我們一般將雙向佇列類別 deque 當作佇列使用 + # 雖然 queue.Queue() 是純正的佇列類別,但不太好用,因此不推薦 + que: deque[int] = deque() + + # 元素入列 + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + + # 訪問佇列首元素 + front: int = que[0] + + # 元素出列 + pop: int = que.popleft() + + # 獲取佇列的長度 + size: int = len(que) + + # 判斷佇列是否為空 + is_empty: bool = len(que) == 0 + ``` + +=== "C++" + + ```cpp title="queue.cpp" + /* 初始化佇列 */ + queue queue; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* 訪問佇列首元素 */ + int front = queue.front(); + + /* 元素出列 */ + queue.pop(); + + /* 獲取佇列的長度 */ + int size = queue.size(); + + /* 判斷佇列是否為空 */ + bool empty = queue.empty(); + ``` + +=== "Java" + + ```java title="queue.java" + /* 初始化佇列 */ + Queue queue = new LinkedList<>(); + + /* 元素入列 */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + + /* 元素出列 */ + int pop = queue.poll(); + + /* 獲取佇列的長度 */ + int size = queue.size(); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + ``` + +=== "C#" + + ```csharp title="queue.cs" + /* 初始化佇列 */ + Queue queue = new(); + + /* 元素入列 */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + + /* 元素出列 */ + int pop = queue.Dequeue(); + + /* 獲取佇列的長度 */ + int size = queue.Count; + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.Count == 0; + ``` + +=== "Go" + + ```go title="queue_test.go" + /* 初始化佇列 */ + // 在 Go 中,將 list 作為佇列來使用 + queue := list.New() + + /* 元素入列 */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + + /* 訪問佇列首元素 */ + peek := queue.Front() + + /* 元素出列 */ + pop := queue.Front() + queue.Remove(pop) + + /* 獲取佇列的長度 */ + size := queue.Len() + + /* 判斷佇列是否為空 */ + isEmpty := queue.Len() == 0 + ``` + +=== "Swift" + + ```swift title="queue.swift" + /* 初始化佇列 */ + // Swift 沒有內建的佇列類別,可以把 Array 當作佇列來使用 + var queue: [Int] = [] + + /* 元素入列 */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + + /* 訪問佇列首元素 */ + let peek = queue.first! + + /* 元素出列 */ + // 由於是陣列,因此 removeFirst 的複雜度為 O(n) + let pool = queue.removeFirst() + + /* 獲取佇列的長度 */ + let size = queue.count + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty + ``` + +=== "JS" + + ```javascript title="queue.js" + /* 初始化佇列 */ + // JavaScript 沒有內建的佇列,可以把 Array 當作佇列來使用 + const queue = []; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* 訪問佇列首元素 */ + const peek = queue[0]; + + /* 元素出列 */ + // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) + const pop = queue.shift(); + + /* 獲取佇列的長度 */ + const size = queue.length; + + /* 判斷佇列是否為空 */ + const empty = queue.length === 0; + ``` + +=== "TS" + + ```typescript title="queue.ts" + /* 初始化佇列 */ + // TypeScript 沒有內建的佇列,可以把 Array 當作佇列來使用 + const queue: number[] = []; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* 訪問佇列首元素 */ + const peek = queue[0]; + + /* 元素出列 */ + // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) + const pop = queue.shift(); + + /* 獲取佇列的長度 */ + const size = queue.length; + + /* 判斷佇列是否為空 */ + const empty = queue.length === 0; + ``` + +=== "Dart" + + ```dart title="queue.dart" + /* 初始化佇列 */ + // 在 Dart 中,佇列類別 Qeque 是雙向佇列,也可作為佇列使用 + Queue queue = Queue(); + + /* 元素入列 */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + + /* 訪問佇列首元素 */ + int peek = queue.first; + + /* 元素出列 */ + int pop = queue.removeFirst(); + + /* 獲取佇列的長度 */ + int size = queue.length; + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.isEmpty; + ``` + +=== "Rust" + + ```rust title="queue.rs" + /* 初始化雙向佇列 */ + // 在 Rust 中使用雙向佇列作為普通佇列來使用 + let mut deque: VecDeque = VecDeque::new(); + + /* 元素入列 */ + deque.push_back(1); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + + /* 訪問佇列首元素 */ + if let Some(front) = deque.front() { + } + + /* 元素出列 */ + if let Some(pop) = deque.pop_front() { + } + + /* 獲取佇列的長度 */ + let size = deque.len(); + + /* 判斷佇列是否為空 */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="queue.c" + // C 未提供內建佇列 + ``` + +=== "Kotlin" + + ```kotlin title="queue.kt" + /* 初始化佇列 */ + val queue = LinkedList() + + /* 元素入列 */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + + /* 訪問佇列首元素 */ + val peek = queue.peek() + + /* 元素出列 */ + val pop = queue.poll() + + /* 獲取佇列的長度 */ + val size = queue.size + + /* 判斷佇列是否為空 */ + val isEmpty = queue.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="queue.rb" + # 初始化佇列 + # Ruby 內建的佇列(Thread::Queue) 沒有 peek 和走訪方法,可以把 Array 當作佇列來使用 + queue = [] + + # 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + + # 訪問佇列元素 + peek = queue.first + + # 元素出列 + # 清注意,由於是陣列,Array#shift 方法時間複雜度為 O(n) + pop = queue.shift + + # 獲取佇列的長度 + size = queue.length + + # 判斷佇列是否為空 + is_empty = queue.empty? + ``` + +=== "Zig" + + ```zig title="queue.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BD%87%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E5%80%91%E4%B8%80%E8%88%AC%E5%B0%87%E9%9B%99%E5%90%91%E4%BD%87%E5%88%97%E9%A1%9E%E5%88%A5%20deque%20%E7%9C%8B%E4%BD%9C%E4%BD%87%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E9%9B%96%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%B4%94%E6%AD%A3%E7%9A%84%E4%BD%87%E5%88%97%E9%A1%9E%E5%88%A5%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%88%97%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%20que%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22%2C%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%88%97%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%88%97%E5%BE%8C%20que%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E4%BD%87%E5%88%97%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E4%BD%87%E5%88%97%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 佇列實現 + +為了實現佇列,我們需要一種資料結構,可以在一端新增元素,並在另一端刪除元素,鏈結串列和陣列都符合要求。 + +### 基於鏈結串列的實現 + +如下圖所示,我們可以將鏈結串列的“頭節點”和“尾節點”分別視為“佇列首”和“佇列尾”,規定佇列尾僅可新增節點,佇列首僅可刪除節點。 + +=== "LinkedListQueue" + ![基於鏈結串列實現佇列的入列出列操作](queue.assets/linkedlist_queue_step1.png) + +=== "push()" + ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) + +=== "pop()" + ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) + +以下是用鏈結串列實現佇列的程式碼: + +```src +[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} +``` + +### 基於陣列的實現 + +在陣列中刪除首元素的時間複雜度為 $O(n)$ ,這會導致出列操作效率較低。然而,我們可以採用以下巧妙方法來避免這個問題。 + +我們可以使用一個變數 `front` 指向佇列首元素的索引,並維護一個變數 `size` 用於記錄佇列長度。定義 `rear = front + size` ,這個公式計算出的 `rear` 指向佇列尾元素之後的下一個位置。 + +基於此設計,**陣列中包含元素的有效區間為 `[front, rear - 1]`**,各種操作的實現方法如下圖所示。 + +- 入列操作:將輸入元素賦值給 `rear` 索引處,並將 `size` 增加 1 。 +- 出列操作:只需將 `front` 增加 1 ,並將 `size` 減少 1 。 + +可以看到,入列和出列操作都只需進行一次操作,時間複雜度均為 $O(1)$ 。 + +=== "ArrayQueue" + ![基於陣列實現佇列的入列出列操作](queue.assets/array_queue_step1.png) + +=== "push()" + ![array_queue_push](queue.assets/array_queue_step2_push.png) + +=== "pop()" + ![array_queue_pop](queue.assets/array_queue_step3_pop.png) + +你可能會發現一個問題:在不斷進行入列和出列的過程中,`front` 和 `rear` 都在向右移動,**當它們到達陣列尾部時就無法繼續移動了**。為了解決此問題,我們可以將陣列視為首尾相接的“環形陣列”。 + +對於環形陣列,我們需要讓 `front` 或 `rear` 在越過陣列尾部時,直接回到陣列頭部繼續走訪。這種週期性規律可以透過“取餘操作”來實現,程式碼如下所示: + +```src +[file]{array_queue}-[class]{array_queue}-[func]{} +``` + +以上實現的佇列仍然具有侷限性:其長度不可變。然而,這個問題不難解決,我們可以將陣列替換為動態陣列,從而引入擴容機制。有興趣的讀者可以嘗試自行實現。 + +兩種實現的對比結論與堆疊一致,在此不再贅述。 + +## 佇列典型應用 + +- **淘寶訂單**。購物者下單後,訂單將加入佇列中,系統隨後會根據順序處理佇列中的訂單。在雙十一期間,短時間內會產生海量訂單,高併發成為工程師們需要重點攻克的問題。 +- **各類待辦事項**。任何需要實現“先來後到”功能的場景,例如印表機的任務佇列、餐廳的出餐佇列等,佇列在這些場景中可以有效地維護處理順序。 diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png new file mode 100644 index 0000000000..b8f7ba6576 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png new file mode 100644 index 0000000000..ace693abc9 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png new file mode 100644 index 0000000000..911ca63c62 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png new file mode 100644 index 0000000000..52eb5753c0 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png new file mode 100644 index 0000000000..0f0798702c Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png new file mode 100644 index 0000000000..f354c00a2b Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/stack_operations.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/stack_operations.png new file mode 100644 index 0000000000..1d46e5b9c9 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/stack_operations.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.md b/zh-hant/docs/chapter_stack_and_queue/stack.md new file mode 100755 index 0000000000..e87ff00fdd --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/stack.md @@ -0,0 +1,436 @@ +# 堆疊 + +堆疊(stack)是一種遵循先入後出邏輯的線性資料結構。 + +我們可以將堆疊類比為桌面上的一疊盤子,如果想取出底部的盤子,則需要先將上面的盤子依次移走。我們將盤子替換為各種型別的元素(如整數、字元、物件等),就得到了堆疊這種資料結構。 + +如下圖所示,我們把堆積疊元素的頂部稱為“堆疊頂”,底部稱為“堆疊底”。將把元素新增到堆疊頂的操作叫作“入堆疊”,刪除堆疊頂元素的操作叫作“出堆疊”。 + +![堆疊的先入後出規則](stack.assets/stack_operations.png) + +## 堆疊的常用操作 + +堆疊的常用操作如下表所示,具體的方法名需要根據所使用的程式語言來確定。在此,我們以常見的 `push()`、`pop()`、`peek()` 命名為例。 + +

  堆疊的操作效率

+ +| 方法 | 描述 | 時間複雜度 | +| -------- | ---------------------- | ---------- | +| `push()` | 元素入堆疊(新增至堆疊頂) | $O(1)$ | +| `pop()` | 堆疊頂元素出堆疊 | $O(1)$ | +| `peek()` | 訪問堆疊頂元素 | $O(1)$ | + +通常情況下,我們可以直接使用程式語言內建的堆疊類別。然而,某些語言可能沒有專門提供堆疊類別,這時我們可以將該語言的“陣列”或“鏈結串列”當作堆疊來使用,並在程式邏輯上忽略與堆疊無關的操作。 + +=== "Python" + + ```python title="stack.py" + # 初始化堆疊 + # Python 沒有內建的堆疊類別,可以把 list 當作堆疊來使用 + stack: list[int] = [] + + # 元素入堆疊 + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + # 訪問堆疊頂元素 + peek: int = stack[-1] + + # 元素出堆疊 + pop: int = stack.pop() + + # 獲取堆疊的長度 + size: int = len(stack) + + # 判斷是否為空 + is_empty: bool = len(stack) == 0 + ``` + +=== "C++" + + ```cpp title="stack.cpp" + /* 初始化堆疊 */ + stack stack; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + int top = stack.top(); + + /* 元素出堆疊 */ + stack.pop(); // 無返回值 + + /* 獲取堆疊的長度 */ + int size = stack.size(); + + /* 判斷是否為空 */ + bool empty = stack.empty(); + ``` + +=== "Java" + + ```java title="stack.java" + /* 初始化堆疊 */ + Stack stack = new Stack<>(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + + /* 元素出堆疊 */ + int pop = stack.pop(); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + ``` + +=== "C#" + + ```csharp title="stack.cs" + /* 初始化堆疊 */ + Stack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + + /* 獲取堆疊的長度 */ + int size = stack.Count; + + /* 判斷是否為空 */ + bool isEmpty = stack.Count == 0; + ``` + +=== "Go" + + ```go title="stack_test.go" + /* 初始化堆疊 */ + // 在 Go 中,推薦將 Slice 當作堆疊來使用 + var stack []int + + /* 元素入堆疊 */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + + /* 訪問堆疊頂元素 */ + peek := stack[len(stack)-1] + + /* 元素出堆疊 */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + /* 獲取堆疊的長度 */ + size := len(stack) + + /* 判斷是否為空 */ + isEmpty := len(stack) == 0 + ``` + +=== "Swift" + + ```swift title="stack.swift" + /* 初始化堆疊 */ + // Swift 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + var stack: [Int] = [] + + /* 元素入堆疊 */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + /* 訪問堆疊頂元素 */ + let peek = stack.last! + + /* 元素出堆疊 */ + let pop = stack.removeLast() + + /* 獲取堆疊的長度 */ + let size = stack.count + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty + ``` + +=== "JS" + + ```javascript title="stack.js" + /* 初始化堆疊 */ + // JavaScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + const stack = []; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + const peek = stack[stack.length-1]; + + /* 元素出堆疊 */ + const pop = stack.pop(); + + /* 獲取堆疊的長度 */ + const size = stack.length; + + /* 判斷是否為空 */ + const is_empty = stack.length === 0; + ``` + +=== "TS" + + ```typescript title="stack.ts" + /* 初始化堆疊 */ + // TypeScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + const stack: number[] = []; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + const peek = stack[stack.length - 1]; + + /* 元素出堆疊 */ + const pop = stack.pop(); + + /* 獲取堆疊的長度 */ + const size = stack.length; + + /* 判斷是否為空 */ + const is_empty = stack.length === 0; + ``` + +=== "Dart" + + ```dart title="stack.dart" + /* 初始化堆疊 */ + // Dart 沒有內建的堆疊類別,可以把 List 當作堆疊來使用 + List stack = []; + + /* 元素入堆疊 */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + + /* 訪問堆疊頂元素 */ + int peek = stack.last; + + /* 元素出堆疊 */ + int pop = stack.removeLast(); + + /* 獲取堆疊的長度 */ + int size = stack.length; + + /* 判斷是否為空 */ + bool isEmpty = stack.isEmpty; + ``` + +=== "Rust" + + ```rust title="stack.rs" + /* 初始化堆疊 */ + // 把 Vec 當作堆疊來使用 + let mut stack: Vec = Vec::new(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + let top = stack.last().unwrap(); + + /* 元素出堆疊 */ + let pop = stack.pop().unwrap(); + + /* 獲取堆疊的長度 */ + let size = stack.len(); + + /* 判斷是否為空 */ + let is_empty = stack.is_empty(); + ``` + +=== "C" + + ```c title="stack.c" + // C 未提供內建堆疊 + ``` + +=== "Kotlin" + + ```kotlin title="stack.kt" + /* 初始化堆疊 */ + val stack = Stack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + + /* 訪問堆疊頂元素 */ + val peek = stack.peek() + + /* 元素出堆疊 */ + val pop = stack.pop() + + /* 獲取堆疊的長度 */ + val size = stack.size + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="stack.rb" + # 初始化堆疊 + # Ruby 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + stack = [] + + # 元素入堆疊 + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 + + # 訪問堆疊頂元素 + peek = stack.last + + # 元素出堆疊 + pop = stack.pop + + # 獲取堆疊的長度 + size = stack.length + + # 判斷是否為空 + is_empty = stack.empty? + ``` + +=== "Zig" + + ```zig title="stack.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A0%86%E7%96%8A%0A%20%20%20%20%23%20Python%20%E6%B2%92%E6%9C%89%E5%85%A7%E5%BB%BA%E7%9A%84%E5%A0%86%E7%96%8A%E9%A1%9E%E5%88%A5%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E7%95%B6%E4%BD%9C%E5%A0%86%E7%96%8A%E4%BE%86%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%96%8A%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%96%8A%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%BE%8C%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 堆疊的實現 + +為了深入瞭解堆疊的執行機制,我們來嘗試自己實現一個堆疊類別。 + +堆疊遵循先入後出的原則,因此我們只能在堆疊頂新增或刪除元素。然而,陣列和鏈結串列都可以在任意位置新增和刪除元素,**因此堆疊可以視為一種受限制的陣列或鏈結串列**。換句話說,我們可以“遮蔽”陣列或鏈結串列的部分無關操作,使其對外表現的邏輯符合堆疊的特性。 + +### 基於鏈結串列的實現 + +使用鏈結串列實現堆疊時,我們可以將鏈結串列的頭節點視為堆疊頂,尾節點視為堆疊底。 + +如下圖所示,對於入堆疊操作,我們只需將元素插入鏈結串列頭部,這種節點插入方法被稱為“頭插法”。而對於出堆疊操作,只需將頭節點從鏈結串列中刪除即可。 + +=== "LinkedListStack" + ![基於鏈結串列實現堆疊的入堆疊出堆疊操作](stack.assets/linkedlist_stack_step1.png) + +=== "push()" + ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) + +=== "pop()" + ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) + +以下是基於鏈結串列實現堆疊的示例程式碼: + +```src +[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} +``` + +### 基於陣列的實現 + +使用陣列實現堆疊時,我們可以將陣列的尾部作為堆疊頂。如下圖所示,入堆疊與出堆疊操作分別對應在陣列尾部新增元素與刪除元素,時間複雜度都為 $O(1)$ 。 + +=== "ArrayStack" + ![基於陣列實現堆疊的入堆疊出堆疊操作](stack.assets/array_stack_step1.png) + +=== "push()" + ![array_stack_push](stack.assets/array_stack_step2_push.png) + +=== "pop()" + ![array_stack_pop](stack.assets/array_stack_step3_pop.png) + +由於入堆疊的元素可能會源源不斷地增加,因此我們可以使用動態陣列,這樣就無須自行處理陣列擴容問題。以下為示例程式碼: + +```src +[file]{array_stack}-[class]{array_stack}-[func]{} +``` + +## 兩種實現對比 + +**支持操作** + +兩種實現都支持堆疊定義中的各項操作。陣列實現額外支持隨機訪問,但這已超出了堆疊的定義範疇,因此一般不會用到。 + +**時間效率** + +在基於陣列的實現中,入堆疊和出堆疊操作都在預先分配好的連續記憶體中進行,具有很好的快取本地性,因此效率較高。然而,如果入堆疊時超出陣列容量,會觸發擴容機制,導致該次入堆疊操作的時間複雜度變為 $O(n)$ 。 + +在基於鏈結串列的實現中,鏈結串列的擴容非常靈活,不存在上述陣列擴容時效率降低的問題。但是,入堆疊操作需要初始化節點物件並修改指標,因此效率相對較低。不過,如果入堆疊元素本身就是節點物件,那麼可以省去初始化步驟,從而提高效率。 + +綜上所述,當入堆疊與出堆疊操作的元素是基本資料型別時,例如 `int` 或 `double` ,我們可以得出以下結論。 + +- 基於陣列實現的堆疊在觸發擴容時效率會降低,但由於擴容是低頻操作,因此平均效率更高。 +- 基於鏈結串列實現的堆疊可以提供更加穩定的效率表現。 + +**空間效率** + +在初始化串列時,系統會為串列分配“初始容量”,該容量可能超出實際需求;並且,擴容機制通常是按照特定倍率(例如 2 倍)進行擴容的,擴容後的容量也可能超出實際需求。因此,**基於陣列實現的堆疊可能造成一定的空間浪費**。 + +然而,由於鏈結串列節點需要額外儲存指標,**因此鏈結串列節點佔用的空間相對較大**。 + +綜上,我們不能簡單地確定哪種實現更加節省記憶體,需要針對具體情況進行分析。 + +## 堆疊的典型應用 + +- **瀏覽器中的後退與前進、軟體中的撤銷與反撤銷**。每當我們開啟新的網頁,瀏覽器就會對上一個網頁執行入堆疊,這樣我們就可以通過後退操作回到上一個網頁。後退操作實際上是在執行出堆疊。如果要同時支持後退和前進,那麼需要兩個堆疊來配合實現。 +- **程式記憶體管理**。每次呼叫函式時,系統都會在堆疊頂新增一個堆疊幀,用於記錄函式的上下文資訊。在遞迴函式中,向下遞推階段會不斷執行入堆疊操作,而向上回溯階段則會不斷執行出堆疊操作。 diff --git a/zh-hant/docs/chapter_stack_and_queue/summary.md b/zh-hant/docs/chapter_stack_and_queue/summary.md new file mode 100644 index 0000000000..128f7d7dd1 --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/summary.md @@ -0,0 +1,31 @@ +# 小結 + +### 重點回顧 + +- 堆疊是一種遵循先入後出原則的資料結構,可透過陣列或鏈結串列來實現。 +- 在時間效率方面,堆疊的陣列實現具有較高的平均效率,但在擴容過程中,單次入堆疊操作的時間複雜度會劣化至 $O(n)$ 。相比之下,堆疊的鏈結串列實現具有更為穩定的效率表現。 +- 在空間效率方面,堆疊的陣列實現可能導致一定程度的空間浪費。但需要注意的是,鏈結串列節點所佔用的記憶體空間比陣列元素更大。 +- 佇列是一種遵循先入先出原則的資料結構,同樣可以透過陣列或鏈結串列來實現。在時間效率和空間效率的對比上,佇列的結論與前述堆疊的結論相似。 +- 雙向佇列是一種具有更高自由度的佇列,它允許在兩端進行元素的新增和刪除操作。 + +### Q & A + +**Q**:瀏覽器的前進後退是否是雙向鏈結串列實現? + +瀏覽器的前進後退功能本質上是“堆疊”的體現。當用戶訪問一個新頁面時,該頁面會被新增到堆疊頂;當用戶點選後退按鈕時,該頁面會從堆疊頂彈出。使用雙向佇列可以方便地實現一些額外操作,這個在“雙向佇列”章節有提到。 + +**Q**:在出堆疊後,是否需要釋放出堆疊節點的記憶體? + +如果後續仍需要使用彈出節點,則不需要釋放記憶體。若之後不需要用到,`Java` 和 `Python` 等語言擁有自動垃圾回收機制,因此不需要手動釋放記憶體;在 `C` 和 `C++` 中需要手動釋放記憶體。 + +**Q**:雙向佇列像是兩個堆疊拼接在了一起,它的用途是什麼? + +雙向佇列就像是堆疊和佇列的組合或兩個堆疊拼在了一起。它表現的是堆疊 + 佇列的邏輯,因此可以實現堆疊與佇列的所有應用,並且更加靈活。 + +**Q**:撤銷(undo)和反撤銷(redo)具體是如何實現的? + +使用兩個堆疊,堆疊 `A` 用於撤銷,堆疊 `B` 用於反撤銷。 + +1. 每當使用者執行一個操作,將這個操作壓入堆疊 `A` ,並清空堆疊 `B` 。 +2. 當用戶執行“撤銷”時,從堆疊 `A` 中彈出最近的操作,並將其壓入堆疊 `B` 。 +3. 當用戶執行“反撤銷”時,從堆疊 `B` 中彈出最近的操作,並將其壓入堆疊 `A` 。 diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png new file mode 100644 index 0000000000..e65009d56f Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png new file mode 100644 index 0000000000..0c9a92976c Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png new file mode 100644 index 0000000000..abe5d48251 Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png new file mode 100644 index 0000000000..124a2c7165 Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.md b/zh-hant/docs/chapter_tree/array_representation_of_tree.md new file mode 100644 index 0000000000..6f395089b5 --- /dev/null +++ b/zh-hant/docs/chapter_tree/array_representation_of_tree.md @@ -0,0 +1,166 @@ +# 二元樹陣列表示 + +在鏈結串列表示下,二元樹的儲存單元為節點 `TreeNode` ,節點之間透過指標相連線。上一節介紹了鏈結串列表示下的二元樹的各項基本操作。 + +那麼,我們能否用陣列來表示二元樹呢?答案是肯定的。 + +## 表示完美二元樹 + +先分析一個簡單案例。給定一棵完美二元樹,我們將所有節點按照層序走訪的順序儲存在一個陣列中,則每個節點都對應唯一的陣列索引。 + +根據層序走訪的特性,我們可以推導出父節點索引與子節點索引之間的“對映公式”:**若某節點的索引為 $i$ ,則該節點的左子節點索引為 $2i + 1$ ,右子節點索引為 $2i + 2$** 。下圖展示了各個節點索引之間的對映關係。 + +![完美二元樹的陣列表示](array_representation_of_tree.assets/array_representation_binary_tree.png) + +**對映公式的角色相當於鏈結串列中的節點引用(指標)**。給定陣列中的任意一個節點,我們都可以透過對映公式來訪問它的左(右)子節點。 + +## 表示任意二元樹 + +完美二元樹是一個特例,在二元樹的中間層通常存在許多 `None` 。由於層序走訪序列並不包含這些 `None` ,因此我們無法僅憑該序列來推測 `None` 的數量和分佈位置。**這意味著存在多種二元樹結構都符合該層序走訪序列**。 + +如下圖所示,給定一棵非完美二元樹,上述陣列表示方法已經失效。 + +![層序走訪序列對應多種二元樹可能性](array_representation_of_tree.assets/array_representation_without_empty.png) + +為了解決此問題,**我們可以考慮在層序走訪序列中顯式地寫出所有 `None`** 。如下圖所示,這樣處理後,層序走訪序列就可以唯一表示二元樹了。示例程式碼如下: + +=== "Python" + + ```python title="" + # 二元樹的陣列表示 + # 使用 None 來表示空位 + tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + ``` + +=== "C++" + + ```cpp title="" + /* 二元樹的陣列表示 */ + // 使用 int 最大值 INT_MAX 標記空位 + vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Java" + + ```java title="" + /* 二元樹的陣列表示 */ + // 使用 int 的包裝類別 Integer ,就可以使用 null 來標記空位 + Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ``` + +=== "C#" + + ```csharp title="" + /* 二元樹的陣列表示 */ + // 使用 int? 可空型別 ,就可以使用 null 來標記空位 + int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Go" + + ```go title="" + /* 二元樹的陣列表示 */ + // 使用 any 型別的切片, 就可以使用 nil 來標記空位 + tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + ``` + +=== "Swift" + + ```swift title="" + /* 二元樹的陣列表示 */ + // 使用 Int? 可空型別 ,就可以使用 nil 來標記空位 + let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "JS" + + ```javascript title="" + /* 二元樹的陣列表示 */ + // 使用 null 來表示空位 + let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "TS" + + ```typescript title="" + /* 二元樹的陣列表示 */ + // 使用 null 來表示空位 + let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Dart" + + ```dart title="" + /* 二元樹的陣列表示 */ + // 使用 int? 可空型別 ,就可以使用 null 來標記空位 + List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Rust" + + ```rust title="" + /* 二元樹的陣列表示 */ + // 使用 None 來標記空位 + let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; + ``` + +=== "C" + + ```c title="" + /* 二元樹的陣列表示 */ + // 使用 int 最大值標記空位,因此要求節點值不能為 INT_MAX + int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 二元樹的陣列表示 */ + // 使用 null 來表示空位 + val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) + ``` + +=== "Ruby" + + ```ruby title="" + ### 二元樹的陣列表示 ### + # 使用 nil 來表示空位 + tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "Zig" + + ```zig title="" + + ``` + +![任意型別二元樹的陣列表示](array_representation_of_tree.assets/array_representation_with_empty.png) + +值得說明的是,**完全二元樹非常適合使用陣列來表示**。回顧完全二元樹的定義,`None` 只出現在最底層且靠右的位置,**因此所有 `None` 一定出現在層序走訪序列的末尾**。 + +這意味著使用陣列表示完全二元樹時,可以省略儲存所有 `None` ,非常方便。下圖給出了一個例子。 + +![完全二元樹的陣列表示](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) + +以下程式碼實現了一棵基於陣列表示的二元樹,包括以下幾種操作。 + +- 給定某節點,獲取它的值、左(右)子節點、父節點。 +- 獲取前序走訪、中序走訪、後序走訪、層序走訪序列。 + +```src +[file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} +``` + +## 優點與侷限性 + +二元樹的陣列表示主要有以下優點。 + +- 陣列儲存在連續的記憶體空間中,對快取友好,訪問與走訪速度較快。 +- 不需要儲存指標,比較節省空間。 +- 允許隨機訪問節點。 + +然而,陣列表示也存在一些侷限性。 + +- 陣列儲存需要連續記憶體空間,因此不適合儲存資料量過大的樹。 +- 增刪節點需要透過陣列插入與刪除操作實現,效率較低。 +- 當二元樹中存在大量 `None` 時,陣列中包含的節點資料比重較低,空間利用率較低。 diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png new file mode 100644 index 0000000000..36e1725399 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png new file mode 100644 index 0000000000..ba6bb73b45 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png new file mode 100644 index 0000000000..2663067827 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png new file mode 100644 index 0000000000..f9621a4a60 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png new file mode 100644 index 0000000000..7c22bf295c Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png new file mode 100644 index 0000000000..1c8fa74a56 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png new file mode 100644 index 0000000000..0b8c52407d Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png new file mode 100644 index 0000000000..28b776856d Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png new file mode 100644 index 0000000000..831538b377 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png new file mode 100644 index 0000000000..956552c3d1 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png new file mode 100644 index 0000000000..bf905a7476 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png new file mode 100644 index 0000000000..7267331620 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.md b/zh-hant/docs/chapter_tree/avl_tree.md new file mode 100644 index 0000000000..1a2c0a93d8 --- /dev/null +++ b/zh-hant/docs/chapter_tree/avl_tree.md @@ -0,0 +1,364 @@ +# AVL 樹 * + +在“二元搜尋樹”章節中我們提到,在多次插入和刪除操作後,二元搜尋樹可能退化為鏈結串列。在這種情況下,所有操作的時間複雜度將從 $O(\log n)$ 劣化為 $O(n)$ 。 + +如下圖所示,經過兩次刪除節點操作,這棵二元搜尋樹便會退化為鏈結串列。 + +![AVL 樹在刪除節點後發生退化](avl_tree.assets/avltree_degradation_from_removing_node.png) + +再例如,在下圖所示的完美二元樹中插入兩個節點後,樹將嚴重向左傾斜,查詢操作的時間複雜度也隨之劣化。 + +![AVL 樹在插入節點後發生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png) + +1962 年 G. M. Adelson-Velsky 和 E. M. Landis 在論文“An algorithm for the organization of information”中提出了 AVL 樹。論文中詳細描述了一系列操作,確保在持續新增和刪除節點後,AVL 樹不會退化,從而使得各種操作的時間複雜度保持在 $O(\log n)$ 級別。換句話說,在需要頻繁進行增刪查改操作的場景中,AVL 樹能始終保持高效的資料操作效能,具有很好的應用價值。 + +## AVL 樹常見術語 + +AVL 樹既是二元搜尋樹,也是平衡二元樹,同時滿足這兩類二元樹的所有性質,因此是一種平衡二元搜尋樹(balanced binary search tree)。 + +### 節點高度 + +由於 AVL 樹的相關操作需要獲取節點高度,因此我們需要為節點類別新增 `height` 變數: + +=== "Python" + + ```python title="" + class TreeNode: + """AVL 樹節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.height: int = 0 # 節點高度 + self.left: TreeNode | None = None # 左子節點引用 + self.right: TreeNode | None = None # 右子節點引用 + ``` + +=== "C++" + + ```cpp title="" + /* AVL 樹節點類別 */ + struct TreeNode { + int val{}; // 節點值 + int height = 0; // 節點高度 + TreeNode *left{}; // 左子節點 + TreeNode *right{}; // 右子節點 + TreeNode() = default; + explicit TreeNode(int x) : val(x){} + }; + ``` + +=== "Java" + + ```java title="" + /* AVL 樹節點類別 */ + class TreeNode { + public int val; // 節點值 + public int height; // 節點高度 + public TreeNode left; // 左子節點 + public TreeNode right; // 右子節點 + public TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* AVL 樹節點類別 */ + class TreeNode(int? x) { + public int? val = x; // 節點值 + public int height; // 節點高度 + public TreeNode? left; // 左子節點引用 + public TreeNode? right; // 右子節點引用 + } + ``` + +=== "Go" + + ```go title="" + /* AVL 樹節點結構體 */ + type TreeNode struct { + Val int // 節點值 + Height int // 節點高度 + Left *TreeNode // 左子節點引用 + Right *TreeNode // 右子節點引用 + } + ``` + +=== "Swift" + + ```swift title="" + /* AVL 樹節點類別 */ + class TreeNode { + var val: Int // 節點值 + var height: Int // 節點高度 + var left: TreeNode? // 左子節點 + var right: TreeNode? // 右子節點 + + init(x: Int) { + val = x + height = 0 + } + } + ``` + +=== "JS" + + ```javascript title="" + /* AVL 樹節點類別 */ + class TreeNode { + val; // 節點值 + height; //節點高度 + left; // 左子節點指標 + right; // 右子節點指標 + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* AVL 樹節點類別 */ + class TreeNode { + val: number; // 節點值 + height: number; // 節點高度 + left: TreeNode | null; // 左子節點指標 + right: TreeNode | null; // 右子節點指標 + constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "Dart" + + ```dart title="" + /* AVL 樹節點類別 */ + class TreeNode { + int val; // 節點值 + int height; // 節點高度 + TreeNode? left; // 左子節點 + TreeNode? right; // 右子節點 + TreeNode(this.val, [this.height = 0, this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* AVL 樹節點結構體 */ + struct TreeNode { + val: i32, // 節點值 + height: i32, // 節點高度 + left: Option>>, // 左子節點 + right: Option>>, // 右子節點 + } + + impl TreeNode { + /* 建構子 */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* AVL 樹節點結構體 */ + typedef struct TreeNode { + int val; + int height; + struct TreeNode *left; + struct TreeNode *right; + } TreeNode; + + /* 建構子 */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* AVL 樹節點類別 */ + class TreeNode(val _val: Int) { // 節點值 + val height: Int = 0 // 節點高度 + val left: TreeNode? = null // 左子節點 + val right: TreeNode? = null // 右子節點 + } + ``` + +=== "Ruby" + + ```ruby title="" + ### AVL 樹節點類別 ### + class TreeNode + attr_accessor :val # 節點值 + attr_accessor :height # 節點高度 + attr_accessor :left # 左子節點引用 + attr_accessor :right # 右子節點引用 + + def initialize(val) + @val = val + @height = 0 + end + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +“節點高度”是指從該節點到它的最遠葉節點的距離,即所經過的“邊”的數量。需要特別注意的是,葉節點的高度為 $0$ ,而空節點的高度為 $-1$ 。我們將建立兩個工具函式,分別用於獲取和更新節點的高度: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{update_height} +``` + +### 節點平衡因子 + +節點的平衡因子(balance factor)定義為節點左子樹的高度減去右子樹的高度,同時規定空節點的平衡因子為 $0$ 。我們同樣將獲取節點平衡因子的功能封裝成函式,方便後續使用: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} +``` + +!!! tip + + 設平衡因子為 $f$ ,則一棵 AVL 樹的任意節點的平衡因子皆滿足 $-1 \le f \le 1$ 。 + +## AVL 樹旋轉 + +AVL 樹的特點在於“旋轉”操作,它能夠在不影響二元樹的中序走訪序列的前提下,使失衡節點重新恢復平衡。換句話說,**旋轉操作既能保持“二元搜尋樹”的性質,也能使樹重新變為“平衡二元樹”**。 + +我們將平衡因子絕對值 $> 1$ 的節點稱為“失衡節點”。根據節點失衡情況的不同,旋轉操作分為四種:右旋、左旋、先右旋後左旋、先左旋後右旋。下面詳細介紹這些旋轉操作。 + +### 右旋 + +如下圖所示,節點下方為平衡因子。從底至頂看,二元樹中首個失衡節點是“節點 3”。我們關注以該失衡節點為根節點的子樹,將該節點記為 `node` ,其左子節點記為 `child` ,執行“右旋”操作。完成右旋後,子樹恢復平衡,並且仍然保持二元搜尋樹的性質。 + +=== "<1>" + ![右旋操作步驟](avl_tree.assets/avltree_right_rotate_step1.png) + +=== "<2>" + ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) + +=== "<3>" + ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) + +=== "<4>" + ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) + +如下圖所示,當節點 `child` 有右子節點(記為 `grand_child` )時,需要在右旋中新增一步:將 `grand_child` 作為 `node` 的左子節點。 + +![有 grand_child 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png) + +“向右旋轉”是一種形象化的說法,實際上需要透過修改節點指標來實現,程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} +``` + +### 左旋 + +相應地,如果考慮上述失衡二元樹的“映象”,則需要執行下圖所示的“左旋”操作。 + +![左旋操作](avl_tree.assets/avltree_left_rotate.png) + +同理,如下圖所示,當節點 `child` 有左子節點(記為 `grand_child` )時,需要在左旋中新增一步:將 `grand_child` 作為 `node` 的右子節點。 + +![有 grand_child 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png) + +可以觀察到,**右旋和左旋操作在邏輯上是映象對稱的,它們分別解決的兩種失衡情況也是對稱的**。基於對稱性,我們只需將右旋的實現程式碼中的所有的 `left` 替換為 `right` ,將所有的 `right` 替換為 `left` ,即可得到左旋的實現程式碼: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} +``` + +### 先左旋後右旋 + +對於下圖中的失衡節點 3 ,僅使用左旋或右旋都無法使子樹恢復平衡。此時需要先對 `child` 執行“左旋”,再對 `node` 執行“右旋”。 + +![先左旋後右旋](avl_tree.assets/avltree_left_right_rotate.png) + +### 先右旋後左旋 + +如下圖所示,對於上述失衡二元樹的映象情況,需要先對 `child` 執行“右旋”,再對 `node` 執行“左旋”。 + +![先右旋後左旋](avl_tree.assets/avltree_right_left_rotate.png) + +### 旋轉的選擇 + +下圖展示的四種失衡情況與上述案例逐個對應,分別需要採用右旋、先左旋後右旋、先右旋後左旋、左旋的操作。 + +![AVL 樹的四種旋轉情況](avl_tree.assets/avltree_rotation_cases.png) + +如下表所示,我們透過判斷失衡節點的平衡因子以及較高一側子節點的平衡因子的正負號,來確定失衡節點屬於上圖中的哪種情況。 + +

  四種旋轉情況的選擇條件

+ +| 失衡節點的平衡因子 | 子節點的平衡因子 | 應採用的旋轉方法 | +| ------------------ | ---------------- | ---------------- | +| $> 1$ (左偏樹) | $\geq 0$ | 右旋 | +| $> 1$ (左偏樹) | $<0$ | 先左旋後右旋 | +| $< -1$ (右偏樹) | $\leq 0$ | 左旋 | +| $< -1$ (右偏樹) | $>0$ | 先右旋後左旋 | + +為了便於使用,我們將旋轉操作封裝成一個函式。**有了這個函式,我們就能對各種失衡情況進行旋轉,使失衡節點重新恢復平衡**。程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{rotate} +``` + +## AVL 樹常用操作 + +### 插入節點 + +AVL 樹的節點插入操作與二元搜尋樹在主體上類似。唯一的區別在於,在 AVL 樹中插入節點後,從該節點到根節點的路徑上可能會出現一系列失衡節點。因此,**我們需要從這個節點開始,自底向上執行旋轉操作,使所有失衡節點恢復平衡**。程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} +``` + +### 刪除節點 + +類似地,在二元搜尋樹的刪除節點方法的基礎上,需要從底至頂執行旋轉操作,使所有失衡節點恢復平衡。程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} +``` + +### 查詢節點 + +AVL 樹的節點查詢操作與二元搜尋樹一致,在此不再贅述。 + +## AVL 樹典型應用 + +- 組織和儲存大型資料,適用於高頻查詢、低頻增刪的場景。 +- 用於構建資料庫中的索引系統。 +- 紅黑樹也是一種常見的平衡二元搜尋樹。相較於 AVL 樹,紅黑樹的平衡條件更寬鬆,插入與刪除節點所需的旋轉操作更少,節點增刪操作的平均效率更高。 diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png new file mode 100644 index 0000000000..dc56cb3d47 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png new file mode 100644 index 0000000000..88f9135ada Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png new file mode 100644 index 0000000000..72e21e95e2 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_insert.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_insert.png new file mode 100644 index 0000000000..405e58c491 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_insert.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png new file mode 100644 index 0000000000..fb4b5b95cb Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png new file mode 100644 index 0000000000..0e47addbed Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png new file mode 100644 index 0000000000..ed8055b126 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png new file mode 100644 index 0000000000..5709cac6b2 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png new file mode 100644 index 0000000000..7678126f54 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png new file mode 100644 index 0000000000..d4efd20fb3 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png new file mode 100644 index 0000000000..e3dd87fa96 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png new file mode 100644 index 0000000000..6c491abf50 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png new file mode 100644 index 0000000000..148441cb57 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png new file mode 100644 index 0000000000..17b4b4c06c Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.md b/zh-hant/docs/chapter_tree/binary_search_tree.md new file mode 100755 index 0000000000..0ffaa61390 --- /dev/null +++ b/zh-hant/docs/chapter_tree/binary_search_tree.md @@ -0,0 +1,129 @@ +# 二元搜尋樹 + +如下圖所示,二元搜尋樹(binary search tree)滿足以下條件。 + +1. 對於根節點,左子樹中所有節點的值 $<$ 根節點的值 $<$ 右子樹中所有節點的值。 +2. 任意節點的左、右子樹也是二元搜尋樹,即同樣滿足條件 `1.` 。 + +![二元搜尋樹](binary_search_tree.assets/binary_search_tree.png) + +## 二元搜尋樹的操作 + +我們將二元搜尋樹封裝為一個類別 `BinarySearchTree` ,並宣告一個成員變數 `root` ,指向樹的根節點。 + +### 查詢節點 + +給定目標節點值 `num` ,可以根據二元搜尋樹的性質來查詢。如下圖所示,我們宣告一個節點 `cur` ,從二元樹的根節點 `root` 出發,迴圈比較節點值 `cur.val` 和 `num` 之間的大小關係。 + +- 若 `cur.val < num` ,說明目標節點在 `cur` 的右子樹中,因此執行 `cur = cur.right` 。 +- 若 `cur.val > num` ,說明目標節點在 `cur` 的左子樹中,因此執行 `cur = cur.left` 。 +- 若 `cur.val = num` ,說明找到目標節點,跳出迴圈並返回該節點。 + +=== "<1>" + ![二元搜尋樹查詢節點示例](binary_search_tree.assets/bst_search_step1.png) + +=== "<2>" + ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) + +=== "<3>" + ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) + +=== "<4>" + ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) + +二元搜尋樹的查詢操作與二分搜尋演算法的工作原理一致,都是每輪排除一半情況。迴圈次數最多為二元樹的高度,當二元樹平衡時,使用 $O(\log n)$ 時間。示例程式碼如下: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} +``` + +### 插入節點 + +給定一個待插入元素 `num` ,為了保持二元搜尋樹“左子樹 < 根節點 < 右子樹”的性質,插入操作流程如下圖所示。 + +1. **查詢插入位置**:與查詢操作相似,從根節點出發,根據當前節點值和 `num` 的大小關係迴圈向下搜尋,直到越過葉節點(走訪至 `None` )時跳出迴圈。 +2. **在該位置插入節點**:初始化節點 `num` ,將該節點置於 `None` 的位置。 + +![在二元搜尋樹中插入節點](binary_search_tree.assets/bst_insert.png) + +在程式碼實現中,需要注意以下兩點。 + +- 二元搜尋樹不允許存在重複節點,否則將違反其定義。因此,若待插入節點在樹中已存在,則不執行插入,直接返回。 +- 為了實現插入節點,我們需要藉助節點 `pre` 儲存上一輪迴圈的節點。這樣在走訪至 `None` 時,我們可以獲取到其父節點,從而完成節點插入操作。 + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} +``` + +與查詢節點相同,插入節點使用 $O(\log n)$ 時間。 + +### 刪除節點 + +先在二元樹中查詢到目標節點,再將其刪除。與插入節點類似,我們需要保證在刪除操作完成後,二元搜尋樹的“左子樹 < 根節點 < 右子樹”的性質仍然滿足。因此,我們根據目標節點的子節點數量,分 0、1 和 2 三種情況,執行對應的刪除節點操作。 + +如下圖所示,當待刪除節點的度為 $0$ 時,表示該節點是葉節點,可以直接刪除。 + +![在二元搜尋樹中刪除節點(度為 0 )](binary_search_tree.assets/bst_remove_case1.png) + +如下圖所示,當待刪除節點的度為 $1$ 時,將待刪除節點替換為其子節點即可。 + +![在二元搜尋樹中刪除節點(度為 1 )](binary_search_tree.assets/bst_remove_case2.png) + +當待刪除節點的度為 $2$ 時,我們無法直接刪除它,而需要使用一個節點替換該節點。由於要保持二元搜尋樹“左子樹 $<$ 根節點 $<$ 右子樹”的性質,**因此這個節點可以是右子樹的最小節點或左子樹的最大節點**。 + +假設我們選擇右子樹的最小節點(中序走訪的下一個節點),則刪除操作流程如下圖所示。 + +1. 找到待刪除節點在“中序走訪序列”中的下一個節點,記為 `tmp` 。 +2. 用 `tmp` 的值覆蓋待刪除節點的值,並在樹中遞迴刪除節點 `tmp` 。 + +=== "<1>" + ![在二元搜尋樹中刪除節點(度為 2 )](binary_search_tree.assets/bst_remove_case3_step1.png) + +=== "<2>" + ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) + +=== "<3>" + ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) + +=== "<4>" + ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) + +刪除節點操作同樣使用 $O(\log n)$ 時間,其中查詢待刪除節點需要 $O(\log n)$ 時間,獲取中序走訪後繼節點需要 $O(\log n)$ 時間。示例程式碼如下: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} +``` + +### 中序走訪有序 + +如下圖所示,二元樹的中序走訪遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的走訪順序,而二元搜尋樹滿足“左子節點 $<$ 根節點 $<$ 右子節點”的大小關係。 + +這意味著在二元搜尋樹中進行中序走訪時,總是會優先走訪下一個最小節點,從而得出一個重要性質:**二元搜尋樹的中序走訪序列是升序的**。 + +利用中序走訪升序的性質,我們在二元搜尋樹中獲取有序資料僅需 $O(n)$ 時間,無須進行額外的排序操作,非常高效。 + +![二元搜尋樹的中序走訪序列](binary_search_tree.assets/bst_inorder_traversal.png) + +## 二元搜尋樹的效率 + +給定一組資料,我們考慮使用陣列或二元搜尋樹儲存。觀察下表,二元搜尋樹的各項操作的時間複雜度都是對數階,具有穩定且高效的效能。只有在高頻新增、低頻查詢刪除資料的場景下,陣列比二元搜尋樹的效率更高。 + +

  陣列與搜尋樹的效率對比

+ +| | 無序陣列 | 二元搜尋樹 | +| -------- | -------- | ----------- | +| 查詢元素 | $O(n)$ | $O(\log n)$ | +| 插入元素 | $O(1)$ | $O(\log n)$ | +| 刪除元素 | $O(n)$ | $O(\log n)$ | + +在理想情況下,二元搜尋樹是“平衡”的,這樣就可以在 $\log n$ 輪迴圈內查詢任意節點。 + +然而,如果我們在二元搜尋樹中不斷地插入和刪除節點,可能導致二元樹退化為下圖所示的鏈結串列,這時各種操作的時間複雜度也會退化為 $O(n)$ 。 + +![二元搜尋樹退化](binary_search_tree.assets/bst_degradation.png) + +## 二元搜尋樹常見應用 + +- 用作系統中的多級索引,實現高效的查詢、插入、刪除操作。 +- 作為某些搜尋演算法的底層資料結構。 +- 用於儲存資料流,以保持其有序狀態。 diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png new file mode 100644 index 0000000000..bda56095a1 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png new file mode 100644 index 0000000000..e0ada30aee Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png new file mode 100644 index 0000000000..2074c16c63 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png new file mode 100644 index 0000000000..138c51c06b Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png new file mode 100644 index 0000000000..d23dbdbbe2 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png new file mode 100644 index 0000000000..b46d6ace79 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/full_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/full_binary_tree.png new file mode 100644 index 0000000000..889a0143a5 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/full_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png new file mode 100644 index 0000000000..bb8a408def Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.md b/zh-hant/docs/chapter_tree/binary_tree.md new file mode 100644 index 0000000000..a2e590b19b --- /dev/null +++ b/zh-hant/docs/chapter_tree/binary_tree.md @@ -0,0 +1,692 @@ +# 二元樹 + +二元樹(binary tree)是一種非線性資料結構,代表“祖先”與“後代”之間的派生關係,體現了“一分為二”的分治邏輯。與鏈結串列類似,二元樹的基本單元是節點,每個節點包含值、左子節點引用和右子節點引用。 + +=== "Python" + + ```python title="" + class TreeNode: + """二元樹節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.left: TreeNode | None = None # 左子節點引用 + self.right: TreeNode | None = None # 右子節點引用 + ``` + +=== "C++" + + ```cpp title="" + /* 二元樹節點結構體 */ + struct TreeNode { + int val; // 節點值 + TreeNode *left; // 左子節點指標 + TreeNode *right; // 右子節點指標 + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + }; + ``` + +=== "Java" + + ```java title="" + /* 二元樹節點類別 */ + class TreeNode { + int val; // 節點值 + TreeNode left; // 左子節點引用 + TreeNode right; // 右子節點引用 + TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* 二元樹節點類別 */ + class TreeNode(int? x) { + public int? val = x; // 節點值 + public TreeNode? left; // 左子節點引用 + public TreeNode? right; // 右子節點引用 + } + ``` + +=== "Go" + + ```go title="" + /* 二元樹節點結構體 */ + type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode + } + /* 建構子 */ + func NewTreeNode(v int) *TreeNode { + return &TreeNode{ + Left: nil, // 左子節點指標 + Right: nil, // 右子節點指標 + Val: v, // 節點值 + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 二元樹節點類別 */ + class TreeNode { + var val: Int // 節點值 + var left: TreeNode? // 左子節點引用 + var right: TreeNode? // 右子節點引用 + + init(x: Int) { + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 二元樹節點類別 */ + class TreeNode { + val; // 節點值 + left; // 左子節點指標 + right; // 右子節點指標 + constructor(val, left, right) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 二元樹節點類別 */ + class TreeNode { + val: number; + left: TreeNode | null; + right: TreeNode | null; + + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; // 節點值 + this.left = left === undefined ? null : left; // 左子節點引用 + this.right = right === undefined ? null : right; // 右子節點引用 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 二元樹節點類別 */ + class TreeNode { + int val; // 節點值 + TreeNode? left; // 左子節點引用 + TreeNode? right; // 右子節點引用 + TreeNode(this.val, [this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 二元樹節點結構體 */ + struct TreeNode { + val: i32, // 節點值 + left: Option>>, // 左子節點引用 + right: Option>>, // 右子節點引用 + } + + impl TreeNode { + /* 建構子 */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* 二元樹節點結構體 */ + typedef struct TreeNode { + int val; // 節點值 + int height; // 節點高度 + struct TreeNode *left; // 左子節點指標 + struct TreeNode *right; // 右子節點指標 + } TreeNode; + + /* 建構子 */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 二元樹節點類別 */ + class TreeNode(val _val: Int) { // 節點值 + val left: TreeNode? = null // 左子節點引用 + val right: TreeNode? = null // 右子節點引用 + } + ``` + +=== "Ruby" + + ```ruby title="" + ### 二元樹節點類別 ### + class TreeNode + attr_accessor :val # 節點值 + attr_accessor :left # 左子節點引用 + attr_accessor :right # 右子節點引用 + + def initialize(val) + @val = val + end + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +每個節點都有兩個引用(指標),分別指向左子節點(left-child node)右子節點(right-child node),該節點被稱為這兩個子節點的父節點(parent node)。當給定一個二元樹的節點時,我們將該節點的左子節點及其以下節點形成的樹稱為該節點的左子樹(left subtree),同理可得右子樹(right subtree)。 + +**在二元樹中,除葉節點外,其他所有節點都包含子節點和非空子樹**。如下圖所示,如果將“節點 2”視為父節點,則其左子節點和右子節點分別是“節點 4”和“節點 5”,左子樹是“節點 4 及其以下節點形成的樹”,右子樹是“節點 5 及其以下節點形成的樹”。 + +![父節點、子節點、子樹](binary_tree.assets/binary_tree_definition.png) + +## 二元樹常見術語 + +二元樹的常用術語如下圖所示。 + +- 根節點(root node):位於二元樹頂層的節點,沒有父節點。 +- 葉節點(leaf node):沒有子節點的節點,其兩個指標均指向 `None` 。 +- 邊(edge):連線兩個節點的線段,即節點引用(指標)。 +- 節點所在的層(level):從頂至底遞增,根節點所在層為 1 。 +- 節點的度(degree):節點的子節點的數量。在二元樹中,度的取值範圍是 0、1、2 。 +- 二元樹的高度(height):從根節點到最遠葉節點所經過的邊的數量。 +- 節點的深度(depth):從根節點到該節點所經過的邊的數量。 +- 節點的高度(height):從距離該節點最遠的葉節點到該節點所經過的邊的數量。 + +![二元樹的常用術語](binary_tree.assets/binary_tree_terminology.png) + +!!! tip + + 請注意,我們通常將“高度”和“深度”定義為“經過的邊的數量”,但有些題目或教材可能會將其定義為“經過的節點的數量”。在這種情況下,高度和深度都需要加 1 。 + +## 二元樹基本操作 + +### 初始化二元樹 + +與鏈結串列類似,首先初始化節點,然後構建引用(指標)。 + +=== "Python" + + ```python title="binary_tree.py" + # 初始化二元樹 + # 初始化節點 + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode* n1 = new TreeNode(1); + TreeNode* n2 = new TreeNode(2); + TreeNode* n3 = new TreeNode(3); + TreeNode* n4 = new TreeNode(4); + TreeNode* n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Java" + + ```java title="binary_tree.java" + // 初始化節點 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* 初始化二元樹 */ + // 初始化節點 + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // 構建節點之間的引用(指標) + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + // 初始化節點 + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* 初始化二元樹 */ + // 初始化節點 + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* 初始化二元樹 */ + // 初始化節點 + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + // 初始化節點 + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // 構建節點之間的引用(指標) + n1.borrow_mut().left = Some(n2.clone()); + n1.borrow_mut().right = Some(n3); + n2.borrow_mut().left = Some(n4); + n2.borrow_mut().right = Some(n5); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + // 初始化節點 + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + # 初始化二元樹 + # 初始化節點 + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%AF%80%E9%BB%9E%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E6%A8%99%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 插入與刪除節點 + +與鏈結串列類似,在二元樹中插入與刪除節點可以透過修改指標來實現。下圖給出了一個示例。 + +![在二元樹中插入與刪除節點](binary_tree.assets/binary_tree_add_remove.png) + +=== "Python" + + ```python title="binary_tree.py" + # 插入與刪除節點 + p = TreeNode(0) + # 在 n1 -> n2 中間插入節點 P + n1.left = p + p.left = n2 + # 刪除節點 P + n1.left = n2 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* 插入與刪除節點 */ + TreeNode* P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + // 刪除節點 P + n1->left = n2; + // 釋放記憶體 + delete P; + ``` + +=== "Java" + + ```java title="binary_tree.java" + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* 插入與刪除節點 */ + TreeNode P = new(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* 插入與刪除節點 */ + // 在 n1 -> n2 中間插入節點 P + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + // 刪除節點 P + n1.Left = n2 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + let P = TreeNode(x: 0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + // 刪除節點 P + n1.left = n2 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* 插入與刪除節點 */ + let P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* 插入與刪除節點 */ + const P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* 插入與刪除節點 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + let p = TreeNode::new(0); + // 在 n1 -> n2 中間插入節點 P + n1.borrow_mut().left = Some(p.clone()); + p.borrow_mut().left = Some(n2.clone()); + // 刪除節點 p + n1.borrow_mut().left = Some(n2); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* 插入與刪除節點 */ + TreeNode *P = newTreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + // 刪除節點 P + n1->left = n2; + // 釋放記憶體 + free(P); + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + val P = TreeNode(0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + // 刪除節點 P + n1.left = n2 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + # 插入與刪除節點 + _p = TreeNode.new(0) + # 在 n1 -> n2 中間插入節點 _p + n1.left = _p + _p.left = n2 + # 刪除節點 + n1.left = n2 + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%85%83%E6%A8%B9%E7%AF%80%E9%BB%9E%E9%A1%9E%E5%88%A5%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AF%80%E9%BB%9E%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E7%AF%80%E9%BB%9E%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%85%83%E6%A8%B9%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%AF%80%E9%BB%9E%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%A7%8B%E5%BB%BA%E7%AF%80%E9%BB%9E%E4%B9%8B%E9%96%93%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E6%A8%99%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%88%87%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%96%93%E6%8F%92%E5%85%A5%E7%AF%80%E9%BB%9E%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%AA%E9%99%A4%E7%AF%80%E9%BB%9E%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +!!! tip + + 需要注意的是,插入節點可能會改變二元樹的原有邏輯結構,而刪除節點通常意味著刪除該節點及其所有子樹。因此,在二元樹中,插入與刪除通常是由一套操作配合完成的,以實現有實際意義的操作。 + +## 常見二元樹型別 + +### 完美二元樹 + +如下圖所示,完美二元樹(perfect binary tree)所有層的節點都被完全填滿。在完美二元樹中,葉節點的度為 $0$ ,其餘所有節點的度都為 $2$ ;若樹的高度為 $h$ ,則節點總數為 $2^{h+1} - 1$ ,呈現標準的指數級關係,反映了自然界中常見的細胞分裂現象。 + +!!! tip + + 請注意,在中文社群中,完美二元樹常被稱為滿二元樹。 + +![完美二元樹](binary_tree.assets/perfect_binary_tree.png) + +### 完全二元樹 + +如下圖所示,完全二元樹(complete binary tree)僅允許最底層的節點不完全填滿,且最底層的節點必須從左至右依次連續填充。請注意,完美二元樹也是一棵完全二元樹。 + +![完全二元樹](binary_tree.assets/complete_binary_tree.png) + +### 完滿二元樹 + +如下圖所示,完滿二元樹(full binary tree)除了葉節點之外,其餘所有節點都有兩個子節點。 + +![完滿二元樹](binary_tree.assets/full_binary_tree.png) + +### 平衡二元樹 + +如下圖所示,平衡二元樹(balanced binary tree)中任意節點的左子樹和右子樹的高度之差的絕對值不超過 1 。 + +![平衡二元樹](binary_tree.assets/balanced_binary_tree.png) + +## 二元樹的退化 + +下圖展示了二元樹的理想結構與退化結構。當二元樹的每層節點都被填滿時,達到“完美二元樹”;而當所有節點都偏向一側時,二元樹退化為“鏈結串列”。 + +- 完美二元樹是理想情況,可以充分發揮二元樹“分治”的優勢。 +- 鏈結串列則是另一個極端,各項操作都變為線性操作,時間複雜度退化至 $O(n)$ 。 + +![二元樹的最佳結構與最差結構](binary_tree.assets/binary_tree_best_worst_cases.png) + +如下表所示,在最佳結構和最差結構下,二元樹的葉節點數量、節點總數、高度等達到極大值或極小值。 + +

  二元樹的最佳結構與最差結構

+ +| | 完美二元樹 | 鏈結串列 | +| --------------------------- | ------------------ | ------- | +| 第 $i$ 層的節點數量 | $2^{i-1}$ | $1$ | +| 高度為 $h$ 的樹的葉節點數量 | $2^h$ | $1$ | +| 高度為 $h$ 的樹的節點總數 | $2^{h+1} - 1$ | $h + 1$ | +| 節點總數為 $n$ 的樹的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png new file mode 100644 index 0000000000..cb98632f35 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png new file mode 100644 index 0000000000..2d5ed8333d Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png new file mode 100644 index 0000000000..c730ee8eb1 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png new file mode 100644 index 0000000000..3b09a8e803 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png new file mode 100644 index 0000000000..fffa7884cf Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png new file mode 100644 index 0000000000..f62028aada Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png new file mode 100644 index 0000000000..62519f18be Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png new file mode 100644 index 0000000000..04a8b3293f Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png new file mode 100644 index 0000000000..6d9aa8c56c Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png new file mode 100644 index 0000000000..452aae1bf3 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png new file mode 100644 index 0000000000..ed69e0e8e3 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png new file mode 100644 index 0000000000..70a373601a Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png new file mode 100644 index 0000000000..67d767d8d1 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.md b/zh-hant/docs/chapter_tree/binary_tree_traversal.md new file mode 100755 index 0000000000..f2a8dbfa02 --- /dev/null +++ b/zh-hant/docs/chapter_tree/binary_tree_traversal.md @@ -0,0 +1,89 @@ +# 二元樹走訪 + +從物理結構的角度來看,樹是一種基於鏈結串列的資料結構,因此其走訪方式是透過指標逐個訪問節點。然而,樹是一種非線性資料結構,這使得走訪樹比走訪鏈結串列更加複雜,需要藉助搜尋演算法來實現。 + +二元樹常見的走訪方式包括層序走訪、前序走訪、中序走訪和後序走訪等。 + +## 層序走訪 + +如下圖所示,層序走訪(level-order traversal)從頂部到底部逐層走訪二元樹,並在每一層按照從左到右的順序訪問節點。 + +層序走訪本質上屬於廣度優先走訪(breadth-first traversal),也稱廣度優先搜尋(breadth-first search, BFS),它體現了一種“一圈一圈向外擴展”的逐層走訪方式。 + +![二元樹的層序走訪](binary_tree_traversal.assets/binary_tree_bfs.png) + +### 程式碼實現 + +廣度優先走訪通常藉助“佇列”來實現。佇列遵循“先進先出”的規則,而廣度優先走訪則遵循“逐層推進”的規則,兩者背後的思想是一致的。實現程式碼如下: + +```src +[file]{binary_tree_bfs}-[class]{}-[func]{level_order} +``` + +### 複雜度分析 + +- **時間複雜度為 $O(n)$** :所有節點被訪問一次,使用 $O(n)$ 時間,其中 $n$ 為節點數量。 +- **空間複雜度為 $O(n)$** :在最差情況下,即滿二元樹時,走訪到最底層之前,佇列中最多同時存在 $(n + 1) / 2$ 個節點,佔用 $O(n)$ 空間。 + +## 前序、中序、後序走訪 + +相應地,前序、中序和後序走訪都屬於深度優先走訪(depth-first traversal),也稱深度優先搜尋(depth-first search, DFS),它體現了一種“先走到盡頭,再回溯繼續”的走訪方式。 + +下圖展示了對二元樹進行深度優先走訪的工作原理。**深度優先走訪就像是繞著整棵二元樹的外圍“走”一圈**,在每個節點都會遇到三個位置,分別對應前序走訪、中序走訪和後序走訪。 + +![二元搜尋樹的前序、中序、後序走訪](binary_tree_traversal.assets/binary_tree_dfs.png) + +### 程式碼實現 + +深度優先搜尋通常基於遞迴實現: + +```src +[file]{binary_tree_dfs}-[class]{}-[func]{post_order} +``` + +!!! tip + + 深度優先搜尋也可以基於迭代實現,有興趣的讀者可以自行研究。 + +下圖展示了前序走訪二元樹的遞迴過程,其可分為“遞”和“迴”兩個逆向的部分。 + +1. “遞”表示開啟新方法,程式在此過程中訪問下一個節點。 +2. “迴”表示函式返回,代表當前節點已經訪問完畢。 + +=== "<1>" + ![前序走訪的遞迴過程](binary_tree_traversal.assets/preorder_step1.png) + +=== "<2>" + ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) + +=== "<3>" + ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) + +=== "<4>" + ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) + +=== "<5>" + ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) + +=== "<6>" + ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) + +=== "<7>" + ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) + +=== "<8>" + ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) + +=== "<9>" + ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) + +=== "<10>" + ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) + +=== "<11>" + ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) + +### 複雜度分析 + +- **時間複雜度為 $O(n)$** :所有節點被訪問一次,使用 $O(n)$ 時間。 +- **空間複雜度為 $O(n)$** :在最差情況下,即樹退化為鏈結串列時,遞迴深度達到 $n$ ,系統佔用 $O(n)$ 堆疊幀空間。 diff --git a/zh-hant/docs/chapter_tree/index.md b/zh-hant/docs/chapter_tree/index.md new file mode 100644 index 0000000000..a4c096227d --- /dev/null +++ b/zh-hant/docs/chapter_tree/index.md @@ -0,0 +1,9 @@ +# 樹 + +![樹](../assets/covers/chapter_tree.jpg) + +!!! abstract + + 參天大樹充滿生命力,根深葉茂,分枝扶疏。 + + 它為我們展現了資料分治的生動形態。 diff --git a/zh-hant/docs/chapter_tree/summary.md b/zh-hant/docs/chapter_tree/summary.md new file mode 100644 index 0000000000..d2304bd136 --- /dev/null +++ b/zh-hant/docs/chapter_tree/summary.md @@ -0,0 +1,54 @@ +# 小結 + +### 重點回顧 + +- 二元樹是一種非線性資料結構,體現“一分為二”的分治邏輯。每個二元樹節點包含一個值以及兩個指標,分別指向其左子節點和右子節點。 +- 對於二元樹中的某個節點,其左(右)子節點及其以下形成的樹被稱為該節點的左(右)子樹。 +- 二元樹的相關術語包括根節點、葉節點、層、度、邊、高度和深度等。 +- 二元樹的初始化、節點插入和節點刪除操作與鏈結串列操作方法類似。 +- 常見的二元樹型別有完美二元樹、完全二元樹、完滿二元樹和平衡二元樹。完美二元樹是最理想的狀態,而鏈結串列是退化後的最差狀態。 +- 二元樹可以用陣列表示,方法是將節點值和空位按層序走訪順序排列,並根據父節點與子節點之間的索引對映關係來實現指標。 +- 二元樹的層序走訪是一種廣度優先搜尋方法,它體現了“一圈一圈向外擴展”的逐層走訪方式,通常透過佇列來實現。 +- 前序、中序、後序走訪皆屬於深度優先搜尋,它們體現了“先走到盡頭,再回溯繼續”的走訪方式,通常使用遞迴來實現。 +- 二元搜尋樹是一種高效的元素查詢資料結構,其查詢、插入和刪除操作的時間複雜度均為 $O(\log n)$ 。當二元搜尋樹退化為鏈結串列時,各項時間複雜度會劣化至 $O(n)$ 。 +- AVL 樹,也稱平衡二元搜尋樹,它透過旋轉操作確保在不斷插入和刪除節點後樹仍然保持平衡。 +- AVL 樹的旋轉操作包括右旋、左旋、先右旋再左旋、先左旋再右旋。在插入或刪除節點後,AVL 樹會從底向頂執行旋轉操作,使樹重新恢復平衡。 + +### Q & A + +**Q**:對於只有一個節點的二元樹,樹的高度和根節點的深度都是 $0$ 嗎? + +是的,因為高度和深度通常定義為“經過的邊的數量”。 + +**Q**:二元樹中的插入與刪除一般由一套操作配合完成,這裡的“一套操作”指什麼呢?可以理解為資源的子節點的資源釋放嗎? + +拿二元搜尋樹來舉例,刪除節點操作要分三種情況處理,其中每種情況都需要進行多個步驟的節點操作。 + +**Q**:為什麼 DFS 走訪二元樹有前、中、後三種順序,分別有什麼用呢? + +與順序和逆序走訪陣列類似,前序、中序、後序走訪是三種二元樹走訪方法,我們可以使用它們得到一個特定順序的走訪結果。例如在二元搜尋樹中,由於節點大小滿足 `左子節點值 < 根節點值 < 右子節點值` ,因此我們只要按照“左 $\rightarrow$ 根 $\rightarrow$ 右”的優先順序走訪樹,就可以獲得有序的節點序列。 + +**Q**:右旋操作是處理失衡節點 `node`、`child`、`grand_child` 之間的關係,那 `node` 的父節點和 `node` 原來的連線不需要維護嗎?右旋操作後豈不是斷掉了? + +我們需要從遞迴的視角來看這個問題。右旋操作 `right_rotate(root)` 傳入的是子樹的根節點,最終 `return child` 返回旋轉之後的子樹的根節點。子樹的根節點和其父節點的連線是在該函式返回後完成的,不屬於右旋操作的維護範圍。 + +**Q**:在 C++ 中,函式被劃分到 `private` 和 `public` 中,這方面有什麼考量嗎?為什麼要將 `height()` 函式和 `updateHeight()` 函式分別放在 `public` 和 `private` 中呢? + +主要看方法的使用範圍,如果方法只在類別內部使用,那麼就設計為 `private` 。例如,使用者單獨呼叫 `updateHeight()` 是沒有意義的,它只是插入、刪除操作中的一步。而 `height()` 是訪問節點高度,類似於 `vector.size()` ,因此設定成 `public` 以便使用。 + +**Q**:如何從一組輸入資料構建一棵二元搜尋樹?根節點的選擇是不是很重要? + +是的,構建樹的方法已在二元搜尋樹程式碼中的 `build_tree()` 方法中給出。至於根節點的選擇,我們通常會將輸入資料排序,然後將中點元素作為根節點,再遞迴地構建左右子樹。這樣做可以最大程度保證樹的平衡性。 + +**Q**:在 Java 中,字串對比是否一定要用 `equals()` 方法? + +在 Java 中,對於基本資料型別,`==` 用於對比兩個變數的值是否相等。對於引用型別,兩種符號的工作原理是不同的。 + +- `==` :用來比較兩個變數是否指向同一個物件,即它們在記憶體中的位置是否相同。 +- `equals()`:用來對比兩個物件的值是否相等。 + +因此,如果要對比值,我們應該使用 `equals()` 。然而,透過 `String a = "hi"; String b = "hi";` 初始化的字串都儲存在字串常數池中,它們指向同一個物件,因此也可以用 `a == b` 來比較兩個字串的內容。 + +**Q**:廣度優先走訪到最底層之前,佇列中的節點數量是 $2^h$ 嗎? + +是的,例如高度 $h = 2$ 的滿二元樹,其節點總數 $n = 7$ ,則底層節點數量 $4 = 2^h = (n + 1) / 2$ 。 diff --git a/zh-hant/docs/index.assets/animation.gif b/zh-hant/docs/index.assets/animation.gif new file mode 100644 index 0000000000..641e677efb Binary files /dev/null and b/zh-hant/docs/index.assets/animation.gif differ diff --git a/zh-hant/docs/index.assets/animation_dark.gif b/zh-hant/docs/index.assets/animation_dark.gif new file mode 100644 index 0000000000..a05aedd70e Binary files /dev/null and b/zh-hant/docs/index.assets/animation_dark.gif differ diff --git a/zh-hant/docs/index.assets/btn_download_pdf.svg b/zh-hant/docs/index.assets/btn_download_pdf.svg new file mode 100644 index 0000000000..2d52ece69c --- /dev/null +++ b/zh-hant/docs/index.assets/btn_download_pdf.svg @@ -0,0 +1 @@ +下載PDF \ No newline at end of file diff --git a/zh-hant/docs/index.assets/btn_download_pdf_dark.svg b/zh-hant/docs/index.assets/btn_download_pdf_dark.svg new file mode 100644 index 0000000000..71ae42ec0e --- /dev/null +++ b/zh-hant/docs/index.assets/btn_download_pdf_dark.svg @@ -0,0 +1 @@ +下載PDF \ No newline at end of file diff --git a/zh-hant/docs/index.assets/btn_read_online.svg b/zh-hant/docs/index.assets/btn_read_online.svg new file mode 100644 index 0000000000..7ff5c3f263 --- /dev/null +++ b/zh-hant/docs/index.assets/btn_read_online.svg @@ -0,0 +1 @@ +線上閱讀 \ No newline at end of file diff --git a/zh-hant/docs/index.assets/btn_read_online_dark.svg b/zh-hant/docs/index.assets/btn_read_online_dark.svg new file mode 100644 index 0000000000..4bc3134b52 --- /dev/null +++ b/zh-hant/docs/index.assets/btn_read_online_dark.svg @@ -0,0 +1 @@ +線上閱讀 \ No newline at end of file diff --git a/zh-hant/docs/index.assets/comment.gif b/zh-hant/docs/index.assets/comment.gif new file mode 100644 index 0000000000..a6c5b84446 Binary files /dev/null and b/zh-hant/docs/index.assets/comment.gif differ diff --git a/zh-hant/docs/index.assets/hello_algo_header.png b/zh-hant/docs/index.assets/hello_algo_header.png new file mode 100644 index 0000000000..51e21c54e4 Binary files /dev/null and b/zh-hant/docs/index.assets/hello_algo_header.png differ diff --git a/zh-hant/docs/index.assets/hello_algo_mindmap_tp.png b/zh-hant/docs/index.assets/hello_algo_mindmap_tp.png new file mode 100644 index 0000000000..9ba5217173 Binary files /dev/null and b/zh-hant/docs/index.assets/hello_algo_mindmap_tp.png differ diff --git a/zh-hant/docs/index.assets/running_code.gif b/zh-hant/docs/index.assets/running_code.gif new file mode 100644 index 0000000000..5c77187f7b Binary files /dev/null and b/zh-hant/docs/index.assets/running_code.gif differ diff --git a/zh-hant/docs/index.assets/running_code_dark.gif b/zh-hant/docs/index.assets/running_code_dark.gif new file mode 100644 index 0000000000..5d56a0184c Binary files /dev/null and b/zh-hant/docs/index.assets/running_code_dark.gif differ diff --git a/zh-hant/docs/index.html b/zh-hant/docs/index.html new file mode 100644 index 0000000000..4628e70706 --- /dev/null +++ b/zh-hant/docs/index.html @@ -0,0 +1,375 @@ + +
+ + + + + +
+ +
+

+ 動畫圖解、一鍵執行的資料結構與演算法教程 +

+ + + + + 開始閱讀 + + + + + + + 程式碼倉庫 + +
+ +
+ + + +
+
+
+ + +
+
+ Preview +
+ + + + + + + + + + + + + + +
+

500 幅動畫圖解、14 種程式語言程式碼、3000 條社群問答,助你快速入門資料結構與演算法

+
+
+ + +
+ +
+ + +
+
+

推薦語

+
+
+

“一本通俗易懂的資料結構與演算法入門書,引導讀者手腦並用地學習,強烈推薦演算法初學者閱讀。”

+

—— 鄧俊輝,清華大學計算機系教授

+
+
+

“如果我當年學資料結構與演算法的時候有《Hello 演算法》,學起來應該會簡單 10 倍!”

+

—— 李沐,亞馬遜資深首席科學家

+
+
+
+
+ + +
+
+
+
+
+
+ + + +

動畫圖解

+
+

內容清晰易懂,學習曲線平滑

+

"A picture is worth a thousand words."
“一圖勝千言”

+
+
+ Animation example +
+ +
+ Running code example +
+
+
+ + + +

一鍵執行

+
+

十餘種程式語言,程式碼視覺化執行

+

"Talk is cheap. Show me the code."
“少吹牛,看程式碼”

+
+
+
+ +
+
+
+
+ + + +

互助學習

+
+

歡迎討論與提問,讀者間攜手共進

+

"Learning by teaching."
“教學相長”

+
+
+ Comments example +
+ +
+
+ + +
+
+ +
+

作者

+ +
+ + + + + +
+

繁體版審閱者

+ +
+ + +
+

貢獻者

+

本書在開源社群一百多位貢獻者的共同努力下不斷完善,感謝他們付出的時間與精力!

+ + Contributors + +
+
+
\ No newline at end of file diff --git a/zh-hant/docs/index.md b/zh-hant/docs/index.md new file mode 100644 index 0000000000..086cf787fd --- /dev/null +++ b/zh-hant/docs/index.md @@ -0,0 +1,5 @@ +# Hello 演算法 + +動畫圖解、一鍵執行的資料結構與演算法教程。 + +[開始閱讀](chapter_hello_algo/) diff --git a/zh-hant/mkdocs.yml b/zh-hant/mkdocs.yml new file mode 100644 index 0000000000..87e5850d1d --- /dev/null +++ b/zh-hant/mkdocs.yml @@ -0,0 +1,178 @@ +# Config inheritance +INHERIT: ../mkdocs.yml + +# Project information +site_name: Hello 演算法 +site_url: https://www.hello-algo.com/zh-hant/ +site_description: "動畫圖解、一鍵執行的資料結構與演算法教程" +docs_dir: ../build/zh-hant/docs +site_dir: ../site/zh-hant +# Repository +edit_uri: tree/main/zh-hant/docs +version: 1.2.0 + +# Configuration +theme: + custom_dir: ../build/overrides + language: zh-Hant + palette: + - scheme: default + primary: white + accent: teal + toggle: + icon: material/theme-light-dark + name: 深色模式 + - scheme: slate + primary: black + accent: teal + toggle: + icon: material/theme-light-dark + name: 淺色模式 + +extra: + status: + new: 最近新增 + +# Page tree +nav: + - 序: + - chapter_hello_algo/index.md + - 第 0 章   前言: + # [icon: material/book-open-outline] + - chapter_preface/index.md + - 0.1   關於本書: chapter_preface/about_the_book.md + - 0.2   如何使用本書: chapter_preface/suggestions.md + - 0.3   小結: chapter_preface/summary.md + - 第 1 章   初識演算法: + # [icon: material/calculator-variant-outline] + - chapter_introduction/index.md + - 1.1   演算法無處不在: chapter_introduction/algorithms_are_everywhere.md + - 1.2   演算法是什麼: chapter_introduction/what_is_dsa.md + - 1.3   小結: chapter_introduction/summary.md + - 第 2 章   複雜度分析: + # [icon: material/timer-sand] + - chapter_computational_complexity/index.md + - 2.1   演算法效率評估: chapter_computational_complexity/performance_evaluation.md + - 2.2   迭代與遞迴: chapter_computational_complexity/iteration_and_recursion.md + - 2.3   時間複雜度: chapter_computational_complexity/time_complexity.md + - 2.4   空間複雜度: chapter_computational_complexity/space_complexity.md + - 2.5   小結: chapter_computational_complexity/summary.md + - 第 3 章   資料結構: + # [icon: material/shape-outline] + - chapter_data_structure/index.md + - 3.1   資料結構分類: chapter_data_structure/classification_of_data_structure.md + - 3.2   基本資料型別: chapter_data_structure/basic_data_types.md + - 3.3   數字編碼 *: chapter_data_structure/number_encoding.md + - 3.4   字元編碼 *: chapter_data_structure/character_encoding.md + - 3.5   小結: chapter_data_structure/summary.md + - 第 4 章   陣列與鏈結串列: + # [icon: material/view-list-outline] + - chapter_array_and_linkedlist/index.md + - 4.1   陣列: chapter_array_and_linkedlist/array.md + - 4.2   鏈結串列: chapter_array_and_linkedlist/linked_list.md + - 4.3   串列: chapter_array_and_linkedlist/list.md + # [status: new] + - 4.4   記憶體與快取 *: chapter_array_and_linkedlist/ram_and_cache.md + - 4.5   小結: chapter_array_and_linkedlist/summary.md + - 第 5 章   堆疊與佇列: + # [icon: material/stack-overflow] + - chapter_stack_and_queue/index.md + - 5.1   堆疊: chapter_stack_and_queue/stack.md + - 5.2   佇列: chapter_stack_and_queue/queue.md + - 5.3   雙向佇列: chapter_stack_and_queue/deque.md + - 5.4   小結: chapter_stack_and_queue/summary.md + - 第 6 章   雜湊表: + # [icon: material/table-search] + - chapter_hashing/index.md + - 6.1   雜湊表: chapter_hashing/hash_map.md + - 6.2   雜湊衝突: chapter_hashing/hash_collision.md + - 6.3   雜湊演算法: chapter_hashing/hash_algorithm.md + - 6.4   小結: chapter_hashing/summary.md + - 第 7 章   樹: + # [icon: material/graph-outline] + - chapter_tree/index.md + - 7.1   二元樹: chapter_tree/binary_tree.md + - 7.2   二元樹走訪: chapter_tree/binary_tree_traversal.md + - 7.3   二元樹陣列表示: chapter_tree/array_representation_of_tree.md + - 7.4   二元搜尋樹: chapter_tree/binary_search_tree.md + - 7.5   AVL *: chapter_tree/avl_tree.md + - 7.6   小結: chapter_tree/summary.md + - 第 8 章   堆積: + # [icon: material/family-tree] + - chapter_heap/index.md + - 8.1   堆積: chapter_heap/heap.md + - 8.2   建堆積操作: chapter_heap/build_heap.md + - 8.3   Top-k 問題: chapter_heap/top_k.md + - 8.4   小結: chapter_heap/summary.md + - 第 9 章   圖: + # [icon: material/graphql] + - chapter_graph/index.md + - 9.1   圖: chapter_graph/graph.md + - 9.2   圖基礎操作: chapter_graph/graph_operations.md + - 9.3   圖的走訪: chapter_graph/graph_traversal.md + - 9.4   小結: chapter_graph/summary.md + - 第 10 章   搜尋: + # [icon: material/text-search] + - chapter_searching/index.md + - 10.1   二分搜尋: chapter_searching/binary_search.md + - 10.2   二分搜尋插入點: chapter_searching/binary_search_insertion.md + - 10.3   二分搜尋邊界: chapter_searching/binary_search_edge.md + - 10.4   雜湊最佳化策略: chapter_searching/replace_linear_by_hashing.md + - 10.5   重識搜尋演算法: chapter_searching/searching_algorithm_revisited.md + - 10.6   小結: chapter_searching/summary.md + - 第 11 章   排序: + # [icon: material/sort-ascending] + - chapter_sorting/index.md + - 11.1   排序演算法: chapter_sorting/sorting_algorithm.md + - 11.2   選擇排序: chapter_sorting/selection_sort.md + - 11.3   泡沫排序: chapter_sorting/bubble_sort.md + - 11.4   插入排序: chapter_sorting/insertion_sort.md + - 11.5   快速排序: chapter_sorting/quick_sort.md + - 11.6   合併排序: chapter_sorting/merge_sort.md + - 11.7   堆積排序: chapter_sorting/heap_sort.md + - 11.8   桶排序: chapter_sorting/bucket_sort.md + - 11.9   計數排序: chapter_sorting/counting_sort.md + - 11.10   基數排序: chapter_sorting/radix_sort.md + - 11.11   小結: chapter_sorting/summary.md + - 第 12 章   分治: + # [icon: material/set-split] + - chapter_divide_and_conquer/index.md + - 12.1   分治演算法: chapter_divide_and_conquer/divide_and_conquer.md + - 12.2   分治搜尋策略: chapter_divide_and_conquer/binary_search_recur.md + - 12.3   構建樹問題: chapter_divide_and_conquer/build_binary_tree_problem.md + - 12.4   河內塔問題: chapter_divide_and_conquer/hanota_problem.md + - 12.5   小結: chapter_divide_and_conquer/summary.md + - 第 13 章   回溯: + # [icon: material/map-marker-path] + - chapter_backtracking/index.md + - 13.1   回溯演算法: chapter_backtracking/backtracking_algorithm.md + - 13.2   全排列問題: chapter_backtracking/permutations_problem.md + - 13.3   子集和問題: chapter_backtracking/subset_sum_problem.md + - 13.4   N 皇后問題: chapter_backtracking/n_queens_problem.md + - 13.5   小結: chapter_backtracking/summary.md + - 第 14 章   動態規劃: + # [icon: material/table-pivot] + - chapter_dynamic_programming/index.md + - 14.1   初探動態規劃: chapter_dynamic_programming/intro_to_dynamic_programming.md + - 14.2   DP 問題特性: chapter_dynamic_programming/dp_problem_features.md + - 14.3   DP 解題思路: chapter_dynamic_programming/dp_solution_pipeline.md + - 14.4   0-1 背包問題: chapter_dynamic_programming/knapsack_problem.md + - 14.5   完全背包問題: chapter_dynamic_programming/unbounded_knapsack_problem.md + - 14.6   編輯距離問題: chapter_dynamic_programming/edit_distance_problem.md + - 14.7   小結: chapter_dynamic_programming/summary.md + - 第 15 章   貪婪: + # [icon: material/head-heart-outline] + - chapter_greedy/index.md + - 15.1   貪婪演算法: chapter_greedy/greedy_algorithm.md + - 15.2   分數背包問題: chapter_greedy/fractional_knapsack_problem.md + - 15.3   最大容量問題: chapter_greedy/max_capacity_problem.md + - 15.4   最大切分乘積問題: chapter_greedy/max_product_cutting_problem.md + - 15.5   小結: chapter_greedy/summary.md + - 第 16 章   附錄: + # [icon: material/help-circle-outline] + - chapter_appendix/index.md + - 16.1   程式設計環境安裝: chapter_appendix/installation.md + - 16.2   一起參與創作: chapter_appendix/contribution.md + - 16.3   術語表: chapter_appendix/terminology.md + - 參考文獻: + - chapter_reference/index.md