数据结构学习(2)——多功能链表的实现(C语言)

上次我们讲到了如何创建一个普通的链表,今天我们将优化这个链表,使其拥有添加数据、插入数据、删除数据、查找数据、替换数据、退出程序等功能,成为一个较为完整的链表。

思维搭建

由于需要实现的功能较多,我们应该想到将各个功能封装成一个个函数,并给每个功能标上编号,根据用户输入的编号在主函数中调用对应的函数,实现对应的功能。同时在创建链表方面,一个节点需要保存下一个节点的位置和当前节点的数据,因此需要创建一个结构体(详细见上一篇博客),再依次将数据保存、相连,得到一个链表。

实现步骤

一、输出提示语句,获取用户输入的功能编号

我们可以在主函数中根据自己的定义方法输出一系列语句,提示用户各种功能对应的编号,并且获取键盘的输入值。同时需要注意的是:我们需要实现的效果是每次完成一个功能,就要再弹出一次这些提示语句和输入功能编号,所以我们需要用while关键字写一个死循环(1在C语言中代表true,所以一直都会通过)。

cs 复制代码
	while (1) {
		printf("功能编号列表:\n");
		printf("==101:在末尾添加元素\n");
		printf("==102:在指定位置添加元素\n");
		printf("==103:替换指定位置的元素\n");
		printf("==201:打印链表中的数据\n");
		printf("==301:删除元素\n");
		printf("==501:查找一个指定的位置的元素\n");
		printf("==502:查找一个指定的元素的位置\n");
		printf("==601:清空链表\n");
		printf("==999:退出程序\n");
		printf("请输入功能编号:\n");
		int cid;
		scanf("%d", &cid);
}

二、书写一个链表的结构体

我们在上一篇博客中提到了创建一个链表的方法,需要一个节点结构体包含data和next属性。(详见上一篇博客)

cs 复制代码
//节点结构体
struct ListNode {
	int data;
	ListNode *next;
};

三、书写每个功能对应的函数

1、创建新节点

由于在许多功能中都涉及到创建一个新节点的操作,我们不妨把这个操作包装成一个函数,在需要创建时直接调用即可,不需要重复书写相同的代码,使代码更加简洁。

该函数的返回值应该是一个节点(指针类型),也就是上面结构体的类型ListNode,需要的参数是data(该节点对应的数据)。我们给这个新节点分配一块空间后,将入的参数赋值该节点的data,并将下一个节点设为NULL,并返回这个节点就可以创建一个新的节点了。

cs 复制代码
ListNode *createNode(int data) {
	ListNode *newNode = (ListNode *)malloc(sizeof(ListNode));
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}

2、在末尾添加元素

这与上篇博客提到的实现方法一致,书写一个返回值为ListNode的函数,需要传入参数有:data(添加的数据)、head(头节点)。注:几乎所有的操作都需要头节点,因为这是操作链表的唯一入口。利用我们刚写好的函数创建一个新的节点,并单独写判断语句排除节点创建失败和空链表的情况,然后建立一个临时的变量保存头节点(防止头节点被改变),然后开始对这个*temp进行类似于遍历的操作,知道*temp的下一个为空(说明到末尾了),就退出循环。最后,将temp的下一个赋值为新节点newNode即可。(最后记得返回头节点)

cs 复制代码
ListNode *add(int data, ListNode *head) {
	ListNode *newNode = createNode(data);
	if (newNode == NULL) {
		printf("新节点创建失败,无法分配内存~");
		return head;
	}
	if (head == NULL) { //说明是空链表
		return newNode;
	}
	ListNode *temp = head;
	while (temp->next != NULL) {
		temp = temp->next;
	}
	//循环结束后,temp是最后一个节点,要将新的节点加载到它的后面
	temp->next = newNode;
	return head;
}

3、在指定位置添加元素

基本与在末尾添加一致,不过参数还需要index,明确需要插入的位置(在这里我们以插入index后面为例)。

我们还需要一个计数变量,让其每次都递增,直到它等于index(说明找到位置了),在尚未等于index时都需要将temp赋值为下一个。

找到位置后,将temp在index位置的下一个赋值给新节点的下一个,将temp的下一个赋值为新节点。(可以通过画示意图来理解)

