首页 > 编程笔记 > Java笔记 阅读:2

数据结构中的双向链表(Java详解版)

我们知道,单链表每个结点的指针域只有一个,用来存放后继结点的指针,而没有关于前驱结点的信息。因此,从某个结点出发,只能顺着指针往后查找其他结点。若要查找结点的前驱,则需要从表头结点开始,顺着指针寻找,显然,使用单链表处理不够方便。同样,从单链表中删除一个结点也会遇到类似的问题。

为了克服单链表的这种缺点,可以使用双向链表。

双向链表的存储结构

在双向链表中,每个结点有两个指针域:一个指向直接前驱结点,另一个指向直接后继结点。双向链表的结点结构如下图所示。


图 1 双向链表的结点结构

在双向链表中,每个结点包括 3 个域,分别是 data 域、prior 域和 next 域:
双向链表也可分为带头结点结构和不带头结点结构,带头结点结构使某些操作更加方便。另外,双向链表也有循环结构,称为双向循环链表。

带头结点的双向循环链表如下图所示。


图 2 带头结点的双向循环链表

双向循环链表为空的情况如下图所示,判断带头结点的双循环链表为空的条件是 head.prior==head 或 head.next==head。


图 3 带头结点的双向循环链表为空的情况

在双向链表中,每个结点既有前驱结点的指针域又有后继结点的指针域,因此查找结点非常方便。若 p 是指向链表中某个结点的指针,则有 p=p.prior.next=p.next.prior。

双向链表的结点类型描述如下:
class DListNode {
    int data;
    DListNode prior, next;
    DListNode(int data) {
        this.data = data;
        prior = null;
        next = null;
    }
}

public class DLinkList {
    DListNode head;
    DLinkList() {
        head = new DListNode(0);
        head.next = head.prior = head;
    }
}

双向链表的插入操作和删除操作

对于双向链表的一些操作,如求链表的长度、查找链表的第 i 个结点等,与单链表中的算法实现基本没什么差异。但是,对于双向循环链表的插入和删除操作,因为涉及前驱结点指针和后继结点指针,所以需要修改两个方向的指针。

1) 插入操作

插入操作就是在双向循环链表的第 i 个位置插入一个元素值为 e 的结点。若插入成功,则返回 true,否则返回 false。

实现思路是:先找到第 i 个结点,用 p 指向该结点。再申请一个新结点,由 s 指向该结点,将 e 放入到据域。然后开始修改 p 和 s 指向的结点的指针域:
插入操作指针修改情况如下图所示:


图 4 双向循环链表的插入操作过程

插入操作的算法实现如下:
public boolean InsertDList(int i, int e) {
    DListNode p = head.next;
    int j = 1;
    while (p != head && j < i) {
        p = p.next;
        j++;
    }
    if (j != i) {
        System.out.println("插入位置不正确!");
        return true;
    }
    DListNode s = new DListNode(e);
    s.prior = p.prior;
    p.prior.next = s;
    s.next = p;
    p.prior = s;
    return false;
}

2) 删除操作

删除操作就是将带头结点的双向循环链表中的第 i 个结点删除。若删除成功,则返回 true,否则返回 false。

实现思路是:先找到第 i 个结点,用 p 指向该结点。然后开始修改 p 指向的结点的直接前驱结点和直接后继结点的指针域,从而将 p 与链表断开。

将 p 指向的结点与链表断开需要两步:
删除操作指针修改情况如下图所示。


图 5 双向循环链表删除操作过程

删除操作的算法实现如下:
public boolean DeleteDList(int i) {
    DListNode p = head.next;
    int j = 1;
    while (p != head && j < i) {
        p = p.next;
        j++;
    }
    if (j != i) {
        System.out.println("删除位置不正确!");
        return false;
    }
    p.prior.next = p.next;
    p.next.prior = p.prior;
    return true;
}
插入和删除操作的时间主要耗费在查找结点上,两者的时间复杂度都为 O(n)。

双向链表的插入和删除操作需要修改结点的 prior 域和 next 域,因此要注意修改结点指针域的顺序。

相关文章