C语言中的链表原理及实现
发布时间: 2024-02-23 05:44:53 阅读量: 60 订阅数: 31 ![](https://csdnimg.cn/release/wenkucmsfe/public/img/col_vip.0fdee7e1.png)
![](https://csdnimg.cn/release/wenkucmsfe/public/img/col_vip.0fdee7e1.png)
![PDF](https://csdnimg.cn/release/download/static_files/pc/images/minetype/PDF.png)
C语言单向链表的表示与实现实例详解
# 1. 链表概述
链表是一种常见的数据结构,它由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域存储节点的值,指针域指向下一个节点,通过指针将各个节点连接在一起,形成链表的结构。链表可以分为单向链表、双向链表和循环链表等不同类型。
## 1.1 链表的基本概念
链表是一种非连续存储结构,节点可以分布在内存的任意位置。每个节点都包含指向下一个节点的指针,最后一个节点的指针指向NULL,表示链表的结束。链表的节点插入和删除操作相对灵活,不需要移动大量元素,因此适合频繁插入和删除操作的场景。
## 1.2 链表与数组的对比
数组是一种连续存储结构,需要在内存中分配一块连续的空间来存储数据,数组的大小在创建时就固定了。链表不需要一块连续的空间,可以动态分配内存,大小可以根据实际需求动态变化。但是链表在访问元素时需要遍历链表,时间复杂度为O(n),而数组可以通过下标直接访问元素,时间复杂度为O(1)。
## 1.3 链表的优缺点
链表的优点:
- 插入和删除操作方便快捷,不需要移动大量元素。
- 大小可动态调整,节省内存空间。
- 支持多种类型的链表结构,灵活多样。
链表的缺点:
- 随机访问性能较差,需要遍历链表来查找特定节点。
- 占用更多的内存空间用于存储指针。
- 不支持随机访问,无法通过下标直接获取元素。
在接下来的章节中,我们将深入探讨链表的结构、实现、算法以及优化与扩展应用。
# 2. 链表的基本结构
### 2.1 单向链表
单向链表是一种最基本的链表结构,由节点组成,每个节点包含数据和指向下一个节点的指针。链表的头节点指向第一个节点,最后一个节点的指针指向NULL。
#### 场景:
假设我们要实现一个简单的单向链表来存储学生的信息,每个节点包含学生的学号和姓名。
#### 代码示例(Python):
```python
class Node:
def __init__(self, student_id, student_name):
self.student_id = student_id
self.student_name = student_name
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, student_id, student_name):
new_node = Node(student_id, student_name)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
# 创建链表并添加节点
linked_list = LinkedList()
linked_list.append(101, 'Alice')
linked_list.append(102, 'Bob')
linked_list.append(103, 'Charlie')
```
#### 代码总结:
- 定义了节点类`Node`,包含学生的学号、姓名和指向下一个节点的指针`next`。
- 定义了链表类`LinkedList`,包含头节点`head`,以及添加节点的方法`append`。
- 创建链表并添加节点。
#### 结果说明:
通过上述代码,我们成功创建了一个单向链表,存储了三位学生的信息。链表的结构为101->102->103->NULL。
### 2.2 双向链表
双向链表是在单向链表的基础上每个节点多了一个指向前一个节点的指针,这样可以方便从任一方向遍历链表。
#### 代码示例(Java):
```java
class Node {
int studentId;
String studentName;
Node prev;
Node next;
public Node(int studentId, String studentName) {
this.studentId = studentId;
this.studentName = studentName;
}
}
class DoublyLinkedList {
Node head;
Node tail;
public void append(int studentId, String studentName) {
Node newNode = new Node(studentId, studentName);
if (head == null) {
head = newNode;
tail = newNode;
return;
}
tail.next = newNode;
newNode.prev = tail;
tail = newNode;
}
}
// 创建双向链表并添加节点
DoublyLinkedList doublyLinkedList = new DoublyLinkedList();
doublyLinkedList.append(201, "David");
doublyLinkedList.append(202, "Emma");
doublyLinkedList.append(203, "Frank");
```
#### 代码总结:
- 定义了节点类`Node`,包含学生的学号、姓名,以及指向前一个节点和后一个节点的指针`prev`和`next`。
- 定义了双向链表类`DoublyLinkedList`,包含头节点`head`和尾节点`tail`,以及添加节点的方法`append`。
#### 结果说明:
通过上述Java代码,我们成功创建了一个双向链表,存储了三位学生的信息。链表的结构为201<-202<-203。
# 3. C语言中链表的实现
链表是一种常见的数据结构,可以动态地存储和管理数据。在C语言中,我们可以通过定义结构体来实现链表。
#### 3.1 节点的定义
在C语言中,链表的节点通常由一个结构体表示,结构体包含数据域和指向下一个节点的指针。
```C
// 节点结构体定义
typedef struct Node {
int data;
struct Node* next;
} Node;
```
#### 3.2 创建链表
创建链表需要初始化头节点,并且可以通过循环动态添加节点来扩展链表。
```C
// 创建链表
Node* createLinkedList(int arr[], int size) {
Node* head = NULL;
Node* tail = NULL;
for (int i = 0; i < size; i++) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = arr[i];
newNode->next = NULL;
if (head == NULL) {
head = newNode;
tail = newNode;
} else {
tail->next = newNode;
tail = tail->next;
}
}
return head;
}
```
#### 3.3 遍历链表
遍历链表是指逐个访问链表中的每个节点并操作其中的数据。
```C
// 遍历链表
void traverseLinkedList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d\n", current->data);
current = current->next;
}
}
```
#### 3.4 插入与删除节点
在链表中,可以通过插入和删除节点来灵活地操作数据。
```C
// 插入节点
void insertNode(Node** head, int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->next = *head;
*head = newNode;
}
// 删除节点
void deleteNode(Node** head, int value) {
Node* current = *head;
Node* prev = NULL;
while (current != NULL && current->data != value) {
prev = current;
current = current->next;
}
if (current == NULL) {
printf("Node not found.\n");
return;
}
if (prev == NULL) {
*head = current->next;
} else {
prev->next = current->next;
}
free(current);
}
```
以上是C语言中链表的基本实现,包括节点定义、创建链表、遍历链表以及插入与删除节点的操作。链表的灵活性使其在许多应用中都得到广泛运用。
# 4. 链表的应用
链表作为一种重要的数据结构,在实际应用中被广泛使用。下面我们将介绍链表在各种场景下的具体应用以及使用链表解决问题的案例分析。
#### 4.1 链表的应用场景
1. **缓存淘汰策略**:在缓存系统中,可以使用链表实现LRU(Least Recently Used)缓存淘汰算法,通过维护一个按访问时间顺序排列的链表,实现高效的缓存淘汰操作。
2. **编辑器的撤销操作**:在文本编辑器中,可以使用链表实现撤销(Undo)操作,将用户的编辑操作依次记录在链表中,撤销操作时只需调整链表指针即可。
3. **表达式求值**:在编写计算器程序时,可以使用链表结构来存储表达式,通过遍历链表实现表达式的求值与计算。
4. **路由算法**:在网络路由算法中,链表可以用来存储路由信息,实现数据包的转发与路由选择。
#### 4.2 使用链表解决问题的案例分析
下面我们通过一个具体的案例来说明如何使用链表解决问题:
**问题描述:** 给定两个非空链表,代表两个非负整数。数字以相反的顺序存储,每个节点包含一个数字,并且每个节点只能存储一位数字。将这两个数相加,返回一个新的链表表示它们的和。
**示例:**
```
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Explanation: 342 + 465 = 807
```
**思路:**
1. 遍历两个链表,同时将对应位置的数字相加,考虑进位。
2. 将相加结果依次存储在新链表中,并更新进位。
3. 注意处理链表长度不一致的情况。
```python
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def addTwoNumbers(l1, l2):
dummy = ListNode()
curr = dummy
carry = 0
while l1 or l2 or carry:
sum_values = (l1.val if l1 else 0) + (l2.val if l2 else 0) + carry
carry, digit = divmod(sum_values, 10)
curr.next = ListNode(digit)
curr = curr.next
l1 = l1.next if l1 else None
l2 = l2.next if l2 else None
return dummy.next
# 构建示例链表
l1 = ListNode(2)
l1.next = ListNode(4)
l1.next.next = ListNode(3)
l2 = ListNode(5)
l2.next = ListNode(6)
l2.next.next = ListNode(4)
result = addTwoNumbers(l1, l2)
# 打印结果链表
while result:
print(result.val, end="->")
result = result.next
```
**代码总结:**
- 通过遍历两个链表,逐位相加,同时考虑进位的处理,创建新链表存储相加结果。
- 最终返回新链表表示的和。
**结果说明:**
上述代码实现了两个链表的数字相加功能,通过构建示例链表并调用函数进行求和操作,最终得到了正确的相加结果链表。
# 5. 链表算法
在本章节中,我们将介绍链表的常见算法,包括基本操作算法、排序算法和搜索查找算法。
### 5.1 链表的基本操作算法
#### 5.1.1 链表反转
下面我们通过一个示例来演示如何反转一个单向链表:
```python
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list(head):
prev = None
current = head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
return prev
# 创建一个单向链表1->2->3->4->5
node5 = ListNode(5)
node4 = ListNode(4, node5)
node3 = ListNode(3, node4)
node2 = ListNode(2, node3)
node1 = ListNode(1, node2)
reversed_head = reverse_list(node1)
# 打印反转后的链表
while reversed_head:
print(reversed_head.val)
reversed_head = reversed_head.next
```
代码总结:通过迭代的方式,我们可以反转一个单向链表。首先将当前节点的next指针指向前一个节点,然后更新prev和current节点,直到遍历完成。
结果说明:上述代码输出结果为:
```
5
4
3
2
1
```
#### 5.1.2 链表合并
接下来我们看一个例子,将两个有序链表合并成一个新的有序链表:
```python
def merge_lists(l1, l2):
if not l1:
return l2
if not l2:
return l1
if l1.val < l2.val:
l1.next = merge_lists(l1.next, l2)
return l1
else:
l2.next = merge_lists(l1, l2.next)
return l2
# 创建有序链表1: 1->2->4
node4 = ListNode(4)
node2 = ListNode(2, node4)
node1 = ListNode(1, node2)
# 创建有序链表2: 1->3->4
node4 = ListNode(4)
node3 = ListNode(3, node4)
node1_2 = ListNode(1, node3)
merged_head = merge_lists(node1, node1_2)
# 打印合并后的有序链表
while merged_head:
print(merged_head.val)
merged_head = merged_head.next
```
代码总结:我们通过递归的方式将两个有序链表合并,保持有序性。
结果说明:上述代码输出结果为:
```
1
1
2
3
4
4
```
### 5.2 链表的排序算法
在这一部分,我们将讨论如何对链表进行排序操作。待补充。
### 5.3 链表的搜索与查找算法
在这部分,我们会探讨如何在链表中进行搜索和查找操作。待补充。
通过上述示例,我们可以看到链表在算法应用上的灵活性和实用性,掌握这些算法将有助于更好地理解和运用链表数据结构。
# 6. 链表的优化与扩展
### 6.1 内存管理与性能优化
在实际应用中,链表的节点可能会因为频繁的插入与删除操作导致内存碎片的产生,从而影响程序的性能。为了优化内存的使用,可以考虑使用内存池来管理链表节点的内存分配与释放,减少内存碎片的产生,提高内存的利用率。
下面是一个简单的示例,展示如何通过内存池来管理链表节点的内存:
```java
public class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
this.next = null;
}
}
public class MemoryPool {
private static final int INITIAL_CAPACITY = 10;
private static final List<ListNode> pool = new ArrayList<>();
static {
for (int i = 0; i < INITIAL_CAPACITY; i++) {
pool.add(new ListNode(0));
}
}
public static ListNode getNode(int val) {
if (pool.isEmpty()) {
for (int i = 0; i < INITIAL_CAPACITY; i++) {
pool.add(new ListNode(0));
}
}
ListNode node = pool.remove(pool.size() - 1);
node.val = val;
return node;
}
public static void releaseNode(ListNode node) {
node.next = null;
pool.add(node);
}
}
```
通过以上示例,我们可以看到如何使用内存池来管理链表节点的内存,避免频繁地进行内存分配与释放,从而提高程序的性能。
### 6.2 链表与其他数据结构的结合应用
链表在实际应用中经常与其他数据结构结合使用,比如将链表与栈或队列结合,构建出栈(`Stack`)或队列(`Queue`)的数据结构。下面是一个使用链表来实现栈的示例:
```java
public class MyStack {
private ListNode top;
public void push(int val) {
ListNode newNode = new ListNode(val);
newNode.next = top;
top = newNode;
}
public int pop() {
if (top == null) {
throw new EmptyStackException();
}
int val = top.val;
top = top.next;
return val;
}
public int peek() {
if (top == null) {
throw new EmptyStackException();
}
return top.val;
}
public boolean isEmpty() {
return top == null;
}
}
```
通过结合链表与其他数据结构的特点,我们可以灵活地应用链表在实际开发中,满足不同的需求。
### 6.3 链表的扩展应用:图、树等
除了在常规的线性数据结构中应用外,链表还可用于构建更复杂的数据结构,如图(`Graph`)和树(`Tree`)。在图的表示中,我们可以使用邻接表来表示图的结构;在树的表示中,链表可以用于构建二叉树、平衡二叉树等数据结构。
通过扩展链表的应用,我们可以更好地理解数据结构之间的联系,提高对数据结构和算法的理解和应用能力。
以上是链表的优化与扩展部分的内容,通过深入了解链表的内存管理、与其他数据结构的结合应用以及扩展应用的场景,可以更好地运用链表解决实际问题,提高程序的性能与可维护性。
0
0
相关推荐
![zip](https://img-home.csdnimg.cn/images/20241231045053.png)
![docx](https://img-home.csdnimg.cn/images/20241231044901.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)