循环链表与双链表

151 阅读7分钟

链表:

循环链表:

循环链表是一种特殊的链式存储结构。它的特点是表中最后一个节点的指针域指向头节点,使得整个链表形成一个环,这种结构的优点是从任何一个节点出发都可以访问到其他所有节点,增加了链表的灵活性。
一个循环链表的实例:
#include <stdio.h>
#include <stdlib.h>
struct Node {
    int data;
    struct Node* next;
};
int main() {
    int n, i, data;
    struct Node* head, * new_node, * temp;
    printf("Enter the number of nodes: ");
    scanf("%d", &n);
    // 创建第一个节点
    head = (struct Node*)malloc(sizeof(struct Node));
    if (!head) {
        printf("Memory allocation failed.");
        return 0;
    }
    printf("Enter the data for node 1: ");
    scanf("%d", &data);
    head->data = data;
    head->next = NULL;
    temp = head;
    // 创建剩余节点
    for (i = 2; i <= n; ++i) {
        new_node = (struct Node*)malloc(sizeof(struct Node));
        if (!new_node) {
            printf("Memory allocation failed.");
            return 0;
        }
        printf("Enter the data for node %d: ", i);
        scanf("%d", &data);
        new_node->data = data;
        new_node->next = NULL;
        temp->next = new_node;
        temp = new_node;
    }
    //循环连接
    new_node->next = head;
    // 输出链表中的数据元素
    printf("\nCreated Linked List is: ");
    temp = head;
    struct node* k = head;
    int l = 1;
    while (temp != k||l==1) {
        printf("%d ", temp->data);
        l = 0;
        temp = temp->next;
    }
    printf("\n");
    // 释放内存
    temp = head;
    l = 1;
    while (temp != k||l==1) {
        head = temp->next;
        free(temp);
        l = 0;
        temp = head;
    }
    return 0;
}

从上可以看出,与一般的单链表相比,多了 new_node->next = head;从而可以循环连接。

除此之外,在输出链表元素时也与一般的单链表不同,需要特殊的手段避免死循环:

 struct node* k = head;//记录头结点的位置,再次循环到头结点时,停止打印
    int l = 1;
    while (temp != k||l==1) {
        printf("%d ", temp->data);
        l = 0;
        temp = temp->next;
    }
    printf("\n");
    // 释放内存
    temp = head;
    l = 1;
    while (temp != k||l==1) {
        head = temp->next;
        free(temp);
        l = 0;
        temp = head;
    }
同样,我们也可以通过一些操作,让两个有头结点的循环链表相连接(假设它们的尾指针分别为reara,rearb):
p=reara->next;
reara->next=rearb->next->next;
q=rearb->next;
rearb->next=p;
free(q);
要注意的是,第一个链表的尾指针连接的不是第二个链表的头结点,而是之后的节点。

下面再来看看双链表:

什么是双链表:

屏幕截图 2023-10-31 195108.png

如图所示:
双链表,也被称为双向链表,是链表的一种。在双链表中,每个数据节点都有两个指针,分别指向直接后继和直接前驱,与单链表相比,双链表的主要区别在于其结构的构造有所不同,在单链表中,每个节点都有一个储存数据的部分和一个指向后继节点的指针。这意味着单链表的遍历操作必须从前节点到后节点进行然而,在双链表中,每个节点除了有储存数据的部分和一个指向后继节点的指针(next)外,还有一个指向前驱节点的指针(pre)这样,双链表就可以实现从后节点到前节点的遍历操作。这种结构使得双链表在进行某些操作时,如插入和删除节点,比单链表更加高效。因为在双链表中,可以直接找到任何节点的前驱节点,而在单链表中,则需要从头开始遍历才能找到前驱节点。当然,在换取方便的同时,这也使得双链表在内存使用上比单链表更加消耗资源,因为它需要额外的空间来存储pre指针。

双链表的使用:

双链表的大多操作都与单链表类似,只不过多了一个前驱指针。

