Please enable Javascript to view the contents

链表

 ·  ☕ 3 分钟

一、数组与链表

链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续)的内
存结构(节点),按特定的顺序链接在一起的抽象数据类型。

数组和链表的区别和优缺点:
数组是一种连续存储线性结构,元素类型相同,大小相等

优点 缺点
存取速度快 事先必须知道数组的长度
插入删除元素很慢
空间通常是有限制的
需要大块连续的内存块
插入删除元素的效率很低

链表是离散存储线性结构
n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一
个后续节点,首节点没有前驱节点,尾节点没有后续节点。

链表优点 缺点
空间没有限制
插入删除元素很快 存取速度很慢

二、链表分类

2.1 单链表

单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域和 Next 指针域组成。如图:

picture

Java表达单链表结点的结构:

class LinkedList<E> {
    Node first;
    class Node {
        E elem;
        Node next;
    }
}

2.1.1 插入

插入的位置为i,插入的元素为elem。从头结点开始遍历链表,找到第i-1个结点,创建包含elem的新结点插入第i-1个结点后面。

Java表达:

void insert(int i, E elem) {
Node temp = new Node();
temp.elem = elem;
Node curr = first;
for (int j = 1; j < i; j++) {
    curr = curr.next;
}
temp.next = curr.next;
curr.next = temp;
}

链表的插入、删除与查找之前是要边界检查的,在这些操作过程中可以使用成员变量size,统计结点数,也就是链表长度,为了突出重要的步骤,不过这里省略了。后面的代码也是如此。

2.1.2 删除

删除第i个元素。从头结点开始遍历链表,找到第i-1个结点,删除其后面的结点。

Java表达:

E delete(int i) {
    Node curr = first;
    for (int i = j; i < i-1; j++) {
        curr = curr.next;
    }
    Node old = curr;
    curr = curr.next;
    old.next = curr.next;
    curr.next = null;
    return curr.elem;
}

2.1.3 查询

查询第i个元素。从头结点开始遍历链表,找到第i个结点,取出元素。

java表达:

E find(int i) {
    Node curr = first;
    for (int j = 0; j < i; j++) {
        curr = curr.next;
    }
    return curr.elem;
}

2.2 双链表

双向链表 [Double Linked List]:由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后继),内存结构由数据域、Prev 指针域和 Next 指针域组成。

picture

它的结构类似单链表,我在链表的首尾加了两个哨兵结点(pre和post),加不加哨兵结点只是出于策略的考虑和自己的喜好使用即可,不过我喜欢在双向链表使用哨兵结点,方便插入与删除的操作,且代码写起来更加简单。

class DoublyLinkedList<E> {
    Node pre;
    Node post;
    class Node {
        E elem;
        Node prev;
        Node next;
    }
    {
        pre = new Node();
        post = new Node()
        pre.next = post;
        post.prev = pre;
    }
}

2.2.1 插入

与单链表插入类似。先创建新结点,顺序查找到第i-1个结点x,再得到其后驱结点y,将新结点插入它们之间。

java表达:

void insert(int i, E elem) {
    Node temp = new Node();
    temp.elem = elem;

    Node x = pre;
    for (int j = 0; j < i; j++) {
        x = x.next;
    }
    Node y = x.next;
    x.next = temp;
    temp.prev = x;
    temp.next = y;
    y.prev = temp;
}

2.2.2 删除

顺序查找到第i个结点y,得到它的前驱结点x和后驱结点z,将y删除。

java表达:

E delete(int i) {
    Node y = pre.next;
    for (int j = 0; j < i; j++) {
        y = y.next;
    }
    Node x = y.prev;
    Node z = y.next;

    x.next = z;
    z.prev = x;
    y.next = null;
    y.prev = null;
}

2.2.3 查询

与单链表一样。

2.3 循环链表

单向循环链表 [Circular Linked List] : 由各个内存结构通过一个指针 Next 链接在一起组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成。

双向循环链表 [Double Circular Linked List] : 由各个内存结构通过指针 Next 和指针Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由数据域、Prev 指针域和 Next 指针域组成。

picture

把单向链表的尾结点的Next指针指向头结点就成了单向循环链表。双向循环链表也是一样。一般地,循环链表都是保存尾结点的指针,通过尾结点的到第一个结点,来遍历链表,方便所有位置的插入、删除。插入、删除还有查找的操作与单链表和双链表没什么区别,这里就不多余展示了。