├── .gitignore ├── LICENSE ├── README.md ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── ch01 │ ├── ch01.md │ └── images │ │ ├── 01-01.png │ │ ├── 01-02.png │ │ ├── 01-03.png │ │ ├── 01-04.png │ │ ├── 01-05.png │ │ ├── 01-06.png │ │ ├── 01-07.png │ │ ├── 01-08.png │ │ ├── 01-09.png │ │ ├── 01-10.png │ │ ├── 01-11.png │ │ ├── 01-12.png │ │ ├── 01-13.png │ │ └── 01-14.png ├── ch02 │ ├── ch02.md │ └── images │ │ ├── 02-01.png │ │ ├── 02-02.png │ │ └── 02-03.png ├── ch03 │ ├── ch03.md │ └── images │ │ ├── 03-01.png │ │ ├── 03-02.png │ │ ├── 03-03.png │ │ ├── 03-04.png │ │ ├── 03-05.png │ │ ├── 03-06.png │ │ ├── 03-07.png │ │ ├── 03-08.png │ │ ├── 03-09.png │ │ ├── 03-10.png │ │ ├── 03-11.png │ │ ├── 03-12.png │ │ ├── 03-13.png │ │ ├── 03-14.png │ │ ├── 03-15.png │ │ └── 03-16.png ├── ch04 │ ├── ch04.md │ └── images │ │ ├── 04-01.png │ │ ├── 04-02.png │ │ ├── 04-03.png │ │ ├── 04-04.png │ │ ├── 04-05.png │ │ ├── 04-06.png │ │ ├── 04-07.png │ │ ├── 04-08.png │ │ ├── 04-09.png │ │ ├── 04-10.png │ │ ├── 04-11.png │ │ ├── 04-12.png │ │ ├── 04-13.png │ │ ├── 04-14.png │ │ └── 04-15.png ├── ch05 │ ├── ch05.md │ └── images │ │ ├── 05-01.png │ │ ├── 05-02.png │ │ ├── 05-03.png │ │ ├── 05-04.png │ │ ├── 05-05.png │ │ ├── 05-06.png │ │ ├── 05-07.png │ │ └── 05-08.png ├── ch06 │ ├── ch06.md │ └── images │ │ ├── 06-01.png │ │ ├── 06-02.png │ │ ├── 06-03.png │ │ ├── 06-04.png │ │ ├── 06-05.png │ │ ├── 06-06.png │ │ ├── 06-07.png │ │ ├── 06-08.png │ │ ├── 06-09.png │ │ ├── 06-10.png │ │ ├── 06-11.png │ │ ├── 06-12.png │ │ ├── 06-13.png │ │ ├── 06-14.png │ │ ├── 06-15.png │ │ ├── 06-16.png │ │ ├── 06-17.png │ │ ├── 06-18.png │ │ ├── 06-19.png │ │ ├── 06-20.png │ │ ├── 06-21.png │ │ ├── 06-22.png │ │ ├── 06-23.png │ │ ├── 06-24.png │ │ ├── 06-25.png │ │ ├── 06-26.png │ │ ├── 06-27.png │ │ ├── 06-28.png │ │ ├── 06-29.png │ │ ├── 06-30.png │ │ ├── 06-31.png │ │ ├── 06-32.png │ │ ├── 06-33.png │ │ └── 06-34.png ├── ch07 │ ├── ch07.md │ └── images │ │ ├── 07-01.png │ │ ├── 07-02.png │ │ ├── 07-03.png │ │ ├── 07-04.png │ │ ├── 07-05.png │ │ ├── 07-06.png │ │ ├── 07-07.png │ │ ├── 07-08.png │ │ ├── 07-09.png │ │ ├── 07-10.png │ │ ├── 07-11.png │ │ ├── 07-12.png │ │ ├── 07-13.png │ │ ├── 07-14.png │ │ ├── 07-15.png │ │ ├── 07-16.png │ │ ├── 07-17.png │ │ ├── 07-18.png │ │ └── 07-19.png ├── ch08 │ ├── ch08.md │ └── images │ │ ├── 08-01.png │ │ ├── 08-02.png │ │ ├── 08-03.png │ │ ├── 08-04.png │ │ ├── 08-05.png │ │ ├── 08-06.png │ │ ├── 08-07.png │ │ ├── 08-08.png │ │ ├── 08-09.png │ │ ├── 08-10.png │ │ └── 08-11.png ├── ch09 │ ├── ch09.md │ └── images │ │ └── 09-01.png ├── index.html └── resources │ └── qrcode.jpeg └── resources └── qrcode.jpeg /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | 141 | ### macOS template 142 | # General 143 | .DS_Store 144 | .AppleDouble 145 | .LSOverride 146 | 147 | # Icon must end with two \r 148 | Icon 149 | 150 | # Thumbnails 151 | ._* 152 | 153 | # Files that might appear in the root of a volume 154 | .DocumentRevisions-V100 155 | .fseventsd 156 | .Spotlight-V100 157 | .TemporaryItems 158 | .Trashes 159 | .VolumeIcon.icns 160 | .com.apple.timemachine.donotpresent 161 | 162 | # Directories potentially created on remote AFP share 163 | .AppleDB 164 | .AppleDesktop 165 | Network Trash Folder 166 | Temporary Items 167 | .apdisk 168 | 169 | ### JetBrains template 170 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 171 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 172 | 173 | # User-specific stuff 174 | .idea/**/workspace.xml 175 | .idea/**/tasks.xml 176 | .idea/**/usage.statistics.xml 177 | .idea/**/dictionaries 178 | .idea/**/shelf 179 | 180 | # Generated files 181 | .idea/**/contentModel.xml 182 | 183 | # Sensitive or high-churn files 184 | .idea/**/dataSources/ 185 | .idea/**/dataSources.ids 186 | .idea/**/dataSources.local.xml 187 | .idea/**/sqlDataSources.xml 188 | .idea/**/dynamic.xml 189 | .idea/**/uiDesigner.xml 190 | .idea/**/dbnavigator.xml 191 | 192 | # Gradle 193 | .idea/**/gradle.xml 194 | .idea/**/libraries 195 | 196 | # Gradle and Maven with auto-import 197 | # When using Gradle or Maven with auto-import, you should exclude module files, 198 | # since they will be recreated, and may cause churn. Uncomment if using 199 | # auto-import. 200 | # .idea/artifacts 201 | # .idea/compiler.xml 202 | # .idea/jarRepositories.xml 203 | # .idea/modules.xml 204 | # .idea/*.iml 205 | # .idea/modules 206 | # *.iml 207 | # *.ipr 208 | 209 | # CMake 210 | cmake-build-*/ 211 | 212 | # Mongo Explorer plugin 213 | .idea/**/mongoSettings.xml 214 | 215 | # File-based project format 216 | *.iws 217 | 218 | # IntelliJ 219 | out/ 220 | 221 | # mpeltonen/sbt-idea plugin 222 | .idea_modules/ 223 | 224 | # JIRA plugin 225 | atlassian-ide-plugin.xml 226 | 227 | # Cursive Clojure plugin 228 | .idea/replstate.xml 229 | 230 | # Crashlytics plugin (for Android Studio and IntelliJ) 231 | com_crashlytics_export_strings.xml 232 | crashlytics.properties 233 | crashlytics-build.properties 234 | fabric.properties 235 | 236 | # Editor-based Rest Client 237 | .idea/httpRequests 238 | 239 | # Android studio 3.1+ serialized cache file 240 | .idea/caches/build_file_checksums.ser 241 | 242 | ### VisualStudioCode template 243 | .vscode/* 244 | !.vscode/settings.json 245 | !.vscode/tasks.json 246 | !.vscode/launch.json 247 | !.vscode/extensions.json 248 | *.code-workspace 249 | 250 | # Local History for Visual Studio Code 251 | .history/ 252 | 253 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Joker-lkc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sweetalk-data-structure 2 | 3 |   本项目主要基于《大话数据结构》这本书,对该书涉及的线性表、栈与队列、串、树、图、查找与排序进行解读,原书采用C90语言编写,本项目使用C++11、Python3和Java进行代码重写,供大家参考不同语言对数据结构的编写。 4 | 5 |   《大话数据结构》这本书,以一个计算机教师教学为场景,讲解数据结构和相关算法的知识。通篇以一种趣味方式来叙述,大量引用了各种各样的生活知识来类比,并充分运用图形语言来体现抽象内容,对数据结构所涉及到的一些经典算法做到逐行分析、多算法比较。与市场上的同类数据结构图书相比,本书内容趣味易读,算法讲解细致深刻,是一本非常适合自学的读物。 6 | 7 | ## 使用说明 8 | 9 |   结合《大话数据结构》这本书,总结了数据结构和算法的知识并配套了相关习题和解答,习题采用C++11、Python3和Java的代码。 10 | 11 |   如果觉得本项目中有错误,可以[点击这里](https://github.com/datawhalechina/sweetalk-data-structure/issues)提交你希望补充的内容,我们看到后会尽快进行补充。 12 | 13 | ### 在线阅读地址 14 | https://datawhalechina.github.io/sweetalk-data-structure 15 | 16 | ## 基本信息 17 | 18 | * 学习周期:9 天,每天平均花费时间 1 小时-3 小时不等,根据个人学习接受能力强弱有所浮动。 19 | * 学习形式:理论学习 + 练习 20 | * 人群定位:有一定编程基础,对学习数据结构和算法有需求的学员。 21 | * 先修内容:Python、C、Java 等编程语言 22 | * 难度系数:中 23 | 24 | ## 学习目标 25 | 26 | 结合《大话数据结构》,通过学习 9 天的打卡学习,掌握线性表、栈与队列、串等知识点,能够独立实现数据结构,并完成相关习题。 27 | 28 | 29 | ## 任务安排 30 | 31 | ### Task01:学习线性表完成以下三个题目并打卡(3 天) 32 | 33 | * [询问学号-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch03/ch03?id=_351-p3156-%e3%80%90%e6%b7%b1%e5%9f%ba15%e4%be%8b1%e3%80%91%e8%af%a2%e9%97%ae%e5%ad%a6%e5%8f%b7-%e7%ae%80%e5%8d%95) 34 | * [寄包柜-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch03/ch03?id=_352-p3613-%e3%80%90%e6%b7%b1%e5%9f%ba15%e4%be%8b2%e3%80%91%e5%af%84%e5%8c%85%e6%9f%9c-%e7%ae%80%e5%8d%95) 35 | * [校门外的树-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch03/ch03?id=_353-p1047-noip2005-%e6%99%ae%e5%8f%8a%e7%bb%84-%e6%a0%a1%e9%97%a8%e5%a4%96%e7%9a%84%e6%a0%91-%e7%ae%80%e5%8d%95) 36 | 37 | ### Task02:学习栈与队列完成以下三个题目并打卡(3 天) 38 | 39 | * [栈-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch04/ch04?id=%e4%b9%a0%e9%a2%981-%e6%a0%88%ef%bc%88%e6%b4%9b%e8%b0%b7noip2003%e6%99%ae%e5%8f%8a%e7%bb%84-%e7%ae%80%e5%8d%95%ef%bc%89) 40 | * [后缀表达式-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch04/ch04?id=%e4%b9%a0%e9%a2%982-%e5%90%8e%e7%bc%80%e8%a1%a8%e8%be%be%e5%bc%8f%ef%bc%88%e6%b4%9b%e8%b0%b7p149-%e7%ae%80%e5%8d%95%ef%bc%89) 41 | * [滑动窗口的最大值-中等](https://datawhalechina.github.io/sweetalk-data-structure/#/ch04/ch04?id=%e4%b9%a0%e9%a2%983-%e6%bb%91%e5%8a%a8%e7%aa%97%e5%8f%a3%e7%9a%84%e6%9c%80%e5%a4%a7%e5%80%bc%ef%bc%88%e7%89%9b%e5%ae%a2-%e4%b8%ad%e7%ad%89%ef%bc%89) 42 | 43 | ### Task03:学习串完成以下三个题目并打卡(3 天) 44 | 45 | * [字符串语句逆序-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch05/ch05?id=%e4%b9%a0%e9%a2%981-%e5%ad%97%e7%ac%a6%e4%b8%b2%e8%af%ad%e5%8f%a5%e9%80%86%e5%ba%8f%ef%bc%88%e7%ae%80%e5%8d%95%ef%bc%89) 46 | * [字符出现情况-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch05/ch05?id=%e4%b9%a0%e9%a2%982-%e5%ad%97%e7%ac%a6%e5%87%ba%e7%8e%b0%e6%83%85%e5%86%b5%ef%bc%88%e7%ae%80%e5%8d%95%ef%bc%89) 47 | * [第一个出现两次的字符-简单](https://datawhalechina.github.io/sweetalk-data-structure/#/ch05/ch05?id=%e4%b9%a0%e9%a2%983-%e7%ac%ac%e4%b8%80%e4%b8%aa%e5%87%ba%e7%8e%b0%e4%b8%a4%e6%ac%a1%e7%9a%84%e5%ad%97%e7%ac%a6%ef%bc%88%e7%ae%80%e5%8d%95%ef%bc%89) 48 | 49 | ### 内容导航 50 | 51 | | 章节 | 习题 | 52 | | :----- | :------------ | 53 | | [第一章 数据结构概论](https://datawhalechina.github.io/sweetalk-data-structure/#/ch01/ch01) | 第一章 习题 | 54 | | [第二章 算法](https://datawhalechina.github.io/sweetalk-data-structure/#/ch02/ch02) | 第二章 习题 | 55 | | [第三章 线性表](https://datawhalechina.github.io/sweetalk-data-structure/#/ch03/ch03) | [第三章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch03/ch03?id=_35-%e4%b9%a0%e9%a2%98) | 56 | | [第四章 栈与队列](https://datawhalechina.github.io/sweetalk-data-structure/#/ch04/ch04) | [第四章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch04/ch04?id=_46-%e4%b9%a0%e9%a2%98) | 57 | | [第五章 串](https://datawhalechina.github.io/sweetalk-data-structure/#/ch05/ch05) | [第五章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch05/ch05?id=_56-%e4%b9%a0%e9%a2%98) | 58 | | [第六章 树](https://datawhalechina.github.io/sweetalk-data-structure/#/ch06/ch06) | [第六章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch06/ch06?id=_69-%e4%b9%a0%e9%a2%98) | 59 | | [第七章 图](https://datawhalechina.github.io/sweetalk-data-structure/#/ch07/ch07) | 第七章 习题 | 60 | | [第八章 查找](https://datawhalechina.github.io/sweetalk-data-structure/#/ch08/ch08) | 第八章 习题 | 61 | | [第九章 排序](https://datawhalechina.github.io/sweetalk-data-structure/#/ch09/ch09) | [第九章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch09/ch09?id=_910-%e4%b9%a0%e9%a2%98) | 62 | 63 | ### 协作规范 64 | 65 | 1. 采用`markdown`的格式进行编写。 66 | 2. 编写完成后,需要进行全部校对审核。 67 | 3. 校对过程中,关于数据结构与算法的思想补充,尽量使用初学者能理解的语言。 68 | 4. 当前进度 69 | 70 | | 章节号 | 标题 | 进度 | 负责人 | 审核人 | 71 | | :------: | :------------: | :------: | :------: | :------: | 72 | | 1 | 数据结构概论 | 已完成 | 李柯辰 | 胡锐锋 | 73 | | 2 | 算法 | 已完成 | 李柯辰 | 胡锐锋 | 74 | | 3 | 线性表 | 已完成 | 李柯辰 | 胡锐锋 | 75 | | 4 | 栈与队列 | 已完成 | 葛云峰 | 胡锐锋 | 76 | | 5 | 串 | 已完成 | 崔腾松 | 胡锐锋 | 77 | | 6 | 树 | 已完成| 安欣锐 | 胡锐锋 | 78 | | 7 | 图 | 已完成 | 李柯辰 | 李柯辰、胡锐锋 | 79 | | 8 | 查找 | 已完成 | 马燕鹏 | 李柯辰、胡锐锋 | 80 | | 9 | 排序 | 已完成 | 王鑫 | 胡锐锋 | 81 | 82 | 83 | ## 致谢 84 | 85 | **核心贡献者** 86 | 87 | * [李柯辰](https://github.com/Joe-2002)(项目负责人) 88 | * 崔腾松 89 | * 王鑫 90 | * 马燕鹏 91 | * 葛云峰 92 | * 安欣锐 93 | 94 | **其他** 95 | 96 | 1. 特别感谢 [胡锐锋](https://github.com/Relph1119)、[Sm1les](https://github.com/Sm1les) 等对[sweetalk-data-structure](https://github.com/datawhalechina/sweetalk-data-structure)的帮助与支持! 97 | 2. 如果有任何想法可以联系我们 DataWhale 也欢迎大家多多提出 issue; 98 | 3. 99 | 100 | 101 | ### 关注我们 102 | 103 |
104 |

扫描下方二维码关注公众号:Datawhale

105 | 106 |
107 |   Datawhale,一个专注于AI领域的学习圈子。初衷是for the learner,和学习者一起成长。目前加入学习社群的人数已经数千人,组织了机器学习,深度学习,数据分析,数据挖掘,爬虫,编程,统计学,Mysql,数据竞赛等多个领域的内容学习,微信搜索公众号Datawhale可以加入我们。 108 | 109 | ## LICENSE 110 | 知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 111 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # sweetalk-data-structure {docsify-ignore-all} 2 | 3 |   本项目主要基于《大话数据结构》这本书,对该书涉及的线性表、栈与队列、串、树、图、查找与排序进行解读,原书采用是C90语言编写,本项目使用C++11、Python3和Java进行代码重写,供大家参考不同语言对数据结构的编写。 4 | 5 |   《大话数据结构》这本书,以一个计算机教师教学为场景,讲解数据结构和相关算法的知识。通篇一种趣味方式来叙述,大量引用了各种各样的生活知识来类比,并充分运用图形语言来体现抽象内容,对数据结构所涉及到的一些经典算法做到逐行分析、多算法比较。与市场上的同类数据结构图书相比,本书内容趣味易读,算法讲解细致深刻,是一本非常适合自学的读物。 6 | 7 | ## 使用说明 8 | 9 |   结合《大话数据结构》这本书,总结了数据结构和算法的知识并配套了相关习题和解答,习题采用C++11、Python3和Java的代码。 10 | 11 |   如果觉得本项目中有错误,可以[点击这里](https://github.com/datawhalechina/sweetalk-data-structure/issues)提交你希望补充的内容,我们看到后会尽快进行补充。 12 | 13 | ### 在线阅读地址 14 | https://datawhalechina.github.io/sweetalk-data-structure 15 | 16 | ### 内容导航 17 | 18 | | 章节 | 习题 | 19 | | :----- | :------------ | 20 | | [第一章 数据结构概论](https://datawhalechina.github.io/sweetalk-data-structure/#/ch01/ch01) | 第一章 习题 | 21 | | [第二章 算法](https://datawhalechina.github.io/sweetalk-data-structure/#/ch02/ch02) | 第二章 习题 | 22 | | [第三章 线性表](https://datawhalechina.github.io/sweetalk-data-structure/#/ch03/ch03) | [第三章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch03/ch03?id=_35-%e4%b9%a0%e9%a2%98) | 23 | | [第四章 栈与队列](https://datawhalechina.github.io/sweetalk-data-structure/#/ch04/ch04) | [第四章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch04/ch04?id=_46-%e4%b9%a0%e9%a2%98) | 24 | | [第五章 串](https://datawhalechina.github.io/sweetalk-data-structure/#/ch05/ch05) | [第五章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch05/ch05?id=_56-%e4%b9%a0%e9%a2%98) | 25 | | [第六章 树](https://datawhalechina.github.io/sweetalk-data-structure/#/ch06/ch06) | [第六章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch06/ch06?id=_69-%e4%b9%a0%e9%a2%98) | 26 | | [第七章 图](https://datawhalechina.github.io/sweetalk-data-structure/#/ch07/ch07) | 第七章 习题 | 27 | | [第八章 查找](https://datawhalechina.github.io/sweetalk-data-structure/#/ch08/ch08) | 第八章 习题 | 28 | | [第九章 排序](https://datawhalechina.github.io/sweetalk-data-structure/#/ch09/ch09) | [第九章 习题](https://datawhalechina.github.io/sweetalk-data-structure/#/ch09/ch09?id=_910-%e4%b9%a0%e9%a2%98) | 29 | 30 | 31 | ## 致谢 32 | 33 | **核心贡献者** 34 | 35 | * 李柯辰 36 | * 崔腾松 37 | * 王鑫 38 | * 马燕鹏 39 | * 葛云峰 40 | * 安欣锐 41 | 42 | **重要贡献者(根据内容+社区贡献程度筛选)** 43 | 44 | * 45 | 46 | **其他** 47 | 48 | 感谢[胡锐锋](https://github.com/Relph1119)等在最早期的时候对[sweetalk-data-structure](https://github.com/datawhalechina/sweetalk-data-structure)所做的贡献! 49 | 50 | ### 关注我们 51 | 52 |
53 |

