您现在的位置是:首页 >技术教程 >数据结构-链表网站首页技术教程

数据结构-链表

小昭dedug 2023-06-15 08:00:02
简介数据结构-链表

单向链表

链表是一种常见的数据结构,通常由节点组成。每个节点包含一个数据元素和指向下一个节点的指针。以下是链表的初始化、插入和删除操作的示例代码:

链表的初始化:

typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;

ListNode* createNode(int value) {
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    node->val = value;
    node->next = NULL;
    return node;
}

ListNode* initList(int arr[], int n) {
    ListNode* head = NULL, *p = NULL;
    for (int i = 0; i < n; i++) {
        if (head == NULL) {
            head = p = createNode(arr[i]);
        } else {
            p->next = createNode(arr[i]);
            p = p->next;
        }
    }
    return head;
}

链表的插入:

void insertNode(ListNode* head, int index, int value) {
    ListNode* node = createNode(value);
    ListNode* p = head;
    for (int i = 0; i < index - 1 && p != NULL; i++) {
        p = p->next;
    }
    if (p == NULL) {
        return;
    }
    node->next = p->next;
    p->next = node;
}

链表的删除:

void deleteNode(ListNode* head, int index) {
    ListNode* p = head;
    for (int i = 0; i < index - 1 && p != NULL; i++) {
        p = p->next;
    }
    if (p == NULL || p->next == NULL) {
        return;
    }
    ListNode* q = p->next;
    p->next = q->next;
    free(q);
}

这些是链表的基本操作,它们提供了一种极为灵活的数据结构,可以用于各种不同的应用场景。

双向链表

双向链表是一种常用的数据结构,在 C 语言中,可以使用结构体和指针来实现双向链表。每个节点由一个包含数据和两个指针(prev 和 next)的结构体来表示。prev 指向前一个节点,next 指向后一个节点。下面是该数据结构的初始化、插入和删除操作的 C 语言实现。

初始化

双向链表的初始化操作包括创建一个头节点和将其 prev 和 next 指针均指向 NULL。

typedef struct Node {
    int data;
    struct Node* prev;
    struct Node* next;
} Node;

Node* head = NULL;

void init() {
    head = (Node*)malloc(sizeof(Node)); // 分配空间
    head-&gt;prev = NULL;
    head-&gt;next = NULL;
}

插入操作

双向链表的插入操作包括在指定位置插入一个节点,并将其 prev 和 next 指针指向相邻的节点。

void insert(int data, int position) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // 新建节点
    newNode-&gt;data = data;
    Node* curr = head-&gt;next;
    Node* prev = head;
    int i = 0;
    while (curr &amp;&amp; i &lt; position) { // 遍历链表,找到插入位置
        prev = curr;
        curr = curr-&gt;next;
        i++;
    }
    if (i == position) { // 找到位置
        prev-&gt;next = newNode;
        newNode-&gt;prev = prev;
        newNode-&gt;next = curr;
        if (curr) { // curr 不为 NULL,即插入位置不为链表末尾
            curr-&gt;prev = newNode;
        }
    }
}

删除操作

双向链表的删除操作包括删除指定位置的节点,并将其前后节点的 prev 和 next 指针正确连接。

void delete(int position) {
    Node* curr = head-&gt;next;
    Node* prev = head;
    int i = 0;
    while (curr &amp;&amp; i &lt; position) { // 遍历链表,找到删除位置
        prev = curr;
        curr = curr-&gt;next;
        i++;
    }
    if (i == position &amp;&amp; curr) { // 找到位置且 curr 不为 NULL
        prev-&gt;next = curr-&gt;next;
        if (curr-&gt;next) { // curr 不为链表末尾,修改其后继节点的 prev 指针
            curr-&gt;next-&gt;prev = prev;
        }
        free(curr); // 释放空间
    }
}

总体来说,双向链表优点是可以快速地在链表中间删除和插入节点。在 C 语言中,使用指针实现链表可以有效地利用内存。

双向量表

