1. Add the building util of Python

for the markdown docs.
2. Update the deploy.sh
This commit is contained in:
krahets 2023-02-06 23:23:21 +08:00
parent 64f251f933
commit ea901af217
28 changed files with 292 additions and 933 deletions

3
.gitignore vendored
View file

@ -7,9 +7,6 @@
hello-algo.iml
# mkdocs files
site/
.cache/
scripts/
docs/overrides/
src/

View file

@ -6,7 +6,7 @@ Author: msk397 (machangxinq@gmail.com)
""" 键值对 int->String """
class Entry:
def __init__(self, key: int, val: str):
def __init__(self, key, val):
self.key = key
self.val = val

View file

@ -54,11 +54,9 @@ class BinarySearchTree:
# 若树为空,直接提前返回
if root is None:
return None
cur = root
pre = None
# 循环查找,越过叶结点后跳出
cur, pre = root, None
while cur is not None:
# 找到重复结点,直接返回
if cur.val == num:
@ -86,10 +84,8 @@ class BinarySearchTree:
if root is None:
return None
cur = root
pre = None
# 循环查找,越过叶结点后跳出
cur, pre = root, None
while cur is not None:
# 找到待删除结点,跳出循环
if cur.val == num:
@ -99,7 +95,6 @@ class BinarySearchTree:
cur = cur.right
else: # 待删除结点在 cur 的左子树中
cur = cur.left
# 若无待删除结点,则直接返回
if cur is None:
return None

View file

@ -1,8 +0,0 @@
rm -rf ./site
mkdocs build --clean
cd site
git init
git add -A
git commit -m "deploy"
git push -f git@github.com:krahets/hello-algo.git main:gh-pages
cd -

51
docs/chapter_array_and_linkedlist/array.md Normal file → Executable file
View file

@ -143,13 +143,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
=== "Python"
```python title="array.py"
""" 随机访问元素 """
def random_access(nums):
# 在区间 [0, len(nums)-1] 中随机抽取一个数字
random_index = random.randint(0, len(nums) - 1)
# 获取并返回随机元素
random_num = nums[random_index]
return random_num
[class]{}-[func]{random_access}
```
=== "Go"
@ -279,17 +273,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
=== "Python"
```python title="array.py"
""" 扩展数组长度 """
# 请注意Python 的 list 是动态数组,可以直接扩展
# 为了方便学习,本函数将 list 看作是长度不可变的数组
def extend(nums, enlarge):
# 初始化一个扩展长度后的数组
res = [0] * (len(nums) + enlarge)
# 将原数组中的所有元素复制到新数组
for i in range(len(nums)):
res[i] = nums[i]
# 返回扩展后的新数组
return res
[class]{}-[func]{extend}
```
=== "Go"
@ -452,19 +436,9 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
=== "Python"
```python title="array.py"
""" 在数组的索引 index 处插入元素 num """
def insert(nums, num, index):
# 把索引 index 以及之后的所有元素向后移动一位
for i in range(len(nums) - 1, index, -1):
nums[i] = nums[i - 1]
# 将 num 赋给 index 处元素
nums[index] = num
[class]{}-[func]{insert}
""" 删除索引 index 处元素 """
def remove(nums, index):
# 把索引 index 之后的所有元素向前移动一位
for i in range(index, len(nums) - 1):
nums[i] = nums[i + 1]
[class]{}-[func]{remove}
```
=== "Go"
@ -648,15 +622,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
=== "Python"
```python title="array.py"
""" 遍历数组 """
def traverse(nums):
count = 0
# 通过索引遍历数组
for i in range(len(nums)):
count += 1
# 直接遍历数组
for num in nums:
count += 1
[class]{}-[func]{traverse}
```
=== "Go"
@ -803,12 +769,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
=== "Python"
```python title="array.py"
""" 在数组中查找指定元素 """
def find(nums, target):
for i in range(len(nums)):
if nums[i] == target:
return i
return -1
[class]{}-[func]{find}
```
=== "Go"

33
docs/chapter_array_and_linkedlist/linked_list.md Normal file → Executable file
View file

@ -369,20 +369,9 @@ comments: true
=== "Python"
```python title="linked_list.py"
""" 在链表的结点 n0 之后插入结点 P """
def insert(n0, P):
n1 = n0.next
n0.next = P
P.next = n1
[class]{}-[func]{insert}
""" 删除链表的结点 n0 之后的首个结点 """
def remove(n0):
if not n0.next:
return
# n0 -> P -> n1
P = n0.next
n1 = P.next
n0.next = n1
[class]{}-[func]{remove}
```
=== "Go"
@ -557,13 +546,7 @@ comments: true
=== "Python"
```python title="linked_list.py"
""" 访问链表中索引为 index 的结点 """
def access(head, index):
for _ in range(index):
if not head:
return None
head = head.next
return head
[class]{}-[func]{access}
```
=== "Go"
@ -704,15 +687,7 @@ comments: true
=== "Python"
```python title="linked_list.py"
""" 在链表中查找值为 target 的首个结点 """
def find(head, target):
index = 0
while head:
if head.val == target:
return index
head = head.next
index += 1
return -1
[class]{}-[func]{find}
```
=== "Go"

63
docs/chapter_array_and_linkedlist/list.md Normal file → Executable file
View file

