目录
[四、用 C 实现单向链表](#四、用 C 实现单向链表)
2.创建双向链表节点函数(createDoublyNode)
3.在双向链表尾部插入节点函数(insertAtTailDoubly)
5.反向打印双向链表函数(printDoublyListReverse)
2.创建循环链表节点函数(createCircularNode)
3.在循环链表尾部插入节点函数(insertAtTailCircular)
5.释放循环链表内存函数(freeCircularList)
一、引言
在 C 语言中,指针是一种强大的工具,它允许我们直接操作内存地址。而链表作为一种常见的数据结构,广泛应用于各种程序中。将指针与链表结合起来,可以实现高效、灵活的数据存储和操作。本文将深入探讨 C 指针与链表的概念、实现以及应用,帮助读者更好地理解和掌握这一重要的编程技术。
二、指针的基础概念
1.指针的定义与作用
- 指针是一个变量,它存储的是另一个变量的内存地址。通过指针,我们可以间接访问和操作内存中的数据。
- 指针的主要作用包括:动态内存分配、函数参数传递、实现复杂的数据结构(如链表、树、图等)。
2.指针的类型与取值
- 指针有不同的类型,如指向整数的指针(
int *
)、指向字符的指针(char *
)等。指针的类型决定了它所指向的数据的类型。 - 指针的值是一个内存地址,可以通过取地址运算符(
&
)获取变量的地址,并将其赋值给指针。例如,int a = 10; int *p = &a;,这里p
就是一个指向整数a
的指针。
3.指针的运算
- 指针可以进行一些特定的运算,如加法、减法和比较。指针的加法和减法通常是按照指针所指向的数据类型的大小进行的。例如,如果指针指向一个整数,那么指针加一将指向下一个整数的地址。
- 指针的比较可以用于判断两个指针是否指向同一个地址或者判断指针是否在某个范围内。
三、链表的基本概念
1.链表的定义与特点
- 链表是一种动态的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针,链表的起点为头节点,尾节点的指针域为NULL。链表的特点是可以动态地增长和缩小,不需要预先知道数据的数量。
- 与数组相比,链表的优点是插入和删除元素的操作更加高效,不需要移动大量的数据。缺点是访问特定位置的元素需要遍历链表,时间复杂度较高。
2.链表的类型
- 单向链表:每个节点只有一个指向下一个节点的指针,只能从链表的头部开始遍历到尾部。
- 双向链表:每个节点有两个指针,一个指向前一个节点,一个指向下一个节点。双向链表可以在两个方向上进行遍历,并且在某些操作(如删除节点)上更加高效。
- 循环链表:最后一个节点的指针指向链表的头部,形成一个循环。循环链表可以方便地实现一些特定的算法,如约瑟夫问题。
四、用 C 实现单向链表
cpp
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构
struct ListNode {
int data;
struct ListNode* next;
};
// 创建链表节点
struct ListNode* createNode(int data) {
struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表尾部插入节点
void insertAtTail(struct ListNode** head, int data) {
struct ListNode* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
struct ListNode* current = *head;
while (current->next!= NULL) {
current = current->next;
}
current->next = newNode;
}
// 打印链表
void printList(struct ListNode* head) {
struct ListNode* current = head;
while (current!= NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 释放链表内存
void freeList(struct ListNode* head) {
struct ListNode* current = head;
while (current!= NULL) {
struct ListNode* next = current->next;
free(current);
current = next;
}
}
以下是对上述代码的分析:
1.定义链表节点结构
struct ListNode
定义了链表的节点结构,包含一个整数data
用于存储数据,以及一个指向下一个节点的指针next
。
2.创建链表节点函数(createNode)
- 这个函数接受一个整数参数
data
,用于初始化新节点的数据域。 - 首先使用
malloc
动态分配内存来创建一个新的节点。 - 然后将传入的数据赋值给新节点的
data
成员,并将next
成员初始化为NULL
,表示这个节点目前没有下一个节点。 - 最后返回新创建的节点指针。
3.在链表尾部插入节点函数(insertAtTail)
- 该函数接受一个链表头指针的指针
head
(双重指针)和一个整数data
作为参数,用于在链表尾部插入一个新节点。 - 首先创建一个新节点,使用
createNode(data)
函数。 - 如果链表为空(即
*head
为NULL
),则将新节点设置为链表的头节点。 - 如果链表不为空,则遍历链表找到最后一个节点。通过一个
while
循环,从链表头开始,依次移动current
指针,直到current->next
为NULL
,即找到最后一个节点。 - 找到最后一个节点后,将其
next
指针指向新创建的节点,从而将新节点插入到链表尾部。
4.打印链表函数(printList)
- 这个函数接受一个链表头指针
head
作为参数,用于打印链表中的所有数据。 - 从链表头开始,使用一个
while
循环遍历链表。只要当前节点不为NULL
,就打印当前节点的数据,并将current
指针移动到下一个节点。
5.释放链表内存函数(freeList)
- 该函数用于释放链表所占用的内存空间。
- 从链表头开始,使用一个
while
循环遍历链表。对于每个节点,先保存下一个节点的指针next
,然后释放当前节点的内存,最后将current
指针移动到下一个节点。
以下是使用这些函数创建和操作链表的示例:
cpp
int main() {
struct ListNode* head = NULL;
insertAtTail(&head, 1);
insertAtTail(&head, 2);
insertAtTail(&head, 3);
printList(head);
freeList(head);
return 0;
}
在main
函数中,首先创建一个空链表(head = NULL
),然后通过调用insertAtTail
函数向链表中插入三个节点,最后调用printList
函数打印链表内容,并调用freeList
函数释放链表占用的内存。
五、用C实现双向链表
cpp
#include <stdio.h>
#include <stdlib.h>
// 定义双向链表节点结构
struct DoublyListNode {
int data;
struct DoublyListNode* prev;
struct DoublyListNode* next;
};
// 创建双向链表节点
struct DoublyListNode* createDoublyNode(int data) {
struct DoublyListNode* newNode = (struct DoublyListNode*)malloc(sizeof(struct DoublyListNode));
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
// 在双向链表尾部插入节点
void insertAtTailDoubly(struct DoublyListNode** head, int data) {
struct DoublyListNode* newNode = createDoublyNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
struct DoublyListNode* current = *head;
while (current->next!= NULL) {
current = current->next;
}
current->next = newNode;
newNode->prev = current;
}
// 打印双向链表
void printDoublyList(struct DoublyListNode* head) {
struct DoublyListNode* current = head;
while (current!= NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 反向打印双向链表
void printDoublyListReverse(struct DoublyListNode* tail) {
struct DoublyListNode* current = tail;
while (current!= NULL) {
printf("%d ", current->data);
current = current->prev;
}
printf("\n");
}
// 释放双向链表内存
void freeDoublyList(struct DoublyListNode* head) {
struct DoublyListNode* current = head;
while (current!= NULL) {
struct DoublyListNode* next = current->next;
free(current);
current = next;
}
}
1.定义双向链表节点结构
struct DoublyListNode
定义了双向链表的节点结构,包含一个整数data
用于存储数据,以及两个指针prev
和next
分别指向前一个节点和后一个节点。
2.创建双向链表节点函数(createDoublyNode)
- 与单向链表的创建节点函数类似,接受一个整数参数
data
,动态分配内存创建新节点,初始化数据域为data
,并将前后指针初始化为NULL
。
3.在双向链表尾部插入节点函数(insertAtTailDoubly)
- 创建新节点后,如果链表为空,直接将新节点设置为链表头。
- 如果链表不为空,遍历链表找到最后一个节点,然后将新节点插入到最后一个节点之后,并更新新节点和原最后一个节点的前后指针关系。
4.打印双向链表函数(printDoublyList)
从链表头开始,依次遍历并打印每个节点的数据,与单向链表的打印函数类似。
5.反向打印双向链表函数(printDoublyListReverse)
从链表尾开始,通过反向遍历(利用prev
指针)并打印每个节点的数据。
6.释放双向链表内存函数(freeDoublyList)
与单向链表的释放内存函数类似,从链表头开始遍历,释放每个节点的内存。
使用双向链表的示例:
cpp
int main() {
struct DoublyListNode* head = NULL;
insertAtTailDoubly(&head, 1);
insertAtTailDoubly(&head, 2);
insertAtTailDoubly(&head, 3);
printDoublyList(head);
printDoublyListReverse(head);
freeDoublyList(head);
return 0;
}
六、用C实现循环链表
cpp
#include <stdio.h>
#include <stdlib.h>
// 定义循环链表节点结构
struct CircularListNode {
int data;
struct CircularListNode* next;
};
// 创建循环链表节点
struct CircularListNode* createCircularNode(int data) {
struct CircularListNode* newNode = (struct CircularListNode*)malloc(sizeof(struct CircularListNode));
newNode->data = data;
newNode->next = newNode; // 初始时指向自身
return newNode;
}
// 在循环链表尾部插入节点
void insertAtTailCircular(struct CircularListNode** head, int data) {
struct CircularListNode* newNode = createCircularNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
struct CircularListNode* current = *head;
while (current->next!= *head) {
current = current->next;
}
current->next = newNode;
newNode->next = *head;
}
// 打印循环链表
void printCircularList(struct CircularListNode* head) {
if (head == NULL) return;
struct CircularListNode* current = head;
do {
printf("%d ", current->data);
current = current->next;
} while (current!= head);
printf("\n");
}
// 释放循环链表内存
void freeCircularList(struct CircularListNode* head) {
if (head == NULL) return;
struct CircularListNode* current = head;
struct CircularListNode* temp;
do {
temp = current;
current = current->next;
free(temp);
} while (current!= head);
}
分析:
1.定义循环链表节点结构
struct CircularListNode
定义了循环链表的节点结构,包含一个整数data
用于存储数据,以及一个指针next
指向下一个节点。
2.创建循环链表节点函数(createCircularNode)
- 接受一个整数参数
data
,动态分配内存创建新节点,初始化数据域为data
,并将next
指针初始化为指向自身,因为单个节点的循环链表就是指向自己。
3.在循环链表尾部插入节点函数(insertAtTailCircular)
- 创建新节点后,如果链表为空,直接将新节点设置为链表头。
- 如果链表不为空,遍历链表找到最后一个节点(即当前节点的下一个节点是头节点的节点),然后将新节点插入到最后一个节点之后,并更新新节点的
next
指针指向链表头。
4.打印循环链表函数(printCircularList)
- 从链表头开始,使用
do-while
循环遍历链表,直到再次回到头节点为止,依次打印每个节点的数据。
5.释放循环链表内存函数(freeCircularList)
- 从链表头开始,使用
do-while
循环遍历链表,释放每个节点的内存,直到再次回到头节点为止。
使用循环链表的示例:
七、总结
C 指针与链表是 C 语言编程中的重要概念和技术。通过指针,我们可以灵活地操作内存地址,实现高效的数据存储和操作。链表作为一种动态的数据结构,在很多场景下都有广泛的应用。掌握指针与链表的使用方法,对于深入理解 C 语言编程和实现复杂的数据结构至关重要。希望本文能够帮助读者更好地理解和应用 C 指针与链表。