以下是一个双链表节点的C语言定义示例:
struct dnode{
    int data;
    struct next*;
    struct pre*;
};
当然,我们也可以创建一个头结点:
struct dnode*head=(struct dnode*)malloc(sizeof(struct dnode));
head->next=NULL;
head->pre=NULL;
也可以增加一个节点,这与单链表相比,有一定的变化:
struct dnode*new=(struct dnode*)malloc(sizeof(struct dnode));
//假如在当前节点p后面添加。
new->next=p->next->next;//1
new->pre=p;//2
p->next->pre=new;//3
p->next=new;//4
注意3,4步都使用到了p->next;如果先进行了第四步,那么第三步就会出错。所以,顺序很重要,
不过我们可以将三四步这样写,就避免了顺序错误的问题:
new->next->pre=new;
new->pre->next=new;
也可删除一个节点,比如删除p节点:
p->next->pre=p->pre;
p->pre->next=p->next;
free(p);
用完之后,别忘了释放空间,可以用双链表的整表删除:
void free_dnode(struct dnode* head) {
    struct dnode* p = head;
    while (p->next) {
        struct dnode* k = p->next;
        free(p);
        p = k;
    }
    free(p);
    
}

完整的代码如下;

#include <stdio.h>
#include <stdlib.h>
struct dnode {
    int data;
    struct dnode* pre;
    struct dnode* next;
};
struct dnode* init_head(void) {
    struct dnode* head = (struct dnode*)malloc(sizeof(struct dnode));
    head->next = NULL;
    head->pre = NULL;
    return head;
}
void add_dnode(struct dnode* head, int a, int b) {
    struct dnode* new = (struct dnode*)malloc(sizeof(struct dnode));
    new->data = b;
    struct dnode* p = head;
    while (a > 0 && p->next != NULL) {
        p = p->next;
        a--;
    }
    new->pre = p;
    new->next = p->next;
    if (p->next != NULL) {
        p->next->pre = new;
    }
    p->next = new;
}
void print(struct dnode* head) {
    struct dnode* p = head->next; 
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}
void del_dnode(struct dnode* head, int place) {
    struct dnode* p = head;
    while (place > 0 && p->next != NULL) {
        p = p->next;
        place--;
    }
    if (p->next != NULL) { 
        struct dnode* to_delete = p->next;
        p->next = to_delete->next;
        if (to_delete->next != NULL) {
            to_delete->next->pre = p;
        }
        free(to_delete);
    }
}
void free_dnode(struct dnode* head) {
    struct dnode* p = head;
    while (p->next) {
        struct dnode* k = p->next;
        free(p);
        p = k;
    }
    free(p);    
}
int main() {
    struct dnode* head = init_head(); 
    int k;
    scanf("%d", &k);
    for (int i = 0; i < k; i++) {
        int a, b;
        scanf("%d%d", &a, &b); 
        add_dnode(head, a, b);
    }
    int place;
    scanf("%d", &place);
    del_dnode(head, place);
    print(head);
    free_dnode(head);
    head = NULL;//避免出现悬挂指针
    return 0;
}
我们也可以像单链表一样,用数组来模拟一个双链表:
先来看看一个实例:
#include <stdio.h>
#include <string.h>
int l[10000];
int r[10000];
int data[10000];
int idx = 2;
void init() {
    r[0] = 1; // 左头节点
    l[1] = 0; // 右尾结点
}
void add_l(int x) { // 头插
    data[idx] = x;
    l[idx] = 0;
    r[idx] = r[0];
    l[r[0]] = idx;
    r[0] = idx;
    idx++;
}
void add_r(int x) { // 尾插
    data[idx] = x;
    r[idx] = 1;
    l[idx] = l[1];
    r[l[1]] = idx;
    l[1] = idx;
    idx++;
}
void insert_r(int pos, int x) { // 右插
pos++;
data[idx] = x;
l[idx] = pos;
r[idx] = r[pos];
l[r[pos]] = idx;
r[pos] = idx;
idx++;
}
void insert_l(int pos, int x) { // 左插
    pos++;
    pos = l[pos];
    data[idx] = x;
    l[idx] = pos;
    r[idx] = r[pos];
    l[r[pos]] = idx;
    r[pos] = idx;
    idx++;
}
void del(int k) { // 删除
    k++;
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}
int main() {
    int m;
    scanf("%d", &m);
    getchar();
    init();
    while (m--) {
        char str[1000];
        gets(str);
    if (str[0] == 'L') {
        int x;
        sscanf(str, "%*s %d", &x);
        add_l(x);
    }
    else if (str[0] == 'R') {
        int x;
        sscanf(str, "%*s %d", &x);
        add_r(x);
    }
    else if (str[0] == 'D') {
        int x;
        sscanf(str, "%*s %d", &x);
        del(x);
    }
    else if (str[0] == 'I') {
        int x, pos;
        sscanf(str, "%*s %d %d", &pos, &x);
        if (str[1] == 'L') {
            insert_l(pos, x);
        }
        else if (str[1] == 'R') {
            insert_r(pos, x);
        }
    }
}
for (int i = r[0]; i != 1; i = r[i]) {
    printf("%d ", data[i]);
}
return 0;
}
数组模拟的的双链表不但有双链表的所有功能,还避免了频繁分配空间,在处理大数据时,更加快速,而相比与数组,又具备了链表可以插入元素的功能。
当然,我们也可以把它变成双向循环链表,改动如下:
void make_circular(struct dnode* head) {
    struct dnode* p = head;
    while (p->next != NULL) {
        p = p->next;
    }
    p->next = head;
    head->pre = p;
}
输出也会相应变化,以下是完整版本:
#include <stdio.h>
#include <stdlib.h>
struct dnode {
    int data;
    struct dnode* pre;
    struct dnode* next;
};
struct dnode* init_head(void) {
    struct dnode* head = (struct dnode*)malloc(sizeof(struct dnode));
    head->next = NULL;
    head->pre = NULL;
    return head;
}
void add_dnode(struct dnode* head, int a, int b) {
    struct dnode* new = (struct dnode*)malloc(sizeof(struct dnode));
    new->data = b;
    struct dnode* p = head;
    while (a > 0 && p->next != NULL) {
        p = p->next;
        a--;
    }
    new->pre = p;
    new->next = p->next;
    if (p->next != NULL) {
        p->next->pre = new;
    }
    p->next = new;
}
void print(struct dnode* head) {
    struct dnode* p = head->next; 
    while (p != NULL && p != head) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}
void del_dnode(struct dnode* head, int place) {
    struct dnode* p = head;
    while (place > 0 && p->next != NULL) {
        p = p->next;
        place--;
    }
    if (p->next != NULL) { 
        struct dnode* to_delete = p->next;
        p->next = to_delete->next;
        if (to_delete->next != NULL) {
            to_delete->next->pre = p;
        }
        free(to_delete);
    }
}
void free_dnode(struct dnode* head) {
    struct dnode* p = head;
    while (p->next && p->next != head) {
        struct dnode* k = p->next;
        free(p);
        p = k;
    }
    free(p);    
}
void make_circular(struct dnode* head) {
    struct dnode* p = head;
    while (p->next != NULL) {
        p = p->next;
    }
    p->next = head;
    head->pre = p;
}
int main() {
    struct dnode* head = init_head(); 
    int k;
    scanf("%d", &k);
    for (int i = 0; i < k; i++) {
        int a, b;
        scanf("%d%d", &a, &b); 
        add_dnode(head, a, b);
    }
    make_circular(head);
    int place;
    scanf("%d", &place);
    del_dnode(head, place);
    print(head);
    free_dnode(head);
    head = NULL;//避免出现悬挂指针
    return 0;
}

以上就是本次的分享,我们下周见!