扫描下方二维码关注公众号:Datawhale

54 | 55 |
56 |   Datawhale,一个专注于AI领域的学习圈子。初衷是for the learner,和学习者一起成长。目前加入学习社群的人数已经数千人,组织了机器学习,深度学习,数据分析,数据挖掘,爬虫,编程,统计学,Mysql,数据竞赛等多个领域的内容学习,微信搜索公众号Datawhale可以加入我们。 57 | 58 | ## LICENSE 59 | 知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 60 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [目录](README.md) 2 | * [第1章 数据结构概论](ch01/ch01.md) 3 | * [第2章 算法](ch02/ch02.md) 4 | * [第3章 线性表](ch03/ch03.md) 5 | * [第4章 栈与队列](ch04/ch04.md) 6 | * [第5章 串](ch05/ch05.md) 7 | * [第6章 树](ch06/ch06.md) 8 | * [第7章 图](ch07/ch07.md) 9 | * [第8章 查找](ch08/ch08.md) 10 | * [第9章 排序](ch09/ch09.md) 11 | -------------------------------------------------------------------------------- /docs/ch01/ch01.md: -------------------------------------------------------------------------------- 1 | # 第一章 数据结构概论 2 | 3 | ## 1.1 数据结构起源 4 | 5 |   **数据结构**是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。早期计算机理解为数值计算工具,所以用计算机解决问题,应该先从具体问题中**抽象出适当的数据模型**,设计解决数据模型的算法,编写程序得到的软件。为了更好的解决问题,需要使用更科学有效的手段(表、树、图等数据结构)。 6 | 7 |   **程序设计 = 数据结构 + 算法** 8 | 9 | ## 1.2 基本概念和术语 10 | 11 |   数据 > 数据对象 > 数据元素(基本单位) > 数据项(最小单位) 12 | 13 | ![image](images/01-01.png) 14 | 15 | - **数据**:描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不仅仅包括**整型、实型(浮点型)等数值类型**,还包括**字符及声音、图像、视频等非数值类型**。 16 | 17 | - **数据元素**:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录。例如在人类中,人是数据元素。在畜禽类中,鸡鸭鹅等是数据元素。数据元素用于描述一个个体。是数据结构中建立数据模型的着眼点。 18 | 19 |   ![image](images/01-02.png) 20 | 21 |   ![image](images/01-03.png) 22 | 23 | - **数据项**:—个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位。描述个体(种类)的具体字段。 24 | 25 |   ![image](images/01-04.png)​ 26 | 27 | - **数据对象**:是性质相同的数据元素的集合,是数据的子集。性质相同是指数据元素具有相同数量和类型的数据项,比如人都有姓名、生日、性别等相同的数据项(字段)。 28 | 29 | - **数据结构**:是相互之间存在一种或多种**特定关系**的数据元素的结合。结构,简单的理解就是关系。不同数据元素之间不是独立的,而是存在特定的关系,我们将这些关系(联系)称为结构。 30 | 31 | ## 1.3 逻辑结构与物理结构 32 | 33 | ### 1.3.1 逻辑结构 34 | 35 |   **逻辑结构**:是指数据对象中数据元素之间的相互关系,包括集合结构、线性结构(一对一)、非线性结构(一对多、多对多)。 36 | 37 | - **集合结构**:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系,数据结构中的集合关系就类似于数学中的集合。 38 | 39 | ![image](images/01-05.png) 40 | 41 | - **线性结构**:线性结构中的数据元素之间是**一对一**的关系。 42 | 43 | ![image](images/01-06.png) 44 | 45 | ![image](images/01-07.png) 46 | 47 | - 非线性结构(一对多、多对多) 48 | - **树形结构**:树形结构中的数据元素之间存在一种一对多的层次关系。 49 | 50 | ![image](images/01-08.png) 51 | 52 |  ![image](images/01-09.png) 53 | 54 | - **图形结构**:图形结构的数据元素是多对多的关系。 55 | 56 |  ![image](images/01-10.png) 57 | 58 |  ![image](images/01-11.png) 59 | 60 |   逻辑结构是针对具体问题的,为了解决某个问题,在对问题的理解基础上,选择一个合适的数据结构表示数据元素之间的逻辑关系。 61 | 62 | ### 1.3.2 物理(存储)结构 63 | 64 |   **物理结构**:是指数据的逻辑结构在计算机中的存储形式,因此也称为存储结构。数据是数据元素的集合,如何把数据元素存储到计算机的存储器中,并且数据的存储结构应正确反映数据元素之间的逻辑关系。这是实现物理结构的重点和难点。数据元素的存储结构形式有两种:**顺序存储和链式存储**。 65 | 66 | - **顺序存储结构**:是把数据元素存放在地址连续的存储单元里(物理地址上相邻),其数据间的**逻辑关系和物理关系**是一致的。 67 | 68 |   ![image.png](images/01-12.png) 69 | 70 | - **链式存储结构**:是把数据元素存放在任意的存储单元里,这组存储单元**可以是连续的,也可以是不连续的**。 71 | 72 |   ![image.png](images/01-13.png) 73 | 74 |   链式存储灵活很多,数据存在哪里不重要,只要有一个指针存放了相应的地址就能找到对应的数据。地址上不相邻,通过指针描述前后关系。 75 | 76 |   ![image](images/01-14.png) 77 | 78 |   逻辑结构是面向问题的,而物理结构就是面向计算机的,其基本的目标就是将数据及其逻辑关系存储到计算机的内存中。 79 | 80 | ## 1.4 数据类型 81 | 82 |   **数据类型是按照值的不同进行划分的。**在高级语言中,每个变量、常量和表达式都有各自的取值范围。类型就用来说明变量或表达式的取值范围和所能进行的操作。所以对数据进行分类,分出来多种数据类型,用来满足不同计算的需求。 83 | 84 |   在高级程序设计语言中,按"值"的不同特性,可将数据类型分为两类。 85 | - **原子类型**:整型、实型、字符型等 86 | - **结构类型**:结构体、数组等 87 | 88 |   **抽象是指抽取出事物具有的普遍性的本质**。它是抽出问题的特征而忽略非本质的细节,是对具体事物的一个概括。抽象是一种思考问题的方式,隐藏了繁杂的细节,只保留实现目标所必需的信息。 89 | 90 |   **抽象数据类型**:一个数学模型及定义在该模型上的**一组操作。** 91 | -------------------------------------------------------------------------------- /docs/ch01/images/01-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-01.png -------------------------------------------------------------------------------- /docs/ch01/images/01-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-02.png -------------------------------------------------------------------------------- /docs/ch01/images/01-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-03.png -------------------------------------------------------------------------------- /docs/ch01/images/01-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-04.png -------------------------------------------------------------------------------- /docs/ch01/images/01-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-05.png -------------------------------------------------------------------------------- /docs/ch01/images/01-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-06.png -------------------------------------------------------------------------------- /docs/ch01/images/01-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-07.png -------------------------------------------------------------------------------- /docs/ch01/images/01-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-08.png -------------------------------------------------------------------------------- /docs/ch01/images/01-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-09.png -------------------------------------------------------------------------------- /docs/ch01/images/01-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-10.png -------------------------------------------------------------------------------- /docs/ch01/images/01-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-11.png -------------------------------------------------------------------------------- /docs/ch01/images/01-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-12.png -------------------------------------------------------------------------------- /docs/ch01/images/01-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-13.png -------------------------------------------------------------------------------- /docs/ch01/images/01-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch01/images/01-14.png -------------------------------------------------------------------------------- /docs/ch02/ch02.md: -------------------------------------------------------------------------------- 1 | # 第2章 算法 2 | 3 | ## 2.1 两种算法的对比 4 | 5 | **例题**: 6 | 求 1+ 2+3····+100 的结果 7 | 8 | **解题方法:** 9 | 10 | **第一种方法**:使用`for`循环 11 | 12 | ```cpp 13 | #include 14 | using namespace std; 15 | int main() 16 | { 17 | int sum=0; 18 | for(int i=1;i<=100;i++) 19 | { 20 | sum+=i; 21 | } 22 | cout< 31 | using namespace std; 32 | int main() 33 | { 34 | int sum=0; 35 | sum=(1+100)*100/2; 36 | cout< 96 | using namespace std; 97 | int main() 98 | { 99 | int sum=0;//1次 100 | for(int i=1;i<=100;i++)//100次 101 | { 102 | sum+=i; 103 | } 104 | cout<L.length) 124 | return ERROR; 125 | *e=L.data[i-1]; 126 | 127 | return OK; 128 | } 129 | ``` 130 | 131 | #### 3.3.1.3 元素的插入操作 132 | 133 | 插入算法的思路: 134 | 1. 如果插入位置不合理,抛出异常; 135 | 2. 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量; 136 | 3. 从最后一个元素开始向前遍历到第 $i$ 个位置,分别将它们都向后移动一个位置; 137 | 4. 将要插入元素填入位置 $i$ 处; 138 | 5. 表长加1。 139 | 140 | ```c 141 | 142 | /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */ 143 | /* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */ 144 | Status ListInsert(SqList *L,int i,ElemType e) 145 | { 146 | int k; 147 | if (L->length==MAXSIZE) /* 顺序线性表已经满 */ 148 | return ERROR; 149 | if (i<1 || i>L->length+1) /* 当i比第一位置小或者比最后一位置后一位置还要大时 */ 150 | return ERROR; 151 | 152 | if (i<=L->length) /* 若插入数据位置不在表尾 */ 153 | { 154 | for(k=L->length-1;k>=i-1;k--) /* 将要插入位置后的元素向后移一位 */ 155 | L->data[k+1]=L->data[k]; 156 | } 157 | L->data[i-1]=e; /* 将新元素插入 */ 158 | L->length++; 159 | 160 | return OK; 161 | } 162 | ``` 163 | 164 | #### 3.3.1.4 元素的删除操作 165 | 166 | 删除算法的思路: 167 | 1. 如果删除位置不合理,抛出异常; 168 | 2. 取出删除元素; 169 | 3. 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置; 170 | 4. 表长减1 171 | 172 | ```c 173 | 174 | /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */ 175 | /* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */ 176 | Status ListDelete(SqList *L,int i,ElemType *e) 177 | { 178 | int k; 179 | if (L->length==0) /* 线性表为空 */ 180 | return ERROR; 181 | if (i<1 || i>L->length) /* 删除位置不正确 */ 182 | return ERROR; 183 | *e=L->data[i-1]; 184 | if (ilength) /* 如果删除不是最后位置 */ 185 | { 186 | for(k=i;klength;k++) /* 将删除位置后继元素前移 */ 187 | L->data[k-1]=L->data[k]; 188 | } 189 | L->length--; 190 | return OK; 191 | } 192 | ``` 193 | 194 | #### 3.3.1.5 顺序存储结构的优缺点 195 | 196 | **优点:** 197 | 198 | - 无需为表示表中元素之间的逻辑关系而增加额外的存储空间 199 | - 可以快速地存取表中任一位置的元素 200 | 201 | **缺点:** 202 | 203 | - 插入和删除操作需要移动大量元素 204 | - 当线性表长度变化较大时,难以确定存储空间的容量 205 | - 造成存储空间的“碎片” 206 | 207 | 208 | ### 3.3.2 链式存储结构 209 | 210 | ![image](images/03-04.png) 211 | 212 |   顺序结构的缺点在于插入和删除,需要移动大量的元素,耗费时间。为了解决这个整体移动的情况,用一组任意的存储单元来存储数据元素,这些存储位置可以是连续的,也可以是不连续的,但是要保证元素之间的直接前驱元素和直接后继元素能够有关联且唯一,即存储对应的地址。 213 | 214 | #### 3.3.2.1 组成部分 215 | 216 | - 数据域:存储数据元素信息的域叫做数据域 217 | - 指针域:存储直接后继元素位置的域叫做指针域 218 | - 结点:数据域+指针域组合 219 | 220 |   $n$个节点($a_i$的存储映像)链结成一个链表,即为线性表 $(a_1, a_2, \cdots, a_n)$ 的链式存储结构,该链表的每个结点中包含一个指针域,称为**单链表**,通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。 221 | 222 |   把链表中的第一个节点的存储位置称为**头指针**,规定线性链表的最后一个结点指针为“空”(通常用`NULL`或`^`符号表示)。有时,为了更加方便对链表进行操作,会在单链表的第一个结点前附设一个结点,称为**头节点**。头结点的数据域可以不存储任何信息, 223 | 224 | ![image](images/03-05.png) 225 | 226 | #### 3.3.2.2 注意事项 227 | 228 | 头指针与头结点的比较: 229 | 1. 头指针 230 | - 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针 231 | - 头指针具有标志作用,所以常用头指针作为链表的名字 232 | - 无论链表是否为空,头指针均不为空。头指针是链表的必要元素 233 | 234 | 2. 头结点 235 | - 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度) 236 | - 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了 237 | - 头结点不一定是链表必需要素 238 | 239 | #### 3.3.2.3 链式结构代码 240 | 241 | ```c 242 | /* 线性表的单链表存储结构 */ 243 | typedef struct Node 244 | { 245 | ElemType data; 246 | struct Node *next; 247 | }Node; 248 | typedef struct Node *LinkList; /* 定义LinkList */ 249 | 250 | ``` 251 | 252 |   以上的结构定义表示,**结点由存放数据元素的数据域和存放后继结点地址的指针域组成。**假设 $p$ 是指向线性表第 $i$ 个元素的指针,则该结点 $a_i$ 的数据域可以用`p->data`表示,`p->data`的值是一个数据元素,结点 $a_i$的指针域可以用`p->next`表示,`p->next`的值是一个指针。 253 | 254 | ![image](images/03-06.png) 255 | 256 | 257 | ## 3.4 各种各样的链表 258 | 259 | ### 3.4.1 单链表 260 | 261 | #### 3.4.1.1 单链表的读取 262 | 263 |   在单链表中,如果要找第 $i$ 个元素,需要从头开始找,对于单链表实现获取第 $i$ 个元素的数据的操作`GetElem`,在算法上要麻烦一些。 264 | 265 | **算法思路:** 266 | 1. 声明一个指针`p`指向链表第一个结点,初始化`j`从1开始; 267 | 2. 当 $jnext; /* 让p指向链表L的第一个结点 */ 279 | j = 1; /* j为计数器 */ 280 | while (p && jnext; /* 让p指向下一个结点 */ 283 | ++j; 284 | } 285 | if ( !p || j>i ) 286 | return ERROR; /* 第i个元素不存在 */ 287 | *e = p->data; /* 取第i个元素的数据 */ 288 | return OK; 289 | } 290 | ``` 291 | 292 | #### 3.4.1.2 单链表的插入 293 | 294 | ```c 295 | s->next = p->next; /* 将p的后继结点赋值给s的后继 */ 296 | p->next = s; /* 将s赋值给p的后继 */ 297 | ``` 298 | 299 |   上述代码表示让`p`的后继结点改成`s`的后继结点,再把结点`s`变成`p`的后继结点,如下图所示: 300 | 301 | 302 | ![image](images/03-07.png) 303 | 304 | **算法思路:** 305 | 1. 声明一指针`p`指向链表头结点,初始化`j`从1开始; 306 | 2. 当 $jdata`; 310 | 6. 单链表的插入标准语句`s->next=p->next; p->next=s`; 311 | 7. 返回成功。 312 | 313 | **算法代码:** 314 | 315 | ```c 316 | /* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */ 317 | /* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */ 318 | Status ListInsert(LinkList *L,int i,ElemType e) 319 | { 320 | int j; 321 | LinkList p,s; 322 | p = *L; 323 | j = 1; 324 | while (p && j < i) /* 寻找第i个结点 */ 325 | { 326 | p = p->next; 327 | ++j; 328 | } 329 | if (!p || j > i) 330 | return ERROR; /* 第i个元素不存在 */ 331 | 332 | s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */ 333 | s->data = e; 334 | s->next = p->next; /* 将p的后继结点赋值给s的后继 */ 335 | p->next = s; /* 将s赋值给p的后继 */ 336 | return OK; 337 | } 338 | ``` 339 | 340 | #### 3.4.1.3 单链表的删除 341 | 342 |   单链表的删除操作,设存储元素 $a_i$ 的结点为`q`,要实现将结点`q`删除单链表的操作,就是将它的前继结点的指针绕过,指向它的后继结点即可。 343 | 344 | ![image](images/03-08.png) 345 | 346 | **算法思路:** 347 | 1. 声明一指针`p`指向链表头结点,初始化`j`从1开始; 348 | 2. 当 $jnext`赋值给`q`; 351 | 5. 单链表的删除标准语句`p->next=q->next`; 352 | 6. 将`q`结点中的数据赋值给`e`,作为返回 353 | 7. 释放`q`结点; 354 | 8. 返回成功。 355 | 356 | **算法代码:** 357 | 358 | ```c 359 | /* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */ 360 | /* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */ 361 | Status ListDelete(LinkList *L,int i,ElemType *e) 362 | { 363 | int j; 364 | LinkList p,q; 365 | p = *L; 366 | j = 1; 367 | while (p->next && j < i) /* 遍历寻找第i个元素 */ 368 | { 369 | p = p->next; 370 | ++j; 371 | } 372 | if (!(p->next) || j > i) 373 | return ERROR; /* 第i个元素不存在 */ 374 | q = p->next; 375 | p->next = q->next; /* 将q的后继赋值给p的后继 */ 376 | *e = q->data; /* 将q结点中的数据给e */ 377 | free(q); /* 让系统回收此结点,释放内存 */ 378 | return OK; 379 | } 380 | ``` 381 | 382 | #### 3.4.1.4 单链表的整表创建 383 | 384 | **头插法的算法思路:** 385 | 1. 声明一指针`p`和计数器变量`i` 386 | 2. 初始化一空链表`L` 387 | 3. 让`L`的头结点的指针指向`NULL`,即建立一个带头结点的单链表 388 | 4. 循环: 389 | - 生成一新结点赋值给`p`; 390 | - 随机生成一数字赋值给`p`的数据域`p->data`; 391 | - 将`p`插入到头结点与前一新结点之间。 392 | 393 | **头插法的算法代码:** 394 | 395 | ```c 396 | /* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */ 397 | void CreateListHead(LinkList *L, int n) 398 | { 399 | LinkList p; 400 | int i; 401 | srand(time(0)); /* 初始化随机数种子 */ 402 | *L = (LinkList)malloc(sizeof(Node)); 403 | (*L)->next = NULL; /* 先建立一个带头结点的单链表 */ 404 | for (i=0; idata = rand()%100+1; /* 随机生成100以内的数字 */ 408 | p->next = (*L)->next; 409 | (*L)->next = p; /* 插入到表头 */ 410 | } 411 | } 412 | ``` 413 | 414 | **尾插法的算法代码:** 415 | 416 | ```c 417 | /* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */ 418 | void CreateListTail(LinkList *L, int n) 419 | { 420 | LinkList p,r; 421 | int i; 422 | srand(time(0)); /* 初始化随机数种子 */ 423 | *L = (LinkList)malloc(sizeof(Node)); /* L为整个线性表 */ 424 | r=*L; /* r为指向尾部的结点 */ 425 | for (i=0; idata = rand()%100+1; /* 随机生成100以内的数字 */ 429 | r->next=p; /* 将表尾终端结点的指针指向新结点 */ 430 | r = p; /* 将当前的新结点定义为表尾终端结点 */ 431 | } 432 | r->next = NULL; /* 表示当前链表结束 */ 433 | } 434 | ``` 435 | 436 | #### 3.4.1.5 单链表的整表删除 437 | 438 | **算法思路:** 439 | 1. 声明一指针`p`和`q`; 440 | 2. 将第一个结点赋值给`p` 441 | 3. 循环: 442 | - 将下一结点赋值给`q`; 443 | - 释放`p` 444 | - 将`q`赋值给`p` 445 | 446 | **算法代码:** 447 | 448 | ```c 449 | /* 初始条件:链式线性表L已存在。操作结果:将L重置为空表 */ 450 | Status ClearList(LinkList *L) 451 | { 452 | LinkList p,q; 453 | p=(*L)->next; /* p指向第一个结点 */ 454 | while(p) /* 没到表尾 */ 455 | { 456 | q=p->next; 457 | free(p); 458 | p=q; 459 | } 460 | (*L)->next=NULL; /* 头结点指针域为空 */ 461 | return OK; 462 | } 463 | ``` 464 | 465 | #### 3.4.1.6 单链表与顺序存储结构的对比 466 | 467 | | | 顺序存储结构 | 单链表 | 468 | | ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 469 | | 存储分配方式 | 使用一段连续存储单元依次存储线性表的数据元素 | 采用链式存储结构,用一组任意的存储单元存放线性表的元素 | 470 | | 时间性能 | 查找:$O(1)$
插入和删除:需要平均移动表长一半的元素,时间复杂度为$O(n)$ | 查找:$O(n)$
插入和删除:在找出位置的指针后,时间复杂度为$O(1)$ | 471 | | 空间性能 | 需要预分配存储空间,分大了浪费,分小了容易发生上溢 | 不需要分配存储空间,只要有就可以分配,元素个数不受限制 | 472 | 473 | 474 | - 若线性表需要频繁查找,很少进行插入和删除操作,宜采用顺序存储结构 475 | - 若线性表中的元素个数变化比较大或者根本不知道有多大时,最好用单链表结构 476 | 477 | 478 | ### 3.4.2 静态链表 479 | 480 | #### 3.4.2.1 定义 481 | 482 |   对于没有指针的高级语言,用数组来代替指针描述单链表。数组的元素都是由两个数据域组成,`data`和`cur`,`cur`就相当于指针,叫做游标。把这种用数组描述的链表叫做**静态链表**,或者**游标实现法** 483 | 484 | ```c 485 | #define MAXSIZE 1000 /* 存储空间初始分配量 */ 486 | 487 | /* 线性表的静态链表存储结构 */ 488 | typedef struct 489 | { 490 | ElemType data; 491 | int cur; /* 游标(Cursor) ,为0时表示无指向 */ 492 | } Component,StaticLinkList[MAXSIZE]; 493 | ``` 494 | 495 |   对数组第一个和最后一个元素作为特殊元素处理,不存数据。把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为0的元素的`cur`存放备用链表的第一个结点的下标;最后一个元素的`cur`存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为0。 496 | 497 | ![image](images/03-09.png) 498 | 499 | ```c 500 | /* 将一维数组space中各分量链成一个备用链表,space[0].cur为头指针,"0"表示空指针 */ 501 | Status InitList(StaticLinkList space) 502 | { 503 | int i; 504 | for (i=0; i ListLength(L) + 1) 537 | return ERROR; 538 | j = Malloc_SSL(L); /* 获得空闲分量的下标 */ 539 | if (j) 540 | { 541 | L[j].data = e; /* 将数据赋值给此分量的data */ 542 | for(l = i; l <= i - 1; l++) /* 找到第i个元素之前的位置 */ 543 | k = L[k].cur; 544 | L[j].cur = L[k].cur; /* 把第i个元素之前的cur赋值给新元素的cur */ 545 | L[k].cur = j; /* 把新元素的下标赋值给第i个元素之前元素的cur */ 546 | return OK; 547 | } 548 | return ERROR; 549 | } 550 | ``` 551 | 552 | 553 | #### 3.4.2.3 静态链表的删除 554 | 555 | **算法代码:** 556 | 557 | ```c 558 | /* 删除在L中第i个数据元素 */ 559 | Status ListDelete(StaticLinkList L, int i) 560 | { 561 | int j, k; 562 | if (i < 1 || i > ListLength(L)) 563 | return ERROR; 564 | k = MAXSIZE - 1; 565 | for (j = 1; j <= i - 1; j++) 566 | k = L[k].cur; 567 | j = L[k].cur; 568 | L[k].cur = L[j].cur; 569 | Free_SSL(L, j); 570 | return OK; 571 | } 572 | ``` 573 | 574 | ```c 575 | /* 将下标为k的空闲结点回收到备用链表 */ 576 | void Free_SSL(StaticLinkList space, int k) 577 | { 578 | space[k].cur = space[0].cur; /* 把第一个元素的cur值赋给要删除的分量cur */ 579 | space[0].cur = k; /* 把要删除的分量下标赋值给第一个元素的cur */ 580 | } 581 | ``` 582 | 583 | ```c 584 | /* 初始条件:静态链表L已存在。操作结果:返回L中数据元素个数 */ 585 | int ListLength(StaticLinkList L) 586 | { 587 | int j=0; 588 | int i=L[MAXSIZE-1].cur; 589 | while(i) 590 | { 591 | i=L[i].cur; 592 | j++; 593 | } 594 | return j; 595 | } 596 | ``` 597 | 598 | #### 3.4.2.4 静态链表的优缺点 599 | 600 | **优点:** 601 |   在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中插入和删除操作需要移动大量元素的缺点。 602 | 603 | **缺点:** 604 |   没有解决连续存储分配带来的表长难以确定的问题,失去了链式存储结构随机存取的特性。 605 | 606 | ### 3.4.3 循环链表 607 | 608 | 循环链表简图如下: 609 | 610 | ![image](images/03-10.png) 611 | 612 | - 空循环链表 613 | 614 | ![image](images/03-11.png) 615 | 616 | - 非空循环链表 617 | 618 | ![image](images/03-12.png) 619 | 620 |   对于非空循环链表,不用头指针,而是用指向终端结点的尾指针来表示循环链表,方便查找开始结点和终端结点。 621 | 622 | ```c 623 | p=rearA->next; /* 保存A表的头结点,即① */ 624 | rearA->next=rearB->next->next; /* 将本是指向B表的第一个结点(不是头结点)*/ 625 | /* 赋值给reaA->next,即② */ 626 | q=rearB->next; 627 | rearB->next=p; /* 将原A表的头结点赋值给rearB->next,即③ */ 628 | free(q); /* 释放q */ 629 | ``` 630 | 631 | ### 3.4.4 双向链表 632 | 633 |   双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以再双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。 634 | 635 | ```c 636 | /*线性表的双向链表存储结构*/ 637 | typedef struct DulNode 638 | { 639 | ElemType data; 640 | struct DuLNode *prior; /*直接前驱指针*/ 641 | struct DuLNode *next; /*直接后继指针*/ 642 | } DulNode, *DuLinkList; 643 | ``` 644 | 645 | - 双向链表的循环带头结点的空链表 646 | 647 | ![image](images/03-13.png) 648 | 649 | - 非空的循环带头结点的双向链表 650 | 651 | ![image](images/03-14.png) 652 | 653 | **插入操作:** 654 |   假设存储元素`e`的结点为`s`,要实现将结点`s`插入到结点`p`和`p->next`之间的操作示意图如下: 655 | 656 | ![image](images/03-15.png) 657 | 658 | ```c 659 | s - >prior = p; /*把p赋值给s的前驱,如图中①*/ 660 | s -> next = p -> next; /*把p->next赋值给s的后继,如图中②*/ 661 | p -> next -> prior = s; /*把s赋值给p->next的前驱,如图中③*/ 662 | p -> next = s; /*把s赋值给p的后继,如图中④*/ 663 | 664 | ``` 665 | **删除操作:** 666 |   删除结点`p`的示意图: 667 | 668 | ![image](images/03-16.png) 669 | 670 | ```c 671 | p->prior->next=p->next; /*把p->next赋值给p->prior的后继,如图中①*/ 672 | p->next->prior=p->prior; /*把p->prior赋值给p->next的前驱,如图中②*/ 673 | free(p); /*释放结点*/ 674 | ``` 675 | 676 | ## 3.5 习题 677 | 678 | ### 3.5.1 P3156 【深基15.例1】询问学号-简单 679 | 680 | [P3156 【深基15.例1】询问学号 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)](https://www.luogu.com.cn/problem/P3156) 681 | 682 | **题目描述** 683 | 684 | 有 $n(n \le 2 \times 10^6)$ 名同学陆陆续续进入教室。我们知道每名同学的学号(在 $1$ 到 $10^9$ 之间),按进教室的顺序给出。上课了,老师想知道第 $i$ 个进入教室的同学的学号是什么(最先进入教室的同学 $i=1$),询问次数不超过 $10^5$ 次。 685 | 686 | **输入格式** 687 | 688 | 第一行 $2$ 个整数 $n$ 和 $m$,表示学生个数和询问次数。 689 | 690 | 第二行 $n$ 个整数,表示按顺序进入教室的学号。 691 | 692 | 第三行 $m$ 个整数,表示询问第几个进入教室的同学。 693 | 694 | **输出格式** 695 | 696 | 输出 $m$ 个整数表示答案,用换行隔开。 697 | 698 | **样例输入 #1** 699 | 700 | ``` 701 | 10 3 702 | 1 9 2 60 8 17 11 4 5 14 703 | 1 5 9 704 | ``` 705 | 706 | **样例输出 #1** 707 | 708 | ``` 709 | 1 710 | 8 711 | 5 712 | ``` 713 | 714 | 715 | ### 3.5.2 P3613 【深基15.例2】寄包柜-简单 716 | 717 | [P3613 【深基15.例2】寄包柜 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)](https://www.luogu.com.cn/problem/P3613) 718 | 719 | **题目描述** 720 | 721 | 超市里有 $n(1\le n\le10^5)$ 个寄包柜。每个寄包柜格子数量不一,第 $i$ 个寄包柜有 $a_i(1\le a_i\le10^5)$ 个格子,不过我们并不知道各个 $a_i$ 的值。对于每个寄包柜,格子编号从 1 开始,一直到 $a_i$。现在有 $q(1 \le q\le10^5)$ 次操作: 722 | 723 | * `1 i j k`:在第 $i$ 个柜子的第 $j$ 个格子存入物品 $k(0\le k\le 10^9)$。当 $k=0$ 时说明清空该格子。 724 | * `2 i j`:查询第 $i$ 个柜子的第 $j$ 个格子中的物品是什么,保证查询的柜子有存过东西。 725 | 726 | 已知超市里共计不会超过 $10^7$ 个寄包格子,$a_i$ 是确定然而未知的,但是保证一定不小于该柜子存物品请求的格子编号的最大值。当然也有可能某些寄包柜中一个格子都没有。 727 | 728 | **输入格式** 729 | 730 | 第一行 2 个整数 $n$ 和 $q$,寄包柜个数和询问次数。 731 | 732 | 接下来 $q$ 个整数,表示一次操作。 733 | 734 | **输出格式** 735 | 736 | 对于查询操作时,输出答案,以换行隔开。 737 | 738 | **样例输入 #1** 739 | 740 | ``` 741 | 5 4 742 | 1 3 10000 118014 743 | 1 1 1 1 744 | 2 3 10000 745 | 2 1 1 746 | ``` 747 | 748 | **样例输出 #1** 749 | 750 | ``` 751 | 118014 752 | 1 753 | ``` 754 | 755 | 756 | ### 3.5.3 P1047 [NOIP2005 普及组] 校门外的树-简单 757 | 758 | [P1047 [NOIP2005 普及组] 校门外的树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)](https://www.luogu.com.cn/problem/P1047) 759 | 760 | **题目描述** 761 | 762 | 某校大门外长度为 $l$ 的马路上有一排树,每两棵相邻的树之间的间隔都是 $1$ 米。我们可以把马路看成一个数轴,马路的一端在数轴 $0$ 的位置,另一端在 $l$ 的位置;数轴上的每个整数点,即 $0,1,2,\dots,l$,都种有一棵树。 763 | 764 | 由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。 765 | 766 | **输入格式** 767 | 768 | 第一行有两个整数,分别表示马路的长度 $l$ 和区域的数目 $m$。 769 | 770 | 接下来 $m$ 行,每行两个整数 $u, v$,表示一个区域的起始点和终止点的坐标。 771 | 772 | **输出格式** 773 | 774 | 输出一行一个整数,表示将这些树都移走后,马路上剩余的树木数量。 775 | 776 | **样例输入 #1** 777 | 778 | ``` 779 | 500 3 780 | 150 300 781 | 100 200 782 | 470 471 783 | ``` 784 | 785 | **样例输出 #1** 786 | 787 | ``` 788 | 298 789 | ``` 790 | 791 | 792 | ### 3.5.4 P1160 队列安排-中等 793 | 794 | [P1160 队列安排 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)](https://www.luogu.com.cn/problem/P1160) 795 | 796 | **题目描述** 797 | 798 | 一个学校里老师要将班上 $N$ 个同学排成一列,同学被编号为 $1\sim N$,他采取如下的方法: 799 | 800 | 1. 先将 $1$ 号同学安排进队列,这时队列中只有他一个人; 801 | 2. $2-N$ 号同学依次入列,编号为 $i$ 的同学入列方式为:老师指定编号为 $i$ 的同学站在编号为 $1\sim(i-1)$ 中某位同学(即之前已经入列的同学)的左边或右边; 802 | 3. 从队列中去掉 $M(M 根据输入学号的顺次,利用下标作为次序存储进入教室同学的学号。根据查询的序号作为下标,直接输出答案。 912 | 913 | **答案:** 914 | 915 | ```cpp 916 | #include 917 | #define endl '\n' 918 | #define int long long 919 | using namespace std; 920 | const int N = 2e6+10; 921 | typedef long long ll; 922 | int ans[N]; 923 | signed main() 924 | { 925 | ios::sync_with_stdio(false); 926 | cin.tie(0),cout.tie(0); 927 | int n,m; 928 | cin>>n>>m; 929 | for(int i=1;i<=n;i++)//从1开始,模拟学生从第一个进来 930 | { 931 | cin>>ans[i]; 932 | } 933 | for(int i=0;i>x; 937 | cout< 如果要开一个 a[$10^5$]$10^5$] 的数组,肯定 **MLE**。事实上并没有这么多个值要存储。可以使用 **Map** 来进行数据离散化。 949 | > 950 | > `m[i][j] ` 来表示第 `i` 个柜子的第 `j` 个物品存放的东西(0 表示无东西存放)。 951 | 952 | **答案:** 953 | 954 | ```cpp 955 | #include 956 | #define endl '\n' 957 | #define int long long 958 | using namespace std; 959 | const int N = 2e5+10; 960 | typedef long long ll; 961 | mapm[N]; //二维map 962 | signed main() 963 | { 964 | ios::sync_with_stdio(false); 965 | cin.tie(0),cout.tie(0); 966 | int n,q; 967 | cin>>n>>q; 968 | while(q--) 969 | { 970 | int op; 971 | cin>>op; 972 | if(op==1)//存入 973 | { 974 | int i,j,k; 975 | cin>>i>>j>>k; 976 | m[i][j]=k; //直接映射 977 | }else{//查询 978 | int i,j; 979 | cin>>i>>j; 980 | cout< 暴力求解,对建造地铁的路段全部赋值为 1,最后遍历数组,累加还剩多少棵树。 993 | 994 | **答案:** 995 | 996 | ```cpp 997 | #include 998 | //#define endl "\n" 999 | #define int long long 1000 | using namespace std; 1001 | const int N = 2e5+10; 1002 | typedef long long ll; 1003 | int num[10005]; 1004 | signed main() 1005 | { 1006 | ios::sync_with_stdio(false); 1007 | cin.tie(0),cout.tie(0); 1008 | int l,m; 1009 | cin>>l>>m; 1010 | for(int i=0;i>u>>v; 1014 | for(int j=u;j<=v;j++) 1015 | { 1016 | num[j]=1; 1017 | } 1018 | } 1019 | int sum=0; 1020 | for(int i=0;i<=l;i++) 1021 | { 1022 | if(num[i]==0) 1023 | { 1024 | sum++; 1025 | } 1026 | } 1027 | cout< 547 | using namespace std; 548 | int n,f[19]={1,1}; 549 | signed main() { 550 | scanf("%d",&n); 551 | for(int i=2;i<=n;i++) 552 | for(int j=0;j<=i-1;j++) 553 | f[i]+=f[j]*f[i-j-1]; 554 | printf("%d",f[n]); 555 | return 0; 556 | } 557 | 558 | ``` 559 | 560 | ### 习题2 后缀表达式(洛谷P149 简单) 561 | 562 | **题目描述:** 563 | 564 |   所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。 565 | 566 | **样例:** 567 | 568 | 如:3*(5-2)+7对应的后缀表达式为:3.5.2.-*7.+@。在该式中,`@` 为表达式的结束符号。`.` 为操作数的结束符号。 569 | 570 | 输入一行一个字符串 $s$,表示后缀表达式。 571 | 572 | 输出一个整数,表示表达式的值。 573 | 574 | ```python 575 | 3.5.2.-*7.+@ #input 576 | 16 #output 577 | ``` 578 | 579 | ```c 580 | // 581 | // main.cpp 582 | // 后缀表达式 583 | // 584 | // Created by Helen on 2020/7/26. 585 | // Copyright © 2020 Helen. All rights reserved. 586 | // 587 | 588 | #include 589 | #include 590 | #include 591 | using namespace std; 592 | string s; 593 | int n,ans; 594 | stacknum; 595 | int main () 596 | { 597 | cin >> s; // 输入原字符串 598 | for (int i=0;i='0' && s[i]<='9') // 如果是数字 601 | { 602 | n*=10; // 乘10 603 | n+=s[i]-'0'; // 转为数字 604 | } 605 | else if (s[i]=='.') // 如果是'.' 606 | { 607 | num.push(n); // 把前面处理的数字push进栈里 608 | n=0; 609 | } 610 | else if (s[i]=='+') // 判断加减乘除就好啦 611 | { 612 | int a=num.top(); 613 | num.pop(); 614 | int b=num.top(); 615 | num.pop(); 616 | ans=a+b; 617 | num.push(ans); 618 | } 619 | else if (s[i]=='-') 620 | { 621 | int a=num.top(); 622 | num.pop(); 623 | int b=num.top(); 624 | num.pop(); 625 | ans=b-a; 626 | num.push(ans); 627 | } 628 | else if (s[i]=='*') 629 | { 630 | int a=num.top(); 631 | num.pop(); 632 | int b=num.top(); 633 | num.pop(); 634 | ans=a*b; 635 | num.push(ans); 636 | } 637 | else if (s[i]=='/') 638 | { 639 | int a=num.top(); 640 | num.pop(); 641 | int b=num.top(); 642 | num.pop(); 643 | ans=b/a; 644 | num.push(ans); 645 | } 646 | } 647 | cout << num.top() << endl; // 就剩一个数字了嘛 所以直接输出 648 | } 649 | ``` 650 | 651 | ### 习题3 滑动窗口的最大值(牛客 中等) 652 | 653 | **题目描述:** 654 | 655 |   给定一个长度为 $n$ 的数组`nums`和滑动窗口的大小`size`,找出所有滑动窗口里数值的最大值。 656 | 657 |   例如,如果输入数组 $\{ 2, 3, 4, 2, 6, 2, 5,1\}$ 及滑动窗口的大小3,那么一共存在6个滑动窗口,它们的最大值分别为 $\{4, 4, 6, 6, 6, 5\}$;针对数组 $\{ 2, 3, 4, 2, 6, 2, 5, 1\}$的滑动窗口有以下6个: 658 | $$\{[2, 3, 4], 2, 6, 2, 5, 1\}, \{ 2, [3, 4, 2], 6, 2, 5, 1\},\{ 2, 3, [4, 2, 6], 2, 5, 1\}, \{ 2, 3, 4, [2, 6, 2], 5, 1\}, \{ 2, 3, 4, 2, [6, 2, 5], 1\}, \{ 2, 3, 4, 2, 6, [2, 5, 1]\}$$ 659 | 660 |   数据范围:1 ≤ size ≤ n ≤ 10000,数组中每个元素的值满足 $|val| \leqslant 10000$ 661 | 662 |   要求:空间复杂度 $O(n)$,时间复杂度 $O(n)$ 663 | 664 | ```c 665 | class Solution: 666 | def maxInWindows(self, num, size): 667 | ans=[] 668 | q=[] 669 | for i in range(len(num)): 670 | if len(q)>0 and i-size==q[0]:#判断最大值是否还在滑动窗口中 671 | q.pop(0) 672 | while len(q)>0 and num[i]>=num[q[-1]]: 673 | q.pop() 674 | q.append(i) 675 | if i>=size-1:#相等表示第一个滑动窗口 676 | ans.append(num[q[0]]) 677 | return ans 678 | 679 | ``` 680 | 681 | ### 习题4 时间复杂度(洛谷 中等) 682 | 683 | 684 | #### 描述 685 | 686 | 小明正在学习一种新的编程语言 A++,刚学会循环语句的他激动地写了好多程序并 给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序, 于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。 687 | 688 | A++语言的循环结构如下: 689 | 690 | 691 | ```cpp 692 | F i x y 693 | 循环体 694 | E 695 | ``` 696 | 697 | 698 | 其中`F i x y`表示新建变量`i`(变量 `i` 不可与未被销毁的变量重名)并初始化为 `x`, 然后判断 `x`和 `y` 的大小关系,若`i`小于等于`y` 则进入循环,否则不进入。每次循环结束后`i` 都会被修改成`i+1`,一旦 `i` 大于`y` 终止循环。 699 | 700 | `x` 和 `y` 可以是正整数(`x` 和 `y` 的大小关系不定)或变量 `n`。`n` 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 `100`。 701 | 702 | `E` 表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。 703 | 704 | 注:本题中为了书写方便,在描述复杂度时,使用大写英文字母 ` O` 表示通常意义下 `Θ` 的概念。 705 | 706 | ##### 输入格式 707 | 708 | 输入文件第一行一个正整数 `t`,表示有 `t`(`t ≤ 10`)个程序需要计算时间复杂度。 每个程序我们只需抽取其中 `F i x y` 和 `E` 即可计算时间复杂度。注意:循环结构允许嵌套。 709 | 710 | 接下来每个程序的第一行包含一个正整数 `L` 和一个字符串,`L` 代表程序行数,字符串表示这个程序的复杂度,`O(1)` 表示常数复杂度,`O(n^w)` 表示复杂度为 `n^w`,其中 `w` 是一个小于 `100` 的正整数,输入保证复杂度只有 `O(1)` 和 `O(n^w)` 两种类型。 711 | 712 | 接下来 `L` 行代表程序中循环结构中的`F i x y`或者 `E`。 程序行若以`F`开头,表示进入一个循环,之后有空格分离的三个字符(串)`i x y`, 其中 `i` 是一个小写字母(保证不为`n`),表示新建的变量名,`x` 和 `y` 可能是正整数或 `n` ,已知若为正整数则一定小于 `100`。 713 | 714 | 程序行若以`E`开头,则表示循环体结束。 715 | 716 | ##### 输出格式 717 | 718 | 输出文件共 `t` 行,对应输入的 `t` 个程序,每行输出 `Yes` 或 `No` 或者 `ERR`,若程序实际复杂度与输入给出的复杂度一致则输出 `Yes`,不一致则输出 `No`,若程序有语法错误(其中语法错误只有: ① `F` 和 `E` 不匹配 ②新建的变量与已经存在但未被销毁的变量重复两种情况),则输出 `ERR`。 719 | 720 | 注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出 `ERR`。 721 | ```c 722 | #include 723 | using namespace std; 724 | int top=0,k=0,x=0; 725 | int n,h,t,ans=0; 726 | int f[1000001]={};//表示当前循环是否能进人或是否是继承上一层循环(-1位进入不了,0为继承,1为能够继续算) 727 | bool fn[1000001]={};//表示当前的n^w是否进入 728 | bool fx[200001]={};//表示当前变量是否用过 729 | bool ff=0;//表示是否存在语法错误 730 | char chtop[1000001];//记录栈顶的变量 731 | string s,s1; 732 | int main(){ 733 | scanf("%d",&t); 734 | while (t--){ 735 | scanf("%d ",&n);//输入行数 736 | top=0; 737 | k=0; 738 | ans=0; 739 | ff=0; 740 | x=0; 741 | memset(f,0,sizeof(f)); 742 | memset(fn,0,sizeof(fn)); 743 | memset(fx,0,sizeof(fx));//清空 744 | fn[100]=1; 745 | getline(cin,s); 746 | if (s[2]=='1') ans=0,fn[100]=1;//常数复杂度 747 | else{ 748 | int i=4; 749 | for (;s[i]!=')';) 750 | ans=ans*10+s[i]-'0',i++;//找出次方 751 | } 752 | // cout<<"ans:"<='0'&&s[x+1]<='9') ans2=ans2*10+s[x+1]-'0'; else; 784 | // cout<<"x:"<ans2) 786 | f[top+100]=-1;//如果后面比前面的小,不能进入循环 787 | else f[top+100]=0;//两边都属于常数,继承 788 | } 789 | } 790 | else{ 791 | fx[chtop[top+100]-'a']=0;//变量销毁 792 | if (f[top+100]==1) k--;else;//如果结束了能计算的循环,结束循环,少一层n 793 | f[top+100]=0;//结束循环 794 | top--; 795 | } 796 | } 797 | if (ff||top!=0)printf("ERR\n");//如果存在语法错误或E与F不匹配 798 | else if (fn[ans+100]==1&&fn[ans+101]==0)//如果有n^ans且这是最大时间复杂度 799 | printf("Yes\n"); 800 | else printf("No\n"); 801 | } 802 | return 0; 803 | } 804 | 805 | ``` 806 | 807 | 808 | 809 | 810 | ### 习题5 双栈(牛客 简单) 811 | 812 | 请写一个整数计算器,支持加减乘三种运算和括号。 813 | 814 | 815 | 816 | 数据范围:`0≤ |s| ≤ 1000`保证计算结果始终在整型范围内 817 | 818 | 要求:空间复杂度: O(n),时间复杂度 O(n) 819 | 820 | 输入:`1+2`输出:3 821 | 822 | 输入:`(2*(3-4))*5 `输出:-10 823 | 824 | 输入:`3+2*3*4-1`输出:26 825 | 826 | ``` 827 | 828 | /** 829 | * 返回表达式的值 830 | * @param s string字符串 待计算的表达式 831 | * @return int整型 832 | */ 833 | public int solve (String s) { 834 | return solve(s, 0)[0]; 835 | } 836 | 837 | /** 838 | * 返回表达式不包含括号的值,以及遇到')'的坐标 839 | * @param s 表达式 840 | * @param index 开始的坐标 841 | * @return int[] 842 | */ 843 | public int[] solve (String s, int index) { 844 | //存储数字 845 | Stack stack = new Stack<>(); 846 | int num = 0; 847 | char flag = '+'; 848 | int i; 849 | for (i = index; i < s.length(); i++) { 850 | //如果检测到数字的话,转换成int数字 851 | if(Character.isDigit(s.charAt(i))){ 852 | num = num*10 + s.charAt(i) -'0'; 853 | if(i!=s.length()-1){ 854 | continue; 855 | } 856 | } 857 | //碰到'('时,把整个括号内的当成一个数字处理 858 | if(s.charAt(i) == '('){ 859 | int[] result = solve(s, i+1); 860 | num = result[0]; 861 | i = result[1]; 862 | if(i!=s.length()-1){ 863 | continue; 864 | } 865 | } 866 | 867 | switch (flag) { 868 | case '+': 869 | stack.add(num); 870 | break; 871 | case '-': 872 | stack.add(-num); 873 | break; 874 | case '*': 875 | stack.add(stack.pop() * num); 876 | break; 877 | 878 | default: 879 | break; 880 | } 881 | num = 0; 882 | if(s.charAt(i) == ')'){ 883 | break; 884 | }else { 885 | flag = s.charAt(i); 886 | } 887 | } 888 | int sum = 0; 889 | while(!stack.empty()){ 890 | sum+=stack.pop(); 891 | } 892 | return new int[]{sum,i}; 893 | } 894 | 895 | ``` 896 | 897 | ### 习题6 Sramoc 问题(洛谷 难) 898 | 899 | 话说员工们整理好了筷子之后,就准备将快餐送出了,但是一看订单,都傻眼了:订单上没有留电话号码,只写了一个 `sramoc(k,m)` 函数,这什么东西?什么意思?于是餐厅找来了资深顾问团的成员,YQ,SC,HQ,经过大量的查阅,大家获得了一些信息,`sramoc(k,m)` 表示用数字 `0,1,2,... k-1` 组成的正整数中能被 `m` 整除的最小数。例如 `k=2,m=7` 的时候,`sramoc(2,7)=1001`。自然电话号码就是 `1001`,为了尽快将快餐送出,电脑组的童鞋们埋头算起了这个齐葩的号码。。。 900 | 901 | #### 输入格式 902 | 903 | 第 `1` 行为两个整数 `k,m`。 904 | 905 | #### 输出格式 906 | 907 | 仅 `1` 行,那个电话号码(最小的数)。 908 | 909 | ``` 910 | 911 | #include 912 | #include 913 | #include 914 | #include 915 | #include 916 | using namespace std; 917 | #define MAXN 1005 918 | int K,M; 919 | bool vis[MAXN]; 920 | void bfs() 921 | { 922 | int i; 923 | queue q; 924 | queue > q2; 925 | vector s; 926 | for(i=1;i<=K-1;i++) //i表示的是余数,所以不能为0,为0就直接结束了(下面) 927 | { 928 | q.push(i%M); //这里取余是已经判断出传递对象了 929 | vis[i%M]=true; 930 | s.push_back(i); 931 | q2.push(s); // 队列在这个循环中都是没出队列的 932 | s.pop_back(); //但不定数组每次都要重置 ,不定数组存的是一个数字串 933 | } //利用广搜处理暴力枚举k^a种情况a确定大小 934 | long long u,k; 935 | while(!q.empty()) //出队列才是广搜的开始 936 | { 937 | u=q.front(); 938 | q.pop(); 939 | s=q2.front(); 940 | q2.pop(); 941 | for(i=0;i<=K-1;i++) //枚举K,和暴力一致(K>=2&&K<=10,m>=0&&m<=1000) //以暴力为基础思想的还是用 long long比较保险 942 | { //利用同余简化暴力枚举的次数,但枚举形式不变 943 | k=u*(10%M)+i%M; //(x*k+y)mod m=((x mod m)*(k mod m)+(y mod m) ) mod m .//本题的k为10 944 | // ((x*k+y)*k+i) mod m==(((x*k+y) mod m)*(k mod m)+i mod m) mod m==上述,所以可以迭代 945 | s.push_back(i); // 所以每次 u取(x*k+y) mod m,也就是k%M 946 | if(k%M==0) //用同余就考虑两种对比情况,找到传递的对象 947 | { 948 | for(unsigned int j=0;j>K>>M; 990 | bfs(); 991 | return 0; 992 | } 993 | 994 | ``` 995 | 996 | ## 4.7 习题答案 997 | 998 | ### 习题1 栈(洛谷NOIP2003普及组 简单) 999 | 1000 | **思路:** 1001 | 1002 | > 本题解题思路:考虑有1个元素入栈时,出栈的情况;考虑有2个元素入栈时,出栈的情况;...;考虑有n个元素入栈时,出栈的情况。然后将这n中情况遍历一遍。先乘积(入栈情况数*出栈情况数)再求和即可。 1003 | 1004 | **答案:** 1005 | 1006 | ```c 1007 | #include 1008 | using namespace std; 1009 | int n,dp[20][20]; 1010 | int main(){ 1011 | cin>>n; 1012 | for(int i=0;i<=n;i++) 1013 | dp[i][0]=1;//设边界 1014 | for(int j=1;j<=n;j++)//指未进栈数字的个数 1015 | for(int i=0;i<=n;i++){//指栈内数字的个数 1016 | if(i>=1)//站内有数就使未进栈的个数-1,栈内个数+1 1017 | dp[i][j]=dp[i-1][j]+dp[i+1][j-1]; 1018 | if(i==0)//站内没有数,只能进栈 1019 | dp[i][j]=dp[i+1][j-1]; 1020 | } 1021 | cout< 本题是对于栈的较为直接的考察,我们可以直接选择建立一个栈来存储我们的每次输入的数字,然后遇到一个符号就弹出两个数来进行运算,运算的结果再入栈,最后运算结束之后,栈中只会剩下最后一个数,就是我们的结果。 1033 | 1034 | **答案:** 1035 | 1036 | ```c 1037 | #define _CRT_SECURE_NO_WARNINGS 1038 | 1039 | #include 1040 | 1041 | int main() 1042 | { 1043 | int stack[1000]; // 用来放置相关数字的栈 1044 | char ch; // 读入字符 1045 | int top = 0; //记录栈目前的栈顶位置 1046 | while ((ch = getchar()) != '@') 1047 | { 1048 | if (ch <= '9' && ch >= '0') 1049 | { 1050 | int num; // 用来做读取的每一个数字 1051 | num = ch - '0'; 1052 | while ((ch = getchar()) != '\.') // 如果是数字的话 1053 | { 1054 | num *= 10; 1055 | num += ch - '0'; 1056 | } 1057 | stack[top++] = num; // 入栈 1058 | // printf("%d", num); 1059 | } 1060 | else // 如果是运算符就出栈运算然后入栈 1061 | { 1062 | int right = stack[--top], left = stack[--top]; // 得到左右操作数 1063 | 1064 | switch (ch) 1065 | { 1066 | case'*': stack[top++] = left * right; break; 1067 | case'/': stack[top++] = left / right; break; 1068 | case'+': stack[top++] = left + right; break; 1069 | case'-': stack[top++] = left - right; break; 1070 | default:break; 1071 | } 1072 | } 1073 | // printf("top : %d\n", stack[top - 1]); 1074 | } 1075 | printf("%d", stack[0]); 1076 | return 0; 1077 | } 1078 | ``` 1079 | 1080 | ### 习题3 滑动窗口的最大值(牛客 中等) 1081 | 1082 | **思路:** 1083 | 1084 | > 本题利用一个队列维护这个窗口,队【0】出总是最大值,当滑到最新的元素时,判断队【0】是否还在窗口中,不在的话将其取出。然后判断最新的元素是否比其前面的大,如果比前面的大则将前面的取出(表示前面的已经没有用了),然后将这个最新的元素加入队,注意每次最新的都要加进去,即使不是最大也要加进去,因为有可能此时不是最大,但随着滑动窗口的滑动,前面比它大的滑出,然后它可能就是最大的了。最后将每个滑动窗口队[0]加如答案。 1085 | 1086 | **答案:** 1087 | 1088 | ```c 1089 | class Solution: 1090 | def maxInWindows(self, num, size): 1091 | ans=[] 1092 | q=[] 1093 | for i in range(len(num)): 1094 | if len(q)>0 and i-size==q[0]:#判断最大值是否还在滑动窗口中 1095 | q.pop(0) 1096 | while len(q)>0 and num[i]>=num[q[-1]]: 1097 | q.pop() 1098 | q.append(i) 1099 | if i>=size-1:#相等表示第一个滑动窗口 1100 | ans.append(num[q[0]]) 1101 | return ans 1102 | 1103 | ``` 1104 | 1105 | 1106 | 1107 | ### 习题4 时间复杂度(洛谷 中等) 1108 | 1109 | **思路:** 1110 | 1111 | > 我们要做的事情是对一组for循环进行时间复杂度的计算, 1112 | > 这些for循环是可以嵌套的,那么for循环的嵌套不就可以看成是一个**多叉树**嘛。 1113 | > 1114 | > 那么求时间复杂度 => 求多叉树的**最大深度**。 1115 | > 1116 | > 那么一个树的遍历就搞定了。 1117 | > 1118 | > 但是题目输入的是字符串,我们想要的树,所以先根据题目输入的字符串**构造一颗树**,与此同时进行语法判断。 1119 | > 1120 | > 构造树那么就需要一个**栈**来辅助,大致思路就是遍历字符串数组,根据是F 还是 E 来压栈,弹栈,构造树。 1121 | > 1122 | > 与此同时,为了保证可以识别,**新建的变量与已经存在但未被销毁的变量重复** 我们需要在每个树的节点维护一个集合,来保留当前作用域下出现的变量,同时有一个指向父节点的指针,来递归的判断当前这个变量是否在当前节点之前出现过。 1123 | 1124 | **答案:** 1125 | 1126 | ```c 1127 | #include 1128 | using namespace std; 1129 | int top=0,k=0,x=0; 1130 | int n,h,t,ans=0; 1131 | int f[1000001]={};//表示当前循环是否能进人或是否是继承上一层循环(-1位进入不了,0为继承,1为能够继续算) 1132 | bool fn[1000001]={};//表示当前的n^w是否进入 1133 | bool fx[200001]={};//表示当前变量是否用过 1134 | bool ff=0;//表示是否存在语法错误 1135 | char chtop[1000001];//记录栈顶的变量 1136 | string s,s1; 1137 | int main(){ 1138 | scanf("%d",&t); 1139 | while (t--){ 1140 | scanf("%d ",&n);//输入行数 1141 | top=0; 1142 | k=0; 1143 | ans=0; 1144 | ff=0; 1145 | x=0; 1146 | memset(f,0,sizeof(f)); 1147 | memset(fn,0,sizeof(fn)); 1148 | memset(fx,0,sizeof(fx));//清空 1149 | fn[100]=1; 1150 | getline(cin,s); 1151 | if (s[2]=='1') ans=0,fn[100]=1;//常数复杂度 1152 | else{ 1153 | int i=4; 1154 | for (;s[i]!=')';) 1155 | ans=ans*10+s[i]-'0',i++;//找出次方 1156 | } 1157 | // cout<<"ans:"<='0'&&s[x+1]<='9') ans2=ans2*10+s[x+1]-'0'; else; 1189 | // cout<<"x:"<ans2) 1191 | f[top+100]=-1;//如果后面比前面的小,不能进入循环 1192 | else f[top+100]=0;//两边都属于常数,继承 1193 | } 1194 | } 1195 | else{ 1196 | fx[chtop[top+100]-'a']=0;//变量销毁 1197 | if (f[top+100]==1) k--;else;//如果结束了能计算的循环,结束循环,少一层n 1198 | f[top+100]=0;//结束循环 1199 | top--; 1200 | } 1201 | } 1202 | if (ff||top!=0)printf("ERR\n");//如果存在语法错误或E与F不匹配 1203 | else if (fn[ans+100]==1&&fn[ans+101]==0)//如果有n^ans且这是最大时间复杂度 1204 | printf("Yes\n"); 1205 | else printf("No\n"); 1206 | } 1207 | return 0; 1208 | } 1209 | 1210 | ``` 1211 | 1212 | 1213 | 1214 | ### 习题5 双栈(牛客 简单) 1215 | 1216 | **思路:** 1217 | 1218 | > 本题用到一个栈,存放各部分的结果值(带符号的存放),,然后最后一次弹栈相加,为所要的结果。 1219 | > 注意,所给表达式中 具有括号的子表达式,需要递归求得结果,递归结束返回值number,接着执行下面的压栈。开始默认的运算符为 ‘+’,默认的数值为 0,当表达式以负数开头的时候,实际上第一次是压栈了一个 + 0 ,接着下次就是记录 负号 ,和数值。 1220 | 1221 | **答案:** 1222 | 1223 | ```c 1224 | 1225 | /** 1226 | * 返回表达式的值 1227 | * @param s string字符串 待计算的表达式 1228 | * @return int整型 1229 | */ 1230 | public int solve (String s) { 1231 | return solve(s, 0)[0]; 1232 | } 1233 | 1234 | /** 1235 | * 返回表达式不包含括号的值,以及遇到')'的坐标 1236 | * @param s 表达式 1237 | * @param index 开始的坐标 1238 | * @return int[] 1239 | */ 1240 | public int[] solve (String s, int index) { 1241 | //存储数字 1242 | Stack stack = new Stack<>(); 1243 | int num = 0; 1244 | char flag = '+'; 1245 | int i; 1246 | for (i = index; i < s.length(); i++) { 1247 | //如果检测到数字的话,转换成int数字 1248 | if(Character.isDigit(s.charAt(i))){ 1249 | num = num*10 + s.charAt(i) -'0'; 1250 | if(i!=s.length()-1){ 1251 | continue; 1252 | } 1253 | } 1254 | //碰到'('时,把整个括号内的当成一个数字处理 1255 | if(s.charAt(i) == '('){ 1256 | int[] result = solve(s, i+1); 1257 | num = result[0]; 1258 | i = result[1]; 1259 | if(i!=s.length()-1){ 1260 | continue; 1261 | } 1262 | } 1263 | 1264 | switch (flag) { 1265 | case '+': 1266 | stack.add(num); 1267 | break; 1268 | case '-': 1269 | stack.add(-num); 1270 | break; 1271 | case '*': 1272 | stack.add(stack.pop() * num); 1273 | break; 1274 | 1275 | default: 1276 | break; 1277 | } 1278 | num = 0; 1279 | if(s.charAt(i) == ')'){ 1280 | break; 1281 | }else { 1282 | flag = s.charAt(i); 1283 | } 1284 | } 1285 | int sum = 0; 1286 | while(!stack.empty()){ 1287 | sum+=stack.pop(); 1288 | } 1289 | return new int[]{sum,i}; 1290 | } 1291 | ``` 1292 | 1293 | 1294 | 1295 | ### 习题6 Sramoc 问题(洛谷 难) 1296 | 1297 | **思路:** 1298 | 1299 | > 在广搜时,队列里的每个元素由一个高精度的数(字符串)和那个数模m的值。拓展节点时,如果拓展得到的余数为零,直接返回输出,如果这个余数不为零且之前没有出现过,就加入队列,如果之前出现过,则舍弃。 1300 | 1301 | **答案:** 1302 | 1303 | ``` 1304 | 1305 | #include 1306 | #include 1307 | #include 1308 | #include 1309 | #include 1310 | using namespace std; 1311 | #define MAXN 1005 1312 | int K,M; 1313 | bool vis[MAXN]; 1314 | void bfs() 1315 | { 1316 | int i; 1317 | queue q; 1318 | queue > q2; 1319 | vector s; 1320 | for(i=1;i<=K-1;i++) //i表示的是余数,所以不能为0,为0就直接结束了(下面) 1321 | { 1322 | q.push(i%M); //这里取余是已经判断出传递对象了 1323 | vis[i%M]=true; 1324 | s.push_back(i); 1325 | q2.push(s); // 队列在这个循环中都是没出队列的 1326 | s.pop_back(); //但不定数组每次都要重置 ,不定数组存的是一个数字串 1327 | } //利用广搜处理暴力枚举k^a种情况a确定大小 1328 | long long u,k; 1329 | while(!q.empty()) //出队列才是广搜的开始 1330 | { 1331 | u=q.front(); 1332 | q.pop(); 1333 | s=q2.front(); 1334 | q2.pop(); 1335 | for(i=0;i<=K-1;i++) //枚举K,和暴力一致(K>=2&&K<=10,m>=0&&m<=1000) //以暴力为基础思想的还是用 long long比较保险 1336 | { //利用同余简化暴力枚举的次数,但枚举形式不变 1337 | k=u*(10%M)+i%M; //(x*k+y)mod m=((x mod m)*(k mod m)+(y mod m) ) mod m .//本题的k为10 1338 | // ((x*k+y)*k+i) mod m==(((x*k+y) mod m)*(k mod m)+i mod m) mod m==上述,所以可以迭代 1339 | s.push_back(i); // 所以每次 u取(x*k+y) mod m,也就是k%M 1340 | if(k%M==0) //用同余就考虑两种对比情况,找到传递的对象 1341 | { 1342 | for(unsigned int j=0;j>K>>M; 1384 | bfs(); 1385 | return 0; 1386 | } 1387 | 1388 | ``` 1389 | 1390 | -------------------------------------------------------------------------------- /docs/ch04/images/04-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-01.png -------------------------------------------------------------------------------- /docs/ch04/images/04-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-02.png -------------------------------------------------------------------------------- /docs/ch04/images/04-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-03.png -------------------------------------------------------------------------------- /docs/ch04/images/04-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-04.png -------------------------------------------------------------------------------- /docs/ch04/images/04-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-05.png -------------------------------------------------------------------------------- /docs/ch04/images/04-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-06.png -------------------------------------------------------------------------------- /docs/ch04/images/04-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-07.png -------------------------------------------------------------------------------- /docs/ch04/images/04-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-08.png -------------------------------------------------------------------------------- /docs/ch04/images/04-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-09.png -------------------------------------------------------------------------------- /docs/ch04/images/04-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-10.png -------------------------------------------------------------------------------- /docs/ch04/images/04-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-11.png -------------------------------------------------------------------------------- /docs/ch04/images/04-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-12.png -------------------------------------------------------------------------------- /docs/ch04/images/04-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-13.png -------------------------------------------------------------------------------- /docs/ch04/images/04-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-14.png -------------------------------------------------------------------------------- /docs/ch04/images/04-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch04/images/04-15.png -------------------------------------------------------------------------------- /docs/ch05/ch05.md: -------------------------------------------------------------------------------- 1 | # 第五章 串 2 | 3 | ## 5.1 串的定义 4 | 5 | - 串:英文称 `string`,又叫字符串,是由零个或多个字符组成的有限序列。 6 | - 串的长度:串中的字符数目 7 | - 空串:零个字符的串,它的长度为0,可以直接用双引号""表示 8 | - 子串与主串:串中任意个数的连续字符组成的子序列称为该串的子串,相应地包含子串的串称为主串。 9 | 10 | ```python 11 | str1 = "123" # 字符串,长度为3 12 | str2 = "" # 空串,长度为0 13 | str3 = "hello" # 子串 14 | str3 = "hello world" # 主串 15 | ``` 16 | 17 | ## 5.2 串的比较 18 | 19 | - 比较原理:串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。 20 | - 比较规则:首先比较两个字符串中的第一个字符,如果相等则继续比较下一个字符,依次比较下去,直到两个字符串中的字符不相等时,其比较结果就是两个字符串的比较结果,两个字符串中的所有后续字符将不再被比较 21 | - 比较操作运算符:>,>=,<,<=,==,!= 22 | 23 | ```python 24 | print("hello" > "hel") # True 25 | print("hello" > "interest") # False 26 | print(ord("h"), ord("i")) # 104 < 145 调用内置函数ord可以得到指定字符的ASCII码 27 | print(ord("G") < ord("g")) # True G:71, g:103 所以:G 试想:由于树中每个结点可能有多棵子树,可以考虑用多重链表,即每个结点有多个指针域,其中每个指针指向棵一子树的根结点,把这种方法叫做多重链表表示法。不过,树的每个结点的度,也就是它的孩子个数是不同的,所以可以设计两种方案来解决。 82 | 83 | - **方案一** 84 | 85 |   指针域的个数就等于树的度 86 | 87 | ![image](images\06-03.png) 88 | 89 |   其中`data`是数据域,`child1~childd`是指针域,用来指向该结点的孩子结点。 90 | 91 | ![image](images\06-04.png) 92 | 93 |   这种方法对于树中各结点的度相差很大时,显然是很浪费空间的,因为有很多的结点的指针域都是空的。不过如果树的各结点度相差很小时,那就意昧着开辟的空间被充分利用了,这时,存储结构的缺点反而变成了优点。 94 | 95 |   此种存储结构声明如下: 96 | 97 | ```c++ 98 | typedef struct node 99 | { 100 | ElemType data; 101 | struct node *sons[Dgree]; 102 | }CTree 103 | ``` 104 | 105 | - **方案二** 106 | 107 |   每个结点指针域的个数等于该结点的度,可以专门取一个位置来存储结点指针域的个数,其结构如下表所示。 108 | 109 | ![image](images\06-05.png) 110 | 111 |   其中,`data`为数据域,`degree`为度域,也就是存储该结点的孩子结点的个数; chjldl~childd为指针域,指向该结点的各个孩子的结点。 112 | 113 | ![image](images\06-06.png) 114 | 115 |   这种方法克服了浪费空间的缺点,对空间利用率是很高了,但是由于各个结点的链 表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。 116 | 117 |   我们为了要遍历整棵树,把每个结点放到一个顺序存储结构的数组中是合理的,但每个结点的孩子有多少是不确定的,所以我们再对每个结点的孩子建立一个单链表体现它们的关系。 118 | 119 |   具体办法是,把每个结点的孩子结点排列起来,以单链表作存储结构,则 $n$ 个结点有 $n$ 个孩子链表,如果是叶子结点,则此单链表为空。然后 $n$ 个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中,如下图所示: 120 | 121 | ![image](images\06-07.png) 122 | 123 |   为此,设计两种结点结构 124 | 125 | - 孩子链表的孩子结点,如下表所示 126 | 127 | ![image](images\06-08.png) 128 | 129 |   其中`child`是数据域,用来存储某个结点在表头数组中的下标;`next`是指针域,用来存储指向某结点的下一个孩子结点的指针。 130 | 131 | - 表头数组的表头结点,如下表所示 132 | 133 | ![image](images\06-09.png) 134 | 135 |   其中,`data`是数据域,存储某结点的数据信息;`firstchild`是头指针域,存储该结点的孩子链表的头指针。 136 | 137 |   结构定义代码如下: 138 | 139 | ```c++ 140 | #define MAX_TREE_SIZE 100 141 | typedef struct CTNode{ 142 | int child; //结点在数组的下标 143 | CTNode *next; //孩子链表 144 | } *ChildPtr; 145 | 146 | typedef struct { /*表头结构*/ 147 | ElemType data; //结点的值 148 | ChildPtr firstchild; //该结点的孩子 149 | } CTbox; 150 | 151 | typedef struct{ /*树结构*/ 152 | CTbox nodes[MAX_TREE_SIZE]; 153 | int n; //结点数 154 | int r; //根的位置 155 | } CTree 156 | ``` 157 | 158 |   上述结构,对于要查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。对于遍历整棵树也是很方便的,只需对头结点的数组循环即可。但是,这也存在问题,如果需要获取结点的双亲,需要遍历整个树。 159 | 160 | ### 6.3.3 孩子兄弟表示法 161 | 162 |   可以观察到,任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。如下图所示: 163 | 164 | ![image](images\06-10.png) 165 | 166 |   其中`data`是数据域;`firstchild`为指针域,存储该结点的第一个孩子结点的存储地址; `rightsib`是指针域,存储该结点的右兄弟结点的存储地址。 167 | 168 |   结构定义代码如下: 169 | 170 | ```c++ 171 | #define ElemType char 172 | typedef struct CSNode{ 173 | ElemType data; 174 | CSNode * firstchild,*rightsib; 175 | }CSNode,*CSTree; 176 | ``` 177 | 178 |   如图所示: 179 | 180 | ![image](images\06-11.png) 181 | 182 |   这种表示法,给查找某个结点的某个孩子带来了方便,只需要通过`firstchild`找到此结点的长子,然后再通过长子结点的`rightsib`找到它的二弟,接着一直下去,直到找到具体的孩子。 183 | 184 | ## 6.4二叉树 185 | 186 | ### 6.4.1二叉树的定义 187 | 188 |   **二叉树**(binary tree)是$n(n > 0)$ 个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根结点和两棵互不相交、分别称为根结点的左子树和右子树的二叉树组成。 189 | 190 |   二叉树的递归定义为:二叉树是一棵**空树**,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的**非空树**;左子树和右子树又同样都是二叉树。 191 | 192 | ### 6.4.2 二叉树的特点 193 | 194 | - 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。注意不是只有两棵子树,而是最多有,没有子树或者有一棵子树都是可以的。 195 | - 左子树和右子树是有顺序的,次序不能任意颠倒。 196 | - 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。 197 | 198 | ### 6.4.3 二叉树的基本形态 199 | 200 | - 空二叉树 201 | - 只有一个根结点 202 | - 结点只有左子树 203 | - 根结点只有右子树 204 | - 根结点既有左子树又有右子树 205 | 206 | ![image](images\06-12.png) 207 | 208 | ### 6.4.4 特殊二叉树 209 | 210 | - 满二叉树 211 | 212 |   在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并目所有叶子都在同 一层上,这样的二叉树称为满二叉树。 213 | 214 | ![image](.\images\06-13.png) 215 | 216 | - 完全二叉树 217 | 218 |   对一棵具有 $n$ 个结点的二叉树按层序编号,如果编号为$i(1 \leqslant i \leqslant n)$ 的结点与同样深度的满二叉树中编号为 $i$ 的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。 219 | 220 | ![image](.\images\06-14.png) 221 | 222 |   可以理解为:除了最下层,其他层都饱满,最下层的结点都集中于该层最左边的若干位置上。 223 | 224 | **完全二叉树的特点:** 225 | 1. 叶子结点只能出现在最下两层。 226 | 2. 最下层的叶子一定集中在左部连续位置。 227 | 3. 倒数二层,若有叶子结点,一定都在右部连续位置。 228 | 4. 如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况。 229 | 5. 同样结点数的二叉树,完全二叉树的深度最小。 230 | 231 | - 斜树 232 | 233 |   所有的结点都只有左子树的二叉树叫左斜树,所有结点都是只有右子树的二叉树叫右斜树,这两者统称为斜树。斜树的明显的特点,就是每一层都只有一个结点,结点的个数与二叉树的[深度](https://so.csdn.net/so/search?q=深度&spm=1001.2101.3001.7020)相同。 234 | 235 | ### 6.4.5 二叉树的性质 236 | 237 | 1. 在二叉树的第 $i$ 层上至多有 $2^i - 1$ 个结点($i \geqslant 1$)。 238 | 2. 深度为 $k$ 的二叉树至多有 $2^k - 1$ 个结点($k \geqslant 1$)。 239 | 3. 对任何一棵二叉树 T,如果其终端结点数为 $n_0$,度为2的结点数为 $n_2$,则 $n_0 = n_2 + 1$。 240 | 4. 具有 $n$ 个结点的完全二叉树的深度为 $\lfloor \log_2n \rfloor + 1$。($\lfloor x \rfloor$ 表示不大于$x$的最大整数, $\lfloor \log_2n \rfloor$ 表示取小于 $\log_2n$ 的最大整数)。 241 | 5. 如果对一棵有 $n$ 个结点的完全二叉树(其深度为 $\lfloor \log_2n \rfloor + 1$ 的结点按层序编号(从第1层到第 $\lfloor \log_2n \rfloor + 1$ 层,每层从左到右),对任一结点$i (1 \leqslant i \leqslant n)$有: 242 | - 如果 $i=1$,则结点 $i$ 是二叉树的根,无双亲;如果 $i>1$,则其双亲是结点 $i/2$。 243 | - 如果 $2 i > n$,则结点 $i$ 无左孩子(结点 $i$ 为叶子结点);否则其左孩子是结点 $2i$。 244 | - 如果 $2 i + 1> n$,则结点 $i$ 无右孩子;否则其右孩子是结点 $2i+1$。 245 | 246 | ## 6.5 二叉树的存储结构 247 | 248 | ### 6.5.1 二叉树的顺序存储 249 | 250 |   二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标可以体现出结点之间的逻辑关系。 251 | 252 | ![image](images\06-15.png) 253 | 254 | 代码实现: 255 | 256 | ```c++ 257 | typedef ElemType SqBinTree[MaxSize]; 258 | ``` 259 | 260 | ### 6.5.2 二叉链表 261 | 262 |   二叉树每个结点最多有 2 个孩子,所以为它设计一个数据域和 2 个指针域是比较自然的想法,称这样的链表叫做二叉链表。 263 | 264 | ![image](images\06-16.png) 265 | 266 |   其中`data`是数据域;`lchild`和`rchild`都是指针域,分别存放指向左孩子和右孩子的指针。 267 | 268 |   结点结构定义代码 269 | 270 | ```c++ 271 | struct BiNode{ 272 | ElemType data; // 数据域,存放该结点的信息 273 | BiNode *lchild; // 左指针域,存放指向左孩子的指针,当左孩子不存在时为空 274 | BiNode *rchild; // 右指针域,存放指向右孩子的指针,当右孩子不存在时为空 275 | }BiNode; 276 | ``` 277 | 278 | ![image](images\06-17.png) 279 | 280 | ## 6.6 遍历二叉树 281 | 282 |   二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种**次序**依次**访问**二叉树中所有的结点,使得每个结点被访问依次且仅被访问一次。(即深度优先搜索) 283 | 284 | ### 6.6.1 二叉树的遍历方法 285 | 286 | #### 6.6.1.1 前序遍历 287 | 288 |   前序遍历规则:若二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树。如图所示的二叉树遍历的顺序为ABDGHCEIF。 289 | 290 | ![image](images\06-18.png) 291 | 292 | ```c++ 293 | void PreOrder(BiNode* t) 294 | { 295 | if (t != NULL) 296 | { 297 | cout<data<left); 299 | PreOrder(t->right); 300 | } 301 | } 302 | ``` 303 | 304 | #### 6.6.1.2 中序遍历 305 | 306 |   中序遍历的规则是若树为空,则空操作返回,否则从根结点开始中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。如图所示的二叉树遍历的顺序为:GDHBAEICF。 307 | 308 | ![image](images\06-19.png) 309 | 310 | ```c++ 311 | void inOrder(tnode* t) 312 | { 313 | if (t != NULL) 314 | { 315 | inOrder(t->left); 316 | cout<data<right); 318 | } 319 | } 320 | ``` 321 | 322 | #### 6.6.1.3 后序遍历 323 | 324 |   后序遍历的规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点。如图所示的二叉树遍历的顺序为:GHDBIEFCA。 325 | 326 | ![image](images\06-20.png) 327 | 328 | ```c++ 329 | void PostOrder(tnode* t) 330 | { 331 | if (t != NULL) 332 | { 333 | PostOrder(t->left); 334 | PostOrder(t->right); 335 | cout<data< q; 350 | if (t != NULL) 351 | { 352 | q.push(t); //根节点进队列 353 | } 354 | 355 | while (q.empty() == false) //队列不为空判断 356 | { 357 | cout << q.front()->data << " → "; 358 | 359 | if (q.front()->left != NULL) //如果有左孩子,leftChild入队列 360 | { 361 | q.push(q.front()->left); 362 | } 363 | 364 | if (q.front()->right != NULL) //如果有右孩子,rightChild入队列 365 | { 366 | q.push(q.front()->right); 367 | } 368 | q.pop(); //已经遍历过的节点出队列 369 | } 370 | } 371 | ``` 372 | 373 | ### 6.6.2 线索二叉树 374 | 375 |   我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)。 376 | 377 | #### 6.6.2.1 原理 378 | 379 |   遍历二叉树的其实就是以一定规则将二叉树中的结点排列成一个线性序列,得到二叉树中结点的先序序列、中序序列或后序序列。这些线性序列中的每一个元素都有且仅有一个前驱结点和后继结点。 380 | 381 |   但是,当我们希望得到二叉树中某一个结点的前驱或者后继结点时,普通的二叉树是无法直接得到的,只能通过遍历一次二叉树得到。每当涉及到求解前驱或者后继就需要将二叉树遍历一次,非常不方便。于是,是否能够改变原有的结构,将结点的前驱和后继的信息存储进来。 382 | 383 | ![image](images\06-22.png) 384 | 385 |   图中,将这棵二叉树的所有空指针域中的`lchlld`改为指向当前结点的前驱。因此`H`的前驱是`NULL`(图中①),`I`的前驱是`D`(图中②),`J`的前驱是`B`(图中③),`F`的前驱是`A`(图中④),`G`的前驱是`C`(图中⑤)。一共5个空指针域被利用,正好和上面的后继加起来是11个。 386 | 387 | ![image](images\06-23.png) 388 | 389 | 其中: 390 | 391 | - `ltag`为0时,`lchild`指向该结点的左孩子,为1时,指向该结点的前驱。 392 | - `rtag`为0时,`rchild`指向该结点的右孩子,为1时,指向该结点的后继。 393 | 394 | #### 6.6.2.2 实现 395 | 396 | 二叉线索树存储结构定义如下: 397 | 398 | ```c++ 399 | struct BiTrNode{ 400 | ElemType data; //结点数据 401 | int ltag,rtag; //左右标志 402 | BiTrNode* lchild; //孩子指针 403 | BiTrNode* rchild; 404 | }TBTNode; 405 | ``` 406 | 407 |   线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。由于前驱和后继信息只有在遍历该二叉树时才能得到,所以,线索化的过程就是在遍历的过程中修改空指针的过程。 408 | 409 | - 中序遍历线索化的递归函数代码: 410 | 411 | ```c++ 412 | TBTNode* pre; 413 | void InThred(ThBiTreeNode* &p){ 414 | if(T){ 415 | InThred(p->lchild); 416 | if(!(p->lchild)) 417 | { 418 | p->lchild = pre; 419 | p->ltag = 1; 420 | } 421 | else 422 | p->lrag = 0; 423 | if(!(pre->rchild)){ 424 | pre -> rchild = p; 425 | pre -> rtag = 1; 426 | } 427 | else 428 | pre->rtag = 0; 429 | pre = p; 430 | InThred(p->rchild); 431 | } 432 | } 433 | ``` 434 | 435 | - 中序线索化二叉树数代码: 436 | 437 | ```c++ 438 | TBTNode *CreatThread(TBTNode *b) 439 | { 440 | TBTNode *root; 441 | root=(TBTNode *)malloc(sizeof(TBTNode)); 442 | root->ltag=0; root->rtag=1; root->rchild=b; 443 | if (b==NULL) 444 | root->lchild=root; 445 | else 446 | { 447 | root->lchild=b; 448 | pre=root; 449 | Thread(b); 450 | pre->rchild=root; 451 | pre->rtag=1; 452 | root->rchild=pre; 453 | } 454 | return root; 455 | } 456 | ``` 457 | 458 | - 遍历的代码: 459 | 460 | ```c++ 461 | void InOrderTraverse_Thr(TBTNode* T) 462 | { 463 | 464 | TBTNode* temp = T->lchild; 465 | while(temp != T){ 466 | while(temp->ltag == 0){ 467 | temp = temp->lchild; 468 | cout<< temp->data << endl; 469 | while( temp-> rtag == 1 && p -> rchild != T) 470 | { 471 | temp = temp-> rchild; 472 | cout << temp -> rchild; 473 | } 474 | temp = temp -> rchild 475 | } 476 | 477 | } 478 | ``` 479 | 480 | ## 6.7 树、森林与二叉树的转换 481 | 482 | ### 6.7.1 树转换为二叉树 483 | 484 | 1. 加线:在所有兄弟结点之间加一条连线,如下图所示: 485 | 486 | ![image](images\06-24.png) 487 | 488 | 2. 去线:对树中每个结点,只保留它与第—个孩子结点的连线,删除它与其他孩子结点之间的连线,如下图所示: 489 | 490 | ![image](images\06-25.png) 491 | 492 | 3. 层次调整:以树的根结点为轴心,将整棵树顺时针旋转—定的角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子,如下图所示: 493 | 494 | ![image](images\06-26.png) 495 | 496 | ### 6.7.2 森林转换为二叉树 497 | 498 | ![image](images\06-27.png) 499 | 500 | 1. 首先将森林中所有的普通树各自转化为二叉树; 501 | 502 | ![image](images\06-28.png) 503 | 504 | 2. 将森林中第一棵树的树根作为整个森林的树根,其他树的根节点看作是第一棵树根节点的兄弟节点,采用孩子兄弟表示法将所有树进行连接。 505 | 506 | ![image](images\06-29.png) 507 | 508 | ### 6.7.3 二叉树转换为树 509 | 510 | 1. 加线:在兄弟之间加一连线。 511 | 2. 去线:对每个结点,除了其左孩子外,抹掉其与其余孩子之间的连线。 512 | 3. 层次调整(旋转):使之结构层次分明。 513 | 514 | ![image](images\06-30.png) 515 | 516 | ### 6.7.4 二叉树转换为森林 517 | 518 | ![image](images\06-31.png) 519 | 520 | 1. 从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,如下图所示: 521 | 522 | ![image](images\06-32.png) 523 | 524 | 2. 再查看分离后的二叉树,若右孩子存在,则连线删除......,直到所有右孩子连线都删除为止,得到分离的二叉树。 525 | 526 | ![image](images\06-33.png) 527 | 528 | 3. 再将每棵分离后的二叉树转换为树。 529 | 530 | ![image](images\06-34.png) 531 | 532 | ## 6.8 森林的遍历 533 | 534 |   只有二叉树森林才有中序遍历与完整二叉树中序遍历对应 535 | 536 | - 前序遍历 537 | 538 |   若森林为非空,则先访问森林中第一棵树的根结点;然后前序遍历第一棵树中根结点的子树森林;再依次通过前序遍历除去第一课树之后剩余的树构成的森林。 539 | 其访问顺序与**森林相应二叉树的先序遍历**顺序相同。 540 | 541 | - 后序遍历 542 | 543 |   先访问森林中第—棵树,后根遍历的方式遍历每棵子树,然后再访问根结点,再依次用同样方式遍历除去第一棵树的剩余树构成的森林。 544 | 545 | ## 6.9 习题 546 | 547 | ### 习题1 548 | 计算一棵给定二叉树的所有双分支结点 549 | 550 | ### 习题2 551 | 设树B是一棵采用链式结构存储的二叉树,编写一个把树B中所有结点的左、右子树进行交换的函数。 552 | 553 | ### 习题3 554 | 已知二叉树以二叉链表存储,编写算法完成;对于树中每个元素值为x的结点,删去以它为根的子树,并释放相应的空间。 555 | 556 | ### 习题4 557 | 给你一个二叉树的根结点 `root` ,请返回出现次数最多的子树元素和。如果有多个元素出现的次数相同,返回所有出现次数最多的子树元素和(不限顺序)。 558 | 559 | 560 | ### 习题解答 561 | 562 | #### 习题1解答 563 | 564 | ```c++ 565 | /* 566 | 递归表达式: 567 | f(b)=0; //若b=null 568 | f(b)=f(b->lchild)+f(b->rchild)+1; //若*b为双分支结点 569 | f(b)=f(b->lchild)+f(b->rchild); //其它情况 570 | */ 571 | 572 | int DsonNodes(BiTree b) 573 | { 574 | if(b==NULL) 575 | return 0; 576 | else if(b->lchild!=NULL&&b->rchild!=NULL) 577 | return DsonNodes(b->lchild)+DsonNodes(b->rchild)+1; 578 | else 579 | return DsonNodes(b->lchild)+DsonNodes(b->rchild); 580 | } 581 | ``` 582 | 583 | #### 习题2解答 584 | 585 | ```c++ 586 | /* 587 | 算法思想:采用递归的方式,本质就是后序遍历 588 | */ 589 | 590 | void swap(BiTree b) 591 | { 592 | swap(b->lchild); 593 | swap(b->rchild); 594 | temp=b->lchild; 595 | b->lchild=b->rchild; 596 | b->rchild=temp; 597 | } 598 | ``` 599 | 600 | #### 习题3解答 601 | 602 | ```c++ 603 | /* 604 | 算法思想:删除以x为值的根结点,用后序遍历。算法思想:删除值为x的结点,意味着应将其父结点的左(右)子女指针置空,用层次遍历易于找到某结点的父结点。 605 | */ 606 | 607 | void DeleteXTree(BiTree bt) //删除以bt为根的结点 608 | { 609 | if(bt) 610 | { 611 | DeleteXTree(bt->lchild); 612 | DeleteXTree(bt->rchild); 613 | free(bt); 614 | } 615 | } 616 | 617 | //在二叉树上查找所有以x为元素值的结点,并删除以其为根的子树 618 | 619 | void Search(BiTree bt,ElemType x) 620 | { 621 | BiTree Q[]; 622 | if(bt) 623 | { 624 | if(bt->data==x) 625 | { 626 | DeletXTree(bt); 627 | exit(0); 628 | } 629 | InitQueue(Q); 630 | EnQueue(Q,bt); 631 | 632 | while(!IsEmpty(Q)) 633 | { 634 | DeQueue(Q,p); 635 | if(p->lchild) 636 | if(p->lchld->data==x) 637 | { 638 | DeleteXTree(p->lchild); 639 | p->lchild=NULL; 640 | } 641 | else 642 | EnQueue(p->lchild); 643 | 644 | if(p->rchild) 645 | if(p->rchld->data==x) 646 | { 647 | DeleteXTree(p->rchild); 648 | p->rchild=NULL; 649 | } 650 | else 651 | EnQueue(p->rchild); 652 | } 653 | } 654 | } 655 | ``` 656 | 657 | #### 习题4解答 658 | 659 | ```c++ 660 | /** 661 | * Definition for a binary tree node. 662 | * struct TreeNode { 663 | * int val; 664 | * TreeNode *left; 665 | * TreeNode *right; 666 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 667 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 668 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 669 | * }; 670 | */ 671 | class Solution { 672 | public: 673 | unordered_map cnt; 674 | int cntMax = 0; 675 | int sum(TreeNode* curNode) { 676 | if(curNode == NULL) return 0; 677 | 678 | 679 | cout << curNode->val << endl; 680 | 681 | int curSum = curNode->val + sum(curNode->left) + sum(curNode->right); 682 | 683 | cnt[curSum]++; 684 | cntMax = max(cntMax, cnt[curSum]); 685 | return curSum; 686 | } 687 | vector findFrequentTreeSum(TreeNode* root) { 688 | sum(root); 689 | // 求解ans 690 | vector ans; 691 | for(pair x : cnt) { 692 | if(x.second == cntMax) ans.emplace_back(x.first); 693 | } 694 | return ans; 695 | } 696 | }; 697 | ``` 698 | 699 | -------------------------------------------------------------------------------- /docs/ch06/images/06-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-01.png -------------------------------------------------------------------------------- /docs/ch06/images/06-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-02.png -------------------------------------------------------------------------------- /docs/ch06/images/06-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-03.png -------------------------------------------------------------------------------- /docs/ch06/images/06-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-04.png -------------------------------------------------------------------------------- /docs/ch06/images/06-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-05.png -------------------------------------------------------------------------------- /docs/ch06/images/06-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-06.png -------------------------------------------------------------------------------- /docs/ch06/images/06-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-07.png -------------------------------------------------------------------------------- /docs/ch06/images/06-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-08.png -------------------------------------------------------------------------------- /docs/ch06/images/06-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-09.png -------------------------------------------------------------------------------- /docs/ch06/images/06-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-10.png -------------------------------------------------------------------------------- /docs/ch06/images/06-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-11.png -------------------------------------------------------------------------------- /docs/ch06/images/06-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-12.png -------------------------------------------------------------------------------- /docs/ch06/images/06-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-13.png -------------------------------------------------------------------------------- /docs/ch06/images/06-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-14.png -------------------------------------------------------------------------------- /docs/ch06/images/06-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-15.png -------------------------------------------------------------------------------- /docs/ch06/images/06-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-16.png -------------------------------------------------------------------------------- /docs/ch06/images/06-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-17.png -------------------------------------------------------------------------------- /docs/ch06/images/06-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-18.png -------------------------------------------------------------------------------- /docs/ch06/images/06-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-19.png -------------------------------------------------------------------------------- /docs/ch06/images/06-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-20.png -------------------------------------------------------------------------------- /docs/ch06/images/06-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-21.png -------------------------------------------------------------------------------- /docs/ch06/images/06-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-22.png -------------------------------------------------------------------------------- /docs/ch06/images/06-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-23.png -------------------------------------------------------------------------------- /docs/ch06/images/06-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-24.png -------------------------------------------------------------------------------- /docs/ch06/images/06-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-25.png -------------------------------------------------------------------------------- /docs/ch06/images/06-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-26.png -------------------------------------------------------------------------------- /docs/ch06/images/06-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-27.png -------------------------------------------------------------------------------- /docs/ch06/images/06-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-28.png -------------------------------------------------------------------------------- /docs/ch06/images/06-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-29.png -------------------------------------------------------------------------------- /docs/ch06/images/06-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-30.png -------------------------------------------------------------------------------- /docs/ch06/images/06-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-31.png -------------------------------------------------------------------------------- /docs/ch06/images/06-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-32.png -------------------------------------------------------------------------------- /docs/ch06/images/06-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-33.png -------------------------------------------------------------------------------- /docs/ch06/images/06-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch06/images/06-34.png -------------------------------------------------------------------------------- /docs/ch07/ch07.md: -------------------------------------------------------------------------------- 1 | # 第七章 图 2 | 3 | ![image](images/07-01.png) 4 | 5 | ## 7.1 图的基本概念 6 | 7 |   图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成的。通常表示为 `G(V,E)`,其中,`G`表示—个图,`V`是图 `G`中**顶点的集合**,`E`是图`G`中**边的集合**。**图是多对多的逻辑结构。** 8 | 9 |   通过以下示例理解图的基本概念: 10 | - 假设 ABCDEFG 是 7 个电话,之间的连线表示有通信线路 11 | - **电话**就是**图的顶点**,**通信线路**是**边**,整体就是一个图 12 | - 任意两个电话间有线路,就可以相互通话(无向图) 13 | - 电话(顶点)连接的线路**(边)的数量**就是**度** 14 | - `ABCD`和`GF`之间消息**无法传递**,就是**不连通** 15 | - `ABCD`和`GF`是两个 **连通分量** 16 | 17 | ![image](images/07-02.png) 18 | 19 | - 假设 `ABCDE`是五个电话,之间的连线表示修有通信线路有,数字表示该线路的电话费 20 | - 不同通信线路上的电话费不同:**加权图 ** 21 | 22 | ![image](images/07-03.png) 23 | 24 | - 假设`ABCDE`是五个城市,带箭头连线表示该方向上有航班飞行 25 | - 例如航班`A->D`只能支持`A`飞往`D`,边是单向的(**有向图**) 26 | - 飞来某地的航班数量:**入度** 27 | - 从某地起飞的航班数量:**出度** 28 | 29 | ![image](images/07-04.png) 30 | 31 |   图并非只能表示地理数据,只要数据元素间满足多对多关系即可。为了更好的理解图,大家可以尝试回答以下思考题: 32 | 33 | 1. 几个学生之间的朋友关系用什么图表示? 34 | 2. 社交媒体的关注/粉丝关系用什么图表示? 35 | 3. 多个耗时不同的任务之间的依赖关系用什么图表示? 36 | 37 | **解答:** 38 | 39 | 1. 无向有权图 40 | 2. 有向无权图 41 | 3. 有向加权图 42 | 43 | **相关知识点:** 44 | 45 | - 无向和有向的区别在于,关系上是双向还是单向,如果所有路径都是双向的,则为无向图,有单向的路径则为有向图。 46 | - 无权和加权的区别在于,是否有消耗或者是费用等附加量,有则为加权图,没有则为无权图。 47 | - 在一个**无向图**中,所有顶点的**度数之和**为**边数量的 2 倍**,边数最多为 $\text{结点数} \times (\text{结点数}-1) / 2$。 48 | - 在一个**有向图**中,所有顶点的**出度之和**为所有顶点的**入度之和**。 49 | 50 | ## 7.2 邻接矩阵和邻接表 51 | 52 | ### 7.2.1 邻接矩阵 53 | 54 |   图可用一个 $n \times n$ 方阵表示,即一个二维数组`AdjMat[n][n]`,被称为邻接矩阵,其中`AdjMat[i][j]`表示 $v_i$ 到 $v_j$ 的邻接情况。 55 | 56 | **无向无权图:** 57 | 58 | ![image](images/07-05.png) 59 | 60 | **有向加权图:** 61 | 62 | ![image](images/07-06.png) 63 | 64 | ### 7.2.2 邻接表 65 | 66 |   每个顶点用一个链表存下自己的邻居,有 $n$ 个链表,即图可用一个链表的数组`Adjlist[n]`存储,该数组被称为邻接表。其中,`Adjlist[i]`表示顶点 $v_i$ 的链表(头结点),从`Adjlist[i]`开始遍历所有 $v_i$ 的邻居。 67 | 68 | ![image](images/07-07.png) 69 | 70 | ### 7.2.3 邻接矩阵和邻接表的比较 71 | 72 |   **邻接矩阵**无论如何**都需要一个二维数组** `[n][n]`表示,而**邻接表**中每条链表长度**取决于它有多少邻居(即边越少,占空间越小)。** 73 | 74 |   **邻接矩阵**访问`AdjMat[i][j]`的复杂度为 $O(1)$,但邻接表访问特定边需要顺着起点的**链表向后查找**,算法复杂度为 $O(N)$。 75 | 76 | - **邻接表的优点**:在边较少时节省许多空间,使用于**稀疏图**(适用于边少的情况)。 77 | - **邻接表的缺点**:无法直接获取某条边的信息,需要 $v_i$ 链表进行从头顺序存取,最坏情况下为 $O(N)$。 78 | 79 | ## 7.3 图的 DFS 和 BFS 遍历 80 | 81 | ### 7.3.1 DFS:深度优先遍历 82 | 83 |   深度优先遍历使用**递归/栈**实现,从起点开始,优先遍历除了自己以外的结点中所有未访问过的结点。 84 | 85 | ![image](images/07-08.png) 86 | 87 |   大家可以尝试回答以下问题:给定如下邻接矩阵,写出由 B 点出发的一个 DFS 序列。 88 | 89 | ![image](images/07-09.png) 90 | 91 | **解答:** 92 | 93 | ![image](images/07-10.png)​ 94 | 95 | ### 7.3.2 BFS:广度优先遍历 96 | 97 |   广度优先遍历也称为**层序遍历**,使用**队列**实现, 98 | 99 | ![image](images/07-11.png) 100 | 101 | ![image](images/07-12.png) 102 | 103 |   大家可以尝试回答以下问题:给定如下邻接矩阵,写出由 $v_0$ 出发的深度优先遍历结果为(),广度优先遍历结果为()。 104 | 105 | ![image](images/07-13.png) 106 | 107 | **解答:** 108 | 109 | 1. 0123 110 | 2. 0123 111 | 112 | ## 7.4 最小生成树 113 | 114 | ### 7.4.1 生成树 115 | 116 |   对于含 $n$ 个结点的一个无向连通图,其边数最多为 $n(n-1) / 2$ 条,最少为 $n-1$ 条。保持连通性的情况下,选 $n-1$ 条边出来,剔除其他边(去闭环),它就变成了一棵树。生成树中是没有环的。生成树的公式如下: 117 | $$ 118 | n 个结点 + n-1 条边 + 连通 = 生成树 119 | $$ 120 | 121 | ![image](images/07-14.png) 122 | 123 | ### 7.4.2 最小生成树 MST 124 | 125 |   在加权图中选出 $n-1$ 条边来构成生成树,使得这个生成树的边的权值之和最小,则称这个生成树为最小生成树(MST)。MST 不一定唯一(如最小权值相同的生成树有多个)。如下图所示: 126 | 127 | ![image](images/07-15.png) 128 | 129 | ![image](images/07-16.png) 130 | 131 |   大家可以尝试回答以下问题:MST 的意义是什么? 132 | 133 | **解答:** 134 | 135 |   求在多个解决方案中,消耗或者是费用最小的解决方案。例如:城市造路,求花费最小的造路方案。 136 | 137 | ### 7.4.3 求最小生成树:Prim 算法 138 | 139 |   Prim 算法也称为**加点法**。构造最小生成树可以使用以下策略:贪心的思想。每次在连接**已完成结点和未完成结点的边中**,选一条权值最小的,重复 $n-1$ 遍。局部最优使得全局最优。每次加入结点时必须满足:这个点所属边的权值最小,加点不会形成环。 140 | 141 |   大家可以尝试回答以下问题:给定如下图结构,求其最小生成树。 142 | 143 | ![image](images/07-17.png) 144 | 145 | **解答:** 146 | 147 | ![image](images/07-18.png) 148 | 149 | ## 7.5 Dijkstra算法 150 | 151 |   求单源点最短路径算法是 Dijkstra 算法,该算法的主要思想是在加权图中,求从一个顶点`s`出发到其他各点的最短距离,算法循环 $n-1$ 次,每次执行以下操作: 152 | 153 | 1. 找到未完成结点中,`s->t` 距离最短的`t`,将`t`标注未已完成 154 | 2. 以`t`为中转更新`s`至`t`的邻居们的距离 155 | 156 | ![image](images/07-19.png) 157 | -------------------------------------------------------------------------------- /docs/ch07/images/07-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-01.png -------------------------------------------------------------------------------- /docs/ch07/images/07-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-02.png -------------------------------------------------------------------------------- /docs/ch07/images/07-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-03.png -------------------------------------------------------------------------------- /docs/ch07/images/07-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-04.png -------------------------------------------------------------------------------- /docs/ch07/images/07-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-05.png -------------------------------------------------------------------------------- /docs/ch07/images/07-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-06.png -------------------------------------------------------------------------------- /docs/ch07/images/07-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-07.png -------------------------------------------------------------------------------- /docs/ch07/images/07-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-08.png -------------------------------------------------------------------------------- /docs/ch07/images/07-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-09.png -------------------------------------------------------------------------------- /docs/ch07/images/07-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-10.png -------------------------------------------------------------------------------- /docs/ch07/images/07-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-11.png -------------------------------------------------------------------------------- /docs/ch07/images/07-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-12.png -------------------------------------------------------------------------------- /docs/ch07/images/07-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-13.png -------------------------------------------------------------------------------- /docs/ch07/images/07-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-14.png -------------------------------------------------------------------------------- /docs/ch07/images/07-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-15.png -------------------------------------------------------------------------------- /docs/ch07/images/07-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-16.png -------------------------------------------------------------------------------- /docs/ch07/images/07-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-17.png -------------------------------------------------------------------------------- /docs/ch07/images/07-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-18.png -------------------------------------------------------------------------------- /docs/ch07/images/07-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch07/images/07-19.png -------------------------------------------------------------------------------- /docs/ch08/images/08-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-01.png -------------------------------------------------------------------------------- /docs/ch08/images/08-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-02.png -------------------------------------------------------------------------------- /docs/ch08/images/08-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-03.png -------------------------------------------------------------------------------- /docs/ch08/images/08-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-04.png -------------------------------------------------------------------------------- /docs/ch08/images/08-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-05.png -------------------------------------------------------------------------------- /docs/ch08/images/08-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-06.png -------------------------------------------------------------------------------- /docs/ch08/images/08-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-07.png -------------------------------------------------------------------------------- /docs/ch08/images/08-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-08.png -------------------------------------------------------------------------------- /docs/ch08/images/08-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-09.png -------------------------------------------------------------------------------- /docs/ch08/images/08-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-10.png -------------------------------------------------------------------------------- /docs/ch08/images/08-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/ch08/images/08-11.png -------------------------------------------------------------------------------- /docs/ch09/ch09.md: -------------------------------------------------------------------------------- 1 | # 第九章 排序 2 | 3 | ## 9.1 排序的基本概念与分类 4 | 5 | 排序的严格意义: 6 |   假设含有 $n$ 个记录的序列为 $\{ r_1, r_2, \cdots, r_n \}$,其相应的关键字分别为 $\{ k_1, k_2, \cdots, k_3 \}$,需确定 $1,2,\cdots, n$ 的一种排列 $p_1, p_2, \cdots, p_n$,使其相应的关键字满足 $k_{p_1} \leqslant k_{p_2} \leqslant \cdots \leqslant k_{p_n}$ 非递减(或非递增)关系,即使得序列成为一个按关键字有序的序列$\{ r_{p_1}, r_{p_2}, \cdots, r_{p_n} \}$,这样的操作就称为排序。 7 | 8 |   读起来其实有点拗口,也有点难以理解,那就用几个例子来打个比方,排序是我们生活中经常会面对的问题。同学们做操时会按照从矮到高排列;老师查看上课出勤情况时,会按学生学号顺序点名;逛某宝时,习惯性的升序价格来看商品(不是)。 9 | 10 |   注意,我们在排序问题中,通常将数据元素称为记录。显然输入的是一个记录集合,输出的也是一个记录集合,所以说,可以将排序看成是线性表的—种操作。 11 | 12 |   排序的依据是关键字之间的大小关系,那么,对同一个记录集合,针对不同的关键字进行排序,可以得到不同的序列。 13 | 14 | ### 9.1.1 排序的稳定性 15 | 16 |   也正是由于排序不仅是针对主关键字,那么对于次关键字,因为待排序的记录序列中可能存在两个或两个以上的关键字相等的记录。排序结果可能会存在不唯—的情况,我们给出了稳定与不稳定排序的定义。 17 | 18 |   假设 $k_i = k_j (1 \leqslant i \leqslant n, 1 \leqslant j \leqslant n, i \neq j)$,且在排序前的序列中 $r_i$ 领先于$r_j$(即$ i \leqslant j$)。如果排序后 $r_i$ 仍领先于 $r_j$,则称所用的排序方法是稳定的;反之,若可能使得排序后的序列中 $r_j$ 领先于 $r_i$,则称所用的排序方法是不稳定的。 19 | 20 | ### 9.2.2 内排序与外排序 21 | 22 |   根据在排序过程中待排序的记录是否全部被放置在内存中,排序分为内排序和外排序。 23 | 24 |   **内排序**是在排序整个过程中,待排序的所有记录全部被放置在内存中。**外排序**是由于排序的记录个数太多,不能同时放置在内存中,整个排序过程需要在内存之间多次交换数据才能进行。**本章主要讲述的是内排序的方法** 25 | 26 |   对于内排序来说,排序算法的性能主要受三个方面的影晌: 27 | 28 | ![image](images/09-01.png) 29 | 30 | - 时间性能 31 |   排序是数据处理中经常执行的一种操作,往往属于系统的核心部分,因此排序算法的时间开销是衡量其好坏的最重要的标志。在内排序中,主要进行两种操作:比较和移动。而高效率的内排序算法应该是具有尽可能少的关键字比较次数和尽可能少的记录移动次数。 32 | 33 | - 辅助空间 34 |   评价排序算法的另—个主要标准是执行算法所需要的辅助存储空间。辅助存储空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间。 35 | 36 | - 算法的复杂性 37 |   注意这里指的是算法本身的复杂度,而不是指算法的时间复杂度。显然算法过于复杂也会影晌排序的性能。 38 | 39 |   本章一共要讲解7种排序的算法,按照算法的复杂度分为两大类,冒泡排序、简单选择排序和直接插入排序属于简单算法,而希尔排序、堆排序、归并排序、决速排序属于改进算法。 40 | 41 | **Python数据结构实现各种排序算法(含算法介绍与稳定性,复杂度汇总表)** 42 | 43 | | 排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 | 44 | | :------: | :-----------------------: | :-----------: | :-----------: | :---------------------: | :----: | 45 | | 冒泡排序 | $O(n^2)$ | $O(n)$ | $O(n^2)$ | $O(1)$ | 稳定 | 46 | | 选择排序 | $O(n^2)$ | $O(n^2)$ | $O(n^2)$ | $O(1)$ | 不稳定 | 47 | | 插入排序 | $O(n^2)$ | $O(n)$ | $O(n^2)$ | $O(1)$ | 稳定 | 48 | | 希尔排序 | $O(n \log n) \sim O(n^2)$ | $O(n^{1.3})$ | $O(n^2)$ | $O(1)$ | 不稳定 | 49 | | 堆排序 | $O(n \log n)$ | $O(n \log n)$ | $O(n \log n)$ | $O(1)$ | 不稳定 | 50 | | 归并排序 | $O(n \log n)$ | $O(n \log n)$ | $O(n \log n)$ | $O(n)$ | 稳定 | 51 | | 快速排序 | $O(n \log n)$ | $O(n \log n)$ | $O(n^2)$ | $O(n \log n) \sim O(n)$ | 不稳定 | 52 | 53 | ### 9.2.3 python排序用到的结构与函数 54 | 55 |   python与c不同,实现排序使用`list`即可,所以我们不需要用python再去设计一个顺序表结构,也能够很好的实现排序算法的讲解,所以我们下面就不会用顺序表来实现排序,但也提供python实现顺序表的代码。 56 | 57 | ```python 58 | class SqList: 59 | 60 | def __init__(self, capacity): 61 | self.capacity = capacity 62 | self.data = [None] * self.capacity 63 | self.size = 0 64 | 65 | def resize(self, newcapacity): 66 | assert newcapacity >= 0 67 | if newcapacity >= self.capacity: 68 | self.data = self.data + [None] * (newcapacity - self.capacity) 69 | else: 70 | self.data = self.data[:newcapacity] 71 | self.capacity = newcapacity 72 | 73 | def fromlist(self, a): 74 | for i in range(len(a)): 75 | if self.size == self.capacity: 76 | self.resize(self.size * 2) 77 | self.data[self.size] = a[i] 78 | self.size += 1 79 | 80 | def add(self, e): 81 | if self.size == self.capacity: 82 | self.resize(self.size * 2) 83 | self.data[self.size] = e 84 | self.size += 1 85 | 86 | def insert(self, i, e): 87 | assert 0 <= i <= self.size 88 | if self.size == self.capacity: 89 | self.resize(self.size * 2) 90 | for j in range(self.size, i, -1): 91 | self.data[j] = self.data[j - 1] 92 | self.data[i] = e 93 | self.size += 1 94 | 95 | def delete(self, i): 96 | assert 0 <= i < self.size 97 | for j in range(i, self.size - 1): 98 | self.data[j] = self.data[j + 1] 99 | self.size -= 1 100 | if self.size < self.capacity / 2: 101 | self.resize(self.capacity / 2) 102 | 103 | def __getitem__(self, i): 104 | assert 0 <= i < self.size 105 | return self.data[i] 106 | 107 | def __setitem__(self, i, x): 108 | assert 0 <= i < self.size 109 | self.data[i] = x 110 | 111 | def getno(self, e): 112 | for i in range(self.size): 113 | if self.data[i] == e: 114 | return i 115 | return -1 116 | 117 | def getsize(self): 118 | return self.size 119 | 120 | def display(self): 121 | print(*self.data[:self.size]) 122 | 123 | ``` 124 | 125 |   由于排序最常用到的操作是数组的两个元素的交换,我们将它写成函数,在之后的讲解中会大量用到。 126 | 127 | ```python 128 | def swap(i,j): 129 | return j,i 130 | ``` 131 | 132 | ## 9.2 冒泡排序 133 | 134 |   **冒泡排序(Bubble Sort)**是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。 135 | 136 | ### 9.2.1 python实现算法 137 | 138 | ```python 139 | array_test = [8, 3, 5, 1, 10, 4, 2, 6, 7, 9] 140 | # 冒泡排序 141 | # 从后向前依次对比 每一轮都把最小的放在最前面 142 | # 一共需要进行n-1次 143 | def swap(i,j): 144 | return j,i 145 | 146 | def BubbleSort(array): 147 | n = len(array) 148 | for i in range(0, n-1): 149 | j = n-1 150 | while j > i: 151 | if array[j] < array[j-1]: 152 | array[j],array[j-1] = swap(array[j],array[j-1]) 153 | j = j - 1 154 | return array 155 | 156 | print(BubbleSort(array_test)) 157 | ``` 158 | 159 | 优化通过增加一个标记变量`flag`来实现: 160 | 161 | ```python 162 | array_test = [9, 1, 5, 8, 3, 7, 4, 6, 2] 163 | # 冒泡排序 164 | # 从后向前依次对比 每一轮都把最小的放在最前面 165 | # 一共需要进行n-1次 166 | def swap(i,j): 167 | return j,i 168 | 169 | def BubbleSort(array): 170 | n = len(array) 171 | for i in range(0, n-1): 172 | flag = False 173 | j = n-1 174 | while j > i: 175 | if array[j] < array[j-1]: 176 | array[j],array[j-1] = swap(array[j],array[j-1]) 177 | flag = True 178 | j = j - 1 179 | if not flag: 180 | break 181 | return array 182 | 183 | print(BubbleSort(array_test)) 184 | ``` 185 | 186 |   代码改动的关键就是在`j`变量的循环中,增加了对`flag`是否为`TRUE`的判断。经过这样的改进,冒泡排序在性能上就有了一些提升,可以避免已经有序的情况下的无意义循环判断。 187 | 188 | ### 9.2.2 复杂度分析 189 | 190 |   最好的情况就是,要排序的表本身就是有序的,那么比较次数,根据最后改进的代码,可以推断出就是 $n-1$ 次的比较,没有数据交换,时间复杂度为 $O(n)$。最坏的情况是,待排序表是逆序的情况,此时需要比较 $\displaystyle \sum_{i=2}^n (i-1) = 1+ 2 + 3 + \cdots + (n-1) = \frac{n (n-1)}{2}$ 次,并做等数量级的记录移动。因此,总的时间复杂度为 $O(n^2)$。 191 | 192 | 193 | ## 9.3 简单选择排序 194 | 195 |   **简单选择排序法(Simple Selection Sort)**就是通过 $n-i$ 次关键字间的比较,从 $n-i+1$ 个记录中选出关键字最小的记录,并和第$i (1 \leqslant i \leqslant n)$ 个记录交换。 196 | 197 | ### 9.3.1 python实现算法: 198 | 199 | ```python 200 | array_test = [8, 3, 5, 1, 10, 4, 2, 6, 7, 9] 201 | # 选择排序 202 | # 从左向右 每次选择最小的值,放到未排序序列的首部 203 | # 一共需要n-1次 204 | def swap(i,j): 205 | return j,i 206 | 207 | def SelectSort(array): 208 | n = len(array) 209 | for i in range(0,n-1): 210 | min_pos = i # 记录最小元素位置 211 | for j in range(i+1,n): 212 | if array[j] temp and j >= 0: 247 | j = j - 1 248 | # 注意此时j指向的是待插入元素的前一个位置 249 | # 所以不能移动array[j] 从后到前移动到array[j+1] 250 | while k > j+1: 251 | array[k] = array[k-1] 252 | k = k - 1 253 | # 把temp放进来 254 | array[j+1] = temp 255 | return array 256 | print(InsertSort(array_test)) 257 | ``` 258 | 259 |   **优化**:折半插入排序(在寻找插入位置时,通过改用折半查找以减少比较次数,提升效率) 260 | 261 | ```python 262 | def InsertSort(array): 263 | for i in range(1,len(array)): # 默认0号位置已经排好 264 | if array[i] < array[i-1]: # 如果比已排序好的列表最大的元素还大 就不需要比较 265 | temp = array[i] 266 | # 思路:将temp放到合适位置 并将temp后的元素向后移 267 | high = i-1 268 | low = 0 269 | k = i 270 | # 找到temp的合适位置 271 | while low <= high: 272 | mid = (low+high)/2 273 | if array[mid] > temp: 274 | high = mid - 1 275 | else: 276 | low = mid + 1 277 | # 注意此时j多减了1 已经是temp之前的位置 278 | # 把合适位置后的所有元素向后移位时 j要加一 279 | while k > high+1: 280 | array[k] = array[k-1] 281 | k = k - 1 282 | # 把temp放进来 283 | array[high+1] = temp 284 | return array 285 | ``` 286 | 287 |   在代码中,我们需要注意的是二分查找进行了一点小改动,取消了`array[mid] == temp`的情况,这是因为即时二者相等,出于排序算法稳定性的考虑,希望“原本位置靠后的元素”仍可以排在“值相等、但原本靠前的元素”之后。 288 | 289 |   折半插入排序优化了比较时间复杂度($O(n \log n)$),但由于其移动的时间复杂度仍为 $O(n^2)$,故其总的时间复杂度为 $O(n^2)$。 290 | 291 | ### 9.4.2 直接插入排序复杂度分析 292 | 293 |   最好的情况是要排序的表本身就是有序的,因此没有移动的记录,时间复杂度为 $O(n)$。 294 | 295 |   最坏的情况是待排序表是逆序的情况,此时需要比较 $\displaystyle \sum_{i=2}^n i = 1 + 2 + 3 + \cdots + (n-1) = \frac{(n+1)(n-1)}{2}$ 次,而记录的移动次数也达到最大值$\displaystyle \sum_{i=2}^n (i+1) = \frac{(n+4)(n-1)}{2}$ 次。 296 | 297 |   如果排序记录是随机的,那么根据概率相同的原则,平均比较和移动次数约为$\frac{n^2}{4}$ 次。因此,我们可以得出直接插入排序法的时间复杂度为 $O(n^2)$。从这里也看出同样的 $O(n^2)$ 时间复杂度,直接插入排序法比冒泡和简单选择排序的性能要好一些。 298 | 299 | 300 | 301 | 302 | 303 | ## 9.5 希尔排序 304 | 305 |   希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 306 | 307 |   希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列,分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。 308 | 309 | ### 9.5.1 python实现算法 310 | 311 | ```python 312 | array_test = [8, 3, 5, 1, 10, 4, 2, 6, 7, 9] 313 | # 希尔排序 314 | # 希尔排序是基于“插入排序对有序表/基本有序表效率高”产生的 315 | # 先追求部分有序 再追求全局有序 316 | # 排序增量为d的子表[i,i+d,i+2d...] 不断缩小增量d 317 | def ShellSort(array): 318 | d = len(array)//2 319 | while d >= 1: 320 | for i in range(d, len(array)): 321 | if array[i] < array[i-d]: # 则需要将array[i]插入有序子表 322 | # 插入排序 323 | temp = array[i] 324 | j = i - 1 325 | k = i 326 | while array[j] > temp and j >= 0: 327 | j = j - 1 328 | while k > j + 1: 329 | array[k] = array[k - 1] 330 | k = k - 1 331 | array[j + 1] = temp 332 | d = d//2 # 步长折半 333 | return array 334 | 335 | print(ShellSort(array_test)) 336 | ``` 337 | 338 | 339 | 340 | ## 9.6 堆排序 341 | 342 |   堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于具左右孩子结点的值,称为小顶堆。 343 | 344 |   堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走 (其实就将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的 $n-1$ 个序列重新构造成一个堆,这样就会得到 $n$ 个元素中的次大值。如此反复执行,便能得到一个有序序列了。 345 | 346 | ### 9.6.1 python实现算法 347 | 348 | ```python 349 | array_test = [8, 3, 5, 1, 10, 4, 2, 6, 7, 9] 350 | # 堆排序 351 | # 建立大根堆 352 | # 将堆顶元素和待排序序列中的最后一个交换 353 | # 将剩余的[0,n-i]个元素调整为大根堆 354 | def swap(i, j): 355 | return j, i 356 | 357 | # 将k为根结点的树 调整为大根堆 358 | def HeadAjust(array, k, len): 359 | temp = array[k] # 保存要判断的结点 直到下降到满足大根堆的位置 360 | i = 2*k # 左孩子 361 | while i < len: # 存在左孩子 362 | if i+1 < len and array[i] < array[i+1]: # 存在右孩子 363 | # 比较左右孩子的大小 364 | i = i + 1 365 | # 若以该结点为根节点的树不是最大堆,以上操作已经找到了应该成为根节点的孩子 366 | if temp >= array[i]: # 如果已经满足了最大堆 367 | break 368 | else: 369 | array[k] = array[i] # 与较大子树的值交换 370 | # 小元素逐层下坠 371 | k = i # 此时还不能直接完成交换 仍要继续向下比较 372 | i = i * 2 373 | array[k] = temp 374 | 375 | # 建立大根堆 376 | def BuildMaxHeap(array,len): 377 | # 只需要处理非叶结点 378 | k = len // 2 # 一颗完全二叉树的叶子结点数为 n/2向上取整 379 | while k >= 0: 380 | HeadAjust(array, k, len) 381 | k = k - 1 # 自底向上调整 382 | 383 | # 堆排序 384 | def HeapSort(array): 385 | n = len(array) 386 | # 建立大根堆 387 | BuildMaxHeap(array, n) 388 | # 指向待排序数组的最后位置 389 | i = n-1 390 | while i > 0: 391 | array[i], array[0] = swap(array[i], array[0]) 392 | # 将根节点为0的调整为大根堆 即选出了数组中最大的元素 放到array[i]的位置 393 | HeadAjust(array, 0, i-1) 394 | # 每次待排序的长度都减一 395 | i = i - 1 396 | return array 397 | 398 | print(HeapSort(array_test)) 399 | ``` 400 | 401 | ### 9.6.2 堆排序复杂度分析 402 | 403 |   堆排序的运行时间主要消耗在初始构建堆和在重建堆时的反复筛选上。 404 | 405 |   在构建堆的过程中,因为是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较,若有必要进行互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为 $O(n)$。 406 | 407 |   在正式排序时,第 $i$ 次取堆顶记录重建堆需要用 $O(\log i)$ 的时间(完全二叉树的某个结点到根结点的距离为$\lfloor \log_2i \rfloor + 1$),并且需要取 $n-1$ 次堆顶记录,因此,重建堆的时间复杂度为 $O(n \log n)$。 408 | 409 |   空间复杂度上,它只有一个用来交换的暂存单元。不过由于记录的比较与交换是跳跃式进行的,因此堆排序也是一种**不稳定**的排序方法。 410 | 411 |   另外,由于初始构建堆所需的比较次数较多,因此,它并**不适合待排序序列个数较少**的情况。 412 | 413 | ## 9.7 归并排序 414 | 415 |   归并排序(Merging Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有 $n$ 个记录,则可以看成是 $n$ 个有序的子序列,每个子序列的长度为1,然后两两归并,得到$[n/2]$($[x]$ 表示不小于 $x$ 的最小整数)个长度为2或1的有序子序列;再两两归并......如此重复,直至得到一个长度为 $n$ 的有序序列为止,这种排序方法称为二路归并排序。 416 | 417 | ### 9.7.1 python实现算法 418 | 419 | ```python 420 | array_test = [8, 3, 5, 1, 10, 4, 2, 6, 7, 9] 421 | # 归并排序 422 | 423 | # 合并两个有序数组的操作 Merge 424 | def Merge(array,low,mid,high): 425 | # 必须要分配空间 若直接temp = [] 则temp[k]会报错 426 | temp = [0] * (high+1) 427 | # 将原数组复制到temp中 428 | for k in range(low, high+1): 429 | temp[k] = array[k] 430 | i = low 431 | j = mid+1 432 | k = i 433 | # 合并过程 434 | while i <= mid and j <= high: 435 | if temp[i] <= temp[j]: 436 | array[k] = temp[i] 437 | i = i + 1 438 | else: 439 | array[k] = temp[j] 440 | j = j + 1 441 | k = k + 1 442 | # 处理没有合并完的子序列 443 | while i <= mid: 444 | array[k] = temp[i] 445 | k = k + 1 446 | i = i + 1 447 | while j <= high: 448 | array[k] = temp[j] 449 | k = k + 1 450 | j = j + 1 451 | 452 | def MergeSort(array,low,high): 453 | if low < high: 454 | mid = (low+high)//2 455 | # 归并排序左半边 456 | MergeSort(array, low, mid) 457 | # 归并排序右半边 458 | MergeSort(array, mid+1, high) 459 | # 合并 460 | Merge(array, low, mid, high) 461 | return array 462 | print(MergeSort(array_test, 0, len(array_test)-1)) 463 | ``` 464 | 465 | ### 9.7.2 归并排序复杂度分析 466 | 467 |   归并排序需要将待排序序列中的所有记录扫描一遍,因此耗费 $O(n)$ 时间,而由完全二叉树的深度可知,整个归并排序需要进行 $[\log_2 n]$ 次,因此总的时间复杂度为 $O(n \log n)$,而且这是归并排序算法中最好、最坏、平均的时间性能。 468 | 469 |   由于归并排序在归并过程中需要与原始记录序列同样数量的存储空间存放归并结果 以及递归时深度为$ \log_2 n$ 的栈空间,因此空间复杂度为 $O(n + \log n)$。 470 | 471 |   另外,对代码进行仔细研究,发现`Merge`函数中有`if temp[i] <= temp[j]`语句,这就说明它需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。 472 | 473 |   也就是说,归并排序是—种比较占用内存,但却效率高且稳定的算法。 474 | 475 | ### 9.7.3 非递归实现归并排序 476 | 477 | ```python 478 | def merge(array, low, mid, high): 479 | left = array[low: mid] 480 | right = array[mid: high] 481 | k = 0 482 | j = 0 483 | result = [] 484 | while k < len(left) and j < len(right): 485 | if left[k] <= right[j]: 486 | result.append(left[k]) 487 | k += 1 488 | else: 489 | result.append(right[j]) 490 | j += 1 491 | result += left[k:] 492 | result += right[j:] 493 | array[low: high] = result 494 | 495 | def MergeSort(array): 496 | i = 1 # i是步长 497 | while i < len(array): 498 | low = 0 499 | while low < len(array): 500 | mid = low + i #mid前后均为有序 501 | high = min(low+2*i,len(array)) 502 | if mid < high: 503 | merge(array, low, mid, high) 504 | low += 2*i 505 | i*= 2 506 | 507 | ``` 508 | 509 |   非递归版本不需要额外的空间。直接在原数组上进行切割合并。 510 | 511 | 512 | 513 | ## 9.8 快速排序 514 | 515 |   快速排序(Quick Sort)的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另—部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。 516 | 517 | ### 9.8.1 python实现算法 518 | 519 | ```python 520 | array_test = [8, 3, 5, 1, 10, 4, 2, 6, 7, 9] 521 | # 快速排序 522 | # 每次选择一个元素作为基准 将表“划分”为左右两个部分 523 | # 递归的对子表选择基准 进行划分 524 | def QuickSort(array,low,high): 525 | if low < high: # 递归终止条件 526 | # low == high 的时候即为划分后为两个元素的情况 527 | pivotpos = Partition(array,low,high) 528 | QuickSort(array,low,pivotpos-1) 529 | QuickSort(array, pivotpos+1,high) 530 | return array 531 | 532 | def Partition(array,low,high): 533 | privot = array[low] # 第一个元素作为privot 534 | # 因为已经用privot保存了第一个元素的值 所以可以将low指针指向的位置视为空 535 | while low < high: 536 | # 此时,可以将low指针指向的位置视为空 537 | # 先移动high指针 538 | while low < high and array[high] >= privot: 539 | high = high - 1 540 | array[low] = array[high] # 比privot小的放左边 541 | # 此时,可以将high指针指向的位置视为空 542 | while low < high and array[low] <= privot: 543 | low = low + 1 544 | array[high] = array[low] # 比privot大的放右边 545 | array[low] = privot # 此时low == high 将privot元素放置于此 546 | return low # 返回存放privot的位置 547 | 548 | print(QuickSort(array_test,0,len(array_test)-1)) 549 | ``` 550 | 551 |   快速排序递归调用的层数就是二叉树的高度。 552 | 553 | ### 9.8.2 快速排序复杂度分析 554 | 555 |   在最好的情况下,快速排序算法的时间复杂度是$O(n \log n)$。 556 | 557 |   在最坏的情况下,待排序的序列为正序或逆序,每次划分只得到一个比上一次划分少一个记录的子序列,此时需要执行 $n-1$ 次递归调用,且第 $i$ 次划分需要经过 $n-i$ 次关键字的比较才能找到第 $i$ 个记录,因此比较次数为 $\displaystyle \sum_{i=1}^{n-1} (n-i) = n - 1 + n - 2+ \cdots + 1 = \frac{n (n-1)}{2}$, 最终其时间复杂度为 $O(n^2)$。 558 | 559 |   平均的情况,设枢轴的关键字应该在第 $k$ 的位置($1 \leqslant k \leqslant n$),那么 560 | $$ 561 | T(n) = \frac{1}{n} \sum_{k=1}^n (T(k-1) + T(n-k)) + n = \frac{2}{n} \sum_{k=1}^n T(k) + n 562 | $$ 563 |   由数学归纳法可证明,其数量级为$O(n \log n)$。 564 | 565 |   空间复杂度,主要是递归造成的栈空间的使用,最好情况,递归树的深度为 $\log_2 n$,空间复杂度为 $O(\log n)$,最坏情况,需要进行 $n-1$ 次递归调用,其空间复杂度为$O(n)$,平均情况,空间复杂度为 $O(\log n)$。 566 | 567 |   由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法 568 | 569 | ### 9.8.3 快速排序优化方法 570 | 571 | 1. 优化选取枢轴 572 | 2. 优化不必要的交换 573 | 3. 优化小数组时的排序方案 574 | 4. 优化递归操作 575 | 576 | 577 | 578 | ## 9.9 总结 579 | 580 | ### 9.9.1 排序算法汇总 581 | 582 | - 冒泡排序:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。 583 | - 快速排序:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。 584 | - 归并排序:假设初始序列含有 $n$ 个记录,则可以看成是 $n$ 个有序的子序列,每个子序列的长度为1,然后两两归并,得到 $[n/2]$($[x]$ 表示不小于 $x$ 的最小整数)个长度的2或1的有序子序列;再两两归并,......,如此重复,直到得到一个长度为 $n$ 的有序序列为止。 585 | - 直接插入排序:将一个记录插入到已经排序的有序表中,从而得到一个新的、记录数增1的有序表 586 | - 希尔排序:将相邻某个“增量”的记录组成一个子序列,这样才能保证在子序列内,分别进行直接插入排序后得到的结果是基本有序而不是局部有序。 587 | - 简单选择排序:通过 $n-i$ 次关键字间的比较,从 $n - i+ 1$ 个记录中选出关键字最小的记录,并和第$i (1 \leqslant i \leqslant n)$ 个记录交换。 588 | - 堆排序:将待排序的序列构成一个大顶堆,此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的 $n-1$ 个序列重新构成一个堆,这样就会得到 $n$ 个元素中的次大值。 589 | 590 | ### 9.9.2 排序算法比较 591 | 592 | | 排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 | 593 | | :------: | :-----------------------: | :-----------: | :-----------: | :---------------------: | :----: | 594 | | 冒泡排序 | $O(n^2)$ | $O(n)$ | $O(n^2)$ | $O(1)$ | 稳定 | 595 | | 选择排序 | $O(n^2)$ | $O(n^2)$ | $O(n^2)$ | $O(1)$ | 不稳定 | 596 | | 插入排序 | $O(n^2)$ | $O(n)$ | $O(n^2)$ | $O(1)$ | 稳定 | 597 | | 希尔排序 | $O(n \log n) \sim O(n^2)$ | $O(n^{1.3})$ | $O(n^2)$ | $O(1)$ | 不稳定 | 598 | | 堆排序 | $O(n \log n)$ | $O(n \log n)$ | $O(n \log n)$ | $O(1)$ | 不稳定 | 599 | | 归并排序 | $O(n \log n)$ | $O(n \log n)$ | $O(n \log n)$ | $O(n)$ | 稳定 | 600 | | 快速排序 | $O(n \log n)$ | $O(n \log n)$ | $O(n^2)$ | $O(n \log n) \sim O(n)$ | 不稳定 | 601 | 602 | ## 9.10 习题 603 | 604 | ### 9.10.1 简单选择排序习题 605 | 606 | **题目来源:** [洛谷:1104生日](https://www.luogu.com.cn/problem/P1104) 607 | 608 | **题目描述:** 609 | 610 |   `cjf`君想调查学校`OI`组每个同学的生日,并按照年龄从大到小的顺序排序。但 `cjf`君最近作业很多,没有时间,所以请你帮她排序。 611 | 612 | **输入格式:** 613 | 614 |   输入共有 2 行:第1行为 OI 组总人数 $n$;第2行至第 $n+1$ 行分别是每人的姓名 $s$、出生年 $y$、月 $m$、日 $d$。 615 | 616 | **输出格式:** 617 | 618 |   输出共有 $n$ 行,即 $n$ 个生日从大到小同学的姓名。(如果有两个同学生日相同,输入靠后的同学先输出) 619 | 620 | **样例 #1** 621 | 622 | 样例输入 #1 623 | 624 | ``` 625 | 3 626 | Yangchu 1992 4 23 627 | Qiujingya 1993 10 13 628 | Luowen 1991 8 1 629 | ``` 630 | 631 | 样例输出 #1 632 | 633 | ``` 634 | Luowen 635 | Yangchu 636 | Qiujingya 637 | ``` 638 | 639 | **提示:** 640 | 641 |   数据保证,$1a_j$ 且 $i 2 | 3 | 4 | 5 | sweetalk data structure 6 | 7 | 8 | 10 | 11 | 12 | 13 |
14 | 15 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /docs/resources/qrcode.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/docs/resources/qrcode.jpeg -------------------------------------------------------------------------------- /resources/qrcode.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/sweetalk-data-structure/f188992a596904e9b0ebbe8a5259998f556be424/resources/qrcode.jpeg --------------------------------------------------------------------------------