引言 参照的是左程云的课程:https://space.bilibili.com/8888480/lists/3509640?type=series 本笔记包括了class002 -> class 007的内容,涵盖了社会实验模拟、位运算、基础排序算法、算法验证方法、二分搜索以及复杂度分析等核心内容。
原代码是java版,我改成了python
002【入门】从社会实验到入门提醒 基尼系数的理论基础 基尼系数是经济学中衡量收入分配不平等程度的重要指标,其数学定义为:
$$ G = \frac{\sum_{i=1}^{n}\sum_{j=1}^{n}|x_i - x_j|}{2n\sum_{i=1}^{n}x_i} $$
其中 $x_i$ 表示第 $i$ 个个体的财富值,$n$ 为总人数。
基尼系数的经济学意义
G = 0 :完全平等,所有人财富相同
G = 1 :完全不平等,一人拥有全部财富
G = 0.4-0.5 :国际公认的贫富差距警戒线
G > 0.5 :社会可能面临动荡风险
财富分配模拟实验 通过计算机模拟研究在完全随机的财富转移过程中,社会财富分配的自然演化规律。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import numpy as npimport randomdef calculate_gini (wealth ): """ 计算基尼系数的函数 参数: wealth - 财富分布列表 返回: 基尼系数值 """ n = len (wealth) sum_of_wealth = sum (wealth) sum_of_absolute_differences = 0 for i in range (n): for j in range (n): sum_of_absolute_differences += abs (wealth[i] - wealth[j]) return sum_of_absolute_differences / (2 * n * sum_of_wealth) def experiment (n, t ): """ 财富分配模拟实验 参数: n - 人数, t - 模拟轮数 """ wealth = [100 ] * n for _ in range (t): has_money = [w > 0 for w in wealth] transfers = [] for j in range (n): if has_money[j]: other = j while other == j: other = random.randint(0 , n - 1 ) transfers.append((j, other)) for giver, receiver in transfers: wealth[giver] -= 1 wealth[receiver] += 1 wealth.sort() print ("财富分布(从贫穷到富有):" ) for idx, w in enumerate (wealth): print (int (w), end=' ' ) if idx % 10 == 9 : print () print () print ("社会基尼系数:" , calculate_gini(wealth))
实验意义与启示
随机性中的必然性 :即使在完全公平的随机转移规则下,财富差距仍会自然产生
马太效应 :财富分配存在自然的分化趋势
社会政策启示 :需要主动的调节机制来维护社会公平
003【入门】二进制和位运算 计算机数值表示系统 正数的二进制表示 正数采用标准的二进制表示法,最高位为符号位(0表示正数)。
负数的补码表示 负数采用补码(Two’s Complement)表示:
$$ \text{负数补码} = \sim(\text{原码}) + 1 $$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def print_binary (num ): """ 打印32位二进制表示 参数: num - 要打印的整数 """ s = '' for i in range (31 , -1 , -1 ): s += '1' if (num & (1 << i)) != 0 else '0' print (s) if __name__ == "__main__" : a = 78 print (f"正数{a} 的二进制表示:" ) print_binary(a) b = -6 print (f"负数{b} 的二进制表示:" ) print_binary(b) print (f"~{a} + 1 = {~a + 1 } " ) print_binary(~a + 1 )
核心位运算操作 基本位运算符详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 def bitwise_operations_demo (): """位运算操作演示""" '''0b 是Python中表示二进制数字的前缀。''' g = 0b0001010 h = 0b0001100 print ("操作数g:" , bin (g)) print ("操作数h:" , bin (h)) print ("g | h =" , bin (g | h)) print ("g & h =" , bin (g & h)) print ("g ^ h =" , bin (g ^ h)) def shift_operations_demo (): """移位运算演示""" i = 0b0011010 print (f"原数: {i} , 二进制: {bin (i)} " ) print (f"{i} << 1 = {i << 1 } " ) print (f"{i} << 2 = {i << 2 } " ) print (f"{i} << 3 = {i << 3 } " ) k = 10 print (f"{k} >> 1 = {k >> 1 } " ) print (f"{k} >> 2 = {k >> 2 } " ) print (f"{k} >> 3 = {k >> 3 } " )
逻辑运算与位运算的重要区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def return_true (): print ("执行了return_true函数" ) return True def return_false (): print ("执行了return_false函数" ) return False def logical_vs_bitwise (): """演示逻辑运算与位运算的区别""" print ("=== 位运算测试 ===" ) test1 = return_true() | return_false() print (f"位运算结果: {test1} " ) print ("=== 逻辑运算测试 ===" ) test2 = return_true() or return_false() print (f"逻辑运算结果: {test2} " )
位运算的实际应用
快速乘除法 :左移代替乘法,右移代替除法
奇偶性判断 :num & 1 == 0 判断偶数。这是因为:num & 1 只保留 num 的二进制最低位,其余全部变成0。
集合操作 :用位掩码表示集合的并、交、差运算
状态压缩 :在动态规划中压缩状态空间
004【入门】选择、冒泡、插入排序 理解python中的class:什么是“实例化类”?
类(class):可以理解为一个“模具”或者“模板”,描述一类对象应该有哪些属性和行为。
实例(instance):就是根据这个“模具”制造出来的一个具体的“物品”。
实例化:把类变成实例(对象)的过程,叫做实例化。
选择排序(Selection Sort) 算法思想 每次从未排序部分选择最小(或最大)元素,将其放置到已排序部分的末尾。
时间复杂度分析
比较次数 :$\sum_{i=0}^{n-2}(n-1-i) = \frac{n(n-1)}{2} = O(n^2)$
交换次数 :$O(n)$
总体复杂度 :$O(n^2)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class SortingAlgorithms : @staticmethod def swap (arr, i, j ): """ 交换数组中两个位置的元素 参数: arr - 数组, i,j - 要交换的索引 """ arr[i], arr[j] = arr[j], arr[i] @staticmethod def selection_sort (arr ): """ 选择排序实现 时间复杂度: O(n²), 空间复杂度: O(1) 不稳定排序 """ if arr is None or len (arr) < 2 : return for i in range (len (arr) - 1 ): min_index = i for j in range (i + 1 , len (arr)): if arr[j] < arr[min_index]: min_index = j SortingAlgorithms.swap(arr, i, min_index)
冒泡排序(Bubble Sort) 算法思想 重复遍历数组,比较相邻元素并在必要时交换,使得大元素逐渐”冒泡”到数组末尾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @staticmethod def bubble_sort (arr ): """ 冒泡排序实现 时间复杂度: O(n²), 空间复杂度: O(1) 稳定排序 """ if arr is None or len (arr) < 2 : return for end in range (len (arr) - 1 , 0 , -1 ): for i in range (end): if arr[i] > arr[i + 1 ]: SortingAlgorithms.swap(arr, i, i + 1 )
冒泡排序的优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @staticmethod def bubble_sort_optimized (arr ): """ 优化版冒泡排序:添加提前终止条件 如果某轮遍历中没有发生交换,说明数组已有序 """ if arr is None or len (arr) < 2 : return for end in range (len (arr) - 1 , 0 , -1 ): swapped = False for i in range (end): if arr[i] > arr[i + 1 ]: SortingAlgorithms.swap(arr, i, i + 1 ) swapped = True if not swapped: break
插入排序(Insertion Sort) 算法思想 将数组分为已排序和未排序两部分,依次将未排序元素插入到已排序部分的正确位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @staticmethod def insertion_sort (arr ): """ 插入排序实现 时间复杂度: 最坏O(n²), 最好O(n), 平均O(n²) 空间复杂度: O(1) 稳定排序,对小规模或近似有序数据效率高 """ if arr is None or len (arr) < 2 : return for i in range (1 , len (arr)): for j in range (i - 1 , -1 , -1 ): if arr[j] > arr[j + 1 ]: SortingAlgorithms.swap(arr, j, j + 1 ) else : break
插入排序的另一种实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @staticmethod def insertion_sort_v2 (arr ): """ 插入排序的另一种实现:先保存要插入的元素,然后移动其他元素 减少交换次数,提高效率 """ if arr is None or len (arr) < 2 : return for i in range (1 , len (arr)): key = arr[i] j = i - 1 while j >= 0 and arr[j] > key: arr[j + 1 ] = arr[j] j -= 1 arr[j + 1 ] = key
排序算法性能对比
算法
时间复杂度(最好)
时间复杂度(平均)
时间复杂度(最坏)
空间复杂度
稳定性
选择排序
$O(n^2)$
$O(n^2)$
$O(n^2)$
$O(1)$
不稳定
冒泡排序
$O(n)$
$O(n^2)$
$O(n^2)$
$O(1)$
稳定
插入排序
$O(n)$
$O(n^2)$
$O(n^2)$
$O(1)$
稳定
005【入门】对数器-验证的重要手段 对数器的理论基础 对数器(Logarithmic Validator)是一种系统性验证算法正确性的重要工具,通过大量随机测试用例来检验算法实现的可靠性。
对数器设计的六个核心原则
确定待测算法a :需要验证正确性的高效算法
实现简单算法b :复杂度可能不优但逻辑简单、容易验证正确的算法
构建随机样本生成器 :能够产生各种边界情况的测试数据
对比验证 :在相同输入下比较两种算法的输出结果
错误定位 :当发现不一致时,人工分析并修正错误
大规模验证 :通过大量测试建立对算法正确性的信心
对数器实现框架(以上节课的三种排序方法为例) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 import randomclass AlgorithmValidator : """算法验证器类""" @staticmethod def random_array (n, v ): """ 生成随机数组 参数: n - 数组长度, v - 元素值域[1,v] 返回: 长度为n的随机数组 """ return [random.randint(1 , v) for _ in range (n)] @staticmethod def copy_array (arr ): """ 数组深拷贝 参数: arr - 原数组 返回: 原数组的副本 """ return arr[:] @staticmethod def arrays_equal (arr1, arr2 ): """ 比较两个数组是否相等 参数: arr1, arr2 - 待比较的数组 返回: 布尔值表示是否相等 """ if len (arr1) != len (arr2): return False for a, b in zip (arr1, arr2): if a != b: return False return True @staticmethod def comprehensive_sort_test (): """ 排序算法综合测试 使用对数器方法验证多种排序算法的正确性 """ N = 200 V = 1000 test_times = 50000 print ("算法验证开始..." ) for test_round in range (test_times): n = random.randint(0 , N - 1 ) arr = AlgorithmValidator.random_array(n, V) arr_selection = AlgorithmValidator.copy_array(arr) arr_bubble = AlgorithmValidator.copy_array(arr) arr_insertion = AlgorithmValidator.copy_array(arr) arr_builtin = AlgorithmValidator.copy_array(arr) SortingAlgorithms.selection_sort(arr_selection) SortingAlgorithms.bubble_sort(arr_bubble) SortingAlgorithms.insertion_sort(arr_insertion) arr_builtin.sort() if not (AlgorithmValidator.arrays_equal(arr_selection, arr_builtin) and AlgorithmValidator.arrays_equal(arr_bubble, arr_builtin) and AlgorithmValidator.arrays_equal(arr_insertion, arr_builtin)): print ("发现算法错误!" ) print (f"测试轮次: {test_round + 1 } " ) print (f"原始数组: {arr} " ) print (f"选择排序: {arr_selection} " ) print (f"冒泡排序: {arr_bubble} " ) print (f"插入排序: {arr_insertion} " ) print (f"内置排序: {arr_builtin} " ) return False if (test_round + 1 ) % 1000 == 0 : print (f"已完成 {test_round + 1 } 次测试..." ) print ("所有测试通过!算法实现正确。" ) return True
对数器方法的优势
自动化验证 :减少人工测试的工作量和错误率
覆盖边界情况 :随机生成能够触及各种极端情况
置信度建立 :大量测试通过后可以高度确信算法正确性
错误定位 :一旦发现问题能够提供具体的错误样例
006【入门】二分搜索 二分搜索的数学基础 二分搜索基于分治思想 ,每次将搜索空间减半,时间复杂度为 $O(\log n)$。
设数组长度为 $n$,经过 $k$ 次二分后搜索空间大小为 $\frac{n}{2^k}$,当搜索空间减小到1时:
$$\frac{n}{2^k} = 1 \Rightarrow k = \log_2 n$$
基础二分搜索 问题:判断有序数组中是否存在目标值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def binary_search_exist (arr, target ): """ 在有序数组中查找目标值是否存在 参数: arr - 有序数组, target - 目标值 返回: True/False 表示是否存在 时间复杂度: O(log n), 空间复杂度: O(1) """ if arr is None or len (arr) == 0 : return False left, right = 0 , len (arr) - 1 while left <= right: mid = left + (right - left) // 2 if arr[mid] == target: return True elif arr[mid] > target: right = mid - 1 else : left = mid + 1 return False
暴力验证方法 1 2 3 4 5 6 7 8 9 def linear_search_exist (arr, target ): """ 线性搜索验证方法 用于对数器验证二分搜索的正确性 """ for element in arr: if element == target: return True return False
二分搜索的边界查找变种 查找左边界:>=target的最左位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def binary_search_left_bound (arr, target ): """ 在有序数组中查找 >= target 的最左位置 参数: arr - 有序数组, target - 目标值 返回: 满足条件的最左索引,不存在返回-1 """ if arr is None or len (arr) == 0 : return -1 left, right = 0 , len (arr) - 1 ans = -1 while left <= right: mid = left + (right - left) // 2 if arr[mid] >= target: ans = mid right = mid - 1 else : left = mid + 1 return ans def linear_search_left_bound (arr, target ): """线性搜索验证:查找>=target的最左位置""" for i in range (len (arr)): if arr[i] >= target: return i return -1
查找右边界:<=target的最右位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def binary_search_right_bound (arr, target ): """ 在有序数组中查找 <= target 的最右位置 参数: arr - 有序数组, target - 目标值 返回: 满足条件的最右索引,不存在返回-1 """ if arr is None or len (arr) == 0 : return -1 left, right = 0 , len (arr) - 1 ans = -1 while left <= right: mid = left + (right - left) // 2 if arr[mid] <= target: ans = mid left = mid + 1 else : right = mid - 1 return ans def linear_search_right_bound (arr, target ): """线性搜索验证:查找<=target的最右位置""" for i in range (len (arr) - 1 , -1 , -1 ): if arr[i] <= target: return i return -1
峰值元素查找 问题定义 在数组中找到任意一个峰值元素(比左右邻居都大的元素),假设边界外的元素为负无穷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def find_peak_element (arr ): """ 查找数组中的峰值元素 参数: arr - 整数数组(相邻元素不相等) 返回: 任意峰值元素的索引 时间复杂度: O(log n) """ n = len (arr) if n == 1 : return 0 if arr[0 ] > arr[1 ]: return 0 if arr[n-1 ] > arr[n-2 ]: return n - 1 left, right = 1 , n - 2 while left <= right: mid = left + (right - left) // 2 if arr[mid-1 ] > arr[mid]: right = mid - 1 elif arr[mid] < arr[mid+1 ]: left = mid + 1 else : return mid return -1
峰值查找的正确性证明 定理 :在满足相邻元素不相等的数组中,上述算法一定能找到峰值。
证明 :
边界已处理端点峰值。
二分查找时,每次都能缩小到含有峰值的半区。
因为每次都“爬坡”,必然最终会达到一个峰值。
相邻元素不等消除了平台的歧义。
因此,算法在O(log n)时间内一定能找到一个峰值。
二分搜索验证框架 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 def test_binary_search_algorithms (): """二分搜索算法综合测试""" N = 100 V = 1000 test_times = 500000 print ("二分搜索算法测试开始..." ) for _ in range (test_times): n = random.randint(0 , N - 1 ) arr = [random.randint(1 , V) for _ in range (n)] arr.sort() target = random.randint(0 , V - 1 ) if binary_search_exist(arr, target) != linear_search_exist(arr, target): print ("基础二分搜索错误!" ) return False if binary_search_left_bound(arr, target) != linear_search_left_bound(arr, target): print ("左边界查找错误!" ) return False if binary_search_right_bound(arr, target) != linear_search_right_bound(arr, target): print ("右边界查找错误!" ) return False print ("所有二分搜索测试通过!" ) return True
007【入门】时间复杂度和空间复杂度 时间复杂度的数学基础 渐近记号系统 设 $f(n)$ 和 $g(n)$ 为定义在正整数集上的函数:
大O记号 $O(g(n))$:$f(n) = O(g(n))$ 当且仅当存在正常数 $c$ 和 $n_0$,使得对所有 $n \geq n_0$ 有 $f(n) \leq c \cdot g(n)$
大Ω记号 $\Omega(g(n))$:$f(n) = \Omega(g(n))$ 当且仅当存在正常数 $c$ 和 $n_0$,使得对所有 $n \geq n_0$ 有 $f(n) \geq c \cdot g(n)$
大Θ记号 $\Theta(g(n))$:$f(n) = \Theta(g(n))$ 当且仅当 $f(n) = O(g(n))$ 且 $f(n) = \Omega(g(n))$
其实和泛函的函数的范数有点像,也就是这个映射算是有界的那种感觉。
常见复杂度级别 $$O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!)$$
复杂嵌套循环分析 等差数列型循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def quadratic_complexity_demo (N ): """ 演示O(n²)时间复杂度 等差数列求和:1 + 2 + ... + n = n(n+1)/2 = O(n²) """ operations = 0 for i in range (1 , N + 1 ): for j in range (i, N + 1 ): operations += 1 print (f"N={N} , 总操作次数={operations} , 理论值={N*(N+1 )//2 } " ) return operations
调和级数型循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def n_log_n_complexity_demo (N ): """ 演示O(n log n)时间复杂度 调和级数:1 + 1/2 + 1/3 + ... + 1/n ≈ ln(n) = O(log n) """ operations = 0 for i in range (1 , N + 1 ): j = i while j <= N: operations += 1 j += i print (f"N={N} , 总操作次数={operations} " ) return operations
复杂度实验验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import timedef complexity_benchmark (): """ 通过实际运行时间验证复杂度分析 """ test_sizes = [1000 , 2000 , 4000 , 8000 ] print ("=== 复杂度实验验证 ===" ) print ("规模\tO(n²)时间\tO(n log n)时间\t比率" ) for N in test_sizes: start_time = time.time() quadratic_complexity_demo(N) quadratic_time = time.time() - start_time start_time = time.time() n_log_n_complexity_demo(N) n_log_n_time = time.time() - start_time ratio = quadratic_time / n_log_n_time if n_log_n_time > 0 else float ('inf' ) print (f"{N} \t{quadratic_time:.4 f} s\t{n_log_n_time:.4 f} s\t{ratio:.2 f} " )
单循环冒泡排序的复杂度分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def single_loop_bubble_sort (arr ): """ 使用单个循环实现冒泡排序 虽然只有一个while循环,但时间复杂度仍然是O(n²) """ if arr is None or len (arr) < 2 : return n = len (arr) end = n - 1 i = 0 while end > 0 : if arr[i] > arr[i + 1 ]: arr[i], arr[i + 1 ] = arr[i + 1 ], arr[i] if i < end - 1 : i += 1 else : end -= 1 i = 0
空间复杂度分析 动态数组的均摊复杂度分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def dynamic_array_analysis (): """ 动态数组扩容的均摊复杂度分析 """ arr = [] operations = [] for i in range (16 ): old_capacity = len (arr) arr.append(i) if len (arr) > old_capacity: cost = old_capacity else : cost = 1 operations.append(cost) print (f"插入元素{i} , 当前大小={len (arr)} , 本次代价={cost} " ) total_cost = sum (operations) average_cost = total_cost / len (operations) print (f"总代价={total_cost} , 平均代价={average_cost:.2 f} " )
递归算法的空间复杂度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def recursive_space_analysis (n ): """ 递归算法空间复杂度分析 计算阶乘的递归实现 """ if n <= 1 : return 1 return n * recursive_space_analysis(n - 1 ) def iterative_space_analysis (n ): """ 迭代版本的阶乘计算 空间复杂度为O(1) """ result = 1 for i in range (1 , n + 1 ): result *= i return result
复杂度分析的实用技巧 主定理(Master Theorem) 对于递归关系 $T(n) = aT(\frac{n}{b}) + f(n)$,其中 $a \geq 1, b > 1$:
如果 $f(n) = O(n^{\log_b a - \epsilon})$,则 $T(n) = \Theta(n^{\log_b a})$
如果 $f(n) = \Theta(n^{\log_b a})$,则 $T(n) = \Theta(n^{\log_b a} \log n)$
如果 $f(n) = \Omega(n^{\log_b a + \epsilon})$,则 $T(n) = \Theta(f(n))$
均摊分析方法
聚合分析 :分析一系列操作的总代价
核算法 :为每种操作分配均摊代价
势能法 :定义势能函数分析代价分布
实际性能考虑因素 1 2 3 4 5 6 7 8 9 10 11 def practical_performance_factors (): """ 影响实际性能的因素 """ print ("影响算法实际性能的因素:" ) print ("1. 常数因子:O(n)算法的常数可能很大" ) print ("2. 数据规模:小规模时简单算法可能更快" ) print ("3. 内存访问模式:缓存友好的算法性能更好" ) print ("4. 分支预测:减少条件分支可提高性能" ) print ("5. 编译器优化:现代编译器能显著优化代码" ) print ("6. 硬件特性:利用SIMD等特性可大幅提速" )