数据结构——链表
一、概念
链表(Linked list)是一种常见的基础数据结构,是一种线性列表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。链表通常由一连串节点组成,每个节点包含两种域,即信息域和指针域,信息域可以存储任意的实例数据(data fields)指针域包含一或两个用来指向上一个或下一个节点的位置的链接(“links”)。
二、链表的优点和缺点
优点
1、由于链表不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多。
2、使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
缺点
1、查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
2、但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
3、由于链表需要存储数据和指针,所以空间开销大,数据密度低。
总结
链表插入节点时,效率较高。但是查询和修改的效率就很低。后面通过代码实现后会有深刻的体会。
链表可以充分利用计算机内存,因为它的内存空间不一定是连续的,可以实现内存的灵活的动态管理。
总之,常用于组织删除、检索较少,而添加、遍历较多的数据。如果与上述情形相反,应采用其他数据结构或者与其他数据结构组合使用。
三、链表的分类
链表主要的分为三类:单项链表、双向链表、循环链表
当然也有其他链表。
单向链表
链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。
链表最基本的结构是在每个节点保存数据和到下一个节点的地址,在最后一个节点保存一个特殊的结束标记,另外在一个固定的位置保存指向第一个节点的指针,有的时候也会同时储存指向最后一个节点的指针。一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。但是也可以提前把一个节点的位置另外保存起来,然后直接访问。当然如果只是访问数据就没必要了,不如在链表上储存指向实际数据的指针。这样一般是为了访问链表中的下一个或者前一个(需要储存反向的指针,见下面的双向链表)节点。
双向链表
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个连接:一个指向前一个节点,(当此“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当此“连接”为最后一个“连接”时,指向空值或者空列表)
双向链表也叫双链表。双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针。这样可以从任何一个节点访问前一个节点,当然也可以访问后一个节点,以至整个链表。一般是在需要大批量的另外储存数据在链表中的位置的时候用。
循环链表
在一个 循环链表中, 首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环链表可以被视为“无头无尾”。这种列表很利于节约数据存储缓存, 假定你在一个列表中有一个对象并且希望所有其他对象迭代在一个非特殊的排列下。
指向整个列表的指针可以被称作访问指针。
循环链表中第一个节点之前就是最后一个节点,反之亦然。循环链表的无边界使得在这样的链表上设计算法会比普通链表更加容易。对于新加入的节点应该是在第一个节点之前还是最后一个节点之后可以根据实际要求灵活处理,区别不大(详见下面实例代码)。当然,如果只会在最后插入数据(或者只会在之前),处理也是很容易的。
另外有一种模拟的循环链表,就是在访问到最后一个节点之后的时候,手工的跳转到第一个节点。访问到第一个节点之前的时候也一样。这样也可以实现循环链表的功能,在直接用循环链表比较麻烦或者可能会出现问题的时候可以用。
四、代码实现
使用java语言实现单项链表
/**
* 单向链表数据结构
* @author 14128
*/
public class MySingleLinkedList {
//这里不需要数组,不需要其他的复杂的结构,我只要记录单向链表的“头”结点
private Node first;//first中记录的是第一个结点的地址
private int total;//记录节点数
/*
* 内部类,因为这种Node结点的类型,在别的地方没有用,只在单向链表中,用于存储和表示它的结点关系。
*/
private class Node{
Object data;//因为数据可以是任意类型的对象,所以设计为Object
Node next;//因为next中记录的下一个结点的地址,因此类型是结点类型
Node(Object data, Node next){
this.data = data;
this.next = next;
}
}
/**
* 1、向链表添加元素:add()方法
* (1)将data包装成Node类型
* (2)判断链表是否为空
* 如果first = null,那么就表示当前链表为空
* (3)如果链表不是空的,那么就应该讲data添加到链表最后一个节点的后面
* 1、如何找到最后一个节点? 判断依据是节点.next == null
* @param data
*/
public void add(Object data){
//1、将data包装成Node类型
Node newNode = new Node(data,null);
//2、判断链表是否为空
if(firs