单链表的应用
目录
一、单链表经典算法
试题1:移除链表元素
题目内容:
给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val == val的节点,并返回新的头节点
示例:
输入:head = [1,2,6,3,4,5,6],val = 6
输出:[1,2,3,4,5]
思路1:三指针法
思路解析:
创建一个指针pcur指向头节点,遍历原链表
当指向指定数据的节点时:
先创建一个指针prev指向pcur前一个节点,该节点的指针域存放pcur下一个节点的地址
再创建一个指针next指向pcur后一个节点,释放pcur节点,让pcur指向next指向的节点
当指向非指定数据节点时:
next先往后走,pcur,prev依次往后走
思路2:尾插法
思路解析:
创建一个指针pcur指向原链表的头节点
找值不为val的节点,尾插到新的链表中
创建一个头指针newHead,一个尾指针newTail,初值为NULL
尾插完一个新节点后,需要更新尾指针,指向新的节点地址
注:
最后需要判断尾指针是否为空,如果为空,直接返回头指针
如果不为空,先将尾节点的指针域置为NULL,再返回头指针
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val)
{
/*创建一个空链表*/
ListNode* newHead,*newTail;
newHead = newTail = NULL;
/*遍历原链表*/
ListNode* pcur = head;
while(pcur)
{
/*找值不为val的节点,尾插到新链表中*/
if(pcur->val != val)
{
/*如果链表为空*/
if(newHead == NULL)
{
newHead = newTail = pcur;
}
else
{
/*链表不为空*/
newTail->next = pcur;
newTail = pcur;
}
}
pcur = pcur->next;
}
if(newTail)
{
newTail->next = NULL;
}
return newHead;
}
试题2:反转链表
题目内容:
给你单链表的头节点head,请你反转链表,并返回反转后的链表
示例:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
思路1:头插法
思路解析:
创建一个头指针newHead,一个尾指针newTail,初值为NULL
创建指针pcur遍历原链表,将原链表的节点头插到新链表中
头插完一个新节点后,需要更新头指针,指向新的节点地址
思路2:三指针法
思路解析:
创建三个指针n1,n2,n3
初始时,n1指向NULL,n2指向头节点,n3指向第二个节点
每次先将n2指向节点的指针域换为n1指向节点的地址
n1指向n2指向节点的地址
n2指向n3指向节点的地址
n3指向下一个节点的地址
注:
当n2指向最后一个节点时,n3为NULL,无法对其解引用,所以要进行判断
当链表为空链表时,n2为NULL,无法对其解引用,所以也要进行判断

代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head)
{
/*判断是否为空*/
if(head == NULL)
{
return head;
}
/*创建三个指针*/
ListNode* n1,*n2,*n3;
n1 = NULL;n2 = head;n3 = n2->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3)
{
n3 = n3->next;
}
}
return n1;
}
试题3:链表的中间节点
题目内容:
给你单链表的头结点head,请你找出并返回链表的中间结点
如果有两个中间结点,则返回第二个中间结点
示例:
输入:head = [1,2,3,4,5]
输出:[3,4,5]
**解释:**链表只有一个中间节点,值为3
思路1:快慢指针法
思路解析:
定义一个快指针fast,一个慢指针slow
slow每次走一步,fast每次走两步
当fast为NULL时(偶数),slow刚好指向中间节点
当fast的指针域为NULL时(奇数),slow刚好指向中间节点
**原理:**2 * slow == fast
**注:**while判断时fast->next和fast不能换位,因为判断fast为NULL后,就无法再对fast进行解引用
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head)
{
/*创建快慢指针*/
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
/*此时slow刚好指向的就是中间节点*/
return slow;
}
试题4:合并两个有序链表
题目内容:
将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成
示例:
输入:l1 = [1,2,4] l2 = [1,3,4]
输出:[1,1,2,3,4,4]
思路1:尾插法
思路解析:
创建l1,l2两个指针变量,分别遍历链表1和链表2
依次比较两个链表中的数据大小,谁小谁尾插到新的链表中
某个指针为NULL后将另一个剩下节点全部尾插到新链表中
**注:**如果原链表为NULL,需要先进行判断,直接返回另一个链表
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
/*判空*/
if(list1 == NULL)
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
ListNode* l1 = list1;
ListNode* l2 = list2;
ListNode* newHead,*newTail;
newHead = newTail = NULL;
while(l1 && l2)
{
/*l1拿下来尾插*/
if(l1->val < l2->val)
{
/*头节点为空*/
if(newHead == NULL)
{
newHead = newTail = l1;
}
else/*头节点不为空*/
{
newTail->next = l1;
newTail = newTail->next;
}
l1 = l1->next;
}
else/*l2拿下来尾插*/
{
/*头节点为空*/
if(newHead == NULL)
{
newHead = newTail = l2;
}
else/*头节点不为空*/
{
newTail->next = l2;
newTail = newTail->next;
}
l2 = l2->next;
}
}
/*跳出循环有两种情况:l1先走空;l2先走空*/
if(l2)
{
newTail->next = l2;
}
if(l1)
{
newTail->next = l1;
}
return newHead;
}
补充:
以上代码因为每次开始插入链表之前都要先判断空链表和非空链表两种情况
所以存在重复代码,可以通过动态内存申请让新链表不为空,
创建临时变量ret存放头节点的下一个节点
然后释放头节点动态申请的空间并返回ret
修改后的代码如下:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
/*判空*/
if(list1 == NULL)
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
ListNode* l1 = list1;
ListNode* l2 = list2;
ListNode* newHead,*newTail;
newHead = newTail = (ListNode*)malloc(sizeof(ListNode));
while(l1 && l2)
{
/*l1拿下来尾插*/
if(l1->val < l2->val)
{
newTail->next = l1;
newTail = newTail->next;
l1 = l1->next;
}
else/*l2拿下来尾插*/
{
newTail->next = l2;
newTail = newTail->next;
l2 = l2->next;
}
}
/*跳出循环有两种情况:l1先走空;l2先走空*/
if(l2)
{
newTail->next = l2;
}
if(l1)
{
newTail->next = l1;
}
return newHead->next;
}
**注:**这里的newHead指的是头节点,即哨兵位,实际上是链表分类的一种,叫做带头链表
试题5:环形链表的约瑟夫问题
题目内容:
编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开
下一个人继续从 1 开始报
n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少
示例:
输入:5,2
输出:3
思路1:环形链表法
思路解析:
创建带环链表,遍历带环链表计数