cs 复制代码
ListNode *insert(int index, int data, ListNode *head) {
	ListNode *newNode = createNode(data);
	if (newNode == NULL) {
		printf("新节点创建失败,无法分配内存~");
		return head;
	}
	if (head == NULL) { //说明是空链表
		return newNode;
	}
	ListNode *temp = head;
	int count = 0;
	while (temp->next != NULL) {
		if (count == index) {
			newNode->next = temp->next;
			temp->next = newNode;
			break;
		}
		temp = temp->next;
		//没通过判断就继续递增,直到等于index
		count++;
	}
	return head;
}

4、替换指定位置的元素

与插入元素类似,只不过不是创建一个新的节点插入,而是直接更改该节点的数据,不需要操作指针。

cs 复制代码
ListNode *replace(int index, int data, ListNode *head) {
	int count = 0;
	ListNode *cur = head;
	while (cur != NULL && count < index) {
		cur = cur->next;
		count++;
	}
	cur->data = data;
	return head;
}

5、打印链表中的数据

这个功能其实在每一次执行完一个功能后都应该调用一次,来显示出操作的效果。

这个功能不需要返回值,同样是利用链表的遍历方法,不断将temp赋值为下一个节点,逐个打印出该节点的元素。同时,应该有一个判断在前:如果链表为空,直接输出一个空括号[ ],防止程序因通不过下面的条件而卡在中间进程。

还有,由于在末尾的节点下一个为空,无法赋值到temp,导致最后一个元素的data打印不了,所以还应该有一个单独的输出语句打印末尾节点的数据。

cs 复制代码
void printList(ListNode *head) {
	ListNode *temp = head;
	if (head == NULL) {
		printf("\n链表为:[]");
	} else {
		printf("\n链表为:[");
		while (temp->next != NULL) {
			printf("%d->", temp->data);
			temp = temp->next;
		}
		printf("%d]\n", temp->data);
	}
}

6、删除元素(通过下标)

需要传入的参数是index、*head。

思路应该是通过遍历到达要删除元素的位置,设置一个prev表示上一个节点,cur表示当前节点(需要保证一直有两个连续的节点可以操作)。单独书写删除头节点的情况(头节点没有上一个元素),定义一个变量保存原始的头节点,然后直接将头节点赋值为下一个节点,用free关键字清楚temp的 空间即可。注意:不可以直接free(head),否则链表就没有头节点。

若不是删除头节点,就要不断将cur往下遍历,而prev等于还没往下移动的cur,并用计数变量确定位置。到达index后,直接将cur的下一个赋值给prev的下一个,将cur的内存释放掉即可。

cs 复制代码
//通过下标删除元素
ListNode *cutByE(int index, ListNode *head) {
	int count = 0;
	if (head == NULL) {
		printf("链表为空,无法删除元素");
	}
	//删除头节点的情况
	if (index == 0) {
		ListNode *temp = head;
		head = head->next;
		free(temp);
		return head;
	}
	ListNode *prev = NULL;
	ListNode *cur = head;
	while (cur != NULL && count < index) {
		prev = cur;
		cur = cur->next;
		count++;
	}
	//执行删除操作
	prev->next = cur->next;
	free(cur);
	return head;
}

7、查找一个指定的位置的元素

给定一个参数index,通过计数变量,经过遍历到达指定位置,然后将该节点的数据返回即可。

cs 复制代码
//查找指定位置的元素
int getByi(int index, ListNode *head) {
	int count = 0;
	ListNode *temp = head;
	while (temp != NULL && count < index) {
		temp = temp->next;
		count++;
	}
	return temp->data;
}

8、查找一个指定的元素的位置

同样道理,不断遍历直到要求的data等于链表中某个节点的data,通过计数变量测定位置,最后返回该计数变量即可。

cs 复制代码
//查找指定元素的位置
int getByE(int data, ListNode *head) {
	ListNode *temp = head;
	int index = 0;
	while (data != temp->data && temp->next != NULL) {
		temp = temp->next;
		index++;
	}
	return index;
}

9、清空链表

清空链表本质就是释放内存,只需要不断遍历并将每个temp释放内存即可。

