单向链表

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

链表的初始化:

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 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 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 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->prev = NULL;head->next = NULL;}

插入操作

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

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

删除操作

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

void delete(int position) {Node* curr = head->next;Node* prev = head;int i = 0;while (curr && i < position) { // 遍历链表,找到删除位置prev = curr;curr = curr->next;i++;}if (i == position && curr) { // 找到位置且 curr 不为 NULLprev->next = curr->next;if (curr->next) { // curr 不为链表末尾,修改其后继节点的 prev 指针curr->next->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->next = head; // next 指向自身,形成一个循环链表}

插入操作

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

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

删除操作

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

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

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

小结

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

单向链表

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

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

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

双向链表

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

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

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

循环链表

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

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

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