diff --git a/.gitignore b/.gitignore index ee40bb3..eb4b4af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea/ -*.pyc \ No newline at end of file +*.pyc +/venv/ +site/ diff --git a/README.md b/README.md index 8bd5df4..4900804 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,50 @@ -## 《算法导论》第3版算法集锦 - python实现 +## 飞污熊算法笔记 - python3实现 -### 作者的话 -《算法导论》第3版,这本书的经典程度就无需我多言了。 +算法参考书籍列表,建议按照顺序阅读: -书中所有的算法示例都用python语言实现,并且配有详细的算法原理说明,有些复杂算法还会专门写博客来讲解。 +* 《Hello算法》 +* 《算法图解》 +* 《啊哈算法》 +* 《算法导论》 +* 《labuladong的算法小抄》 -希望这些对自己,还有其他人都有所帮助。 +## 章节说明 + +1. 【第一章】基础数据结构:数组、链表、队列、栈、哈希表。 +2. 【第二章】树数据结构:二叉树、红黑树、递归树、堆。 +3. 【第三章】图数据结构 +4. 【第四章】各类排序算法:(冒泡、插入、选择)、(快排、归并)、(桶、计数、基数)。 +5. 【第五章】各类搜索算法:二分查找、调表、散列表、哈希算法 +6. 【第六章】字符串匹配算法 +7. 【第七章】分治算法 +8. 【第八章】回溯算法 +9. 【第九章】动态规划算法 +10. 【第十章】贪心算法 +11. 【第十一章】高级数据结构和算法 +12. 【第十三章】LeetCode刷题 +13. 【第十二章】AI算法 +14. 【第十四章】算法小抄 + +## 作者的话 + +书中经典的算法示例使用python3语言实现。 + +整个工程分为三个部分。第一部分是阅读经典算法书籍后自己的总结,作为基础算法部分。 +第二部分是自己在LeetCode上面的刷题总结,第三部分是人工智能AI算法示例。 + +从2013年就开始写这个系列,写到动态规划后就停了,那段时间实在是太懒了。 +今年2020年开始决定继续把之前的捡起来,重新更新这个系列,做事情得有始有终,希望能把这个系列坚持写完。 有任何问题都可以联系我: -* Email: yidao620@gmail.com -* 博客: http://yidao620c.github.io/ +* Email: yidao620@gmail.com +* Blog: https://www.xncoding.com/ * GitHub: https://github.com/yidao620c +**欢迎关注我的个人公众号“飞污熊”,我会定期分享一些自己的Python学习笔记和心得。** + + + ## How to Contribute You are welcome to contribute to the project as follow @@ -23,7 +55,7 @@ You are welcome to contribute to the project as follow * commit new feature * add testcase -Meanwhile you'd better follow the rules below +Meanwhile, you'd better follow the rules below * It's *NOT* recommended to submit a pull request directly to `master` branch. `develop` branch is more appropriate * Follow common Python coding conventions @@ -33,14 +65,14 @@ Meanwhile you'd better follow the rules below (The Apache License) -Copyright (c) 2011-2015 [Xiong Neng](http://yidao620c.github.io/) and other contributors +Copyright (c) 2013-2020 [Xiong Neng](https://www.xncoding.com/) and other contributors -Licensed under the Apache License, Version 2.0 (the "License"); +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/algorithms/__init__.py b/algorithms/__init__.py new file mode 100644 index 0000000..cf0aa6e --- /dev/null +++ b/algorithms/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +《算法导论》第3版,使用python3语言实现书中经典的算法示例 +""" diff --git a/algorithms/build.py b/algorithms/build.py new file mode 100644 index 0000000..98523f2 --- /dev/null +++ b/algorithms/build.py @@ -0,0 +1,20 @@ +import glob +import py_compile as pyc + +if __name__ == "__main__": + # find source code files + src_paths = sorted(glob.glob("codes/python/**/*.py")) + num_src = len(src_paths) + num_src_error = 0 + + # compile python code + for src_path in src_paths: + try: + pyc.compile(src_path, doraise=True) + except pyc.PyCompileError as e: + num_src_error += 1 + print(e) + + print(f"===== Build Complete =====") + print(f"Total: {num_src}") + print(f"Error: {num_src_error}") diff --git a/algorithms/c01_data_structure/__init__.py b/algorithms/c01_data_structure/__init__.py new file mode 100644 index 0000000..adb9640 --- /dev/null +++ b/algorithms/c01_data_structure/__init__.py @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- +""" +线性表数据结构:数组、链表、队列、栈。 +""" + + +class Node: + """ + 单指针节点 + """ + + def __init__(self, data_, next_=None): + self.data = data_ + self.next = next_ + + def __str__(self): + return str(self.data) + + +class NodeDouble: + """ + 双指针节点 + """ + + def __init__(self, data_, next_=None, pre_=None): + self.data = data_ + self.next = next_ + self.pre = pre_ + + def __str__(self): + return str(self.data) diff --git a/algorithms/c01_data_structure/hashing/__init__.py b/algorithms/c01_data_structure/hashing/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c01_data_structure/hashing/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c01_data_structure/hashing/array_hash_map.py b/algorithms/c01_data_structure/hashing/array_hash_map.py new file mode 100644 index 0000000..f71f95c --- /dev/null +++ b/algorithms/c01_data_structure/hashing/array_hash_map.py @@ -0,0 +1,117 @@ +""" +File: array_hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + + +class Pair: + """键值对""" + + def __init__(self, key: int, val: str): + self.key = key + self.val = val + + +class ArrayHashMap: + """基于数组简易实现的哈希表""" + + def __init__(self): + """构造方法""" + # 初始化数组,包含 100 个桶 + self.buckets: list[Pair | None] = [None] * 100 + + def hash_func(self, key: int) -> int: + """哈希函数""" + index = key % 100 + return index + + def get(self, key: int) -> str: + """查询操作""" + index: int = self.hash_func(key) + pair: Pair = self.buckets[index] + if pair is None: + return None + return pair.val + + def put(self, key: int, val: str): + """添加操作""" + pair = Pair(key, val) + index: int = self.hash_func(key) + self.buckets[index] = pair + + def remove(self, key: int): + """删除操作""" + index: int = self.hash_func(key) + # 置为 None ,代表删除 + self.buckets[index] = None + + def entry_set(self) -> list[Pair]: + """获取所有键值对""" + result: list[Pair] = [] + for pair in self.buckets: + if pair is not None: + result.append(pair) + return result + + def key_set(self) -> list[int]: + """获取所有键""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.key) + return result + + def value_set(self) -> list[str]: + """获取所有值""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.val) + return result + + def print(self): + """打印哈希表""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化哈希表 + hmap = ArrayHashMap() + + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小啰") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + hmap.print() + + # 查询操作 + # 向哈希表输入键 key ,得到值 value + name = hmap.get(15937) + print("\n输入学号 15937 ,查询到姓名 " + name) + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hmap.remove(10583) + print("\n删除 10583 后,哈希表为\nKey -> Value") + hmap.print() + + # 遍历哈希表 + print("\n遍历键值对 Key->Value") + for pair in hmap.entry_set(): + print(pair.key, "->", pair.val) + + print("\n单独遍历键 Key") + for key in hmap.key_set(): + print(key) + + print("\n单独遍历值 Value") + for val in hmap.value_set(): + print(val) diff --git a/algorithms/c01_data_structure/hashing/built_in_hash.py b/algorithms/c01_data_structure/hashing/built_in_hash.py new file mode 100644 index 0000000..09b6713 --- /dev/null +++ b/algorithms/c01_data_structure/hashing/built_in_hash.py @@ -0,0 +1,36 @@ +""" +File: built_in_hash.py +Created Time: 2023-06-15 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import ListNode + +"""Driver Code""" +if __name__ == "__main__": + num = 3 + hash_num = hash(num) + print(f"整数 {num} 的哈希值为 {hash_num}") + + bol = True + hash_bol = hash(bol) + print(f"布尔量 {bol} 的哈希值为 {hash_bol}") + + dec = 3.14159 + hash_dec = hash(dec) + print(f"小数 {dec} 的哈希值为 {hash_dec}") + + str = "Hello 算法" + hash_str = hash(str) + print(f"字符串 {str} 的哈希值为 {hash_str}") + + tup = (12836, "小哈") + hash_tup = hash(tup) + print(f"元组 {tup} 的哈希值为 {hash(hash_tup)}") + + obj = ListNode(0) + hash_obj = hash(obj) + print(f"节点对象 {obj} 的哈希值为 {hash_obj}") diff --git a/algorithms/c01_data_structure/hashing/hash_map.py b/algorithms/c01_data_structure/hashing/hash_map.py new file mode 100644 index 0000000..a9d23af --- /dev/null +++ b/algorithms/c01_data_structure/hashing/hash_map.py @@ -0,0 +1,50 @@ +""" +File: hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import print_dict + +"""Driver Code""" +if __name__ == "__main__": + # 初始化哈希表 + hmap = dict[int, str]() + + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" + print("\n添加完成后,哈希表为\nKey -> Value") + print_dict(hmap) + + # 查询操作 + # 向哈希表输入键 key ,得到值 value + name: str = hmap[15937] + print("\n输入学号 15937 ,查询到姓名 " + name) + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hmap.pop(10583) + print("\n删除 10583 后,哈希表为\nKey -> Value") + print_dict(hmap) + + # 遍历哈希表 + print("\n遍历键值对 Key->Value") + for key, value in hmap.items(): + print(key, "->", value) + + print("\n单独遍历键 Key") + for key in hmap.keys(): + print(key) + + print("\n单独遍历值 Value") + for val in hmap.values(): + print(val) diff --git a/algorithms/c01_data_structure/hashing/hash_map_chaining.py b/algorithms/c01_data_structure/hashing/hash_map_chaining.py new file mode 100644 index 0000000..448ee2c --- /dev/null +++ b/algorithms/c01_data_structure/hashing/hash_map_chaining.py @@ -0,0 +1,117 @@ +""" +File: hash_map_chaining.py +Created Time: 2023-06-13 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.c01_data_structure.hashing.array_hash_map import Pair + + +class HashMapChaining: + """链式地址哈希表""" + + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets = [[] for _ in range(self.capacity)] # 桶数组 + + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity + + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity + + def get(self, key: int) -> str | None: + """查询操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若找到 key 则返回对应 val + for pair in bucket: + if pair.key == key: + return pair.val + # 若未找到 key 则返回 None + return None + + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in bucket: + if pair.key == key: + pair.val = val + return + # 若无该 key ,则将键值对添加至尾部 + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 + + def remove(self, key: int): + """删除操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,从中删除键值对 + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + break + + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [[] for _ in range(self.capacity)] + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for bucket in buckets: + for pair in bucket: + self.put(pair.key, pair.val) + + def print(self): + """打印哈希表""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化哈希表 + hashmap = HashMapChaining() + + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小啰") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鸭") + print("\n添加完成后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() + + # 查询操作 + # 向哈希表输入键 key ,得到值 value + name = hashmap.get(13276) + print("\n输入学号 13276 ,查询到姓名 " + name) + + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hashmap.remove(12836) + print("\n删除 12836 后,哈希表为\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() diff --git a/algorithms/c01_data_structure/hashing/hash_map_open_addressing.py b/algorithms/c01_data_structure/hashing/hash_map_open_addressing.py new file mode 100644 index 0000000..f57c916 --- /dev/null +++ b/algorithms/c01_data_structure/hashing/hash_map_open_addressing.py @@ -0,0 +1,137 @@ +""" +File: hash_map_open_addressing.py +Created Time: 2023-06-13 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.c01_data_structure.hashing.array_hash_map import Pair + + +class HashMapOpenAddressing: + """开放寻址哈希表""" + + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets: list[Pair | None] = [None] * self.capacity # 桶数组 + self.TOMBSTONE = Pair(-1, "-1") # 删除标记 + + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity + + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity + + def find_bucket(self, key: int) -> int: + """搜索 key 对应的桶索引""" + index = self.hash_func(key) + first_tombstone = -1 + # 线性探测,当遇到空桶时跳出 + while self.buckets[index] is not None: + # 若遇到 key ,返回对应桶索引 + if self.buckets[index].key == key: + # 若之前遇到了删除标记,则将键值对移动至该索引 + if first_tombstone != -1: + self.buckets[first_tombstone] = self.buckets[index] + self.buckets[index] = self.TOMBSTONE + return first_tombstone # 返回移动后的桶索引 + return index # 返回桶索引 + # 记录遇到的首个删除标记 + if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: + first_tombstone = index + # 计算桶索引,越过尾部返回头部 + index = (index + 1) % self.capacity + # 若 key 不存在,则返回添加点的索引 + return index if first_tombstone == -1 else first_tombstone + + def get(self, key: int) -> str: + """查询操作""" + # 搜索 key 对应的桶索引 + index = self.find_bucket(key) + # 若找到键值对,则返回对应 val + if self.buckets[index] not in [None, self.TOMBSTONE]: + return self.buckets[index].val + # 若键值对不存在,则返回 None + return None + + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + # 搜索 key 对应的桶索引 + index = self.find_bucket(key) + # 若找到键值对,则覆盖 val 并返回 + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index].val = val + return + # 若键值对不存在,则添加该键值对 + self.buckets[index] = Pair(key, val) + self.size += 1 + + def remove(self, key: int): + """删除操作""" + # 搜索 key 对应的桶索引 + index = self.find_bucket(key) + # 若找到键值对,则用删除标记覆盖它 + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index] = self.TOMBSTONE + self.size -= 1 + + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets_tmp = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [None] * self.capacity + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for pair in buckets_tmp: + if pair not in [None, self.TOMBSTONE]: + self.put(pair.key, pair.val) + + def print(self): + """打印哈希表""" + for pair in self.buckets: + if pair is None: + print("None") + elif pair is self.TOMBSTONE: + print("TOMBSTONE") + else: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化哈希表 + hashmap = HashMapOpenAddressing() + + # 添加操作 + # 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小啰") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鸭") + print("\n添加完成后,哈希表为\nKey -> Value") + hashmap.print() + + # 查询操作 + # 向哈希表输入键 key ,得到值 val + name = hashmap.get(13276) + print("\n输入学号 13276 ,查询到姓名 " + name) + + # 删除操作 + # 在哈希表中删除键值对 (key, val) + hashmap.remove(16750) + print("\n删除 16750 后,哈希表为\nKey -> Value") + hashmap.print() diff --git a/algorithms/c01_data_structure/hashing/simple_hash.py b/algorithms/c01_data_structure/hashing/simple_hash.py new file mode 100644 index 0000000..c1a02e1 --- /dev/null +++ b/algorithms/c01_data_structure/hashing/simple_hash.py @@ -0,0 +1,57 @@ +""" +File: simple_hash.py +Created Time: 2023-06-15 +""" + + +def add_hash(key: str) -> int: + """加法哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash += ord(c) + return hash % modulus + + +def mul_hash(key: str) -> int: + """乘法哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = 31 * hash + ord(c) + return hash % modulus + + +def xor_hash(key: str) -> int: + """异或哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash ^= ord(c) + return hash % modulus + + +def rot_hash(key: str) -> int: + """旋转哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = (hash << 4) ^ (hash >> 28) ^ ord(c) + return hash % modulus + + +"""Driver Code""" +if __name__ == "__main__": + key = "Hello 算法" + + hash = add_hash(key) + print(f"加法哈希值为 {hash}") + + hash = mul_hash(key) + print(f"乘法哈希值为 {hash}") + + hash = xor_hash(key) + print(f"异或哈希值为 {hash}") + + hash = rot_hash(key) + print(f"旋转哈希值为 {hash}") diff --git a/algorithms/c01_data_structure/linkedlist/__init__.py b/algorithms/c01_data_structure/linkedlist/__init__.py new file mode 100644 index 0000000..894ec9e --- /dev/null +++ b/algorithms/c01_data_structure/linkedlist/__init__.py @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- +""" +链表数据结构 + +练习题LeetCode对应编号:206,141,21,19,876 + +* 单链表反转 +* 链表中环的检测 +* 两个有序的链表合并 +* 删除链表倒数第 n 个结点 +* 求链表的中间结点 +""" + + +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next diff --git a/algorithms/c01_data_structure/linkedlist/linked_list_double.py b/algorithms/c01_data_structure/linkedlist/linked_list_double.py new file mode 100644 index 0000000..f694991 --- /dev/null +++ b/algorithms/c01_data_structure/linkedlist/linked_list_double.py @@ -0,0 +1,58 @@ +# -*- encoding: utf-8 -*- +""" +双向链表数据结构 +""" +from algorithms.c01_data_structure import NodeDouble + + +class LinkedListDouble: + """ + 双向链表,带哨兵 + """ + + def __init__(self): + self.head = NodeDouble(None) + + def insert_node(self, node, new_node): + """ + :param node:插入点节点 + :param new_node:新节点 + :return: + """ + new_node.next = node.next + new_node.pre = node + node.next = new_node + + def remove_node(self, node): + """ + :param node:待删除节点 + :return: + """ + if not node.pre: # 空表 + return + node.pre.next = node.next + if node.next: # 如果node有下一个节点 + node.next.pre = node.pre + + def search(self, data): + """ + 直接比较原始数据 + :param data: 待查询数据 + :return: 查询到的节点 + """ + target = self.head.next + while target and target.data != data: + target = target.next + return target + + def search_equal(self, data, equal): + """ + 通过传入equal比较函数来进行相等判断 + :param data: 待查询数据 + :param equal: 相等函数 + :return: 查询到的节点 + """ + target = self.head.next + while target and equal(target.data, data): + target = target.next + return target diff --git a/algorithms/c01_data_structure/linkedlist/linked_list_double_cycle.py b/algorithms/c01_data_structure/linkedlist/linked_list_double_cycle.py new file mode 100644 index 0000000..2241a61 --- /dev/null +++ b/algorithms/c01_data_structure/linkedlist/linked_list_double_cycle.py @@ -0,0 +1,59 @@ +# -*- encoding: utf-8 -*- +""" +双向循环链表数据结构 +""" +from algorithms.c01_data_structure import NodeDouble + + +class LinkedListDouble: + """ + 双向循环链表,带哨兵 + """ + + def __init__(self): + head = NodeDouble(None) + head.pre = head.next = head # 将pre和next都指向自己 + self.head = head + + def insert_node(self, node, new_node): + """ + :param node:插入点节点 + :param new_node:新节点 + :return: + """ + new_node.next = node.next + new_node.pre = node + node.next = new_node + + def remove_node(self, node): + """ + :param node:待删除节点 + :return: + """ + if node == self.head: # 不能删除头节点 + return + node.pre.next = node.next + node.next.pre = node.pre + + def search(self, data): + """ + 直接比较原始数据 + :param data: 待查询数据 + :return: 查询到的节点 + """ + target = self.head.next + while target != self.head and target.data != data: + target = target.next + return target + + def search_equal(self, data, equal): + """ + 通过传入equal比较函数来进行相等判断 + :param data: 待查询数据 + :param equal: 相等函数 + :return: 查询到的节点 + """ + target = self.head.next + while target != self.head and equal(target.data, data): + target = target.next + return target diff --git a/algorithms/c01_data_structure/linkedlist/linked_list_single.py b/algorithms/c01_data_structure/linkedlist/linked_list_single.py new file mode 100644 index 0000000..0579f23 --- /dev/null +++ b/algorithms/c01_data_structure/linkedlist/linked_list_single.py @@ -0,0 +1,85 @@ +# -*- encoding: utf-8 -*- +""" +单向链表数据结构 +""" +from algorithms.c01_data_structure import Node + + +class LinkedListSingle: + """ + 单向链表,带哨兵 + """ + + def __init__(self, limit): + self.head = Node(None) + self.size = 1 + self.limit = limit + 1 # 预留一个给哨兵头节点 + + def insert(self, node, new_node): + """ + :param node: 插入点节点 + :param new_node: 新节点 + :return: + """ + if self.size >= self.limit: + raise IndexError('list is full') + new_node.next = node.next + node.next = new_node + self.size += 1 + + def is_full(self): + return self.size >= self.limit + + def is_empty(self): + return self.size == 1 + + def remove(self, node): + """ + :param node: 待删除节点 + :return: + """ + if self.is_empty(): + return + pre_node = self.head + while pre_node.next != node: + pre_node = pre_node.next + pre_node.next = node.next + self.size -= 1 + + def get_tail(self): + target = self.head + while target.next: + target = target.next + return target + + def search(self, data): + """ + 直接比较原始数据 + :param data: 待查询数据 + :return: 查询到的节点 + """ + target = self.head.next + while target and target.data != data: + target = target.next + return target + + def search_equal(self, data, equal): + """ + 通过传入equal比较函数来进行相等判断 + :param data: 待查询数据 + :param equal: 相等函数 + :return: 查询到的节点 + """ + target = self.head.next + while target and equal(target.data, data): + target = target.next + return target + + def __iter__(self): + node = self.head.next + while node: + yield node.data + node = node.next + + def print(self): + print(list(x for x in self)) diff --git a/algorithms/c01_data_structure/linkedlist/linked_list_single_cycle.py b/algorithms/c01_data_structure/linkedlist/linked_list_single_cycle.py new file mode 100644 index 0000000..2d07470 --- /dev/null +++ b/algorithms/c01_data_structure/linkedlist/linked_list_single_cycle.py @@ -0,0 +1,57 @@ +# -*- encoding: utf-8 -*- +""" +单向循环链表数据结构 +""" +from algorithms.c01_data_structure import Node + + +class LinkedListSingleCycle: + """ + 单向循环链表,带哨兵 + """ + + def __init__(self): + self.head = Node(None) + self.head.next = self.head + + def insert_node(self, node, new_node): + """ + :param node:插入点节点 + :param new_node:新节点 + :return: + """ + new_node.next = node.next + node.next = new_node + + def remove_node(self, node): + """ + :param node:待删除节点 + :return: + """ + pre_node = self.head + while pre_node.next != node: + pre_node = pre_node.next + pre_node.next = node.next + + def search(self, data): + """ + 直接比较原始数据 + :param data: 待查询数据 + :return: 查询到的节点 + """ + target = self.head.next + while target != self.head and target.data != data: + target = target.next + return target + + def search_equal(self, data, equal): + """ + 通过传入equal比较函数来进行相等判断 + :param data: 待查询数据 + :param equal: 相等函数 + :return: 查询到的节点 + """ + target = self.head.next + while target != self.head and equal(target.data, data): + target = target.next + return target diff --git a/algorithms/c01_data_structure/linkedlist/lru_cache.py b/algorithms/c01_data_structure/linkedlist/lru_cache.py new file mode 100644 index 0000000..5f29ece --- /dev/null +++ b/algorithms/c01_data_structure/linkedlist/lru_cache.py @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 -*- +""" +Question:怎样用单链表实现LRU缓存淘汰算法? + +我们维护一个单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时, +我们从链表头开始顺序遍历链表。 + +1. 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除, +然后再插入到链表的头部。 +2. 如果此数据没有在缓存链表中,又可以分为两种情况: + 2.1 如果此时缓存未满,则将此结点直接插入到链表的头部; + 2.2 如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。 +""" +from algorithms.c01_data_structure import Node +from algorithms.c01_data_structure.linkedlist.linked_list_single import LinkedListSingle + + +def lru(list_single_, data): + find_node = list_single_.search(data) + if find_node: + list_single_.remove(find_node) + list_single_.insert(list_single_.head, find_node) + else: + if not list_single_.is_full(): + list_single_.insert(list_single_.head, new_node=Node(data)) + else: + tail = list_single_.get_tail() + new_node = Node(data) + list_single_.remove(tail) + list_single_.insert(list_single_.head, new_node) + list_single_.print() + + +if __name__ == '__main__': + list_single = LinkedListSingle(3) + lru(list_single, 2) + lru(list_single, 3) + lru(list_single, 2) + lru(list_single, 1) + lru(list_single, 5) + lru(list_single, 6) diff --git a/algorithms/c01_data_structure/linkedlist/palindromic_number.py b/algorithms/c01_data_structure/linkedlist/palindromic_number.py new file mode 100644 index 0000000..51582b2 --- /dev/null +++ b/algorithms/c01_data_structure/linkedlist/palindromic_number.py @@ -0,0 +1,69 @@ +# -*- encoding: utf-8 -*- +""" +Question:如果字符串是通过单链表来存储的,那该如何来判断是一个回文串? + +快慢指针法实现过程: +由于回文串最重要的就是对称,那么最重要的问题就是找到那个中心,用快指针每步两格走,当他到达链表末端的时候, +慢指针刚好到达中心,慢指针在过来的这趟路上还做了一件事,他把走过的节点反向了,在中心点再开辟一个新的指针用于往回走, +而慢指针继续向前,当慢指针扫完整个链表,就可以判断这是回文串,否则就提前退出。 + +总的来说时间复杂度按慢指针遍历一遍来算是O(n),空间复杂度因为只开辟了3个额外的辅助,所以是o(1) + +1.1 奇数情况,中点位置不需要矫正 +1.2 偶数情况,使用偶数定位中点策略,要确定是返回上中位数或下中位数 + 1.2.1 如果是返回上中位数,后半部分串头取next <<=这里使用这张上中位数数 + 1.2.2 如果是返回下中位数,后半部分串头既是当前节点位置,但前半部分串尾要删除掉当前节点 +""" +from algorithms.c01_data_structure import Node +from algorithms.c01_data_structure.linkedlist.linked_list_single import LinkedListSingle + + +def palindromic(list_single_): + if list_single_.is_empty() or list_single_.size == 2: + return True + if list_single_.size == 3: + return list_single_.head.next.data == list_single_.head.next.next.data + + slow = list_single_.head.next.next # slow指向第2个节点 + slow_next = slow.next # 保存下一步的slow后驱节点 + fast = slow.next # fast指向第3个节点 + slow.next = list_single_.head.next # 第一个slow指针反向 + list_single_.head.next.next = None # 第一个节点下一个指针初始化为None + + while fast.next and fast.next.next: # 还能继续往前跑 + fast = fast.next.next # 快指针先往后面走2步 + slow_pre = slow # 临时保存slow + slow = slow_next # slow往后走1步 + slow_next = slow.next # 保存slow下一步的后驱节点 + slow.next = slow_pre # 将slow节点的next反转 + if not fast.next: # 奇数情况 + # 左边指针从slow.next开始,右边指针从slow_next开始 + slow = slow.next + if not slow: + return True + while slow: + if slow.data != slow_next.data: + return False + slow = slow.next + slow_next = slow_next.next + else: # 偶数情况 + # 左边指针从slow开始,右边指针从slow_next开始 + while slow: + if slow.data != slow_next.data: + return False + slow = slow.next + slow_next = slow_next.next + return True + + +if __name__ == '__main__': + list_single = LinkedListSingle(30) + list_single.insert(list_single.head, Node('d')) + list_single.insert(list_single.head, Node('a')) + list_single.insert(list_single.head, Node('b')) + list_single.insert(list_single.head, Node('c')) + list_single.insert(list_single.head, Node('b')) + list_single.insert(list_single.head, Node('a')) + list_single.insert(list_single.head, Node('d')) + list_single.print() + print(palindromic(list_single)) diff --git a/algorithms/c01_data_structure/queue/__init__.py b/algorithms/c01_data_structure/queue/__init__.py new file mode 100644 index 0000000..4920954 --- /dev/null +++ b/algorithms/c01_data_structure/queue/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description... +""" diff --git a/algorithms/c01_data_structure/queue/array_deque.py b/algorithms/c01_data_structure/queue/array_deque.py new file mode 100644 index 0000000..ffd0faf --- /dev/null +++ b/algorithms/c01_data_structure/queue/array_deque.py @@ -0,0 +1,128 @@ +""" +File: array_deque.py +Created Time: 2023-03-01 +""" + + +class ArrayDeque: + """基于环形数组实现的双向队列""" + + def __init__(self, capacity: int): + """构造方法""" + self._nums: list[int] = [0] * capacity + self._front: int = 0 + self._size: int = 0 + + def capacity(self) -> int: + """获取双向队列的容量""" + return len(self._nums) + + def size(self) -> int: + """获取双向队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断双向队列是否为空""" + return self._size == 0 + + def index(self, i: int) -> int: + """计算环形数组索引""" + # 通过取余操作实现数组首尾相连 + # 当 i 越过数组尾部后,回到头部 + # 当 i 越过数组头部后,回到尾部 + return (i + self.capacity()) % self.capacity() + + def push_first(self, num: int): + """队首入队""" + if self._size == self.capacity(): + print("双向队列已满") + return + # 队首指针向左移动一位 + # 通过取余操作,实现 front 越过数组头部后回到尾部 + self._front = self.index(self._front - 1) + # 将 num 添加至队首 + self._nums[self._front] = num + self._size += 1 + + def push_last(self, num: int): + """队尾入队""" + if self._size == self.capacity(): + print("双向队列已满") + return + # 计算尾指针,指向队尾索引 + 1 + rear = self.index(self._front + self._size) + # 将 num 添加至队尾 + self._nums[rear] = num + self._size += 1 + + def pop_first(self) -> int: + """队首出队""" + num = self.peek_first() + # 队首指针向后移动一位 + self._front = self.index(self._front + 1) + self._size -= 1 + return num + + def pop_last(self) -> int: + """队尾出队""" + num = self.peek_last() + self._size -= 1 + return num + + def peek_first(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._nums[self._front] + + def peek_last(self) -> int: + """访问队尾元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + # 计算尾元素索引 + last = self.index(self._front + self._size - 1) + return self._nums[last] + + def to_array(self) -> list[int]: + """返回数组用于打印""" + # 仅转换有效长度范围内的列表元素 + res = [] + for i in range(self._size): + res.append(self._nums[self.index(self._front + i)]) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化双向队列 + deque = ArrayDeque(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("双向队列 deque =", deque.to_array()) + + # 访问元素 + peek_first: int = deque.peek_first() + print("队首元素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("队尾元素 peek_last =", peek_last) + + # 元素入队 + deque.push_last(4) + print("元素 4 队尾入队后 deque =", deque.to_array()) + deque.push_first(1) + print("元素 1 队首入队后 deque =", deque.to_array()) + + # 元素出队 + pop_last: int = deque.pop_last() + print("队尾出队元素 =", pop_last, ",队尾出队后 deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("队首出队元素 =", pop_first, ",队首出队后 deque =", deque.to_array()) + + # 获取双向队列的长度 + size: int = deque.size() + print("双向队列长度 size =", size) + + # 判断双向队列是否为空 + is_empty: bool = deque.is_empty() + print("双向队列是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/queue/array_queue.py b/algorithms/c01_data_structure/queue/array_queue.py new file mode 100644 index 0000000..73b773b --- /dev/null +++ b/algorithms/c01_data_structure/queue/array_queue.py @@ -0,0 +1,98 @@ +""" +File: array_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayQueue: + """基于环形数组实现的队列""" + + def __init__(self, size: int): + """构造方法""" + self._nums: list[int] = [0] * size # 用于存储队列元素的数组 + self._front: int = 0 # 队首指针,指向队首元素 + self._size: int = 0 # 队列长度 + + def capacity(self) -> int: + """获取队列的容量""" + return len(self._nums) + + def size(self) -> int: + """获取队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断队列是否为空""" + return self._size == 0 + + def push(self, num: int): + """入队""" + if self._size == self.capacity(): + raise IndexError("队列已满") + # 计算尾指针,指向队尾索引 + 1 + # 通过取余操作,实现 rear 越过数组尾部后回到头部 + rear: int = (self._front + self._size) % self.capacity() + # 将 num 添加至队尾 + self._nums[rear] = num + self._size += 1 + + def pop(self) -> int: + """出队""" + num: int = self.peek() + # 队首指针向后移动一位,若越过尾部则返回到数组头部 + self._front = (self._front + 1) % self.capacity() + self._size -= 1 + return num + + def peek(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("队列为空") + return self._nums[self._front] + + def to_list(self) -> list[int]: + """返回列表用于打印""" + res = [0] * self.size() + j: int = self._front + for i in range(self.size()): + res[i] = self._nums[(j % self.capacity())] + j += 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化队列 + queue = ArrayQueue(10) + + # 元素入队 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("队列 queue =", queue.to_list()) + + # 访问队首元素 + peek: int = queue.peek() + print("队首元素 peek =", peek) + + # 元素出队 + pop: int = queue.pop() + print("出队元素 pop =", pop) + print("出队后 queue =", queue.to_list()) + + # 获取队列的长度 + size: int = queue.size() + print("队列长度 size =", size) + + # 判断队列是否为空 + is_empty: bool = queue.is_empty() + print("队列是否为空 =", is_empty) + + # 测试环形数组 + for i in range(10): + queue.push(i) + queue.pop() + print("第", i, "轮入队 + 出队后 queue = ", queue.to_list()) diff --git a/algorithms/c01_data_structure/queue/deque.py b/algorithms/c01_data_structure/queue/deque.py new file mode 100644 index 0000000..2717165 --- /dev/null +++ b/algorithms/c01_data_structure/queue/deque.py @@ -0,0 +1,42 @@ +""" +File: deque.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # 初始化双向队列 + deq: deque[int] = deque() + + # 元素入队 + deq.append(2) # 添加至队尾 + deq.append(5) + deq.append(4) + deq.appendleft(3) # 添加至队首 + deq.appendleft(1) + print("双向队列 deque =", deq) + + # 访问元素 + front: int = deq[0] # 队首元素 + print("队首元素 front =", front) + rear: int = deq[-1] # 队尾元素 + print("队尾元素 rear =", rear) + + # 元素出队 + pop_front: int = deq.popleft() # 队首元素出队 + print("队首出队元素 pop_front =", pop_front) + print("队首出队后 deque =", deq) + pop_rear: int = deq.pop() # 队尾元素出队 + print("队尾出队元素 pop_rear =", pop_rear) + print("队尾出队后 deque =", deq) + + # 获取双向队列的长度 + size: int = len(deq) + print("双向队列长度 size =", size) + + # 判断双向队列是否为空 + is_empty: bool = len(deq) == 0 + print("双向队列是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/queue/linked_queue.py b/algorithms/c01_data_structure/queue/linked_queue.py new file mode 100644 index 0000000..9d10fc0 --- /dev/null +++ b/algorithms/c01_data_structure/queue/linked_queue.py @@ -0,0 +1,42 @@ +# -*- encoding: utf-8 -*- +"""基于链表实现的一个简单队列 +队尾插入元素,队头取元素。就跟在菜市场排队买菜原理是一样的 +""" +from algorithms.c01_data_structure import Node + + +class LinkedQueue: + def __init__(self): + self.first = None # beginning of queue + self.last = None # end of queue + self.n = 0 # number of elements on queue + + def is_empty(self): + return self.first is None + + def size(self): + return self.n + + def enqueue(self, item): + old_last = self.last + self.last = Node(item) # 将新插入的节点变成队尾元素 + if self.is_empty(): + self.first = self.last + else: + old_last.next = self.last # 同时将原来的队尾元素的next指向新的队尾元素 + + def dequeue(self): + if self.is_empty(): + raise LookupError('Queue underflow') + item = self.first.data + self.first = self.first.next + self.n -= 1 + if self.is_empty(): + # 如果队列现在为空了,说明之前队列中只有一个元素了。 + # 这时候需要把last赋空,防止一直引用着对象,导致无法内存回收 + self.last = None + return item + + def __iter__(self): + while self.n > 0: + yield self.dequeue() diff --git a/algorithms/c01_data_structure/queue/linkedlist_deque.py b/algorithms/c01_data_structure/queue/linkedlist_deque.py new file mode 100644 index 0000000..b47963c --- /dev/null +++ b/algorithms/c01_data_structure/queue/linkedlist_deque.py @@ -0,0 +1,150 @@ +""" +File: linkedlist_deque.py +Created Time: 2023-03-01 +""" + + +class ListNode: + """双向链表节点""" + + def __init__(self, val: int): + """构造方法""" + self.val: int = val + self.next: ListNode | None = None # 后继节点引用 + self.prev: ListNode | None = None # 前驱节点引用 + + +class LinkedListDeque: + """基于双向链表实现的双向队列""" + + def __init__(self): + """构造方法""" + self._front: ListNode | None = None # 头节点 front + self._rear: ListNode | None = None # 尾节点 rear + self._size: int = 0 # 双向队列的长度 + + def size(self) -> int: + """获取双向队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断双向队列是否为空""" + return self.size() == 0 + + def push(self, num: int, is_front: bool): + """入队操作""" + node = ListNode(num) + # 若链表为空,则令 front, rear 都指向 node + if self.is_empty(): + self._front = self._rear = node + # 队首入队操作 + elif is_front: + # 将 node 添加至链表头部 + self._front.prev = node + node.next = self._front + self._front = node # 更新头节点 + # 队尾入队操作 + else: + # 将 node 添加至链表尾部 + self._rear.next = node + node.prev = self._rear + self._rear = node # 更新尾节点 + self._size += 1 # 更新队列长度 + + def push_first(self, num: int): + """队首入队""" + self.push(num, True) + + def push_last(self, num: int): + """队尾入队""" + self.push(num, False) + + def pop(self, is_front: bool) -> int: + """出队操作""" + if self.is_empty(): + raise IndexError("双向队列为空") + # 队首出队操作 + if is_front: + val: int = self._front.val # 暂存头节点值 + # 删除头节点 + fnext: ListNode | None = self._front.next + if fnext != None: + fnext.prev = None + self._front.next = None + self._front = fnext # 更新头节点 + # 队尾出队操作 + else: + val: int = self._rear.val # 暂存尾节点值 + # 删除尾节点 + rprev: ListNode | None = self._rear.prev + if rprev != None: + rprev.next = None + self._rear.prev = None + self._rear = rprev # 更新尾节点 + self._size -= 1 # 更新队列长度 + return val + + def pop_first(self) -> int: + """队首出队""" + return self.pop(True) + + def pop_last(self) -> int: + """队尾出队""" + return self.pop(False) + + def peek_first(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._front.val + + def peek_last(self) -> int: + """访问队尾元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._rear.val + + def to_array(self) -> list[int]: + """返回数组用于打印""" + node = self._front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化双向队列 + deque = LinkedListDeque() + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("双向队列 deque =", deque.to_array()) + + # 访问元素 + peek_first: int = deque.peek_first() + print("队首元素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("队尾元素 peek_last =", peek_last) + + # 元素入队 + deque.push_last(4) + print("元素 4 队尾入队后 deque =", deque.to_array()) + deque.push_first(1) + print("元素 1 队首入队后 deque =", deque.to_array()) + + # 元素出队 + pop_last: int = deque.pop_last() + print("队尾出队元素 =", pop_last, ",队尾出队后 deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("队首出队元素 =", pop_first, ",队首出队后 deque =", deque.to_array()) + + # 获取双向队列的长度 + size: int = deque.size() + print("双向队列长度 size =", size) + + # 判断双向队列是否为空 + is_empty: bool = deque.is_empty() + print("双向队列是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/queue/linkedlist_queue.py b/algorithms/c01_data_structure/queue/linkedlist_queue.py new file mode 100644 index 0000000..58f1794 --- /dev/null +++ b/algorithms/c01_data_structure/queue/linkedlist_queue.py @@ -0,0 +1,97 @@ +""" +File: linkedlist_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import ListNode + + +class LinkedListQueue: + """基于链表实现的队列""" + + def __init__(self): + """构造方法""" + self._front: ListNode | None = None # 头节点 front + self._rear: ListNode | None = None # 尾节点 rear + self._size: int = 0 + + def size(self) -> int: + """获取队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断队列是否为空""" + return not self._front + + def push(self, num: int): + """入队""" + # 尾节点后添加 num + node = ListNode(num) + # 如果队列为空,则令头、尾节点都指向该节点 + if self._front is None: + self._front = node + self._rear = node + # 如果队列不为空,则将该节点添加到尾节点后 + else: + self._rear.next = node + self._rear = node + self._size += 1 + + def pop(self) -> int: + """出队""" + num = self.peek() + # 删除头节点 + self._front = self._front.next + self._size -= 1 + return num + + def peek(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("队列为空") + return self._front.val + + def to_list(self) -> list[int]: + """转化为列表用于打印""" + queue = [] + temp = self._front + while temp: + queue.append(temp.val) + temp = temp.next + return queue + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化队列 + queue = LinkedListQueue() + + # 元素入队 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("队列 queue =", queue.to_list()) + + # 访问队首元素 + peek: int = queue.peek() + print("队首元素 front =", peek) + + # 元素出队 + pop_front: int = queue.pop() + print("出队元素 pop =", pop_front) + print("出队后 queue =", queue.to_list()) + + # 获取队列的长度 + size: int = queue.size() + print("队列长度 size =", size) + + # 判断队列是否为空 + is_empty: bool = queue.is_empty() + print("队列是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/queue/prior_queue.py b/algorithms/c01_data_structure/queue/prior_queue.py new file mode 100644 index 0000000..daf0ed5 --- /dev/null +++ b/algorithms/c01_data_structure/queue/prior_queue.py @@ -0,0 +1,103 @@ +# -*- encoding: utf-8 -*- +""" +基于二叉堆实现的优先队列 +""" +from functools import total_ordering + +from algorithms.c01_data_structure.stack.stack_linked_list import LinkedStack + + +class MaxPriorQueue: + """ + 使用一个数组存储二叉堆,所有元素存储在seq[1..max_size]中,se[0]不存储任何东西。 + 优先队列支持插入和删除操作。 + 1. 插入元素放到最后一个,然后向上游到自己的位置。 + 2. 删除元素是移除第一个,然后将最后一个放到第一个,并向下沉到自己的位置。 + """ + + def __init__(self, max_size): + self._seq = [None] * (max_size + 1) # 初始化长度为max_size+1的数组 + self._size = 0 # 元素长度为0 + + def is_empty(self): + """判断队列是否为空""" + return self._size == 0 + + def size(self): + return self._size + + def insert(self, item): + self._size += 1 + self._seq[self._size] = item + self._swim(self._size) # 末端插入元素后向上游,游到自己合适的位置去 + + def del_max(self): + item = self._seq[1] # 根节点拿到最大元素,保留下来后面会作为结果返回 + self._exchange(1, self._size) # 跟最后一个元素做交换 + self._seq[self._size] = None # 清空最后一个元素 + self._size -= 1 # 长度-1 + self._sink(1) # 下沉操作,将第一个元素下沉至合适自己的位置 + return item + + def _less(self, i, j): + """比较两个元素大小,如果左边小于等于右边,返回True""" + if self._seq[i] is None or self._seq[j] is None: + raise IndexError('index error') + return self._seq[i] < self._seq[j] + + def _exchange(self, i, j): + """交换两个元素""" + self._seq[i], self._seq[j] = self._seq[j], self._seq[i] + + def _swim(self, index): + """向上游上去,游到合适的位置""" + while index > 1 and self._less(index // 2, index): + self._exchange(index // 2, index) + index = index // 2 + + def _sink(self, index): + """向下沉下去,把老大的位置叫出来,谁更牛逼谁做老大""" + while 2 * index <= self._size: + j = 2 * index + if j < self._size and self._less(j, j + 1): + j += 1 # 如果有两个下属,把最牛逼的下属拿出来做对比 + if not self._less(index, j): + break # 如果比最牛逼的那个下属还要厉害,说明这个老大位置没问题了 + self._exchange(index, j) # 如果没有下属厉害,就自己乖乖把位置让出来,跟他交换一下 + index = j # 现在index的值修改成新的位置,继续向下做对比,直到找到自己合适的位置 + + +@total_ordering +class Item: + def __init__(self, key, data, index=-1): + self.key = key + self.data = data + self.index = index + + def __eq__(self, other): + return self.key == other.key + + def __lt__(self, other): + return self.key < other.key + + def __str__(self): + return str((self.data, self.key, self.index)) + + +if __name__ == '__main__': + prior_queue = MaxPriorQueue(20) + items = [ + Item(2, "22222"), + Item(5, "55555"), + Item(5, "5===="), + Item(3, "33333"), + Item(9, "99999"), + ] + for item in items: + prior_queue.insert(item) + + stack = LinkedStack() + while not prior_queue.is_empty(): + stack.push(prior_queue.del_max()) + for item in stack: + print(item) diff --git a/algorithms/c01_data_structure/queue/queue.py b/algorithms/c01_data_structure/queue/queue.py new file mode 100644 index 0000000..923070f --- /dev/null +++ b/algorithms/c01_data_structure/queue/queue.py @@ -0,0 +1,39 @@ +""" +File: queue.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # 初始化队列 + # 在 Python 中,我们一般将双向队列类 deque 看作队列使用 + # 虽然 queue.Queue() 是纯正的队列类,但不太好用 + que: deque[int] = deque() + + # 元素入队 + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + print("队列 que =", que) + + # 访问队首元素 + front: int = que[0] + print("队首元素 front =", front) + + # 元素出队 + pop: int = que.popleft() + print("出队元素 pop =", pop) + print("出队后 que =", que) + + # 获取队列的长度 + size: int = len(que) + print("队列长度 size =", size) + + # 判断队列是否为空 + is_empty: bool = len(que) == 0 + print("队列是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/stack/__init__.py b/algorithms/c01_data_structure/stack/__init__.py new file mode 100644 index 0000000..c0053c9 --- /dev/null +++ b/algorithms/c01_data_structure/stack/__init__.py @@ -0,0 +1,7 @@ +# -*- encoding: utf-8 -*- +""" +栈数据结构 + +LeetCode题目: +20,155,224,232,496,682,844 +""" diff --git a/algorithms/c01_data_structure/stack/array_stack.py b/algorithms/c01_data_structure/stack/array_stack.py new file mode 100644 index 0000000..6f270e9 --- /dev/null +++ b/algorithms/c01_data_structure/stack/array_stack.py @@ -0,0 +1,72 @@ +""" +File: array_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayStack: + """基于数组实现的栈""" + + def __init__(self): + """构造方法""" + self._stack: list[int] = [] + + def size(self) -> int: + """获取栈的长度""" + return len(self._stack) + + def is_empty(self) -> bool: + """判断栈是否为空""" + return self._stack == [] + + def push(self, item: int): + """入栈""" + self._stack.append(item) + + def pop(self) -> int: + """出栈""" + if self.is_empty(): + raise IndexError("栈为空") + return self._stack.pop() + + def peek(self) -> int: + """访问栈顶元素""" + if self.is_empty(): + raise IndexError("栈为空") + return self._stack[-1] + + def to_list(self) -> list[int]: + """返回列表用于打印""" + return self._stack + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化栈 + stack = ArrayStack() + + # 元素入栈 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("栈 stack =", stack.to_list()) + + # 访问栈顶元素 + peek: int = stack.peek() + print("栈顶元素 peek =", peek) + + # 元素出栈 + pop: int = stack.pop() + print("出栈元素 pop =", pop) + print("出栈后 stack =", stack.to_list()) + + # 获取栈的长度 + size: int = stack.size() + print("栈的长度 size =", size) + + # 判断是否为空 + is_empty: bool = stack.is_empty() + print("栈是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/stack/linkedlist_stack.py b/algorithms/c01_data_structure/stack/linkedlist_stack.py new file mode 100644 index 0000000..34cf9ea --- /dev/null +++ b/algorithms/c01_data_structure/stack/linkedlist_stack.py @@ -0,0 +1,89 @@ +""" +File: linkedlist_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import ListNode + + +class LinkedListStack: + """基于链表实现的栈""" + + def __init__(self): + """构造方法""" + self._peek: ListNode | None = None + self._size: int = 0 + + def size(self) -> int: + """获取栈的长度""" + return self._size + + def is_empty(self) -> bool: + """判断栈是否为空""" + return not self._peek + + def push(self, val: int): + """入栈""" + node = ListNode(val) + node.next = self._peek + self._peek = node + self._size += 1 + + def pop(self) -> int: + """出栈""" + num = self.peek() + self._peek = self._peek.next + self._size -= 1 + return num + + def peek(self) -> int: + """访问栈顶元素""" + if self.is_empty(): + raise IndexError("栈为空") + return self._peek.val + + def to_list(self) -> list[int]: + """转化为列表用于打印""" + arr = [] + node = self._peek + while node: + arr.append(node.val) + node = node.next + arr.reverse() + return arr + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化栈 + stack = LinkedListStack() + + # 元素入栈 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("栈 stack =", stack.to_list()) + + # 访问栈顶元素 + peek: int = stack.peek() + print("栈顶元素 peek =", peek) + + # 元素出栈 + pop: int = stack.pop() + print("出栈元素 pop =", pop) + print("出栈后 stack =", stack.to_list()) + + # 获取栈的长度 + size: int = stack.size() + print("栈的长度 size =", size) + + # 判断是否为空 + is_empty: bool = stack.is_empty() + print("栈是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/stack/stack.py b/algorithms/c01_data_structure/stack/stack.py new file mode 100644 index 0000000..1276271 --- /dev/null +++ b/algorithms/c01_data_structure/stack/stack.py @@ -0,0 +1,36 @@ +""" +File: stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # 初始化栈 + # Python 没有内置的栈类,可以把 list 当作栈来使用 + stack: list[int] = [] + + # 元素入栈 + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("栈 stack =", stack) + + # 访问栈顶元素 + peek: int = stack[-1] + print("栈顶元素 peek =", peek) + + # 元素出栈 + pop: int = stack.pop() + print("出栈元素 pop =", pop) + print("出栈后 stack =", stack) + + # 获取栈的长度 + size: int = len(stack) + print("栈的长度 size =", size) + + # 判断是否为空 + is_empty: bool = len(stack) == 0 + print("栈是否为空 =", is_empty) diff --git a/algorithms/c01_data_structure/stack/stack_array.py b/algorithms/c01_data_structure/stack/stack_array.py new file mode 100644 index 0000000..8936f42 --- /dev/null +++ b/algorithms/c01_data_structure/stack/stack_array.py @@ -0,0 +1,55 @@ +# -*- encoding: utf-8 -*- +""" +基于数组实现的栈结构 +""" +from algorithms.c01_data_structure import Node + + +class ArrayStack: + def __init__(self, size): + self.items = [None] * size # 初始数组大小 + self.size = size # size of the stack + self.num = 0 # 元素的个数 + + def push(self, item): + """ + 入栈操作 + :param item: 入栈数据 + :return: 是否入栈成功 + """ + # 数组空间不够了,直接返回false,入栈失败。 + if self.num == self.size: + return False + # 将item放到下标为num的位置,并且num加一 + self.items[self.num] = Node(item) + self.num += 1 + return True + + def pop(self): + if self.num == 0: + return None + result = self.items[self.num - 1].data + self.items[self.num - 1] = None + self.num -= 1 + return result + + def peek(self): + if self.num == 0: + return None + return self.items[0].data + + def __iter__(self): + while self.num > 0: + yield self.pop() + + +if __name__ == '__main__': + s = ArrayStack(3) + s.push(1) + s.push(2) + s.push(3) + print("-----------------") + print(s.pop()) + print(s.pop()) + print(s.pop()) + print("=================") diff --git a/algorithms/c01_data_structure/stack/stack_linked_list.py b/algorithms/c01_data_structure/stack/stack_linked_list.py new file mode 100644 index 0000000..a733946 --- /dev/null +++ b/algorithms/c01_data_structure/stack/stack_linked_list.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +"""自己实现的一个简单栈,内部使用链表方式,同时将这个栈设计成一个可迭代对象。 +可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现`__iter__`方法,但不能实现 `__next__` 方法。 +另一方面,迭代器应该一直可以迭代。迭代器的 `__iter__` 方法应该返回自身。 +一般可使用生成器函数实现更符合python风格的可迭代对象。 +""" +from algorithms.c01_data_structure import Node + + +class LinkedStack: + def __init__(self): + self.first = None # top of stack + self.n = 0 # size of the stack + + def is_empty(self): + return self.first is None + + def size(self): + return self.n + + def push(self, item): + self.first = Node(item, self.first) + self.n += 1 + + def pop(self): + if self.is_empty(): + return None + result = self.first.data + self.first = self.first.next + self.n -= 1 + return result + + def peek(self): + if self.is_empty(): + return None + return self.first.data + + def __iter__(self): + while self.n > 0: + yield self.pop() diff --git a/algorithms/c02_tree/__init__.py b/algorithms/c02_tree/__init__.py new file mode 100644 index 0000000..40e8162 --- /dev/null +++ b/algorithms/c02_tree/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +树结构、堆结构。 +""" diff --git a/algorithms/c02_tree/array_binary_tree.py b/algorithms/c02_tree/array_binary_tree.py new file mode 100644 index 0000000..5d262a1 --- /dev/null +++ b/algorithms/c02_tree/array_binary_tree.py @@ -0,0 +1,118 @@ +""" +File: array_binary_tree.py +Created Time: 2023-07-19 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import list_to_tree, print_tree + + +class ArrayBinaryTree: + """数组表示下的二叉树类""" + + def __init__(self, arr: list[int | None]): + """构造方法""" + self._tree = list(arr) + + def size(self): + """节点数量""" + return len(self._tree) + + def val(self, i: int) -> int: + """获取索引为 i 节点的值""" + # 若索引越界,则返回 None ,代表空位 + if i < 0 or i >= self.size(): + return None + return self._tree[i] + + def left(self, i: int) -> int | None: + """获取索引为 i 节点的左子节点的索引""" + return 2 * i + 1 + + def right(self, i: int) -> int | None: + """获取索引为 i 节点的右子节点的索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int | None: + """获取索引为 i 节点的父节点的索引""" + return (i - 1) // 2 + + def level_order(self) -> list[int]: + """层序遍历""" + self.res = [] + # 直接遍历数组 + for i in range(self.size()): + if self.val(i) is not None: + self.res.append(self.val(i)) + return self.res + + def dfs(self, i: int, order: str): + """深度优先遍历""" + if self.val(i) is None: + return + # 前序遍历 + if order == "pre": + self.res.append(self.val(i)) + self.dfs(self.left(i), order) + # 中序遍历 + if order == "in": + self.res.append(self.val(i)) + self.dfs(self.right(i), order) + # 后序遍历 + if order == "post": + self.res.append(self.val(i)) + + def pre_order(self) -> list[int]: + """前序遍历""" + self.res = [] + self.dfs(0, order="pre") + return self.res + + def in_order(self) -> list[int]: + """中序遍历""" + self.res = [] + self.dfs(0, order="in") + return self.res + + def post_order(self) -> list[int]: + """后序遍历""" + self.res = [] + self.dfs(0, order="post") + return self.res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二叉树 + # 这里借助了一个从数组直接生成二叉树的函数 + arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + root = list_to_tree(arr) + print("\n初始化二叉树\n") + print("二叉树的数组表示:") + print(arr) + print("二叉树的链表表示:") + print_tree(root) + + # 数组表示下的二叉树类 + abt = ArrayBinaryTree(arr) + + # 访问节点 + i = 1 + l, r, p = abt.left(i), abt.right(i), abt.parent(i) + print(f"\n当前节点的索引为 {i} ,值为 {abt.val(i)}") + print(f"其左子节点的索引为 {l} ,值为 {abt.val(l)}") + print(f"其右子节点的索引为 {r} ,值为 {abt.val(r)}") + print(f"其父节点的索引为 {p} ,值为 {abt.val(p)}") + + # 遍历树 + res = abt.level_order() + print("\n层序遍历为:", res) + res = abt.pre_order() + print("前序遍历为:", res) + res = abt.in_order() + print("中序遍历为:", res) + res = abt.post_order() + print("后序遍历为:", res) diff --git a/algorithms/c02_tree/avl_tree.py b/algorithms/c02_tree/avl_tree.py new file mode 100644 index 0000000..2a29a16 --- /dev/null +++ b/algorithms/c02_tree/avl_tree.py @@ -0,0 +1,202 @@ +""" +File: avl_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree + + +class AVLTree: + """AVL 树""" + + def __init__(self): + """构造方法""" + self._root = None + + def get_root(self) -> TreeNode | None: + """获取二叉树根节点""" + return self._root + + def height(self, node: TreeNode | None) -> int: + """获取节点高度""" + # 空节点高度为 -1 ,叶节点高度为 0 + if node is not None: + return node.height + return -1 + + def update_height(self, node: TreeNode | None): + """更新节点高度""" + # 节点高度等于最高子树高度 + 1 + node.height = max([self.height(node.left), self.height(node.right)]) + 1 + + def balance_factor(self, node: TreeNode | None) -> int: + """获取平衡因子""" + # 空节点平衡因子为 0 + if node is None: + return 0 + # 节点平衡因子 = 左子树高度 - 右子树高度 + return self.height(node.left) - self.height(node.right) + + def right_rotate(self, node: TreeNode | None) -> TreeNode | None: + """右旋操作""" + child = node.left + grand_child = child.right + # 以 child 为原点,将 node 向右旋转 + child.right = node + node.left = grand_child + # 更新节点高度 + self.update_height(node) + self.update_height(child) + # 返回旋转后子树的根节点 + return child + + def left_rotate(self, node: TreeNode | None) -> TreeNode | None: + """左旋操作""" + child = node.right + grand_child = child.left + # 以 child 为原点,将 node 向左旋转 + child.left = node + node.right = grand_child + # 更新节点高度 + self.update_height(node) + self.update_height(child) + # 返回旋转后子树的根节点 + return child + + def rotate(self, node: TreeNode | None) -> TreeNode | None: + """执行旋转操作,使该子树重新恢复平衡""" + # 获取节点 node 的平衡因子 + balance_factor = self.balance_factor(node) + # 左偏树 + if balance_factor > 1: + if self.balance_factor(node.left) >= 0: + # 右旋 + return self.right_rotate(node) + else: + # 先左旋后右旋 + node.left = self.left_rotate(node.left) + return self.right_rotate(node) + # 右偏树 + elif balance_factor < -1: + if self.balance_factor(node.right) <= 0: + # 左旋 + return self.left_rotate(node) + else: + # 先右旋后左旋 + node.right = self.right_rotate(node.right) + return self.left_rotate(node) + # 平衡树,无须旋转,直接返回 + return node + + def insert(self, val): + """插入节点""" + self._root = self.insert_helper(self._root, val) + + def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: + """递归插入节点(辅助方法)""" + if node is None: + return TreeNode(val) + # 1. 查找插入位置,并插入节点 + if val < node.val: + node.left = self.insert_helper(node.left, val) + elif val > node.val: + node.right = self.insert_helper(node.right, val) + else: + # 重复节点不插入,直接返回 + return node + # 更新节点高度 + self.update_height(node) + # 2. 执行旋转操作,使该子树重新恢复平衡 + return self.rotate(node) + + def remove(self, val: int): + """删除节点""" + self._root = self.remove_helper(self._root, val) + + def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: + """递归删除节点(辅助方法)""" + if node is None: + return None + # 1. 查找节点,并删除之 + if val < node.val: + node.left = self.remove_helper(node.left, val) + elif val > node.val: + node.right = self.remove_helper(node.right, val) + else: + if node.left is None or node.right is None: + child = node.left or node.right + # 子节点数量 = 0 ,直接删除 node 并返回 + if child is None: + return None + # 子节点数量 = 1 ,直接删除 node + else: + node = child + else: + # 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + temp = node.right + while temp.left is not None: + temp = temp.left + node.right = self.remove_helper(node.right, temp.val) + node.val = temp.val + # 更新节点高度 + self.update_height(node) + # 2. 执行旋转操作,使该子树重新恢复平衡 + return self.rotate(node) + + def search(self, val: int) -> TreeNode | None: + """查找节点""" + cur = self._root + # 循环查找,越过叶节点后跳出 + while cur is not None: + # 目标节点在 cur 的右子树中 + if cur.val < val: + cur = cur.right + # 目标节点在 cur 的左子树中 + elif cur.val > val: + cur = cur.left + # 找到目标节点,跳出循环 + else: + break + # 返回目标节点 + return cur + + +"""Driver Code""" +if __name__ == "__main__": + + def test_insert(tree: AVLTree, val: int): + tree.insert(val) + print("\n插入节点 {} 后,AVL 树为".format(val)) + print_tree(tree.get_root()) + + + def test_remove(tree: AVLTree, val: int): + tree.remove(val) + print("\n删除节点 {} 后,AVL 树为".format(val)) + print_tree(tree.get_root()) + + + # 初始化空 AVL 树 + avl_tree = AVLTree() + + # 插入节点 + # 请关注插入节点后,AVL 树是如何保持平衡的 + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]: + test_insert(avl_tree, val) + + # 插入重复节点 + test_insert(avl_tree, 7) + + # 删除节点 + # 请关注删除节点后,AVL 树是如何保持平衡的 + test_remove(avl_tree, 8) # 删除度为 0 的节点 + test_remove(avl_tree, 5) # 删除度为 1 的节点 + test_remove(avl_tree, 4) # 删除度为 2 的节点 + + result_node = avl_tree.search(7) + print("\n查找到的节点对象为 {},节点值 = {}".format(result_node, result_node.val)) diff --git a/algorithms/c02_tree/binary_search_tree.py b/algorithms/c02_tree/binary_search_tree.py new file mode 100644 index 0000000..0a6eb9d --- /dev/null +++ b/algorithms/c02_tree/binary_search_tree.py @@ -0,0 +1,146 @@ +""" +File: binary_search_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree + + +class BinarySearchTree: + """二叉搜索树""" + + def __init__(self): + """构造方法""" + # 初始化空树 + self._root = None + + def get_root(self) -> TreeNode | None: + """获取二叉树根节点""" + return self._root + + def search(self, num: int) -> TreeNode | None: + """查找节点""" + cur = self._root + # 循环查找,越过叶节点后跳出 + while cur is not None: + # 目标节点在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 目标节点在 cur 的左子树中 + elif cur.val > num: + cur = cur.left + # 找到目标节点,跳出循环 + else: + break + return cur + + def insert(self, num: int): + """插入节点""" + # 若树为空,则初始化根节点 + if self._root is None: + self._root = TreeNode(num) + return + # 循环查找,越过叶节点后跳出 + cur, pre = self._root, None + while cur is not None: + # 找到重复节点,直接返回 + if cur.val == num: + return + pre = cur + # 插入位置在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 插入位置在 cur 的左子树中 + else: + cur = cur.left + # 插入节点 + node = TreeNode(num) + if pre.val < num: + pre.right = node + else: + pre.left = node + + def remove(self, num: int): + """删除节点""" + # 若树为空,直接提前返回 + if self._root is None: + return + # 循环查找,越过叶节点后跳出 + cur, pre = self._root, None + while cur is not None: + # 找到待删除节点,跳出循环 + if cur.val == num: + break + pre = cur + # 待删除节点在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 待删除节点在 cur 的左子树中 + else: + cur = cur.left + # 若无待删除节点,则直接返回 + if cur is None: + return + + # 子节点数量 = 0 or 1 + if cur.left is None or cur.right is None: + # 当子节点数量 = 0 / 1 时, child = null / 该子节点 + child = cur.left or cur.right + # 删除节点 cur + if cur != self._root: + if pre.left == cur: + pre.left = child + else: + pre.right = child + else: + # 若删除节点为根节点,则重新指定根节点 + self._root = child + # 子节点数量 = 2 + else: + # 获取中序遍历中 cur 的下一个节点 + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # 递归删除节点 tmp + self.remove(tmp.val) + # 用 tmp 覆盖 cur + cur.val = tmp.val + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二叉搜索树 + bst = BinarySearchTree() + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + for num in nums: + bst.insert(num) + print("\n初始化的二叉树为\n") + print_tree(bst.get_root()) + + # 查找节点 + node = bst.search(7) + print("\n查找到的节点对象为: {},节点值 = {}".format(node, node.val)) + + # 插入节点 + bst.insert(16) + print("\n插入节点 16 后,二叉树为\n") + print_tree(bst.get_root()) + + # 删除节点 + bst.remove(1) + print("\n删除节点 1 后,二叉树为\n") + print_tree(bst.get_root()) + + bst.remove(2) + print("\n删除节点 2 后,二叉树为\n") + print_tree(bst.get_root()) + + bst.remove(4) + print("\n删除节点 4 后,二叉树为\n") + print_tree(bst.get_root()) diff --git a/algorithms/c02_tree/binary_tree.py b/algorithms/c02_tree/binary_tree.py new file mode 100644 index 0000000..674ee14 --- /dev/null +++ b/algorithms/c02_tree/binary_tree.py @@ -0,0 +1,40 @@ +""" +File: binary_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二叉树 + # 初始化节点 + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # 构建引用指向(即指针) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\n初始化二叉树\n") + print_tree(n1) + + # 插入与删除节点 + P = TreeNode(0) + # 在 n1 -> n2 中间插入节点 P + n1.left = P + P.left = n2 + print("\n插入节点 P 后\n") + print_tree(n1) + # 删除节点 + n1.left = n2 + print("\n删除节点 P 后\n") + print_tree(n1) diff --git a/algorithms/c02_tree/binary_tree_bfs.py b/algorithms/c02_tree/binary_tree_bfs.py new file mode 100644 index 0000000..e09765b --- /dev/null +++ b/algorithms/c02_tree/binary_tree_bfs.py @@ -0,0 +1,42 @@ +""" +File: binary_tree_bfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, list_to_tree, print_tree +from collections import deque + + +def level_order(root: TreeNode | None) -> list[int]: + """层序遍历""" + # 初始化队列,加入根节点 + queue: deque[TreeNode] = deque() + queue.append(root) + # 初始化一个列表,用于保存遍历序列 + res = [] + while queue: + node: TreeNode = queue.popleft() # 队列出队 + res.append(node.val) # 保存节点值 + if node.left is not None: + queue.append(node.left) # 左子节点入队 + if node.right is not None: + queue.append(node.right) # 右子节点入队 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二叉树 + # 这里借助了一个从数组直接生成二叉树的函数 + root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\n初始化二叉树\n") + print_tree(root) + + # 层序遍历 + res: list[int] = level_order(root) + print("\n层序遍历的节点打印序列 = ", res) diff --git a/algorithms/c02_tree/binary_tree_dfs.py b/algorithms/c02_tree/binary_tree_dfs.py new file mode 100644 index 0000000..91b93bc --- /dev/null +++ b/algorithms/c02_tree/binary_tree_dfs.py @@ -0,0 +1,65 @@ +""" +File: binary_tree_dfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, list_to_tree, print_tree + + +def pre_order(root: TreeNode | None): + """前序遍历""" + if root is None: + return + # 访问优先级:根节点 -> 左子树 -> 右子树 + res.append(root.val) + pre_order(root=root.left) + pre_order(root=root.right) + + +def in_order(root: TreeNode | None): + """中序遍历""" + if root is None: + return + # 访问优先级:左子树 -> 根节点 -> 右子树 + in_order(root=root.left) + res.append(root.val) + in_order(root=root.right) + + +def post_order(root: TreeNode | None): + """后序遍历""" + if root is None: + return + # 访问优先级:左子树 -> 右子树 -> 根节点 + post_order(root=root.left) + post_order(root=root.right) + res.append(root.val) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化二叉树 + # 这里借助了一个从数组直接生成二叉树的函数 + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\n初始化二叉树\n") + print_tree(root) + + # 前序遍历 + res = [] + pre_order(root) + print("\n前序遍历的节点打印序列 = ", res) + + # 中序遍历 + res.clear() + in_order(root) + print("\n中序遍历的节点打印序列 = ", res) + + # 后序遍历 + res.clear() + post_order(root) + print("\n后序遍历的节点打印序列 = ", res) diff --git a/ch03_datastruct/at301_bisearch_tree.py b/algorithms/c02_tree/bisearch_tree.py similarity index 94% rename from ch03_datastruct/at301_bisearch_tree.py rename to algorithms/c02_tree/bisearch_tree.py index 82ebd72..68033e7 100644 --- a/ch03_datastruct/at301_bisearch_tree.py +++ b/algorithms/c02_tree/bisearch_tree.py @@ -1,12 +1,9 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# 二叉搜索树 """ - Topic: sample - Desc : 二叉搜索树 - 二叉搜索树是指:对每个节点,其左子树元素不大于它,右子树元素不小于它 +二叉搜索树 +二叉搜索树是指:对每个节点,其左子树元素不大于它,右子树元素不小于它 """ -__author__ = 'Xiong Neng' class Tree(): @@ -160,5 +157,3 @@ def transplant(T, u, v): inOrderWalk(tree) treeDelete(tree, n) inOrderWalk(tree) - - diff --git a/algorithms/c02_tree/btree.py b/algorithms/c02_tree/btree.py new file mode 100644 index 0000000..15a223c --- /dev/null +++ b/algorithms/c02_tree/btree.py @@ -0,0 +1,129 @@ +# -*- encoding: utf-8 -*- +""" +description +""" + + +class BTreeNode: + def __init__(self, leaf=False): + self.keys = [] + self.children = [] + self.leaf = leaf + + def split(self, parent, key): + new_node = BTreeNode(self.leaf) + + mid = len(self.keys) // 2 + split_key = self.keys[mid] + + parent.add_key(split_key) + + new_node.children = self.children[mid + 1:] + self.children = self.children[:mid + 1] + + new_node.keys = self.keys[mid + 1:] + self.keys = self.keys[:mid] + + parent.children = parent.add_child(new_node) + + if key < split_key: + return self + else: + return new_node + + def add_key(self, key): + self.keys.append(key) + self.keys.sort() + + def add_child(self, new_node): + i = len(self.children) - 1 + while i >= 0 and self.children[i].keys[0] > new_node.keys[0]: + i -= 1 + return self.children[:i + 1] + [new_node] + self.children[i + 1:] + + def __str__(self): + return str(self.keys) + + +class BTree: + def __init__(self, t): + self.t = t + self.root = BTreeNode(leaf=True) + + def insert(self, key): + node = self.root + + if len(node.keys) == (2 * self.t) - 1: + new_root = BTreeNode() + new_root.children.append(self.root) + new_root.leaf = False + + node = node.split(new_root, key) + self.root = new_root + while not node.leaf: + i = 0 + while i < len(node.keys) and key > node.keys[i]: + i += 1 + + if len(node.children[i].keys) == (2 * self.t) - 1: + node = node.split(node, i) + if key > node.keys[i]: + i += 1 + + node = node.children[i] + + if key not in node.keys: + node.add_key(key) + + if len(node.keys) == (2 * self.t) - 1: + self.split(node) + + def split(self, node): + new_node = BTreeNode(node.leaf) + + mid = len(node.keys) // 2 + split_key = node.keys[mid] + + parent = node + parent.add_key(split_key) + + new_node.children = node.children[mid + 1:] + node.children = node.children[:mid + 1] + + new_node.keys = node.keys[mid + 1:] + node.keys = node.keys[:mid] + + parent.children = parent.add_child(new_node) + + def __str__(self): + return str(self.root) + + +# Example usage: +tree = BTree(t=2) + +tree.insert(1) +tree.insert(3) +tree.insert(7) +tree.insert(10) +tree.insert(11) +tree.insert(13) +tree.insert(14) +tree.insert(15) +tree.insert(18) +tree.insert(16) +tree.insert(19) +tree.insert(24) +tree.insert(25) +tree.insert(26) +tree.insert(21) +tree.insert(4) +tree.insert(5) +tree.insert(20) +tree.insert(22) +tree.insert(2) +tree.insert(17) +tree.insert(12) +tree.insert(6) + +print(tree) diff --git a/algorithms/c02_tree/heap.py b/algorithms/c02_tree/heap.py new file mode 100644 index 0000000..3ff78a5 --- /dev/null +++ b/algorithms/c02_tree/heap.py @@ -0,0 +1,70 @@ +""" +File: heap.py +Created Time: 2023-02-23 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import print_heap + +import heapq + + +def test_push(heap: list, val: int, flag: int = 1): + heapq.heappush(heap, flag * val) # 元素入堆 + print(f"\n元素 {val} 入堆后") + print_heap([flag * val for val in heap]) + + +def test_pop(heap: list, flag: int = 1): + val = flag * heapq.heappop(heap) # 堆顶元素出堆 + print(f"\n堆顶元素 {val} 出堆后") + print_heap([flag * val for val in heap]) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化小顶堆 + min_heap, flag = [], 1 + # 初始化大顶堆 + max_heap, flag = [], -1 + + print("\n以下测试样例为大顶堆") + # Python 的 heapq 模块默认实现小顶堆 + # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + + # 元素入堆 + test_push(max_heap, 1, flag) + test_push(max_heap, 3, flag) + test_push(max_heap, 2, flag) + test_push(max_heap, 5, flag) + test_push(max_heap, 4, flag) + + # 获取堆顶元素 + peek: int = flag * max_heap[0] + print(f"\n堆顶元素为 {peek}") + + # 堆顶元素出堆 + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + # 获取堆大小 + size: int = len(max_heap) + print(f"\n堆元素数量为 {size}") + + # 判断堆是否为空 + is_empty: bool = not max_heap + print(f"\n堆是否为空 {is_empty}") + + # 输入列表并建堆 + # 时间复杂度为 O(n) ,而非 O(nlogn) + min_heap = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + print("\n输入列表并建立小顶堆后") + print_heap(min_heap) diff --git a/algorithms/c02_tree/my_heap.py b/algorithms/c02_tree/my_heap.py new file mode 100644 index 0000000..d70344a --- /dev/null +++ b/algorithms/c02_tree/my_heap.py @@ -0,0 +1,136 @@ +""" +File: my_heap.py +Created Time: 2023-02-23 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import print_heap + + +class MaxHeap: + """大顶堆""" + + def __init__(self, nums: list[int]): + """构造方法,根据输入列表建堆""" + # 将列表元素原封不动添加进堆 + self.max_heap = nums + # 堆化除叶节点以外的其他所有节点 + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) + + def left(self, i: int) -> int: + """获取左子节点索引""" + return 2 * i + 1 + + def right(self, i: int) -> int: + """获取右子节点索引""" + return 2 * i + 2 + + def parent(self, i: int) -> int: + """获取父节点索引""" + return (i - 1) // 2 # 向下整除 + + def swap(self, i: int, j: int): + """交换元素""" + self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] + + def size(self) -> int: + """获取堆大小""" + return len(self.max_heap) + + def is_empty(self) -> bool: + """判断堆是否为空""" + return self.size() == 0 + + def peek(self) -> int: + """访问堆顶元素""" + return self.max_heap[0] + + def push(self, val: int): + """元素入堆""" + # 添加节点 + self.max_heap.append(val) + # 从底至顶堆化 + self.sift_up(self.size() - 1) + + def sift_up(self, i: int): + """从节点 i 开始,从底至顶堆化""" + while True: + # 获取节点 i 的父节点 + p = self.parent(i) + # 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # 交换两节点 + self.swap(i, p) + # 循环向上堆化 + i = p + + def pop(self) -> int: + """元素出堆""" + # 判空处理 + if self.is_empty(): + raise IndexError("堆为空") + # 交换根节点与最右叶节点(即交换首元素与尾元素) + self.swap(0, self.size() - 1) + # 删除节点 + val = self.max_heap.pop() + # 从顶至底堆化 + self.sift_down(0) + # 返回堆顶元素 + return val + + def sift_down(self, i: int): + """从节点 i 开始,从顶至底堆化""" + while True: + # 判断节点 i, l, r 中值最大的节点,记为 ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i: + break + # 交换两节点 + self.swap(i, ma) + # 循环向下堆化 + i = ma + + def print(self): + """打印堆(二叉树)""" + print_heap(self.max_heap) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化大顶堆 + max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\n输入列表并建堆后") + max_heap.print() + + # 获取堆顶元素 + peek = max_heap.peek() + print(f"\n堆顶元素为 {peek}") + + # 元素入堆 + val = 7 + max_heap.push(val) + print(f"\n元素 {val} 入堆后") + max_heap.print() + + # 堆顶元素出堆 + peek = max_heap.pop() + print(f"\n堆顶元素 {peek} 出堆后") + max_heap.print() + + # 获取堆大小 + size = max_heap.size() + print(f"\n堆元素数量为 {size}") + + # 判断堆是否为空 + is_empty = max_heap.is_empty() + print(f"\n堆是否为空 {is_empty}") diff --git a/ch03_datastruct/at302_redblack_tree.py b/algorithms/c02_tree/redblack_tree.py similarity index 89% rename from ch03_datastruct/at302_redblack_tree.py rename to algorithms/c02_tree/redblack_tree.py index 993e8b4..281f937 100644 --- a/ch03_datastruct/at302_redblack_tree.py +++ b/algorithms/c02_tree/redblack_tree.py @@ -1,20 +1,16 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# 红黑树 """ - Topic: sample - Desc : 红黑树 - 红黑树是满足下面红黑性质的二叉搜索树: - 1,每个结点或者是红色的,或者是黑色的 - 2,根结点黑色 - 3,每个叶子结点(None)是黑色的 - 4,如果一个结点是红色的,则它的两个子结点都是黑色的 - 5,对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。 - 一个有n个内部结点的红黑树的高度最多为2lg(n+1) +Desc : 红黑树 + 红黑树是满足下面红黑性质的二叉搜索树: + 1,每个结点或者是红色的,或者是黑色的 + 2,根结点黑色 + 3,每个叶子结点(None)是黑色的 + 4,如果一个结点是红色的,则它的两个子结点都是黑色的 + 5,对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。 +一个有n个内部结点的红黑树的高度最多为2lg(n+1) """ -from .at301_bisearch_tree import treeMinimum, inOrderWalk - -__author__ = 'Xiong Neng' +from algorithms.c02_tree.bisearch_tree import treeMinimum class RBTree(): @@ -241,6 +237,7 @@ def inOrderRBWalkNode(node, ni): print(node.key), inOrderRBWalkNode(node.right, ni) + if __name__ == '__main__': ss = [4, 23, 65, 22, 12, 3, 7, 1, 256, 34, 27] tree = RBTree() @@ -251,4 +248,4 @@ def inOrderRBWalkNode(node, ni): rbTreeInsert(tree, n) inOrderRBWalk(tree) rbTreeDelete(tree, n) - inOrderRBWalk(tree) \ No newline at end of file + inOrderRBWalk(tree) diff --git a/algorithms/c02_tree/top_k.py b/algorithms/c02_tree/top_k.py new file mode 100644 index 0000000..9d4b468 --- /dev/null +++ b/algorithms/c02_tree/top_k.py @@ -0,0 +1,38 @@ +""" +File: top_k.py +Created Time: 2023-06-10 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import print_heap + +import heapq + + +def top_k_heap(nums: list[int], k: int) -> list[int]: + """基于堆查找数组中最大的 k 个元素""" + # 初始化小顶堆 + heap = [] + # 将数组的前 k 个元素入堆 + for i in range(k): + heapq.heappush(heap, nums[i]) + # 从第 k+1 个元素开始,保持堆的长度为 k + for i in range(k, len(nums)): + # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > heap[0]: + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + return heap + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + print(f"最大的 {k} 个元素为") + print_heap(res) diff --git a/algorithms/c03_graph/__init__.py b/algorithms/c03_graph/__init__.py new file mode 100644 index 0000000..7cc5861 --- /dev/null +++ b/algorithms/c03_graph/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +"""图算法 +Some of description... +""" diff --git a/algorithms/c03_graph/graph_adjacency_list.py b/algorithms/c03_graph/graph_adjacency_list.py new file mode 100644 index 0000000..ff7f61f --- /dev/null +++ b/algorithms/c03_graph/graph_adjacency_list.py @@ -0,0 +1,110 @@ +""" +File: graph_adjacency_list.py +Created Time: 2023-02-23 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import Vertex, vals_to_vets + + +class GraphAdjList: + """基于邻接表实现的无向图类""" + + def __init__(self, edges: list[list[Vertex]]): + """构造方法""" + # 邻接表,key: 顶点,value:该顶点的所有邻接顶点 + self.adj_list = dict[Vertex, list[Vertex]]() + # 添加所有顶点和边 + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + def size(self) -> int: + """获取顶点数量""" + return len(self.adj_list) + + def add_edge(self, vet1: Vertex, vet2: Vertex): + """添加边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 添加边 vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """删除边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 删除边 vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + def add_vertex(self, vet: Vertex): + """添加顶点""" + if vet in self.adj_list: + return + # 在邻接表中添加一个新链表 + self.adj_list[vet] = [] + + def remove_vertex(self, vet: Vertex): + """删除顶点""" + if vet not in self.adj_list: + raise ValueError() + # 在邻接表中删除顶点 vet 对应的链表 + self.adj_list.pop(vet) + # 遍历其他顶点的链表,删除所有包含 vet 的边 + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) + + def print(self): + """打印邻接表""" + print("邻接表 =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + # 添加边 + # 顶点 1, 2 即 v[0], v[2] + graph.add_edge(v[0], v[2]) + print("\n添加边 1-2 后,图为") + graph.print() + + # 删除边 + # 顶点 1, 3 即 v[0], v[1] + graph.remove_edge(v[0], v[1]) + print("\n删除边 1-3 后,图为") + graph.print() + + # 添加顶点 + v5 = Vertex(6) + graph.add_vertex(v5) + print("\n添加顶点 6 后,图为") + graph.print() + + # 删除顶点 + # 顶点 3 即 v[1] + graph.remove_vertex(v[1]) + print("\n删除顶点 3 后,图为") + graph.print() diff --git a/algorithms/c03_graph/graph_adjacency_matrix.py b/algorithms/c03_graph/graph_adjacency_matrix.py new file mode 100644 index 0000000..d9afd01 --- /dev/null +++ b/algorithms/c03_graph/graph_adjacency_matrix.py @@ -0,0 +1,115 @@ +""" +File: graph_adjacency_matrix.py +Created Time: 2023-02-23 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import print_matrix + + +class GraphAdjMat: + """基于邻接矩阵实现的无向图类""" + + def __init__(self, vertices: list[int], edges: list[list[int]]): + """构造方法""" + # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + self.vertices: list[int] = [] + # 邻接矩阵,行列索引对应“顶点索引” + self.adj_mat: list[list[int]] = [] + # 添加顶点 + for val in vertices: + self.add_vertex(val) + # 添加边 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """获取顶点数量""" + return len(self.vertices) + + def add_vertex(self, val: int): + """添加顶点""" + n = self.size() + # 向顶点列表中添加新顶点的值 + self.vertices.append(val) + # 在邻接矩阵中添加一行 + new_row = [0] * n + self.adj_mat.append(new_row) + # 在邻接矩阵中添加一列 + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """删除顶点""" + if index >= self.size(): + raise IndexError() + # 在顶点列表中移除索引 index 的顶点 + self.vertices.pop(index) + # 在邻接矩阵中删除索引 index 的行 + self.adj_mat.pop(index) + # 在邻接矩阵中删除索引 index 的列 + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """添加边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + def remove_edge(self, i: int, j: int): + """删除边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 + + def print(self): + """打印邻接矩阵""" + print("顶点列表 =", self.vertices) + print("邻接矩阵 =") + print_matrix(self.adj_mat) + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat(vertices, edges) + print("\n初始化后,图为") + graph.print() + + # 添加边 + # 顶点 1, 2 的索引分别为 0, 2 + graph.add_edge(0, 2) + print("\n添加边 1-2 后,图为") + graph.print() + + # 删除边 + # 顶点 1, 3 的索引分别为 0, 1 + graph.remove_edge(0, 1) + print("\n删除边 1-3 后,图为") + graph.print() + + # 添加顶点 + graph.add_vertex(6) + print("\n添加顶点 6 后,图为") + graph.print() + + # 删除顶点 + # 顶点 3 的索引为 1 + graph.remove_vertex(1) + print("\n删除顶点 3 后,图为") + graph.print() diff --git a/algorithms/c03_graph/graph_bfs.py b/algorithms/c03_graph/graph_bfs.py new file mode 100644 index 0000000..2ff4281 --- /dev/null +++ b/algorithms/c03_graph/graph_bfs.py @@ -0,0 +1,63 @@ +""" +File: graph_bfs.py +Created Time: 2023-02-23 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import Vertex, vals_to_vets, vets_to_vals +from collections import deque +from graph_adjacency_list import GraphAdjList + + +def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """广度优先遍历 BFS""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set[Vertex]([start_vet]) + # 队列用于实现 BFS + que = deque[Vertex]([start_vet]) + # 以顶点 vet 为起点,循环直至访问完所有顶点 + while len(que) > 0: + vet = que.popleft() # 队首顶点出队 + res.append(vet) # 记录访问顶点 + # 遍历该顶点的所有邻接顶点 + for adj_vet in graph.adj_list[vet]: + if adj_vet in visited: + continue # 跳过已被访问过的顶点 + que.append(adj_vet) # 只入队未访问的顶点 + visited.add(adj_vet) # 标记该顶点已被访问 + # 返回顶点遍历序列 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + # 广度优先遍历 BFS + res = graph_bfs(graph, v[0]) + print("\n广度优先遍历(BFS)顶点序列为") + print(vets_to_vals(res)) diff --git a/algorithms/c03_graph/graph_dfs.py b/algorithms/c03_graph/graph_dfs.py new file mode 100644 index 0000000..d455b37 --- /dev/null +++ b/algorithms/c03_graph/graph_dfs.py @@ -0,0 +1,56 @@ +""" +File: graph_dfs.py +Created Time: 2023-02-23 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import Vertex, vets_to_vals, vals_to_vets +from graph_adjacency_list import GraphAdjList + + +def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """深度优先遍历 DFS 辅助函数""" + res.append(vet) # 记录访问顶点 + visited.add(vet) # 标记该顶点已被访问 + # 遍历该顶点的所有邻接顶点 + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # 跳过已被访问过的顶点 + # 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) + + +def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """深度优先遍历 DFS""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set[Vertex]() + dfs(graph, visited, res, start_vet) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 初始化无向图 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList(edges) + print("\n初始化后,图为") + graph.print() + + # 深度优先遍历 DFS + res = graph_dfs(graph, v[0]) + print("\n深度优先遍历(DFS)顶点序列为") + print(vets_to_vals(res)) diff --git a/algorithms/c04_sort/__init__.py b/algorithms/c04_sort/__init__.py new file mode 100644 index 0000000..2edcf76 --- /dev/null +++ b/algorithms/c04_sort/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +Topic: 各类排序算法 +""" diff --git a/ch05_greedy/__init__.py b/algorithms/c04_sort/base/__init__.py similarity index 88% rename from ch05_greedy/__init__.py rename to algorithms/c04_sort/base/__init__.py index 0baca96..692a585 100644 --- a/ch05_greedy/__init__.py +++ b/algorithms/c04_sort/base/__init__.py @@ -2,6 +2,4 @@ # -*- encoding: utf-8 -*- """ Topic: sample -Desc : """ - diff --git a/algorithms/c04_sort/base/sortutil.py b/algorithms/c04_sort/base/sortutil.py new file mode 100644 index 0000000..c41a4c4 --- /dev/null +++ b/algorithms/c04_sort/base/sortutil.py @@ -0,0 +1,16 @@ +# -*- encoding: utf-8 -*- +""" +将重复的方法抽取出来 +""" + + +def insert_sort(seq_): + """插入排序""" + for j in range(1, len(seq_)): + key = seq_[j] + # insert arrays[j] into the sorted seq[0...j-1] + i = j - 1 + while i >= 0 and key < seq_[i]: + seq_[i + 1] = seq_[i] # element move forward + i -= 1 + seq_[i + 1] = key # at last, put key to right place diff --git a/algorithms/c04_sort/base/template.py b/algorithms/c04_sort/base/template.py new file mode 100644 index 0000000..b67f7ca --- /dev/null +++ b/algorithms/c04_sort/base/template.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +Topic: 排序模板 +""" + + +class SortTemplate: + + def __init__(self, seq): + self.seq = seq + + def sort(self): + """ + 排序方法 + """ + pass + + def less(self, val1, val2): + """ + 对比两个元素,如果从小到大则返回True + """ + return val1 <= val2 + + def show(self, vals): + for val in vals: + print("{}".format(val), end=' ') + + def is_sorted(self, vals): + for i in range(1, len(vals)): + if self.less(vals[i], vals[i - 1]): + return False + return True + + def main(self): + self.sort() + assert self.is_sorted(self.seq) + self.show(self.seq) diff --git a/algorithms/c04_sort/bubble_sort.py b/algorithms/c04_sort/bubble_sort.py new file mode 100644 index 0000000..238444e --- /dev/null +++ b/algorithms/c04_sort/bubble_sort.py @@ -0,0 +1,44 @@ +""" +File: bubble_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def bubble_sort(nums: list[int]): + """冒泡排序""" + n = len(nums) + # 外循环:未排序区间为 [0, i] + for i in range(n - 1, 0, -1): + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交换 nums[j] 与 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + + +def bubble_sort_with_flag(nums: list[int]): + """冒泡排序(标志优化)""" + n = len(nums) + # 外循环:未排序区间为 [0, i] + for i in range(n - 1, 0, -1): + flag = False # 初始化标志位 + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交换 nums[j] 与 nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = True # 记录交换元素 + if not flag: + break # 此轮冒泡未交换任何元素,直接跳出 + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + print("冒泡排序完成后 nums =", nums) + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + print("冒泡排序完成后 nums =", nums1) diff --git a/algorithms/c04_sort/bucket_sort.py b/algorithms/c04_sort/bucket_sort.py new file mode 100644 index 0000000..8ff7d79 --- /dev/null +++ b/algorithms/c04_sort/bucket_sort.py @@ -0,0 +1,34 @@ +""" +File: bucket_sort.py +Created Time: 2023-03-30 +""" + + +def bucket_sort(nums: list[float]): + """桶排序""" + # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + k = len(nums) // 2 + buckets = [[] for _ in range(k)] + # 1. 将数组元素分配到各个桶中 + for num in nums: + # 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + i = int(num * k) + # 将 num 添加进桶 i + buckets[i].append(num) + # 2. 对各个桶执行排序 + for bucket in buckets: + # 使用内置排序函数,也可以替换成其他排序算法 + bucket.sort() + # 3. 遍历桶合并结果 + i = 0 + for bucket in buckets: + for num in bucket: + nums[i] = num + i += 1 + + +if __name__ == "__main__": + # 设输入数据为浮点数,范围为 [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + print("桶排序完成后 nums =", nums) diff --git a/algorithms/c04_sort/counting_sort.py b/algorithms/c04_sort/counting_sort.py new file mode 100644 index 0000000..ffe4159 --- /dev/null +++ b/algorithms/c04_sort/counting_sort.py @@ -0,0 +1,63 @@ +""" +File: counting_sort.py +Created Time: 2023-03-21 +""" + + +def counting_sort_naive(nums: list[int]): + """计数排序""" + # 简单实现,无法用于排序对象 + # 1. 统计数组最大元素 m + m = 0 + for num in nums: + m = max(m, num) + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 遍历 counter ,将各元素填入原数组 nums + i = 0 + for num in range(m + 1): + for _ in range(counter[num]): + nums[i] = num + i += 1 + + +def counting_sort(nums: list[int]): + """计数排序""" + # 完整实现,可排序对象,并且是稳定排序 + # 1. 统计数组最大元素 m + m = max(nums) + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i in range(m): + counter[i + 1] += counter[i] + # 4. 倒序遍历 nums ,将各元素填入结果数组 res + # 初始化数组 res 用于记录结果 + n = len(nums) + res = [0] * n + for i in range(n - 1, -1, -1): + num = nums[i] + res[counter[num] - 1] = num # 将 num 放置到对应索引处 + counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引 + # 使用结果数组 res 覆盖原数组 nums + for i in range(n): + nums[i] = res[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + print(f"计数排序(无法排序对象)完成后 nums = {nums}") + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + print(f"计数排序完成后 nums1 = {nums1}") diff --git a/algorithms/c04_sort/heap_sort.py b/algorithms/c04_sort/heap_sort.py new file mode 100644 index 0000000..bcfbdf7 --- /dev/null +++ b/algorithms/c04_sort/heap_sort.py @@ -0,0 +1,44 @@ +""" +File: heap_sort.py +Created Time: 2023-05-24 +""" + + +def sift_down(nums: list[int], n: int, i: int): + """堆的长度为 n ,从节点 i 开始,从顶至底堆化""" + while True: + # 判断节点 i, l, r 中值最大的节点,记为 ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + if l < n and nums[l] > nums[ma]: + ma = l + if r < n and nums[r] > nums[ma]: + ma = r + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i: + break + # 交换两节点 + nums[i], nums[ma] = nums[ma], nums[i] + # 循环向下堆化 + i = ma + + +def heap_sort(nums: list[int]): + """堆排序""" + # 建堆操作:堆化除叶节点以外的其他所有节点 + for i in range(len(nums) // 2 - 1, -1, -1): + sift_down(nums, len(nums), i) + # 从堆中提取最大元素,循环 n-1 轮 + for i in range(len(nums) - 1, 0, -1): + # 交换根节点与最右叶节点(即交换首元素与尾元素) + nums[0], nums[i] = nums[i], nums[0] + # 以根节点为起点,从顶至底进行堆化 + sift_down(nums, i, 0) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + print("堆排序完成后 nums =", nums) diff --git a/algorithms/c04_sort/insertion_sort.py b/algorithms/c04_sort/insertion_sort.py new file mode 100644 index 0000000..975dbfe --- /dev/null +++ b/algorithms/c04_sort/insertion_sort.py @@ -0,0 +1,25 @@ +""" +File: insertion_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def insertion_sort(nums: list[int]): + """插入排序""" + # 外循环:已排序区间为 [0, i-1] + for i in range(1, len(nums)): + base = nums[i] + j = i - 1 + # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while j >= 0 and nums[j] > base: + nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 + j -= 1 + nums[j + 1] = base # 将 base 赋值到正确位置 + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + insertion_sort(nums) + print("插入排序完成后 nums =", nums) diff --git a/algorithms/c04_sort/m00_bubble_sort.py b/algorithms/c04_sort/m00_bubble_sort.py new file mode 100644 index 0000000..866af42 --- /dev/null +++ b/algorithms/c04_sort/m00_bubble_sort.py @@ -0,0 +1,29 @@ +# -*- encoding: utf-8 -*- +""" +冒泡排序 +当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。 + +算法复杂度:N^2 +稳定排序:重复元素排序完后仍然保持原来的相对位置。 +""" +from algorithms.c04_sort.base.template import SortTemplate + + +class BubbleSort(SortTemplate): + + def sort(self): + le = len(self.seq) + for i in range(le): + # 提前退出排序的标志,本次循环是否有数据交换 + has_exchange = False + for j in range(0, le - i - 1): + if self.seq[j] > self.seq[j + 1]: + self.seq[j], self.seq[j + 1] = self.seq[j + 1], self.seq[j] + has_exchange = True # 表示有数据交换 + if not has_exchange: + break + + +if __name__ == '__main__': + bubble_sort = BubbleSort([4, 2, 5, 1, 6, 3]) + bubble_sort.main() diff --git a/algorithms/c04_sort/m01_insert_sort.py b/algorithms/c04_sort/m01_insert_sort.py new file mode 100644 index 0000000..2788c7b --- /dev/null +++ b/algorithms/c04_sort/m01_insert_sort.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +"""插入排序 +我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。 +插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。 +重复这个过程,直到未排序区间中元素为空,算法结束。 + +由于其内层循环非常紧凑,对于小规模的输入,插入排序是一种非常快的原址排序算法。 +注:如果输入数组中仅有常数个元素需要在排序过程中存储在数组外,则称这种排序算法是原址的。 + +冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要3个赋值操作,而插入排序只需要1个。 +所以,虽然冒泡排序和插入排序在时间复杂度上是一样的,都是 O(n2),但是如果我们希望把性能优化做到极致, +那肯定首选插入排序。 + +算法复杂度:N^2 +稳定排序:重复元素排序完后仍然保持原来的相对位置。 +""" +from algorithms.c04_sort.base.sortutil import insert_sort +from algorithms.c04_sort.base.template import SortTemplate + + +class InsertSort(SortTemplate): + + def sort(self): + insert_sort(self.seq) + + +if __name__ == '__main__': + select_sort = InsertSort([4, 2, 5, 1, 6, 3]) + select_sort.main() diff --git a/algorithms/c04_sort/m02_select_sort.py b/algorithms/c04_sort/m02_select_sort.py new file mode 100644 index 0000000..48ed271 --- /dev/null +++ b/algorithms/c04_sort/m02_select_sort.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +"""选择排序 +1. 找到数组中最小的元素,将其与第一个元素交换位置(如果第一个元素最小,就啥也不做)。 +2. 在剩下的元素中找到最小元素,将其与数组第二个元素交换位置。 +3. 如此反复,直到剩下元素为1个,整个数组排序完。 + +不稳定排序,使用场景少。 +复杂度:O(N^2),大约需要N^2/2次比较和N次交换。 +""" +from algorithms.c04_sort.base.template import SortTemplate + + +class SelectSort(SortTemplate): + + def sort(self): + le = len(self.seq) + for i in range(le - 1): + min_index = i + for j in range(i, le): + if self.seq[min_index] > self.seq[j]: + min_index = j + if i != min_index: + self.seq[i], self.seq[min_index] = self.seq[min_index], self.seq[i] + + +if __name__ == '__main__': + select_sort = SelectSort([4, 2, 5, 1, 6, 3]) + select_sort.main() diff --git a/algorithms/c04_sort/m03_merge_sort.py b/algorithms/c04_sort/m03_merge_sort.py new file mode 100644 index 0000000..d358f32 --- /dev/null +++ b/algorithms/c04_sort/m03_merge_sort.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +"""归并排序(分治法) + 归并排序算法完全遵循分治模式,操作如下: + 分解: 分解待排序的n个元素序列成各具n/2个元素的两个子序列 + 解决: 使用归并排序递归的排序两个子序列 + 合并: 合并两个已排序的子序列以产生已排序的答案 + +复杂度:N*lg(N) +稳定排序:重复元素排序完后仍然保持原来的相对位置。 + +它有一个致命的“弱点”,那就是归并排序不是原地排序算法。 +""" +from algorithms.c04_sort.base.template import SortTemplate + + +class MergeSort(SortTemplate): + + def sort(self): + self.merge_sort_range(0, len(self.seq) - 1) + + def merge_sort_range(self, start, end): + """ + 归并排序一个序列的子序列 + start: 子序列的start下标 + end: 子序列的end下标 + """ + if start < end: # 如果start >= end就终止递归调用 + middle = (start + end) // 2 + self.merge_sort_range(start, middle) # 排好左边的一半 + self.merge_sort_range(middle + 1, end) # 再排好右边的一半 + self.merge_ordered_seq(start, middle, end) # 最后合并排序结果 + + def merge_ordered_seq(self, left, middle, right): + """ + seq: 待排序序列 + left <= middle <= right + 子数组seq[left..middle]和seq[middle+1..right]都是排好序的 + 该排序的时间复杂度为O(n) + """ + temp_seq = [] + i = left + j = middle + 1 + while i <= middle and j <= right: + if self.seq[i] <= self.seq[j]: + temp_seq.append(self.seq[i]) + i += 1 + else: + temp_seq.append(self.seq[j]) + j += 1 + if i <= middle: + temp_seq.extend(self.seq[i:middle + 1]) + else: + temp_seq.extend(self.seq[j:right + 1]) + self.seq[left:right + 1] = temp_seq[:] + + +if __name__ == '__main__': + merge_sort = MergeSort([4, 2, 5, 1, 6, 3]) + merge_sort.main() diff --git a/algorithms/c04_sort/m04_merge_insert_sort.py b/algorithms/c04_sort/m04_merge_insert_sort.py new file mode 100644 index 0000000..c245e28 --- /dev/null +++ b/algorithms/c04_sort/m04_merge_insert_sort.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +"""归并排序中对小数组采用插入排序 + 纯归并排序的复杂度为: O(nlgn),而纯插入排序的时间复杂度为:O(n^2)。数据量很大的时候采用归并排序 + 但是在n较小的时候插入排序可能运行的会更快点。因此在归并排序中当子问题变得足够小时, + 采用插入排序来使得递归的叶子变粗可以加快排序速度。那么这个足够小到底怎么去衡量呢? 请看下面: + 这么几个我不证明了,比较简单: + A,插入排序最坏情况下可以在O(nk)时间内排序每个长度为k的n/k个子列表 + B,在最坏情况下可在O(nlg(n/k))的时间内合并这些子表 + C,修订后的算法的最坏情况运行时间复杂度是O(nk + nlg(n/k)) + 那么,O(nk+nlg(n/k))=O(nlgn).只能最大是k=O(lgn).等式左边中第一项是高阶项。 + k如果大于lgn,则比归并排序复杂度大了。左边可以写成nk+nlgn-nlgk,k等于lgn时, + 就是2nlgn-nlglgn.忽略恒定系数,则与归并排序是一样的。 + + 最后结论:k < lg(n)的时候,使用插入排序 + +复杂度为: O(nlgn) +稳定排序:重复元素排序完后仍然保持原来的相对位置。 + +它有一个致命的“弱点”,那就是归并排序不是原地排序算法。 +""" +from math import log + +from algorithms.c04_sort.base.sortutil import insert_sort +from algorithms.c04_sort.base.template import SortTemplate + + +class MergeAndInsertSort(SortTemplate): + + def sort(self): + self.merge_insert_sort_range(0, len(self.seq) - 1, log(len(self.seq), 2)) + + def merge_insert_sort_range(self, start, end, threshold): + """ + 归并排序一个序列的子序列 + start: 子序列的start下标 + end: 子序列的end下标 + threshold: 待排序长度低于这个值,就采用插入排序 + """ + if end - start + 1 < threshold: + temp_seq = self.seq[start: end + 1] + insert_sort(temp_seq) # 小数组使用插入排序 + self.seq[start: end + 1] = temp_seq[:] + elif start < end: # 如果start >= end就终止递归调用 + middle = (start + end) // 2 + self.merge_insert_sort_range(start, middle, threshold) # 排好左边的一半 + self.merge_insert_sort_range(middle + 1, end, threshold) # 再排好右边的一半 + self.merge_insert_sort_seq(start, middle, end) # 最后合并排序结果 + + def merge_insert_sort_seq(self, left, middle, right): + """ + seq: 待排序序列 + left <= middle <= right + 子数组seq[left..middle]和seq[middle+1..right]都是排好序的 + 该排序的时间复杂度为O(n) + """ + temp_seq = [] + i = left + j = middle + 1 + while i <= middle and j <= right: + if self.seq[i] <= self.seq[j]: + temp_seq.append(self.seq[i]) + i += 1 + else: + temp_seq.append(self.seq[j]) + j += 1 + if i <= middle: + temp_seq.extend(self.seq[i:middle + 1]) + else: + temp_seq.extend(self.seq[j:right + 1]) + self.seq[left:right + 1] = temp_seq[:] + + +if __name__ == '__main__': + merge_and_insert_sort = MergeAndInsertSort([4, 2, 5, 1, 6, 3, 7, 9, 8]) + merge_and_insert_sort.main() diff --git a/algorithms/c04_sort/m05_quick_sort.py b/algorithms/c04_sort/m05_quick_sort.py new file mode 100644 index 0000000..fa677c1 --- /dev/null +++ b/algorithms/c04_sort/m05_quick_sort.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +""" +快速排序 +采用分治法思想: +分解: 将数组A[p..r]划分成两个(也可能是空)的子数组A[p..q-1]和A[q+1..r], + 使得左边数组中的元素都小于A[p],而右边数组元素都大于A[p] +解决: 通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]进行排序 +合并: 原址排序,不需要合并,数组已经排好序了 + +快速排序的优点: +最坏情况下时间复杂度为O(n^2),但是期望时间是O(nlg(n)), +而且O(n*lg(n))隐含常数因子非常的小,而且还是原址排序, +所以实际中使用最多的排序算法就是快速排序 + +复杂度为: O(n*lgn) +快速排序的缺点是它是一个不稳定排序算法。如果要保持原有的相同元素顺序,则不能选择快排。 +""" +from random import randint + +from algorithms.c04_sort.base.template import SortTemplate + + +class QuickSort(SortTemplate): + + def sort(self): + # self._quick_sub_sort_recursive(seq, 0, len(seq) - 1) + self._quick_sub_sort_tail(0, len(self.seq) - 1) + + def _quick_sub_sort_tail(self, start, end): + """循环版本,模拟尾递归,可以大大减少递归栈深度,而且时间复杂度不变""" + while start < end: + pivot = self._rand_partition(start, end) + if pivot - start < end - pivot: + self._quick_sub_sort_tail(start, pivot - 1) + start = pivot + 1 # 巧妙的通过改变start来实现右边数组的递归 + else: + self._quick_sub_sort_tail(pivot + 1, end) + end = pivot - 1 + + def _rand_partition(self, start, end): + """分解子数组:随机化版本""" + pivot = randint(start, end) # 随机的pivot + # 还是将这个pivot放到最后 + self.seq[pivot], self.seq[end] = self.seq[end], self.seq[pivot] + pivot_value = self.seq[end] + i = start - 1 # 以退为进,先初始化为start-1 + for j in range(start, end): + if self.seq[j] <= pivot_value: + i += 1 + # 只需要确保所有不大于标杆的元素被蛇吞掉即可。 + self.seq[i], self.seq[j] = self.seq[j], self.seq[i] + self.seq[i + 1], self.seq[end] = self.seq[end], self.seq[i + 1] + return i + 1 + + def _quick_sub_sort_recursive(self, start, end): + """递归版本的""" + if start < end: + q = self._rand_partition(start, end) + self._quick_sub_sort_recursive(start, q - 1) + self._quick_sub_sort_recursive(q + 1, end) + + +if __name__ == '__main__': + quick_sort = QuickSort([9, 7, 8, 10, 16, 3, 14, 2, 1, 4]) + quick_sort.main() diff --git a/algorithms/c04_sort/m06_heap_sort.py b/algorithms/c04_sort/m06_heap_sort.py new file mode 100644 index 0000000..298222c --- /dev/null +++ b/algorithms/c04_sort/m06_heap_sort.py @@ -0,0 +1,57 @@ +# -*- encoding: utf-8 -*- +"""使用二叉堆实现堆排序最优雅代码 +原理:如果左右子树都已经是最大堆了,则通过下沉当前根节点操作即可重新构造为一个最大堆。 +""" +from algorithms.c04_sort.base.template import SortTemplate + + +class HeapSort(SortTemplate): + + def __init__(self, seq): + super().__init__(seq) + self._size = len(seq) + + def sort(self): + """最简的堆排序算法""" + # 第一步:首先通过下沉操作构造最大二叉堆 + k = self._size // 2 # 从中间开始往前循环 + while k >= 1: + self._sink(k, self._size) + k -= 1 + + # 第二步:通过每次将堆顶元素跟后面元素交换,堆大小减少1。 + # 然后再下沉堆顶元素重新构造堆,直到堆大小为1 + k = self._size + while k > 1: + self._exchange(1, k) + k -= 1 + self._sink(1, k) + + def _sink(self, index, n): + """向下沉下去,把老大的位置叫出来,谁更牛逼谁做老大""" + while 2 * index <= n: + j = 2 * index + if j < n and self._less(j, j + 1): + j += 1 # 如果有两个下属,把最牛逼的下属拿出来做对比 + if not self._less(index, j): + break # 如果比最牛逼的那个下属还要厉害,说明这个老大位置没问题了 + self._exchange(index, j) # 如果没有下属厉害,就自己乖乖把位置让出来,跟他交换一下 + index = j # 现在index的值修改成新的位置,继续向下做对比,直到找到自己合适的位置 + + def _less(self, i, j): + """ + 比较两个元素大小,如果左边小于等于右边,返回True + 注意,这里索引都减1,用来支持索引值从1开始的序列 + """ + if self.seq[i - 1] is None or self.seq[j - 1] is None: + raise IndexError('index error') + return self.seq[i - 1] < self.seq[j - 1] + + def _exchange(self, i, j): + """交换两个元素,这里索引也都减1""" + self.seq[i - 1], self.seq[j - 1] = self.seq[j - 1], self.seq[i - 1] + + +if __name__ == '__main__': + heap_sort = HeapSort([9, 7, 8, 10, 16, 3, 14, 2, 1, 4]) + heap_sort.main() diff --git a/ch02_sort/at204_count_sort.py b/algorithms/c04_sort/m07_count_sort.py similarity index 97% rename from ch02_sort/at204_count_sort.py rename to algorithms/c04_sort/m07_count_sort.py index c19b095..29ca3eb 100644 --- a/ch02_sort/at204_count_sort.py +++ b/algorithms/c04_sort/m07_count_sort.py @@ -9,7 +9,6 @@ 那么可以用这个计数排序 计数排序是稳定的:原数组中相同元素在输出数组中的次序是一样的 """ -__author__ = 'Xiong Neng' def countSort(A, k, offset=0): @@ -33,6 +32,7 @@ def countSort(A, k, offset=0): if offset > 0: A[:] = [p + offset for p in A] + if __name__ == '__main__': A = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] countSort(A, 20) @@ -40,4 +40,4 @@ def countSort(A, k, offset=0): A = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] B = [100 + p for p in A] countSort(B, 30, 96) - print(B) \ No newline at end of file + print(B) diff --git a/ch02_sort/at205_radix_sort.py b/algorithms/c04_sort/m08_radix_sort.py similarity index 86% rename from ch02_sort/at205_radix_sort.py rename to algorithms/c04_sort/m08_radix_sort.py index 9faf439..d173a26 100644 --- a/ch02_sort/at205_radix_sort.py +++ b/algorithms/c04_sort/m08_radix_sort.py @@ -8,7 +8,6 @@ 可以用基数排序,用一种稳定排序算法(比如计数排序)对这些信息进行三次排序: 优先级从低到高,权重从低到高。 """ -__author__ = 'Xiong Neng' def radixSort(A, digit, base): @@ -21,12 +20,12 @@ def radixSort(A, digit, base): C = [0] * base # 临时存储数组 for i in range(0, len(A)): # split the specified digit from the element - tmpSplitDigit = A[i] / pow(10, di - 1) - (A[i] / pow(10, di)) * 10 + tmpSplitDigit = A[i] // pow(10, di - 1) - (A[i] // pow(10, di)) * 10 C[tmpSplitDigit] += 1 # C[i]现在代表数组A中元素等于i的个数 for i in range(1, base): C[i] += C[i - 1] # C[i]现在代表数组A中元素小于等于i的个数 for j in range(len(A) - 1, -1, -1): - tmpSplitDigit = A[j] / pow(10, di - 1) - (A[j] / pow(10, di)) * 10 + tmpSplitDigit = A[j] // pow(10, di - 1) - (A[j] // pow(10, di)) * 10 B[C[tmpSplitDigit] - 1] = A[j] C[tmpSplitDigit] -= 1 # 防止数组A有重复的数,占据了相同的位置 A[:] = B[:] @@ -35,4 +34,4 @@ def radixSort(A, digit, base): if __name__ == '__main__': A = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] radixSort(A, 2, 10) - print(A) \ No newline at end of file + print(A) diff --git a/ch02_sort/at206_bucket_sort.py b/algorithms/c04_sort/m09_bucket_sort.py similarity index 88% rename from ch02_sort/at206_bucket_sort.py rename to algorithms/c04_sort/m09_bucket_sort.py index 5b26206..45dfaec 100644 --- a/ch02_sort/at206_bucket_sort.py +++ b/algorithms/c04_sort/m09_bucket_sort.py @@ -11,9 +11,7 @@ """ from math import floor -from ch02_sort.at103_insert_sort import insertSort - -__author__ = 'Xiong Neng' +from algorithms.c04_sort.base.sortutil import insert_sort def bucketSort(A): @@ -23,7 +21,7 @@ def bucketSort(A): ind = int(floor(n * A[i])) B[ind].append(A[i]) for i in range(0, n): - insertSort(B[i]) + insert_sort(B[i]) res = [] for i in range(0, n): res.extend(B[i]) @@ -35,4 +33,4 @@ def bucketSort(A): BB = [i / 20.0 for i in AA] print(BB) bucketSort(BB) - print(BB) \ No newline at end of file + print(BB) diff --git a/ch02_sort/at207_find_minmax.py b/algorithms/c04_sort/m10_find_minmax.py similarity index 92% rename from ch02_sort/at207_find_minmax.py rename to algorithms/c04_sort/m10_find_minmax.py index a876883..d5f499e 100644 --- a/ch02_sort/at207_find_minmax.py +++ b/algorithms/c04_sort/m10_find_minmax.py @@ -16,9 +16,9 @@ def minMax(A): lastMin, lastMax = (A[0], A[1]) if A[0] < A[1] else (A[1], A[0]) else: lastMin = lastMax = A[0] - for i in range(0, (n + 1)//2-1): + for i in range(0, (n + 1) // 2 - 1): tmp1 = A[2 * i + 1] - tmp2 = A[2*i + 2] + tmp2 = A[2 * i + 2] tmpMin, tmpMax = (tmp1, tmp2) if tmp1 < tmp2 else (tmp2, tmp1) lastMin = lastMin if lastMin < tmpMin else tmpMin lastMax = lastMax if lastMax > tmpMax else tmpMax diff --git a/ch02_sort/at208_imin_select.py b/algorithms/c04_sort/m11_imin_select.py similarity index 99% rename from ch02_sort/at208_imin_select.py rename to algorithms/c04_sort/m11_imin_select.py index 75a7a00..2685f51 100644 --- a/ch02_sort/at208_imin_select.py +++ b/algorithms/c04_sort/m11_imin_select.py @@ -7,6 +7,7 @@ pivot加入了随机特性,算法的期望运行时间是O(n) """ from random import randint + __author__ = 'Xiong Neng' diff --git a/ch02_sort/at209_imin_select2.py b/algorithms/c04_sort/m12_imin_select2.py similarity index 93% rename from ch02_sort/at209_imin_select2.py rename to algorithms/c04_sort/m12_imin_select2.py index 209822e..3ffddfa 100644 --- a/ch02_sort/at209_imin_select2.py +++ b/algorithms/c04_sort/m12_imin_select2.py @@ -6,9 +6,7 @@ Desc : 顺序统计量的选择算法(最坏情况下O(n)) 利用中位数的中位数作为pivot划分数组 """ -from ch02_sort.at103_insert_sort import insertSort - -__author__ = 'Xiong Neng' +from algorithms.c04_sort.base.sortutil import insert_sort def iminSelect2(A, i): @@ -48,11 +46,11 @@ def __selectMidOfMid(seq): midArr = [] # 每组的中位数列表 for i in range(0, grpNum): eachGroup = seq[i * 5: (i + 1) * 5] - insertSort(eachGroup) + insert_sort(eachGroup) midArr.append(eachGroup[2]) if lastNum > 0: lastGroup = seq[grpNum * 5: grpNum * 5 + lastNum] - insertSort(lastGroup) + insert_sort(lastGroup) midArr.append(lastGroup[(lastNum - 1) // 2]) seq = midArr return seq[0] diff --git a/ch02_sort/at210_imin_list.py b/algorithms/c04_sort/m13_imin_list.py similarity index 91% rename from ch02_sort/at210_imin_list.py rename to algorithms/c04_sort/m13_imin_list.py index fe219cb..86cd86f 100644 --- a/ch02_sort/at210_imin_list.py +++ b/algorithms/c04_sort/m13_imin_list.py @@ -7,9 +7,7 @@ 先通过找到第i小的数,然后将这个数作为pivot去划分这个数组, 左边 + 这个pivot即是解 """ -from .at209_imin_select2 import iminSelect2 - -__author__ = 'Xiong Neng' +from algorithms.c04_sort.m12_imin_select2 import iminSelect2 def iminList(A, i): @@ -39,4 +37,4 @@ def __midPartition(A, p, r, midNum): if __name__ == '__main__': - print(iminList([4, 23, 65, 3, 22, 3, 34, 3, 67, 3, 12, 3, 7, 1, 1, 256, 3, 34, 27], 10)) \ No newline at end of file + print(iminList([4, 23, 65, 3, 22, 3, 34, 3, 67, 3, 12, 3, 7, 1, 1, 256, 3, 34, 27], 10)) diff --git a/algorithms/c04_sort/merge_sort.py b/algorithms/c04_sort/merge_sort.py new file mode 100644 index 0000000..8fdf1be --- /dev/null +++ b/algorithms/c04_sort/merge_sort.py @@ -0,0 +1,54 @@ +""" +File: merge_sort.py +Created Time: 2022-11-25 +""" + + +def merge(nums: list[int], left: int, mid: int, right: int): + """合并左子数组和右子数组""" + # 左子数组区间 [left, mid], 右子数组区间 [mid+1, right] + # 创建一个临时数组 tmp ,用于存放合并后的结果 + tmp = [0] * (right - left + 1) + # 初始化左子数组和右子数组的起始索引 + i, j, k = left, mid + 1, 0 + # 当左右子数组都还有元素时,比较并将较小的元素复制到临时数组中 + while i <= mid and j <= right: + if nums[i] <= nums[j]: + tmp[k] = nums[i] + i += 1 + else: + tmp[k] = nums[j] + j += 1 + k += 1 + # 将左子数组和右子数组的剩余元素复制到临时数组中 + while i <= mid: + tmp[k] = nums[i] + i += 1 + k += 1 + while j <= right: + tmp[k] = nums[j] + j += 1 + k += 1 + # 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for k in range(0, len(tmp)): + nums[left + k] = tmp[k] + + +def merge_sort(nums: list[int], left: int, right: int): + """归并排序""" + # 终止条件 + if left >= right: + return # 当子数组长度为 1 时终止递归 + # 划分阶段 + mid = (left + right) // 2 # 计算中点 + merge_sort(nums, left, mid) # 递归左子数组 + merge_sort(nums, mid + 1, right) # 递归右子数组 + # 合并阶段 + merge(nums, left, mid, right) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, len(nums) - 1) + print("归并排序完成后 nums =", nums) diff --git a/algorithms/c04_sort/quick_sort.py b/algorithms/c04_sort/quick_sort.py new file mode 100644 index 0000000..9257236 --- /dev/null +++ b/algorithms/c04_sort/quick_sort.py @@ -0,0 +1,130 @@ +""" +File: quick_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +class QuickSort: + """快速排序类""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分""" + # 以 nums[left] 作为基准数 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 从右向左找首个小于基准数的元素 + while i < j and nums[i] <= nums[left]: + i += 1 # 从左向右找首个大于基准数的元素 + # 元素交换 + nums[i], nums[j] = nums[j], nums[i] + # 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + return i # 返回基准数的索引 + + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序""" + # 子数组长度为 1 时终止递归 + if left >= right: + return + # 哨兵划分 + pivot = self.partition(nums, left, right) + # 递归左子数组、右子数组 + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortMedian: + """快速排序类(中位基准数优化)""" + + def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: + """选取三个元素的中位数""" + # 此处使用异或运算来简化代码 + # 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 + if (nums[left] < nums[mid]) ^ (nums[left] < nums[right]): + return left + elif (nums[mid] < nums[left]) ^ (nums[mid] < nums[right]): + return mid + return right + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分(三数取中值)""" + # 以 nums[left] 作为基准数 + med = self.median_three(nums, left, (left + right) // 2, right) + # 将中位数交换至数组最左端 + nums[left], nums[med] = nums[med], nums[left] + # 以 nums[left] 作为基准数 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 从右向左找首个小于基准数的元素 + while i < j and nums[i] <= nums[left]: + i += 1 # 从左向右找首个大于基准数的元素 + # 元素交换 + nums[i], nums[j] = nums[j], nums[i] + # 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + return i # 返回基准数的索引 + + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序""" + # 子数组长度为 1 时终止递归 + if left >= right: + return + # 哨兵划分 + pivot = self.partition(nums, left, right) + # 递归左子数组、右子数组 + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortTailCall: + """快速排序类(尾递归优化)""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分""" + # 以 nums[left] 作为基准数 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 从右向左找首个小于基准数的元素 + while i < j and nums[i] <= nums[left]: + i += 1 # 从左向右找首个大于基准数的元素 + # 元素交换 + nums[i], nums[j] = nums[j], nums[i] + # 将基准数交换至两子数组的分界线 + nums[i], nums[left] = nums[left], nums[i] + return i # 返回基准数的索引 + + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序(尾递归优化)""" + # 子数组长度为 1 时终止 + while left < right: + # 哨兵划分操作 + pivot = self.partition(nums, left, right) + # 对两个子数组中较短的那个执行快排 + if pivot - left < right - pivot: + self.quick_sort(nums, left, pivot - 1) # 递归排序左子数组 + left = pivot + 1 # 剩余未排序区间为 [pivot + 1, right] + else: + self.quick_sort(nums, pivot + 1, right) # 递归排序右子数组 + right = pivot - 1 # 剩余未排序区间为 [left, pivot - 1] + + +"""Driver Code""" +if __name__ == "__main__": + # 快速排序 + nums = [2, 4, 1, 0, 3, 5] + QuickSort().quick_sort(nums, 0, len(nums) - 1) + print("快速排序完成后 nums =", nums) + + # 快速排序(中位基准数优化) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) + print("快速排序(中位基准数优化)完成后 nums =", nums1) + + # 快速排序(尾递归优化) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1) + print("快速排序(尾递归优化)完成后 nums =", nums2) diff --git a/algorithms/c04_sort/radix_sort.py b/algorithms/c04_sort/radix_sort.py new file mode 100644 index 0000000..7ce90da --- /dev/null +++ b/algorithms/c04_sort/radix_sort.py @@ -0,0 +1,68 @@ +""" +File: radix_sort.py +Created Time: 2023-03-26 +""" + + +def digit(num: int, exp: int) -> int: + """获取元素 num 的第 k 位,其中 exp = 10^(k-1)""" + # 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num // exp) % 10 + + +def counting_sort_digit(nums: list[int], exp: int): + """计数排序(根据 nums 第 k 位排序)""" + # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 + counter = [0] * 10 + n = len(nums) + # 统计 0~9 各数字的出现次数 + for i in range(n): + d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d + counter[d] += 1 # 统计数字 d 的出现次数 + # 求前缀和,将“出现个数”转换为“数组索引” + for i in range(1, 10): + counter[i] += counter[i - 1] + # 倒序遍历,根据桶内统计结果,将各元素填入 res + res = [0] * n + for i in range(n - 1, -1, -1): + d = digit(nums[i], exp) + j = counter[d] - 1 # 获取 d 在数组中的索引 j + res[j] = nums[i] # 将当前元素填入索引 j + counter[d] -= 1 # 将 d 的数量减 1 + # 使用结果覆盖原数组 nums + for i in range(n): + nums[i] = res[i] + + +def radix_sort(nums: list[int]): + """基数排序""" + # 获取数组的最大元素,用于判断最大位数 + m = max(nums) + # 按照从低位到高位的顺序遍历 + exp = 1 + while exp <= m: + # 对数组元素的第 k 位执行计数排序 + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # 即 exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + + +"""Driver Code""" +if __name__ == "__main__": + # 基数排序 + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + print("基数排序完成后 nums =", nums) diff --git a/algorithms/c04_sort/selection_sort.py b/algorithms/c04_sort/selection_sort.py new file mode 100644 index 0000000..136832b --- /dev/null +++ b/algorithms/c04_sort/selection_sort.py @@ -0,0 +1,25 @@ +""" +File: selection_sort.py +Created Time: 2023-05-22 +""" + + +def selection_sort(nums: list[int]): + """选择排序""" + n = len(nums) + # 外循环:未排序区间为 [i, n-1] + for i in range(n - 1): + # 内循环:找到未排序区间内的最小元素 + k = i + for j in range(i + 1, n): + if nums[j] < nums[k]: + k = j # 记录最小元素的索引 + # 将该最小元素与未排序区间的首个元素交换 + nums[i], nums[k] = nums[k], nums[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + print("选择排序完成后 nums =", nums) diff --git a/algorithms/c05_search/__init__.py b/algorithms/c05_search/__init__.py new file mode 100644 index 0000000..334a6a5 --- /dev/null +++ b/algorithms/c05_search/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +二分查找、调表、散列表、哈希算法 +""" diff --git a/algorithms/c05_search/binary_search.py b/algorithms/c05_search/binary_search.py new file mode 100644 index 0000000..26005ab --- /dev/null +++ b/algorithms/c05_search/binary_search.py @@ -0,0 +1,52 @@ +""" +File: binary_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + + +def binary_search(nums: list[int], target: int) -> int: + """二分查找(双闭区间)""" + # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + i, j = 0, len(nums) - 1 + # 循环,当搜索区间为空时跳出(当 i > j 时为空) + while i <= j: + # 理论上 Python 的数字可以无限大(取决于内存大小),无须考虑大数越界问题 + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # 此情况说明 target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # 此情况说明 target 在区间 [i, m-1] 中 + else: + return m # 找到目标元素,返回其索引 + return -1 # 未找到目标元素,返回 -1 + + +def binary_search_lcro(nums: list[int], target: int) -> int: + """二分查找(左闭右开)""" + # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + i, j = 0, len(nums) + # 循环,当搜索区间为空时跳出(当 i = j 时为空) + while i < j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # 此情况说明 target 在区间 [m+1, j) 中 + elif nums[m] > target: + j = m # 此情况说明 target 在区间 [i, m) 中 + else: + return m # 找到目标元素,返回其索引 + return -1 # 未找到目标元素,返回 -1 + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分查找(双闭区间) + index: int = binary_search(nums, target) + print("目标元素 6 的索引 = ", index) + + # 二分查找(左闭右开) + index: int = binary_search_lcro(nums, target) + print("目标元素 6 的索引 = ", index) diff --git a/algorithms/c05_search/binary_search_edge.py b/algorithms/c05_search/binary_search_edge.py new file mode 100644 index 0000000..da0cacc --- /dev/null +++ b/algorithms/c05_search/binary_search_edge.py @@ -0,0 +1,48 @@ +""" +File: binary_search_edge.py +Created Time: 2023-08-04 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from binary_search_insertion import binary_search_insertion + + +def binary_search_left_edge(nums: list[int], target: int) -> int: + """二分查找最左一个 target""" + # 等价于查找 target 的插入点 + i = binary_search_insertion(nums, target) + # 未找到 target ,返回 -1 + if i == len(nums) or nums[i] != target: + return -1 + # 找到 target ,返回索引 i + return i + + +def binary_search_right_edge(nums: list[int], target: int) -> int: + """二分查找最右一个 target""" + # 转化为查找最左一个 target + 1 + i = binary_search_insertion(nums, target + 1) + # j 指向最右一个 target ,i 指向首个大于 target 的元素 + j = i - 1 + # 未找到 target ,返回 -1 + if j == -1 or nums[j] != target: + return -1 + # 找到 target ,返回索引 j + return j + + +"""Driver Code""" +if __name__ == "__main__": + # 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\n数组 nums = {nums}") + + # 二分查找左边界和右边界 + for target in [6, 7]: + index = binary_search_left_edge(nums, target) + print(f"最左一个元素 {target} 的索引为 {index}") + index = binary_search_right_edge(nums, target) + print(f"最右一个元素 {target} 的索引为 {index}") diff --git a/algorithms/c05_search/binary_search_insertion.py b/algorithms/c05_search/binary_search_insertion.py new file mode 100644 index 0000000..abff2c5 --- /dev/null +++ b/algorithms/c05_search/binary_search_insertion.py @@ -0,0 +1,53 @@ +""" +File: binary_search_insertion.py +Created Time: 2023-08-04 +""" + + +def binary_search_insertion_simple(nums: list[int], target: int) -> int: + """二分查找插入点(无重复元素)""" + i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] + while i <= j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在区间 [i, m-1] 中 + else: + return m # 找到 target ,返回插入点 m + # 未找到 target ,返回插入点 i + return i + + +def binary_search_insertion(nums: list[int], target: int) -> int: + """二分查找插入点(存在重复元素)""" + i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] + while i <= j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在区间 [i, m-1] 中 + else: + j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 + # 返回插入点 i + return i + + +"""Driver Code""" +if __name__ == "__main__": + # 无重复元素的数组 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print(f"\n数组 nums = {nums}") + # 二分查找插入点 + for target in [6, 9]: + index = binary_search_insertion_simple(nums, target) + print(f"元素 {target} 的插入点的索引为 {index}") + + # 包含重复元素的数组 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\n数组 nums = {nums}") + # 二分查找插入点 + for target in [2, 6, 20]: + index = binary_search_insertion(nums, target) + print(f"元素 {target} 的插入点的索引为 {index}") diff --git a/algorithms/c05_search/hashing_search.py b/algorithms/c05_search/hashing_search.py new file mode 100644 index 0000000..013c20c --- /dev/null +++ b/algorithms/c05_search/hashing_search.py @@ -0,0 +1,51 @@ +""" +File: hashing_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import ListNode, list_to_linked_list + + +def hashing_search_array(hmap: dict[int, int], target: int) -> int: + """哈希查找(数组)""" + # 哈希表的 key: 目标元素,value: 索引 + # 若哈希表中无此 key ,返回 -1 + return hmap.get(target, -1) + + +def hashing_search_linkedlist( + hmap: dict[int, ListNode], target: int +) -> ListNode | None: + """哈希查找(链表)""" + # 哈希表的 key: 目标元素,value: 节点对象 + # 若哈希表中无此 key ,返回 None + return hmap.get(target, None) + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # 哈希查找(数组) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # 初始化哈希表 + map0 = dict[int, int]() + for i in range(len(nums)): + map0[nums[i]] = i # key: 元素,value: 索引 + index: int = hashing_search_array(map0, target) + print("目标元素 3 的索引 =", index) + + # 哈希查找(链表) + head: ListNode = list_to_linked_list(nums) + # 初始化哈希表 + map1 = dict[int, ListNode]() + while head: + map1[head.val] = head # key: 节点值,value: 节点 + head = head.next + node: ListNode = hashing_search_linkedlist(map1, target) + print("目标节点值 3 的对应节点对象为", node) diff --git a/algorithms/c05_search/linear_search.py b/algorithms/c05_search/linear_search.py new file mode 100644 index 0000000..f2e17f8 --- /dev/null +++ b/algorithms/c05_search/linear_search.py @@ -0,0 +1,45 @@ +""" +File: linear_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import ListNode, list_to_linked_list + + +def linear_search_array(nums: list[int], target: int) -> int: + """线性查找(数组)""" + # 遍历数组 + for i in range(len(nums)): + if nums[i] == target: # 找到目标元素,返回其索引 + return i + return -1 # 未找到目标元素,返回 -1 + + +def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: + """线性查找(链表)""" + # 遍历链表 + while head: + if head.val == target: # 找到目标节点,返回之 + return head + head = head.next + return None # 未找到目标节点,返回 None + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # 在数组中执行线性查找 + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index: int = linear_search_array(nums, target) + print("目标元素 3 的索引 =", index) + + # 在链表中执行线性查找 + head: ListNode = list_to_linked_list(nums) + node: ListNode | None = linear_search_linkedlist(head, target) + print("目标节点值 3 的对应节点对象为", node) diff --git a/algorithms/c05_search/two_sum.py b/algorithms/c05_search/two_sum.py new file mode 100644 index 0000000..8e09945 --- /dev/null +++ b/algorithms/c05_search/two_sum.py @@ -0,0 +1,41 @@ +""" +File: two_sum.py +Created Time: 2022-11-25 +""" + + +def two_sum_brute_force(nums: list[int], target: int) -> list[int]: + """方法一:暴力枚举""" + # 两层循环,时间复杂度 O(n^2) + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return [i, j] + return [] + + +def two_sum_hash_table(nums: list[int], target: int) -> list[int]: + """方法二:辅助哈希表""" + # 辅助哈希表,空间复杂度 O(n) + dic = {} + # 单层循环,时间复杂度 O(n) + for i in range(len(nums)): + if target - nums[i] in dic: + return [dic[target - nums[i]], i] + dic[nums[i]] = i + return [] + + +"""Driver Code""" +if __name__ == "__main__": + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Driver Code ====== + # 方法一 + res: list[int] = two_sum_brute_force(nums, target) + print("方法一 res =", res) + # 方法二 + res: list[int] = two_sum_hash_table(nums, target) + print("方法二 res =", res) diff --git a/algorithms/c06_string/__init__.py b/algorithms/c06_string/__init__.py new file mode 100644 index 0000000..9cbff8c --- /dev/null +++ b/algorithms/c06_string/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +"""字符串相关算法 +Some of description... +""" diff --git a/algorithms/c07_divide/__init__.py b/algorithms/c07_divide/__init__.py new file mode 100644 index 0000000..84eb509 --- /dev/null +++ b/algorithms/c07_divide/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +分治算法 +""" diff --git a/algorithms/c07_divide/binary_search_recur.py b/algorithms/c07_divide/binary_search_recur.py new file mode 100644 index 0000000..0297820 --- /dev/null +++ b/algorithms/c07_divide/binary_search_recur.py @@ -0,0 +1,40 @@ +""" +File: binary_search_recur.py +Created Time: 2023-07-17 +Author: krahets (krahets@163.com) +""" + + +def dfs(nums: list[int], target: int, i: int, j: int) -> int: + """二分查找:问题 f(i, j)""" + # 若区间为空,代表无目标元素,则返回 -1 + if i > j: + return -1 + # 计算中点索引 m + m = (i + j) // 2 + if nums[m] < target: + # 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j) + elif nums[m] > target: + # 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1) + else: + # 找到目标元素,返回其索引 + return m + + +def binary_search(nums: list[int], target: int) -> int: + """二分查找""" + n = len(nums) + # 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1) + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分查找(双闭区间) + index: int = binary_search(nums, target) + print("目标元素 6 的索引 = ", index) diff --git a/algorithms/c07_divide/build_tree.py b/algorithms/c07_divide/build_tree.py new file mode 100644 index 0000000..c8c9054 --- /dev/null +++ b/algorithms/c07_divide/build_tree.py @@ -0,0 +1,53 @@ +""" +File: build_tree.py +Created Time: 2023-07-15 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree + + +def dfs( + preorder: list[int], + inorder_map: dict[int, int], + i: int, + l: int, + r: int, +) -> TreeNode | None: + """构建二叉树:分治""" + # 子树区间为空时终止 + if r - l < 0: + return None + # 初始化根节点 + root = TreeNode(preorder[i]) + # 查询 m ,从而划分左右子树 + m = inorder_map[preorder[i]] + # 子问题:构建左子树 + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # 子问题:构建右子树 + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + # 返回根节点 + return root + + +def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: + """构建二叉树""" + # 初始化哈希表,存储 inorder 元素到索引的映射 + inorder_map = {val: i for i, val in enumerate(inorder)} + root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + print(f"前序遍历 = {preorder}") + print(f"中序遍历 = {inorder}") + + root = build_tree(preorder, inorder) + print("构建的二叉树为:") + print_tree(root) diff --git a/algorithms/c07_divide/hanota.py b/algorithms/c07_divide/hanota.py new file mode 100644 index 0000000..bbaec2e --- /dev/null +++ b/algorithms/c07_divide/hanota.py @@ -0,0 +1,52 @@ +""" +File: hanota.py +Created Time: 2023-07-16 +""" + + +def move(src: list[int], tar: list[int]): + """移动一个圆盘""" + # 从 src 顶部拿出一个圆盘 + pan = src.pop() + # 将圆盘放入 tar 顶部 + tar.append(pan) + + +def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): + """求解汉诺塔:问题 f(i)""" + # 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1: + move(src, tar) + return + # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf) + # 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar) + # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar) + + +def solve_hanota(A: list[int], B: list[int], C: list[int]): + """求解汉诺塔""" + n = len(A) + # 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C) + + +"""Driver Code""" +if __name__ == "__main__": + # 列表尾部是柱子顶部 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + print("初始状态下:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") + + solve_hanota(A, B, C) + + print("圆盘移动完成后:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") diff --git a/algorithms/c08_backtrack/__init__.py b/algorithms/c08_backtrack/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c08_backtrack/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c08_backtrack/backtrack.py b/algorithms/c08_backtrack/backtrack.py new file mode 100644 index 0000000..338e344 --- /dev/null +++ b/algorithms/c08_backtrack/backtrack.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +""" +回溯法框架代码 +""" + + +def backtrack(state, choices, res): + # 判断是否为解 + if is_solution(state): + # 记录解 + record_solution(state, res) + # 不再继续搜索 + return + # 遍历所有选择 + for choice in choices: + # 剪枝 + if is_valid(state, choice): + # 尝试:做出选择,更新状态 + make_choice(state, choice) + # 使用新的状态继续寻找 + backtrack(state, choices, res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + + +def is_solution(state) -> bool: + return True + + +def record_solution(state, res): + pass + + +def is_valid(state, choice) -> bool: + return True + + +def make_choice(state, choice): + pass + + +def undo_choice(state, choice): + pass diff --git a/algorithms/c08_backtrack/n_queens.py b/algorithms/c08_backtrack/n_queens.py new file mode 100644 index 0000000..9608347 --- /dev/null +++ b/algorithms/c08_backtrack/n_queens.py @@ -0,0 +1,61 @@ +""" +File: n_queens.py +Created Time: 2023-04-26 +""" + + +def backtrack( + row: int, + n: int, + state: list[list[str]], + res: list[list[list[str]]], + cols: list[bool], + diags1: list[bool], + diags2: list[bool], +): + """回溯算法:N 皇后""" + # 当放置完所有行时,记录解 + if row == n: + res.append([list(row) for row in state]) + return + # 遍历所有列 + for col in range(n): + # 计算该格子对应的主对角线和副对角线 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 + if not cols[col] and not diags1[diag1] and not diags2[diag2]: + # 尝试:将皇后放置在该格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = True + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:将该格子恢复为空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = False + + +def n_queens(n: int) -> list[list[list[str]]]: + """求解 N 皇后""" + # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + state = [["#" for _ in range(n)] for _ in range(n)] + cols = [False] * n # 记录列是否有皇后 + diags1 = [False] * (2 * n - 1) # 记录主对角线是否有皇后 + diags2 = [False] * (2 * n - 1) # 记录副对角线是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 4 + res = n_queens(n) + + print(f"输入棋盘长宽为 {n}") + print(f"皇后放置方案共有 {len(res)} 种") + for state in res: + print("--------------------") + for row in state: + print(row) diff --git a/algorithms/c08_backtrack/permutations_i.py b/algorithms/c08_backtrack/permutations_i.py new file mode 100644 index 0000000..cee01c1 --- /dev/null +++ b/algorithms/c08_backtrack/permutations_i.py @@ -0,0 +1,43 @@ +""" +File: permutations_i.py +Created Time: 2023-04-15 +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """回溯算法:全排列 I""" + # 当状态长度等于元素数量时,记录解 + if len(state) == len(choices): + res.append(list(state)) + return + # 遍历所有选择 + for i, choice in enumerate(choices): + # 剪枝:不允许重复选择元素 + if not selected[i]: + # 尝试:做出选择,更新状态 + selected[i] = True + state.append(choice) + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = False + state.pop() + + +def permutations_i(nums: list[int]) -> list[list[int]]: + """全排列 I""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 3] + + res = permutations_i(nums) + + print(f"输入数组 nums = {nums}") + print(f"所有排列 res = {res}") diff --git a/algorithms/c08_backtrack/permutations_ii.py b/algorithms/c08_backtrack/permutations_ii.py new file mode 100644 index 0000000..ee1ad29 --- /dev/null +++ b/algorithms/c08_backtrack/permutations_ii.py @@ -0,0 +1,43 @@ +""" +File: permutations_ii.py +Created Time: 2023-04-15 +""" + + +def backtrack(state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]): + """回溯算法:全排列 II""" + # 当状态长度等于元素数量时,记录解 + if len(state) == len(choices): + res.append(list(state)) + return + # 遍历所有选择 + duplicated = set[int]() + for i, choice in enumerate(choices): + # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if not selected[i] and choice not in duplicated: + # 尝试:做出选择,更新状态 + duplicated.add(choice) # 记录选择过的元素值 + selected[i] = True + state.append(choice) + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = False + state.pop() + + +def permutations_ii(nums: list[int]) -> list[list[int]]: + """全排列 II""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 2] + + res = permutations_ii(nums) + + print(f"输入数组 nums = {nums}") + print(f"所有排列 res = {res}") diff --git a/algorithms/c08_backtrack/preorder_traversal_i_compact.py b/algorithms/c08_backtrack/preorder_traversal_i_compact.py new file mode 100644 index 0000000..8f38204 --- /dev/null +++ b/algorithms/c08_backtrack/preorder_traversal_i_compact.py @@ -0,0 +1,35 @@ +""" +File: preorder_traversal_i_compact.py +Created Time: 2023-04-15 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序遍历:例题一""" + if root is None: + return + if root.val == 7: + # 记录解 + res.append(root) + pre_order(root.left) + pre_order(root.right) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 前序遍历 + res = list[TreeNode]() + pre_order(root) + + print("\n输出所有值为 7 的节点") + print([node.val for node in res]) diff --git a/algorithms/c08_backtrack/preorder_traversal_ii_compact.py b/algorithms/c08_backtrack/preorder_traversal_ii_compact.py new file mode 100644 index 0000000..57cf1a1 --- /dev/null +++ b/algorithms/c08_backtrack/preorder_traversal_ii_compact.py @@ -0,0 +1,41 @@ +""" +File: preorder_traversal_ii_compact.py +Created Time: 2023-04-15 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序遍历:例题二""" + if root is None: + return + # 尝试 + path.append(root) + if root.val == 7: + # 记录解 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 前序遍历 + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\n输出所有根节点到节点 7 的路径") + for path in res: + print([node.val for node in path]) diff --git a/algorithms/c08_backtrack/preorder_traversal_iii_compact.py b/algorithms/c08_backtrack/preorder_traversal_iii_compact.py new file mode 100644 index 0000000..e10b0f2 --- /dev/null +++ b/algorithms/c08_backtrack/preorder_traversal_iii_compact.py @@ -0,0 +1,42 @@ +""" +File: preorder_traversal_iii_compact.py +Created Time: 2023-04-15 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前序遍历:例题三""" + # 剪枝 + if root is None or root.val == 3: + return + # 尝试 + path.append(root) + if root.val == 7: + # 记录解 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 前序遍历 + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") + for path in res: + print([node.val for node in path]) diff --git a/algorithms/c08_backtrack/preorder_traversal_iii_template.py b/algorithms/c08_backtrack/preorder_traversal_iii_template.py new file mode 100644 index 0000000..cca84f4 --- /dev/null +++ b/algorithms/c08_backtrack/preorder_traversal_iii_template.py @@ -0,0 +1,68 @@ +""" +File: preorder_traversal_iii_template.py +Created Time: 2023-04-15 +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from algorithms.models import TreeNode, print_tree, list_to_tree + + +def is_solution(state: list[TreeNode]) -> bool: + """判断当前状态是否为解""" + return state and state[-1].val == 7 + + +def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): + """记录解""" + res.append(list(state)) + + +def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: + """判断在当前状态下,该选择是否合法""" + return choice is not None and choice.val != 3 + + +def make_choice(state: list[TreeNode], choice: TreeNode): + """更新状态""" + state.append(choice) + + +def undo_choice(state: list[TreeNode], choice: TreeNode): + """恢复状态""" + state.pop() + + +def backtrack(state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]): + """回溯算法:例题三""" + # 检查是否为解 + if is_solution(state): + # 记录解 + record_solution(state, res) + # 遍历所有选择 + for choice in choices: + # 剪枝:检查选择是否合法 + if is_valid(state, choice): + # 尝试:做出选择,更新状态 + make_choice(state, choice) + # 进行下一轮选择 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二叉树") + print_tree(root) + + # 回溯算法 + res = [] + backtrack(state=[], choices=[root], res=res) + + print("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点") + for path in res: + print([node.val for node in path]) diff --git a/algorithms/c08_backtrack/search_node.py b/algorithms/c08_backtrack/search_node.py new file mode 100644 index 0000000..68d0518 --- /dev/null +++ b/algorithms/c08_backtrack/search_node.py @@ -0,0 +1,44 @@ +# -*- encoding: utf-8 -*- +""" +在二叉树中搜索所有值为的节点,请返回根节点到这些节点的路径。 +使用回溯框架代码实现 +""" + + +def backtrack(state: list[int], choices: list[int], res: list[list[int]]): + # 判断是否为解 + if is_solution(state): + # 记录解 + record_solution(state, res) + # 不再继续搜索 + return + # 遍历所有选择 + for choice in choices: + # 剪枝 + if is_valid(state, choice): + # 尝试:做出选择,更新状态 + make_choice(state, choice) + # 使用新的状态继续寻找 + backtrack(state, choices, res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + + +def is_solution(state: list[int]) -> bool: + return state[-1] == 7 + + +def record_solution(state, res): + pass + + +def is_valid(state, choice) -> bool: + return True + + +def make_choice(state, choice): + pass + + +def undo_choice(state, choice): + pass diff --git a/algorithms/c08_backtrack/subset_sum_i.py b/algorithms/c08_backtrack/subset_sum_i.py new file mode 100644 index 0000000..97fe85b --- /dev/null +++ b/algorithms/c08_backtrack/subset_sum_i.py @@ -0,0 +1,47 @@ +""" +File: subset_sum_i.py +Created Time: 2023-06-17 +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """回溯算法:子集和 I""" + # 子集和等于 target 时,记录解 + if target == 0: + res.append(list(state)) + return + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in range(start, len(choices)): + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0: + break + # 尝试:做出选择,更新 target, start + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() + + +def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 I""" + state = [] # 状态(子集) + nums.sort() # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + print(f"输入数组 nums = {nums}, target = {target}") + print(f"所有和等于 {target} 的子集 res = {res}") diff --git a/algorithms/c08_backtrack/subset_sum_i_naive.py b/algorithms/c08_backtrack/subset_sum_i_naive.py new file mode 100644 index 0000000..116e420 --- /dev/null +++ b/algorithms/c08_backtrack/subset_sum_i_naive.py @@ -0,0 +1,49 @@ +""" +File: subset_sum_i_naive.py +Created Time: 2023-06-17 +""" + + +def backtrack( + state: list[int], + target: int, + total: int, + choices: list[int], + res: list[list[int]], +): + """回溯算法:子集和 I""" + # 子集和等于 target 时,记录解 + if total == target: + res.append(list(state)) + return + # 遍历所有选择 + for i in range(len(choices)): + # 剪枝:若子集和超过 target ,则跳过该选择 + if total + choices[i] > target: + continue + # 尝试:做出选择,更新元素和 total + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() + + +def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 I(包含重复子集)""" + state = [] # 状态(子集) + total = 0 # 子集和 + res = [] # 结果列表(子集列表) + backtrack(state, target, total, nums, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + print(f"输入数组 nums = {nums}, target = {target}") + print(f"所有和等于 {target} 的子集 res = {res}") + print(f"请注意,该方法输出的结果包含重复集合") diff --git a/algorithms/c08_backtrack/subset_sum_ii.py b/algorithms/c08_backtrack/subset_sum_ii.py new file mode 100644 index 0000000..104f06e --- /dev/null +++ b/algorithms/c08_backtrack/subset_sum_ii.py @@ -0,0 +1,51 @@ +""" +File: subset_sum_ii.py +Created Time: 2023-06-17 +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """回溯算法:子集和 II""" + # 子集和等于 target 时,记录解 + if target == 0: + res.append(list(state)) + return + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + # 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in range(start, len(choices)): + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0: + break + # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start and choices[i] == choices[i - 1]: + continue + # 尝试:做出选择,更新 target, start + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() + + +def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 II""" + state = [] # 状态(子集) + nums.sort() # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + print(f"输入数组 nums = {nums}, target = {target}") + print(f"所有和等于 {target} 的子集 res = {res}") diff --git a/algorithms/c09_dynamic/__init__.py b/algorithms/c09_dynamic/__init__.py new file mode 100644 index 0000000..e75dc1c --- /dev/null +++ b/algorithms/c09_dynamic/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +动态规划 +""" diff --git a/algorithms/c09_dynamic/climbing_stairs_backtrack.py b/algorithms/c09_dynamic/climbing_stairs_backtrack.py new file mode 100644 index 0000000..3ae7d79 --- /dev/null +++ b/algorithms/c09_dynamic/climbing_stairs_backtrack.py @@ -0,0 +1,36 @@ +""" +File: climbing_stairs_backtrack.py +Created Time: 2023-06-30 +""" + + +def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: + """回溯""" + # 当爬到第 n 阶时,方案数量加 1 + if state == n: + res[0] += 1 + # 遍历所有选择 + for choice in choices: + # 剪枝:不允许越过第 n 阶 + if state + choice > n: + break + # 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res) + # 回退 + + +def climbing_stairs_backtrack(n: int) -> int: + """爬楼梯:回溯""" + choices = [1, 2] # 可选择向上爬 1 或 2 阶 + state = 0 # 从第 0 阶开始爬 + res = [0] # 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res) + return res[0] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_backtrack(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/algorithms/c09_dynamic/climbing_stairs_constraint_dp.py b/algorithms/c09_dynamic/climbing_stairs_constraint_dp.py new file mode 100644 index 0000000..a85abfb --- /dev/null +++ b/algorithms/c09_dynamic/climbing_stairs_constraint_dp.py @@ -0,0 +1,28 @@ +""" +File: climbing_stairs_constraint_dp.py +Created Time: 2023-06-30 +""" + + +def climbing_stairs_constraint_dp(n: int) -> int: + """带约束爬楼梯:动态规划""" + if n == 1 or n == 2: + return 1 + # 初始化 dp 表,用于存储子问题的解 + dp = [[0] * 3 for _ in range(n + 1)] + # 初始状态:预设最小子问题的解 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + return dp[n][1] + dp[n][2] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_constraint_dp(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/algorithms/c09_dynamic/climbing_stairs_dfs.py b/algorithms/c09_dynamic/climbing_stairs_dfs.py new file mode 100644 index 0000000..797aba6 --- /dev/null +++ b/algorithms/c09_dynamic/climbing_stairs_dfs.py @@ -0,0 +1,27 @@ +""" +File: climbing_stairs_dfs.py +Created Time: 2023-06-30 +""" + + +def dfs(i: int) -> int: + """搜索""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1) + dfs(i - 2) + return count + + +def climbing_stairs_dfs(n: int) -> int: + """爬楼梯:搜索""" + return dfs(n) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/algorithms/c09_dynamic/climbing_stairs_dfs_mem.py b/algorithms/c09_dynamic/climbing_stairs_dfs_mem.py new file mode 100644 index 0000000..a0ec9e8 --- /dev/null +++ b/algorithms/c09_dynamic/climbing_stairs_dfs_mem.py @@ -0,0 +1,34 @@ +""" +File: climbing_stairs_dfs_mem.py +Created Time: 2023-06-30 +""" + + +def dfs(i: int, mem: list[int]) -> int: + """记忆化搜索""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # 若存在记录 dp[i] ,则直接返回之 + if mem[i] != -1: + return mem[i] + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # 记录 dp[i] + mem[i] = count + return count + + +def climbing_stairs_dfs_mem(n: int) -> int: + """爬楼梯:记忆化搜索""" + # mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + mem = [-1] * (n + 1) + return dfs(n, mem) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs_mem(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/algorithms/c09_dynamic/climbing_stairs_dp.py b/algorithms/c09_dynamic/climbing_stairs_dp.py new file mode 100644 index 0000000..22d9e38 --- /dev/null +++ b/algorithms/c09_dynamic/climbing_stairs_dp.py @@ -0,0 +1,39 @@ +""" +File: climbing_stairs_dp.py +Created Time: 2023-06-30 +""" + + +def climbing_stairs_dp(n: int) -> int: + """爬楼梯:动态规划""" + if n == 1 or n == 2: + return n + # 初始化 dp 表,用于存储子问题的解 + dp = [0] * (n + 1) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = 1, 2 + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] + + +def climbing_stairs_dp_comp(n: int) -> int: + """爬楼梯:空间优化后的动态规划""" + if n == 1 or n == 2: + return n + a, b = 1, 2 + for _ in range(3, n + 1): + a, b = b, a + b + return b + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dp(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") + + res = climbing_stairs_dp_comp(n) + print(f"爬 {n} 阶楼梯共有 {res} 种方案") diff --git a/algorithms/c09_dynamic/coin_change.py b/algorithms/c09_dynamic/coin_change.py new file mode 100644 index 0000000..dfd1abb --- /dev/null +++ b/algorithms/c09_dynamic/coin_change.py @@ -0,0 +1,59 @@ +""" +File: coin_change.py +Created Time: 2023-07-10 +""" + + +def coin_change_dp(coins: list[int], amt: int) -> int: + """零钱兑换:动态规划""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 状态转移:首行首列 + for a in range(1, amt + 1): + dp[0][a] = MAX + # 状态转移:其余行列 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else: + # 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + return dp[n][amt] if dp[n][amt] != MAX else -1 + + +def coin_change_dp_comp(coins: list[int], amt: int) -> int: + """零钱兑换:空间优化后的动态规划""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [MAX] * (amt + 1) + dp[0] = 0 + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + else: + # 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + return dp[amt] if dp[amt] != MAX else -1 + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 4 + + # 动态规划 + res = coin_change_dp(coins, amt) + print(f"凑到目标金额所需的最少硬币数量为 {res}") + + # 空间优化后的动态规划 + res = coin_change_dp_comp(coins, amt) + print(f"凑到目标金额所需的最少硬币数量为 {res}") diff --git a/algorithms/c09_dynamic/coin_change_ii.py b/algorithms/c09_dynamic/coin_change_ii.py new file mode 100644 index 0000000..2c23b71 --- /dev/null +++ b/algorithms/c09_dynamic/coin_change_ii.py @@ -0,0 +1,58 @@ +""" +File: coin_change_ii.py +Created Time: 2023-07-10 +""" + + +def coin_change_ii_dp(coins: list[int], amt: int) -> int: + """零钱兑换 II:动态规划""" + n = len(coins) + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 初始化首列 + for i in range(n + 1): + dp[i][0] = 1 + # 状态转移 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else: + # 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + return dp[n][amt] + + +def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: + """零钱兑换 II:空间优化后的动态规划""" + n = len(coins) + # 初始化 dp 表 + dp = [0] * (amt + 1) + dp[0] = 1 + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + else: + # 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + return dp[amt] + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 5 + n = len(coins) + + # 动态规划 + res = coin_change_ii_dp(coins, amt) + print(f"凑出目标金额的硬币组合数量为 {res}") + + # 空间优化后的动态规划 + res = coin_change_ii_dp_comp(coins, amt) + print(f"凑出目标金额的硬币组合数量为 {res}") diff --git a/algorithms/c09_dynamic/edit_distance.py b/algorithms/c09_dynamic/edit_distance.py new file mode 100644 index 0000000..7856509 --- /dev/null +++ b/algorithms/c09_dynamic/edit_distance.py @@ -0,0 +1,122 @@ +""" +File: edit_distancde.py +Created Time: 2023-07-04 +""" + + +def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: + """编辑距离:暴力搜索""" + # 若 s 和 t 都为空,则返回 0 + if i == 0 and j == 0: + return 0 + # 若 s 为空,则返回 t 长度 + if i == 0: + return j + # 若 t 为空,则返回 s 长度 + if j == 0: + return i + # 若两字符相等,则直接跳过此两字符 + if s[i - 1] == t[j - 1]: + return edit_distance_dfs(s, t, i - 1, j - 1) + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # 返回最少编辑步数 + return min(insert, delete, replace) + 1 + + +def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int: + """编辑距离:记忆化搜索""" + # 若 s 和 t 都为空,则返回 0 + if i == 0 and j == 0: + return 0 + # 若 s 为空,则返回 t 长度 + if i == 0: + return j + # 若 t 为空,则返回 s 长度 + if j == 0: + return i + # 若已有记录,则直接返回之 + if mem[i][j] != -1: + return mem[i][j] + # 若两字符相等,则直接跳过此两字符 + if s[i - 1] == t[j - 1]: + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 记录并返回最少编辑步数 + mem[i][j] = min(insert, delete, replace) + 1 + return mem[i][j] + + +def edit_distance_dp(s: str, t: str) -> int: + """编辑距离:动态规划""" + n, m = len(s), len(t) + dp = [[0] * (m + 1) for _ in range(n + 1)] + # 状态转移:首行首列 + for i in range(1, n + 1): + dp[i][0] = i + for j in range(1, m + 1): + dp[0][j] = j + # 状态转移:其余行列 + for i in range(1, n + 1): + for j in range(1, m + 1): + if s[i - 1] == t[j - 1]: + # 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + else: + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + return dp[n][m] + + +def edit_distance_dp_comp(s: str, t: str) -> int: + """编辑距离:空间优化后的动态规划""" + n, m = len(s), len(t) + dp = [0] * (m + 1) + # 状态转移:首行 + for j in range(1, m + 1): + dp[j] = j + # 状态转移:其余行 + for i in range(1, n + 1): + # 状态转移:首列 + leftup = dp[0] # 暂存 dp[i-1, j-1] + dp[0] += 1 + # 状态转移:其余列 + for j in range(1, m + 1): + temp = dp[j] + if s[i - 1] == t[j - 1]: + # 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + else: + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(dp[j - 1], dp[j], leftup) + 1 + leftup = temp # 更新为下一轮的 dp[i-1, j-1] + return dp[m] + + +"""Driver Code""" +if __name__ == "__main__": + s = "bag" + t = "pack" + n, m = len(s), len(t) + + # 暴力搜索 + res = edit_distance_dfs(s, t, n, m) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") + + # 记忆化搜索 + mem = [[-1] * (m + 1) for _ in range(n + 1)] + res = edit_distance_dfs_mem(s, t, mem, n, m) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") + + # 动态规划 + res = edit_distance_dp(s, t) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") + + # 空间优化后的动态规划 + res = edit_distance_dp_comp(s, t) + print(f"将 {s} 更改为 {t} 最少需要编辑 {res} 步") diff --git a/algorithms/c09_dynamic/knapsack.py b/algorithms/c09_dynamic/knapsack.py new file mode 100644 index 0000000..ad5c777 --- /dev/null +++ b/algorithms/c09_dynamic/knapsack.py @@ -0,0 +1,100 @@ +""" +File: knapsack.py +Created Time: 2023-07-03 +""" + + +def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: + """0-1 背包:暴力搜索""" + # 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 or c == 0: + return 0 + # 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs(wgt, val, i - 1, c) + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 返回两种方案中价值更大的那一个 + return max(no, yes) + + +def knapsack_dfs_mem( + wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int +) -> int: + """0-1 背包:记忆化搜索""" + # 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 or c == 0: + return 0 + # 若已有记录,则直接返回 + if mem[i][c] != -1: + return mem[i][c] + # 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes) + return mem[i][c] + + +def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 状态转移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:空间优化后的动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 状态转移 + for i in range(1, n + 1): + # 倒序遍历 + for c in range(cap, 0, -1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # 暴力搜索 + res = knapsack_dfs(wgt, val, n, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 记忆化搜索 + mem = [[-1] * (cap + 1) for _ in range(n + 1)] + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 动态规划 + res = knapsack_dp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 空间优化后的动态规划 + res = knapsack_dp_comp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") diff --git a/ch04_dynamic/at400_cut_steel.py b/algorithms/c09_dynamic/m01_cut_steel.py similarity index 65% rename from ch04_dynamic/at400_cut_steel.py rename to algorithms/c09_dynamic/m01_cut_steel.py index 5471c7d..e5ab2b6 100644 --- a/ch04_dynamic/at400_cut_steel.py +++ b/algorithms/c09_dynamic/m01_cut_steel.py @@ -18,25 +18,26 @@ def bottom_up_cut_rod(p, n): param p: 价格数组,长度为i的钢条价格为p[i] param n: 钢条总长度 """ - # 先初始化数组r - r = [0] * (n + 1) - # 用来保存每个最优解二维数组,ans[i]表示长度为i的最优解 + # 先初始化数组r,最优解值数组。r[i]表示长度为i的时候的最优解。 + vals = [0] * (n + 1) + # 用来保存每个最优切割方案二维数组,ans[i]表示长度为i的最优切割方案 ans = [[]] - for j in range(1, n + 1): - q = -999 + for j in range(1, n + 1): # 自底向上迭代 + max_val = -999 # 下面这个内层循环保证长度为j时候所有情况都考虑到了 # 因为i会从1迭代到j,也就是切割方案中左边方案为1,2...j的时候,跟右边已经有最优解的加起来, - # 然后算所有的加起来的和的最大值,那肯定就是最优解了! NB啊 + # 然后算所有的加起来的和的最大值,那肯定就是最优解了! + first = 0 for i in range(1, j + 1): - if q < p[i] + r[j - i]: - q = p[i] + r[j - i] - ii = i - r[j] = q - ans.append([ii] + ans[j - ii]) - return r[n], ans[len(ans) - 1] + if max_val < p[i] + vals[j - i]: + max_val = p[i] + vals[j - i] + first = i + vals[j] = max_val + ans.append([first] + ans[j - first]) + return vals[n], ans[n] + if __name__ == '__main__': - parry = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30] - for k in range(1, 11): + parry = [0, 1, 5, 8, 9, 10, 17, 18, 20, 21, 23, 25, 26, 30] + for k in range(1, 13): print(bottom_up_cut_rod(parry, k)) - diff --git a/ch04_dynamic/at401_fibonacci.py b/algorithms/c09_dynamic/m02_fibonacci.py similarity index 99% rename from ch04_dynamic/at401_fibonacci.py rename to algorithms/c09_dynamic/m02_fibonacci.py index 9ffcc27..3b400e9 100644 --- a/ch04_dynamic/at401_fibonacci.py +++ b/algorithms/c09_dynamic/m02_fibonacci.py @@ -15,4 +15,3 @@ def dynamic_fibo(n): if __name__ == '__main__': print(dynamic_fibo(10)) - diff --git a/ch04_dynamic/at402_matrix_chain.py b/algorithms/c09_dynamic/m03_matrix_chain.py similarity index 61% rename from ch04_dynamic/at402_matrix_chain.py rename to algorithms/c09_dynamic/m03_matrix_chain.py index 95ab339..19a831e 100644 --- a/ch04_dynamic/at402_matrix_chain.py +++ b/algorithms/c09_dynamic/m03_matrix_chain.py @@ -3,40 +3,41 @@ """ Topic: 动态规划:最优矩阵链乘法括号化算法 Desc : - 对于矩阵A1A2...An相乘,由于满足结合律,求一个最优括号算法使得计算的次数最少 - 对于矩阵Ai*Aj的计算次数为:p[i-1]*p[i]*p[j],row(左)*column(左)*column(右) + 对于矩阵A[1]A[2]...A[n]相乘,由于满足结合律,求一个最优括号算法使得计算的次数最少 + 对于矩阵A[i]*A[j]的计算次数为:p[i-1]*p[i]*p[j] => row(左)*column(左)*column(右) 算法思路: 给定一个序列p:
,对每个Ai
定义一个n*n二维数组m,其中m[i,j]=min(i<= k < j){m[i,k]+m[k+1,j]+p[i-1]*p[k]*p[j]}
再定义一个n-1*n-1二维数组s,其中s[i,j]表示m[i,j]时候的括号分割点,利用s可以获得最后的括号序列
-
"""
+
def matrix_order(p):
- """:param p: 矩阵规模序列,Ai行列分别为p[i-1],p[i]"""
- INF = float('inf')
+ """:param p: 矩阵规模序列,A[i]行列分别为p[i-1],p[i]"""
+ INF = float('inf') # 无穷大
n = len(p) - 1 # 矩阵长度
- m = [[0 for zz in range(n)] for z in range(n)] # 保存Ai...Aj最优值
- s = [['-' for zz in range(n)] for z in range(n)] # 保存Ai...Aj最优值时候的括号分割点
+ vals = [[0 for _ in range(n)] for _ in range(n)] # 保存子问题A[i]...A[j]最优值
+ ans = [[-1 for _ in range(n)] for _ in range(n)] # 保存子问题A[i]...A[j]最优值时候的括号分割点
for chain_len in range(2, n + 1): # chain_len表示每次循环计算链的长度2..n
for i in range(0, n - chain_len + 1):
j = i + chain_len - 1
- m[i][j] = INF # 上面两层循环则是对m方阵的右上三角(除对角线)进行某个赋值MAX
+ vals[i][j] = INF # 上面两层循环则是对m方阵的右上三角(除对角线)进行某个赋值MAX
for k in range(i, j): # 然后对每个计算最小值
# 此时m[i][k]和m[k + 1][j]一定已经有值了。why???
# 因为对于某个i,比j小的肯定赋值过
# 对于某个j,比i大的肯定也赋值过
# 上面循环方向示意图可以画下,是从右上三角,斜右下右下的循环。
- q = m[i][k] + m[k + 1][j] + p[i] * p[k + 1] * p[j + 1]
- if q < m[i][j]:
- m[i][j] = q
- s[i][j] = k
- for mm in m:
+ q = vals[i][k] + vals[k + 1][j] + p[i] * p[k + 1] * p[j + 1]
+ if q < vals[i][j]:
+ vals[i][j] = q
+ ans[i][j] = k
+ for mm in vals:
print(mm)
- for ss in s:
+ for ss in ans:
print(ss)
- print_optimal(s, 0, n - 1)
- return m, s
+ print_optimal(ans, 0, n - 1)
+ return vals, ans
+
def print_optimal(s, i, j):
"""根据保存的括号位置表打印出最后的括号最优解"""
@@ -48,7 +49,7 @@ def print_optimal(s, i, j):
print_optimal(s, s[i][j] + 1, j)
print(')', end='')
+
if __name__ == '__main__':
p = [30, 35, 15, 5, 10, 20, 25]
matrix_order(p)
-
diff --git a/ch04_dynamic/at403_elevator.py b/algorithms/c09_dynamic/m04_elevator.py
similarity index 98%
rename from ch04_dynamic/at403_elevator.py
rename to algorithms/c09_dynamic/m04_elevator.py
index 8945247..0b7eef3 100644
--- a/ch04_dynamic/at403_elevator.py
+++ b/algorithms/c09_dynamic/m04_elevator.py
@@ -44,4 +44,4 @@ def elevatorSchedule(seq):
if __name__ == '__main__':
s = [0, 0, 2, 4, 5, 7, 2, 1]
- print(elevatorSchedule(s))
\ No newline at end of file
+ print(elevatorSchedule(s))
diff --git a/ch04_dynamic/at404_lcs.py b/algorithms/c09_dynamic/m05_subsequence.py
similarity index 63%
rename from ch04_dynamic/at404_lcs.py
rename to algorithms/c09_dynamic/m05_subsequence.py
index 61b5eb8..8d24891 100644
--- a/ch04_dynamic/at404_lcs.py
+++ b/algorithms/c09_dynamic/m05_subsequence.py
@@ -1,21 +1,21 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
-# 最长公共子序列:longest-common-subsequence
+# 最长公共子序列
# author: XiongNeng
"""
-Topic: 一个子序列代表,将一个序列中去掉若干元素后得到的序列,可以间隔。
- 公共子序列就是,序列A和序列B的公共子序列
- 最长公共子序列就是,公共子序列里面长度最长的。
+一个子序列代表,将一个序列中去掉若干元素后得到的序列,可以间隔。
+公共子序列就是,序列A和序列B的公共子序列
+最长公共子序列就是,公共子序列里面长度最长的。
- 思路:
- 接受两个序列。并且满足按照结束时间升序排列
+"""
+
+
+def greedy_activity_selector(s, f):
+ n = len(s)
+ ans = [0] # 初始选择第一个活动,必定在其中,这里保存最终活动下标即可。
+ for m in range(1, n):
+ if s[m] >= f[ans[-1]]:
+ ans.append(m)
+ return ans
+
+
+if __name__ == '__main__':
+ s = [1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12]
+ f = [4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16]
+ print(greedy_activity_selector(s, f))
diff --git a/algorithms/c10_greedy/max_capacity.py b/algorithms/c10_greedy/max_capacity.py
new file mode 100644
index 0000000..706b60e
--- /dev/null
+++ b/algorithms/c10_greedy/max_capacity.py
@@ -0,0 +1,32 @@
+"""
+File: max_capacity.py
+Created Time: 2023-07-21
+"""
+
+
+def max_capacity(ht: list[int]) -> int:
+ """最大容量:贪心"""
+ # 初始化 i, j 分列数组两端
+ i, j = 0, len(ht) - 1
+ # 初始最大容量为 0
+ res = 0
+ # 循环贪心选择,直至两板相遇
+ while i < j:
+ # 更新最大容量
+ cap = min(ht[i], ht[j]) * (j - i)
+ res = max(res, cap)
+ # 向内移动短板
+ if ht[i] < ht[j]:
+ i += 1
+ else:
+ j -= 1
+ return res
+
+
+"""Driver Code"""
+if __name__ == "__main__":
+ ht = [3, 8, 5, 2, 7, 7, 3, 4]
+
+ # 贪心算法
+ res = max_capacity(ht)
+ print(f"最大容量为 {res}")
diff --git a/algorithms/c10_greedy/max_product_cutting.py b/algorithms/c10_greedy/max_product_cutting.py
new file mode 100644
index 0000000..f6ed48c
--- /dev/null
+++ b/algorithms/c10_greedy/max_product_cutting.py
@@ -0,0 +1,32 @@
+"""
+File: max_product_cutting.py
+Created Time: 2023-07-21
+"""
+
+import math
+
+
+def max_product_cutting(n: int) -> int:
+ """最大切分乘积:贪心"""
+ # 当 n <= 3 时,必须切分出一个 1
+ if n <= 3:
+ return 1 * (n - 1)
+ # 贪心地切分出 3 ,a 为 3 的个数,b 为余数
+ a, b = n // 3, n % 3
+ if b == 1:
+ # 当余数为 1 时,将一对 1 * 3 转化为 2 * 2
+ return int(math.pow(3, a - 1)) * 2 * 2
+ if b == 2:
+ # 当余数为 2 时,不做处理
+ return int(math.pow(3, a)) * 2
+ # 当余数为 0 时,不做处理
+ return int(math.pow(3, a))
+
+
+"""Driver Code"""
+if __name__ == "__main__":
+ n = 58
+
+ # 贪心算法
+ res = max_product_cutting(n)
+ print(f"最大切分乘积为 {res}")
diff --git a/algorithms/c11_advanced/__init__.py b/algorithms/c11_advanced/__init__.py
new file mode 100644
index 0000000..1cd9106
--- /dev/null
+++ b/algorithms/c11_advanced/__init__.py
@@ -0,0 +1,6 @@
+# -*- encoding: utf-8 -*-
+"""
+高级数据结构和算法:
+拓扑排序、最短路径、位图、概率统计、向量空间、B+树。
+搜索、索引、并行计算
+"""
diff --git a/algorithms/c12_leetcode/__init__.py b/algorithms/c12_leetcode/__init__.py
new file mode 100644
index 0000000..c8dcb5a
--- /dev/null
+++ b/algorithms/c12_leetcode/__init__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+"""
+LeeCode算法题库
+"""
diff --git a/algorithms/c12_leetcode/p000/__init__.py b/algorithms/c12_leetcode/p000/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p000/a05_longest_palindromic_substring.py b/algorithms/c12_leetcode/p000/a05_longest_palindromic_substring.py
new file mode 100644
index 0000000..4a41354
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/a05_longest_palindromic_substring.py
@@ -0,0 +1,39 @@
+# -*- encoding: utf-8 -*-
+"""
+5. 最长回文子串
+给你一个字符串 s,找到 s 中最长的回文子串。
+如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
+
+示例 1:
+输入:s = "babad"
+输出:"bab"
+解释:"aba" 同样是符合题意的答案。
+"""
+
+
+class Solution:
+ def longestPalindrome(self, s: str) -> str:
+ res = ''
+ for i in range(len(s)):
+ s1 = self._max_palindrome(s, i, i)
+ s2 = self._max_palindrome(s, i, i + 1)
+ res = res if len(res) > len(s1) else s1
+ res = res if len(res) > len(s2) else s2
+ return res
+
+ def _max_palindrome(self, s: str, l: int, r: int) -> str:
+ step = 0
+ max_index = len(s) - 1
+ while True:
+ left = l - step
+ right = r + step
+ if left < 0 or right > max_index:
+ break
+ if s[left] != s[right]:
+ break
+ step += 1
+ return s[left + 1:right]
+
+
+if __name__ == '__main__':
+ print(Solution().longestPalindrome("babad"))
diff --git a/algorithms/c12_leetcode/p000/a19_remove_nth_node_from_end_of_list.py b/algorithms/c12_leetcode/p000/a19_remove_nth_node_from_end_of_list.py
new file mode 100644
index 0000000..4c58946
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/a19_remove_nth_node_from_end_of_list.py
@@ -0,0 +1,59 @@
+# -*- encoding: utf-8 -*-
+"""
+19. 删除单链表的倒数第N个结点
+
+给你一个单链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
+
+两轮循环:
+1、第一轮循环获取链表的节点总数size。
+2、第二轮循环删除第size-n位置上的节点。
+"""
+
+
+# Definition for singly-linked list.
+class ListNode:
+ def __init__(self, val=0, next=None):
+ self.val = val
+ self.next = next
+
+ def __str__(self):
+ return str(self.val)
+
+
+class Solution:
+ def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
+ if not head:
+ return head
+ size = 0
+ temp = head
+ while temp:
+ size += 1
+ temp = temp.next
+ if n > size:
+ return head
+
+ pre = None
+ remove_item = head
+ for _ in range(size - n):
+ pre = remove_item
+ remove_item = remove_item.next
+ if pre:
+ pre.next = remove_item.next
+ else:
+ head = remove_item.next
+ return head
+
+
+if __name__ == '__main__':
+ # 本地运行需要以下输入输出的补充
+ head = ListNode(1)
+ head.next = ListNode(2)
+ head.next.next = ListNode(3)
+ head.next.next.next = ListNode(4)
+ head.next.next.next.next = ListNode(5)
+
+ # solve
+ solution = Solution()
+ res = solution.removeNthFromEnd(head, 5)
+ print()
+ # output
diff --git a/algorithms/c12_leetcode/p000/a20_valid_parentheses.py b/algorithms/c12_leetcode/p000/a20_valid_parentheses.py
new file mode 100644
index 0000000..a3cd5e9
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/a20_valid_parentheses.py
@@ -0,0 +1,52 @@
+# -*- encoding: utf-8 -*-
+"""
+20. 有效的括号
+
+给定一个只包括 '(',')','{','}','[',']' 的字符串s ,判断字符串是否有效。
+
+有效字符串需满足:
+
+左括号必须用相同类型的右括号闭合。
+左括号必须以正确的顺序闭合。
+
+示例 2:
+输入:s = "()[]{}"
+输出:true
+
+示例 4:
+输入:s = "([)]"
+输出:false
+
+例 5:
+输入:s = "{[]}"
+输出:true
+
+算法思路:栈的最简单的应用。左括号入栈,右括号出栈+对比匹配。不匹配则False,最后栈空则True
+"""
+from algorithms.c01_data_structure.stack.stack_linked_list import LinkedStack
+
+
+class Solution:
+ def isValid(self, s: str) -> bool:
+ p_map = {'{': '}', '[': ']', '(': ')'}
+ stack = LinkedStack()
+ for ch in s:
+ if ch in p_map:
+ stack.push(ch)
+ else:
+ stack_item = stack.pop()
+ if stack_item not in p_map or ch != p_map[stack_item]:
+ return False
+ return stack.is_empty()
+
+
+if __name__ == '__main__':
+ # print(Solution().isValid(']]]'))
+ print(Solution().isValid(']'))
+ # s = input('input something: ')
+ # print(s)
+ # while True:
+ # s = input()
+ # if not s:
+ # break
+ # print(Solution().isValid(s))
diff --git a/algorithms/c12_leetcode/p000/a21_merge_two_sorted_lists.py b/algorithms/c12_leetcode/p000/a21_merge_two_sorted_lists.py
new file mode 100644
index 0000000..14b341c
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/a21_merge_two_sorted_lists.py
@@ -0,0 +1,61 @@
+# -*- encoding: utf-8 -*-
+"""
+21. 合并两个有序链表
+
+将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
+
+可使用类似贪吃蛇的算法。当前指针就像一条贪吃蛇一样,将两个队列节点一个个吃掉。
+任何时刻,都有一个队列是蛇,一个队列是食物。但是贪吃蛇一次只能吃一边的一个节点,
+当它吃掉一个节点时,该节点所在的队列成为蛇的身体,另外一个队列成为食物。最后食物吃完循环结束。
+"""
+
+# Definition for singly-linked list.
+from typing import Optional
+
+
+class ListNode:
+ def __init__(self, val=0, next=None):
+ self.val = val
+ self.next = next
+
+ def __str__(self):
+ return self.val
+
+
+class Solution:
+ def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
+ if not list1:
+ return list2
+ if not list2:
+ return list1
+ result = list1 if list1.val <= list2.val else list2
+ snake = result # 贪吃蛇指针
+ food = list2 if list1.val <= list2.val else list1 # 要吞并的食物,跟蛇身取反
+ while food: # 只要食物还在就一直吃下去
+ if not snake.next: # 贪吃蛇自己到头了,就一口吃掉剩余所有食物,循环结束。
+ snake.next = food
+ break
+ if snake.next.val <= food.val:
+ snake = snake.next # 自己的身体的下一部分比较小,就先吃自己。
+ else:
+ temp_food = snake.next
+ snake.next = food # 食物比较小,先吃食物,让食物变成蛇的一部分
+ food = temp_food # 将原来剩余的蛇身变成食物。这样完成了蛇身和食物的交换。
+ return result
+
+
+if __name__ == '__main__':
+ a = ListNode(1)
+ b = ListNode(2)
+ c = ListNode(4)
+ a.next = b
+ b.next = c
+
+ aa = ListNode(1)
+ bb = ListNode(3)
+ cc = ListNode(4)
+ aa.next = bb
+ bb.next = cc
+
+ r = Solution().mergeTwoLists(a, aa)
+ print(r)
diff --git a/algorithms/c12_leetcode/p000/a23_merge_k_sorted_lists.py b/algorithms/c12_leetcode/p000/a23_merge_k_sorted_lists.py
new file mode 100644
index 0000000..27aed33
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/a23_merge_k_sorted_lists.py
@@ -0,0 +1,58 @@
+# -*- encoding: utf-8 -*-
+"""
+23. 合并 K 个升序链表
+
+给你一个链表数组,每个链表都已经按升序排列。
+请你将所有链表合并到一个升序链表中,返回合并后的链表。
+
+输入:lists = [[1,4,5],[1,3,4],[2,6]]
+输出:[1,1,2,3,4,4,5,6]
+"""
+from typing import List, Optional
+
+from algorithms.c01_data_structure.linkedlist import ListNode
+from queue import PriorityQueue
+
+
+class Solution:
+ def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
+ if not lists:
+ return None
+ p = None
+ p_head = None
+ pq = PriorityQueue()
+ for list_idx, each in enumerate(lists):
+ if each:
+ pq.put((each.val, list_idx, each))
+ while not pq.empty():
+ node = pq.get()
+ if p:
+ p.next = node[2]
+ p = p.next
+ else:
+ p = node[2]
+ p_head = node[2]
+ if node[2].next:
+ pq.put((node[2].next.val, node[1], node[2].next))
+ return p_head
+
+
+if __name__ == '__main__':
+ """
+ 输入:lists = [[1,4,5],[1,3,4],[2,6]]
+ 输出:[1,1,2,3,4,4,5,6]
+ """
+ node1 = ListNode(5)
+ node1 = ListNode(4, node1)
+ node1 = ListNode(1, node1)
+ node2 = ListNode(4)
+ node2 = ListNode(3, node2)
+ node2 = ListNode(1, node2)
+ node3 = ListNode(6)
+ node3 = ListNode(2, node3)
+ lists = [node1, node2, node3]
+ head = Solution().mergeKLists(lists)
+ while head:
+ print(head.val, end=' ')
+ head = head.next
+ ...
diff --git a/algorithms/c12_leetcode/p000/a70_climbing_stairs.py b/algorithms/c12_leetcode/p000/a70_climbing_stairs.py
new file mode 100644
index 0000000..83663eb
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/a70_climbing_stairs.py
@@ -0,0 +1,30 @@
+# -*- encoding: utf-8 -*-
+"""
+70. 爬楼梯
+假设你正在爬楼梯。需要 n阶你才能到达楼顶。
+每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
+注意:给定 n 是一个正整数。
+"""
+
+
+class Solution:
+ def climb_stairs(self, n: int) -> int:
+ if n == 1:
+ return 1
+ if n == 2:
+ return 2
+ a, b = 1, 2
+ for i in range(3, n + 1):
+ a, b = b, a + b
+
+ return b
+
+
+if __name__ == '__main__':
+ import sys
+
+ while True:
+ line = sys.stdin.readline().strip()
+ if line == '':
+ break
+ print(Solution().climb_stairs(int(line)))
diff --git a/algorithms/c12_leetcode/p000/a86_partition_list.py b/algorithms/c12_leetcode/p000/a86_partition_list.py
new file mode 100644
index 0000000..f8537c6
--- /dev/null
+++ b/algorithms/c12_leetcode/p000/a86_partition_list.py
@@ -0,0 +1,59 @@
+# -*- encoding: utf-8 -*-
+"""
+给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
+
+你应当 保留 两个分区中每个节点的初始相对位置。
+
+输入:head = [1,4,3,2,5,2], x = 3
+输出:[1,2,2,4,3,5]
+
+输入:head = [2,1], x = 2
+输出:[1,2]
+"""
+from typing import Optional
+
+from algorithms.c01_data_structure.linkedlist import ListNode
+
+
+class Solution:
+ def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
+ p1 = None # 小于x的链表
+ p1_head = None
+ p2 = None # 大于等于x的链表
+ p2_head = None
+ p = head # 循环原链表
+ while p:
+ if p.val < x:
+ if not p1:
+ p1 = p
+ p1_head = p
+ else:
+ p1.next = p
+ p1 = p1.next
+ else:
+ if not p2:
+ p2 = p
+ p2_head = p
+ else:
+ p2.next = p
+ p2 = p2.next
+ temp = p.next
+ p.next = None
+ p = temp
+ if p1:
+ p1.next = p2_head
+ return p1_head if p1_head else p2_head
+
+
+if __name__ == '__main__':
+ # [1,4,3,2,5,2]
+ head = ListNode(2)
+ head = ListNode(5, head)
+ head = ListNode(2, head)
+ head = ListNode(3, head)
+ head = ListNode(4, head)
+ head = ListNode(1, head)
+ head = Solution().partition(head, 3)
+ while head:
+ print(head.val, end=' ')
+ head = head.next
diff --git a/algorithms/c12_leetcode/p100/__init__.py b/algorithms/c12_leetcode/p100/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p100/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p100/a141_linked_list_cycle.py b/algorithms/c12_leetcode/p100/a141_linked_list_cycle.py
new file mode 100644
index 0000000..c1b3e9c
--- /dev/null
+++ b/algorithms/c12_leetcode/p100/a141_linked_list_cycle.py
@@ -0,0 +1,48 @@
+# -*- encoding: utf-8 -*-
+"""
+141. 环形链表
+给你一个链表的头节点 head ,判断链表中是否有环。
+
+如果链表中有某个节点,可以通过连续跟踪 next指针再次到达,则链表中存在环。
+不能直接通过判定next是否为null来决定是否有环,因为如有环则是一个无限循环。
+
+通过快慢指针法,慢指针走一步,快指针走两步。
+如果快指针率先走到尽头结束,无环。
+否则快指针率先进环,慢指针则在后面进环。快指针一定会追上满指针,一旦追上了,循环结束。
+"""
+# Definition for singly-linked list.
+from typing import Optional
+
+
+class ListNode:
+ def __init__(self, x):
+ self.val = x
+ self.next = None
+
+
+class Solution:
+ def hasCycle(self, head: Optional[ListNode]) -> bool:
+ if not head or not head.next or not head.next.next:
+ return False
+ slow = head.next
+ fast = head.next.next
+ while slow != fast:
+ if not fast.next or not fast.next.next:
+ return False
+ slow = slow.next
+ fast = fast.next.next
+ return True
+
+
+if __name__ == '__main__':
+ a = ListNode(1)
+ b = ListNode(2)
+ c = ListNode(3)
+ d = ListNode(4)
+ f = ListNode(5)
+ a.next = b
+ b.next = c
+ c.next = d
+ d.next = f
+ f.next = b
+ print(Solution().hasCycle(a))
diff --git a/algorithms/c12_leetcode/p100/a142_linked_list_cycle_ii.py b/algorithms/c12_leetcode/p100/a142_linked_list_cycle_ii.py
new file mode 100644
index 0000000..c4b45d4
--- /dev/null
+++ b/algorithms/c12_leetcode/p100/a142_linked_list_cycle_ii.py
@@ -0,0 +1,36 @@
+# -*- encoding: utf-8 -*-
+"""
+142. 环形链表 II
+给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
+
+如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,
+评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。
+注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
+
+解题思路:
+判断链表是否包含环属于经典问题了,解决方案也是用快慢指针:
+每当慢指针 slow 前进一步,快指针 fast 就前进两步。
+如果 fast 最终遇到空指针,说明链表中没有环;如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈,
+说明链表中含有环。
+当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
+"""
+from typing import Optional
+
+from algorithms.c01_data_structure.linkedlist import ListNode
+
+
+class Solution:
+ def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
+ slow, fast = head, head
+ while fast and fast.next:
+ slow = slow.next
+ fast = fast.next.next
+ if fast == slow:
+ break
+ if not fast or not fast.next:
+ return None
+ slow = head
+ while slow != fast:
+ fast = fast.next
+ slow = slow.next
+ return slow
diff --git a/algorithms/c12_leetcode/p100/a155_min_stack.py b/algorithms/c12_leetcode/p100/a155_min_stack.py
new file mode 100644
index 0000000..a2203ce
--- /dev/null
+++ b/algorithms/c12_leetcode/p100/a155_min_stack.py
@@ -0,0 +1,55 @@
+# -*- encoding: utf-8 -*-
+"""
+155. 最小栈
+设计一个支持 push、pop、top 操作,并能在常数时间内检索到最小元素的栈。
+
+实现 MinStack 类:
+
+MinStack() 初始化堆栈对象。
+void push(int val) 将元素val推入堆栈。
+void pop() 删除堆栈顶部的元素。
+int top() 获取堆栈顶部的元素。
+int getMin() 获取堆栈中的最小元素。
+
+算法思路:
+通过空间换时间的方法。将每次入栈的节点包装成一个对象,存储两个信息,一个是节点值,一个是当前栈的最小值。
+这样获取栈顶元素就同时能获取到这两个值了。
+"""
+from algorithms.c01_data_structure import Node
+
+
+class MinStack:
+
+ def __init__(self):
+ self.first = None # top of stack
+
+ def push(self, val: int) -> None:
+ current_min = self.getMin()
+ min_val = min(val, current_min) if current_min is not None else val
+ self.first = Node((val, min_val), self.first)
+
+ def pop(self) -> None:
+ if self.first:
+ self.first = self.first.next
+
+ def top(self) -> int:
+ if not self.first:
+ return None
+ return self.first.data[0]
+
+ def getMin(self) -> int:
+ if not self.first:
+ return None
+ return self.first.data[1]
+
+
+if __name__ == '__main__':
+ pass
+ # Your MinStack object will be instantiated and called as such:
+ obj = MinStack()
+ obj.push(0)
+ obj.push(1)
+ obj.push(0)
+ print(obj.getMin())
+ obj.pop()
+ print(obj.getMin())
diff --git a/algorithms/c12_leetcode/p100/a160_intersection_of_two_linked_lists.py b/algorithms/c12_leetcode/p100/a160_intersection_of_two_linked_lists.py
new file mode 100644
index 0000000..36db51f
--- /dev/null
+++ b/algorithms/c12_leetcode/p100/a160_intersection_of_two_linked_lists.py
@@ -0,0 +1,27 @@
+# -*- encoding: utf-8 -*-
+"""
+160. 相交链表
+给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
+输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
+输出:Intersected at '8'
+输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
+输出:Intersected at '2'
+
+解题思路:
+我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,
+这样相当于「逻辑上」两条链表接在了一起。
+如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1
+"""
+from typing import Optional
+
+from algorithms.c01_data_structure.linkedlist import ListNode
+
+
+class Solution:
+ def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
+ p1 = headA
+ p2 = headB
+ while p1 != p2:
+ p1 = p1.next if p1 else headB
+ p2 = p2.next if p2 else headA
+ return p1
diff --git a/algorithms/c12_leetcode/p100/a167_two_sum_ii_input_array_is_sorted.py b/algorithms/c12_leetcode/p100/a167_two_sum_ii_input_array_is_sorted.py
new file mode 100644
index 0000000..fdf1de4
--- /dev/null
+++ b/algorithms/c12_leetcode/p100/a167_two_sum_ii_input_array_is_sorted.py
@@ -0,0 +1,30 @@
+# -*- encoding: utf-8 -*-
+"""
+167. 两数之和 II - 输入有序数组
+给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,
+请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,
+则 1 <= index1 < index2 <= numbers.length 。
+以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
+你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
+你所设计的解决方案必须只使用常量级的额外空间。
+
+示例 1:
+输入:numbers = [2,7,11,15], target = 9
+输出:[1,2]
+解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
+"""
+from typing import List, Optional
+
+
+class Solution:
+ def twoSum(self, numbers: List[int], target: int) -> Optional[List[int]]:
+ left, right = 0, len(numbers) - 1
+ while left < right:
+ sum_ = numbers[left] + numbers[right]
+ if sum_ == target:
+ return [left + 1, right + 1]
+ elif sum_ < target:
+ left += 1
+ else:
+ right -= 1
+ return [-1, -1]
diff --git a/algorithms/c12_leetcode/p1000/__init__.py b/algorithms/c12_leetcode/p1000/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1000/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1100/__init__.py b/algorithms/c12_leetcode/p1100/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1100/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1200/__init__.py b/algorithms/c12_leetcode/p1200/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1200/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1300/__init__.py b/algorithms/c12_leetcode/p1300/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1300/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1400/__init__.py b/algorithms/c12_leetcode/p1400/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1400/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1500/__init__.py b/algorithms/c12_leetcode/p1500/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1500/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1600/__init__.py b/algorithms/c12_leetcode/p1600/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1600/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1700/__init__.py b/algorithms/c12_leetcode/p1700/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1700/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1800/__init__.py b/algorithms/c12_leetcode/p1800/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1800/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p1900/__init__.py b/algorithms/c12_leetcode/p1900/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p1900/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p200/__init__.py b/algorithms/c12_leetcode/p200/__init__.py
new file mode 100644
index 0000000..5d50274
--- /dev/null
+++ b/algorithms/c12_leetcode/p200/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+"""
+description
+"""
diff --git a/algorithms/c12_leetcode/p200/a206_reverse_linked_list.py b/algorithms/c12_leetcode/p200/a206_reverse_linked_list.py
new file mode 100644
index 0000000..54a5eb9
--- /dev/null
+++ b/algorithms/c12_leetcode/p200/a206_reverse_linked_list.py
@@ -0,0 +1,57 @@
+# -*- encoding: utf-8 -*-
+"""
+206. 反转链表
+给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
+输入:head = [1,2,3,4,5]
+输出:[5,4,3,2,1]
+
+提示:
+链表中节点的数目范围是 [0, 5000]
+-5000 <= Node.val <= 5000
+
+进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
+
+使用贪吃蛇算法,将原始链表比喻成糖葫芦,贪吃蛇一口一个,把糖葫芦吃完为止。
+"""
+
+
+# Definition for singly-linked list.
+class ListNode:
+ def __init__(self, val=0, next=None):
+ self.val = val
+ self.next = next
+
+ def __str__(self):
+ return str(self.val)
+
+
+class Solution:
+ def reverseList(self, head: ListNode) -> ListNode:
+ if not head:
+ return None
+ food = head
+ snake = None
+
+ while food:
+ apple = food # 取下糖葫芦第一个苹果
+ food = food.next # 糖葫芦减掉一个
+ apple.next = snake # 吞掉一个苹果
+ snake = apple # 调整蛇的头部指向这个苹果
+ return snake
+
+
+if __name__ == '__main__':
+ # 本地运行需要以下输入输出的补充
+ # list_input = list(input('input list: ').split())
+
+ head = ListNode(1)
+ head.next = ListNode(2)
+ head.next.next = ListNode(3)
+ head.next.next.next = ListNode(4)
+ head.next.next.next.next = ListNode(5)
+
+ # solve
+ solution = Solution()
+ res = solution.reverseList(head)
+ print()
+ # output
diff --git a/algorithms/c12_leetcode/p200/a215_kth_largest_element_in_an_array.py b/algorithms/c12_leetcode/p200/a215_kth_largest_element_in_an_array.py
new file mode 100644
index 0000000..1033b79
--- /dev/null
+++ b/algorithms/c12_leetcode/p200/a215_kth_largest_element_in_an_array.py
@@ -0,0 +1,52 @@
+# -*- encoding: utf-8 -*-
+"""
+215. 数组中的第K个最大元素
+
+给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
+请注意,你需要找的是数组排序后的第k个最大的元素,而不是第k个不同的元素。
+
+输入: [3,2,1,5,6,4] 和 k = 2
+输出: 5
+
+输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
+输出: 4
+
+算法思路:
+采用快排的分治思想。我们选择数组区间 A[0...n-1]的最后一个元素 A[n-1]作为 pivot,
+对数组 A[0...n-1]原地分区,左边大右边小。分区完成后数组就分成了三部分,A[0...p-1]、A[p]、A[p+1...n-1]。
+如果 p+1=K,那 A[p]就是要求解的元素;
+如果 p+1