代码部分:
cpp
/**
* @param n int整型
* @param m int整型
* @return int整型
*/
typedef struct ListNode ListNode;
/*创建节点*/
ListNode* buyNode(int x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->val = x;
node->next = NULL;
return node;
}
/*创建带环链表*/
ListNode* createCircle(int n)
{
/*创建头节点*/
ListNode* phead = buyNode(1);
/*创建尾节点*/
ListNode* ptail = phead;
/*尾插节点*/
for(int i = 2;i <= n;i++)
{
ptail->next = buyNode(i);
ptail = ptail->next;
}
/*首尾相连,链表成环*/
ptail->next = phead;
/*返回尾节点*/
return ptail;
}
int ysf(int n, int m )
{
/*1.根据n创建带环链表*/
ListNode* prev = createCircle(n);
/*2.找到首位节点的地址*/
ListNode* pcur = prev->next;
/*3.创建计次变量*/
int count = 1;
/*4.循环报数*/
while(pcur->next != pcur)
{
if(count == m)/*需要销毁节点*/
{
/*prev存放pcur下一个节点*/
prev->next = pcur->next;
/*销毁pcur指向的节点*/
free(pcur);
/*pcur走到下一个节点*/
pcur = prev->next;
/*重新报数*/
count = 1;
}
else/*不需要销毁节点*/
{
prev = pcur;
pcur = pcur->next;
count++;
}
}
/*此时剩下的一个节点就是要返回的节点里的值*/
return pcur->val;
}
试题6:分割链表
题目内容:
给你一个链表的头节点head和一个特定值x,请你对链表进行分隔
使得所有小于x的节点都出现在大于或等于x的节点之前
示例:
输入:head = [1,4,3,2,5,2],x = 3
输出:[1,2,2,4,3,5]
思路1:四指针法
思路解析:
创建四个指针变量,prev(存放pcur的下一个节点地址),pcur(指向当前节点地址),ptail(原链表旧的尾节点),newptail(原链表新的尾节点),在原链表上进行修改
若pcur节点的值小于x,prev走到pcur节点的地址,pcur往后走到下一个节点
若pcur节点的值大于或等于x,第一次尾插在ptail后面,以后尾插在newptail后面,更新尾节点,然后在prev指向节点的指针域中存放pcur下一个节点地址,删除旧节点,pcur往后走
直到pcur与ptail相同时,遍历结束
思路2:哨兵位法
思路解析:
创建哨兵位newHead,和尾指针newTail,指针pcur指向原链表
将pcur第一次指向的节点尾插到哨兵位后面,更新尾节点
若pcur节点的值小于x,就头插在newHead节点的后面
若pcur节点的值大于或等于x,尾插在newTail节点后面
思路3:大小链表法
创建大小链表的哨兵位和尾指针
小链表:lessHead,lessTail
大链表:greaterHead,greaterTail
创建pcur指向原链表的头结点,遍历原链表
若pcur节点的值小于x,尾插在lessTail节点的后面,更新尾节点
若pcur节点的值大于或等于x,尾插在greaterTaill节点后面,更新尾节点
将大链表的尾节点的指针域设置为NULL,避免造成死循环,初始化next指针
将小链表的尾节点和大链表的第一个有效节点(哨兵位的下一个节点)相连

代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x)
{
/*判断原链表是否为空*/
if(head == NULL)
{
return head;
}
/*创建两个带头链表*/
ListNode* lessHead,*lessTail;
ListNode* greaterHead,*greaterTail;
lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
/*遍历原链表,将原链表中的节点尾插到大小链表中*/
ListNode* pcur = head;
while(pcur)
{
if(pcur->val < x)
{
/*尾插到小链表中*/
lessTail->next = pcur;
/*更新尾节点*/
lessTail = lessTail->next;
}
else
{
/*尾插到大链表中*/
greaterTail->next = pcur;
/*更新尾节点*/
greaterTail = greaterTail->next;
}
pcur = pcur->next;
}
/*修改大链表尾节点的next指针指向*/
greaterTail->next = NULL;//避免代码死循环,初始化next指针
/*小链表的尾节点和大链表哨兵位的下一个节点相连*/
lessTail->next = greaterHead->next;
ListNode* ret = lessHead->next;
free(lessHead);
free(greaterHead);
lessHead = greaterHead = NULL;
return ret;
}
二、链表的分类
链表的结构非常多样,以下情况组合起来就有8种(2*2*2):

**2.1.单向或者双向:**单向只能从一个方向遍历,双向可以从两个方向遍历

**2.2.带头或者不带头:**带头指的是链表中有哨兵位节点,即为头节点,不带头没有哨兵位

**2.3.循环或者不循环:**循环尾节点的指针域为头指针,不循环尾节点的指针域为NULL

**单链表:**单向不带头不循环链表
**双向链表:**双向带头循环链表
三、总结
本篇博客是对于数据结构中单链表应用的整理归纳,后续还会更新双向链表等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~