【深入探究数据结构中的链表】

深入探究数据结构中的链表

在数据结构的广袤世界里,链表作为一种重要的存储结构,有着多种类型,各有其独特之处和适用场景。接下来,让我们详细地探讨一下顺序表中常见的链表类型。

一、双向链表(Doubly Linked List)

双向链表的节点比单链表多了一个指向前一个节点的指针。

  • 优点:
    可以双向遍历,能更方便地实现某些操作,如查找前一个节点或反向遍历链表。
    在删除节点时,无需额外遍历找到前一个节点,时间复杂度为 O(1)。
  • 缺点:
    由于每个节点多了一个指针,占用的内存空间相对较大。
    实现相对复杂。

下面是一个用 C 语言实现双向链表创建、插入和删除节点的示例代码:

#include <stdio.h>
#include <stdlib.h>

// 双向链表节点结构体
typedef struct DoubleNode {
    int data;
    struct DoubleNode* prev;
    struct DoubleNode* next;
} DoubleNode;

// 创建新的双向链表节点
DoubleNode* createDoubleNode(int data) {
    DoubleNode* newNode = (DoubleNode*)malloc(sizeof(DoubleNode));
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

// 在双向链表头部插入节点
void insertAtHeadDouble(DoubleNode** head, int data) {
    DoubleNode* newNode = createDoubleNode(data);
    if (*head == NULL) {
        *head = newNode;
        return;
    }
    (*head)->prev = newNode;
    newNode->next = *head;
    *head = newNode;
}

// 在双向链表尾部插入节点
void insertAtTailDouble(DoubleNode** head, int data) {
    DoubleNode* newNode = createDoubleNode(data);
    if (*head == NULL) {
        *head = newNode;
        return;
    }
    DoubleNode* current = *head;
    while (current->next!= NULL) {
        current = current->next;
    }
    current->next = newNode;
    newNode->prev = current;
}

// 打印双向链表
void printDoubleList(DoubleNode* head) {
    DoubleNode* current = head;
    printf("正向遍历: ");
    while (current!= NULL) {
        printf("%d <-> ", current->data);
        current = current->next;
    }
    printf("NULL\n");

    current = head;
    while (current->next!= NULL) {
        current = current->next;
    }
    printf("反向遍历: ");
    while (current!= NULL) {
        printf("%d <-> ", current->data);
        current = current->prev;
    }
    printf("NULL\n");
}

// 删除双向链表头部节点
void deleteAtHeadDouble(DoubleNode** head) {
    if (*head == NULL) {
        return;
    }
    DoubleNode* temp = *head;
    *head = (*head)->next;
    if (*head!= NULL) {
        (*head)->prev = NULL;
    }
    free(temp);
}

int main() {
    DoubleNode* head = NULL;

    insertAtHeadDouble(&head, 10);
    insertAtHeadDouble(&head, 20);
    insertAtTailDouble(&head, 30);

    printf("初始双向链表: \n");
    printDoubleList(head);

    deleteAtHeadDouble(&head);

    printf("删除头部节点后的双向链表: \n");
    printDoubleList(head);

    return 0;
}

二、循环链表(Circular Linked List)

循环链表分为单向循环链表和双向循环链表。
单向循环链表:尾节点的指针指向头节点,形成一个环形结构。

  • 优点:可以实现循环遍历,适合处理具有循环特性的数据。
  • 缺点:遍历结束条件需要特别处理,否则可能陷入死循环。
    双向循环链表:头节点的前指针指向尾节点,尾节点的后指针指向头节点。
    循环链表的节点结构与对应的单向或双向链表类似,只是在指针的连接上形成了环。
    下面是一个用 C 语言实现单向循环链表创建和遍历的示例代码:
#include <stdlib.h>

// 单向循环链表节点结构体
typedef struct CircularNode {
    int data;
    struct CircularNode* next;
} CircularNode;

// 创建新的单向循环链表节点
CircularNode* createCircularNode(int data) {
    CircularNode* newNode = (CircularNode*)malloc(sizeof(CircularNode));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

// 插入节点到单向循环链表头部
void insertAtHeadCircular(CircularNode** head, int data) {
    CircularNode* newNode = createCircularNode(data);
    if (*head == NULL) {
        *head = newNode;
        newNode->next = *head;
        return;
    }
    CircularNode* current = *head;
    while (current->next!= *head) {
        current = current->next;
    }
    newNode->next = *head;
    current->next = newNode;
    *head = newNode;
}

// 打印单向循环链表
void printCircularList(CircularNode* head) {
    if (head == NULL) {
        return;
    }
    CircularNode* current = head;
    do {
        printf("%d -> ", current->data);
        current = current->next;
    } while (current!= head);
    printf("NULL\n");
}

int main() {
    CircularNode* head = NULL;

    insertAtHeadCircular(&head, 10);
    insertAtHeadCircular(&head, 20);
    insertAtHeadCircular(&head, 30);

    printf("单向循环链表: ");
    printCircularList(head);

    return 0;
}

四、静态链表
静态链表借助数组来模拟链表的存储结构。
优点:在某些不支持指针操作的环境中可以使用。
缺点:存储空间有限,不便于动态扩展。
下面是一个用 C 语言实现静态链表基本操作的示例代码:

#include <stdio.h>

// 定义静态链表最大长度
#define MAX_SIZE 100

// 静态链表节点结构体
typedef struct StaticNode {
    int data;
    int next;
} StaticNode;

// 初始化静态链表
void initStaticList(StaticNode list[]) {
    for (int i = 0; i < MAX_SIZE - 1; i++) {
        list[i].next = i + 1;
    }
    list[MAX_SIZE - 1].next = -1;
}

// 从静态链表中分配节点
int allocateNode(StaticNode list[]) {
    int index = list[0].next;
    if (index!= -1) {
        list[0].next = list[index].next;
    }
    return index;
}

// 回收静态链表中的节点
void freeNode(StaticNode list[], int index) {
    list[index].next = list[0].next;
    list[0].next = index;
}

int main() {
    StaticNode list[MAX_SIZE];

    initStaticList(list);

    int node1 = allocateNode(list);
    list[node1].data = 10;

    int node2 = allocateNode(list);
    list[node2].data = 20;

    printf("静态链表中的数据: %d %d\n", list[node1].data, list[node2].data);

    freeNode(list, node1);

    return 0;
}

五、链表与顺序表的对比

内存分配:

  • 链表:动态分配内存,节点可以分散在内存中的不同位置。
  • 顺序表:一次性分配连续的内存空间。

插入和删除操作:

  • 链表:在特定位置插入和删除元素的时间复杂度较低,通常为 O(1)。
  • 顺序表:在中间位置插入和删除元素时,需要移动大量元素,时间复杂度较高。

随机访问:

  • 链表:不支持随机访问,访问特定元素需要从头节点开始遍历。
  • 顺序表:可以通过索引直接访问元素,时间复杂度为 O(1)。
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ヾ慈城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值