数据结构笔记04

更多链表模型

系列文章

双链表

双链表的概念

有时需要倒序扫描链表,但是标准方法实现却非常复杂。这里提出双链表(doubly linked list)的概念,本质上就是在链表的基础上增加了一个前向指针 prev 。下图展示了双链表的原理:

双链表可以减少向前检索的时间,但代价是一个附加的链,增加了空间的需求,同时也增加了插入的开销,因为有更多指针需要定位。

双链表需要一个 isheader() 函数来判断当前结点是否为表头:

/* Return true if current node is the header position */
int isheader(node* nd) {
    return nd->prev == NULL;
}

双链表的插入

下面先看双链表的插入。如果需要向双链表中插入元素,需要完成以下几个工作:

  1. 修改前一结点的 next 元素和当前结点的 prev 元素
  2. 修改当前结点的 next 元素和后一结点的 prev 元素

操作的图示为:

双链表的插入只需要给出前面或后面的结点作为定位即可。这里以给出前面的结点为例编写代码:

/* insert after prior, header implementation assumed */
void insert(int value, node* prior) {
    node* latter = prior->next;
    node* temp = malloc(sizeof(node));
    if (temp == NULL) {
        /* raise warnings */
        return;
    }
    temp->elem = value;
    temp->next = latter;  // adjusting between this and next
    latter->prev = temp;
    temp->prev = prior;   // adjusting between this and prev
    prior->next = temp;
}

双链表的删除

双链表在删除时,需要调整的指针个数比单链表更多,但是双链表的删除操作比单链表简单,因为前驱元素的信息是现成的。

下图展示了双链表的删除操作:

可以正序扫描双链表以找出所需元素,也可以倒序扫描。这里给出了一种倒序扫描的删除函数:

/* delete right first occurence of element in linked list */
void rdelete(int value, node* last) {
    node* tmp = last;
    while (tmp && tmp->elem != value)
        tmp = tmp->prev;
    if (tmp && !isheader(tmp)) {
        tmp->prev->next = tmp->next;
        tmp->next->prev = tmp->prev;
        free(tmp);
    }
}

从双链表的删除中,就可以明白双链表的 find 和 rfind 如何操作。

循环链表

循环链表的概念

让链表的最后一个元素指向第一个元素或表头是一种实用的做法,它可以有表头也可以没有表头,并且也可以是双向链表。

下图展示了一个有表头的单链循环链表:

下图展示了一个没有表头的双链循环链表:

下面以带有表头的双向循环链表为例,介绍如何创建循环链表。

循环链表的结点就是于双链表的结点,但是循环链表还需要在第一个和最后一个元素间创建结点,因此比双链表的创建多了一步:

/* Create double-linked circular list */
node* init(node* header, int length) {
    header = (node*)malloc(sizeof(node));
    header->prev = NULL;
    header->next = NULL;
    header->elem = 0;
    node* list = header;
    for (int i = 0; i < length; i++) {
        node* body = (node*)malloc(sizeof(node));
        body->prev = NULL;
        body->next = NULL;
        body->elem = 0;
        list->next = body;
        body->prev = list;
        list = list->next;
    }
    /* doubly linking between first and last nodes */
    list->next = header;
    header->prev = list;
    return header;
}

给定表头元素和循环链表长度,可以轻松遍历整个循环链表。但是,循环链表对首末结点的测试被影响了。