伯克利CS 61A的教学用书笔记和一些课程笔记
2.3.7 Linked Lists
链表,同样也是一种数据抽象,是一种数据结构。
four = [1, [2, [3, [4, 'empty']]]]
链表包含了一对元素,第一个元素是值,另一个元素是一个链表,最内层的4后是一个"empty" ,表示一个空链表。同样我们开始定义链表,和前面讲到的数据结构一样,首先可以定义构造器(constructor)、选择器(selectors)以及验证器(validator),先借助python的list来定义。
>>> empty = 'empty'
>>> def is_link(s):
"""s is a linked list if it is empty or a (first, rest) pair."""
return s == empty or (len(s) == 2 and is_link(s[1]))
>>> def link(first, rest):
"""Construct a linked list from its first element and the rest."""
assert is_link(rest), "rest must be a linked list."
return [first, rest]
>>> def first(s):
"""Return the first element of a linked list s."""
assert is_link(s), "first only applies to linked lists."
assert s != empty, "empty linked list has no first element."
return s[0]
>>> def rest(s):
"""Return the rest of the elements of a linked list s."""
assert is_link(s), "rest only applies to linked lists."
assert s != empty, "empty linked list has no rest."
return s[1]
我们将链表分为了两部分:first和rest,分别表示第一个元素和第二个元素(是一个链表)。测试一下。
>>> four = link(1, link(2, link(3, link(4, empty))))
>>> first(four)
1
>>> rest(four)
[2, [3, [4, 'empty']]]
要实现数据对,除了用python内置的数据类型list,我们还可以用函数来定义,参考文章Composing Programs 2.2 Data Abstraction的2.2.4 The Properties of Data。
Using the abstract data representation we have defined, we can implement the two behaviors that characterize a sequence: length and element selection. 下面我们来定义关于链表的两个函数,获取链表长度、获取链表元素。
>>> def len_link(s):
"""Return the length of linked list s."""
length = 0
while s != empty:
s, length = rest(s), length + 1
return length
>>> def getitem_link(s, i):
"""Return the element at index i of linked list s."""
while i > 0:
s, i = rest(s), i - 1
return first(s)
>>> len_link(four)
4
>>> getitem_link(four, 1)
2
由于我们可以用这些函数将链表当作一个序列来操作(我们还不能使用内置的len函数,元素下标等操作)。This example demonstrates a common pattern of computation with linked lists, where each step in an iteration operates on an increasingly shorter suffix of the original list. getitem_link和len_link函数为我们展示了对链表操作的一种常见形式,对链表的rest部分不断深入进行操作,每次操作都在原有链表的一个越来越短的rest部分上。This incremental processing to find the length and elements of a linked list does take some time to compute. 但是这种操作确实很耗时。Python's built-in sequence types are implemented in a different way that does not have a large cost for computing the length of a sequence or retrieving its elements. python内置的序列类型(比如list)的实现是用的不同的方式,那些方式在计算序列长度和获取元素的操作上不需要花费这么多的计算。详细细节不在此讨论。
Recursive manipulation
递归操作。上面实现getitem_link和len_link函数都是用的循环,我们也能用递归实现。
>>> def len_link_recursive(s):
"""Return the length of a linked list s."""
if s == empty:
return 0
return 1 + len_link_recursive(rest(s))
>>> def getitem_link_recursive(s, i):
"""Return the element at index i of linked list s."""
if i == 0:
return first(s)
return getitem_link_recursive(rest(s), i - 1)
>>> len_link_recursive(four)
4
>>> getitem_link_recursive(four, 1)
2
递归对实现另外的链表函数也有帮助。比如扩展链表。
>>> def extend_link(s, t):
"""Return a list with the elements of s followed by those of t."""
assert is_link(s) and is_link(t)
if s == empty:
return t
else:
return link(first(s), extend_link(rest(s), t))
>>> extend_link(four, four)
[1, [2, [3, [4, [1, [2, [3, [4, 'empty']]]]]]]]
对所有链表元素应用 f 函数。
>>> def apply_to_all_link(f, s):
"""Apply f to each element of s."""
assert is_link(s)
if s == empty:
return s
else:
return link(f(first(s)), apply_to_all_link(f, rest(s)))
>>> apply_to_all_link(lambda x: x*x, four)
[1, [4, [9, [16, 'empty']]]]
留下满足 f 条件的元素。
>>> def keep_if_link(f, s):
"""Return a list with elements of s for which f(e) is true."""
assert is_link(s)
if s == empty:
return s
else:
kept = keep_if_link(f, rest(s))
if f(first(s)):
return link(first(s), kept)
else:
return kept
>>> keep_if_link(lambda x: x%2 == 0, four)
[2, [4, 'empty']]
将链表元素拼接为一个字符串。
>>> def join_link(s, separator):
"""Return a string of all elements in s separated by separator."""
if s == empty:
return ""
elif rest(s) == empty:
return str(first(s))
else:
return str(first(s)) + separator + join_link(rest(s), separator)
>>> join_link(four, ", ")
'1, 2, 3, 4'
Recursive Construction
再来考虑前面的问题,详细内容见文章问题:用最大不超过m的正整数来划分n,有多少种方式?
我们以及用递归、树递归的方式来解决过这个问题。我们也可以用链表来解决这个问题,可以用到上面以及定义好的函数,核心思路是和以前一样的,将问题分为了m和m-1两部分,不断递归。In the recursive case, we construct two sublists of partitions. The first uses m, and so we prepend m to each element of the result using_m to form with_m.
>>> def partitions(n, m):
"""Return a linked list of partitions of n using parts of up to m.
Each partition is represented as a linked list.
"""
if n == 0:
return link(empty, empty) # A list containing the empty partition
elif n < 0 or m == 0:
return empty
else:
using_m = partitions(n-m, m)
with_m = apply_to_all_link(lambda s: link(m, s), using_m)
without_m = partitions(n, m-1)
return extend_link(with_m, without_m)
我们定义一个打印函数将这个链表以人更容易阅读的方式打印出来。
>>> def print_partitions(n, m):
lists = partitions(n, m)
strings = apply_to_all_link(lambda s: join_link(s, " + "), lists)
print(join_link(strings, "\n"))
>>> print_partitions(6, 4)
4 + 2
4 + 1 + 1
3 + 3
3 + 2 + 1
3 + 1 + 1 + 1
2 + 2 + 2
2 + 2 + 1 + 1
2 + 1 + 1 + 1 + 1
1 + 1 + 1 + 1 + 1 + 1