链表 (C/C++)
链表的基本概念
什么是链表?
链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。
数组 vs 链表
特性 | 数组 | 链表 |
---|---|---|
内存分配 | 静态连续 | 动态非连续 |
插入删除 | 慢(O(n)) | 快(O(1)) |
随机访问 | 快(O(1)) | 慢(O(n)) |
内存使用 | 固定 | 按需分配 |
C
链表的基本结构
节点定义
// 最基本的链表节点
typedef struct Node {int data; // 数据域struct Node* next; // 指针域,指向下一个节点
} Node;
链表可视化
头指针 -> [数据|next] -> [数据|next] -> [数据|next] -> NULL
3. 链表的创建和基本操作
创建新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));if (newNode == NULL) {printf("内存分配失败!\n");return NULL;}newNode->data = data;newNode->next = NULL;return newNode;
}
在链表头部插入(头插法)
void insertAtHead(Node** head, int data) {Node* newNode = createNode(data);newNode->next = *head; // 新节点指向原头节点*head = newNode; // 头指针指向新节点
}
头插法图示:
原链表: head -> [A|next] -> [B|next] -> NULL 插入C: 1. 创建新节点: [C|next] 2. newNode->next = head: [C|next] -> [A|next] 3. head = newNode: head -> [C|next] -> [A|next] -> [B|next] -> NULL
在链表尾部插入(尾插法)
void insertAtTail(Node** head, int data) {Node* newNode = createNode(data);// 如果链表为空if (*head == NULL) {*head = newNode;return;}// 找到最后一个节点Node* current = *head;while (current->next != NULL) {current = current->next;}current->next = newNode; // 最后一个节点指向新节点
}
4. 完整的链表操作示例
#include <stdio.h>
#include <stdlib.h>typedef struct Node {int data;struct Node* next;
} Node;// 创建新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));if (!newNode) {printf("内存分配失败\n");exit(1);}newNode->data = data;newNode->next = NULL;return newNode;
}// 头插法
void insertAtHead(Node** head, int data) {Node* newNode = createNode(data);newNode->next = *head;*head = newNode;
}// 尾插法
void insertAtTail(Node** head, int data) {Node* newNode = createNode(data);if (*head == NULL) {*head = newNode;return;}Node* current = *head;while (current->next != NULL) {current = current->next;}current->next = newNode;
}// 在指定位置插入
void insertAtPosition(Node** head, int data, int position) {if (position < 0) {printf("位置无效\n");return;}if (position == 0) {insertAtHead(head, data);return;}Node* newNode = createNode(data);Node* current = *head;// 找到position-1的位置for (int i = 0; i < position - 1 && current != NULL; i++) {current = current->next;}if (current == NULL) {printf("位置超出范围\n");free(newNode);return;}newNode->next = current->next;current->next = newNode;
}// 删除节点
void deleteNode(Node** head, int data) {if (*head == NULL) {printf("链表为空\n");return;}// 如果要删除头节点if ((*head)->data == data) {Node* temp = *head;*head = (*head)->next;free(temp);return;}// 查找要删除的节点Node* current = *head;Node* prev = NULL;while (current != NULL && current->data != data) {prev = current;current = current->next;}if (current == NULL) {printf("未找到数据 %d\n", data);return;}prev->next = current->next;free(current);
}// 查找节点
Node* searchNode(Node* head, int data) {Node* current = head;while (current != NULL) {if (current->data == data) {return current;}current = current->next;}return NULL;
}// 获取链表长度
int getLength(Node* head) {int count = 0;Node* current = head;while (current != NULL) {count++;current = current->next;}return count;
}// 打印链表
void printList(Node* head) {Node* current = head;printf("链表: ");while (current != NULL) {printf("%d -> ", current->data);current = current->next;}printf("NULL\n");
}// 释放链表内存
void freeList(Node* head) {Node* current = head;while (current != NULL) {Node* next = current->next;free(current);current = next;}
}int main() {Node* head = NULL;printf("=== 链表操作演示 ===\n\n");// 尾插法添加节点insertAtTail(&head, 10);insertAtTail(&head, 20);insertAtTail(&head, 30);printf("尾插法创建链表:\n");printList(head);// 头插法添加节点insertAtHead(&head, 5);insertAtHead(&head, 1);printf("头插法添加节点后:\n");printList(head);// 在指定位置插入insertAtPosition(&head, 15, 3);printf("在位置3插入15后:\n");printList(head);// 删除节点deleteNode(&head, 20);printf("删除20后:\n");printList(head);// 查找节点Node* found = searchNode(head, 15);if (found) {printf("找到节点: %d\n", found->data);} else {printf("未找到节点\n");}// 获取长度printf("链表长度: %d\n", getLength(head));// 释放内存freeList(head);head = NULL;return 0;
}
5. 高级链表操作
反转链表
Node* reverseList(Node* head) {Node* prev = NULL;Node* current = head;Node* next = NULL;while (current != NULL) {next = current->next; // 保存下一个节点current->next = prev; // 反转指针prev = current; // 移动prevcurrent = next; // 移动current}return prev; // 新的头节点
}
检测环(Floyd判圈算法)
c
int hasCycle(Node* head) {if (head == NULL || head->next == NULL) {return 0;}Node* slow = head; // 慢指针,每次走一步Node* fast = head; // 快指针,每次走两步while (fast != NULL && fast->next != NULL) {slow = slow->next;fast = fast->next->next;if (slow == fast) {return 1; // 有环}}return 0; // 无环
}
6. 双向链表
双向链表节点定义
typedef struct DNode {int data;struct DNode* prev; // 指向前一个节点struct DNode* next; // 指向下一个节点
} DNode;
双向链表操作
// 在双向链表中插入节点
void insertDNode(DNode** head, int data) {DNode* newNode = (DNode*)malloc(sizeof(DNode));newNode->data = data;newNode->prev = NULL;newNode->next = *head;if (*head != NULL) {(*head)->prev = newNode;}*head = newNode;
}// 双向链表删除
void deleteDNode(DNode** head, int data) {DNode* current = *head;while (current != NULL && current->data != data) {current = current->next;}if (current == NULL) return;if (current->prev != NULL) {current->prev->next = current->next;} else {*head = current->next;}if (current->next != NULL) {current->next->prev = current->prev;}free(current);
}
C++
单向链表的实现
节点定义
struct ListNode {int val; // 存储的数据ListNode* next; // 指向下一个节点的指针// 构造函数ListNode(int x) : val(x), next(nullptr) {}
};
ListNode(int x) : val(x), next(nullptr) {}
构造函数:用于创建新的
ListNode
对象ListNode(int x)
:构造函数名,接受一个整数参数x
: val(x), next(nullptr)
:成员初始化列表val(x)
:将参数x
的值赋给val
成员next(nullptr)
:将next
指针初始化为空指针
{}
:空的函数体(因为初始化已经在初始化列表中完成)
链表类实现
class LinkedList {
private:ListNode* head; // 头指针public:// 构造函数LinkedList() : head(nullptr) {}// 析构函数 - 释放所有节点内存~LinkedList() {ListNode* current = head;while (current != nullptr) {ListNode* next = current->next;delete current;current = next;}}// 在链表头部插入节点void insertAtHead(int val) {ListNode* newNode = new ListNode(val);newNode->next = head;head = newNode;}// 在链表尾部插入节点void insertAtTail(int val) {ListNode* newNode = new ListNode(val);if (head == nullptr) {head = newNode;return;}ListNode* current = head;while (current->next != nullptr) {current = current->next;}current->next = newNode;}// 删除指定值的节点void deleteNode(int val) {if (head == nullptr) return;// 如果要删除的是头节点if (head->val == val) {ListNode* temp = head;head = head->next;delete temp;return;}ListNode* current = head;while (current->next != nullptr && current->next->val != val) {current = current->next;}if (current->next != nullptr) {ListNode* temp = current->next;current->next = current->next->next;delete temp;}}// 查找节点bool search(int val) {ListNode* current = head;while (current != nullptr) {if (current->val == val) {return true;}current = current->next;}return false;}// 打印链表void printList() {ListNode* current = head;while (current != nullptr) {std::cout << current->val << " -> ";current = current->next;}std::cout << "nullptr" << std::endl;}// 获取链表长度int getLength() {int length = 0;ListNode* current = head;while (current != nullptr) {length++;current = current->next;}return length;}
};
使用示例
int main() {LinkedList list;// 插入操作list.insertAtHead(3);list.insertAtHead(2);list.insertAtHead(1);list.insertAtTail(4);list.insertAtTail(5);std::cout << "链表内容: ";list.printList(); // 输出: 1 -> 2 -> 3 -> 4 -> 5 -> nullptr// 查找操作std::cout << "查找 3: " << (list.search(3) ? "找到" : "未找到") << std::endl;std::cout << "查找 6: " << (list.search(6) ? "找到" : "未找到") << std::endl;// 删除操作list.deleteNode(3);std::cout << "删除 3 后: ";list.printList(); // 输出: 1 -> 2 -> 4 -> 5 -> nullptr// 获取长度std::cout << "链表长度: " << list.getLength() << std::endl;return 0;
}
双向链表
双向链表的每个节点有两个指针:一个指向前一个节点,一个指向后一个节点。
struct DoublyListNode {int val;DoublyListNode* prev;DoublyListNode* next;DoublyListNode(int x) : val(x), prev(nullptr), next(nullptr) {}
};class DoublyLinkedList {
private:DoublyListNode* head;DoublyListNode* tail;public:DoublyLinkedList() : head(nullptr), tail(nullptr) {}// 实现类似的方法,但需要处理prev指针// ...
};
链表的优缺点
优点:
动态大小:不需要预先指定大小
高效插入/删除:在已知位置插入/删除的时间复杂度为O(1)
内存利用率:不需要连续的内存空间
缺点:
随机访问效率低:访问第n个元素需要O(n)时间
额外内存:需要额外的指针空间
缓存不友好:节点在内存中不连续
常见链表操作
反转链表
void reverse() {ListNode* prev = nullptr;ListNode* current = head;ListNode* next = nullptr;while (current != nullptr) {next = current->next;current->next = prev;prev = current;current = next;}head = prev;
}
检测环
bool hasCycle() {if (head == nullptr) return false;ListNode* slow = head;ListNode* fast = head;while (fast != nullptr && fast->next != nullptr) {slow = slow->next;fast = fast->next->next;if (slow == fast) {return true;}}return false;
}
题目:
生成10个随机数,按从大到小的顺序存入链表,最后打印链表内容。
C语言实现
#include <stdio.h>
#include <stdlib.h>
#include <time.h>// 链表节点结构体
typedef struct ListNode {int val;struct ListNode* next;
} ListNode;// 创建新节点
ListNode* createNode(int val) {ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));if (newNode == NULL) {printf("内存分配失败\n");exit(1);}newNode->val = val;newNode->next = NULL;return newNode;
}// 按从大到小顺序插入节点
void insertSorted(ListNode** head, int val) {ListNode* newNode = createNode(val);// 如果链表为空或新节点值大于头节点if (*head == NULL || val > (*head)->val) {newNode->next = *head;*head = newNode;return;}// 找到合适的插入位置ListNode* current = *head;while (current->next != NULL && current->next->val > val) {current = current->next;}// 插入新节点newNode->next = current->next;current->next = newNode;
}// 打印链表
void printList(ListNode* head) {ListNode* current = head;printf("链表内容(从大到小): ");while (current != NULL) {printf("%d ", current->val);current = current->next;}printf("\n");
}// 释放链表内存
void freeList(ListNode* head) {ListNode* current = head;while (current != NULL) {ListNode* temp = current;current = current->next;free(temp);}printf("链表内存已释放\n");
}int main() {// 初始化随机数种子srand(time(NULL));ListNode* head = NULL;printf("生成的10个随机数字: ");for (int i = 0; i < 10; i++) {int num = rand() % 100 + 1; // 生成1-100的随机数printf("%d ", num);insertSorted(&head, num); // 按顺序插入链表}printf("\n");// 打印排序后的链表printList(head);// 释放链表内存freeList(head);return 0;
}
C++实现
#include <iostream>
#include <cstdlib>
#include <ctime>// 链表节点结构体
struct ListNode {int val;ListNode* next;// 构造函数ListNode(int x) : val(x), next(nullptr) {}
};class SortedLinkedList {
private:ListNode* head;public:// 构造函数SortedLinkedList() : head(nullptr) {}// 析构函数~SortedLinkedList() {clear();}// 按从大到小顺序插入节点void insertSorted(int val) {ListNode* newNode = new ListNode(val);// 如果链表为空或新节点值大于头节点if (head == nullptr || val > head->val) {newNode->next = head;head = newNode;return;}// 找到合适的插入位置ListNode* current = head;while (current->next != nullptr && current->next->val > val) {current = current->next;}// 插入新节点newNode->next = current->next;current->next = newNode;}// 打印链表void printList() const {ListNode* current = head;std::cout << "链表内容(从大到小): ";while (current != nullptr) {std::cout << current->val << " ";current = current->next;}std::cout << std::endl;}// 清空链表(释放内存)void clear() {ListNode* current = head;while (current != nullptr) {ListNode* temp = current;current = current->next;delete temp;}head = nullptr;std::cout << "链表内存已释放" << std::endl;}
};int main() {// 初始化随机数种子std::srand(std::time(nullptr));SortedLinkedList list;std::cout << "生成的10个随机数字: ";for (int i = 0; i < 10; i++) {int num = std::rand() % 100 + 1; // 生成1-100的随机数std::cout << num << " ";list.insertSorted(num); // 按顺序插入链表}std::cout << std::endl;// 打印排序后的链表list.printList();// 析构函数会自动调用clear()释放内存// 也可以手动调用:list.clear();return 0;
}
两种实现的区别
C语言实现特点:
使用
malloc()
和free()
进行内存管理需要手动传递头指针的地址 (
ListNode**
)需要显式调用释放函数
使用结构体和函数分离的方式
C++实现特点:
使用
new
和delete
进行内存管理使用类和成员函数封装链表操作
析构函数自动释放内存(RAII原则)
更好的封装性和安全性
可能的输出结果
生成的10个随机数字: 45 78 23 91 34 67 12 89 56 3 链表内容(从大到小): 91 89 78 67 56 45 34 23 12 3 链表内存已释放
std::srand(std::time(nullptr));
这行代码的作用是初始化随机数生成器的种子。
详细解释
1. std::time(nullptr)
作用:获取当前的时间戳(从1970年1月1日到现在经过的秒数)
参数:
nullptr
表示不需要存储时间值,只需要返回值返回值:返回当前时间的整数值,每秒都在变化
2. std::srand(seed)
作用:设置随机数生成器的种子
参数:一个整数种子值
功能:基于给定的种子初始化随机数序列
为什么需要这样做?
问题:没有种子的随机数
// 如果不设置种子,每次运行都会生成相同的"随机"数序列
std::cout << std::rand() << std::endl; // 每次都是相同的数字
解决方案:使用时间作为种子
// 使用当前时间作为种子,确保每次运行都不同
std::srand(std::time(nullptr));
std::cout << std::rand() << std::endl; // 每次运行都不同
注意事项
只需要调用一次:在程序开始时调用一次即可
不要频繁调用:如果在循环中频繁调用,可能得到相同的随机数
C++11更好的选择:现代C++推荐使用
<random>
库: