单链表的构建
-
定义节点结构: 在C或C++中,首先你需要定义一个结构体(
struct
或class
),用于表示链表中的一个节点。这个结构体通常包含两部分:一个是存储数据的字段,另一个是指向下一个节点的指针 -
struct Node { int data; // 存储数据的字段 struct Node* next; // 指向下一个节点的指针 };
-
初始化链表: 初始化链表通常意味着创建一个头节点,头节点可以是空的,或者包含一些默认数据。在初始化时,链表的头指针指向这个头节点。
struct Node* head = NULL; // 如果不使用头结点
如果使用带头结点的链表,则初始化头结点并让头指针指向它
struct Node* head = (struct Node*)malloc(sizeof(struct Node)); head->data = 0; // 可以设置默认值 head->next = NULL;
-
插入节点: 插入节点到链表中可以通过几种方式,最常见的是头插法和尾插法。
头插法示例:
void insertAtHead(struct Node** head, int newData) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = newData; newNode->next = *head; *head = newNode; }
尾插法示例:
void insertAtTail(struct Node** head, int newData) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = newData; newNode->next = NULL; if (*head == NULL) { *head = newNode; return; } struct Node* last = *head; while (last->next != NULL) last = last->next; last->next = newNode; }
- 头插法:新节点插入到头结点的后面,成为新的头结点的下一个节点。链表的结点顺序与逻辑次序相反。
- 尾插法:新节点总是添加到链表的末尾。链表的结点顺序与逻辑次序相同。
-
遍历链表: 遍历链表是为了访问每一个节点,通常从头节点开始,直到到达链表的末尾。
void printList(struct Node* node) { while (node != NULL) { printf("%d ", node->data); node = node->next; } }
-
删除节点: 删除节点可以从链表的任何位置删除,这通常涉及到更新前一个节点的
next
指针,使其指向被删除节点的下一个节点。 -
销毁链表: 当不再需要链表时,应释放所有节点占用的内存,避免内存泄漏。
节点的查找
1. 按值查找
如果你想要查找具有特定值的节点,你可以从链表的头部开始,遍历链表直到找到具有所需值的节点或者到达链表的尾部。这个过程通常需要线性时间,即O(n),其中n是链表中节点的数量。
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
// 查找具有特定值的节点
Node* findNodeByValue(Node* head, int value) {
Node* current = head;
while (current != NULL) {
if (current->data == value) {
return current;
}
current = current->next;
}
return NULL; // 如果没有找到,返回NULL
}
2. 按位置查找
如果你想按照节点在链表中的位置(例如,第k个节点)进行查找,你可以从链表的头部开始,向前移动k次,到达目标节点或发现链表太短。
Node* findNodeByPosition(Node* head, int position) {
Node* current = head;
int count = 0;
while (current != NULL) {
if (count == position) {
return current;
}
count++;
current = current->next;
}
return NULL; // 如果没有找到,返回NULL
}
3. 查找中间节点
要查找链表的中间节点,你可以使用“快慢指针”技巧。快指针每次移动两步,慢指针每次移动一步。当快指针到达链表尾部时,慢指针正好位于中间。
Node* findMiddleNode(Node* head) {
Node* slow = head;
Node* fast = head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
4. 查找倒数第k个节点
查找倒数第k个节点通常需要两次遍历,第一次遍历得到链表的长度,第二次遍历可以计算出正数第(length - k + 1)个节点。然而,更高效的方法是使用两个指针,一个领先另一个k步,然后同时移动直到领先指针到达链表尾部。
Node* findKthFromEnd(Node* head, int k) {
Node* lead = head;
Node* follow = head;
for (int i = 0; i < k; i++) {
if (lead == NULL) return NULL; // 如果k大于链表长度,返回NULL
lead = lead->next;
}
while (lead != NULL) {
lead = lead->next;
follow = follow->next;
}
return follow;
}
单链表与线性表
单链表作为线性表的一种链式存储结构,支持多种基本运算。以下是基于带头结点的单链表的九种基本运算,它们涵盖了线性表的主要操作:
-
初始化单链表 (
InitList
): 创建一个空的单链表,通常只包含一个头结点,且头结点的next
指针指向NULL
。 -
判断单链表是否为空 (
ListEmpty
): 检查除了头结点外是否还有其他节点,即检查头结点的next
是否为NULL
。 -
获取单链表的长度 (
ListLength
): 从头结点开始遍历,统计非头结点的数量。 -
插入节点 (
ListInsert
): 在指定位置插入一个新节点,可以是在链表的头部、尾部或任意位置。 -
删除节点 (
ListDelete
): 根据位置或值删除链表中的一个节点。 -
查找节点 (
LocateElem
): 按值查找节点,返回节点的位置或直接返回节点本身。 -
访问节点 (
GetElem
): 根据位置访问链表中的一个节点,返回节点的数据。 -
修改节点数据 (
ListUpdate
): 修改链表中指定位置的节点数据。 -
输出单链表 (
PrintList
): 从头结点开始遍历并打印链表中每个节点的数据。