cs 复制代码
//清空链表
void clean(ListNode *head) {
	ListNode *cur = head;
	while (cur->next != NULL) {
		ListNode *temp = cur;
		cur = cur->next;
		free(temp);
	}
}

10、退出程序

使用exit关键字,参数给0即可退出程序的运行。

cs 复制代码
//推出程序
void esc() {
	printf("[999退出程序] 程序退出成功!");
	exit(0);
}

四、调用函数

在主函数中,书写一些判断语句分析用户输入的编号,调用对应的函数,给定要求的参数即可。

cs 复制代码
if (cid == 101) {
			printf("[101添加元素]>>请输入一个数字:");
			int data;
			scanf("%d", &data);
			head = add(data, head);
		} else if (cid == 102) {
			printf("[102插入元素]>>请输入插入的位置和数据:");
			int index, data;
			scanf("%d %d", &index, &data);
			head = insert(index, data, head);
		} else if (cid == 103) {
			printf("[103替换元素]>>请输入替换的位置和新数据:");
			int index, data;
			scanf("%d %d", &index, &data);
			head = replace(index, data, head);
		} else if (cid == 501) {
			printf("[501查找指定位置的元素]>>请输入所需查找的位置:");
			int index;
			scanf("%d", &index);
			printf("该位置的元素为:");
			int data = getByi(index, head);
			printf("%d", data);
		} else if (cid == 502) {
			printf("[502查找指定元素的位置]>>请输入所需查找的元素:");
			int data;
			scanf("%d", &data);
			printf("该元素的位置为:");
			int place = getByE(data, head);
			printf("%d", place);
		} else if (cid == 601) {
			printf("[601清空链表]");
			clean(head);
			head = NULL;
		} else if (cid == 999) {
			esc();
		} else if (cid == 301) {
			int index;
			printf("[301删除元素]>>请输入所删除数据的下标:");
			scanf("%d", &index);
			head = cutByE(index, head);
		}

代码展示

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

//节点结构体
struct ListNode {
	int data;
	ListNode *next;
};

//创建新节点
ListNode *createNode(int data) {
	ListNode *newNode = (ListNode *)malloc(sizeof(ListNode));
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}

//添加元素
ListNode *add(int data, ListNode *head) {
	ListNode *newNode = createNode(data);
	if (newNode == NULL) {
		printf("新节点创建失败,无法分配内存~");
		return head;
	}
	if (head == NULL) { //说明是空链表
		return newNode;
	}
	ListNode *temp = head;
	while (temp->next != NULL) {
		temp = temp->next;
	}
	//循环结束后,temp是最后一个节点,要将新的节点加载到它的后面
	temp->next = newNode;
	return head;
}

//将数据插入到指定的位置
ListNode *insert(int index, int data, ListNode *head) {
	ListNode *newNode = createNode(data);
	if (newNode == NULL) {
		printf("新节点创建失败,无法分配内存~");
		return head;
	}
	if (head == NULL) { //说明是空链表
		return newNode;
	}
	ListNode *temp = head;
	int count = 0;
	while (temp->next != NULL) {
		if (count == index) {
			newNode->next = temp->next;
			temp->next = newNode;
			break;
		}
		temp = temp->next;
		//没通过判断就继续递增,直到等于index
		count++;
	}
	return head;
}

//替换指定位置的元素
ListNode *replace(int index, int data, ListNode *head) {
	int count = 0;
	ListNode *cur = head;
	while (cur != NULL && count < index) {
		cur = cur->next;
		count++;
	}
	cur->data = data;
	return head;
}

//通过下标删除元素
ListNode *cutByE(int index, ListNode *head) {
	int count = 0;
	if (head == NULL) {
		printf("链表为空,无法删除元素");
	}
	//删除头节点的情况
	if (index == 0) {
		ListNode *temp = head;
		head = head->next;
		free(temp);
		return head;
	}
	ListNode *prev = NULL;
	ListNode *cur = head;
	while (cur != NULL && count < index) {
		prev = cur;
		cur = cur->next;
		count++;
	}
	//执行删除操作
	prev->next = cur->next;
	free(cur);
	return head;
}