循环链表是一种特殊的链表,与双向链表的区别在于最后一个节点的 next 指向头节点,形成一个环形结构。在 C 语言中,可以使用结构体和指针来实现循环链表。每个节点仍由一个包含数据和一个指针(next)的结构体来表示。下面是该数据结构的初始化、插入和删除操作的 C 语言实现。

初始化

循环链表的初始化包括创建一个头节点并使其 next 指向自身。

typedef struct Node {
    int data;
    struct Node* next;
} Node;

Node* head = NULL;

void init() {
    head = (Node*)malloc(sizeof(Node)); // 分配空间
    head-&gt;next = head; // next 指向自身,形成一个循环链表
}

插入操作

循环链表的插入操作与单链表和双向链表相似,区别在于插入位置可能是最后一个节点的后面,应该让新节点的 next 指向头节点。

void insert(int data, int position) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // 新建节点
    newNode-&gt;data = data;
    Node* curr = head-&gt;next;
    Node* prev = head;
    int i = 0;
    while (curr != head &amp;&amp; i &lt; position) { // 遍历链表,找到插入位置
        prev = curr;
        curr = curr-&gt;next;
        i++;
    }
    prev-&gt;next = newNode;
    newNode-&gt;next = curr;
    if (curr == head &amp;&amp; i == position) { // 插入的是最后一个节点的后面,将新节点指向头节点
        curr-&gt;next = newNode;
    }
}

删除操作

循环链表的删除操作与单链表和双向链表相似,需要特别处理删除的是最后一个节点的情况。

void delete(int position) {
    Node* curr = head-&gt;next;
    Node* prev = head;
    int i = 0;
    while (curr != head &amp;&amp; i &lt; position) { // 遍历链表,找到删除位置
        prev = curr;
        curr = curr-&gt;next;
        i++;
    }
    if (curr != head) { // 找到位置,需要特别处理最后一个节点的情况
        prev-&gt;next = curr-&gt;next;
        if (curr == head-&gt;next) { // 最后一个节点
            head-&gt;next = curr-&gt;next;
        }
        free(curr); // 释放空间
    }
}

循环链表可以更好地处理环形结构的问题,如约瑟夫问题。但需要注意循环链表的特殊性,在插入和删除操作中需要特别处理最后一个节点的情况。

小结

单向链表、双向链表和循环链表都是常用的链表数据结构,每种链表都有各自的应用场景、优势和缺点。

单向链表

单向链表是最简单的链表数据结构,每个节点只包含一个指针(next)指向下一个节点,适合场景如下:

需要频繁地在链表的头部插入和删除节点,因为单向链表可以通过修改头指针来实现这些操作,时间复杂度为 O(1)。
数据元素的插入和删除较为频繁,但查找操作很少进行。因为单向链表的查找操作需要从头节点开始遍历整个链表,时间复杂度为 O(n),效率较低。
需要在较少的空间内存储大量数据,因为单向链表可以灵活地利用内存,每个节点只需要存储一个指针和数据元素。

优势:插入和删除操作时间复杂度为 O(1),空间利用率高。
缺点:查找操作时间复杂度较高,需要遍历整个链表。

双向链表

双向链表是在单向链表的基础上增加了一个指针(prev)指向前一个节点,适合场景如下:

需要经常在链表的任意位置插入和删除节点,双向链表可以通过修改相邻节点的指针来实现这些操作,时间复杂度为 O(1)。
需要实现双向遍历,即可以从头节点开始遍历整个链表,也可以从尾节点开始遍历整个链表。
需要在较少的空间内存储大量数据,因为双向链表每个节点需要存储两个指针和数据元素。

优势:插入和删除操作时间复杂度为 O(1),支持双向遍历。
缺点:相比于单向链表,需要额外使用一个指针,增加了存储空间的消耗。

循环链表

循环链表是指最后一个节点的 next 指针指向链表的头节点,形成一个环形结构。适用场景如下:

需要处理环形结构的问题,如约瑟夫问题。
需要经常在链表的任意位置插入和删除节点。

优势:插入和删除操作时间复杂度为 O(1),处理环形结构方便。
缺点:需要特别处理最后一个节点的情况,不适合原地反转链表等操作。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。