前言(下面链表的结构都为此种类型)
typedef int Slistdatatype;
typedef struct Slist
{
Slistdatatype data;
struct Slist* next;
}Slist;
目录:
一:单链表的翻转(三个指针操作链表)
二:找到单链表的中间结点
三:合并两个有序链表,使得结果依旧有序
四:判断环形链表
五:求解单链表的入口
一:单链表的翻转(三个指针操作链表)
例如有入上图的单链表a,现在需要将其翻转成为b的样式。
我们知道链表的顺序指向性是因为链表的前一个结点有指向后一个结点的指针, 那么我们只需要将该指针的指向改变,就可以实现链表的翻转了。
因为该链表的类型是单向不带头不循环链表。所以这里我们需要使用三个指针去维护这个链表,每次修改next1和next2的指向,修改完成后,
next2赋值给next1,
next3赋值给next2,
next3=next3->next;。
这样当next2走到最后一个结点的时候,该类型单链表就翻转完成了。
//链表翻转
void Slist_revise(Slist* * ps)
{
Slist* next = *ps;
Slist* next1 = *ps;
Slist* next2 = *ps;
if (next == NULL || next->next==NULL)
{//链表无结点 ,或者只有一个结点
return;
}
else if (next->next->next == NULL)
{//只有两个结点
next1 = next->next;
next1->next = next;
next->next = NULL;
*ps = next1;
}
else//有三个或者三个以上结点
{
next1 = next->next;
next2 = next1->next;
next->next = NULL;
while (next2->next != NULL)
{
next1->next = next;
next = next1;
next1 = next2;
next2 = next2->next;
}//出循环时,next2->next == NULL
next2->next = next1;
next1->next = next;
*ps = next2;
}
}
当然我们需要将链表没有结点,只有一个或者两个结点的情况考虑并处理好。
二:找到单链表的中间结点(当结点数目为偶数个时,后一个结点代表该链表的中间结点)
如图所示,我们需要找到该链表的中间结点pos指向的位置。那么我们使用两个指针去操作该链表。slow指针每次跳过一个结点,fast指针每次跳过两个结点(这两个指针就被称为快慢指针)。当fast指向NULL或者fast->next指向NULL的时候,就说明fast走到了链表末尾。此时slow指针刚好指向链表的中间位置。
Slist* Slow_fast(Slist*ps)
{
Slist* slow = ps;
Slist* fast = ps;
while (fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
//2:快慢指针遍历链表,查询中间结点数据。
int main()
{
Slist* plist = NULL;
for(int i=0;i<6;i++)
Slist_push_tail(&plist, i);
Slist_Prin(plist);
Slist* pos=Slow_fast(plist);
printf("%d \n", pos->data);
return 0;
}
三:合并两个有序链表,使得结果依旧有序
这里的操作方法是直接将两个链表从头开始遍历比较,取出较小的值放入新开辟的链表中。
(当然还有其他更好的解法,有兴趣小伙伴可以在评论区讨论)
Slist* order(Slist* ps1, Slist* ps2)
{
Slist* p1 = ps1;
Slist* p2 = ps2;
Slist* phead = (Slist*)malloc(sizeof(Slist));
Slist* cur = phead;
Slist* ptr = NULL;
while (ps1 && ps2)
{
if (ps1->data <= ps2->data)
{
phead->next = (Slist*)malloc(sizeof(Slist));
phead = phead->next;
phead->data = ps1->data;
ps1 = ps1->next;
}
else
{
phead->next = (Slist*)malloc(sizeof(Slist));
phead = phead->next;
phead->data = ps2->data;
ps2 = ps2->next;
}
}
if (ps1 != NULL)
{
phead->next = ps1;
//释放ps2。和ps1已经被拷贝到新链表中的。
while (p2->next != NULL)
{
ptr = p2;
p2 = p2->next;
free(ptr);
}
while (p1 != ps1)
{
ptr = p1;
p1 = p1->next;
free(ptr);
}
}
if (ps2 != NULL)
{
phead->next = ps2;
//释放ps1。和ps2已经被拷贝到新链表中的。
while (p1->next != NULL)
{
ptr = p1;
p1 = p1->next;
free(ptr);
}
while (p2 != ps2)
{
ptr = p2;
p2 = p2->next;
free(ptr);
}
}
return cur->next;
}
//合并两个有序链表,使得结果依旧有序。
int main()
{
Slist* plist1 = NULL;
Slist* plist2 = NULL;
for (int i = 20; i < 23; i++)
{
Slist_push_tail(&plist1, i);
}
for (int i = 10; i < 15; i++)
{
Slist_push_tail(&plist2, i);
}
Slist_Prin(plist1);
Slist_Prin(plist2);
Slist* pos=order(plist1, plist2);
Slist_Prin(pos);
return 0;
}
四:判断环形链表
环形链表:在单链表中,处于末尾端的结点本来是指向NULL的,但是现在它指向了该链表其中一个结点。
这里我们依旧使用快慢指针去遍历链表。因为当链表无环时,快指针一定会先走到链表末尾,而当链表有环时,慢指针就有可能追上快指针。 (但是并不是一定可以追上快指针,当快慢指针的速度差距不为1的时候,遇到某些链表,可能会一直追不上)
//判断环形链表 快慢指针
int Slow_Fast(Slist* ps)
{
Slist* slow = ps;
Slist* fast = ps;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow->data == fast->data)
return 1;
}
return 0;
}
判断有环形,则返回1,反之返回0.
五:求解单链表的入口
结论:
先使用快慢指针去遍历该链表,当两指针在环内相遇时,定义一个新的指针从这个位置开始遍历,另一个新的指针从表头开始遍历。那么这两个新的指针就会在环的入口出相遇与。
那为什么快指针走的距离是L+X+NC呢????
慢指针走的距离一定是 L+X.而快指针因为要与慢指针相遇,肯定也走了 L+ X这一段距离。
那么在慢指针slow走到 L/2时,快指针已经走到 L了 。那么在慢指针走剩下的 L/2+X时,快指针不知道走了多长,但是它肯定走过了 X这一段路程。而快指针是从入环处开始走的,最后一段又是从入环点开始走,走到相遇的meet结点处。那么它还走过了 NC的长度。 (也就是整数被的圈数)。
//求单链表环的入口
Slist* SlowFast(Slist* ps)
{
Slist* slow = ps;
Slist* fast = ps;
while (ps && ps->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
Slist* meet = slow;
Slist* cur = ps;
while (cur != meet)
{
cur = cur->next;
meet = meet->next;
}
return meet;
}
}
return NULL;
}