链表
链表的属性
你可以使用下面的图片来可视化一个链表:
每个节点包含一个值——在本例中是一个整数——和指向下一个节点的引用(也称为指针)。最后一个节点,在本例中是2,指向a零
节点。这意味着列表已经结束了。
与其他线性数据结构相比,链表提供了一些重要的优势。不像数组,它们是动态数据结构,可在运行时调整大小。此外,插入和删除操作是高效且易于实现的。
然而,链表确实有一些缺点。不像数组,链表在查找 项。找到一个位置上的节点 ,则必须从链表的第一个节点开始搜索,沿着引用的路径 次了。此外,由于链表在前进的方向在美国,像向后遍历(从末尾开始访问每个节点,从前面结束访问每个节点)这样的操作特别麻烦。
此外,链表使用的存储空间比数组因为它们的属性是引用链表中的下一个节点。
最后,与所有值都存储在连续内存中的数组不同,链表的节点位于内存中的任意位置,可能相距很远。这意味着CPU不能像缓存数组那样有效地缓存链表的内容,从而导致性能低下。这是主要的原因环形缓冲区用于实现队列而不是在高性能应用程序的链表,中间插入和删除功能是不需要的(例如网络驱动程序)。
时空复杂性
示例Java实现
Java代码示例:
1 2 3 4 5 6 7 8 |
|
上面的代码描述了一个节点对象;这些类型的对象通常包含一个值(存储在价值
)和指针下一个
指向内存中的另一个节点。这可以表示为一个包含一个元素的列表
1 |
|
变量一个
指向一个包含值6的节点。的下一个
单元格的字段为空,这可能表示列表的结束。这可以通过以下方式表示为一个包含两个元素的列表
1 |
|
变量两个
指向值为的单元格4.的下一个
该节点的字段指向一个值为6的节点。的下一个
第二个字段指向一个空对象。所以,这个列表是
.一个3元素列表是这样创建的:
1 |
|
现在,它的 .
注意:在这个实现中,我们必须传入指向列表中下一个节点的指针。这样做是为了简单起见,但通常必须编写代码来处理列表中节点的插入和删除。
当我们需要向链表添加新项时,并不需要创建局部变量。也可以通过读取数组来构造链表。
1 2 3 4 5 6 7公共静态节点createArray(int[]数组){节点列表=零;为(int我:数组){列表=新节点(我,列表)}返回列表;}
链表的迭代和递归
可以使用递归或循环(迭代)来打印链表。下面是打印链表的迭代方法:
1 2 3 4 5 6 |
|
我们可以递归地编写相同的方法:
1 2 3 4 5 6 |
|
编写一个方法,确定给定链表的长度递归。
用我们讨论的迭代法,方法可以写成:
1 2 3 4 5 6 7公共静态无效长度(节点细胞){int数=0;而(细胞! =零){数+ =1;细胞=细胞.下一个;}}
现在编写一个方法来确定链表的长度递归。
我们可以通过考虑基本情况(当P为空时)和递归情况来解决这个问题
1 2 3 4 5 5公共静态int长度(节点细胞){如果(细胞! =零)返回1+长度(细胞.下一个);返回0;}
链表上的大多数方法都可以递归和迭代实现。但一般来说,递归解决方案在处理链表时更方便。例如,考虑以下方法,该方法将一个链表作为输入并将其反转。对于递归版本,我们再次标识基本情况(当列表为空时)和递归情况:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
双链表
到目前为止,我们只讨论了单链表,列表,其中指针从一个方向从每个单元格指向下一个单元格。然而,创建一个双指针双向的链表。
这允许我们从两个方向遍历列表,而且它还使删除等操作更容易。因为我们有指针指向我们想要删除的节点之前和之后的节点,所以我们不需要目标节点以外的任何信息。在单链表中,我们还需要指向要删除的节点的指针。
在Java中实现:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
现在遍历链表更加方便,我们可以使用上一页
和下一个
字段,分别。这种类型节点的设计允许灵活地将任何数据类型存储为链表数据。
实现一个将节点添加到链表末尾的方法:
1 2 3 4 5 5公共无效addLast(int年代){细胞p=新细胞(年代,头,头.上一页);头.上一页.下一个=p;头.上一页=p;}