数据结构用于描述一种或多种数据元素之间的特定关系,算法是程序设计中对数据操作的描述,数据结构和算法组成了程序。对于简单的任务,只要使用编程语言提供的基本数据类型就足够了;而对于较复杂的任务,就需要使用基本的数据类型来构造出更加复杂的数据结构。
5.1 表、栈和队列
表、栈和队列都是基本的线性数据结构。由于Python具有设计良好的数据结构,因此其列表可以当作表来使用,而且列表的某些特性和链表相似,使得在Python中表的实现非常简单。对于栈和队列,则可以自己编写脚本来构建。
5.1.1 表
表是最基本的数据结构,在Python中可以使用列表来创建表。列表本身就提供了插入和删除操作,可以充当链表使用,在靠近列表开始位置插入新元素时,执行这次插入操作的时间复杂度与列表的长度相当。
还有双向链表,如下图所示。双向链表中不仅保存了指向下一个元素地址的指针,还保存了指向其上一个元素地址的指针。相对于单向链表,双向链表需要占用更多的储存空间,但使用双向链表可以完成正序和倒序扫描链表。
5.1.2 栈
栈可以看作在同一位置上进行插入和删除的表,这个位置一般称为栈顶。栈的基本操作是进栈和出栈。栈可以看作容器,如图所示,先入栈的数据保存在容器底部,后入栈的数据保存在容器顶部。出栈的时候,后入栈的数据先出,而先入栈的数据后出,因此栈有一个特性叫做后进先出(LIFO)。
在Python中,仍然可以使用列表来储存栈数据。通过创建一个栈类,即可实现对栈的操作。例如,进栈PUSH方法,出栈POP方法,编写检查栈是否为满栈或者空栈的方法,等等。下面是栈的相关实现:
初始化
# 类的初始化
class Stack(object):
def __init__(self):
self.items = []
补充说明:
这段代码定义了一个名为 Stack 的 Python 类,它继承自 object 类。类的初始化(也称为构造函数)是在创建 Stack 对象时自动调用的方法,用于设置类的初始状态。
以下是代码的详细解释:
- class Stack(object): 这是类定义的开始,Stack 是类名,object 是父类,这里是所有类的默认基类,提供了很多内置方法和属性。
- def __init__(self): __init__ 是 Python 中的特殊方法,也称为构造方法,当一个新的 Stack 实例被创建时,这个方法会被自动调用。self 是指向当前对象的引用,是所有实例方法的隐含参数。
- self.items = []: 这行代码定义了一个名为 items 的私有属性(在 Python 中,没有严格的私有属性概念,但用下划线开头通常表示不打算从类的外部直接访问),它是一个空列表,用来存储栈(Stack,一种数据结构)中的元素。每当创建一个新的 Stack 对象时,它将自动分配一个空的列表给 items 属性。
判空
初始化完之后看下其他的操作,一般来说都会有判空的操作,因此需要定义一个isEmpty的方法。原理也很简单,当items长度是0 的时候就是空的,return True即可。否则返回False。
def isEmpty(self):
return len(self.items) == 0
入栈
进来的数据是要加到items里面的,items是一个list,可以用append方法加入,append是把元素加到list的末尾,因此可以用items的尾部元素当做栈顶。当然,头部那就是底部了。这里是进行操作,可以没有返回值,由于是加入元素,因此需要有一个参数item,将要加入的元素带入item即可。
# 入栈
def push(self, item):
self.items.append(item)
出栈
依旧是对栈顶元素操作,将栈顶元素从items当中剔除即可,很容易想到list可以用pop()
# 删除栈顶元素(出栈)
def remove(self):
self.items.pop()
size
如若想知道大小,那么可以定义一个size方法,直接返回现在items的长度即可,len(self.items) 表示计算 items 列表或集合的元素数量。通常情况下,这样的方法用于获取容器类中元素的个数,如列表、元组或字典等。
def size(self):
return len(self.items)
遍历
此外还可以设计一个遍历的方法,以便于打印输出看效果。下面travel函数这么写是为了打印更好看点。
def travel(self):
for i in self.items:
print(i, end=' ')
print('')
该代码定义了一个名为 travel 的方法,这个方法的作用是对类的 items 属性中的每个元素进行遍历,并将其打印出来,每个元素之间用空格分隔,最后在所有元素打印完毕后打印一个空行。
具体分析如下:
- for i in self.items: 这里使用了 for 循环,遍历 self 对象的 items 属性中的每一个元素,i 在每次迭代中代表 items 集合中的一个值。
- print(i, end=' '): 这一行用于打印当前迭代到的元素 i,end=' ' 表示在每个元素打印后不换行,而是直接添加一个空格。
- print(''): 循环结束后,添加一个空行,使得输出结果更清晰,每个 items 对象之间有一个空行间隔。
取栈顶元素
如若是在C/C++里面,有可能会考虑栈满的情况,但是在Python中list自己会扩张,用不着担心满的情况。在C里面,items用的一般是规定大小的数组,因此会有判满的操作。有的可能还会有取栈顶元素的操作。那可以实现下。直接读取items的最后一个元素即可。最后一个元素的下标是len(items) - 1。
def get_top(self):
return self.items[len(self.items) - 1]
这个代码片段定义了一个名为 get_top 的方法,这个方法的作用是从类的 items 列表中返回最后一个元素。
具体步骤如下:
- self.items[len(self.items) - 1]:self.items 是一个列表,len(self.items) 返回列表的长度,减去1是因为列表索引从0开始,所以-1 表示最后一个元素的索引。
- return self.items[len(self.items) - 1]:这行代码表示当调用 get_top 方法时,它会直接返回 items 列表中的最后一个元素。
完整代码
class Stack(object):
def __init__(self):
self.items = []
def isEmpty(self):
return len(self.items) == 0
# 栈的特点是先进后出,操作的都是栈顶元素
# 先定义入栈的方法
# self.items是一个list,利用append方法可以将加进来的item加到列表的末尾,
# items的末尾相当于栈顶,items的头部就当做栈的底部
def push(self, item):
self.items.append(item)
# 删除栈顶元素
def remove(self):
self.items.pop()
def travel(self):
for i in self.items:
print(i, end=' ')
print('')
def get_top(self):
return self.items[len(self.items) - 1]
def size(self):
return len(self.items)
st = Stack()
print(st.isEmpty()) # 刚初始化,应该是空栈,应return True
for i in range(5):
st.push(i) # 将 0-4几个数加入,此时size应为5
st.travel() # 遍历打印出来
st.remove() # 删除栈顶元素,size应为4
st.travel()
print(st.size()) # 预期结果4
print(st.get_top()) # 之前的栈顶元素4弹出去了,现在3是老大(栈顶),应打印3
结果
5.1.3 队列
队列与栈的结构类似,如图所示,队列的出队操作是在队首元素进行的删除操作。因此,对于队列而言,先入队的元素将先出队。队列的这种特性可以称为先进先出(FIFO)。
和栈类似,在Python中同样可以使用列表来构建一个队列,并完成对队列的操作。
5.2 树和图
树不是线性的,在处理较多数据时,使用线性结构较慢,而使用树结构则可以提高处理速度。不过,相对于表、栈和队列等线性数据结构来说,树的构建较为复杂。
5.2.1 树
树(tree)是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据几个。它是由n(n>=1)个有限节点组成一个具有层次关系的集合。
树在计算机科学的许多领域中使用,包括操作系统,图形,数据库系统和计算机网络。树数据结构与他们的植物表亲有许多共同之处。树数据结构具有根,分支和叶。自然界中的树和计算机科学中的树之间的区别在于树数据结构的根在顶部,其叶在底部。树的特点如下:
- 每个节点有零个或者多个子节点(例如下图的B节点有3个子节点,而K节点有零个子节点);
- 没有父节点的节点称为称为根节点(例如下图的A节点);
- 每一个非根节点有且只有一个父节点(例如下图的B和C节点,它们的父节点为A节点);
- 除了根节点外,每个子节点可以分为多个不相交的子树(根节点在树的定义中是一个特殊的节点,因为它没有父节点,并且是整棵树的起点。当我们说“子树”时,我们通常指的是从树中的某个节点(除了根节点)及其所有后代节点构成的树。这个定义隐含了子树有一个明确的根节点,而这个根节点是原树中的一个非根节点。根节点不能“分为”多个不相交的子树,因为它本身就是整棵树的起点);
1.树的相关定义
节点:树的基本部分(例如上图的A、B、C等都为节点)。它可以有一个名称,我们称之为“键”。节点也可以有附加信息。我们将这个附加信息称为“有效载荷”。虽然有效载荷信息不是许多树算法的核心,但在利用树的应用中通常是关键的。
- 根节点:树的最顶层节点称为根节点(Root Node)。它是整棵树的起点,并且没有父节点
- 父节点:若一个节点含有子节点,则这个节点称为称为其子节点的父节点(如上图的A为B和C的父节点)
- 子节点:除了根节点外,树中的每个节点都称为子节点(Child Node)。一个节点可以有零个或多个子节点。
- 兄弟节点:具有相同父节点的节点互称为兄弟节点(例如上图的B和C节点)。
- 堂兄弟节点:父节点在同一层的节点互为堂兄弟(例如上图的D、E、F、G、H节点互为堂兄弟)
- 节点的祖先:从根到该节点所经分支上的所有节点(例如上图的A、C、G和M都是O的祖先)
- 子孙节点:以某节点为根的子树中任一节点都称为该节点的子孙节点
- 叶节点或终端节点:度为零的节点(如上图的K、J、L、O和P)
- 节点的层次:从根开始定义起,根为第一层,根的子节点为第2层,以此类推(例如上图的树的层次为5)
子树:从树中的某个节点(称为子树的根)及其所有后代节点(包括该节点的所有子节点、孙子节点等)构成的树称为该节点的子树。
节点的度 :一个节点含有的子树的个数称为该节点的度(上图的B节点有3个子树,B的度为3);
树的度:一颗树中,最大的节点的度称为树的度(上图B节点的度最大,则树的度为3);
边:树的另一个基本部分。边连接两个节点以显示它们之间存在关系。每个节点(除根之外)都恰好从另一个节点的传入连接。每个节点可以具有多个输出边。
路径:路径是由边连接节点的有序列表。
树的高度或深度:树的高度等于树中任何节点的最大层数(例如上图的树的层数/高度为5)。
森林:由m(m>=0)棵互不相交的树的集合称为森林
定义一:树由一组节点和一组连接节点的边组成。树具有以下属性:
树的一个节点被指定为根节点。
除了根节点之外,每个节点 n 通过一个其他节点 p 的边连接,其中 p 是 n 的父节点。
从根路径遍历到每个节点路径唯一。
如果树中的每个节点最多有两个子节点,我们说该树是一个二叉树。下图展示了合适定义一的。
定义二:树是空的,或者由一个根节点和零个或多个子树组成,每个子树也是一棵树。每个子树的根节点通过边连接到父树的根节点。 下图 说明了树的这种递归定义。使用树的递归定义,我们知道 下图中的树至少有四个节点,因为表示一个子树的每个三角形必须有一个根节点。 它可能有比这更多的节点,但我们不知道,除非我们更深入树。
在Python中,树的实现可以使用列表或者类的方式。使用列表的方式较为简便,但树的构建过程较为复杂。使用类的方式构建树时,首先要确定树中的节点能拥有的最大儿子数。因为每个节点所拥有的儿子数并不一定相同,因此使用类的方法将占用更大的储存空间。
下图展示了一个简单的树和相应的列表实现。
# 树的根是 myTree[0],根的左子树是 myTree[1],右子树是 myTree[2]
myTree = ['a', #root
['b', #left subtree
['d', [], []],
['e', [], []] ],
['c', #right subtree
['f', [], []],
[] ]
]
2.树的种类
无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称自由树
有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树
二叉树:每个节点最多含有两个子树的树称为二叉树
完全二叉树:对于一颗二叉树,假设其深度为 d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树,其中满二叉树的定义是所有叶节点都在最底层的完全二叉树
平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于 1 的二叉树
排序二叉树(二叉查找树(英语:Binary Search Tree),也称二叉搜索树、有序二叉树)
霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树
B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树
3.树的储存与表示
顺序储存:将数据结构存储在固定的数组中,虽然在遍历速度上有一定的优势,但是所占空间比较大,是非主流二叉树。二叉树通常以链式储存。
由于对节点的个数无法掌握,常见树的存储表示都转换成二叉树进行处理,子节点个数最多为2
4.树的应用场景
1.xml,html 等,那么编写这些东西的解析器的时候,不可避免用到树
2.路由协议就是使用了树的算法
3.mysql 数据库索引
4.文件系统的目录结构
5.所以很多经典的 AI 算法其实都是树搜索,此外机器学习中的 decision tree 也是树结构
5.2.2 二叉树
二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)
和“右子树”(right subtree)。
1.二叉树的性质
性质 1: 在二叉树的第 i 层上至多有 2^(i-1)个节点(i>0)
性质 2: 深度为 k 的二叉树至多有 2^k - 1 个节点(k>0)
性质 3: 对于任意一棵二叉树,如果其叶节点数为 N0,而度数为 2 的结点总数为 N2,则 N0=N2+1;
性质 4:具有 n 个节点的完全二叉树的深度必为 log2(n+1)
性质 5:对完全二叉树,若从上至下、从左至右编号,则编号为 i 的结点,其左孩子编号必为 2i,其右孩子编号必为 2i+1;其双亲的编号必为 i/2(i=1时为根,除外)
(1)完全二叉树——若设二叉树的高度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉
树。
2.二叉树的节点及树的创建
【示例】通过使用 Node 类中定义三个属性 ,分别为 elem 本身的值,还有 lchild 左孩子和rchild 右孩子
class Node(object):
""""节点类"""
def __init__(self,item):
self.elem = item
self.lchild = None
self.rchild = None
【示例】树的创建,创建一个树的类,并给一个 root 根节点,一开始为空,随后添加节点
class Tree(object):
"""树类"""
def __init__(self, root=None):
self.root = root
def add(self, elem):
"""为树添加节点"""
node = Node(elem)
#如果树是空的,则对根节点赋值
if self.root == None:
self.root = node
else:
queue = []
queue.append(self.root)
#对已有的节点进行层次遍历
while queue:
#弹出队列的第一个元素
cur = queue.pop(0)
if cur.lchild == None:
cur.lchild = node
return
elif cur.rchild == None:
cur.rchild = node
return
else:
#如果左右子树都不为空,加入队列继续判断
queue.append(cur.lchild)
queue.append(cur.rchild)
3.二叉树的遍历
树的遍历是树的一种重要的运算。所谓遍历是指对树中所有结点的信息的访问,即依次对树中每个结点访问一次且仅访问一次,我们把这种对所有节点的访问称为遍历(traversal)。那么树的两种重要的遍历模式是深度优先遍历和广度优先遍历,深度优先一般用递归,广度优先一般用队列。一般情况下能用递归实现的算法大部分也能用堆栈来实现。
3.1深度优先遍历
对于一颗二叉树,深度优先搜索(Depth First Search)是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。那么深度遍历有重要的三种方法。这三种方式常被用于访问树的节点,它们之间的不同在于访问每个节点的次序不同。这三种遍历分别叫做先序遍历(preorder),中序(inorder)
和后序遍历(postorder)。我们来给出它们的详细定义,然后举例看看它们的应用。
先序遍历
在先序遍历中,我们先访问根节点,然后递归使用先序遍历访问左子树,再递归使用先序遍历访问右子树。 总结下来就是根节点->左子树->右子树。
def preorder(self, root):
"""递归实现先序遍历"""
if root == None:
return
print(root.elem)
self.preorder(root.lchild)
self.preorder(root.rchild)
中序遍历
在中序遍历中,我们递归使用中序遍历访问左子树,然后访问根节点,最后再递归使用中序遍历访问右子树。总结下来就是左子树->根节点->右子树。
def inorder(self, root):
"""递归实现中序遍历"""
if root == None:
return
self.inorder(root.lchild)
print(root.elem)
self.inorder(root.rchild)
后序遍历
在后序遍历中,我们先递归使用后序遍历访问左子树和右子树,最后访问根节点。总结下来就是左子树->右子树->根节点。
def postorder(self, root):
"""递归实现后续遍历"""
if root == None:
return
self.postorder(root.lchild)
self.postorder(root.rchild)
print(root.elem)
3.2 广度优先遍历(层次遍历)
从树的 root 开始,从上到下从从左到右遍历整个树的节点。
def breadth_travel(self):
"""利用队列实现树的层次遍历"""
if self.root == None:
return
queue = []
queue.append(self.root)
while queue:
node = queue.pop(0)
print(node.elem)
if node.lchild != None:
queue.append(node.lchild)
if node.rchild != None:
queue.append(node.rchild)
4.完整的二叉树结构
使用节点 a 作为根的简单树,并将节点 b 和 c 添加为子节点。下面代码创建树并查看存储在 key,left 和 right 中的一些值。
class BinaryTree:
def __init__(self,rootObj):
self.key = rootObj
self.leftChild = None
self.rightChild = None
def insertLeft(self,newNode):
if self.leftChild == None:
self.leftChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.leftChild = self.leftChild
self.leftChild = t
def insertRight(self,newNode):
if self.rightChild == None:
self.rightChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.rightChild = self.rightChild
self.rightChild = t
def getRightChild(self):
return self.rightChild
def getLeftChild(self):
return self.leftChild
def setRootVal(self,obj):
self.key = obj
def getRootVal(self):
return self.key
r = BinaryTree('a')
print(r.getRootVal())
print(r.getLeftChild())
r.insertLeft('b')
print(r.getLeftChild())
print(r.getLeftChild().getRootVal())
r.insertRight('c')
print(r.getRightChild())
print(r.getRightChild().getRootVal())
r.getRightChild().setRootVal('hello')
print(r.getRightChild().getRootVal())
6.树的列表函数表达
下面我们用列表作为树的函数来形式化树数据结构的定义(并非定义一个二叉树类),帮助我们操纵一个标准列表。
def BinaryTree(r):
return [r, [], []]
BinaryTree 函数简单地构造一个具有根节点和两个子列表为空的列表。
6.1 插入子节点
要将左子树添加到树的根,我们需要在根列表的第二个位置插入一个新的列表。我们必须小心。如果列表已经在第二个位置有东西,我们需要跟踪它,并沿着树向下把它作为我们添加的列表的左子节点。下面代码展示了插图左子节点和右子节点的 python 代码。
def insertLeft(root,newBranch):
t = root.pop(1)
if len(t) > 1:
root.insert(1,[newBranch,t,[]])
else:
root.insert(1,[newBranch, [], []])
return root
代码解释:这是一个 Python 函数,名为 insertLeft,它接收两个参数:root(一个列表,通常表示树的节点,其中每个元素是一个子列表,子列表的第一个元素是值,剩余元素是子节点)和 newBranch(要插入的新分支或节点值)。该函数的目的是将 newBranch 插入到给定的二叉搜索树(BST)的左子树的根位置,同时保持 BST 的特性。
- 首先,使用 pop(1) 方法从 root 中移除并返回第二个元素(即左子树的根),假设树非空。pop 操作会改变 root 的结构,因为它返回并移除了指定索引处的元素。
- 接着,检查 t(即刚刚移除的左子树)是否还有其他子节点(len(t) > 1)。如果 t 不是叶子节点(即有子节点),说明需要在新的 root 结构中创建一个新的子节点列表,包含 newBranch、原来的左子节点 t 和空列表(表示没有更多的子节点)。
- 如果 t 是叶子节点(即 len(t) == 1 或 len(t) == 0),则直接在新的 root 结构中插入 newBranch 作为左子节点,因为没有其他子节点需要处理,所以添加两个空列表来表示空的右子节点和空的左子节点。
- 最后,函数返回更新后的 root 列表,即插入了 newBranch 的新树结构。
这个函数假设输入的 root 是一个符合二叉搜索树规范的嵌套列表表示形式。在实际应用中,可能会根据具体的树数据结构和实现来调整这个函数。
def insertRight(root,newBranch):
t = root.pop(2)
if len(t) > 1:
root.insert(2,[newBranch,[],t])
else:
root.insert(2,[newBranch,[],[]])
return root
-
这是一个 Python 函数,名为
insertRight
,它接受两个参数:root
和newBranch
。这个函数的目的是在二叉树(root
)的右子树中插入一个新的分支(newBranch
)。以下是代码的逐行解释:-
t = root.pop(2)
:pop(2)
方法用于从二叉树root
中删除并返回第3个元素(索引从0开始计数,因此第三个节点是根节点的右孩子)。将删除的节点赋值给变量t
。
-
if len(t) > 1:
:- 检查被删除节点
t
是否有子节点(即,如果它是一个二叉节点而不是叶子节点)。如果t
的长度大于1,说明它至少有一个子节点。
- 检查被删除节点
-
root.insert(2, [newBranch, [], t])
:- 如果
t
是一个二叉节点(长度大于1),则在root
的第3个位置(也就是原来t
的位置)插入一个新列表,该列表由3个元素组成:- 第1个元素是
newBranch
(即将插入的新分支); - 第2个元素是一个空列表,表示新分支没有左孩子;
- 第3个元素是原
t
节点(如果有子节点)。
- 第1个元素是
- 如果
-
else:
:- 否则,如果
t
只是一个单节点或叶子节点(长度不大于1),则直接在root
的第3个位置插入一个新列表,结构如下:- 新分支
newBranch
; - 空列表,表示新分支没有左孩子;
- 空列表,表示新分支也没有右孩子(因为
t
是一个叶子节点)。
- 新分支
- 否则,如果
-
return root
:- 最后,函数返回更新后的二叉树
root
。
- 最后,函数返回更新后的二叉树
总的来说,这个函数主要用于在二叉树的右子树中插入新的节点,并确保树的结构保持正确。
-
要插入一个左子节点,我们首先获得与当前左子节点对应的(可能为空的)列表。然后我们添加新的左子树,添加旧的左子数作为新子节点的左子节点。这允许我们在任何位置将新节点拼接到树中。
下面编写一些访问函数来获取和设置根节点的值,以及获取左或右子树,完成这组树形函数。
def getRootVal(root):
return root[0]
def setRootVal(root,newVal):
root[0] = newVal
def getLeftChild(root):
return root[1]
def getRightChild(root):
return root[2]
5.2.3 图(Graph)
在图的概念中,我们主要涉及以下几个基本元素:
- 节点(Vertex): 也称为顶点,表示图中的一个对象。
- 边(Edge): 表示节点之间的关系,可以是有向的或无向的。
- 权重(Weight): 与边相关联的数值,表示两个节点之间的距离、成本等。
根据边的有无方向和权重的存在与否,图可以分为无向无权图、有向无权图、无向带权图和有向带权图。
图是非线性的数据结构,它是由顶点和边组成的。如果图中的顶点是有序的,那么图是有方向的,称之为有向图,如下图所示。在图中,由顶点组成的序列称为路径。图和树相比,少了树那样明显的层次结构。
在python中,可以采用字典的方式来创建图,图中的每个元素都是字典中的键,该元素所指向的图中的其他元素组成键的值。与树一样,对于图来说,也可以对其进行遍历。除遍历以外,还可以在图中搜索从一个顶点到另一个顶点的所有路径。图中的每个顶点可以看作一座城市,路径可以看作城市与城市之间的公路。因此,通过搜索所有的路径,可以找到从一个顶点到另一个顶点的最短路径,即城市与城市之间的最短路线。
如下所示的pygraph.py脚本使用字典的方式构建如5-9所示的有向图,并搜索图中的路径。
5.3 查找与排序
查找和排序是最基本的算法,在很多脚本中都会用到。其实,在前面各章的例子中已经多次用到Python提供的查找和排序函数,用来查找字符串中的子字符串。尽管Python提供的查找和排序函数能够满足绝大多数需求,但还是有必要了解最基本的查找和排序算法,以便在有特殊需求的情况下,可以自己编写查找与排序脚本。
5.3.1 查找
基本的查找方法有顺序查找、二分查找和分块查找等。其中,顺序查找是最简单的查找方法,就是按数据排列的顺序依次查找,直到找到所要查找的数据为止。
二分查找需要首先对要查找的数据进行排序。有按大小顺序排好的9个数字,如图所示。如果要查找数字5,则首先与中间值10比较,5小于10,于是对序列的前半部分1-9进行查找。此时,中间值为5,恰好为查找的数字,查找结束。
分块查找是介于顺序查找和二分查找之间的一种查找方法。在使用分块查找方法时,首先对查找表建立一个索引表,然后进行分块查找。在建立索引表时,需要先对查找表进行分块,要求''分块有序",即块内关键字不一定有序,但分块之间有大小顺序。抽取各块中的最大关键字及其起始位置构成索引表,如下图所示:
分块查找:先查找索引表,因为索引表是有序的,查找索引表时可以使用二分查找法进行:查找完索引表以后,就确定了要查找的数据所在的分块,然后再该分块中再进行顺序查找。
5.3.2 排序
排序的方法较多,常用的有冒泡法排序、希尔排序、二叉树排序和快速排序等。其中,二叉树排序便于操作。二叉树排序的过程主要是二叉树的构建和遍历过程。例如,有一组数据3,5,7,20,43,2,15,30,则二叉树的构建过程如下:
1.首先将第一个数据3放入根节点。
2.将数据5与根节点中的数据3进行比较,由于5大于3,因此应将5放入3的右子树中。
3.将数据7与根节点中的数据3进行比较,由于7大于3,因此应将7放入3的右子树中;由于3已经有右儿子5,所以将7与5进行比较,7大于5,应将7放入5的右子树中。
4.将数据20与根节点中的数据3进行比较,由于20大于3,因此应将20放入3的右子树中;重复比较,最终将20放入7的右子树中。
5.将数据43与树中的节点值进行比较,最终将其放入20的右子树中。
6.将数据2与根节点中的数据3进行比较,由于2小于3,因此应将2放入3的左子树中。
7.同样的,对数据15和30进行处理,最终形成如图所示的二叉树
当树构建好之后,对树进行中序遍历,得到的遍历结果就是对数据从小到大进行排序的结果。如果要从大到小进行排序,则可以先从右子树开始进行中序遍历。
引用说明
《Python数据分析从入门到精通》 张啸宇 李静 编著
(Python3)数据结构——01.栈的原理及实现_python里面的栈的实现底层原理-CSDN博客