@ -912,68 +912,7 @@ comments: true
=== "Python"
```python title="my_list.py"
""" 列表类简易实现 """
class MyList:
""" 构造函数 """
def __init__(self):
self.__capacity = 10 # 列表容量
self.__nums = [0] * self.__capacity # 数组(存储列表元素)
self.__size = 0 # 列表长度(即当前元素数量)
self.__extend_ratio = 2 # 每次列表扩容的倍数
""" 获取列表长度(即当前元素数量) """
def size(self):
return self.__size
""" 获取列表容量 """
def capacity(self):
return self.__capacity
""" 访问元素 """
def get(self, index):
# 索引如果越界则抛出异常,下同
assert index >= 0 and index < self.__size, "索引越界"
return self.__nums[index]
""" 更新元素 """
def set(self, num, index):
assert index >= 0 and index < self.__size, "索引越界"
self.__nums[index] = num
""" 中间插入(尾部添加)元素 """
def add(self, num, index=-1):
assert index >= 0 and index < self.__size, "索引越界"
# 若不指定索引 index ,则向数组尾部添加元素
if index == -1:
index = self.__size
# 元素数量超出容量时,触发扩容机制
if self.__size == self.capacity():
self.extend_capacity()
# 索引 i 以及之后的元素都向后移动一位
for j in range(self.__size - 1, index - 1, -1):
self.__nums[j + 1] = self.__nums[j]
self.__nums[index] = num
# 更新元素数量
self.__size += 1
""" 删除元素 """
def remove(self, index):
assert index >= 0 and index < self.__size, "索引越界"
num = self.nums[index]
# 索引 i 之后的元素都向前移动一位
for j in range(index, self.__size - 1):
self.__nums[j] = self.__nums[j + 1]
# 更新元素数量
self.__size -= 1
# 返回被删除元素
return num
""" 列表扩容 """
def extend_capacity(self):
# 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组
self.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1)
# 更新列表容量
self.__capacity = len(self.__nums)
[class]{MyList}-[func]{}
```
=== "Go"

View file

@ -624,18 +624,7 @@ $$
=== "Python"
```python title="space_complexity.py"
""" 常数阶 """
def constant(n):
# 常量、变量、对象占用 O(1) 空间
a = 0
nums = [0] * 10000
node = ListNode(0)
# 循环中的变量占用 O(1) 空间
for _ in range(n):
c = 0
# 循环中的函数占用 O(1) 空间
for _ in range(n):
function()
[class]{}-[func]{constant}
```
=== "Go"
@ -829,14 +818,7 @@ $$
=== "Python"
```python title="space_complexity.py"
""" 线性阶 """
def linear(n):
# 长度为 n 的列表占用 O(n) 空间
nums = [0] * n
# 长度为 n 的哈希表占用 O(n) 空间
mapp = {}
for i in range(n):
mapp[i] = str(i)
[class]{}-[func]{linear}
```
=== "Go"
@ -996,11 +978,7 @@ $$
=== "Python"
```python title="space_complexity.py"
""" 线性阶(递归实现) """
def linear_recur(n):
print("递归 n =", n)
if n == 1: return
linear_recur(n - 1)
[class]{}-[func]{linear_recur}
```
=== "Go"
@ -1127,10 +1105,7 @@ $$
=== "Python"
```python title="space_complexity.py"
""" 平方阶 """
def quadratic(n):
# 二维列表占用 O(n^2) 空间
num_matrix = [[0] * n for _ in range(n)]
[class]{}-[func]{quadratic}
```
=== "Go"
@ -1272,12 +1247,7 @@ $$
=== "Python"
```python title="space_complexity.py"
""" 平方阶(递归实现) """
def quadratic_recur(n):
if n <= 0: return 0
# 数组 nums 长度为 n, n-1, ..., 2, 1
nums = [0] * n
return quadratic_recur(n - 1)
[class]{}-[func]{quadratic_recur}
```
=== "Go"
@ -1400,13 +1370,7 @@ $$
=== "Python"
```python title="space_complexity.py"
""" 指数阶(建立满二叉树) """
def build_tree(n):
if n == 0: return None
root = TreeNode(0)
root.left = build_tree(n - 1)
root.right = build_tree(n - 1)
return root
[class]{}-[func]{build_tree}
```
=== "Go"

View file

@ -70,14 +70,7 @@ comments: true
=== "Python"
```python title="leetcode_two_sum.py"
class SolutionBruteForce:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 两层循环,时间复杂度 O(n^2)
for i in range(len(nums) - 1):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return i, j
return []
[class]{SolutionBruteForce}-[func]{}
```
=== "Go"
@ -247,16 +240,7 @@ comments: true
=== "Python"
```python title="leetcode_two_sum.py"
class SolutionHashMap:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 辅助哈希表,空间复杂度 O(n)
dic = {}
# 单层循环,时间复杂度 O(n)
for i in range(len(nums)):
if target - nums[i] in dic:
return dic[target - nums[i]], i
dic[nums[i]] = i
return []
[class]{SolutionHashMap}-[func]{}
```
=== "Go"

111
docs/chapter_computational_complexity/time_complexity.md Normal file → Executable file
View file

