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学习笔记和心得。** + +![公众号](https://github.com/yidao620c/python3-cookbook/raw/master/exts/wuxiong.jpg) + ## 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的公共子序列 +最长公共子序列就是,公共子序列里面长度最长的。 - 思路: - 接受两个序列作为输入 - 将两个序列安装下标变成矩阵或者是二维数组c(m+1 * n+1) - 按行主次序(row-major-order)计算表项,即首先计算C的第一行,然后是第二行。。 - 另外还维护一个二维数组b(m * n),b[i,j]指向的表项对应计算c[i,j]时选择的子问题最优解 - c[m-1][n-1]保存了X和Y的LCS的长度 -Desc : +思路: +接受两个序列作为输入 +将两个序列按照下标变成矩阵或者是二维数组c(m+1 * n+1) +按行主次序(row-major-order)计算表项,即首先计算C的第一行,然后是第二行。。 +另外还维护一个二维数组b(m * n),b[i,j]指向的表项对应计算c[i,j]时选择的子问题最优解 +c[m-1][n-1]保存了X和Y的LCS的长度 """ + def lcs(arr1, arr2): m = len(arr1) n = len(arr2) @@ -38,6 +38,7 @@ def lcs(arr1, arr2): print('一个最优解:%s' % str(rlcs)) return c[m][n], rlcs + def get_lcs_arr(b, X, i, j, arr): if i < 0 or j < 0: return @@ -49,7 +50,8 @@ def get_lcs_arr(b, X, i, j, arr): else: get_lcs_arr(b, X, i, j - 1, arr) + if __name__ == '__main__': x = ['A', 'B', 'D', 'A', 'C', 'K'] y = ['B', 'D', 'D', 'E', 'C', 'K', 'M'] - lcs(x, y) \ No newline at end of file + lcs(x, y) diff --git a/algorithms/c09_dynamic/m06_bag.py b/algorithms/c09_dynamic/m06_bag.py new file mode 100644 index 0000000..89d7afe --- /dev/null +++ b/algorithms/c09_dynamic/m06_bag.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +""" +动态规划解决0-1背包问题 +有n个商品a[0..n],价值v[0..n]美元,重量w[0..n]磅。一个背包最多能承重top磅。 +怎样装商品能获得最大价值。 +""" + + +def bag_choice(v, w, top): + n = len(v) - 1 + # 先初始化最优解值二维数组 + # vals[i][w]表示:对于前i个物品,当前背包的容量为w时,这种情况下可以装下的最大价值 + vals = [[0 for _ in range(top + 1)] for _ in range(n + 1)] + # 用来保存每个最优方案三维数组,ans[i][j]表示前i个物品上限为j时候的最优方案 + ans = [[[0 for _ in range(i + 1)] for _ in range(top + 1)] for i in range(n + 1)] + for i in range(1, n + 1): # 物品选择从1到n + for j in range(1, top + 1): # 重量上限从1到top + if j - w[i] < 0: + # 这时候只能选择不选i + vals[i][j] = vals[i - 1][j] + # 方案为前i-1最优方案+不选择i + ans[i][j] = ans[i - 1][j] + [0] + else: + # 不选择i的时候,则最优解就是前i-1个物品上限为j的最优解 + unchoose_val = vals[i - 1][j] + # 选择i的时候,则最优解就是前i-1个物品上限为j-w[i]最优解+v[i]的和 + choose_val = vals[i - 1][j - w[i]] + v[i] + if unchoose_val > choose_val: + vals[i][j] = unchoose_val + # 方案为前i-1最优方案+不选择i + ans[i][j] = ans[i - 1][j] + [0] + else: + vals[i][j] = choose_val + # 方案为前i-1最优方案+选择i + ans[i][j] = ans[i - 1][j - w[i]] + [1] + return vals[n][top], ans[n][top] + + +if __name__ == '__main__': + w = [0, 1, 2, 12, 1, 4] # 重量数组 + v = [0, 1, 2, 4, 2, 10] # 价值数组 + top = 7 # 背包重量上限 + print(bag_choice(v, w, top)) diff --git a/algorithms/c09_dynamic/min_cost_climbing_stairs_dp.py b/algorithms/c09_dynamic/min_cost_climbing_stairs_dp.py new file mode 100644 index 0000000..082699a --- /dev/null +++ b/algorithms/c09_dynamic/min_cost_climbing_stairs_dp.py @@ -0,0 +1,42 @@ +""" +File: min_cost_climbing_stairs_dp.py +Created Time: 2023-06-30 +""" + + +def min_cost_climbing_stairs_dp(cost: list[int]) -> int: + """爬楼梯最小代价:动态规划""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + # 初始化 dp 表,用于存储子问题的解 + dp = [0] * (n + 1) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = cost[1], cost[2] + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + return dp[n] + + +def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: + """爬楼梯最小代价:空间优化后的动态规划""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + a, b = cost[1], cost[2] + for i in range(3, n + 1): + a, b = b, min(a, b) + cost[i] + return b + + +"""Driver Code""" +if __name__ == "__main__": + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print(f"输入楼梯的代价列表为 {cost}") + + res = min_cost_climbing_stairs_dp(cost) + print(f"爬完楼梯的最低代价为 {res}") + + res = min_cost_climbing_stairs_dp_comp(cost) + print(f"爬完楼梯的最低代价为 {res}") diff --git a/algorithms/c09_dynamic/min_path_sum.py b/algorithms/c09_dynamic/min_path_sum.py new file mode 100644 index 0000000..d42866c --- /dev/null +++ b/algorithms/c09_dynamic/min_path_sum.py @@ -0,0 +1,103 @@ +""" +File: min_path_sum.py +Created Time: 2023-07-04 +""" + +from math import inf + + +def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: + """最小路径和:暴力搜索""" + # 若为左上角单元格,则终止搜索 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0: + return inf + # 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j] + + +def min_path_sum_dfs_mem( + grid: list[list[int]], mem: list[list[int]], i: int, j: int +) -> int: + """最小路径和:记忆化搜索""" + # 若为左上角单元格,则终止搜索 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0: + return inf + # 若已有记录,则直接返回 + if mem[i][j] != -1: + return mem[i][j] + # 左边和上边单元格的最小路径代价 + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] + + +def min_path_sum_dp(grid: list[list[int]]) -> int: + """最小路径和:动态规划""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [[0] * m for _ in range(n)] + dp[0][0] = grid[0][0] + # 状态转移:首行 + for j in range(1, m): + dp[0][j] = dp[0][j - 1] + grid[0][j] + # 状态转移:首列 + for i in range(1, n): + dp[i][0] = dp[i - 1][0] + grid[i][0] + # 状态转移:其余行列 + for i in range(1, n): + for j in range(1, m): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + return dp[n - 1][m - 1] + + +def min_path_sum_dp_comp(grid: list[list[int]]) -> int: + """最小路径和:空间优化后的动态规划""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [0] * m + # 状态转移:首行 + dp[0] = grid[0][0] + for j in range(1, m): + dp[j] = dp[j - 1] + grid[0][j] + # 状态转移:其余行 + for i in range(1, n): + # 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + # 状态转移:其余列 + for j in range(1, m): + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + return dp[m - 1] + + +"""Driver Code""" +if __name__ == "__main__": + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = len(grid), len(grid[0]) + + # 暴力搜索 + res = min_path_sum_dfs(grid, n - 1, m - 1) + print(f"从左上角到右下角的做小路径和为 {res}") + + # 记忆化搜索 + mem = [[-1] * m for _ in range(n)] + res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) + print(f"从左上角到右下角的做小路径和为 {res}") + + # 动态规划 + res = min_path_sum_dp(grid) + print(f"从左上角到右下角的做小路径和为 {res}") + + # 空间优化后的动态规划 + res = min_path_sum_dp_comp(grid) + print(f"从左上角到右下角的做小路径和为 {res}") diff --git a/algorithms/c09_dynamic/unbounded_knapsack.py b/algorithms/c09_dynamic/unbounded_knapsack.py new file mode 100644 index 0000000..d393098 --- /dev/null +++ b/algorithms/c09_dynamic/unbounded_knapsack.py @@ -0,0 +1,54 @@ +""" +File: unbounded_knapsack.py +Created Time: 2023-07-10 +""" + + +def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 状态转移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:空间优化后的动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 动态规划 + res = unbounded_knapsack_dp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") + + # 空间优化后的动态规划 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") diff --git a/algorithms/c10_greedy/__init__.py b/algorithms/c10_greedy/__init__.py new file mode 100644 index 0000000..870df7d --- /dev/null +++ b/algorithms/c10_greedy/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +贪心算法 +""" diff --git a/algorithms/c10_greedy/coin_change_greedy.py b/algorithms/c10_greedy/coin_change_greedy.py new file mode 100644 index 0000000..66a5d24 --- /dev/null +++ b/algorithms/c10_greedy/coin_change_greedy.py @@ -0,0 +1,47 @@ +""" +File: coin_change_greedy.py +Created Time: 2023-07-18 +""" + + +def coin_change_greedy(coins: list[int], amt: int) -> int: + """零钱兑换:贪心""" + # 假设 coins 列表有序 + i = len(coins) - 1 + count = 0 + # 循环进行贪心选择,直到无剩余金额 + while amt > 0: + # 找到小于且最接近剩余金额的硬币 + while i > 0 and coins[i] > amt: + i -= 1 + # 选择 coins[i] + amt -= coins[i] + count += 1 + # 若未找到可行方案,则返回 -1 + return count if amt == 0 else -1 + + +"""Driver Code""" +if __name__ == "__main__": + # 贪心:能够保证找到全局最优解 + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"凑到 {amt} 所需的最少硬币数量为 {res}") + + # 贪心:无法保证找到全局最优解 + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"凑到 {amt} 所需的最少硬币数量为 {res}") + print(f"实际上需要的最少数量为 3 ,即 20 + 20 + 20") + + # 贪心:无法保证找到全局最优解 + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"凑到 {amt} 所需的最少硬币数量为 {res}") + print(f"实际上需要的最少数量为 2 ,即 49 + 49") diff --git a/algorithms/c10_greedy/fractional_knapsack.py b/algorithms/c10_greedy/fractional_knapsack.py new file mode 100644 index 0000000..fde8fc9 --- /dev/null +++ b/algorithms/c10_greedy/fractional_knapsack.py @@ -0,0 +1,45 @@ +""" +File: fractional_knapsack.py +Created Time: 2023-07-19 +""" + + +class Item: + """物品""" + + def __init__(self, w: int, v: int): + self.w = w # 物品重量 + self.v = v # 物品价值 + + +def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: + """分数背包:贪心""" + # 创建物品列表,包含两个属性:重量、价值 + items = [Item(w, v) for w, v in zip(wgt, val)] + # 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort(key=lambda item: item.v / item.w, reverse=True) + # 循环贪心选择 + res = 0 + for item in items: + if item.w <= cap: + # 若剩余容量充足,则将当前物品整个装进背包 + res += item.v + cap -= item.w + else: + # 若剩余容量不足,则将当前物品的一部分装进背包 + res += (item.v / item.w) * cap + # 已无剩余容量,因此跳出循环 + break + return res + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # 贪心算法 + res = fractional_knapsack(wgt, val, cap) + print(f"不超过背包容量的最大物品价值为 {res}") diff --git a/algorithms/c10_greedy/m01_activity.py b/algorithms/c10_greedy/m01_activity.py new file mode 100644 index 0000000..69f269d --- /dev/null +++ b/algorithms/c10_greedy/m01_activity.py @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- +""" +活动选择问题。 +活动序列a[1..n],开始时间序列s[1..n],结束时间序列f[1..n]。 +每个a[i]活动时间为。并且满足按照结束时间升序排列 +""" + + +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+1K,说明第 K 大元素出现在左边 A[0...p-1]区间。 +我们再按照上面的思路递归地在 A[p+1...n-1] 或者A[0...p-1]区间内存查找。 +""" +from typing import List + + +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + return self.recursive_choose_pivot(nums, 0, len(nums) - 1, k) + + def recursive_choose_pivot(self, nums: List[int], start, end, k: int) -> int: + while start <= end: + pivot = self.choose_pivot_by_last(nums, start, end) + if pivot + 1 == k: + return nums[pivot] + if pivot + 1 < k: + return self.recursive_choose_pivot(nums, pivot + 1, end, k) + else: + return self.recursive_choose_pivot(nums, start, pivot - 1, k) + + def choose_pivot_by_last(self, nums: List[int], start, end) -> int: + pivot_value = nums[end] # 将最后一个元素定为pivot + i = start - 1 # 以退为进,初始值设置为start-1 + for j in range(start, end): + if nums[j] >= pivot_value: # 将大的数放左边 + i += 1 + nums[i], nums[j] = nums[j], nums[i] + nums[i + 1], nums[end] = nums[end], nums[i + 1] + return i + 1 + + +if __name__ == '__main__': + res = Solution().findKthLargest([3, 2, 1, 5, 6, 4], 2) + assert res == 5 diff --git a/algorithms/c12_leetcode/p200/a224_basic_calculator.py b/algorithms/c12_leetcode/p200/a224_basic_calculator.py new file mode 100644 index 0000000..4158d11 --- /dev/null +++ b/algorithms/c12_leetcode/p200/a224_basic_calculator.py @@ -0,0 +1,138 @@ +# -*- encoding: utf-8 -*- +""" +224. 基本计算器 + +给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 + +注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()。 + +输入:s = "1 + 1" +输出:2 + +输入:s = " 2-1 + 2 " +输出:3 + +输入:s = "(1+(4+5+2)-3)+(6+8)" +输出:23 + +算法描述:使用两个栈来实现。 +正常不带括号的+-*/运算的逻辑: + 1)操作数有优先级,+-优先级低,*/优先级高。 + 2)左栈压入数字,右栈压入操作数。 + 3)每次向右栈压入操作数的时候,先对比栈顶操作数跟当前要入栈操作数优先级。 + i:如果当前入栈优先级高,就直接入栈。 + ii:否则将栈顶操作数弹出,并从左栈弹出2个数来做运算。将结果再次压入左栈 + iii:最后将当前要入栈的操作数入栈 + 4)循环结束后,如果操作数栈还有剩余操作数。就每次右栈弹1个操作数,左栈弹2个数做运算。结果入左栈 + 5)最后判断左栈是否只剩下一个数,如果是则返回结果。否则表达式有问题,报错。 + +带括号()的+-*/运算的逻辑: + 1)操作数有优先级,+-优先级低,*/优先级高。 + 2)左栈压入数字,右栈压入操作数。 + 3)每次向右栈压入操作数的时候逻辑如下: + i:如果碰到(直接入栈。 + ii:如果当前为操作符+-*/,并且栈顶为(,直接入栈。 + iii:如果当前为操作符+-*/,并且栈顶不为(,则对比两者优先级 + iii-a:如果当前入栈优先级高,就直接入栈。 + iii-b:否则将栈顶操作数弹出,并从左栈弹出2个数来做运算。将结果再次压入左栈。然后再将当前操作符压入右栈 + IV:如果当前为操作符),则循环从右栈弹1个操作数,左栈弹2个数做运算压入左栈。直到弹出第一个(为止。 + 4)循环结束后,如果操作数栈还有剩余操作数。就每次右栈弹1个操作数,左栈弹2个数做运算。结果入左栈 + 5)最后判断左栈是否只剩下一个数,如果是则返回结果。否则表达式有问题,报错。 +""" +import re + + +class LinkedNode: + def __init__(self, val_, next_=None): + self.val = val_ + self.next = next_ + + def __str__(self): + return str(self.val) + + +class LinkedStack: + def __init__(self): + self.first = None + + def push(self, val): + self.first = LinkedNode(val, self.first) + + def pop(self): + if not self.first: + return None + res = self.first.val + self.first = self.first.next + return res + + def peek(self): + if not self.first: + return None + return self.first.val + + def empty(self): + return self.first is None + + +class Solution: + def __init__(self): + self.left_stack = LinkedStack() + self.right_stack = LinkedStack() + self.calc_funcs = { + '+': lambda a, b: a + b, + '-': lambda a, b: a - b, + '*': lambda a, b: a * b, + '/': lambda a, b: a / b, + } + + def calculate(self, s: str) -> int: + expression = s.replace(' ', '') # 去掉空格 + expression = '0' + expression if expression.startswith('-') else expression + expression = expression.replace('(-', '(0-') + expression_list = [s for s in re.split(r'([()+*/])|((?<=[\d)])-)', expression) if s] + for c in expression_list: + if self.is_number(c): + self.left_stack.push(int(c)) + else: + if c == '(': + self.right_stack.push(c) + elif c in '+-*/' and self.right_stack.peek() == '(': + self.right_stack.push(c) + elif c in '+-*/' and self.right_stack.peek() != '(': + if c in '*/' and self.right_stack.peek() in '+-': + self.right_stack.push(c) + else: + self.calc_unit() + self.right_stack.push(c) + elif c == ')': + while self.right_stack.peek() != '(': + self.calc_unit() + self.right_stack.pop() + else: + raise ValueError('wrong cal expression') + while not self.right_stack.empty(): + self.calc_unit() + if self.left_stack.first.next is not None: + raise ValueError('wrong input expression') + return self.left_stack.pop() + + def is_number(self, str_): + try: + int(str_) + return True + except ValueError: + return False + + def calc_unit(self): + if self.right_stack.empty(): + return + cal = self.right_stack.pop() + val2 = self.left_stack.pop() + val1 = self.left_stack.pop() + if val2 is None or val1 is None: + raise ValueError('wrong number expression') + self.left_stack.push(self.calc_funcs[cal](val1, val2)) + + +if __name__ == '__main__': + print(Solution().calculate("- (3 - (- (4 + 5) ) )")) diff --git a/algorithms/c12_leetcode/p200/a232_implement_queue_using_stacks.py b/algorithms/c12_leetcode/p200/a232_implement_queue_using_stacks.py new file mode 100644 index 0000000..98e651f --- /dev/null +++ b/algorithms/c12_leetcode/p200/a232_implement_queue_using_stacks.py @@ -0,0 +1,88 @@ +# -*- encoding: utf-8 -*- +""" +232. 用栈实现队列 +请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty) + +实现 MyQueue 类: +void push(int x) 将元素 x 推到队列的末尾 +int pop() 从队列的开头移除并返回元素 +int peek() 返回队列开头的元素 +boolean empty() 如果队列为空,返回 true ;否则,返回 false + +说明: +你只能使用标准的栈操作,也就是只有push to top, peek/pop from top, size,和is empty作是合法的。 +你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 + +算法思路: +两个栈,左边栈用于最终存放数据,右边栈只是临时来交换数据用。每次push新数据的时候, +先将左边栈所有的数据一个个压到右边栈,然后将新数据压到左边栈底,再将右边栈一个个压到左边栈。 +""" + + +class ListNode: + def __init__(self, val_, next_=None): + self.val = val_ + self.next = next_ + + def __str__(self): + return str(self.val) + + +class MyStack: + def __init__(self): + self.first = None + + def push(self, val): + self.first = ListNode(val, self.first) + + def pop(self): + if not self.first: + return None + res = self.first.val + self.first = self.first.next + return res + + def empty(self): + return self.first is None + + +class MyQueue: + + def __init__(self): + self.left_stack = MyStack() + self.right_stack = MyStack() + + def push(self, x: int) -> None: + # 先将左栈全部压入右栈 + while not self.left_stack.empty(): + self.right_stack.push(self.left_stack.pop()) + # 然后将新数据压入左栈底 + self.left_stack.push(x) + # 最后将右栈全部压入左栈 + while not self.right_stack.empty(): + self.left_stack.push(self.right_stack.pop()) + + def pop(self) -> int: + if self.left_stack.empty(): + return None + return self.left_stack.pop() + + def peek(self) -> int: + if self.left_stack.empty(): + return None + return self.left_stack.first.val + + def empty(self) -> bool: + return self.left_stack.empty() + + +if __name__ == '__main__': + # Your MyQueue object will be instantiated and called as such: + obj = MyQueue() + obj.push(1) + obj.push(2) + obj.push(3) + param_2 = obj.pop() + param_3 = obj.peek() + param_4 = obj.empty() + print(param_2, param_3, param_4) diff --git a/algorithms/c12_leetcode/p200/a283_move_zeroes.py b/algorithms/c12_leetcode/p200/a283_move_zeroes.py new file mode 100644 index 0000000..cb2cda1 --- /dev/null +++ b/algorithms/c12_leetcode/p200/a283_move_zeroes.py @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- +""" +283. 移动零 +给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 +请注意 ,必须在不复制数组的情况下原地对数组进行操作。 +输入: nums = [0,1,0,3,12] +输出: [1,3,12,0,0] +输入: nums = [0] +输出: [0] + +解题思路: +快慢指针,慢指针保留所有非0值,快指针遍历完数组。最后将慢指针后面的值全部赋值为0即可 +""" +from typing import List, NoReturn + + +class Solution: + def moveZeroes(self, nums: List[int]) -> NoReturn: + """ + Do not return anything, modify nums in-place instead. + """ + slow, fast = 0, 0 + while fast < len(nums): + if nums[fast] != 0: + nums[slow] = nums[fast] + slow += 1 + fast += 1 + while slow < len(nums): + nums[slow] = 0 + slow += 1 diff --git a/algorithms/c12_leetcode/p2000/__init__.py b/algorithms/c12_leetcode/p2000/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p2000/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p2100/__init__.py b/algorithms/c12_leetcode/p2100/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p2100/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p2200/__init__.py b/algorithms/c12_leetcode/p2200/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p2200/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p300/__init__.py b/algorithms/c12_leetcode/p300/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p300/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p300/a392_is_subsequence.py b/algorithms/c12_leetcode/p300/a392_is_subsequence.py new file mode 100644 index 0000000..2f54213 --- /dev/null +++ b/algorithms/c12_leetcode/p300/a392_is_subsequence.py @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- +""" +392. 判断子序列 +给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 + +字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。 +(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 + +进阶: +如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。 +在这种情况下,你会怎样改变代码? + +算法思路: +通过双指针遍历两个序列,两个相等时,左指针向前一步,直到左指针跑完左边字符串s即可。 +python中可通过生成器方式简化算法的写法。 + +输入:s = "abc", t = "ahbgdc" +输出:true + +输入:s = "axc", t = "ahbgdc" +输出:false +""" + + +class Solution: + def isSubsequence(self, s: str, t: str) -> bool: + t_iter = iter(t) # 这里需要单独提取出来,否则for中每次都会生成新的迭代器。 + return all(i in t_iter for i in s) + + +if __name__ == '__main__': + print(Solution().isSubsequence('abc', 'ahbgdc')) + print(Solution().isSubsequence('axc', 'ahbgdc')) + print(Solution().isSubsequence('acb', 'ahbgdc')) diff --git a/algorithms/c12_leetcode/p400/__init__.py b/algorithms/c12_leetcode/p400/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p400/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p400/a496_next_greater_element.py b/algorithms/c12_leetcode/p400/a496_next_greater_element.py new file mode 100644 index 0000000..2b94d08 --- /dev/null +++ b/algorithms/c12_leetcode/p400/a496_next_greater_element.py @@ -0,0 +1,98 @@ +# -*- encoding: utf-8 -*- +""" +496. 下一个更大元素 I +nums1中数字x的下一个更大元素是指x在nums2中对应位置右侧的第一个比x大的元素。 + +给你两个没有重复元素的数组nums1和nums2,下标从0开始计数,其中nums1是nums2的子集。 + +对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j , +并且在nums2确定nums2[j]的下一个更大元素。如果不存在下一个更大元素,那么本次查询的答案是-1 。 + +返回一个长度为nums1.length的数组 ans 作为答案,满足ans[i]是如上所述的下一个更大元素。 + +输入:nums1 = [4,1,2], nums2 = [1,3,4,2]. +输出:[-1,3,-1] +解释:nums1 中每个值的下一个更大元素如下所述: +- 4 ,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 +- 1 ,nums2 = [1,3,4,2]。下一个更大元素是 3 。 +- 2 ,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 + +输入:nums1 = [2,4], nums2 = [1,2,3,4]. +输出:[3,-1] +解释:nums1 中每个值的下一个更大元素如下所述: +- 2 ,nums2 = [1,2,3,4]。下一个更大元素是 3 。 +- 4 ,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1。 + +进阶:你可以设计一个时间复杂度为 O(nums1.length + nums2.length) 的解决方案吗? + +算法思路: +这个问题可以这样抽象思考:把数组的元素想象成并列站立的人,元素大小想象成人的身高。这些人面对你站成一列, +如何求元素「2」的 Next Greater Number 呢?很简单,如果能够看到元素「2」, +那么他后面可见的第一个人就是「2」的 Next Greater Number,因为比「2」小的元素身高不够,都被「2」挡住了, +第一个露出来的就是答案。 + +单调栈解决问题的模板。for 循环要从后往前扫描元素,因为我们借助的是栈的结构,倒着入栈, +其实是正着出栈。while 循环是把两个“高个”元素之间的元素排除,因为他们的存在没有意义,前面挡着个“更高”的元素, +所以他们不可能被作为后续进来的元素的 Next Great Number 了。 + +这个算法的时间复杂度不是那么直观,如果你看到 for 循环嵌套 while 循环,可能认为这个算法的复杂度也是 O(n^2), +但是实际上这个算法的复杂度只有 O(n)。 + +分析它的时间复杂度,要从整体来看:总共有 n 个元素,每个元素都被 push 入栈了一次,而最多会被 pop 一次, +没有任何冗余操作。所以总的计算规模是和元素规模 n 成正比的,也就是 O(n) 的复杂度。 + +先用单调栈找到nums2每个元素对应的下个最大元素,然后装入Map中。后面对于num1的求值直接从Map中获取。 +""" +from typing import List + + +class LinkedNode: + def __init__(self, val_, next_=None): + self.val = val_ + self.next = next_ + + def __str__(self): + return str(self.val) + + +class LinkedStack: + def __init__(self): + self.first = None + + def push(self, val): + self.first = LinkedNode(val, self.first) + + def pop(self): + if not self.first: + return None + res = self.first.val + self.first = self.first.next + return res + + def peek(self): + if not self.first: + return None + return self.first.val + + def empty(self): + return self.first is None + + +class Solution: + def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: + nums2_size = len(nums2) + nums2_map = {} # 存放num2的单调栈 + count = 0 + stack = LinkedStack() # 存放高个元素的栈 + for item in nums2[::-1]: + while not stack.empty() and nums2[nums2_size - 1 - count] > stack.peek(): + stack.pop() # 把中间小的数剔除掉 + nums2_map[item] = -1 if stack.empty() else stack.peek() + stack.push(item) # 然后再把这个数入栈,接受后面的身高判定 + count += 1 + return [nums2_map[i] for i in nums1] + + +if __name__ == '__main__': + ans = Solution().nextGreaterElement([4, 1, 2], [1, 3, 4, 2]) + print(ans) diff --git a/algorithms/c12_leetcode/p500/__init__.py b/algorithms/c12_leetcode/p500/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p500/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p600/__init__.py b/algorithms/c12_leetcode/p600/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p600/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p600/a682_baseball_game.py b/algorithms/c12_leetcode/p600/a682_baseball_game.py new file mode 100644 index 0000000..a7a9b00 --- /dev/null +++ b/algorithms/c12_leetcode/p600/a682_baseball_game.py @@ -0,0 +1,109 @@ +# -*- encoding: utf-8 -*- +""" +682. 棒球比赛 +你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。 + +比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则: + +整数 x - 表示本回合新获得分数 x +"+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。 +"D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。 +"C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。 +请你返回记录中所有得分的总和。 + +输入:ops = ["5","2","C","D","+"] +输出:30 +解释: +"5" - 记录加 5 ,记录现在是 [5] +"2" - 记录加 2 ,记录现在是 [5, 2] +"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5]. +"D" - 记录加 2 * 5 = 10 ,记录现在是 [5, 10]. +"+" - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15]. +所有得分的总和 5 + 10 + 15 = 30 + +输入:ops = ["5","-2","4","C","D","9","+","+"] +输出:27 +解释: +"5" - 记录加 5 ,记录现在是 [5] +"-2" - 记录加 -2 ,记录现在是 [5, -2] +"4" - 记录加 4 ,记录现在是 [5, -2, 4] +"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5, -2] +"D" - 记录加 2 * -2 = -4 ,记录现在是 [5, -2, -4] +"9" - 记录加 9 ,记录现在是 [5, -2, -4, 9] +"+" - 记录加 -4 + 9 = 5 ,记录现在是 [5, -2, -4, 9, 5] +"+" - 记录加 9 + 5 = 14 ,记录现在是 [5, -2, -4, 9, 5, 14] +所有得分的总和 5 + -2 + -4 + 9 + 5 + 14 = 27 + +算法思路: +跟加减乘除思路类似,这里的C、D、+代表操作符,优先级一样。 +准备两个栈,左栈作为操作数,右栈作为操作符。遇到数字压入左栈,遇到操作符根据规则进行计算即可。 +""" +from typing import List + + +class LinkedNode: + def __init__(self, val_, next_=None): + self.val = val_ + self.next = next_ + + def __str__(self): + return str(self.val) + + +class LinkedStack: + def __init__(self): + self.first = None + + def push(self, val): + self.first = LinkedNode(val, self.first) + + def pop(self): + if not self.first: + return None + res = self.first.val + self.first = self.first.next + return res + + def peek(self): + if not self.first: + return None + return self.first.val + + def empty(self): + return self.first is None + + def __iter__(self): + while self.first: + yield self.first.val + self.first = self.first.next + + +class Solution: + + def __init__(self): + self.left_stack = LinkedStack() + self.right_stack = LinkedStack() + + def calPoints(self, ops: List[str]) -> int: + for op in ops: + if self.is_number(op): + self.left_stack.push(int(op)) + else: + if op == 'C': + self.left_stack.pop() + elif op == 'D': + self.left_stack.push(self.left_stack.peek() * 2) + elif op == '+': + self.left_stack.push(self.left_stack.peek() + self.left_stack.first.next.val) + return sum(self.left_stack) + + def is_number(self, str_): + try: + int(str_) + return True + except ValueError: + return False + + +if __name__ == '__main__': + print(Solution().calPoints(["5", "2", "C", "D", "+"])) diff --git a/algorithms/c12_leetcode/p700/__init__.py b/algorithms/c12_leetcode/p700/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p700/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p800/__init__.py b/algorithms/c12_leetcode/p800/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p800/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c12_leetcode/p800/a844_backspace_string_compare.py b/algorithms/c12_leetcode/p800/a844_backspace_string_compare.py new file mode 100644 index 0000000..acf8977 --- /dev/null +++ b/algorithms/c12_leetcode/p800/a844_backspace_string_compare.py @@ -0,0 +1,94 @@ +# -*- encoding: utf-8 -*- +""" +844. 比较含退格的字符串 +给定s和t两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。 + +注意:如果对空文本输入退格字符,文本继续为空。 + +输入:s = "ab#c", t = "ad#c" +输出:true +解释:s 和 t 都会变成 "ac"。 + +输入:s = "ab##", t = "c#d#" +输出:true +解释:s 和 t 都会变成 ""。 + +输入:s = "a#c", t = "b" +输出:false +解释:s 会变成 "c",但 t 仍然是 "b"。 + +算法思路: +用两个栈遍历两个字符串,遇到#号就从栈弹出一个元素。最后再依次对比两个栈的每个元素。 +""" + + +class LinkedNode: + def __init__(self, val_, next_=None): + self.val = val_ + self.next = next_ + + def __str__(self): + return str(self.val) + + +class LinkedStack: + def __init__(self): + self.first = None + + def push(self, val): + self.first = LinkedNode(val, self.first) + + def pop(self): + if not self.first: + return None + res = self.first.val + self.first = self.first.next + return res + + def peek(self): + if not self.first: + return None + return self.first.val + + def empty(self): + return self.first is None + + def __iter__(self): + while self.first: + yield self.first.val + self.first = self.first.next + + +class Solution: + def __init__(self): + self.left_stack = LinkedStack() + self.right_stack = LinkedStack() + + def backspaceCompare(self, s: str, t: str) -> bool: + for left_val in s: + if left_val == '#': + if not self.left_stack.empty(): + self.left_stack.pop() + else: + self.left_stack.push(left_val) + for right_val in t: + if right_val == '#': + if not self.right_stack.empty(): + self.right_stack.pop() + else: + self.right_stack.push(right_val) + while not self.left_stack.empty() and not self.right_stack.empty(): + if self.left_stack.pop() != self.right_stack.pop(): + return False + if not self.left_stack.empty() and self.right_stack.empty(): + return False + if self.left_stack.empty() and not self.right_stack.empty(): + return False + return True + + +if __name__ == '__main__': + print(Solution().backspaceCompare("ab##", "c#d#")) + print(Solution().backspaceCompare("ab#c", "ad#c")) + print(Solution().backspaceCompare("a#c", "b")) + print(Solution().backspaceCompare("y#fo##f", "y#f#o##f")) diff --git a/algorithms/c12_leetcode/p800/a876_middle_of_the_linked_list.py b/algorithms/c12_leetcode/p800/a876_middle_of_the_linked_list.py new file mode 100644 index 0000000..bff832a --- /dev/null +++ b/algorithms/c12_leetcode/p800/a876_middle_of_the_linked_list.py @@ -0,0 +1,67 @@ +# -*- encoding: utf-8 -*- +""" +876. 链表的中间结点 +给定一个头结点为 head 的非空单链表,返回链表的中间结点。 + +如果有两个中间结点,则返回第二个中间结点。 + +示例 1: +输入:[1,2,3,4,5] +输出:此列表中的结点 3 (序列化形式:[3,4,5]) +返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 +注意,我们返回了一个 ListNode 类型的对象 ans,这样: +ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. + +示例 2: +输入:[1,2,3,4,5,6] +输出:此列表中的结点 4 (序列化形式:[4,5,6]) +由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 + +快慢指针经典案例。 +""" + + +# 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) + + def __iter__(self): + node = self + while node: + yield node.val + node = node.next + + +class Solution: + def middleNode(self, head: ListNode) -> ListNode: + if not head: + return head + slow = fast = head + while fast.next and fast.next.next: + slow = slow.next + fast = fast.next.next + if fast.next: # 偶数情况 + return slow.next + else: + return slow + + +if __name__ == '__main__': + # input + # 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) + head.next.next.next.next.next = ListNode(6) + # solve + solution = Solution() + res = solution.middleNode(head) + # output + print(list(res)) diff --git a/algorithms/c12_leetcode/p900/__init__.py b/algorithms/c12_leetcode/p900/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c12_leetcode/p900/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c13_ai/__init__.py b/algorithms/c13_ai/__init__.py new file mode 100644 index 0000000..5d50274 --- /dev/null +++ b/algorithms/c13_ai/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +description +""" diff --git a/algorithms/c13_ai/basic/__init__.py b/algorithms/c13_ai/basic/__init__.py new file mode 100644 index 0000000..66995af --- /dev/null +++ b/algorithms/c13_ai/basic/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +""" +AI基础算法集合 +""" diff --git a/algorithms/c13_ai/basic/a01_maze_bfs.py b/algorithms/c13_ai/basic/a01_maze_bfs.py new file mode 100644 index 0000000..6f02bbb --- /dev/null +++ b/algorithms/c13_ai/basic/a01_maze_bfs.py @@ -0,0 +1,49 @@ +# -*- encoding: utf-8 -*- +""" +迷宫搜索:广度优先算法,通过队列来实现 +""" + +from algorithms.c01_data_structure.queue.linked_queue import LinkedQueue +from algorithms.c01_data_structure.stack.stack_linked_list import LinkedStack + + +class Point: + def __init__(self, row, column): + self.row = row + self.column = column + self.parent = None + + def __eq__(self, other): + return self.row == other.row and self.column == other.column + + def __str__(self): + return f'({self.row},{self.column})' + + +def print_path(point): + """打印路径""" + stack = LinkedStack() + while point is not None: + stack.push(stack) + point = point.parent + for point in stack: + print(point, sep=',') + + +def run_bfs(maze, current_point, visited_points): + q = LinkedQueue() + q.enqueue(current_point) + visited_points.add(current_point) + while not q.is_empty(): + current_point = q.dequeue() + # 当前节点的东南西北节点作为邻居节点 + neighbors = [] + for neighbor in neighbors: + if neighbor not in visited_points: + visited_points.add(neighbor) + neighbor.parent = current_point + q.enqueue(neighbor) + if neighbor == maze.goal: + print_path(neighbor) + return neighbor + print('find no path') diff --git a/algorithms/c13_ai/basic/a02_maze_dfs.py b/algorithms/c13_ai/basic/a02_maze_dfs.py new file mode 100644 index 0000000..3007604 --- /dev/null +++ b/algorithms/c13_ai/basic/a02_maze_dfs.py @@ -0,0 +1,47 @@ +# -*- encoding: utf-8 -*- +""" +迷宫搜索:深度优先算法,通过栈来实现 +""" + +from algorithms.c01_data_structure.stack.stack_linked_list import LinkedStack + + +class Point: + def __init__(self, row, column): + self.row = row + self.column = column + self.parent = None + + def __eq__(self, other): + return self.row == other.row and self.column == other.column + + def __str__(self): + return f'({self.row},{self.column})' + + +def print_path(point): + """打印路径""" + stack = LinkedStack() + while point is not None: + stack.push(stack) + point = point.parent + for point in stack: + print(point, sep=',') + + +def run_dfs(maze, root_point, visited_points): + s = LinkedStack() + s.push(root_point) + while not s.is_empty(): + current_point = s.pop() + if current_point not in visited_points: + visited_points.add(current_point) + if current_point == maze.goal: + print_path(current_point) + return current_point + # 构造当前节点的东南西北节点作为邻居节点 + neighbors = [] + for neighbor in neighbors: + neighbor.parent = current_point + s.push(neighbor) + print('find no path') diff --git a/algorithms/c14_sample/__init__.py b/algorithms/c14_sample/__init__.py new file mode 100644 index 0000000..1832bfc --- /dev/null +++ b/algorithms/c14_sample/__init__.py @@ -0,0 +1,3 @@ +# -*- encoding: utf-8 -*- +"""一些常见算法示例 +""" diff --git a/ch01_basic/at001_math.py b/algorithms/c14_sample/m01_math.py similarity index 98% rename from ch01_basic/at001_math.py rename to algorithms/c14_sample/m01_math.py index 70fe4b5..a0eda53 100644 --- a/ch01_basic/at001_math.py +++ b/algorithms/c14_sample/m01_math.py @@ -93,7 +93,7 @@ def main(): print(fibonacci(8)) mod(0.78) print(maxCommonDivisor(24, 36)) - print(maxCommonMultiple(24, 36)) + print(minCommonMultiple(24, 36)) if __name__ == '__main__': diff --git a/ch01_basic/at002_triangle_str.py b/algorithms/c14_sample/m02_triangle_str.py similarity index 99% rename from ch01_basic/at002_triangle_str.py rename to algorithms/c14_sample/m02_triangle_str.py index 4698996..ffbfd53 100644 --- a/ch01_basic/at002_triangle_str.py +++ b/algorithms/c14_sample/m02_triangle_str.py @@ -20,5 +20,6 @@ def triangleDisplay(mystr): result.append(mystr[i: -1]) return result + for each in triangleDisplay(u"我和我的小伙伴们都惊呆了"): print(each) diff --git a/ch01_basic/at003_duplicate_words.py b/algorithms/c14_sample/m03_duplicate_words.py similarity index 98% rename from ch01_basic/at003_duplicate_words.py rename to algorithms/c14_sample/m03_duplicate_words.py index 73686ae..cf4bced 100644 --- a/ch01_basic/at003_duplicate_words.py +++ b/algorithms/c14_sample/m03_duplicate_words.py @@ -5,8 +5,6 @@ Desc : """ -import sys - def find_count(filename): count_map = {} diff --git a/ch01_basic/at004_binary_add.py b/algorithms/c14_sample/m04_binary_add.py similarity index 93% rename from ch01_basic/at004_binary_add.py rename to algorithms/c14_sample/m04_binary_add.py index 77c7ad0..925f91f 100644 --- a/ch01_basic/at004_binary_add.py +++ b/algorithms/c14_sample/m04_binary_add.py @@ -11,7 +11,7 @@ def biAdd(a, b): res = [] m = 0 - r = range(0, len(a)) + r = list(range(0, len(a))) r.reverse() for i in r: m, n = divmod(a[i] + b[i] + m, 2) diff --git a/ch01_basic/at005_nine_number.py b/algorithms/c14_sample/m05_nine_number.py similarity index 99% rename from ch01_basic/at005_nine_number.py rename to algorithms/c14_sample/m05_nine_number.py index 5ecceed..81b3e0d 100644 --- a/ch01_basic/at005_nine_number.py +++ b/algorithms/c14_sample/m05_nine_number.py @@ -36,5 +36,6 @@ def nine_number(): result.append((each1, each2, each3)) return result + if __name__ == '__main__': print(nine_number()) diff --git a/ch01_basic/at008_hornerpoly.py b/algorithms/c14_sample/m06_hornerpoly.py similarity index 64% rename from ch01_basic/at008_hornerpoly.py rename to algorithms/c14_sample/m06_hornerpoly.py index 5c7f4d1..832cece 100644 --- a/ch01_basic/at008_hornerpoly.py +++ b/algorithms/c14_sample/m06_hornerpoly.py @@ -9,11 +9,12 @@ __author__ = 'Xiong Neng' -def hornerPoly(coefficientArr, x): +def horner_poly(coefficient_arr, x): res = 0 - for i in range(len(coefficientArr))[-1::-1]: - res = coefficientArr[i] + x * res + for i in range(len(coefficient_arr))[-1::-1]: + res = coefficient_arr[i] + x * res return res + if __name__ == '__main__': - print(hornerPoly((1, 2, 3), 2)) + print(horner_poly((1, 2, 3), 2)) diff --git a/ch01_basic/at010_max_subarr.py b/algorithms/c14_sample/m07_max_subarr.py similarity index 98% rename from ch01_basic/at010_max_subarr.py rename to algorithms/c14_sample/m07_max_subarr.py index 584b5bb..eb6843e 100644 --- a/ch01_basic/at010_max_subarr.py +++ b/algorithms/c14_sample/m07_max_subarr.py @@ -22,7 +22,7 @@ def __findMaxSubArr(seq, low, high): if low == high: return low, high, seq[low] else: - mid = (low + high) / 2 + mid = (low + high) // 2 l = lefLow, leftHigh, leftSum = __findMaxSubArr(seq, low, mid) r = rightLow, rightHigh, right_sum = __findMaxSubArr(seq, mid + 1, high) c = crossLow, crossHigh, crossSum = __maxCrossingSubArr(seq, low, mid, high) @@ -50,5 +50,6 @@ def __maxCrossingSubArr(seq, low, mid, high): maxRight = j return maxLeft, maxRight, leftSum + rightSum + if __name__ == '__main__': print(maxSubArr([13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7])) diff --git a/ch01_basic/at011_max_subarr2.py b/algorithms/c14_sample/m08_max_subarr2.py similarity index 98% rename from ch01_basic/at011_max_subarr2.py rename to algorithms/c14_sample/m08_max_subarr2.py index 68dbc14..d57f7fd 100644 --- a/ch01_basic/at011_max_subarr2.py +++ b/algorithms/c14_sample/m08_max_subarr2.py @@ -8,6 +8,7 @@ 最后结论: k < lg(n)的时候,使用暴力算法 """ from math import log + __author__ = 'Xiong Neng' @@ -21,7 +22,7 @@ def __findMaxSubArr(seq, low, high, threshold): elif low == high: return low, high, seq[low] else: - mid = (low + high) / 2 + mid = (low + high) // 2 l = lefLow, leftHigh, leftSum = __findMaxSubArr(seq, low, mid, threshold) r = rightLow, rightHigh, right_sum = __findMaxSubArr(seq, mid + 1, high, threshold) c = crossLow, crossHigh, crossSum = __maxCrossingSubArr(seq, low, mid, high) @@ -61,5 +62,6 @@ def __maxCrossingSubArr(seq, low, mid, high): maxRight = j return maxLeft, maxRight, leftSum + rightSum + if __name__ == '__main__': print(maxSubArr([13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7])) diff --git a/ch01_basic/at011_max_subarr3.py b/algorithms/c14_sample/m09_max_subarr3.py similarity index 100% rename from ch01_basic/at011_max_subarr3.py rename to algorithms/c14_sample/m09_max_subarr3.py diff --git a/ch01_basic/at012_code_funny.py b/algorithms/c14_sample/m10_code_funny.py similarity index 99% rename from ch01_basic/at012_code_funny.py rename to algorithms/c14_sample/m10_code_funny.py index 2c0d0d9..dd7d005 100644 --- a/ch01_basic/at012_code_funny.py +++ b/algorithms/c14_sample/m10_code_funny.py @@ -16,5 +16,6 @@ def list_chess(): for i in range(1, 10): print([(i, k) for k in range(1, 10) if abs(k - i) % 3 != 0]) + if __name__ == '__main__': list_chess() diff --git a/ch01_basic/at013_rand_permute.py b/algorithms/c14_sample/m11_rand_permute.py similarity index 99% rename from ch01_basic/at013_rand_permute.py rename to algorithms/c14_sample/m11_rand_permute.py index a46e515..cb8feca 100644 --- a/ch01_basic/at013_rand_permute.py +++ b/algorithms/c14_sample/m11_rand_permute.py @@ -49,4 +49,3 @@ def randPermuteBySwap(seq): se = [4, 5, 12, 44, 56, 6] shuffle(se) # python中的函数 print(se) - diff --git a/ch01_basic/at014_right_shift.py b/algorithms/c14_sample/m12_right_shift.py similarity index 100% rename from ch01_basic/at014_right_shift.py rename to algorithms/c14_sample/m12_right_shift.py diff --git a/ch01_basic/at015_sin_cpu.py b/algorithms/c14_sample/m13_sin_cpu.py similarity index 79% rename from ch01_basic/at015_sin_cpu.py rename to algorithms/c14_sample/m13_sin_cpu.py index 9ee7149..47eb9ff 100644 --- a/ch01_basic/at015_sin_cpu.py +++ b/algorithms/c14_sample/m13_sin_cpu.py @@ -5,11 +5,14 @@ Topic: sample Desc : CPU正弦曲线 """ -import itertools, math, time, sys +import itertools +import math +import sys +import time __author__ = 'Xiong Neng' -time_period = float(sys.argv[1]) if len(sys.argv) > 1 else 60 # seconds +time_period = float(sys.argv[1]) if len(sys.argv) > 1 else 60 # seconds time_slice = float(sys.argv[2]) if len(sys.argv) > 2 else 0.04 # seconds N = int(time_period / time_slice) diff --git a/ch01_basic/at016_bestsinger.py b/algorithms/c14_sample/m14_bestsinger.py similarity index 98% rename from ch01_basic/at016_bestsinger.py rename to algorithms/c14_sample/m14_bestsinger.py index 505c78c..f7a1f7d 100644 --- a/ch01_basic/at016_bestsinger.py +++ b/algorithms/c14_sample/m14_bestsinger.py @@ -37,7 +37,7 @@ def lucky_seven(rows=166, cols=5, choices=13, lucky=7, start=1): if nextnum > choices: nextnum -= choices if nextnum > choices: - nextnum -= choices + nextnum -= choices votes[row][col] = nextnum nextnum += 1 return votes diff --git a/ch01_basic/at017_bracket_match.py b/algorithms/c14_sample/m15_bracket_match.py similarity index 100% rename from ch01_basic/at017_bracket_match.py rename to algorithms/c14_sample/m15_bracket_match.py diff --git a/ch03_datastruct/at303_circle_queue.py b/algorithms/c14_sample/m16_circle_queue.py similarity index 95% rename from ch03_datastruct/at303_circle_queue.py rename to algorithms/c14_sample/m16_circle_queue.py index 811c515..55f2d96 100644 --- a/ch03_datastruct/at303_circle_queue.py +++ b/algorithms/c14_sample/m16_circle_queue.py @@ -1,13 +1,12 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -# Josephus排列 """ +Josephus排列 Topic: n个人围成一圈,从某个指定人开始,沿着环将遇到的每第m个人移出去。 每个人移出去后,继续沿着环将剩下的人按同样规则移出去 默认队列第一个编号为1,以此类推。。。 -Desc : """ -__author__ = 'Xiong Neng' + def circle_out(n, m): # 初始化数组,1表示在队列中,0表示已经出了队列 @@ -31,5 +30,6 @@ def circle_out(n, m): print(result) return result + if __name__ == '__main__': circle_out(7, 3) diff --git a/algorithms/models/__init__.py b/algorithms/models/__init__.py new file mode 100644 index 0000000..0ff746b --- /dev/null +++ b/algorithms/models/__init__.py @@ -0,0 +1,20 @@ +# Follow the PEP 585 – Type Hinting Generics In Standard Collections +# https://peps.python.org/pep-0585/ +from __future__ import annotations + +# Import common libs here to simplify the code by `from module import *` +from .list_node import ( + ListNode, + list_to_linked_list, + linked_list_to_list, + get_list_node, +) +from .tree_node import TreeNode, list_to_tree, tree_to_list +from .vertex import Vertex, vals_to_vets, vets_to_vals +from .print_util import ( + print_matrix, + print_linked_list, + print_tree, + print_dict, + print_heap, +) diff --git a/algorithms/models/list_node.py b/algorithms/models/list_node.py new file mode 100644 index 0000000..fe0fed0 --- /dev/null +++ b/algorithms/models/list_node.py @@ -0,0 +1,38 @@ +""" +File: list_node.py +Created Time: 2021-12-11 +""" + + +class ListNode: + """Definition for a singly-linked list node""" + + def __init__(self, val: int): + self.val: int = val # 节点值 + self.next: ListNode | None = None # 后继节点引用 + + +def list_to_linked_list(arr: list[int]) -> ListNode | None: + """Generate a linked list with a list""" + dum = head = ListNode(0) + for a in arr: + node = ListNode(a) + head.next = node + head = head.next + return dum.next + + +def linked_list_to_list(head: ListNode | None) -> list[int]: + """Serialize a linked list into an array""" + arr: list[int] = [] + while head: + arr.append(head.val) + head = head.next + return arr + + +def get_list_node(head: ListNode | None, val: int) -> ListNode | None: + """Get a list node with specific value from a linked list""" + while head and head.val != val: + head = head.next + return head diff --git a/algorithms/models/print_util.py b/algorithms/models/print_util.py new file mode 100644 index 0000000..793f2b0 --- /dev/null +++ b/algorithms/models/print_util.py @@ -0,0 +1,78 @@ +""" +File: print_util.py +Created Time: 2021-12-11 +""" + +from .tree_node import TreeNode, list_to_tree +from .list_node import ListNode, linked_list_to_list + + +def print_matrix(mat: list[list[int]]): + """Print a matrix""" + s = [] + for arr in mat: + s.append(" " + str(arr)) + print("[\n" + ",\n".join(s) + "\n]") + + +def print_linked_list(head: ListNode | None): + """Print a linked list""" + arr: list[int] = linked_list_to_list(head) + print(" -> ".join([str(a) for a in arr])) + + +class Trunk: + def __init__(self, prev, string: str | None = None): + self.prev = prev + self.str = string + + +def show_trunks(p: Trunk | None): + if p is None: + return + show_trunks(p.prev) + print(p.str, end="") + + +def print_tree(root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False): + """ + Print a binary tree + This tree printer is borrowed from TECHIE DELIGHT + https://www.techiedelight.com/c-program-print-binary-tree/ + """ + if root is None: + return + + prev_str = " " + trunk = Trunk(prev, prev_str) + print_tree(root.right, trunk, True) + + if prev is None: + trunk.str = "———" + elif is_right: + trunk.str = "/———" + prev_str = " |" + else: + trunk.str = "\———" + prev.str = prev_str + + show_trunks(trunk) + print(" " + str(root.val)) + if prev: + prev.str = prev_str + trunk.str = " |" + print_tree(root.left, trunk, False) + + +def print_dict(hmap: dict): + """Print a dict""" + for key, value in hmap.items(): + print(key, "->", value) + + +def print_heap(heap: list[int]): + """Print a heap both in array and tree representations""" + print("堆的数组表示:", heap) + print("堆的树状表示:") + root: TreeNode | None = list_to_tree(heap) + print_tree(root) diff --git a/algorithms/models/tree_node.py b/algorithms/models/tree_node.py new file mode 100644 index 0000000..4f0fe0e --- /dev/null +++ b/algorithms/models/tree_node.py @@ -0,0 +1,68 @@ +""" +File: tree_node.py +Created Time: 2021-12-11 +""" + +from collections import deque + + +class TreeNode: + """二叉树节点类""" + + def __init__(self, val: int = 0): + self.val: int = val # 节点值 + self.height: int = 0 # 节点高度 + self.left: TreeNode | None = None # 左子节点引用 + self.right: TreeNode | None = None # 右子节点引用 + + # 序列化编码规则请参考: + # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + # 二叉树的数组表示: + # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + # 二叉树的链表表示: + # /——— 15 + # /——— 7 + # /——— 3 + # | \——— 6 + # | \——— 12 + # ——— 1 + # \——— 2 + # | /——— 9 + # \——— 4 + # \——— 8 + + +def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: + """将列表反序列化为二叉树:递归""" + # 如果索引超出数组长度,或者对应的元素为 None ,则返回 None + if i < 0 or i >= len(arr) or arr[i] is None: + return None + # 构建当前节点 + root = TreeNode(arr[i]) + # 递归构建左右子树 + root.left = list_to_tree_dfs(arr, 2 * i + 1) + root.right = list_to_tree_dfs(arr, 2 * i + 2) + return root + + +def list_to_tree(arr: list[int]) -> TreeNode | None: + """将列表反序列化为二叉树""" + return list_to_tree_dfs(arr, 0) + + +def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: + """将二叉树序列化为列表:递归""" + if root is None: + return + if i >= len(res): + res += [None] * (i - len(res) + 1) + res[i] = root.val + tree_to_list_dfs(root.left, 2 * i + 1, res) + tree_to_list_dfs(root.right, 2 * i + 2, res) + + +def tree_to_list(root: TreeNode | None) -> list[int]: + """将二叉树序列化为列表""" + res = [] + tree_to_list_dfs(root, 0, res) + return res diff --git a/algorithms/models/vertex.py b/algorithms/models/vertex.py new file mode 100644 index 0000000..b34168c --- /dev/null +++ b/algorithms/models/vertex.py @@ -0,0 +1,19 @@ +# File: vertex.py +# Created Time: 2023-02-23 +# + +class Vertex: + """顶点类""" + + def __init__(self, val: int): + self.val = val + + +def vals_to_vets(vals: list[int]) -> list["Vertex"]: + """输入值列表 vals ,返回顶点列表 vets""" + return [Vertex(val) for val in vals] + + +def vets_to_vals(vets: list["Vertex"]) -> list[int]: + """输入顶点列表 vets ,返回值列表 vals""" + return [vet.val for vet in vets] diff --git a/algorithms/test_all.py b/algorithms/test_all.py new file mode 100644 index 0000000..267afd1 --- /dev/null +++ b/algorithms/test_all.py @@ -0,0 +1,33 @@ +import os +import glob +import subprocess + +env = os.environ.copy() +env["PYTHONIOENCODING"] = "utf-8" + +if __name__ == "__main__": + # find source code files + src_paths = sorted(glob.glob("chapter*/*.py")) + errors = [] + + # run python code + for src_path in src_paths: + process = subprocess.Popen( + ["python", src_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + encoding='utf-8' + ) + # Wait for the process to complete, and get the output and error messages + stdout, stderr = process.communicate() + # Check the exit status + exit_status = process.returncode + if exit_status != 0: + errors.append(stderr) + + print(f"Tested {len(src_paths)} files") + print(f"Found exception in {len(errors)} files") + if len(errors) > 0: + raise RuntimeError("\n\n".join(errors)) diff --git a/ch01_basic/__init__.py b/ch01_basic/__init__.py deleted file mode 100644 index 0593072..0000000 --- a/ch01_basic/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -""" - Topic: sample - Desc : 算法基础 -""" -__author__ = 'Xiong Neng' diff --git a/ch02_sort/__init__.py b/ch02_sort/__init__.py deleted file mode 100644 index d5e3647..0000000 --- a/ch02_sort/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -""" - Topic: sample - Desc : 排序和顺序统计量 -""" -__author__ = 'Xiong Neng' diff --git a/ch02_sort/at103_insert_sort.py b/ch02_sort/at103_insert_sort.py deleted file mode 100644 index 4f1c1e7..0000000 --- a/ch02_sort/at103_insert_sort.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 插入排序 -""" - Topic: sample - Desc : 插入排序 - 由于其内层循环非常紧凑,对于小规模的输入, - 插入排序是一种非常快的原址排序算法 - 注: 如果输入数组中仅有常数个元素需要在排序过程中存储在数组外, - 则称这种排序算法是原址的。 -""" -__author__ = 'Xiong Neng' - - -def insertSort(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 seq[i] > key: - seq[i + 1] = seq[i] - i -= 1 - seq[i + 1] = key - -if __name__ == '__main__': - seq = [5, 2, 4, 6, 1, 3] - insertSort(seq) - print(seq) \ No newline at end of file diff --git a/ch02_sort/at105_select_sort.py b/ch02_sort/at105_select_sort.py deleted file mode 100644 index 188b4c5..0000000 --- a/ch02_sort/at105_select_sort.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 选择排序 -""" - Topic: sample - Desc : 选择排序 -""" -__author__ = 'Xiong Neng' - - -def selectSort(seq): - le = len(seq) - for i in range(le - 1): - minIndx = i - for j in range(i, le): - if seq[minIndx] > seq[j]: - minIndx = j - if i != minIndx: - seq[i], seq[minIndx] = seq[minIndx], seq[i] - -if __name__ == '__main__': - se = [4, 2, 5, 1, 6, 3] - selectSort(se) - print(se) \ No newline at end of file diff --git a/ch02_sort/at106_merge_sort.py b/ch02_sort/at106_merge_sort.py deleted file mode 100644 index 3e458b3..0000000 --- a/ch02_sort/at106_merge_sort.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 归并排序(分治法) -""" - Topic: sample - Desc : 归并排序 - 归并排序算法完全遵循分治模式,操作如下: - 分解: 分解待排序的n个元素序列成各具n/2个元素的两个子序列 - 解决: 使用归并排序递归的排序两个子序列 - 合并: 合并两个已排序的子序列以产生已排序的答案 -""" -__author__ = 'Xiong Neng' - - -def mergeSort(seq): - mergeSortRange(seq, 0, len(seq) - 1) - - -def mergeOrderedSeq(seq, left, middle, right): - """ - seq: 待排序序列 - left <= middle <= right - 子数组seq[left..middle]和seq[middle+1..right]都是排好序的 - 该排序的时间复杂度为O(n) - """ - tempSeq = [] - i = left - j = middle + 1 - while i <= middle and j <= right: - if seq[i] <= seq[j]: - tempSeq.append(seq[i]) - i += 1 - else: - tempSeq.append(seq[j]) - j += 1 - if i <= middle: - tempSeq.extend(seq[i:middle + 1]) - else: - tempSeq.extend(seq[j:right + 1]) - seq[left:right + 1] = tempSeq[:] - - -def mergeSortRange(seq, start, end): - """ - 归并排序一个序列的子序列 - start: 子序列的start下标 - end: 子序列的end下标 - """ - if start < end: # 如果start >= end就终止递归调用 - middle = (start + end) / 2 - mergeSortRange(seq, start, middle) # 排好左边的一半 - mergeSortRange(seq, middle + 1, end) # 再排好右边的一半 - mergeOrderedSeq(seq, start, middle, end) # 最后合并排序结果 - - -if __name__ == '__main__': - aa = [4, 2, 5, 1, 6, 3, 7, 9, 8] - mergeSort(aa) - print(aa) \ No newline at end of file diff --git a/ch02_sort/at107_merge_sort2.py b/ch02_sort/at107_merge_sort2.py deleted file mode 100644 index b229258..0000000 --- a/ch02_sort/at107_merge_sort2.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 归并排序中对小数组采用插入排序 -""" - Topic: sample - Desc : 归并排序中对小数组采用插入排序 - 纯归并排序的复杂度为: 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)的时候,使用插入排序 -""" -from at003_insertsort import insertSort -from math import log - -__author__ = 'Xiong Neng' - - -def mergeSort(seq): - mergeSortRange(seq, 0, len(seq) - 1, log(len(seq), 2)) - - -def mergeOrderedSeq(seq, left, middle, right): - """ - seq: 待排序序列 - left <= middle <= right - 子数组seq[left..middle]和seq[middle+1..right]都是排好序的 - 该排序的时间复杂度为O(n) - """ - tempSeq = [] - i = left - j = middle + 1 - while i <= middle and j <= right: - if seq[i] <= seq[j]: - tempSeq.append(seq[i]) - i += 1 - else: - tempSeq.append(seq[j]) - j += 1 - if i <= middle: - tempSeq.extend(seq[i:middle + 1]) - else: - tempSeq.extend(seq[j:right + 1]) - seq[left:right + 1] = tempSeq[:] - - -def mergeSortRange(seq, start, end, threshold): - """ - 归并排序一个序列的子序列 - start: 子序列的start下标 - end: 子序列的end下标 - threshold: 待排序长度低于这个值,就采用插入排序 - """ - if end - start + 1 < threshold: - tempSeq = seq[start: end + 1] - insertSort(tempSeq) - seq[start: end + 1] = tempSeq[:] - elif start < end: # 如果start >= end就终止递归调用 - middle = (start + end) / 2 - mergeSortRange(seq, start, middle, threshold) # 排好左边的一半 - mergeSortRange(seq, middle + 1, end, threshold) # 再排好右边的一半 - mergeOrderedSeq(seq, start, middle, end) # 最后合并排序结果 - - -if __name__ == '__main__': - aa = [4, 2, 5, 1, 6, 3, 7, 9, 8] - mergeSort(aa) - print(aa) \ No newline at end of file diff --git a/ch02_sort/at109_bubble_sort.py b/ch02_sort/at109_bubble_sort.py deleted file mode 100644 index 1b113bc..0000000 --- a/ch02_sort/at109_bubble_sort.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 冒泡排序 -""" - Topic: sample - Desc : 冒泡排序 -""" -__author__ = 'Xiong Neng' - - -def bubbleSort(seq): - for i in range(len(seq)): - for j in range(len(seq) - 1, i, -1): - if seq[j] < seq[j - 1]: - seq[j - 1], seq[j] = seq[j], seq[j - 1] - - -if __name__ == '__main__': - s = [4, 6, 2, 5, 7, 9, 8, 1] - bubbleSort(s) - print(s) diff --git a/ch02_sort/at200_heap_sort.py b/ch02_sort/at200_heap_sort.py deleted file mode 100644 index 572c237..0000000 --- a/ch02_sort/at200_heap_sort.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 堆排序 -""" - Topic: sample - Desc : 堆排序 - 堆排序的时间复杂度是O(nlg(n)),并且具有空间原址性 - 二叉堆heap是一种数据结构,可用来实现优先队列 - 给定一个节点的下标i(下标从0开始),则其父节点、坐孩子、右孩子坐标: - parent(i) = floor((i+1)/2 - 1) = ((i + 1) >> 1) - 1 - left(i) = 2*i + 1 = (i << 1) + 1 - right(i) = 2*i + 2 = (i + 1) << 1 - - 最小堆定义: 所有i必须满足A[parent(i)] <= A[i] - 最大堆定义: 所有i必须满足A[parent(i)] >= A[i] - - 在堆排序中,我们使用最大堆 - 在优先队列算法中,使用最小堆 -""" -__author__ = 'Xiong Neng' - - -class Heap(): - def __init__(self, seq, heapSize, length): - """ - seq: 存放待排序的序列 - heapSize: 堆的大小 - lenght: 整个序列大小 - """ - self.seq = seq - self.heapSize = heapSize - self.length = length - - -def heapSort(seq): - """ - 堆排序算法 - """ - heap = Heap(seq, len(seq), len(seq)) - __buildMaxHeap(heap) - s = heap.seq - for i in range(heap.length - 1, 0, -1): - s[0], s[i] = s[i], s[0] - heap.heapSize -= 1 - __maxHeapify(heap, 0) - - -def __maxHeapify(heap, i): - """ - 前提是i的两棵子树left(i)和right(i)的二叉树都是最大堆了 - 现在加入i节点后,要保持这个二叉树为最大堆的性质 - heap: Heap数据结构 - """ - seq = heap.seq - slen = heap.heapSize - while True: - left = (i << 1) + 1 - right = (i + 1) << 1 - if left < slen and seq[left] > seq[i]: - largest = left - else: - largest = i - if right < slen and seq[right] > seq[largest]: - largest = right - if largest != i: - seq[largest], seq[i] = seq[i], seq[largest] - i = largest - else: - break - - -def __buildMaxHeap(heap): - """ - 由完全二叉树的性质可知:对于 n/2..n-1为下标的元素,都是叶子节点, - 那么可从下标floor((i+1)/2 - 1)开始往前到0的元素调用maxHeapify - heap: Heap数据结构 - """ - slen = heap.heapSize - for i in range(((slen + 1) >> 1) - 1, -1, -1): - __maxHeapify(heap, i) - - -if __name__ == '__main__': - iSeq = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] - heapSort(iSeq) - print(iSeq) - diff --git a/ch02_sort/at203_quick_sort.py b/ch02_sort/at203_quick_sort.py deleted file mode 100644 index 44e8316..0000000 --- a/ch02_sort/at203_quick_sort.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 快速排序 -""" - Topic: sample - Desc : 快速排序 - 采用分治法思想: - 分解: 将数组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(nlg(n))隐含常数因子非常的小,而且还是原址排序, - 所以实际中使用最多的排序算法就是快速排序 -""" -from random import randint -__author__ = 'Xiong Neng' - - -def quickSort(seq): - # __quickSubSort(seq, 0, len(seq) - 1) - __quickSubSortTail(seq, 0, len(seq) - 1) - - -def __partition(A, p, r): - """分解子数组""" - x = A[r] - i = p - 1 - for j in range(p, r): - if A[j] <= x: - i += 1 - A[i], A[j] = A[j], A[i] - A[i + 1], A[r] = A[r], A[i + 1] - return i + 1 - - -def __randPartition(A, p, r): - """分解子数组: 随机化版本""" - rinx = randint(p, r) # 随机的pivot - A[rinx], A[r] = A[r], A[rinx] # 还是将这个pivot放到最后 - x = A[r] - i = p - 1 - for j in range(p, r): - if A[j] <= x: - i += 1 - A[i], A[j] = A[j], A[i] - A[i + 1], A[r] = A[r], A[i + 1] - return i + 1 - - -def __quickSubSort(seq, p, r): - """递归版本的""" - if p < r: - q = __randPartition(seq, p, r) - __quickSubSort(seq, p, q - 1) - __quickSubSort(seq, q + 1, r) - - -def __quickSubSortTail(seq, p, r): - """循环版本,模拟尾递归,可以大大减少递归栈深度,而且时间复杂度不变""" - while p < r: - q = __randPartition(seq, p, r) - if q - p < r - q: - __quickSubSortTail(seq, p, q - 1) - p = q + 1 - else: - __quickSubSortTail(seq, q + 1, r) - r = q - 1 - -if __name__ == '__main__': - s = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] - quickSort(s) - print(s) diff --git a/ch02_sort/at211_prior_queue.py b/ch02_sort/at211_prior_queue.py deleted file mode 100644 index 5dad60f..0000000 --- a/ch02_sort/at211_prior_queue.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 最大堆实现最大优先级队列 -""" - Topic: sample - Desc : 最大堆实现最大优先级队列 -""" -import ch02_sort.at200_heap_sort as hsort - -__author__ = 'Xiong Neng' - - -class Item(): - def __init__(self, val, key, index=-1): - self.val = val - self.key = key - self.index = index - - def __cmp__(self, other): - if self.key > other.key: - return 1 - else: - return -1 if self.key < other.key else 0 - - def __str__(self): - return str((self.val, self.key, self.index)) - - -def heapGetMax(heap): - """返回最大优先队列中取最大值""" - return heap.seq[0] - - -def heapPopMax(heap): - """弹出最大优先队列中取最大值并返回这个值""" - if heap.heapSize < 1: - return None - re = heap.seq[0] - heap.seq[0] = heap.seq[heap.heapSize - 1] # 尾上的弄到头上去 - heap.heapSize -= 1 # heap的大小-1 - __maxHeapify(heap, 0) # 然后再次将其构建为最大堆 - return re - - -def heapIncreaseKey(heap, item, newKey): - """增加队列中元素item的key为新的newKey, key <= newKey""" - if newKey < item.key: - return - item.key = newKey - while True: - tmpItem = item - iindex = tmpItem.index - pindex = ((tmpItem.index + 1) >> 1) - 1 - if iindex > 0 and heap.seq[pindex] < tmpItem: - heap.seq[iindex], heap.seq[pindex] = heap.seq[pindex], heap.seq[iindex] - heap.seq[iindex].index, heap.seq[pindex].index = iindex, pindex - else: - break - - -def heapInsert(heap, item): - """最大优先队列中插入一条元素item,将其放入队列到最后一个位置, - 然后调用heapIncreaseKey""" - heap.heapSize += 1 - item.index = heap.heapSize - 1 - heap.seq[heap.heapSize - 1] = item - heapIncreaseKey(heap, item, item.key) - - -def __maxHeapify(heap, i): - """ - 前提是i的两棵子树left(i)和right(i)的二叉树都是最大堆了 - 现在加入i节点后,要保持这个二叉树为最大堆的性质 - heap: Heap数据结构 - """ - seq = heap.seq - slen = heap.heapSize - while True: - left = (i << 1) + 1 - right = (i + 1) << 1 - if left < slen and seq[left] > seq[i]: - largest = left - else: - largest = i - if right < slen and seq[right] > seq[largest]: - largest = right - if largest != i: - seq[largest], seq[i] = seq[i], seq[largest] - seq[largest].index, seq[i].index = seq[i].index, seq[largest].index - i = largest - else: - break - - -def __buildMaxHeap(heap): - """ - 由完全二叉树的性质可知:对于 n/2..n-1为下标的元素,都是叶子节点, - 那么可从下标floor((i+1)/2 - 1)开始往前到0的元素调用maxHeapify - heap: Heap数据结构 - """ - slen = heap.heapSize - for i in range(((slen + 1) >> 1) - 1, -1, -1): - __maxHeapify(heap, i) - for i in range(heap.heapSize): - heap.seq[i].index = i - - -if __name__ == '__main__': - iSeq = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] - iVal = ['9', '7', '8', '10', '16', '3', '14', '2', '1', '4'] - iVal = [2 * k for k in iVal] - pa = zip(iVal, iSeq) - lastParm = [Item(v, k) for (v, k) in pa] - lastParm.extend([None] * 100) - heap = hsort.Heap(lastParm, len(iSeq), len(lastParm)) - __buildMaxHeap(heap) - print([str(k) for k in heap.seq if k]) - - heapInsert(heap, Item('Love', 13)) - print([str(k) for k in heap.seq if k]) diff --git a/ch03_datastruct/__init__.py b/ch03_datastruct/__init__.py deleted file mode 100644 index 5de5a86..0000000 --- a/ch03_datastruct/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -""" - Topic: sample - Desc : 数据结构 -""" -__author__ = 'Xiong Neng' diff --git a/ch03_datastruct/at300_basic.py b/ch03_datastruct/at300_basic.py deleted file mode 100644 index b6221b1..0000000 --- a/ch03_datastruct/at300_basic.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# 基本数据结构 -""" - Topic: sample - Desc : 基本数据结构 -""" -__author__ = 'Xiong Neng' - - diff --git a/ch03_datastruct/redblack.cpp b/ch03_datastruct/redblack.cpp deleted file mode 100644 index 2e568ab..0000000 --- a/ch03_datastruct/redblack.cpp +++ /dev/null @@ -1,366 +0,0 @@ -#include -using namespace std; - -#define BLACK 0 -#define RED 1 - -//红黑树结点结构 -struct node -{ - node *left; - node *right; - node *p; - int key; - bool color; - node(node *init, int k):left(init),right(init),p(init),key(k),color(BLACK){} -}; -//红黑树结构 -class Red_Black_Tree -{ -public: - node *root;//根结点 - node *nil;//哨兵 - Red_Black_Tree(){nil = new node(NULL, -1);root = nil;}; - //13.2旋转 - void Left_Rotate(node *x); - void Right_Rotate(node *x); - //13.3插入 - void RB_Insert_Fixup(node *z); - void RB_Insert(node *z); - //13.4删除 - void RB_Delete_Fixup(node *x); - node *RB_Delete(node *z); - //else - void Print(); - void Print(node *x); - node *RB_Search(node *x, int k); - node *Tree_Successor(node *x); - node *Tree_Minimum(node *x); -}; -//左旋,令y = x->right, 左旋是以x和y之间的链为支轴进行旋转 -//涉及到的结点包括:x,y,y->left,令node={p,l,r},具体变化如下: -//x={x->p,x->left,y}变为{y,x->left,y->left} -//y={x,y->left,y->right}变为{x->p,x,y->right} -//y->left={y,y->left->left,y->left->right}变为{x,y->left->left,y->left->right} -void Red_Black_Tree::Left_Rotate(node *x) -{ - //令y = x->right - node *y = x->right; - //按照上面的方式修改三个结点的指针,注意修改指针的顺序 - x->right = y->left; - if(y->left != nil) - y->left->p = x; - y->p = x->p; - if(x->p == nil)//特殊情况:x是根结点 - root = y; - else if(x == x->p->left) - x->p->left = y; - else - x->p->right = y; - y->left = x; - x->p = y; -} -//右旋,令y = x->left, 左旋是以x和y之间的链为支轴进行旋转 -//旋转过程与上文类似 -void Red_Black_Tree::Right_Rotate(node *x) -{ - node *y = x->left; - x->left = y->right; - if(y->right != nil) - y->right->p = x; - y->p = x->p; - if(x->p == nil) - root = y; - else if(x == x->p->right) - x->p->right = y; - else - x->p->left = y; - y->right = x; - x->p = y; -} -//红黑树调整 -void Red_Black_Tree::RB_Insert_Fixup(node *z) -{ - node *y; - //唯一需要调整的情况,就是违反性质2的时候,如果不违反性质2,调整结束 - while(z->p->color == RED) - { - //p[z]是左孩子时,有三种情况 - if(z->p == z->p->p->left) - { - //令y是z的叔结点 - y = z->p->p->right; - //第一种情况,z的叔叔y是红色的 - if(y->color == RED) - { - //将p[z]和y都着为黑色以解决z和p[z]都是红色的问题 - z->p->color = BLACK; - y->color = BLACK; - //将p[p[z]]着为红色以保持性质5 - z->p->p->color = RED; - //把p[p[z]]当作新增的结点z来重复while循环 - z = z->p->p; - } - else - { - //第二种情况:z的叔叔是黑色的,且z是右孩子 - if(z == z->p->right) - { - //对p[z]左旋,转为第三种情况 - z = z->p; - Left_Rotate(z); - } - //第三种情况:z的叔叔是黑色的,且z是左孩子 - //交换p[z]和p[p[z]]的颜色,并右旋 - z->p->color = BLACK; - z->p->p->color = RED; - Right_Rotate(z->p->p); - } - } - //p[z]是右孩子时,有三种情况,与上面类似 - else if(z->p == z->p->p->right) - { - y = z->p->p->left; - if(y->color == RED) - { - z->p->color = BLACK; - y->color = BLACK; - z->p->p->color = RED; - z = z->p->p; - } - else - { - if(z == z->p->left) - { - z = z->p; - Right_Rotate(z); - } - z->p->color = BLACK; - z->p->p->color = RED; - Left_Rotate(z->p->p); - } - } - } - //根结点置为黑色 - root->color = BLACK; -} -//插入一个结点 -void Red_Black_Tree::RB_Insert(node *z) -{ - node *y = nil, *x = root; - //找到应该插入的位置,与二叉查找树的插入相同 - while(x != nil) - { - y = x; - if(z->key < x->key) - x = x->left; - else - x = x->right; - } - z->p = y; - if(y == nil) - root = z; - else if(z->key < y->key) - y->left = z; - else - y->right = z; - z->left = nil; - z->right = nil; - //将新插入的结点转为红色 - z->color = RED; - //从新插入的结点开始,向上调整 - RB_Insert_Fixup(z); -} -//对树进行调整,x指向一个红黑结点,调整的过程是将额外的黑色沿树上移 -void Red_Black_Tree::RB_Delete_Fixup(node *x) -{ - node *w; - //如果这个额外的黑色在一个根结点或一个红结点上,结点会吸收额外的黑色,成为一个黑色的结点 - while(x != root && x->color == BLACK) - { - //若x是其父的左结点(右结点的情况相对应) - if(x == x->p->left) - { - //令w为x的兄弟,根据w的不同,分为三种情况来处理 - //执行删除操作前x肯定是没有兄弟的,执行删除操作后x肯定是有兄弟的 - w = x->p->right; - //第一种情况:w是红色的 - if(w->color == RED) - { - //改变w和p[x]的颜色 - w->color = BLACK; - x->p->color = RED; - //对p[x]进行一次左旋 - Left_Rotate(x->p); - //令w为x的新兄弟 - w = x->p->right; - //转为2.3.4三种情况之一 - } - //第二情况:w为黑色,w的两个孩子也都是黑色 - if(w->left->color == BLACK && w->right->color == BLACK) - { - //去掉w和x的黑色 - //w只有一层黑色,去掉变为红色,x有多余的一层黑色,去掉后恢复原来颜色 - w->color = RED; - //在p[x]上补一层黑色 - x = x->p; - //现在新x上有个额外的黑色,转入for循环继续处理 - } - //第三种情况,w是黑色的,w->left是红色的,w->right是黑色的 - else - { - if(w->right->color == BLACK) - { - //改变w和left[x]的颜色 - w->left->color = BLACK; - w->color = RED; - //对w进行一次右旋 - Right_Rotate(w); - //令w为x的新兄弟 - w = x->p->right; - //此时转变为第四种情况 - } - //第四种情况:w是黑色的,w->left是黑色的,w->right是红色的 - //修改w和p[x]的颜色 - w->color =x->p->color; - x->p->color = BLACK; - w->right->color = BLACK; - //对p[x]进行一次左旋 - Left_Rotate(x->p); - //此时调整已经结束,将x置为根结点是为了结束循环 - x = root; - } - } - //若x是其父的左结点(右结点的情况相对应) - else if(x == x->p->right) - { - //令w为x的兄弟,根据w的不同,分为三种情况来处理 - //执行删除操作前x肯定是没有兄弟的,执行删除操作后x肯定是有兄弟的 - w = x->p->left; - //第一种情况:w是红色的 - if(w->color == RED) - { - //改变w和p[x]的颜色 - w->color = BLACK; - x->p->color = RED; - //对p[x]进行一次左旋 - Right_Rotate(x->p); - //令w为x的新兄弟 - w = x->p->left; - //转为2.3.4三种情况之一 - } - //第二情况:w为黑色,w的两个孩子也都是黑色 - if(w->right->color == BLACK && w->left->color == BLACK) - { - //去掉w和x的黑色 - //w只有一层黑色,去掉变为红色,x有多余的一层黑色,去掉后恢复原来颜色 - w->color = RED; - //在p[x]上补一层黑色 - x = x->p; - //现在新x上有个额外的黑色,转入for循环继续处理 - } - //第三种情况,w是黑色的,w->right是红色的,w->left是黑色的 - else - { - if(w->left->color == BLACK) - { - //改变w和right[x]的颜色 - w->right->color = BLACK; - w->color = RED; - //对w进行一次右旋 - Left_Rotate(w); - //令w为x的新兄弟 - w = x->p->left; - //此时转变为第四种情况 - } - //第四种情况:w是黑色的,w->right是黑色的,w->left是红色的 - //修改w和p[x]的颜色 - w->color =x->p->color; - x->p->color = BLACK; - w->left->color = BLACK; - //对p[x]进行一次左旋 - Right_Rotate(x->p); - //此时调整已经结束,将x置为根结点是为了结束循环 - x = root; - } - } - } - //吸收了额外的黑色 - x->color = BLACK; -} -//找最小值 -node *Red_Black_Tree::Tree_Minimum(node *x) -{ - //只要有比当前结点小的结点 - while(x->left != nil) - x = x->left; - return x; -} -//查找中序遍历下x结点的后继,后继是大于key[x]的最小的结点 -node *Red_Black_Tree::Tree_Successor(node *x) -{ - //如果有右孩子 - if(x->right != nil) - //右子树中的最小值 - return Tree_Minimum(x->right); - //如果x的右子树为空且x有后继y,那么y是x的最低祖先结点,且y的左儿子也是 - node *y = x->p; - while(y != NULL && x == y->right) - { - x = y; - y = y->p; - } - return y; -} -//递归地查询二叉查找树 -node *Red_Black_Tree::RB_Search(node *x, int k) -{ - //找到叶子结点了还没找到,或当前结点是所查找的结点 - if(x->key == -1 || k == x->key) - return x; - //所查找的结点位于当前结点的左子树 - if(k < x->key) - return RB_Search(x->left, k); - //所查找的结点位于当前结点的左子树 - else - return RB_Search(x->right, k); -} -//红黑树的删除 -node *Red_Black_Tree::RB_Delete(node *z) -{ - //找到结点的位置并删除,这一部分与二叉查找树的删除相同 - node *x, *y; - if(z->left == nil || z->right == nil) - y = z; - else y = Tree_Successor(z); - if(y->left != nil) - x = y->left; - else x = y->right; - x->p = y->p; - if(y->p == nil) - root = x; - else if(y == y->p->left) - y->p->left = x; - else - y->p->right = x; - if(y != z) - z->key = y->key; - //如果被删除的结点是黑色的,则需要调整 - if(y->color == BLACK) - RB_Delete_Fixup(x); - return y; -} -void Red_Black_Tree::Print(node *x) -{ - if(x->key == -1) - return; - Print(x->left); - cout<key<<' '<color<right); -} -void Red_Black_Tree::Print() -{ - Print(root); - cout<