链表和链表节点的 API
^^^^^^^^^^^^^^^^^^^^^^^^^^
表 3-1 列出了所有用于操作链表和链表节点的 API 。
----
表 3-1 链表和链表节点 API
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| 函数 | 作用 | 时间复杂度 |
+===========================+===========================================================+===============================================+
| ``listSetDupMethod`` | 将给定的函数设置为链表的节点值复制函数。 | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listGetDupMethod`` | 返回链表当前正在使用的节点值复制函数。 | 复制函数可以通过链表的 ``dup`` 属性直接获得, |
| | | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listSetFreeMethod`` | 将给定的函数设置为链表的节点值释放函数。 | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listGetFree`` | 返回链表当前正在使用的节点值释放函数。 | 释放函数可以通过链表的 ``free`` 属性直接获得,|
| | | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listSetMatchMethod`` | 将给定的函数设置为链表的节点值对比函数。 | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listGetMatchMethod`` | 返回链表当前正在使用的节点值对比函数。 | 对比函数可以通过链表的 ``match`` |
| | | 属性直接获得, |
| | | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listLength`` | 返回链表的长度(包含了多少个节点)。 | 链表长度可以通过链表的 ``len`` 属性直接获得,|
| | | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listFirst`` | 返回链表的表头节点。 | 表头节点可以通过链表的 ``head`` 属性直接获得,|
| | | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listLast`` | 返回链表的表尾节点。 | 表尾节点可以通过链表的 ``tail`` 属性直接获得,|
| | | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listPrevNode`` | 返回给定节点的前置节点。 | 前置节点可以通过节点的 ``prev`` 属性直接获得,|
| | | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listNextNode`` | 返回给定节点的后置节点。 | 后置节点可以通过节点的 ``next`` 属性直接获得,|
| | | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listNodeValue`` | 返回给定节点目前正在保存的值。 | 节点值可以通过节点的 ``value`` 属性直接获得, |
| | | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listCreate`` | 创建一个不包含任何节点的新链表。 | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listAddNodeHead`` | 将一个包含给定值的新节点添加到给定链表的表头。 | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listAddNodeTail`` | 将一个包含给定值的新节点添加到给定链表的表尾。 | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listInsertNode`` | 将一个包含给定值的新节点添加到给定节点的之前或者之后。 | :math:`O(1)` |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listSearchKey`` | 查找并返回链表中包含给定值的节点。 | :math:`O(N)` , ``N`` 为链表长度。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listIndex`` | 返回链表在给定索引上的节点。 | :math:`O(N)` , ``N`` 为链表长度。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listDelNode`` | 从链表中删除给定节点。 | :math:`O(1)` 。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listRotate`` | 将链表的表尾节点弹出,然后将被弹出的节点插入到链表的表头,| :math:`O(1)` |
| | 成为新的表头节点。 | |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listDup`` | 复制一个给定链表的副本。 | :math:`O(N)` , ``N`` 为链表长度。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
| ``listRelease`` | 释放给定链表,以及链表中的所有节点。 | :math:`O(N)` , ``N`` 为链表长度。 |
+---------------------------+-----------------------------------------------------------+-----------------------------------------------+
----
..
Redis 链表实现的函数库和其他链表函数库没什么两样,
大部分操作都是相同的,
并且使用的算法都可以在书上找到。
Redis 链表实现的 API 包括:
- 创建链表、释放链表、复制链表;
- 返回链表的各项属性;
- 添加节点到表头、添加节点到表尾、插入节点到链表;
- 在链表中查找包含给定值的节点;
- 按索引返回链表中的节点;
- 删除链表中的节点;
本节以下内容将对链表实现的所有 API 进行介绍。
读取宏和设置宏
""""""""""""""""""
链表实现定义了一些宏,
用于读取或者修改链表成员和链表节点成员。
以下三个宏分别用于读取链表的 ``len`` 、 ``head`` 和 ``tail`` 三个成员的值:
::
// 返回给定链表所包含的节点数量
#define listLength(l) ((l)->len)
// 返回给定链表的表头节点
#define listFirst(l) ((l)->head)
// 返回给定链表的表尾节点
#define listLast(l) ((l)->tail)
对于带有三个节点 ``x`` 、 ``y`` 和 ``z`` 的链表 ``l`` 来说:
.. graphviz::
digraph {
rankdir = LR;
//
subgraph cluster_pointers {
style = invisible;
//
node [shape = plaintext];
edge [style = invis];
//
x -> y;
y -> z;
}
//
subgraph cluster_ll {
style = invisible;
node [shape = record];
//
list [label = "list |
head | tail | len \n 3 | ... "];
x_node [label = " listNode | value \n ..."];
y_node [label = " listNode | value \n ..."];
z_node [label = " listNode | value \n ..."];
//
list:head -> x_node;
list:tail -> z_node;
x_node -> y_node;
y_node -> x_node;
y_node -> z_node;
z_node -> y_node;
}
//
x -> x_node;
y -> y_node;
z -> z_node;
}
执行调用 ``listLength(l);`` 将返回数字 ``3`` 。
执行调用 ``listFirst(l);`` 将返回节点 ``x`` 。
执行调用 ``listLast(l);`` 将返回节点 ``z`` 。
以下是链表类型特定函数的读取宏和设置宏,
它们用于读取或者设置链表的 ``dup`` 、 ``free`` 和 ``match`` 三个成员的值:
::
// 将链表 l 的值复制函数设置为 m
#define listSetDupMethod(l,m) ((l)->dup = (m))
// 返回给定链表的值复制函数
#define listGetDupMethod(l) ((l)->dup)
// 将链表 l 的值释放函数设置为 m
#define listSetFreeMethod(l,m) ((l)->free = (m))
// 返回给定链表的值释放函数
#define listGetFree(l) ((l)->free)
// 将链表的对比函数设置为 m
#define listSetMatchMethod(l,m) ((l)->match = (m))
// 返回给定链表的值对比函数
#define listGetMatchMethod(l) ((l)->match)
如果程序执行了以下代码:
::
list *l = listCreate();
listSetDupMethod(l, someDupMethod);
listSetFreeMethod(l, someFreeMethod);
listSetMatchMethod(l, someMatchMethod);
那么执行调用 ``listGetDupMethod(l);`` 将返回 ``someDupMethod`` 。
执行调用 ``listGetFree(l);`` 将返回 ``someFreeMethod`` 。
执行调用 ``listGetMatchMethod(l);`` 将返回 ``someMatchMethod`` 。
以下是链表节点的成员读取宏,
它们分别返回节点的 ``prev`` 、 ``next`` 和 ``value`` 三个成员的值:
::
// 返回给定节点的前置节点
#define listPrevNode(n) ((n)->prev)
// 返回给定节点的后置节点
#define listNextNode(n) ((n)->next)
// 返回给定节点的值
#define listNodeValue(n) ((n)->value)
对于以下三个链表节点 ``x`` 、 ``y`` 和 ``z`` 来说:
.. graphviz::
digraph {
rankdir = LR;
//
subgraph cluster_pointers {
style = invisible;
//
node [shape = plaintext];
edge [style = invis];
//
x -> y;
y -> z;
}
//
subgraph cluster_ll {
style = invisible;
node [shape = record];
//
x_node [label = " listNode | value \n ..."];
y_node [label = " listNode | value \n value_of_y"];
z_node [label = " listNode | value \n ..."];
//
x_node -> y_node;
y_node -> x_node;
y_node -> z_node;
z_node -> y_node;
}
//
x -> x_node;
y -> y_node;
z -> z_node;
}
执行调用 ``listPrevNode(y);`` 将返回节点 ``x`` 。
执行调用 ``listNextNode(y);`` 将返回节点 ``z`` 。
执行调用 ``listNodeValue(y);`` 将返回节点 ``y`` 的值 ``value_of_y`` 。
以上提到的所有宏的复杂度都是 :math:`O(1)` 。
创建链表
"""""""""""""
``listCreate`` 函数用于创建并返回一个新的链表:
::
list *listCreate(void);
以下是新创建链表的状态:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 0 | dup | free | match "];
//
node [shape = plaintext];
head_null [label = "NULL"];
tail_null [label = "NULL"];
dup_null [label = "NULL"];
free_null [label = "NULL"];
match_null [label = "NULL"];
//
list:head -> head_null;
list:tail -> tail_null;
list:dup -> dup_null;
list:free -> free_null;
list:match -> match_null;
}
除了 ``len`` 成员是 ``0`` 之外,
其他成员的值都为 ``NULL`` 。
因为调用 ``listCreate`` 需要为一个 ``list`` 结构分配空间,
所以该函数的复杂度为 :math:`O(1)` 。
添加节点到表头
""""""""""""""""""""""""
``listAddNodeHead`` 函数将一个值为 ``value`` 的新节点添加到链表表头,
并将链表的 ``len`` 成员的值增一:
::
list *listAddNodeHead(list *list, void *value);
对于一个刚刚使用 ``listCreate`` 创建的新链表 ``l`` 来说:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 0 | ... "];
//
node [shape = plaintext];
head_null [label = "NULL"];
tail_null [label = "NULL"];
//
list:head -> head_null;
list:tail -> tail_null;
}
执行 ``listAddNodeHead(l, z);`` 之后,
链表将被更新为以下状态
(图中带虚线的箭头表示一个被更新过的指针):
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 1 | ... "];
z [label = " listNode | value \n z"];
//
list:head -> z [style = dashed];
list:tail -> z [style = dashed];
}
注意,
链表除了将 ``head`` 成员和 ``tail`` 成员指向了包含值 ``x`` 的节点之外,
还将 ``len`` 属性的值更新为 ``1`` 。
继续执行 ``listAddNodeHead(l, y);`` ,
链表将被更新为以下状态:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 2 | ... "];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> y [style = dashed];
list:tail -> z;
y -> z [style = dashed];
z -> y [style = dashed];
}
链表的表头节点变更为包含值 ``y`` 的节点,
并且 ``len`` 属性的值被更新为 ``2`` 。
以下是执行 ``listAddNodeHead(l, x);`` 之后,
链表的状态:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> x [style = dashed];
list:tail -> z;
x -> y [style = dashed];
y -> x [style = dashed];
y -> z;
z -> y;
}
链表的表头节点变更为包含值 ``x`` 的节点,
并且 ``len`` 属性的值被更新为 ``3`` 。
因为链表带有 ``head`` 节点,
所以程序可以在 :math:`O(1)` 复杂度内找到链表的表头节点,
并进行添加新表头节点的动作,
因此,
``listAddNodeHead`` 的复杂度为 :math:`O(1)` 。
添加节点到表尾
""""""""""""""""""""""""""
``listAddNodeTail`` 函数则将一个值为 ``value`` 的新节点添加到链表表尾,
并将链表的 ``len`` 成员的值增一:
::
list *listAddNodeTail(list *list, void *value);
对于一个刚刚使用 ``listCreate`` 创建的新链表 ``l`` 来说:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 0 | ... "];
//
node [shape = plaintext];
head_null [label = "NULL"];
tail_null [label = "NULL"];
//
list:head -> head_null;
list:tail -> tail_null;
}
执行 ``listAddNodeTail(l, x);`` 之后,
链表将被更新为以下状态:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 1 | ... "];
x [label = " listNode | value \n x"];
//
list:head -> x [style = dashed];
list:tail -> x [style = dashed];
}
注意,
链表除了将 ``head`` 成员和 ``tail`` 成员指向了包含值 ``x`` 的节点之外,
还将 ``len`` 属性的值更新为 ``1`` 。
继续执行 ``listAddNodeTail(l, y);`` ,
链表将被更新为以下状态:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 2 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
//
list:head -> x;
list:tail -> y [style = dashed];
x -> y [style = dashed];
y -> x [style = dashed];
}
链表的表尾节点变更为包含值 ``y`` 的节点,
并且 ``len`` 属性的值被更新为 ``2`` 。
以下是执行 ``listAddNodeTail(l, z);`` 之后,
链表的状态:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z [style = dashed];
x -> y;
y -> x;
y -> z [style = dashed];
z -> y [style = dashed];
}
链表的表尾节点变更为包含值 ``z`` 的节点,
并且 ``len`` 属性的值被更新为 ``3`` 。
因为链表带有 ``tail`` 节点,
所以程序可以在 :math:`O(1)` 复杂度内找到链表的表尾节点,
并进行添加新表尾节点的动作,
因此,
``listAddNodeTail`` 的复杂度为 :math:`O(1)` 。
插入新节点
"""""""""""""""
``listInsertNode`` 函数用于在给定的节点 ``old_node`` 之前或之后插入一个包含值 ``value`` 的新节点,
并将链表的 ``len`` 成员的值增一:
::
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
如果 ``after`` 参数的值为 ``0`` ,
那么新节点插入到 ``old_node`` 之前;
如果 ``after`` 参数的值为 ``1`` ,
那么新节点插入到 ``old_node`` 之后。
举个例子,
对于以下链表 ``l`` ,
以及节点指针 ``old`` 来说:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
old [shape = plaintext];
old -> x;
//
subgraph cluster_ll {
style = invisible;
//
list [label = "list | head | tail | len \n 1 | ... "];
x [label = " listNode | value \n x"];
//
list:head -> x;
list:tail -> x;
}
}
执行调用 ``listInsertNode(l, old, before, 0);`` 之后,
一个包含值 ``before`` 的新节点将插入到 ``old`` 所指向节点的前面:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
old [shape = plaintext];
//
subgraph cluster_ll {
style = invisible;
//
list [label = "list | head | tail | len \n 2 | ... "];
before [label = " listNode | value \n before"];
x [label = " listNode | value \n x"];
//
list:head -> before [style = dashed];
list:tail -> x;
x -> before [style = dashed];
before -> x [style = dashed];
}
// 必须放这里,否则链表会以表尾为表头展示
old -> x;
}
如果继续对 ``l`` 执行调用 ``listInsertNode(l, old, after, 1);`` ,
那么一个包含值 ``after`` 的新节点将插入到 ``old`` 所指向节点的后面:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
old [shape = plaintext];
//
subgraph cluster_ll {
style = invisible;
//
list [label = "list | head | tail | len \n 3 | ... "];
before [label = " listNode | value \n before"];
x [label = " listNode | value \n x"];
after [label = " listNode | value \n after"];
//
list:head -> before;
list:tail -> after [style = dashed];
x -> before;
before -> x;
x -> after [style = dashed];
after -> x [style = dashed];
}
// 必须放这里,否则链表图形会乱
old -> x;
}
在插入一个新节点时,
``listInsertNode`` 函数的所有工作,
就是修改新节点、原有节点和原有节点的毗邻节点之间的指针,
因此,
这个函数的复杂度为 :math:`O(1)` 。
查找链表
"""""""""""""""""
``listSearchKey`` 函数接受一个指针 ``key`` ,
并根据 ``key`` 提供的信息,
在链表 ``list`` 中查找和 ``key`` 相匹配的节点:
::
listNode *listSearchKey(list *list, void *key);
如果在链表中找到了匹配节点,
那么返回指向该节点的指针;
如果没找到的话,
返回 ``NULL`` 。
匹配需要用到链表的 ``match`` 函数,
如果 ``match`` 函数没有设置(值为 ``NULL`` ),
那么 ``listSearchKey`` 直接使用 ``==`` 操作来对比 ``key`` 和节点的值 ——
以下是 ``listSearchKey`` 函数的代码片段,
它说明了两种不同的对比方法:
::
// list 为链表
// node 为当前被迭代到的链表节点
if (list->match) {
// 设置了 match 函数
// 使用 match 函数来进行匹配
if (list->match(node->value, key)) {
// ...
}
} else {
// 没有设置 match 函数
// 直接用 == 操作来进行匹配
if (key == node->value) {
// ...
}
}
假设链表 ``l`` 带有三个节点,
它们分别保存了值 ``x`` 、 ``y`` 和 ``z`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z;
x -> y;
y -> x;
y -> z;
z -> y;
}
那么执行调用 ``listSearchKey(l, z);`` 时,
程序将执行以下步骤:
1. 定位到链表的第一个节点,
对比这个节点的值 ``x`` 和查找值 ``z`` ,
结果是不匹配;
2. 定位到链表的第二个节点,
对比这个节点的值 ``y`` 和查找值 ``z`` ,
结果仍然是不匹配;
3. 定位到链表的第三个节点,
对比这个节点的值 ``z`` 和查找值 ``z`` ,
结果是匹配,
返回指向链表第三个节点的指针。
下图用虚线在链表中标示了 ``listSearchKey`` 函数的查找轨迹,
虚线上的数字分别对应上面的三个步骤:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
ret [label = "返回", shape = plaintext];
//
subgraph cluster_nodes {
style = invisible;
//
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
x -> y [dir = back];
y -> x [dir = back, style = dashed, label = "2"];
y -> z [dir = back];
z -> y [dir = back, style = dashed, label = "3"];
}
//
list:head -> x [style = dashed, label = "1"];
list:tail -> z;
//
listSearchKey [label = "list\nSearch\nKey", shape = plaintext];
listSearchKey -> list:head [style = dashed];
//
ret -> z [style = dashed];
}
因为查找是从表头开始的,
而链表允许不同的节点包含同样的值,
所以,
如果链表中有多个和给定 ``key`` 相匹配的节点,
那么 ``listSearchKey`` 返回第一个匹配节点。
举个例子,
假设链表 ``l`` 带有三个节点,
其中最后两个节点保存了同样的值 ``z`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
z1 [label = " listNode | value \n z"];
z2 [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z2;
x -> z1;
z1 -> x;
z1 -> z2;
z2 -> z1;
}
那么在执行 ``listSearchKey(l, z);`` 的时候,
函数将返回指向第二个节点的指针,
因为这个节点是程序第一个发现值可以和 ``z`` 相匹配的节点:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
ret [label = "返回", shape = plaintext];
subgraph cluster_nodes {
style = invisible;
//
x [label = " listNode | value \n x"];
z1 [label = " listNode | value \n z"];
z2 [label = " listNode | value \n z"];
//
x -> z1 [dir = back];
z1 -> x [dir = back, style = dashed, label = "2"];
z1 -> z2;
z2 -> z1;
}
//
list:head -> x [style = dashed, label = "1"];
list:tail -> z2;
//
listSearchKey [label = "list\nSearch\nKey", shape = plaintext];
listSearchKey -> list:head [style = dashed];
//
ret -> z1 [style = dashed];
}
上图用带数字的虚线标示了 ``listSearchKey`` 的查找轨迹。
在最坏情况下,
``listSearchKey`` 为了查找和给定值相匹配的节点,
需要遍历链表的所有节点,
因此,
``listSearchKey`` 的复杂度为 :math:`O(N)` 。
根据索引返回节点
"""""""""""""""""""""""
``listIndex`` 函数用于返回链表在指定索引上的节点:
::
listNode *listIndex(list *list, long index);
索引参数 ``index`` 可以是正数,
也可以是负数:
- 正数索引用 ``0`` 表示表头节点,
``1`` 表示链表第二个节点,
以此类推;
- 负数索引用 ``-1`` 表示表尾节点,
``-2`` 表示链表倒数第二个节点,
以此类推;
- 当给定索引大于等于链表长度,
也即是 ``index`` 大于等于 ``list.len`` 的时候,
返回 ``NULL`` 。
在下图所示的链表 ``l`` 中,
带虚线的箭头分别标示了各个节点的正数和负数索引值:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
//
list:head -> x;
list:tail -> z;
//
subgraph cluster_nodes {
style = invisible;
//
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
x -> y;
y -> x;
y -> z;
z -> y;
}
//
subgraph cluster_pointers {
style = invisible;
edge [style = dashed];
//
zero [label = "0 \n -3", shape = plaintext];
one [label = "1 \n -2", shape = plaintext];
two [label = "2 \n -1" , shape = plaintext];
//
zero -> one -> two [style = invis];
zero -> x;
one -> y;
two -> z;
}
}
以下是一些对 ``l`` 执行 ``listIndex`` 函数的例子:
- 当执行调用 ``listIndex(l, 0);`` 或者 ``listIndex(l, -3);`` 时,
函数返回包含值 ``x`` 的节点;
- 当执行调用 ``listIndex(l, 1);`` 或者 ``listIndex(l, -2);`` 时,
函数返回包含值 ``y`` 的节点;
- 当执行调用 ``listIndex(l, 2);`` 或者 ``listIndex(l, -1);`` 时,
函数返回包含值 ``z`` 的节点。
对于一个长度为 :math:`N` 的链表,
``listIndex`` 最多需要遍历 :math:`N` 个节点,
才能找到给定索引所指定的节点,
因此,
``listIndex`` 的复杂度为 :math:`O(N)` 。
删除节点
""""""""""""""""
``listDelNode`` 函数用于删除链表 ``list`` 中的节点 ``node`` ,
并将链表的长度减一:
::
void listDelNode(list *list, listNode *node);
举个例子,
对于以下链表 ``l`` 和节点指针 ``node`` 来说:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
n [label = "node", shape = plaintext];
list [label = "list | head | tail | len \n 3 | ... "];
//
subgraph cluster_nodes {
style = invisible;
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
x -> y;
y -> x;
y -> z;
z -> y;
}
//
list:head -> x;
list:tail -> z;
// 必须放这里,否则链表图形会乱
n -> y;
}
执行调用 ``listDelNode(l, node);`` 之后,
链表将被更新为以下状态:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 2 | ... "];
x [label = " listNode | value \n x"];
z [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z;
x -> z [style = dashed];
z -> x [style = dashed];
}
注意,
不仅节点 ``node`` 所指向的节点从链表中被删除了,
链表的长度也从原来的 ``3`` 变成了 ``2`` 。
有了指向删除目标节点的指针 ``node`` ,
所有删除工作都可以通过调整 ``node`` 的毗邻节点的指针来完成,
并且对链表的 ``head`` 、 ``tail`` 或者 ``len`` 的修改也可以通过简单的赋值来完成,
因此,
``listDelNode`` 的复杂度为 :math:`O(1)` 。
回转链表
""""""""""""""""""
``listRotate`` 函数用于对链表进行回转操作 ——
每次调用 ``listRotate`` ,
函数都会将链表的表尾节点弹出,
然后将被弹出的节点插入到链表的表头处,
成为新的表头节点:
::
void listRotate(list *list);
假设链表 ``l`` 带有三个节点,
分别保存了值 ``x`` 、 ``y`` 和 ``z`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z;
x -> y;
y -> x;
y -> z;
z -> y;
}
执行 ``listRotate(l);`` 之后,
包含值 ``z`` 的节点将成为链表的新表头节点:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
z [label = " listNode | value \n z"];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
//
list:head -> z [style = dashed];
list:tail -> y;
z -> x [style = dashed];
x -> z [style = dashed];
x -> y;
y -> x;
}
再次执行 ``listRotate(l);`` ,
包含值 ``y`` 的节点将成为链表的新表头节点:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = "list | head | tail | len \n 3 | ... "];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
x [label = " listNode | value \n x"];
//
list:head -> y [style = dashed];
list:tail -> x;
y -> z [style = dashed];
z -> y [style = dashed];
z -> x;
x -> z;
}
因为 ``listRotate`` 函数只需对链表的表头和表尾进行相应的指针操作,
并且所有指针操作都可以在常数时间内完成,
因此,
``listRotate`` 的复杂度为 :math:`O(1)` 。
复制链表
""""""""""""""""
``listDup`` 函数用于复制输入的链表 ``orig`` ,
并返回该链表的副本:
::
list *listDup(list *orig);
复制要用到链表设置的 ``dup`` 函数:
如果设置了 ``dup`` 函数,
那么使用 ``dup`` 函数来复制节点所保存的值;
如果没有设置 ``dup`` 函数(\ ``list.dup`` 为 ``NULL``\ ),
那么直接使用 ``=`` 赋值操作来复制节点的值。
以下代码段来自 ``listDup`` 函数,
它说明了函数复制节点值的方法:
::
// list 为输入链表
// copy 为新创建的链表副本
// node 为当前迭代到的 list 的节点
if (list->dup) {
// 如果有设置 dup 函数
// 那么使用 dup 函数来复制节点的值
value = list->dup(node->value);
} else {
// 否则,直接使用赋值操作
value = node->value;
}
给定一个链表 ``source`` ,
它的三个节点分别保存了值 ``x`` 、 ``y`` 和 ``z`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = " list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z;
x -> y;
y -> x;
y -> z;
z -> y;
//
source [shape = plaintext];
source -> list:list;
}
那么执行代码 ``list *copy = listDup(source);`` ,
将产生和 ``source`` 同样带有三个节点的链表 ``copy`` ,
并且 ``copy`` 的三个节点也同样保存了值 ``x`` 、 ``y`` 和 ``z`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = " list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z;
x -> y;
y -> x;
y -> z;
z -> y;
//
copy [shape = plaintext];
copy -> list:list;
}
对于一个长度为 :math:`N` 的链表来说,
要创建这个链表的副本,
``listDup`` 必须复制 :math:`N` 个节点,
因此,
``listDup`` 的复杂度为 :math:`O(N)` 。
释放链表
""""""""""""""""
``listRelease`` 函数用于删除整个链表的所有节点,
以及链表本身:
::
void listRelease(list *list);
对于带有三个节点的链表 ``lst`` 来说,
执行 ``listRelease(lst);`` 时,
函数将首先删除链表的第一个节点 —— 该节点保存了值 ``x`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = " list | head | tail | len \n 3 | ... "];
x [label = " listNode | value \n x"];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> x;
list:tail -> z;
x -> y;
y -> x;
y -> z;
z -> y;
//
lst [shape = plaintext];
lst -> list:list;
delete [label = "删除", shape = plaintext];
delete -> x [style = dashed];
}
接着,
函数将删除链表的第二个节点 —— 该节点保存了值 ``y`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = " list | head | tail | len \n 2 | ... "];
y [label = " listNode | value \n y"];
z [label = " listNode | value \n z"];
//
list:head -> y;
list:tail -> z;
y -> z;
z -> y;
//
lst [shape = plaintext];
lst -> list:list;
delete [label = "删除", shape = plaintext];
delete -> y [style = dashed];
}
然后删除链表的第三个节点 —— 该节点包含值 ``z`` :
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = " list | head | tail | len \n 1 | ... "];
z [label = " listNode | value \n z"];
//
list:head -> z;
list:tail -> z;
//
lst [shape = plaintext];
lst -> list:list;
delete [label = "删除", shape = plaintext];
delete -> z [style = dashed];
}
最后,
函数将代表链表本身的 ``list`` 结构也删除掉:
.. graphviz::
digraph {
rankdir = LR;
node [shape = record];
//
list [label = " list | head | tail | len \n 0 | ... "];
null_head [label = "NULL", shape = plaintext];
null_tail [label = "NULL", shape = plaintext];
//
list:head -> null_head;
list:tail -> null_tail;
//
lst [shape = plaintext];
lst -> list:list;
delete [label = "删除", shape = plaintext];
delete -> list [style = dashed];
}
至此,
``listRelease`` 函数执行结束。
对于一个长度为 :math:`N` 的链表来说,
删除操作共需要对 :math:`N` 个链表节点执行,
因此,
``listRelease`` 的复杂度为 :math:`O(N)` 。