查看原文
其他

352,数据结构-2,链表

山大王wld 数据结构和算法 2022-05-01

基础知识

链表是一种物理存储单元上非连续的一种数据结构,看名字我们就知道他是一种链式的结构,就像一群人手牵着手一样。链表有单向的,双向的,还有环形的。

1,单向链表

我们先定义一个最简单的单向链表结点类

1class Node<E> {
2    E data;
3    Node<E> next;
4
5    Node(E data, Node<E> next) {
6        this.data = data;
7        this.next = next;
8    }
9}

这个类非常简单,他只有两个变量,一个是data值,一个是指向下一个结点的指针。我们先来看一下单向的链表是什么样的,

链表头是不存储任何数据的,他只有指向下一个结点的指针,当然如果我们不需要头结点,直接拿第一个结点当做头结点也是可以的,像下面这样

单向链表的增删

链表不像数组那样,可以通过索引来获取,单向链表查找的时候必须从头开始往后一个个找,而不能从中间找,也不能从后往前找。我们来看一下链表的添加

1,添加到尾结点

添加到尾结点比较简单,我们只需要找到之前链表的尾结点,然后让他的next指针指向新的结点即可。

2,添加到中间结点

添加到中间结点,分为两步,比如我们要在ab结点之间添加新结点n,第一步让新结点n的指针指向b,然后再让a的指针指向新结点n即可。

3,删除链表的尾结点

只需要让尾结点的上一个结点的指针指向null即可。

4,删除链表的中间结点

只需要把要删除结点的前一个结点的指针指向要删除结点的下一个结点即可,最好还要把要删除结点的数据清空,并且让他的指针指向null。

2,单向环形链表

环形链表一般分为两种,一种是单向环形链表,一种是双向环形链表。我们首先来看一下单向的环形链表是什么样的

他和单向非环形链表的区就是,单向非环形链表的尾结点的指针是指向null的,而环形的是指向头结点。关于他的增删和单向非环形的差不多,只不过在尾结点的时候会有点区别,我们主要来看下这两种

1,添加到尾结点


2,删除尾结点

3,双向链表

我们来定义一个双向链表结点的类

1class Node<E> {
2    E data;
3    Node<E> next;
4    Node<E> prev;
5
6    Node(Node<E> prev, E data, Node<E> next) {
7        this.data = data;
8        this.next = next;
9        this.prev = prev;
10    }
11}

双向链表不光有指向下一个结点的指针,而且还有指向上一个结点的指针,他比单向链表多了一个指向前一个结点的指针,单向链表我们只能从前往后找,而双向链表我们不光可以从前往后找,而且还可以从后往前找。我们来看一下双向链表是什么样的。

双向链表我们可以从头到尾查找,也可以从尾到头查找,双向链表在代码中最常见的就是LinkedList了(java语言),双向链表的增删和单向链表的增删很类似,只不过双向链表不光要调整他指向的下一个结点,还要调整他指向的上一个结点。这里我们来结合图形和代码的方式来分析一下双向链表的增删。

1,添加到尾结点

我们结合着代码来看下

1void linkLast(Object e) {
2    final Node<Object> last = tail;
3    final Node<Object> newNode = new Node<>(last, e, null);
4    tail = newNode;
5    if (last == null)
6        head = newNode;
7    else
8        last.next = newNode;
9    size++;
10}

总共分为4步

2,添加到头结点

1private void linkFirst(Object e) {
2    //用临时变量firt保存head结点
3    final Node<Object> first = head;
4    //创建一个新的结点,让他的pre指针指向null,next指针指向head结点
5    final Node<Object> newNode = new Node<>(null, e, first);
6    //让head指向新的结点
7    head = newNode;
8    //如果之前的first为空,说明之前链表是空的,让head和tial同时指向新结点即可
9    if (first == null)
10        tail = newNode;
11    else//如果之前链表不为空,让之前first结点的pre指向新的结点
12        first.prev = newNode;
13    size++;
14}

添加到头结点和添加到尾结点很类似,图就不在画了,大家可以看下上面的代码。

3,添加到指定结点之前

比如我们在a结点之前添加一个指定的结点,先来看下代码

1void linkBefore(Object e, Node<Object> a) {
2    final Node<Object> pred = a.prev;
3    final Node<Object> newNode = new Node<>(pred, e, a);
4    a.prev = newNode;
5    if (pred == null)
6        head = newNode;
7    else
8        pred.next = newNode;
9    size++;
10}

假如我们在a结点之前添加一个结点,图就不在细画,简单看一下即可

4,删除链表的尾结点

1private Object unlinkLast(Node<Object> last) {
2    final Object element = last.data;
3    final Node<Object> prev = last.prev;
4    last.data = null;
5    last.prev = null;
6    tail = prev;
7    if (prev == null)//如果只有一个结点,把尾结点删除,相当于把链表清空了
8        head = null;
9    else
10        prev.next = null;
11    size--;
12    return element;
13}

如果链表只有一个结点的话,我们把它删除,相当于直接把链表清空了,这种很好理解,就不再画。下面画一个长度大于1的链表,然后删除最后一个结点

5,删除链表的头结点

1private Object unlinkFirst(Node<Object> first) {
2    final Object element = first.data;
3    final Node<Object> next = first.next;
4    first.data = null;
5    first.next = null;
6    head = next;
7    if (next == null)
8        tail = null;
9    else
10        next.prev = null;
11    size--;
12    return element;
13}

6,删除链表的中间结点

1Object unlink(Node<Object> x) {
2    final Object element = x.data;
3    final Node<Object> next = x.next;//x的前一个结点
4    final Node<Object> prev = x.prev;//x的后一个结点
5
6    if (prev == null) {//前一个结点是空
7        head = next;
8    } else {
9        prev.next = next;
10        x.prev = null;
11    }
12
13    if (next == null) {//后一个结点是空
14        tail = prev;
15    } else {
16        next.prev = prev;
17        x.next = null;
18    }
19
20    x.data = null;
21    size--;
22    return element;
23}

4,双向环形链表

双向环形链表在代码中最常见的就是LinkedHashMap了,这个一般用于图片缓存的比较多一些,LRUCache这个类里面主要使用的就是LinkedHashMap(java语言中),通过上面的分析,如果对linkedList能理解的话,那么双向环形链表也就不难理解了,其实原理都差不多,这里就不在过多介绍,下面是双向环形链表的图。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存