链表常见操作
参考:https://blog.csdn.net/maybe3is3u5/article/details/52276623
https://blog.csdn.net/Lily_whl/article/details/71662133
1.链表中是否有环
参考https://blog.csdn.net/fynjy/article/details/47440049
设置两个指针pFast和pSlow,初始时让他们都指向首节点(第一个元素),这里指向头结点也可以,只不过是为了下面计算环入口方便。让pFast指针每次前进两步,pSlow指针每次前进一步,我们可以想象,这两个指针一定会在环中的某个位置相遇。
//判断链表中是否有环,是返回true,没有返回false
//思路:设置两个指针pFast和pSlow,初始时让他们都指向首节点(第一个元素),
//这里指向头结点也可以,只不过是为了下面计算环入口方便。
//让pFast指针每次前进两步,pSlow指针每次前进一步,我们可以想象,
//这两个指针一定会在环中的某个位置相遇。
bool DetectCricle(PNode pHead)
{
//指向首节点
PNode pFast = pHead->next;
PNode pSlow = pHead->next;
//链表空
if (!pFast)
return false;
while (pFast->next && pFast->next->next)
{
pFast = pFast->next->next;
pSlow = pSlow->next;
if (pFast == pSlow)
return true;
}
return false;
}
2.求环的入口
示意图如图所示,当pFast和pSlow第一次相遇时,pSlow一定没有遍历完整个链表,顶多在入口的前一个节点就会相遇,图中就是蓝色的那个点。原因何在?
我们知道两个指针相遇的地方一定在环中的某个点,从入口到蓝色点一圈中的某个位置,假设此时pSlow第一次进入环,也就是刚好指向入口,此时pFast的位置可以指向环中任一位置。我们想象成pFast追赶pSlow的状态,当pFast指向黄色节点时,需要追赶的步数最远,这时一步步走,当pSlow指向绿色节点时,pFast差一步就刚好走满两圈,此时刚好也指向绿色节点,正好相遇(不管在中途有没有相遇)。所以pSlow还没走完一遍,两个节点就会相遇。
设首节点到入口的距离为x,入口到相遇点距离为y,环的长度为r,链表的长度为l,相遇时pSlow走的距离为s,相遇时pFast走了n次环。
我们有:
2s = s+nr;
x+y =s;
得x+y = nr;
的x = nr-y;
我们看看x=nr-y是什么意思,x表示从守节点走到入口的距离,nr-y表示从相遇点开始循环n圈环,再导入y步,刚好又位于入口。此时又相遇了,可以计算出入口位置。
//求环的入口,存在则返回入口地址,不存在返回NULL
PNode CricleEnterAddr(PNode pHead)
{
//指向首节点
PNode pFast = pHead->next;
PNode pSlow = pHead->next;
//链表空
if (!pFast)
return NULL;
while (pFast->next && pFast->next->next)
{
pFast = pFast->next->next;
pSlow = pSlow->next;
if (pFast == pSlow)
{
pFast = pHead->next;
while (pFast != pSlow)
{
pFast = pFast->next;
pSlow = pSlow->next;
}
return pFast;
}
}
return NULL;
}
3.链表反转
方法一、
思路:每次都将原第一个结点之后的那个结点放在新的表头后面。
比如1, 2, 3, 4, 5
第一次:把第一个结点1后边的结点2放到新表头后面,变成2, 1, 3, 4, 5
第二次:把第一个结点1后边的结点3放到新表头后面,变成3, 2, 1, 4, 5
……
直到: 第一个结点1,后边没有结点为止。
//反转链表
//思路:每次都将原第一个结点之后的那个结点放在新的表头后面。
//比如1, 2, 3, 4, 5
//第一次:把第一个结点1后边的结点2放到新表头后面,变成2, 1, 3, 4, 5
//第二次:把第一个结点1后边的结点3放到新表头后面,变成3, 2, 1, 4, 5
//……
//直到: 第一个结点1,后边没有结点为止。
PNode ListReverse(PNode pHead)
{
//链表只有头节点,或者只有一个节点
if (pHead->next == NULL || pHead->next->next == NULL)
{
return pHead;
}
PNode tail = pHead->next;//第一个节点为尾
PNode current = tail->next;//current为当前需要调换到前面的节点
while (current)
{
//指向当前节点的下一个节点
tail->next = current->next;
//头插法,插入节点
current->next = pHead->next;
pHead->next = current;
//更新当前节点
current = tail->next;
}
return pHead;
}
方法二、
利用三个辅助指针,将链表中元素一个一个反转
//反转链表方法二、利用三个辅助指针,将链表中元素一个一个反转
PNode ListReverse2(PNode pHead)
{
//链表为空或者只有一个节点
if (!pHead->next || !pHead->next->next);
{
return pHead;
}
PNode pre = pHead->next;
PNode cur = pre->next;
PNode next = NULL;
pre->next = NULL;
while (cur)
{
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
pHead->next = pre;
return pHead;
}
不带头结点的链表反转,
类似方法二、
PNode ListReverse3(PNode pHead)
{
if (!pHead || !pHead->next)
{
return pHead;
}
PNode pre = pHead;
PNode cur = pre->next;
PNode next = NULL;
pHead->next = NULL;
while(cur)
{
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
pHead = pre;
return pHead;
}
另一种方法:
PNode ListReverse4(PNode pHead)
{
if (!pHead || !pHead->next)
{
return pHead;
}
PNode n = pHead;
pHead = NULL;
while (n)
{
PNode m = n;
n = n->next;
m->next = pHead;
pHead = m;
}
return pHead;
}
4.求链表倒数第k个节点
//查找链表倒数第K个节点
//方法一:需遍历两次链表;先求链表的长度len,然后遍历len-k的距离即可查找到单链表的倒数第k个节点
//方法二:遍历一次链表,时间复杂度为O(n);设置两个指针p1、p2,让p2先走k-1步,然后p1、p2再同时走,
// 当p2走到链表尾时,p1所指位置就是所要找的节点
PNode ListSearchReverseKthNode(PNode pHead, int k)
{
if (pHead->next == NULL || k <= 0)
{
return NULL;
}
PNode p1 = pHead->next;
PNode p2 = pHead->next;
while (--k)
{
p2 = p2->next;
if (p2 == NULL)
return NULL;
}
while (p2->next)
{
p2 = p2->next;
p1 = p1->next;
}
return p1;
}
5.求链表的中点
//求链表的中间节点,(设链表长度为n,返回第n/2+1个点)
//思路:设置两个指针,p1,p2,p2每次走两步,p1每次走一步
PNode ListSearchMidthNode(PNode pHead)
{
if (pHead == NULL||pHead->next==NULL)
return NULL;
PNode p1 = pHead->next;
PNode p2 = pHead->next;
while (p2->next&&p2->next->next)
{
p2 = p2->next->next;
p1 = p1->next;
}
if(p2->next)
{
p2 = p2->next;
p1 = p1->next;
}
return p1;
}