@ -821,13 +821,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 常数阶 """
def constant(n):
count = 0
size = 100000
for _ in range(size):
count += 1
return count
[class]{}-[func]{constant}
```
=== "Go"
@ -958,12 +952,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 线性阶 """
def linear(n):
count = 0
for _ in range(n):
count += 1
return count
[class]{}-[func]{linear}
```
=== "Go"
@ -1091,13 +1080,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 线性阶(遍历数组)"""
def array_traversal(nums):
count = 0
# 循环次数与数组长度成正比
for num in nums:
count += 1
return count
[class]{}-[func]{array_traversal}
```
=== "Go"
@ -1239,14 +1222,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 平方阶 """
def quadratic(n):
count = 0
# 循环次数与数组长度成平方关系
for i in range(n):
for j in range(n):
count += 1
return count
[class]{}-[func]{quadratic}
```
=== "Go"
@ -1425,20 +1401,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 平方阶(冒泡排序)"""
def bubble_sort(nums):
count = 0 # 计数器
# 外循环:待排序元素数量为 n-1, n-2, ..., 1
for i in range(len(nums) - 1, 0, -1):
# 内循环:冒泡操作
for j in range(i):
if nums[j] > nums[j + 1]:
# 交换 nums[j] 与 nums[j + 1]
tmp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = tmp
count += 3 # 元素交换包含 3 个单元操作
return count
[class]{}-[func]{bubble_sort}
```
=== "Go"
@ -1658,16 +1621,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 指数阶(循环实现)"""
def exponential(n):
count, base = 0, 1
# cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
for _ in range(n):
for _ in range(base):
count += 1
base *= 2
# count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
return count
[class]{}-[func]{exponential}
```
=== "Go"
@ -1836,10 +1790,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 指数阶(递归实现)"""
def exp_recur(n):
if n == 1: return 1
return exp_recur(n - 1) + exp_recur(n - 1) + 1
[class]{}-[func]{exp_recur}
```
=== "Go"
@ -1957,13 +1908,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 对数阶(循环实现)"""
def logarithmic(n):
count = 0
while n > 1:
n = n / 2
count += 1
return count
[class]{}-[func]{logarithmic}
```
=== "Go"
@ -2099,10 +2044,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 对数阶(递归实现)"""
def log_recur(n):
if n <= 1: return 0
return log_recur(n / 2) + 1
[class]{}-[func]{log_recur}
```
=== "Go"
@ -2220,14 +2162,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 线性对数阶 """
def linear_log_recur(n):
if n <= 1: return 1
count = linear_log_recur(n // 2) + \
linear_log_recur(n // 2)
for _ in range(n):
count += 1
return count
[class]{}-[func]{linear_log_recur}
```
=== "Go"
@ -2387,14 +2322,7 @@ $$
=== "Python"
```python title="time_complexity.py"
""" 阶乘阶(递归实现)"""
def factorial_recur(n):
if n == 0: return 1
count = 0
# 从 1 个分裂出 n 个
for _ in range(n):
count += factorial_recur(n - 1)
return count
[class]{}-[func]{factorial_recur}
```
=== "Go"
@ -2587,22 +2515,9 @@ $$
=== "Python"
```python title="worst_best_time_complexity.py"
""" 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """
def random_numbers(n):
# 生成数组 nums =: 1, 2, 3, ..., n
nums = [i for i in range(1, n + 1)]
# 随机打乱数组元素
random.shuffle(nums)
return nums
[class]{}-[func]{random_numbers}
""" 查找数组 nums 中数字 1 所在索引 """
def find_one(nums):
for i in range(len(nums)):
# 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
# 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
if nums[i] == 1:
return i
return -1
[class]{}-[func]{find_one}
```
=== "Go"

37
docs/chapter_hashing/hash_map.md Normal file → Executable file
View file

@ -523,42 +523,9 @@ $$
=== "Python"
```python title="array_hash_map.py"
""" 键值对 int->String """
class Entry:
def __init__(self, key, val):
self.key = key
self.val = val
[class]{Entry}-[func]{}
""" 基于数组简易实现的哈希表 """
class ArrayHashMap:
def __init__(self):
# 初始化一个长度为 100 的桶(数组)
self.bucket = [None] * 100
""" 哈希函数 """
def hash_func(self, key):
index = key % 100
return index
""" 查询操作 """
def get(self, key):
index = self.hash_func(key)
pair = self.bucket[index]
if pair is None:
return None
return pair.val
""" 添加操作 """
def put(self, key, val):
pair = Entry(key, val)
index = self.hash_func(key)
self.bucket[index] = pair
""" 删除操作 """
def remove(self, key):
index = self.hash_func(key)
# 置为 None ,代表删除
self.bucket[index] = None
[class]{ArrayHashMap}-[func]{}
```
=== "Go"

29
docs/chapter_searching/binary_search.md Normal file → Executable file
View file

@ -98,19 +98,7 @@ $$
=== "Python"
```python title="binary_search.py"
""" 二分查找(双闭区间) """
def binary_search(nums, target):
# 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2 # 计算中点索引 m
if nums[m] < target: # 此情况说明 target 在区间 [m+1, j]
i = m + 1
elif nums[m] > target: # 此情况说明 target 在区间 [i, m-1] 中
j = m - 1
else:
return m # 找到目标元素,返回其索引
return -1 # 未找到目标元素,返回 -1
[class]{}-[func]{binary_search}
```
=== "Go"
@ -291,20 +279,7 @@ $$
=== "Python"
```python title="binary_search.py"
""" 二分查找(左闭右开) """
def binary_search1(nums, target):
# 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
i, j = 0, len(nums)
# 循环,当搜索区间为空时跳出(当 i = j 时为空)
while i < j:
m = (i + j) // 2 # 计算中点索引 m
if nums[m] < target: # 此情况说明 target 在区间 [m+1, j)
i = m + 1
elif nums[m] > target: # 此情况说明 target 在区间 [i, m) 中
j = m
else: # 找到目标元素,返回其索引
return m
return -1 # 未找到目标元素,返回 -1
[class]{}-[func]{binary_search1}
```
=== "Go"

12
docs/chapter_searching/hashing_search.md Normal file → Executable file
View file

@ -43,11 +43,7 @@ comments: true
=== "Python"
```python title="hashing_search.py"
""" 哈希查找(数组) """
def hashing_search_array(mapp, target):
# 哈希表的 key: 目标元素value: 索引
# 若哈希表中无此 key ,返回 -1
return mapp.get(target, -1)
[class]{}-[func]{hashing_search_array}
```
=== "Go"
@ -153,11 +149,7 @@ comments: true
=== "Python"
```python title="hashing_search.py"
""" 哈希查找(链表) """
def hashing_search_linkedlist(mapp, target):
# 哈希表的 key: 目标元素value: 结点对象
# 若哈希表中无此 key ,返回 -1
return mapp.get(target, -1)
[class]{}-[func]{hashing_search_linkedlist}
```
=== "Go"

17
docs/chapter_searching/linear_search.md Normal file → Executable file
View file

@ -47,13 +47,7 @@ comments: true
=== "Python"
```python title="linear_search.py"
""" 线性查找(数组) """
def linear_search_array(nums, target):
# 遍历数组
for i in range(len(nums)):
if nums[i] == target: # 找到目标元素,返回其索引
return i
return -1 # 未找到目标元素,返回 -1
[class]{}-[func]{linear_search_array}
```
=== "Go"
@ -195,14 +189,7 @@ comments: true
=== "Python"
```python title="linear_search.py"
""" 线性查找(链表) """
def linear_search_linkedlist(head, target):
# 遍历链表
while head:
if head.val == target: # 找到目标结点,返回之
return head
head = head.next
return None # 未找到目标结点,返回 None
[class]{}-[func]{linear_search_linkedlist}
```
=== "Go"

33
docs/chapter_sorting/bubble_sort.md Normal file → Executable file
View file

@ -15,31 +15,24 @@ comments: true
完成此次冒泡操作后,**数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素**。
=== "Step 1"
![bubble_operation_step1](bubble_sort.assets/bubble_operation_step1.png)
=== "Step 2"
![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png)
=== "Step 3"
![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png)
=== "Step 4"
![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png)
=== "Step 5"
![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png)
=== "Step 6"
![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png)
=== "Step 7"
![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png)
<p align="center"> Fig. 冒泡操作 </p>
@ -96,16 +89,7 @@ comments: true
=== "Python"
```python title="bubble_sort.py"
""" 冒泡排序 """
def bubble_sort(nums):
n = len(nums)
# 外循环:待排序元素数量为 n-1, n-2, ..., 1
for i in range(n - 1, 0, -1):
# 内循环:冒泡操作
for j in range(i):
if nums[j] > nums[j + 1]:
# 交换 nums[j] 与 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
[class]{}-[func]{bubble_sort}
```
=== "Go"
@ -304,20 +288,7 @@ comments: true
=== "Python"
```python title="bubble_sort.py"
""" 冒泡排序(标志优化) """
def bubble_sort_with_flag(nums):
n = len(nums)
# 外循环:待排序元素数量为 n-1, n-2, ..., 1
for i in range(n - 1, 0, -1):
flag = False # 初始化标志位
# 内循环:冒泡操作
for j in range(i):
if nums[j] > nums[j + 1]:
# 交换 nums[j] 与 nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = True # 记录交换元素
if not flag:
break # 此轮冒泡未交换任何元素,直接跳出
[class]{}-[func]{bubble_sort_with_flag}
```
=== "Go"

12
docs/chapter_sorting/insertion_sort.md Normal file → Executable file
View file

@ -63,17 +63,7 @@ comments: true
=== "Python"
```python title="insertion_sort.py"
""" 插入排序 """
def insertion_sort(nums):
# 外循环base = nums[1], nums[2], ..., nums[n-1]
for i in range(1, len(nums)):
base = nums[i]
j = i - 1
# 内循环:将 base 插入到左边的正确位置
while j >= 0 and nums[j] > base:
nums[j + 1] = nums[j] # 1. 将 nums[j] 向右移动一位
j -= 1
nums[j + 1] = base # 2. 将 base 赋值到正确位置
[class]{}-[func]{insertion_sort}
```
=== "Go"

41
docs/chapter_sorting/merge_sort.md Normal file → Executable file
View file

@ -150,46 +150,9 @@ comments: true
=== "Python"
```python title="merge_sort.py"
"""
合并左子数组和右子数组
左子数组区间 [left, mid]
右子数组区间 [mid + 1, right]
"""
def merge(nums, left, mid, right):
# 初始化辅助数组 借助 copy模块
tmp = nums[left:right + 1]
# 左子数组的起始索引和结束索引
left_start, left_end = left - left, mid - left
# 右子数组的起始索引和结束索引
right_start, right_end = mid + 1 - left, right - left
# i, j 分别指向左子数组、右子数组的首元素
i, j = left_start, right_start
# 通过覆盖原数组 nums 来合并左子数组和右子数组
for k in range(left, right + 1):
# 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
if i > left_end:
nums[k] = tmp[j]
j += 1
# 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
elif j > right_end or tmp[i] <= tmp[j]:
nums[k] = tmp[i]
i += 1
# 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
else:
nums[k] = tmp[j]
j += 1
[class]{}-[func]{merge}
""" 归并排序 """
def merge_sort(nums, left, right):
# 终止条件
if left >= right:
return # 当子数组长度为 1 时终止递归
# 划分阶段
mid = (left + right) // 2 # 计算中点
merge_sort(nums, left, mid) # 递归左子数组
merge_sort(nums, mid + 1, right) # 递归右子数组
# 合并阶段
merge(nums, left, mid, right)
[class]{}-[func]{merge_sort}
```
=== "Go"

59
docs/chapter_sorting/quick_sort.md Normal file → Executable file
View file

@ -98,20 +98,7 @@ comments: true
=== "Python"
```python title="quick_sort.py"
""" 哨兵划分 """
def partition(self, nums, left, right):
# 以 nums[left] 作为基准数
i, j = left, right
while i < j:
while i < j and nums[j] >= nums[left]:
j -= 1 # 从右向左找首个小于基准数的元素
while i < j and nums[i] <= nums[left]:
i += 1 # 从左向右找首个大于基准数的元素
# 元素交换
nums[i], nums[j] = nums[j], nums[i]
# 将基准数交换至两子数组的分界线
nums[i], nums[left] = nums[left], nums[i]
return i # 返回基准数的索引
[class]{QuickSort}-[func]{partition}
```
=== "Go"
@ -316,16 +303,7 @@ comments: true
=== "Python"
```python title="quick_sort.py"
""" 快速排序 """
def quick_sort(self, nums, left, right):
# 子数组长度为 1 时终止递归
if left >= right:
return
# 哨兵划分
pivot = self.partition(nums, left, right)
# 递归左子数组、右子数组
self.quick_sort(nums, left, pivot - 1)
self.quick_sort(nums, pivot + 1, right)
[class]{QuickSort}-[func]{quick_sort}
```
=== "Go"
@ -509,24 +487,9 @@ comments: true
=== "Python"
```python title="quick_sort.py"
""" 选取三个元素的中位数 """
def median_three(self, nums, left, mid, right):
# 使用了异或操作来简化代码
# 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1
if (nums[left] < nums[mid]) ^ (nums[left] < nums[right]):
return left
elif (nums[mid] < nums[left]) ^ (nums[mid] > nums[right]):
return mid
return right
[class]{QuickSortMedian}-[func]{median_three}
""" 哨兵划分(三数取中值) """
def partition(self, nums, left, right):
# 以 nums[left] 作为基准数
med = self.median_three(nums, left, (left + right) // 2, right)
# 将中位数交换至数组最左端
nums[left], nums[med] = nums[med], nums[left]
# 以 nums[left] 作为基准数
# 下同省略...
[class]{QuickSortMedian}-[func]{partition}
```
=== "Go"
@ -721,19 +684,7 @@ comments: true
=== "Python"
```python title="quick_sort.py"
""" 快速排序(尾递归优化) """
def quick_sort(self, nums, left, right):
# 子数组长度为 1 时终止
while left < right:
# 哨兵划分操作
pivot = self.partition(nums, left, right)
# 对两个子数组中较短的那个执行快排
if pivot - left < right - pivot:
self.quick_sort(nums, left, pivot - 1) # 递归排序左子数组
left = pivot + 1 # 剩余待排序区间为 [pivot + 1, right]
else:
self.quick_sort(nums, pivot + 1, right) # 递归排序右子数组
right = pivot - 1 # 剩余待排序区间为 [left, pivot - 1]
[class]{QuickSortTailCall}-[func]{quick_sort}
```
=== "Go"

86
docs/chapter_stack_and_queue/queue.md Normal file → Executable file
View file

@ -398,49 +398,7 @@ comments: true
=== "Python"
```python title="linkedlist_queue.py"
""" 基于链表实现的队列 """
class LinkedListQueue:
def __init__(self):
self.__front = None # 头结点 front
self.__rear = None # 尾结点 rear
self.__size = 0
""" 获取队列的长度 """
def size(self):
return self.__size
""" 判断队列是否为空 """
def is_empty(self):
return not self.__front
""" 入队 """
def push(self, num):
# 尾结点后添加 num
node = ListNode(num)
# 如果队列为空,则令头、尾结点都指向该结点
if self.__front is None:
self.__front = node
self.__rear = node
# 如果队列不为空,则将该结点添加到尾结点后
else:
self.__rear.next = node
self.__rear = node
self.__size += 1
""" 出队 """
def poll(self):
num = self.peek()
# 删除头结点
self.__front = self.__front.next
self.__size -= 1
return num
""" 访问队首元素 """
def peek(self):
if self.size() == 0:
print("队列为空")
return False
return self.__front.val
[class]{LinkedListQueue}-[func]{}
```
=== "Go"
@ -881,47 +839,7 @@ comments: true
=== "Python"
```python title="array_queue.py"
""" 基于环形数组实现的队列 """
class ArrayQueue:
def __init__(self, size):
self.__nums = [0] * size # 用于存储队列元素的数组
self.__front = 0 # 队首指针,指向队首元素
self.__size = 0 # 队列长度
""" 获取队列的容量 """
def capacity(self):
return len(self.__nums)
""" 获取队列的长度 """
def size(self):
return self.__size
""" 判断队列是否为空 """
def is_empty(self):
return self.__size == 0
""" 入队 """
def push(self, num):
assert self.__size < self.capacity(), "队列已满"
# 计算尾指针,指向队尾索引 + 1
# 通过取余操作,实现 rear 越过数组尾部后回到头部
rear = (self.__front + self.__size) % self.capacity()
# 尾结点后添加 num
self.__nums[rear] = num
self.__size += 1
""" 出队 """
def poll(self):
num = self.peek()
# 队首指针向后移动一位,若越过尾部则返回到数组头部
self.__front = (self.__front + 1) % self.capacity()
self.__size -= 1
return num
""" 访问队首元素 """
def peek(self):
assert not self.is_empty(), "队列为空"
return self.__nums[self.__front]
[class]{ArrayQueue}-[func]{}
```
=== "Go"

61
docs/chapter_stack_and_queue/stack.md Normal file → Executable file
View file

@ -378,39 +378,7 @@ comments: true
=== "Python"
```python title="linkedlist_stack.py"
""" 基于链表实现的栈 """
class LinkedListStack:
def __init__(self):
self.__peek = None
self.__size = 0
""" 获取栈的长度 """
def size(self):
return self.__size
""" 判断栈是否为空 """
def is_empty(self):
return not self.__peek
""" 入栈 """
def push(self, val):
node = ListNode(val)
node.next = self.__peek
self.__peek = node
self.__size += 1
""" 出栈 """
def pop(self):
num = self.peek()
self.__peek = self.__peek.next
self.__size -= 1
return num
""" 访问栈顶元素 """
def peek(self):
# 判空处理
if not self.__peek: return None
return self.__peek.val
[class]{LinkedListStack}-[func]{}
```
=== "Go"
@ -785,32 +753,7 @@ comments: true
=== "Python"
```python title="array_stack.py"
""" 基于数组实现的栈 """
class ArrayStack:
def __init__(self):
self.__stack = []
""" 获取栈的长度 """
def size(self):
return len(self.__stack)
""" 判断栈是否为空 """
def is_empty(self):
return self.__stack == []
""" 入栈 """
def push(self, item):
self.__stack.append(item)
""" 出栈 """
def pop(self):
assert not self.is_empty(), "栈为空"
return self.__stack.pop()
""" 访问栈顶元素 """
def peek(self):
assert not self.is_empty(), "栈为空"
return self.__stack[-1]
[class]{ArrayStack}-[func]{}
```
=== "Go"

125
docs/chapter_tree/avl_tree.md Normal file → Executable file
View file

@ -179,17 +179,9 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
=== "Python"
```python title="avl_tree.py"
""" 获取结点高度 """
def height(self, node: Optional[TreeNode]) -> int:
# 空结点高度为 -1 ,叶结点高度为 0
if node is not None:
return node.height
return -1
[class]{AVLTree}-[func]{height}
""" 更新结点高度 """
def __update_height(self, node: Optional[TreeNode]):
# 结点高度等于最高子树高度 + 1
node.height = max([self.height(node.left), self.height(node.right)]) + 1
[class]{AVLTree}-[func]{__update_height}
```
=== "Go"
@ -316,13 +308,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
=== "Python"
```python title="avl_tree.py"
""" 获取平衡因子 """
def balance_factor(self, node: Optional[TreeNode]) -> int:
# 空结点平衡因子为 0
if node is None:
return 0
# 结点平衡因子 = 左子树高度 - 右子树高度
return self.height(node.left) - self.height(node.right)
[class]{AVLTree}-[func]{balance_factor}
```
=== "Go"
@ -467,18 +453,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
=== "Python"
```python title="avl_tree.py"
""" 右旋操作 """
def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode:
child = node.left
grand_child = child.right
# 以 child 为原点,将 node 向右旋转
child.right = node
node.left = grand_child
# 更新结点高度
self.__update_height(node)
self.__update_height(child)
# 返回旋转后子树的根节点
return child
[class]{AVLTree}-[func]{__right_rotate}
```
=== "Go"
@ -623,18 +598,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
=== "Python"
```python title="avl_tree.py"
""" 左旋操作 """
def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode:
child = node.right
grand_child = child.left
# 以 child 为原点,将 node 向左旋转
child.left = node
node.right = grand_child
# 更新结点高度
self.__update_height(node)
self.__update_height(child)
# 返回旋转后子树的根节点
return child
[class]{AVLTree}-[func]{__left_rotate}
```
=== "Go"
@ -835,30 +799,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
=== "Python"
```python title="avl_tree.py"
""" 执行旋转操作,使该子树重新恢复平衡 """
def __rotate(self, node: Optional[TreeNode]) -> TreeNode:
# 获取结点 node 的平衡因子
balance_factor = self.balance_factor(node)
# 左偏树
if balance_factor > 1:
if self.balance_factor(node.left) >= 0:
# 右旋
return self.__right_rotate(node)
else:
# 先左旋后右旋
node.left = self.__left_rotate(node.left)
return self.__right_rotate(node)
# 右偏树
elif balance_factor < -1:
if self.balance_factor(node.right) <= 0:
# 左旋
return self.__left_rotate(node)
else:
# 先右旋后左旋
node.right = self.__right_rotate(node.right)
return self.__left_rotate(node)
# 平衡树,无需旋转,直接返回
return node
[class]{AVLTree}-[func]{__rotate}
```
=== "Go"
@ -1088,27 +1029,9 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
=== "Python"
```python title="avl_tree.py"
""" 插入结点 """
def insert(self, val) -> TreeNode:
self.root = self.__insert_helper(self.root, val)
return self.root
[class]{AVLTree}-[func]{insert}
""" 递归插入结点(辅助函数)"""
def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode:
if node is None:
return TreeNode(val)
# 1. 查找插入位置,并插入结点
if val < node.val:
node.left = self.__insert_helper(node.left, val)
elif val > node.val:
node.right = self.__insert_helper(node.right, val)
else:
# 重复结点不插入,直接返回
return node
# 更新结点高度
self.__update_height(node)
# 2. 执行旋转操作,使该子树重新恢复平衡
return self.__rotate(node)
[class]{AVLTree}-[func]{__insert_helper}
```
=== "Go"
@ -1340,37 +1263,9 @@ AVL 树的独特之处在于「旋转 Rotation」的操作其可 **在不影
=== "Python"
```python title="avl_tree.py"
""" 删除结点 """
def remove(self, val: int):
root = self.__remove_helper(self.root, val)
return root
[class]{AVLTree}-[func]{remove}
""" 递归删除结点(辅助函数) """
def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if node is None:
return None
# 1. 查找结点,并删除之
if val < node.val:
node.left = self.__remove_helper(node.left, val)
elif val > node.val:
node.right = self.__remove_helper(node.right, val)
else:
if node.left is None or node.right is None:
child = node.left or node.right
# 子结点数量 = 0 ,直接删除 node 并返回
if child is None:
return None
# 子结点数量 = 1 ,直接删除 node
else:
node = child
else: # 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
temp = self.__get_inorder_next(node.right)
node.right = self.__remove_helper(node.right, temp.val)
node.val = temp.val
# 更新结点高度
self.__update_height(node)
# 2. 执行旋转操作,使该子树重新恢复平衡
return self.__rotate(node)
[class]{AVLTree}-[func]{__remove_helper}
```
=== "Go"

101
docs/chapter_tree/binary_search_tree.md Normal file → Executable file
View file

@ -78,21 +78,7 @@ comments: true
=== "Python"
```python title="binary_search_tree.py"
""" 查找结点 """
def search(self, num: int) -> Optional[TreeNode]:
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
[class]{BinarySearchTree}-[func]{search}
```
=== "Go"
@ -286,36 +272,7 @@ comments: true
=== "Python"
```python title="binary_search_tree.py"
""" 插入结点 """
def insert(self, num: int) -> Optional[TreeNode]:
root = self.root
# 若树为空,直接提前返回
if root is None:
return None
cur = root
pre = None
# 循环查找,越过叶结点后跳出
while cur is not None:
# 找到重复结点,直接返回
if cur.val == num:
return None
pre = cur
# 插入位置在 cur 的右子树中
if cur.val < num:
cur = cur.right
# 插入位置在 cur 的左子树中
else:
cur = cur.left
# 插入结点 val
node = TreeNode(num)
if pre.val < num:
pre.right = node
else:
pre.left = node
return node
[class]{BinarySearchTree}-[func]{insert}
```
=== "Go"
@ -640,59 +597,9 @@ comments: true
=== "Python"
```python title="binary_search_tree.py"
""" 删除结点 """
def remove(self, num: int) -> Optional[TreeNode]:
root = self.root
# 若树为空,直接提前返回
if root is None:
return None
cur = root
pre = None
# 循环查找,越过叶结点后跳出
while cur is not None:
# 找到待删除结点,跳出循环
if cur.val == num:
break
pre = cur
if cur.val < num: # 待删除结点在 cur 的右子树中
cur = cur.right
else: # 待删除结点在 cur 的左子树中
cur = cur.left
# 若无待删除结点,则直接返回
if cur is None:
return None
# 子结点数量 = 0 or 1
if cur.left is None or cur.right is None:
# 当子结点数量 = 0 / 1 时, child = null / 该子结点
child = cur.left or cur.right
# 删除结点 cur
if pre.left == cur:
pre.left = child
else:
pre.right = child
# 子结点数量 = 2
else:
# 获取中序遍历中 cur 的下一个结点
nex = self.get_inorder_next(cur.right)
tmp = nex.val
# 递归删除结点 nex
self.remove(nex.val)
# 将 nex 的值复制给 cur
cur.val = tmp
return cur
[class]{BinarySearchTree}-[func]{remove}
""" 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """
def get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root is None:
return root
# 循环访问左子结点,直到叶结点时为最小结点,跳出
while root.left is not None:
root = root.left
return root
[class]{BinarySearchTree}-[func]{get_inorder_next}
```
=== "Go"

43
docs/chapter_tree/binary_tree_traversal.md Normal file → Executable file
View file

@ -65,21 +65,7 @@ comments: true
=== "Python"
```python title="binary_tree_bfs.py"
""" 层序遍历 """
def hier_order(root: Optional[TreeNode]):
# 初始化队列,加入根结点
queue = collections.deque()
queue.append(root)
# 初始化一个列表,用于保存遍历序列
res = []
while queue:
node = queue.popleft() # 队列出队
res.append(node.val) # 保存节点值
if node.left is not None:
queue.append(node.left) # 左子结点入队
if node.right is not None:
queue.append(node.right) # 右子结点入队
return res
[class]{}-[func]{hier_order}
```
=== "Go"
@ -299,32 +285,11 @@ comments: true
=== "Python"
```python title="binary_tree_dfs.py"
""" 前序遍历 """
def pre_order(root: Optional[TreeNode]):
if root is None:
return
# 访问优先级:根结点 -> 左子树 -> 右子树
res.append(root.val)
pre_order(root=root.left)
pre_order(root=root.right)
[class]{}-[func]{pre_order}
""" 中序遍历 """
def in_order(root: Optional[TreeNode]):
if root is None:
return
# 访问优先级:左子树 -> 根结点 -> 右子树
in_order(root=root.left)
res.append(root.val)
in_order(root=root.right)
[class]{}-[func]{in_order}
""" 后序遍历 """
def post_order(root: Optional[TreeNode]):
if root is None:
return
# 访问优先级:左子树 -> 右子树 -> 根结点
post_order(root=root.left)
post_order(root=root.right)
res.append(root.val)
[class]{}-[func]{post_order}
```
=== "Go"

1
docs/utils/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
__pycache__

87
docs/utils/build_markdown.py Executable file
View file

@ -0,0 +1,87 @@
"""
File: build_markdown_docs.py
Created Time: 2023-02-06
Author: Krahets (krahets@163.com)
"""
import sys, os.path as osp
sys.path.append(osp.dirname(osp.dirname(osp.dirname(osp.abspath(__file__)))))
import re
import glob
import shutil
from docs.utils.extract_code_python import ExtractCodeBlocksPython
def build_markdown(md_path):
with open(md_path, "r") as f:
lines = f.readlines()
code_blocks_dict = {}
file_pattern = re.compile(r'\s*```(\w+)\s+title="(.+)"')
src_pattern = re.compile(r'\s*\[class\]\{(.*?)\}-\[func\]\{(.*?)\}')
for i in range(len(lines)):
# Find the line target to the source codes
src_match = src_pattern.match(lines[i])
if src_match is None:
continue
for j in range(i - 1, -1 ,-1):
file_match = file_pattern.match(lines[j])
if file_match is not None:
break
# Get code blocks
lang = file_match[1]
file_name = file_match[2]
if file_name not in code_blocks_dict:
code_blocks_dict[file_name] = ExtractCodeBlocksPython(
file_path=osp.dirname(md_path).replace("docs/", f"codes/{lang}/") + f"/{file_name}")
header_line = i
class_label = src_match[1]
func_label = src_match[2]
code_blocks = code_blocks_dict[file_name]
src_info = {
"line_number": i,
"class_label": src_match[1],
"func_label": src_match[2],
"code_blocks": code_blocks_dict[file_name]
}
# Add the class to the doc
if not func_label and class_label:
if class_label in code_blocks.classes:
lines.pop(header_line)
class_block = code_blocks.classes[class_label]["block"]
for code_line in class_block[::-1]:
ind = " " * 4 if code_line != "\n" else ""
lines.insert(header_line, ind + code_line)
# Add the function to the doc
elif func_label and not class_label:
if func_label in code_blocks.functions:
lines.pop(header_line)
func_block = code_blocks.functions[func_label]
for code_line in func_block["block"][::-1]:
ind = " " * 4 if code_line != "\n" else ""
lines.insert(header_line, ind + code_line)
# Add the class method to the doc
elif func_label and class_label:
if class_label in code_blocks.classes:
class_dict = code_blocks.classes[class_label]
if func_label in class_dict["functions"]:
lines.pop(header_line)
func_block = class_dict["functions"][func_label]
for code_line in func_block["block"][::-1]:
lines.insert(header_line, code_line)
with open(md_path.replace("docs/", "build/"), "w") as f:
f.writelines(lines)
print(f"Built {md_path}")
if __name__ == "__main__":
# Copy files to the build dir
shutil.copytree("docs", "build", dirs_exist_ok=True)
shutil.rmtree("build/utils")
# Build docs
for md_path in glob.glob("docs/chapter_*/*.md"):
build_markdown(md_path)

8
docs/utils/deploy.sh Normal file
View file

@ -0,0 +1,8 @@
# This script is borrowed from https://gist.github.com/cobyism/4730490
git add build && git commit -m "build"
git subtree push --prefix build origin built-docs
mkdocs build --clean
git add site && git commit -m "deploy"
git subtree push --prefix site origin gh-pages

117
docs/utils/extract_code_python.py Executable file
View file

@ -0,0 +1,117 @@
"""
File: extract_code_python.py
Created Time: 2023-02-06
Author: Krahets (krahets@163.com)
"""
import re
import os
import os.path as osp
import glob
class ExtractCodeBlocksPython:
def __init__(self, file_path) -> None:
self.file_path = file_path
with open(file_path) as f:
self.lines = f.readlines()
self.content = "".join(self.lines)
# Regular expression pattern to match function names and class names
self.func_pattern = re.compile(r'(\s*)def\s+(\w+)\s*\(')
self.class_pattern = re.compile(r'class\s+(\w+)')
# Detect and extract all the classes and fucntions
self.classes = self.extract_class_blocks()
self.functions = self.extract_function_blocks()
def search_block(self, header_line, indentation):
"""
Search class/function block given the header_line and indentation
"""
start_line, end_line = 0, len(self.lines)
# Search the code
for i in range(header_line + 1, len(self.lines)):
if re.search("^\s*\n|^\s{ind}\s+.+\n".replace("ind", str(indentation)),
self.lines[i]) is None:
end_line = i
break
# Search the header comment
for i in range(header_line - 1, -1, -1):
if re.search('^\s{ind}""".+'.replace("ind", str(indentation)),
self.lines[i]) is not None:
start_line = i
break
func_block = self.lines[start_line:end_line]
# Remove empty lines at bottom
for i in range(len(func_block) - 1, -1, -1):
if re.search("^\s*\n", func_block[i]) is None:
break
end_line -= 1
return start_line, end_line, self.lines[start_line:end_line]
def extract_function_blocks(self, indentation=0, start_line=-1, end_line=-1):
"""
Extract all the functions with given indentation
"""
functions = {}
if start_line == -1:
start_line = 0
if end_line == -1:
end_line = len(self.lines) - 1
for line_num in range(start_line, end_line + 1):
# Search the function header
func_match = self.func_pattern.match(self.lines[line_num])
if func_match is None: continue
# The function should match the input indentation
if len(func_match.group(1)) != indentation: continue
header_line = line_num
# Search the block from the header line
start_line, end_line, func_block = self.search_block(header_line, indentation)
# Construct the functions dict
func_label = func_match.group(2)
functions[func_label] = {
"indentation": indentation,
"line_number": {
"start": start_line,
"end": end_line,
"header": header_line,
},
"block": func_block,
}
return functions
def extract_class_blocks(self):
"""
Extract all the classes with given indentation
"""
classes = {}
for line_num, line in enumerate(self.lines):
# Search the class header
class_match = self.class_pattern.match(line)
if class_match is None: continue
header_line = line_num
# Search the block from the header line
start_line, end_line, class_block = self.search_block(header_line, 0)
# Construct the classes dict
class_label = class_match.group(1)
classes[class_label] = {
"indentation": 0,
"line_number": {
"start": start_line,
"end": end_line,
"header": header_line,
},
"block": class_block,
"functions": self.extract_function_blocks(
indentation=4, start_line=start_line, end_line=end_line)
}
return classes

View file

@ -3,11 +3,11 @@ site_name: Hello 算法
site_url: https://www.hello-algo.com/
site_author: Krahets
site_description: 一本动画图解、能运行、可提问的数据结构与算法入门书
docs_dir: docs
docs_dir: build
# Repository
repo_name: krahets/hello-algo
repo_url: https://github.com/krahets/hello-algo
edit_uri: https://github.com/krahets/hello-algo/tree/master/docs/
edit_uri: https://github.com/krahets/hello-algo/tree/main/docs/
# Copyright
copyright: Copyright &copy; 2022 Krahets
@ -15,7 +15,7 @@ copyright: Copyright &copy; 2022 Krahets
# Configuration
theme:
name: material
custom_dir: docs/overrides
custom_dir: build/overrides
language: zh
features:
# - announce.dismiss