前言
文中部分内容摘自 [美] Goodrich et al. 所著《Data Structures and Algorithms in Python》。
一、Python语言
与 Java 和 C++ 不同,Python 是一种动态类型语言,标识符的数据类型并不需要事先声明。在代码精简的同时,也使其占用不必要的内存。
伪随机树生成
Python 的 random 模块能够生成伪随机数,在统计上随机(不一定是真实随机)。事实上,一个简单而流行的伪随机数生成器选择它的下一个数字是基于最近选择的数和一些额外的参数,所使用的公式如下: n e x t = ( a ⋅ c u r r e n t + b ) % n next=(a\cdot current+b)\%n next=(a⋅current+b)%n这里的 a、b 和 n 是适当选择的整数。Python 使用更先进的技术梅森旋转算法(Mersenne twister)。这些技术所产生的序列是系统统一的,对于大多数程序而言通常是足够的,但对于如计算机安全设置这样一个需要不可预测的随机序列的程序,就不应该使用这种公式。由于伪随机数生成器的下一个数是由上一个数决定的,这样的发生器总是需要一个开始的数字,这就是所谓的种子。random 模块的顶级函数如下:
语法 | 描述 |
---|---|
seed(hashable) | 基于参数的散列值初始化伪随机数生成器 |
random() | 在开区间 (0,1) 返回一个伪随机浮点数 |
randint(a,b) | 在闭区间 [a,b] 返回一个伪随机整数 |
randrange(start,stop,step) | 在参数指定的 Python 标准范围内返回一个伪随机整数 |
choice(seq) | 返回一个伪随机选择的给定序列中的元素 |
shuffle(seq) | 重新排列给定的伪随机序列中的元素 |
二、数据结构
常见数据结构
数据结构 | 英文名 | 描述 | 评价 |
---|---|---|---|
数组 | Array | 从一维至多维,每一个位置对应唯一的索引;在中间部分插入一个数后,其后每一个数的索引值往后推进一位;Python 中的 list 就是一种数组; | 通过索引,取值便利;但数据插入和删除效率低下; |
散列表 | Hash Table | 又称为哈希表;数据以对象的形式存储,每个对象对应唯一的索引 Key;与数组的区别在于数组的唯一索引对应一个数值,散列表的唯一索引对应一个对象;这个对象可以是任何结构的数据,Python 中的 dict 就是一种散列表; | 通过索引,取值便利,相较于数组插入和删除i效率更高;但需要占用更多内存; |
队列 | Queue | 遵从 First-In-First-Out (FIFO) 的规则,先进入队列的先离开; | 只能在数据一端插入,另一端删除; |
栈 | Stack | 遵从 Last-In-First-Out (LILO) 的规则,后进入栈的先离开;对数据的逆置操作应用的便是栈; | 只能引用最顶端的数据; |
链表 | Linked List | 由节点一一相连组成的链条;每一个节点包含该节点的数据以及指引上下游节点的指针;与图的区别在于每个节点最多附带两个指针; | 数据插入和删除方便(只需要改变指针);但只能通过遍历节点检索数据,效率低下; |
图 | Graph | 由节点相连组成网状结构,节点又成为顶点;分为有向图和无向图; | 可为每一条节点与节点相连的边指定方向; |
树 | Tree | 由层属关系的节点相连构成;与链条和图的不同在于不存在环形结构;最常见的为二叉树,除此之外还有 N 元树、平衡树、红黑树等; | - |
堆 | Heap | 即满足特定条件的完全二叉树;分为大根堆和小根堆,大根堆的父节点数值大于等于子节点数值,小根堆相反;常用于数组排序; | 满足前提条件的应用场景较少; |
代码实现
在 Python 中,对象的属性可作为指针指引下一步遍历或递归的方向,因此以上所有数据结构皆可通过自定义对象搭配系统内置类 list 实现,代码编写并不复杂。本文遂不提供每一种数据结构的代码实现,如读者有需求,可参考本文前言部分所提到的书籍提供的免费源代码,相关网址请点击这里。以下是基于链表实现栈的示例:
class LinkedStack:
"""LIFO Stack implementation using a singly linked list for storage."""
class _Node:
"""Lightweight, nonpublic class for storing a singly linked node."""
__slots__ = '_element', '_next' # streamline memory usage
def __init__(self, element, next): # initialize node's fields
self._element = element # reference to user's element
self._next = next # reference to next node
def __init__(self):
"""Create an empty stack."""
self._head = None # reference to the head node
self._size = 0 # number of stack elements
def __len__(self):
"""Return the number of elements in the stack."""
return self._size
def is_empty(self):
"""Return True if the stack is empty."""
return self._size == 0
def push(self, e):
"""Add element e to the top of the stack."""
self._head = self._Node(e, self._head) # create and link a new node
self._size += 1
def top(self):
"""Return (but do not remove) the element at the top of the stack.
Raise Empty exception if the stack is empty."""
if self.is_empty():
raise IndexError('Stack is empty')
return self._head._element # top of stack is at head of list
def pop(self):
"""Remove and return the element from the top of the stack (i.e., LIFO).
Raise Empty exception if the stack is empty."""
if self.is_empty():
raise IndexError('Stack is empty')
answer = self._head._element
self._head = self._head._next # bypass the former top node
self._size -= 1
return answer
三、算法
除数据压缩以外,所有的数据结构相关算法,其核心思想离不开遍历和递归。以有向图为例,深度优先搜索应用遍历,广度优先搜索同时应用遍历和递归。
遍历
遍历的需求层次决定了算法的时间复杂度上限。例如大多数排序算法需要通过两两比较对原序列的每一个元素进行考察,遍历需求为两层,因此时间复杂度最高为 O ( n 2 ) O(n^2) O(n2)。算法的优化思路在于减小遍历需求,例如在遍历处理前通过某一特征对数据进行预排序,或进行分组。
递归
递归是处理包括链表、图、树、堆等在内的以节点形式存储数据的数据结构的关键。递归通俗而言即在公式中自己调用自己,从而逐层深入。基本的递归流程如下:
Algorithm Recursion(Node):
if Node meets requirements then
call process(Node) # code block for processing Node, i.e. export node information, or break
for each child node V of Node do
recursively call Recursion(V)
Python 解释器存在默认递归深度上限,通常在1000以上。当系统提示达到最高递归深度时,会报错 “maximum recursion depth exceeded while calling a Python object”,这时可以通过以下代码查看和修改递归深度上限:
import sys
sys.getrecursionlimit() #查看递归上限
sys.setrecursionlimit(5000) #修改递归上限