数据结构学习(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;
}
相关推荐
nsjqj2 小时前
数据结构:栈和队列
数据结构
iconball3 小时前
个人用云计算学习笔记 --18(NFS 服务器、iSCSI 服务器)
linux·运维·笔记·学习·云计算
肥肠可耐的西西公主3 小时前
后端(JavaWeb)学习笔记(CLASS 1):maven
笔记·学习·maven
xwl12123 小时前
10.6 作业
数据结构·算法
DKPT6 小时前
JVM栈溢出和堆溢出哪个先满?
java·开发语言·jvm·笔记·学习
Rock_yzh9 小时前
AI学习日记——参数的初始化
人工智能·python·深度学习·学习·机器学习
今天只学一颗糖12 小时前
Linux学习笔记--insmod 命令
linux·笔记·学习
charlie11451419112 小时前
精读C++20设计模式:行为型设计模式:中介者模式
c++·学习·设计模式·c++20·中介者模式
楼田莉子12 小时前
Qt开发学习——QtCreator深度介绍/程序运行/开发规范/对象树
开发语言·前端·c++·qt·学习