C语言实现单向链表

本文详细介绍了C语言实现的单向链表,包括顺序表与链表的区别、单向链表的节点结构,以及头插、尾插、删除操作的实现,同时还讨论了删除操作的优化和mj版本链表的扩展功能。
摘要由CSDN通过智能技术生成

1.线性表

线性表可以分成两种 一种就是顺序表 也就是我们所熟知的数组 一种是今天的主角–链表
他们两者的区别在于 前者是顺序储存元素的 即数组元素的地址值是连续的
后者则是链式储存的 节点的地址值不一定连续

2.单向链表

链表中的节点是由两部分组成的 一部分是数据域 另外一部分是指针域
而且一般都是由虚拟头节点开头的 虚拟头节点的数据域储存的是链表的长度 指针域指向的是实际的头节点

#include <stdio.h> 
#include <stdlib.h>
// 定义一个节点的结构体
typedef struct Node{
	int data;// 数据域
	struct Node* next;// 指针域 由于这边还未起好别名 所以不能直接使用Node*
}Node;
// 定义一个函数 用于初始化链表
Ndoe* initList(){
	// 无非就是创建一个虚拟头节点 
	Node* list = (Node*)malloc(sizeof(Node));
	list->data = 0;
	list->next = NULL;
	return list;
}
// 定义一个函数 用于进行头插操作
void headInsert(Node* list, int data){
	// 一定要注意两个节点的指针域修改顺序 先改待插入节点的next 然后在修改虚拟头节点的next
	Node* newHead = (Node*)malloc(sizeof(Node));
	newHead->data = data;
	newHead->next = list->next;
	list->next = newHead;
	// 最后别忘了更新链表的长度
	list->data++;
}
// 定义一个函数 用于进行尾插操作
void tailInsert(Node* list, int data){
	// 你不要遍历过头了 如果当前节点的下一个节点为空的话 就停止遍历操作 而不是当前节点为空才停止遍历 因为你的目的是取出尾节点 而不是NULL
	// 保留你的虚拟头节点 因为list会随着遍历操作的进行而改变 
	Node* cur = list;
	while(cur->next){
		cur = cur->next;
	}
	Node* tail = (Node*)malloc(sizeof(Node));
	tail->data = data;
	tail->next = NULL;
	cur->next = tail;
	list->data++;
}
// 定义一个函数 用于删除指定元素对应的节点(删除第一个符合条件的节点就行了) 还得通过返回值表示删除成功与否
int delete(Node* list, int data){
	Node* pre = list->next;
	Node* cur = list->next;
	while(cur){
		// 这边的循环条件有别于tailInsert的原因在于 这边需要判断所有的节点是否和指定值相同 而tailInsert目的则是获取一个链表的尾节点而已
		if(cur->data == data){
			pre->next = cur->next;
			free(cur);
			// 更新链表长度
			list->data--;
			// 我们用1表示删除成功
			return 1;
		}
		// 如果当前节点不符合条件的话  则继续往下进行寻找
		pre = cur;
		cur = cur->next;
	}
	// 如果循环结束以后 都没有找到指定节点的话 说明删除失败
	return 0;// 我们用0表示删除失败
}
void printList(Node* list){
	Node* cur = list->next;
	while(cur){
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

测试代码如下所示:

int main() {
	Node* list = initList();
	headInsert(list, 1);
	headInsert(list, 2);
	headInsert(list, 3);
	headInsert(list, 4);
	tailInsert(list, 5);
	printList(list);
	if (delete(list, 0)) {
		printf("删除成功\n");
	}
	else {
		printf("删除失败\n");
	}
	printList(list);
	return 0;
}

3.删除代码的优化

如果单向链表的删除操作中 需要将指定值对应的所有节点都进行删除的话 那么需要用一下代码进行代替

int delete(Node* list, int data){
	// 定义一个变量 用于储存所删除元素的个数 
	int deleted = 0;
	Node* pre = list->next;
	Node* cur = list->next;
	while(cur){
		if(cur->data == data){
			pre->next = cur->next;
			free(cur);
			list->data--;
			// 同时需要更新deleted变量 因为后面需要通过该变量进行删除成功与否的判断
			deleted++;
			// 但是现在pre所表示的节点不会变 但是cur表示的节点需要改变 cur需要更新为pre的下一个节点
			cur = pre->next;
		}else{
			// 如果没有找到指定值对应的节点的话 那么就更新pre为cur 然后更新cur为当前节点的下一个节点
			pre = cur;
			cur = cur->next;
		}
	}
	// 然后最后直接返回deleted 如果为0 说明删除失败 如果为1的话 那么说明删除成功 并且可以从中知道被删除元素的个数
	return deleted;
}

测试代码如下所示:

int main() {
	Node* list = initList();
	headInsert(list, 1);
	headInsert(list, 1);
	headInsert(list, 2);
	headInsert(list, 3);
	headInsert(list, 4);
	tailInsert(list, 5);
	printList(list);
	int deleted = delete(list, 1);
	if (deleted) {
		printf("删除成功 并且被删除的元素个数为%d\n", deleted);
	}
	else {
		printf("删除失败\n");
	}
	printList(list);
	return 0;
}

4.mj版本的单向链表(C语言实现)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define ELEMENT_NOT_FOUND -1
// 定义一个节点类
typedef struct Node {
	int data;
	struct Node* next;
}Node;
// 初始化链表的方法
Node* initList() {
	// 创建一个虚拟头节点
	Node* list = (Node*)malloc(sizeof(Node));
	// 储存链表的长度
	list->data = 0;
	list->next = NULL;
	return list;
}
// 索引越界处理方法
void outOfBounds(int index) {
	printf("索引为%d 索引越界了", index);
}
// 边界检查方法
void rangeCheck(int index, Node* list) {
	if (index < 0 || index >= list->data)
		outOfBounds(index);
}
// 针对添加方法的边界检查方法
void rangeCheckForAdd(int index, Node* list) {
	if (index < 0 || index > list->data)
		outOfBounds(index);
}

// 获取指定索引处的节点
Node* node(int index, Node* list) {
	Node* cur = list->next;
	for (int i = 0; i < index; ++i) {
		cur = cur->next;
	}
	return cur;
}
// 添加方法
void add(int index, int data, Node* list) {
	// 对参数索引进行边界检查
	rangeCheckForAdd(index, list);
	Node* pre = index == 0 ? list : node(index - 1, list);
	Node* node = (Node*)malloc(sizeof(Node));
	node->data = data;
	node->next = pre->next;
	pre->next = node;
	// 更新链表长度
	list->data++;
}
// 删除方法
int delete(int index, Node* list) {
	// 对参数索引进行边界检查
	rangeCheck(index, list);
	Node* delete;
	Node* pre = index == 0 ? list : node(index - 1, list);
	delete = pre->next;
	pre->next = delete->next;
	// 更新链表长度
	list->data--;
	// 返回被删除的节点值
	return delete->data;
}
// 查找指定节点值的位置
int indexOf(int data, Node* list) {
	Node* cur = list->next;
	for (int i = 0; i < list->data; ++i) {
		if (cur->data == data) {
			return i;
		}
		cur = cur->next;
	}
	return ELEMENT_NOT_FOUND;
}
// 获取指定位置处的节点值
int get(int index, Node* list) {
	// 在get方法中无需进行边界检查操作 因为等一下的node方法的开头就会进行边界检查的操作了
	return node(index, list)->data;
}
// 重置指定位置处的节点值
int set(int index, int newData, Node* list) {
	int data = node(index, list)->data;
	node(index, list)->data = newData;
	return data;
}
// 清空方法
void clear(Node* list) {
	Node* cur = list->next;
	Node* next;
	while (cur) {
		next = cur->next;
		free(cur);
		cur = next;
	}
	list->data = 0;
	// 不仅仅需要将链表的长度置为0 而且还需要将虚拟头节点的下一个节点置为空才行
	list->next = NULL;
}
// 是否包含指定节点值的方法
bool contains(int data, Node* list) {
	return indexOf(data, list) != ELEMENT_NOT_FOUND;
}
// 链表是否为空
bool isEmpty(Node* list) {
	return list->data == 0;
}
// 获取链表长度
int size(Node* list) {
	return list->data;
}
// 打印方法
void printList(Node* list) {
	Node* cur = list->next;
	while (cur) {
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
int main() {
	Node* list = initList();
	add(0, 1, list);
	add(0, 2, list);
	add(0, 3, list);
	add(0, 4, list);
	add(size(list), 5, list);
	printList(list);
	printf("%d\n", get(0, list));
	printf("%d\n", set(0, 6, list));
	printf("%d\n", delete(0, list));
	printList(list);
	clear(list);
	printf("%d\n", size(list));
	return 0;
}

通过结果查看 顺利通过测试

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axihaihai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值