hello,大家好,本文学习单链表及其基本操作,话不多说,直接开讲吧
单链表定义
单链表是一种链式存取的数据结构,数据元素的逻辑顺序通过指针链接,下图为单链表的结构示意图。
下述代码为C语言下定义的节点结构
struct LinkList{
int val;
char name[40];
struct LinkList *next;
};
复制
单链表的插入
单链表的插入方式有头插法与尾插法两种,首先介绍头插法。
单链表头插法
下图为单链表头插法示意图,我们将新数据插入到单链表的头部位置。
头插法的代码如下:
/* 单链表头插法 */
void insertHead(struct LinkList **head){
struct LinkList *list, *temp;
list = (struct LinkList *)malloc(sizeof(struct LinkList));
if(list == NULL){
printf("哦豁,内存分配失败了\n");
exit(1);
}
getInput(list);
if(*head == NULL){ // 单链表为空
*head = list;
list->next = NULL;
}else{
temp = *head;
*head = list;
list->next = temp;
}
}
/* 获取用户输入的节点数据 */
void getInput(struct LinkList *list){
printf("请输入姓名:");
scanf("%s", list->name);
printf("请输入学号:");
scanf("%d", &list->val);
}
/* 打印单链表数据信息 */
void printLinkList(const struct LinkList *head){
const struct LinkList *temp = head;
while(temp != NULL){
printf("姓名:%s\t学号:%d\n", temp->name, temp->val);
temp = temp->next;
}
}
/* 释放堆空间 */
void freeLinkList(struct LinkList **head){
struct LinkList *temp;
while(*head != NULL){
temp = *head;
*head = (*head)->next;
free(temp);
temp = NULL;
}
}
复制
需要注意的是,head是指向第一个节点的指针,插入元素需要修改head本身的值,因此参数传递时,需要将head的地址作为参数传递,也即此时参数类型为" struct LinkList ** ";此外,堆空间申请的内存需要及时释放以防止内存泄漏。下图为上述头插法示例程序的执行结果:
单链表尾插法
顾名思义,单链表的尾插法则是每次将新加入的数据添加到单链表的尾部,下图为尾插法示意图。
尾插法代码如下:
void insertTail(struct LinkList **head){
static struct LinkList *tail; // 引入哨兵节点
struct LinkList *list;
list = (struct LinkList *)malloc(sizeof(struct LinkList));
if(list == NULL){
printf("哦豁,内存分配失败了\n");
exit(1);
}
if(*head == NULL){ // 单链表为空
*head = list;
list->next = NULL;
}else{
tail->next = list;
list->next = NULL;
}
tail = list; // 更新tail节点位置
}
复制
在使用单链表尾插法时,为防止每次新插入数据时遍历得到尾节点,我们定义一个静态哨兵节点,使其一直指向单链表的尾部节点,这样可以大大提升尾插法的效率。下图为上述程序的运行结果:
单链表的查找
本文以给定待查找的姓名为例进行演示,代码如下:
struct LinkList *serchLinkList(struct LinkList *head, char *name){
struct LinkList *node = head;
while(node != NULL){
if(!strcmp(node->name, name)){
break;
}
cur = cur->next;
}
return cur;
}
复制
上述代码的运行结果如下:
单链表的删除
单链表的删除节点操作也就是指针操作,首先修改待删除节点的上一节点指针,使其指向待删除节点的下一节点,然后释放删除节点的空间即可,代码描述如下:
void deleteNode(struct LinkList **head, int flag){
struct LinkList *cur, *pre;
cur = *head, pre = NULL;
while(cur && cur->val != flag){
pre = cur;
cur = cur->next;
}
if(cur == NULL){
printf("找不到对应节点\n");
return ;
}else{
if(pre == NULL){ // 待删除节点为头节点
*head = cur->next;
}else{
pre->next = cur->next;
}
free(cur);
cur = NULL;
}
}
复制
下图为上述程序运行结果:
单链表反转
单链表反转为经典的双指针问题,实现代码如下:
struct LinkList *reverseLinkList(struct LinkList **head){
struct LinkList *cur, *pre; // 定义两个指针
// cur指针指向头节点,pre指针指向cur的上一节点
cur = *head, pre = NULL;
while(cur != NULL){
//临时变量保存cur的下一节点
struct LinkList *temp = cur->next;
// cur节点的next指针指向前一节点
cur->next = pre;
// pre指针与cur指针分别前移
pre = cur, cur = temp;
}
return pre;
}
复制
上述链表反转的运行结果如下:
后记
本文讲述了单链表最基本的一些操作,在实际应用中仍存在诸多其他有关单链表的操作,例如两两交换链表节点以及K个一组翻转链表等等,建议大家在LeetCode网站上多多练习。
另外有关单链表的算法,有以下几点建议:
善用哨兵节点简化实现难度,例如在尾插法实现时,定义哨兵节点指向链表的尾节点,可降低算法的时间复杂度
善于画图思考,将指针的链接逻辑清晰化,厘清链表结构,防止指针丢失与内存泄漏
讲完啦,欢迎读者留言,无论是文章勘误亦或是有关文章内容的讨论与建议,都欢迎,谢谢大家。