1. 定义与声明单链表
1.1 定义单链表
struct Lnode{ // 定义名位Lnode的结构体
ElemType data; // 数据域
struct Lnode *next; // 同类型的指针域
};
typedef struct Lnode Lnode; // 对struct Lnode起别名Lnode
typedef struct Lnode *LinkList; // 对struct Lnode* 起别名 *LinkList
typedef struct Lnode {
ElemType data;
struct Lnode *next;
}Lnode, *LinkList;
1.2 声明单链表指针
Lnode* L; // 声明一个指针P指向Lnode数据类型
LinkList L; // 类型的一个变量,该变量本身是一个指针
2. 单链表构建
两种单链表,一种带有头节点,一种不带有头节点,如果不带头节点,那么直接将指针指向NULL即可,如果带头节点,就要分配内存初始化指针L的指向,也就是初始化一个节点
2.1 不带头节点的单链表
// 1. 初始化空单链表
bool InitList(LinkList L){ // 传入单链表的头指针
*L = NULL; // 将头指针取值,赋值为NULL
return true;
}
// 2. 判空操作
bool Empty(LinkList L){
return ((L == NULL));
}
// 3. 测试
void test(){
LinkList L; // 声明指针
InitList(&L); // 传地址初始化指针
if (Empty(L)){printf("链表为空。\n");}
else {printf("链表不为空。\n");}
}
int main() {
test();
return 0;
}
2.2 带头节点的单链表
// 1.初始化单链表
bool InitList(LinkList L) { // 传入指针
*L = (Lnode*)malloc(sizeof(Lnode));
if (*L == NULL) {return false;} // 内存分配错误
(*L)->next = NULL;
return true;
}
// 2.链表判空
bool empty(LinkList L) {
return (L->next == NUll);
}
// 3.测试
void test() {
LinkList L; // 初始化指针
InitList(&L)
if (Empty(L)){printf("链表为空。\n");}
else {printf("链表不为空。\n");}
}
int main() {
test(); // 调用测试函数
return 0;
}
3. 单链表的操作
3.1 按位插入-带头节点
给你一个我想要的位序i,在位序i的位置插入元素e,定义指针pointer走向第i-1各位序,然后分配新节点的内存空间,存放元素数据e,将链表进行拼接
bool ListInsert(LinkList* L, int i, int e) {
if (i < 1) { return false; } // i的有效性
Lnode* Pointer; // 定义指针
Pointer = L; // 指针Pointer指向链表头节点
int count = 0; // 在序位为3插入元素,则指针走到2
while (count < i - 1) {
Pointer = Pointer->next;
count++;
}
Lnode* new = (Lnode*)malloc(sizeof(Lnode)); // 新建节点,存储值e,连接链表
new->data = e;
new->next = Pointer->next;
Pointer->next = new;
return true;
}
3.2 按位插入-不带头节点
如果是不带头节点的单向链表,在对位序位1时要进行特殊处理,创建新节点分配内存,对链表进行拼接处理,否则照常处理即可。
bool ListInsert2(LinkList* L, int i, int e) {
if (i < 1) { return false; } // i的有效性
if (i == 1) { // 插入序位为1,单独处理
Lnode* new = (Lnode*)malloc(sizeof(Lnode)); // 定义新的节点指针
new->data = e;
new->next = L; // 将新指针next指向链表头节点L
L = new; // 改变头节点指向,指向new
}
Lnode* Pointer; // 否则照常处理
Pointer = L;
int count = 0;
while (count < i - 1) {
Pointer = Pointer->next;
count++;
}
Lnode* neww = (Lnode*)malloc(sizeof(Lnode));
neww->data = e;
neww->next = Pointer->next;
Pointer->next = neww;
return true;
}
3.3 后插操作
在指针p后面插入元素e,新增节点存放数据e,进行数据拼接
bool InsertNextNode(Lnode* p, int e) {
if (!p) { return false; } // 若指针指向为空
Lnode* node = (Lnode*)malloc(sizeof(Lnode));
if (!node) { return false; } // 若分配内存出错
node->data = e;
node->next = p->next;
p->next = node;
}
3.4 前插操作
在指针p前面插入元素e,因为单向链表无法获取当前节点的前驱节点,所以使用偷天换日大法,新增节点,复制指针p的元素到新指针数据域中,再把指针p存放元素e,完成链表拼接。
bool InsertPriorNode(Lnode* p, int e) {
if (!p) { return false; }
Lnode* node = (Lnode*)malloc(sizeof(Lnode));
if (!node) { return false; }
node->next = p->next;
p->next = node;
node->data = p->data;
p->data = e;
}
3.5 删除操作
给你一个链表L,想要删除位序位i的内容,并将数据域作e进行返回,首先先定义指针pointer指向链表头节点,然后走到第i-1个位序中,新建指针e_res指向位序i,也就是要删除的元素,存储数据e,完成pointer指针next的重赋值,释放指针e_res的内存空间。
bool ListDelete(LinkList* L, int i, int* e) {
if (i < 1) { return false; } // i的有效性
Lnode* pointer; // 新增指针指向链表头节点
pointer = L;
int count = 0;
while (count < i - 1) { // 走到序列为i-1的位置
pointer = pointer->next;
count++;
}
if (pointer == NULL || pointer->next == NULL) {return false;}
Lnode* e_res; // 新增节点存储要删除的值
e_res = pointer->next;
e = pointer->data;
pointer->next = e_res->next; // p节点的next处理
free(e_res);
return true;
}
4. 单链表的查询
4.1 按位查找
给出一个链表,找到位序为i的节点,返回指针
Lnode* GetElem(LinkList L, int i) {
if (i < 0) { return NULL; }
Lnode* pointer = L; // 定义指针pointer指向链表头节点L
int count = 0;
while (pointer != NULL && count < i) {
pointer = pointer->next;
count++;
}
return pointer;
}
4.2 按值查找
给出一个链表,找到节点值为e的节点,返回指针
Lnode* LocateElem(LinkList* L, int e) {
Lnode* pointer = &L->next; // 因为是带有头节点,所以从L->next开始寻找
while (pointer != NULL && pointer->data != e) { // 如果pointer=NULL,则跳出循环返回NULL
pointer = pointer->next;
}
return pointer; // 如果找到pointer->data=e,退出循环返回正确值
}
4.3 求链表长度
int Length(LinkList* L) {
Lnode* pointer = L;
int count = 0;
while (pointer->next != NULL) {
pointer = pointer->next;
count++;
}
return count;
}
5. 单链表的建立
5.1 基于尾插法的单链表建立
# include <stdio.h>
# include <stdlib.h> // 需要包含stdlib.h以使用malloc和free
# include <stdbool.h>
// 1. 创建结构体
typedef struct Lnode {
int data;
struct Lnode* next;
} Lnode, *LinkList;
// 2. 定义初始化单链表函数(带头节点)
bool InitList(LinkList* L) {
*L = (LinkList)malloc(sizeof(Lnode)); // 分配头节点的内存空间
if (*L == NULL) {
printf("内存分配失败...\n");
return false;
}
(*L)->next = NULL;
return true;
}
// 3. 选择插入方式
// 3.1 如果选择按位插入,每次调用函数的时候,都需要遍历一次链表,时间复杂度高
// 3.2 选择后插法,定义一个指针永远指向链表尾部,每次都在尾部执行调用后插法
// 3.3 定义后插操作
bool InsertNextNode(Lnode* pointer, int e) {
if (!pointer) { return false; }
Lnode* node = (Lnode*)malloc(sizeof(Lnode));
node->next = NULL;
if (!node) { printf("内存分配不够...\n"); return false; }
node->data = e;
node->next = pointer->next;
pointer->next = node;
return true;
}
// 3.4 定义尾插法总函数
void List_TailInsert(LinkList* L) {
Lnode* tail_pointer = *L; // tail_pointer指向头节点
int x;
while (true) {
scanf_s("%d", &x); // 读取用户输入,使用scanf_s
if (x == -1) { // 输入-1作为结束标志
break;
}
bool res = InsertNextNode(tail_pointer, x);
if (res) {
printf("插入数据 %d 成功!\n", x);
tail_pointer = tail_pointer->next; // 更新tail_pointer到新插入的节点
}
else {
printf("插入数据失败...\n");
}
}
}
// 3.5 打印链表
void PrintList(LinkList L) {
Lnode* p = L->next; // 从第一个节点开始
while (p != NULL) {
printf("%d ", p->data); // 打印当前节点的数据
p = p->next; // 指针移动到下一个节点
}
printf("\n"); // 换行
}
// 3.7 测试
void test() {
LinkList L; // 定义指针指向链表头节点
if (InitList(&L)) { // 初始化链表
printf("链表初始化成功。\n");
}
else {
printf("链表初始化失败!\n");
return;
}
List_TailInsert(&L); // 调用尾插法建立链表
printf("链表数据:");
PrintList(L); // 打印链表
}
int main() {
test();
return 0;
}
5.2 基于头插法的单链表建立
- 头插法建立单链表,实际上是单链表逆序的一种方法,还有另一种方法,借助临时变量
tmp
和pre
,实现原地逆转。 - 在基于“尾插法”的基础上进行修改,每次后插都在虚拟头节点之后插入新数据