提交 6c71f601 编写于 作者: W wizardforcel

6.1

上级 c8836e66
# 链表
# Linked lists
我们已经研究了 Python 内置的数组/列表,但它们并不总是最好的列表。 有时,我们会在列表的头部或中间插入和删除内容。 如果我们使用实现为连续数组(由内存中的连续单元组成)的列表来执行此操作,我们必须移动大量单元以为新元素腾出空间或填充删除所产生的空隙。
We've studied arrays/lists that are built into Python but they are not always the best kind of list to use. Sometimes, we are inserting and deleting things from the head or middle of the list. If we do this with lists implemented as contiguous arrays (made up of contiguous cells in memory), we have to move a lot of cells around to make room for a new element or to close a hole made by a deletion.
*链接列表*抽象列表的实现允许我们有效地插入和删除任何我们想要的东西。 这种灵活性是以更多内存为代价的。
*Linked list* implementations of abstract lists allow us to efficiently insert and remove things anywhere we want. This flexibility comes at the cost of more memory.
研究链表的另一个原因是链表*节点*的概念很容易扩展,以创建分层的父子数据结构,称为*树*,然后是*图*(跟踪任何节点->节点的关系)。
The other reason to study linked lists is that the notion of a linked list *node* is easily extended to create hierarchical parent-child data structures called *trees* and then to *graphs* (that track any node->node relationship).
*TODO*:使用 graphviz 或其他东西来可视化结构。 将树的讨论移动到单独的页面。 这个讲座进行得很快,让一些学生感到困惑,花了大约 1.5 个小时都没有讲到树。 让学生将几个笔记连在一起并将其可视化。 从元组开始然后执行 graphviz。
*TODO*: use graphviz or something else to visualize the structures. move the tree discussion to a separate page. This lecture going very quickly, confusing some students, took about 1.5 hours without the trees. Have the students connect a few notes together and visualize it. Start with tuples then do graphviz.
## 链表与数组的隐喻
## Metaphor for linked lists vs arrays
想象一下,我想在课堂上点名。 由于每个人都坐在彼此旁边,即连续,我可以通过向左或向右看,从一个人指向下一个人。这就是列表的工作方式,就像连续的块一样。
Imagine that I wanted to take roll in class. Since everyone is sitting next to each other, i.e. contiguous, I can simply point from one person to the next by looking to the left or right. That's the way lists work, as contiguous chunks.
链表要求每个人不仅要记住他们的名字,还要记住他们的右边(下一个指针)。 只要我记得列表中的第一个人(头部),我可以稍后给那个人打电话并询问他们的名字。 然后我可以让他们引用下一个排队的人。 即使人们分布在整个大陆或随意重新分布他们所在的位置,这也是有效的。 这些元素不要求是连续的,因为列表中的每个节点都具有到达下一个人所需的信息。
A linked list requires everybody to not only remember their name but also who is to the right of them (a next pointer). As long as I remember the first person (the head) in the list, I can call that person later and ask for their name. Then I can ask them to refer to the next person in line. This works even if people distribute across the continent or randomly reassign where they are sitting. There is no requirement that these elements be contiguous because each node in the list has the information needed to get to the next person.
链表实现将下一个指针与每个列表值相关联。 我们称这些东西*节点*,通常是:`[value, next]``(value, next)`。 我们还维护指向列表的*头部*的指针,有时是列表的*尾部*
A linked list implementation associates a next pointer with each list value. We call these things *nodes* usually: `[value,next]` or `(value,next)`. We also keep a pointer to the *head* of the list and sometimes the *tail* of the list.
The simplest list has one element with a `next` pointer/reference that points at nothing.
最简单的列表有一个元素,`next`指针/引用不指向任何东西。
```python
users = ("parrt", None)
```
<img src="img/links1.png" style="width:200px">
Here's one with two elements:
这是一个有两个元素的链表:
```python
users = ("parrt", ("tombu", None))
```
<img src="img/links2.png" style="width:290px">
and three elements:
三个元素的:
```python
users = ("parrt", ("tombu", ("afedosov", None)))
```
<img src="img/links3.png" style="width:400px">
In practice, we'll use lists not tuples, because tuples are immutable. We want to be able to change the node `next` pointers:
在实践中,我们将使用列表而不是元组,因为元组是不可变的。 我们希望能够更改节点的`next`指针:
```python
a = ["parrt", None]
......@@ -49,14 +49,13 @@ a[1] = b # first node's next points to 2nd element
b[1] = c
```
The most basic implementation of a list is just a *head* pointer (here I'm using `users` for a specific list). Creating an empty linked list is then just a matter of saying `users=None`.
Naturally, we can store any kind of object we want for the value component, not just strings. We can store numbers or even other lists!
链表的最基本实现只是一个*头部*指针(这里我使用`users`作为特定链表)。 创建一个空链表只需要`users = None`
## Support code
当然,我们可以存储我们想要的任何类型的对象,而不仅仅是字符串。 我们可以存储数字甚至其他列表!
Let's add some support code that will make it easier to build and manipulate linked lists. It uses some object-oriented programming syntax from Python, which you can ignore if you want. Basically, I'm defining an object (a data aggregate) called `Node` that will have two fields `value` and `next`. It's much easier to access field names than `p[0]` and `p[1]` for some tuple/list `p`.
## 支持代码
让我们添加一些支持代码,以便更轻松地构建和操作链表。 它使用 Python 中的一些面向对象的编程语法,如果需要,可以忽略它。 基本上,我正在定义一个名为`Node`的对象(一个数据聚合),它将有两个字段`value``next`。对于某些元组/列表`p`,访问字段名称要比`p[0]``p[1]`容易得多。
```python
class Node:
......@@ -69,8 +68,7 @@ class Node:
self.next = next
```
With that definition, we can create lists more naturally:
有了这个定义,我们可以更自然地创建列表:
```python
a = Node("parrt")
......@@ -90,18 +88,17 @@ print(users)
'''
```
## Inserting nodes at the head
## 在头部插入节点
Ok, so let's say we create an empty list with `head=None`. To insert something, say, `'hi'` at the head of a linked list, there are two cases: an empty list and a nonempty list. An empty list is a case where `head==None`, the initial conditions of the list. A nonempty list of course will have `head` pointing at some tuple. Both cases can be handled the same way:
好的,我们假设我们用`head = None`创建一个空链表。 要在链表的头部插入一些东西,比如`hi`,有两种情况:空列表和非空列表。 空链表是`head == None`的情况,即链表的初始条件。 一个非空的链表当然会让`head`指向一些元组。 这两种情况都可以用同样的方式处理:
```python
head = Node('hi', head)
```
That makes a new node holding the new value `'hi'` and a `next` pointer pointing at the old head tuple (even if `None`). Finally, it sets the `head` pointer to point at the new tuple.
Inserting in the middle is more complicated. We need to find the node *after* which we want to insert something. Then we hook in the new node.For example, to insert something between `tombu` and `dmose`, we create a new node whose `next` pointer points at `dmose`'s node (what `tombu` points at. Then we set `tombu`'s `next` pointer to point to the new node containing `"mary"`:
这使得一个新节点持有新值`hi`和一个`next`指针,指向旧的头元组(即使是`None`)。 最后,它设置`head`指针来指向新元组。
在中间插入更复杂。 我们需要找到一个节点,在它*之后*我们要插入一些东西。 然后我们挂载新的节点。例如,要在`tombu``dmose`之间插入一些东西,我们创建一个新的节点,其`next`指针指向`dmose`的节点(`tombu`指向的东西)。然后 我们设置`tombu``next`指针来指向包含`mary`的新节点:
```python
b.next = Node("mary",b.next)
......@@ -111,15 +108,9 @@ users
```
## 遍历链表
## Walking a linked list
Assuming we have a linked list constructed, how do we walk that list? With an array, we can just access the *i*th value and move *i* along the list. To walk a linked list, we have to define a *cursor* (often called `p` or `q`), which we can think of as just a finger we move along between the nodes in a list. Here's the code pattern to walk a linked list and print out the values:
假设我们构建了一个链表,我们如何遍历这个列表呢? 使用数组,我们只需访问第 *i* 个值并沿列表移动 *i*。 要遍历链表,我们必须定义一个*光标*(通常称为`p``q`),我们可以将其视为一个手指,我们在链表中的节点之间移动。 以下是遍历链表并打印出值的代码模式:
```python
p = users
......@@ -136,21 +127,20 @@ dmose
'''
```
### A recursive version
### 递归版本
There's another way to walk data structures that uses what we call *recursion*. We are familiar with the concept from recurrence relations. In this case, note that starting at any node in the linked list, the rest of the list looks like a linked list. It's like a fractal, where no matter how much you zoom in and it still looks like a fractal.
还有另一种遍历数据结构的方法,我们称之为*递归*。 我们熟悉递归关系的概念。 在这种情况下,请注意,从链表中的任何节点开始,链表的其余部分看起来像链表。 它就像一个分形,无论你放大多少,它仍然看起来像一个分形。
A recursive function call is one that calls the function surrounding it. The most obvious one is:
递归函数调用是调用它周围函数的调用。 最明显的一个是:
```python
def f():
f()
```
But, that's not very useful because it is an infinite loop. The first thing the function `f()` does is call it self, causing a loop. We typically have a termination condition that tells it when to stop recursing.
Here's how we would recursively walk a list:
但是,这不是很有用,因为它是一个无限循环。 函数`f()`做的第一件事是调用自己,从而产生循环。 我们通常有一个终止条件,告诉它何时停止递归。
以下是我们递归遍历列表的方式:
```python
def walk(p):
......@@ -170,10 +160,9 @@ dmose
```
## Exercise
Create a function called `tostr` that returns a bracketed string like `['parrt', 'tombu', 'dmose']` given a link list `head` as an argument. Hint: Reduce this to a problem we know how to solve. Just add the elements to a regular Python list (`[]`) as you find them in the linked list; then return the string representation of that list using `str()`.
## 练习
创建一个名为`tostr`的函数,给定一个链表`head`作为参数,它返回一个括号包围的字符串,如`['parrt', 'tombu', 'dmose']`。 提示:将此问题转化为我们知道如何解决的问题。 只需将元素添加到常规 Python 列表(`[]`),就像在链表中找到它们一样; 然后使用`str()`返回该列表的字符串表示形式。
```python
def tostr(head):
......@@ -214,14 +203,7 @@ len(users)
# 5
```
We can also make a recursive version of that function:
我们还可以制作该函数的递归版本:
```python
def rlen(head):
......@@ -233,16 +215,9 @@ rlen(users)
# 5
```
## 练习
## Exercise
Implement method called `getitem(head, i)` that returns the ith node in a list `head` starting from zero. Hint: Combine a counter-accumulator loop with a search loop (look for counter hitting `i`) and the list walker pattern. Another way to think about this is that it is adding a search pattern to the previous length exercise.
实现名为`getitem(head, i)`的方法,返回列表`head`中的第i个节点,从零开始。 提示:将一个计数器累加器循环与一个搜索循环(查找等于`i`的计数器)和列表遍历模式相结合。 考虑这一点的另一种方式是,它在前一个长度练习中添加搜索模式。
```python
def getitem(head,j):
......@@ -272,9 +247,9 @@ None
```
## Delete first element
## 删除第一个元素
To delete the first node of a list, all we have to do is make the `head` point at what the first node's `next` points at, being careful to check for the empty list condition:
要删除列表的第一个节点,我们所要做的就是将`head`指向第一个节点的`next`,注意检查空列表条件:
```python
if head is not None:
......@@ -282,16 +257,15 @@ if head is not None:
```
## A taste of trees
## 树的一次尝试
Once we're comfortable with the notion of pointers/references from one node to the other, we can extend nodes to have two pointers instead of just one `next` pointer. A *binary tree* consists of nodes that have `left` and `right` child pointers; one or both of those pointers can be `None`. A tree reduces to a linked list if each tree node has at most one child.
一旦我们熟悉了从一个节点到另一个节点的指针/引用的概念,我们就可以扩展节点来拥有两个指针,而不仅仅是一个`next`指针。*二叉树*由具有`left``right`子指针的节点组成;这些指针中的一个或两个可以是`None`。 如果每个树节点最多只有一个子节点,则树会简化为链表。
Trees are very common data structure in analytics, particularly machine learning. For example, there is a form of clustering called *hierarchical clustering* that constructs trees of related element groups. Then, one of the most powerful machine learning models is called *random forest* and consists of a collection of decision trees that work together to provide a very strong predictor or classifier.
树是分析中非常常见的数据结构,尤其是机器学习。 例如,有一种称为*层次聚类*的聚类形式,它构造相关元素组的树。 然后,最强大的机器学习模型之一被称为*随机森林*,它由一组决策树组成,它们协同工作来提供非常强大的预测器或分类器。
We draw trees upside down from their normal biological perspective. The **root** of the tree is a single node and is drawn at the top of the diagram. The **leaves** of the tree are the children that themselves have no children.
Here is some support code for a tree node that you can safely ignore, except that the tree nodes (data aggregates) contain `value`, `left`, and `right` fields.
我们从正常的生物学角度颠倒树木。 树的**根**是单个节点,并在图的顶部绘制。 树的**叶子**是没有子节点的子节点。
下面是一些树节点的支持代码,除了树节点(数据聚合)包含`value``left``right`字段,你可以安全忽略它。
```python
class TNode:
......@@ -305,8 +279,7 @@ class TNode:
self.right = right
```
To test it out, we can create nodes just like linked list nodes and then hook them up as we want:
为了测试它,我们可以像链表节点一样创建节点,然后根据需要将它们连接起来:
```python
myroot = prez = TNode("Paul")
......@@ -323,29 +296,16 @@ prez
# (Paul,(Don,(Liz,None,None),(Marcelo,None,None)),None)
```
```python
from lolviz import *
treeviz(prez)
```
![svg](img/6.1_linked-list_25_0.svg)
### 树的遍历
### Walking trees
While it is most common to use a `while` loop to traverse a linked list, is most common to use recursion to traverse a tree. For example, here is how we would print out all the elements in a tree in *preorder*, meaning printing the value added note before it's children.
虽然最常见的是使用`while`循环来遍历链表,但最常见的是使用递归来遍历树。 例如,以下是我们如何*前序*打印树中的所有元素,这意味着在它的子节点之前打印它的值。
```python
def walk(t):
......@@ -363,4 +323,3 @@ Liz
Marcelo
'''
```
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册