数据结构与算法
基于数组的序列
张晓平
武汉大学数学与统计学院
Table of contents
1. Python 的序列类型
2. 低级数组 (low-level’s array)
3. 动态数组 (Dynamic Arrays)
4. Python 序列类型的效率
5. 基于数组的序列:应用
Python 的序列类型
Python 的序列类型
本章主要考察 Python 的序列 (Sequence) 类,即内置的列表 (list),元 组 (tuple) 和字符串 (str) 类.
相同点
• 支持索引来访问元素,如seq[k]
• 使用数组 (array)来表示序列.
不同点
• 抽象
• 在 Python 内部表示的方式
这些类在 Python 程序中的使用非常广泛. 基于这些类我们开发更复杂 的数据结构,因此必须弄清楚它们的公共行为和内部工作机制。
Python 的序列类型
本章主要考察 Python 的序列 (Sequence) 类,即内置的列表 (list),元 组 (tuple) 和字符串 (str) 类.
相同点
• 支持索引来访问元素,如seq[k]
• 使用数组 (array)来表示序列.
不同点
• 抽象
• 在 Python 内部表示的方式
这些类在 Python 程序中的使用非常广泛. 基于这些类我们开发更复杂 的数据结构,因此必须弄清楚它们的公共行为和内部工作机制。
2
Python 的序列类型
本章主要考察 Python 的序列 (Sequence) 类,即内置的列表 (list),元 组 (tuple) 和字符串 (str) 类.
相同点
• 支持索引来访问元素,如seq[k]
• 使用数组 (array)来表示序列.
不同点
• 抽象
• 在 Python 内部表示的方式
这些类在 Python 程序中的使用非常广泛. 基于这些类我们开发更复杂 的数据结构,因此必须弄清楚它们的公共行为和内部工作机制。
Python 的序列类型
本章主要考察 Python 的序列 (Sequence) 类,即内置的列表 (list),元 组 (tuple) 和字符串 (str) 类.
相同点
• 支持索引来访问元素,如seq[k]
• 使用数组 (array)来表示序列.
不同点
• 抽象
• 在 Python 内部表示的方式
这些类在 Python 程序中的使用非常广泛. 基于这些类我们开发更复杂 的数据结构,因此必须弄清楚它们的公共行为和内部工作机制。
2
低级数组 (low-level’s array)
低级数组 (low-level’s array)
内存地址
内存的每个字节都与作为其地址的唯一数字相关联.
内存地址通常与内存系统的物理布局相协调,故常以连续方式来 表示这些数字。
3
低级数组 (low-level’s array)
定义 : 数组
可以将一组相关变量一个接一个地存储在内存的连续区域中,这 样的表示称为数组.
低级数组 (low-level’s array)
例
文本字符串以字符的有序序列存储。如下图中的’SAMPLE’,将其 描述为一个 6 个字符的数组,尽管它需要 12 个字节的内存。
将数组中的每个位置称为一个单元格 (cell),并使用整数索引来描 述其在数组中的位置,单元格的编号为 0,1,2,···。
5
低级数组 (low-level’s array)
注
数组的每个单元格必须使用相同的字节数。这一要求允许根据数 组的索引在恒定时间内访问数组的任意单元格。
例
给定数组开始的内存地址 start、每个元素的字节数 cellsize 以及 数组中的一个给定索引 index,则给定索引的内存地址为start+
cellsize
∗index.
低级数组 (low-level’s array)
引用数组 (Referential Arrays)
引用数组 (Referential Arrays)
再次强调,Python 数组的每个单元格必须使用相同的字节数.
问题
给定一个列表
[ ’ R e n e ’ , ’ J o s e p h ’ , ’ J a n e t ’ , ’ J o n a s ’ , ’ H e l e n ’ , ’ V i r g i n i a ’ ]
如何在数组中表示这样的列表呢?
引用数组 (Referential Arrays)
再次强调,Python 数组的每个单元格必须使用相同的字节数.
问题
给定一个列表
[ ’ R e n e ’ , ’ J o s e p h ’ , ’ J a n e t ’ , ’ J o n a s ’ , ’ H e l e n ’ , ’ V i r g i n i a ’ ]
如何在数组中表示这样的列表呢?
7
引用数组 (Referential Arrays)
方法 1:为每个单元格保留足够的空间来存储最长字符串,但这非常浪 费。
方法 2:Python 使用对象引用数组的内部存储机制表示列表或元组实 例。在最底层,存储的是序列元素所在的连续内存地址序列。
引用数组 (Referential Arrays)
方法 1:为每个单元格保留足够的空间来存储最长字符串,但这非常浪 费。
方法 2:Python 使用对象引用数组的内部存储机制表示列表或元组实 例。在最底层,存储的是序列元素所在的连续内存地址序列。
8
引用数组 (Referential Arrays)
尽管各元素的相对大小可能不同,但用于存储每个元素内存地址的位 数 (bits) 是固定的。通过这种方式,Python 可以基于其索引支持对列 表或元组元素的恒定时间访问。
引用数组 (Referential Arrays)
例
计算列表的一个切片时,返回的是一个新的列表实例,但这个新 列表引用了原始列表中的相同元素。
10
引用数组 (Referential Arrays)
修改切片中某元素的值,只会改变该单元格的引用,其他单元格仍然 引用原始列表中的元素。
引用数组 (Referential Arrays)
d a t a = [0] * 8
生成一个长度为 8 的列表,且所有这 8 个元素的值皆为 0。本质上,
列表中的 8 个单元格都引用同一个对象。
12
引用数组 (Referential Arrays)
d a t a [2] += 1
索引为 2 的单元格的引用发生了变化,其余单元格的引用不受影响.
引用数组 (Referential Arrays)
> > > p r i m e s = [2 ,3 ,5 ,7 ,11 ,13 ,17 ,19]
> > > e x t r a s = [23 ,29 ,31]
> > > p r i m e s . e x t e n d ( e x t r a s )
extend 命令用于将一个列表中的所有元素添加到另一个列表的末尾。
扩展列表不接收这些元素的副本,而是接收对这些元素的引用。
14
引用数组 (Referential Arrays)
> > > p r i m e s = [2 ,3 ,5 ,7 ,11 ,13 ,17 ,19]
> > > e x t r a s = [23 ,29 ,31]
> > > p r i m e s . e x t e n d ( e x t r a s )
extend 命令用于将一个列表中的所有元素添加到另一个列表的末尾。
扩展列表不接收这些元素的副本,而是接收对这些元素的引用。
低级数组 (low-level’s array)
Python 中的紧凑数组 (Compact Arrays in Python)
Python 中的紧凑数组 (Compact Arrays in Python)
字符串使用字符数组表示,而非引用数组。
这种更直接的表示形式被称为紧凑数组,因为数组存储的是原始数据 (对于字符串就是字符)。
Python 中的紧凑数组 (Compact Arrays in Python)
在计算性能方面,相比于引用结构,紧凑数组有如下几个优点:
• 总的内存使用率会低得多;
• 原始数据连续存储在内存中。
16
Python 中的紧凑数组 (Compact Arrays in Python)
紧凑数组的主要支持位于名为array的模块中。该模块定义了一个类,
也称为array,为原始数据类型的数组提供紧凑存储。
Python 中的紧凑数组 (Compact Arrays in Python)
> > > f r o m a r r a y i m p o r t a r r a y
> > > p r i n t( a r r a y ( ’ i ’ , [1 ,2 ,3 ,4 ,5]) ) a r r a y ( ’ i ’ , [1 , 2 , 3 , 4])
> > > p r i n t( a r r a y ( ’ f ’ , [1 ,2 ,3 ,4 ,5]) ) a r r a y ( ’ f ’ , [1.0 , 2.0 , 3.0 , 4 . 0 ] )
> > > p r i n t( a r r a y ( ’ d ’ , [1 ,2 ,3 ,4 ,5]) ) a r r a y ( ’ d ’ , [1.0 , 2.0 , 3.0 , 4 . 0 ] )
18
Python 中的紧凑数组 (Compact Arrays in Python)
动态数组 (Dynamic Arrays)
动态数组 (Dynamic Arrays)
创建低级数组时,必须显式地声明该数组的精确大小,以便系统为其 存储正确分配连续的内存。
动态数组 (Dynamic Arrays)
由于系统可能会指定相邻的内存位置来存储其他数据,因此不能通过 扩展到后续单元来简单地增加数组的容量。注意,在表示 Python 元组 或 str 实例时,这个约束没有问题,因为它们是不可变对象
21
动态数组 (Dynamic Arrays)
Python 的 list 类提供了一个更有趣的抽象。虽然列表在构造时有一个 指定长度,但是类允许我们向列表中添加元素,并且对列表的总容量 没有明显的限制。为提供这种抽象,Python 依赖于一种称为“动态数 组”的算法技巧。
动态数组 (Dynamic Arrays)
列表实例维护一个底层数组,该数组的容量通常大于当前列表长度。
有了这个额外的容量,通过使用数组的下一个可用单元格,就可以很 容易将新元素附加到列表中。
23
动态数组 (Dynamic Arrays)
如果用户继续将元素追加到列表中,则预留的容量会最终耗尽。
• 在此情况下,类会从系统请求一个新的更大的数组,并初始化新 的数组,使得原数组中的元素依次存入新数组的靠前位置上.
• 然后原数组不再需要,系统将其回收.
非常像寄居蟹 (hermit crab)
动态数组 (Dynamic Arrays)
如果用户继续将元素追加到列表中,则预留的容量会最终耗尽。
• 在此情况下,类会从系统请求一个新的更大的数组,并初始化新 的数组,使得原数组中的元素依次存入新数组的靠前位置上.
• 然后原数组不再需要,系统将其回收.
非常像寄居蟹 (hermit crab)
24
动态数组 (Dynamic Arrays)
i m p o r t sys try:
n = int( sys . a r g v [ 1 ] ) e x c e p t:
n = 100 d a t a = []
for k in r a n g e( n ) : a = len( d a t a )
b = sys . g e t s i z e o f ( d a t a )
p r i n t( f ’ L e n g t h : { a :3 d }; S i z e in b y t e s : { b :4 d } ’ )
d a t a . a p p e n d ( N o n e )
动态数组 (Dynamic Arrays)
$ p y t h o n l i s t s i z e . py 6
L e n g t h : 0; S i z e in b y t e s : 64 L e n g t h : 1; S i z e in b y t e s : 96 L e n g t h : 2; S i z e in b y t e s : 96 L e n g t h : 3; S i z e in b y t e s : 96 L e n g t h : 4; S i z e in b y t e s : 96 L e n g t h : 5; S i z e in b y t e s : 128
26
动态数组 (Dynamic Arrays)
动态数组的实现
动态数组的实现
尽管 Python 的 list 类提供了动态数组的高度优化实现,但是看看它是 如何实现的仍具有指导意义。
实现动态数组的关键是提供一种方法来扩展存储列表元素的数组 A。
27
动态数组的实现
尽管 Python 的 list 类提供了动态数组的高度优化实现,但是看看它是 如何实现的仍具有指导意义。
实现动态数组的关键是提供一种方法来扩展存储列表元素的数组 A。
动态数组的实现
若底层数组已满,欲将元素追加到列表,将执行以下步骤:
• 分配具有更大容量的数组 B;
• 令 B[i ]=
A[i ]
,i
=0,··· ,n
−1,其中 n 表示当前项目数;• 令 A=
B,此后使用 B 作为支持列表的数组。
新数组的容量应该多大呢?。
一个常用的规则是新数组的容量是已经填充的现有数组的两倍。
28
动态数组的实现
若底层数组已满,欲将元素追加到列表,将执行以下步骤:
• 分配具有更大容量的数组 B;
• 令 B[i ]=
A[i ]
,i
=0,··· ,n
−1,其中 n 表示当前项目数;• 令 A=
B,此后使用 B 作为支持列表的数组。
新数组的容量应该多大呢?。
一个常用的规则是新数组的容量是已经填充的现有数组的两倍。
动态数组的实现
若底层数组已满,欲将元素追加到列表,将执行以下步骤:
• 分配具有更大容量的数组 B;
• 令 B[i ]=
A[i ]
,i
=0,··· ,n
−1,其中 n 表示当前项目数;• 令 A=
B,此后使用 B 作为支持列表的数组。
新数组的容量应该多大呢?。
一个常用的规则是新数组的容量是已经填充的现有数组的两倍。
28
动态数组的实现
动态数组的实现
f r o m c t y p e s i m p o r t p y _ o b j e c t c l a s s D y n a m i c A r r a y (o b j e c t) :
def _ _ i n i t _ _ ( s e l f ) : s e l f . _n = 0
s e l f . _ c a p a c i t y = 1
s e l f . _A = s e l f . _ m a k e _ a r r a y ( s e l f . _ c a p a c i t y ) def _ m a k e _ a r r a y ( self , c ) :
r e t u r n ( c * p y _ o b j e c t ) ()
30
动态数组的实现
def _ _ l e n _ _ ( s e l f ) : r e t u r n s e l f . _n
def _ _ g e t i t e m _ _ ( self , k ) : if not 0 <= k < s e l f . _n :
r a i s e I n d e x E r r o r ( ’ i n v a l i d i n d e x ’ ) r e t u r n s e l f . _A [ k ]
动态数组的实现
def a p p e n d ( self , obj ) :
if s e l f . _n == s e l f . _ c a p a c i t y : se l f . _ r e s i z e (2 * s e l f . _ c a p a c i t y ) s e l f . _A [ s e l f . _n ] = obj
s e l f . _n += 1
def _ r e s i z e ( self , c ) : B = s e l f . _ m a k e _ a r r a y ( c ) for k in r a n g e( s e l f . _n ) :
B [ k ] = s e l f . _A [ k ] s e l f . _A = B
s e l f . _ c a p a c i t y = c
32
动态数组的实现
def i n s e r t ( self , k , v a l u e ) : if s e l f . _n == s e l f . _ c a p a c i t y :
se l f . _ r e s i z e (2 * s e l f . _ c a p a c i t y ) for j in r a n g e( s e l f . _n , k , -1) :
se l f . _A [ j ] = s e l f . _A [ j -1]
s e l f . _A [ k ] = v a l u e s e l f . _n += 1
def r e m o v e ( self , v a l u e ) : for k in r a n g e( s e l f . _n ) :
if s e l f . _A [ k ] == v a l u e :
for j in r a n g e( k , s e l f . _n - 1) : se l f . _A [ j ] = s e l f . _A [ j +1]
se l f . _A [ s e l f . _n - 1] = N o n e se l f . _n -= 1
r e t u r n
r a i s e V a l u e E r r o r ( ’ v a l u e not f o u n d ’ ) 33
动态数组的实现
def pop ( self , k = N o n e ) : if k == N o n e :
v a l u e = s e l f . _A [ s e l f . _n -1]
se l f . _A [ s e l f . _n -1] = N o n e se l f . _n -= 1
r e t u r n v a l u e e l s e:
if not 0 <= k < s e l f . _n :
r a i s e I n d e x E r r o r ( ’ i n v a l i d i n d e x ’ ) v a l u e = s e l f . _A [ k ]
for j in r a n g e( k , s e l f . _n -1) : se l f . _A [ j ] = s e l f . _A [ j +1]
se l f . _A [ s e l f . _n -1] = N o n e se l f . _n -= 1
r e t u r n v a l u e
34
动态数组的实现
def _ _ r e p r _ _ ( s e l f ) :
r e t u r n f ’ { s e l f . _A [0: s e l f . _n ]} ’
动态数组的实现
if _ _ n a m e _ _ == ’ _ _ m a i n _ _ ’ : a = D y n a m i c A r r a y ()
a . a p p e n d ( ’ 1 ’ ) a . a p p e n d ( ’ 2 ’ ) a . i n s e r t (1 , ’ abc ’ ) a . a p p e n d ( ’ 2 ’ ) p r i n t( a ) a . r e m o v e ( ’ 2 ’ ) p r i n t( a ) a . pop (1) p r i n t( a ) a . pop () p r i n t( a )
36
动态数组的实现
[ ’ 1 ’ , ’ abc ’ , ’ 2 ’ , ’ 2 ’ ] [ ’ 1 ’ , ’ abc ’ , ’ 2 ’ ] [ ’ 1 ’ , ’ 2 ’ ]
[ ’ 1 ’ ]
Python 序列类型的效率
Python 序列类型的效率
Python 的列表和元组类
Python 序列类型的效率
38
常数操作
• len(data)
• data[j]
查找值
• count
计算计数的循环必须贯穿整个序列
• index, __contains__
一旦发现元素的索引或确定元素的索引,一旦找到了期望值的最 左边出现,则循环立即退出。
40
查找值
例
data = list(range(10000000))
• 5 in data: Best
• 9999995 in data: Middle
• -5 in data: Worst
创建新实例 (Creating New Instances)
渐近行为与结果的长度成正比。
例
• 切片data[6000000:6000008]几乎可以立即构造,因为它只 有八个元素;
• 切片data[6000000:7000000] 有 100 万个元素,因此创建 比较耗时。
42
列表的可变性
列表的可变性
最简单的可变行为是data[j] = val,并由特殊方法__setitem__支 持。该操作的时间复杂度为 O(1),因为
• 它只是用一个新值替换列表中的一个元素
• 其他元素不受影响,底层数组的大小也不会改变。
44
向列表中添加元素
• append方法
由于底层数组已调整大小,其时间复杂度为 O(n),但在摊余意义 上为 O(1).
向列表中添加元素
• insert方法
insert(k, value)将给定值插入到索引 0≤
k
≤n 处,同时将所
有后续元素向右移动以腾出空间。46
Adding Elements to a List
def i n s e r t ( self , k , v a l u e ) : if s e l f . _n == s e l f . _ c a p a c i t y :
se l f . _ r e s i z e (2 * s e l f . _ c a p a c i t y ) for j in r a n g e( s e l f . _n , k , -1) :
se l f . _A [ j ] = s e l f . _A [ j -1]
s e l f . _A [ k ] = v a l u e s e l f . _n += 1
Adding Elements to a List
• 在列表的开头插入是最昂贵的,每次操作需要线性时间;
• 中间插入需要大约一半的时间,但仍然是 O(n) 时间;
• 在末尾插入为 O(1) 时间,类似于append方法。
48
Adding Elements to a List
• 在列表的开头插入是最昂贵的,每次操作需要线性时间;
• 中间插入需要大约一半的时间,但仍然是 O(n) 时间;
• 在末尾插入为 O(1) 时间,类似于append方法。
从列表中删除元素
• pop(): 从列表中删除最后一个元素
这是最有效的,因为所有其他元素都保留在其原始位置。这实际 上是一个 O(1) 操作,不过 Python 偶尔会收缩底层动态数组以节 省内存.
49
从列表中删除元素
• pop(k): 移除位于列表中索引 k<
n 处的元素,将所有后续元素向
左移动以填补移除所产生的空白。此操作的效率为 O(n−
k),因为移位量取决于索引 k 的选择。
从列表中删除元素
• remove方法
remove(value)允许调用方指定要删除的值。
此操作的效率为 O(n)。流程的一部分从开始搜索到在索引 k 处找 到值,而其余部分从 k 到结束迭代,以便将元素向左移动。
51
从列表中删除元素
def r e m o v e ( self , v a l u e ) : for k in r a n g e( s e l f . _n ) :
if s e l f . _A [ k ] == v a l u e :
for j in r a n g e( k , s e l f . _n - 1) : se l f . _A [ j ] = s e l f . _A [ j +1]
se l f . _A [ s e l f . _n - 1] = N o n e se l f . _n -= 1
r e t u r n
r a i s e V a l u e E r r o r ( ’ v a l u e not f o u n d ’ )
扩展列表
• extend方法
将一个列表的所有元素添加到第二个列表的末尾 调用data.extend(other)产生与以下代码相同的结果:
for e l e m e n t in o t h e r : da t a . a p p e n d ( e l e m e n t )
在这两种情况下,运行时间都与另一个列表的长度成正比,并且 是摊余的,因为第一个列表的底层数组可能会调整大小以容纳其 他元素。
在实际应用中,由于渐近分析中隐藏的常数因子明显较小,因此扩展 方法优于重复调用追加。
53
扩展列表
• extend方法
将一个列表的所有元素添加到第二个列表的末尾 调用data.extend(other)产生与以下代码相同的结果:
for e l e m e n t in o t h e r : da t a . a p p e n d ( e l e m e n t )
在这两种情况下,运行时间都与另一个列表的长度成正比,并且 是摊余的,因为第一个列表的底层数组可能会调整大小以容纳其 他元素。
在实际应用中,由于渐近分析中隐藏的常数因子明显较小,因此扩展 方法优于重复调用追加。
扩展列表
• extend方法
将一个列表的所有元素添加到第二个列表的末尾 调用data.extend(other)产生与以下代码相同的结果:
for e l e m e n t in o t h e r : da t a . a p p e n d ( e l e m e n t )
在这两种情况下,运行时间都与另一个列表的长度成正比,并且 是摊余的,因为第一个列表的底层数组可能会调整大小以容纳其 他元素。
在实际应用中,由于渐近分析中隐藏的常数因子明显较小,因此扩展 方法优于重复调用追加。
53
扩展列表
扩展的高效性源于三个方面:
1. 使用合适的 Python 方法总会有一些好处,因为这些方法通常是用 编译语言(而不是解释的 python 代码)实现的。
2. 与多次调用一些单独的函数相比,调用单个函数以完成所有工作 的开销更小。
3. 扩展效率的提高源于这样一个事实:更新后的列表的结果大小可 以提前计算。如果第二个数据集相当大,则在使用重复的追加调 用时,可能会多次调整底层动态数组的大小。使用一个扩展调用,
最多将执行一个调整大小操作。
构造新列表
• 列表推导式
s q u a r e s = [ k * k for k in r a n g e(1 , n +1) ]
• 循环
s q u a r e s = []
for k in r a n g e (1 , n +1) : s q u a r e s . a p p e n d ( k * k )
列表推导式比通过重复追加来构建列表要快得多。
55
构造新列表
使用乘法运算符初始化常量值列表,如[0]*n,以生成长度为 n 且所有 值都等于零的列表。它比增量构建这样一个列表更有效。
基于数组的序列:应用
基于数组的序列:应用
记分牌 (score board)
记分牌 (score board)
问题
为保持一个高分序列,我们设计一个名为 ScoreBoard 的类. 一 个记分牌只能存储一定数量的高分. 若记分牌已满,则只有当新 的分数高于记分牌中的最低分时才能进入记分牌。记分牌的容量 取决于游戏,如被设置为 10、50、500 等,正因如此,我们会在 ScoreBoard 的构造器中指定一个容量的参数。
57
记分牌 (score board)
例
假设已有一个容量为 7 的记分牌,
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k ’ , 5 1 0 ) ]
• 有一个选手 Jill,其得分为 740,记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k
’ , 5 1 0 ) ]
• 又有一个选手 Jane,得分为 500,则她不能进入记分牌,记 分牌不更新。
• 再有一个选手 David,得分为 600,则记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ David , 6 0 0 ) , ( ’ R o s e
’ , 5 9 0 ) ]
记分牌 (score board)
例
假设已有一个容量为 7 的记分牌,
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k ’ , 5 1 0 ) ]
• 有一个选手 Jill,其得分为 740,记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k
’ , 5 1 0 ) ]
• 又有一个选手 Jane,得分为 500,则她不能进入记分牌,记 分牌不更新。
• 再有一个选手 David,得分为 600,则记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ David , 6 0 0 ) , ( ’ R o s e
’ , 5 9 0 ) ]
58
记分牌 (score board)
例
假设已有一个容量为 7 的记分牌,
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k ’ , 5 1 0 ) ]
• 有一个选手 Jill,其得分为 740,记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k
’ , 5 1 0 ) ]
• 又有一个选手 Jane,得分为 500,则她不能进入记分牌,记 分牌不更新。
• 再有一个选手 David,得分为 600,则记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ David , 6 0 0 ) , ( ’ R o s e
’ , 5 9 0 ) ]
记分牌 (score board)
例
假设已有一个容量为 7 的记分牌,
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k ’ , 5 1 0 ) ]
• 有一个选手 Jill,其得分为 740,记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J a c k
’ , 5 1 0 ) ]
• 又有一个选手 Jane,得分为 500,则她不能进入记分牌,记 分牌不更新。
• 再有一个选手 David,得分为 600,则记分牌将更新为
[( ’ M i k e ’ , 1 1 0 5 ) , ( ’ Rob ’ , 7 5 0 ) , ( ’ J i l l ’ , 7 4 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ David , 6 0 0 ) , ( ’ R o s e
’ , 5 9 0 ) ]
58
记分牌 (score board)
记分牌 (score board)
首先构造用于描述选手信息的类 GameEntry,其中包括选手姓名和分 数:
c l a s s G a m e E n t r y (o b j e c t) :
def _ _ i n i t _ _ ( self , name , s c o r e ) : s e l f . _ n a m e = n a m e
s e l f . _ s c o r e = s c o r e def g e t _ n a m e ( s e l f ) :
r e t u r n s e l f . _ n a m e def g e t _ s c o r e ( s e l f ) :
r e t u r n s e l f . _ s c o r e def _ _ s t r _ _ ( s e l f ) :
r e t u r n f ’ ({ s e l f . _ n a m e } , { s e l f . _ s c o r e }) ’
60
记分牌 (score board)
然后构造描述记分牌的 ScoreBoard 类,其中包含 add 方法,用于添加 新进选手的信息:
c l a s s S c o r e B o a r d (o b j e c t) :
def _ _ i n i t _ _ ( self , c a p a c i t y =5) : s e l f . _ b o a r d = [ N o n e ] * c a p a c i t y s e l f . _n = 0
def _ _ g e t i t e m _ _ ( self , k ) : r e t u r n s e l f . _ b o a r d [ k ] def _ _ s t r _ _ ( s e l f ) :
r e t u r n ’ \ n ’ . j o i n (str( s e l f . _ b o a r d [ j ]) for j in r a n g e( s e l f . _n ) )
记分牌 (score board)
def add ( self , e n t r y ) :
s c o r e = e n t r y . g e t _ s c o r e ()
g o o d = s e l f . _n < len( s e l f . _ b o a r d ) or s c o r e >
s e l f . _ b o a r d [ -1]. g e t _ s c o r e () if g o o d :
if s e l f . _n < len( s e l f . _ b o a r d ) : se l f . _n += 1
j = s e l f . _n -1
w h i l e j > 0 and s e l f . _ b o a r d [ j - 1 ] . g e t _ s c o r e () < s c o r e :
se l f . _ b o a r d [ j ] = s e l f . _ b o a r d [ j -1]
j -= 1
se l f . _ b o a r d [ j ] = e n t r y
62
记分牌 (score board)
记分牌 (score board)
if _ _ n a m e _ _ == ’ _ _ m a i n _ _ ’ : b o a r d = S c o r e B o a r d (5) for e in (
( ’ Rob ’ , 7 5 0 ) , ( ’ M i k e ’ ,1105) , ( ’ R o s e ’ , 5 9 0 ) , ( ’ J i l l ’ , 7 4 0 ) ,( ’ J a c k ’ , 5 1 0 ) , ( ’ A n n a ’ , 6 6 0 ) , ( ’ P a u l ’ , 7 2 0 ) , ( ’ Bob ’ , 4 0 0 ) ,
) :
ge = G a m e E n t r y ( e [0] , e [ 1 ] ) b o a r d . add ( ge )
p r i n t( f ’ A f t e r c o n s i d e r i n g { ge } , s c o r e b o a r d is : ’ )
p r i n t( b o a r d ) p r i n t()
64
基于数组的序列:应用
插入排序
插入排序
问题
对一个无序序列进行非递减排序。
算法描述
• 首先考虑数组中的第一个元素。注意一个元素本身已经排序。
• 再考虑下一个元素。如果它比第一个小,则交换它们。
• 接下来考虑第三个元素,将其向左交换,直到与前两个元素 的顺序正确为止。
• 然后考虑第四个元素,将其向左交换,直到与前三个元素的 顺序正确为止。
• 以这种方式继续使用第五个元素、第六个元素等,直到对整 个数组进行排序。
65
插入排序
问题
对一个无序序列进行非递减排序。
算法描述
• 首先考虑数组中的第一个元素。注意一个元素本身已经排序。
• 再考虑下一个元素。如果它比第一个小,则交换它们。
• 接下来考虑第三个元素,将其向左交换,直到与前两个元素 的顺序正确为止。
• 然后考虑第四个元素,将其向左交换,直到与前三个元素的 顺序正确为止。
• 以这种方式继续使用第五个元素、第六个元素等,直到对整 个数组进行排序。
Insert-Sorting
66
Insert-Sorting
Insert-Sorting
def i n s e r t i o n _ s o r t ( A ) :
for k in r a n g e(1 , len( A ) ) : cur = A [ k ]
j = k
w h i l e j > 0 and A [ j -1] > cur : A [ j ] = A [ j -1]
j -= 1 A [ j ] = cur
68
Insert-Sorting
if _ _ n a m e _ _ == ’ _ _ m a i n _ _ ’ : A = [5 , 6 , 4 , 3]
p r i n t( ’ b e f o r e sort , A = ’ , A ) i n s e r t i o n _ s o r t ( A )
p r i n t( ’ a f t e r sort , A = ’ , A )
b e f o r e sort , A = [5 , 6 , 4 , 3] a f t e r sort , A = [3 , 4 , 5 , 6]
Insert-Sorting
if _ _ n a m e _ _ == ’ _ _ m a i n _ _ ’ : A = [5 , 6 , 4 , 3]
p r i n t( ’ b e f o r e sort , A = ’ , A ) i n s e r t i o n _ s o r t ( A )
p r i n t( ’ a f t e r sort , A = ’ , A ) b e f o r e sort , A = [5 , 6 , 4 , 3]
a f t e r sort , A = [3 , 4 , 5 , 6]
69