//查找指定位置的元素
int getByi(int index, ListNode *head) {
	int count = 0;
	ListNode *temp = head;
	while (temp != NULL && count < index) {
		temp = temp->next;
		count++;
	}
	return temp->data;
}

//查找指定元素的位置
int getByE(int data, ListNode *head) {
	ListNode *temp = head;
	int index = 0;
	while (data != temp->data && temp->next != NULL) {
		temp = temp->next;
		index++;
	}
	return index;
}

//清空链表
void clean(ListNode *head) {
	ListNode *cur = head;
	while (cur->next != NULL) {
		ListNode *temp = cur;
		cur = cur->next;
		free(temp);
	}
}

//推出程序
void esc() {
	printf("[999退出程序] 程序退出成功!");
	exit(0);
}

//打印链表
void printList(ListNode *head) {
	ListNode *temp = head;
	if (head == NULL) {
		printf("\n链表为:[]");
	} else {
		printf("\n链表为:[");
		while (temp->next != NULL) {
			printf("%d->", temp->data);
			temp = temp->next;
		}
		printf("%d]\n", temp->data);
	}
}

int main() {
	ListNode *head = NULL;
	while (1) {
		printf("功能编号列表:\n");
		printf("==101:在末尾添加元素\n");
		printf("==102:在指定位置添加元素\n");
		printf("==103:替换指定位置的元素\n");
		printf("==201:打印链表中的数据\n");
		printf("==301:删除元素\n");
		printf("==501:查找一个指定的位置的元素\n");
		printf("==502:查找一个指定的元素的位置\n");
		printf("==601:清空链表\n");
		printf("==999:退出程序\n");
		printf("请输入功能编号:\n");
		int cid;
		scanf("%d", &cid);
		if (cid == 101) {
			printf("[101添加元素]>>请输入一个数字:");
			int data;
			scanf("%d", &data);
			head = add(data, head);
		} else if (cid == 102) {
			printf("[102插入元素]>>请输入插入的位置和数据:");
			int index, data;
			scanf("%d %d", &index, &data);
			head = insert(index, data, head);
		} else if (cid == 103) {
			printf("[103替换元素]>>请输入替换的位置和新数据:");
			int index, data;
			scanf("%d %d", &index, &data);
			head = replace(index, data, head);
		} else if (cid == 501) {
			printf("[501查找指定位置的元素]>>请输入所需查找的位置:");
			int index;
			scanf("%d", &index);
			printf("该位置的元素为:");
			int data = getByi(index, head);
			printf("%d", data);
		} else if (cid == 502) {
			printf("[502查找指定元素的位置]>>请输入所需查找的元素:");
			int data;
			scanf("%d", &data);
			printf("该元素的位置为:");
			int place = getByE(data, head);
			printf("%d", place);
		} else if (cid == 601) {
			printf("[601清空链表]");
			clean(head);
			head = NULL;
		} else if (cid == 999) {
			esc();
		} else if (cid == 301) {
			int index;
			printf("[301删除元素]>>请输入所删除数据的下标:");
			scanf("%d", &index);
			head = cutByE(index, head);
		}
		printList(head);
	}
	return 0;
}
相关推荐
武文斌7719 小时前
PCB画板:电阻、电容、电感、二极管、三极管、mos管
单片机·嵌入式硬件·学习
sigd20 小时前
排队选人-2024年秋招-小米集团-软件开发岗-第二批笔试
数据结构·算法
charlie11451419120 小时前
CSS学习笔记6:定位与布局
前端·css·笔记·学习·css3·教程
自由日记20 小时前
css学习盒模型:
前端·css·学习
执笔论英雄20 小时前
【大模型推理】sglang 源码学习设计模式: 策略和访问者
python·学习·设计模式
容器( ु⁎ᴗ_ᴗ⁎)ु.。oO1 天前
Magentic-ui 学习
学习
_李小白1 天前
【OPENGL ES 3.0 学习笔记】延伸阅读:VAO与VBO
笔记·学习·elasticsearch
微露清风1 天前
系统性学习C++-第九讲-list类
c++·学习·list
海边夕阳20061 天前
【每天一个AI小知识】:什么是零样本学习?
人工智能·经验分享·学习
墨染点香1 天前
LeetCode 刷题【138. 随机链表的复制】
算法·leetcode·链表