顺序表-单链表



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;
}

![[Pasted image 20241021211214.png]]

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;
}

![[Pasted image 20241021211345.png]]

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;
}

![[Pasted image 20241021212754.png]]


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 基于头插法的单链表建立
  • 头插法建立单链表,实际上是单链表逆序的一种方法,还有另一种方法,借助临时变量 tmppre ,实现原地逆转。
  • 在基于“尾插法”的基础上进行修改,每次后插都在虚拟头节点之后插入新数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值