上次我们讲到了如何创建一个普通的链表,今天我们将优化这个链表,使其拥有添加数据、插入数据、删除数据、查找数据、替换数据、退出程序等功能,成为一个较为完整的链表。
思维搭建
由于需要实现的功能较多,我们应该想到将各个功能封装成一个个函数,并给每个功能标上编号,根据用户输入的编号在主函数中调用对应的函数,实现对应的功能。同时在创建链表方面,一个节点需要保存下一个节点的位置和当前节点的数据,因此需要创建一个结构体(详细见上一篇博客),再依次将数据保存、相连,得到一个链表。
实现步骤
一、输出提示语句,获取用户输入的功能编号
我们可以在主函数中根据自己的定义方法输出一系列语句,提示用户各种功能对应的编号,并且获取键盘的输入值。同时需要注意的是:我们需要实现的效果是每次完成一个功能,就要再弹出一次这些提示语句和输入功能编号,所以我们需要用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;
}