链表是常用的数据结构。
typedef struct Node{
ElemType data;
Node* link;
}Node, *List;
常规链表操作,诸如遍历,插入,删除等就不再给出代码。需要注意的是,对链表的合法性进行检验;删除时,勿忘记释放其结点所占用的空间。
1、链表逆序:
链表逆序需要维护三个指针,一个指向前一个*pre,一个指向当前节点*cur,一个指向下一个节点*next以使当前节点的link域改变时,还能访问到其一下个节点。代码如下:
List reverseList(List head)
{
assert(head);
List next, pre = head, cur = head->link;
while(cur) {
next = cur->link; //保存当前结点的下一个结点
cur->link = pre; //改变当前结点的link域,指向它的前一个结点;
pre = cur; //指针移到一下个结点
cur = next;
}
head = pre; //恢复头结点
return head;
}
2、链表求环:
利用两一个指针,一个速度是另外一个的两倍。即一个指针慢指针:每次移动一个结点;一个快指针,每次移动两结点。若有环:则两指针会在某一点相遇;若无环:则快指针会先走到链表末尾。代码如下:
bool hasCircle(List head)
{
assert(head);
bool hasCircle = false;
List slow = head, fast = head;
while (fast && fast->link) //此处增加了对fast的下一个结点的判断,如果fast->link不存在,fast->link->link语句则是非法的;
{
slow = slow->link;
fast = fast->link->link;
if (slow == fast)
{
hasCircle = ture;
break;
}
}
return hasCircle;
}
3、求有环链表环的起点p,如下图:
假设快指针和慢指针在蓝色圆圈所表示结点p处相遇,则它们走过的路径长度分别为:
快指针fast = 非环段链表长度L + 环上链表结点个数C + 从p到交点的弧长S
慢指针slow = 非环段链表长度L + 从p到交点的弧长S
又有fast = 2*slow
故 L = C - S
因此:设两个指针,一个从表头开始遍历,一个从p开始遍历,相遇的结点就是环的起点。代码如下:
List beginOfCircle(List head)
{
assert(head);
List slow = head, fast = head;
while (fast && fast->link)
{
slow = slow->link;
fast = fast->link->link;
if (slow == fast)
break;
}
if (fast == NULL || fast->next == NULL) //此时链表无环;
return NULL;
fast = head;
while (slow != fast)
{
slow = slow->link;
fast = fast->link;
}
return fast;
}
4、判断链表是否相交
a、无环链表情况:
方法1:把链表的尾结点和另外一个链表的起点链接起来,然后判断是否有环(见编程之美)。代码如下:
bool isCross(List head1, List head2)
{
assert(head1 && head2);
List p1 = head1;
while (p1->link)
p1 = p1->link;
p1->link = head2;
if (hasCircle(head1))
return true;
return false;
}
方法2:如果两个无环链表相交,其尾节点必然相等!代码如下:
bool isCross(List head1, List head2)
{
assert(head1 && head2);
while (head1->link)
head1 = head1->link;
while (head2->link)
head2 = head2->link;
if (head1 == head2)
return true;
return false;
}
解法:有环链表相交,两必然有同一个环;可设一快指针,一慢指针,在绕环N圈后,快指针必然遇到慢指针。代码如下:
bool isCross_(List head1, List head2)
{
bool isCross = false;
assert(head1 && head2);
List fast = head1, slow = head2;
while (fast && fast->link)
{
fast = fast->link->link;
slow = slow->link;
if (slow && slow == fast)
{
isCross = true;
break;
}
}
return isCross;
}
5、求交点:
从上面的图像和推导可以看出,求交点都可以转化为求环的起点问题。
无环->链表首尾相接;
有环->交点一个或者两个;
typedef struct Node{
ElemType data;
Node* link;
}Node, *List;
常规链表操作,诸如遍历,插入,删除等就不再给出代码。需要注意的是,对链表的合法性进行检验;删除时,勿忘记释放其结点所占用的空间。
1、链表逆序:
链表逆序需要维护三个指针,一个指向前一个*pre,一个指向当前节点*cur,一个指向下一个节点*next以使当前节点的link域改变时,还能访问到其一下个节点。代码如下:
List reverseList(List head)
{
assert(head);
List next, pre = head, cur = head->link;
while(cur) {
next = cur->link; //保存当前结点的下一个结点
cur->link = pre; //改变当前结点的link域,指向它的前一个结点;
pre = cur; //指针移到一下个结点
cur = next;
}
head = pre; //恢复头结点
return head;
}
2、链表求环:
利用两一个指针,一个速度是另外一个的两倍。即一个指针慢指针:每次移动一个结点;一个快指针,每次移动两结点。若有环:则两指针会在某一点相遇;若无环:则快指针会先走到链表末尾。代码如下:
bool hasCircle(List head)
{
assert(head);
bool hasCircle = false;
List slow = head, fast = head;
while (fast && fast->link) //此处增加了对fast的下一个结点的判断,如果fast->link不存在,fast->link->link语句则是非法的;
{
slow = slow->link;
fast = fast->link->link;
if (slow == fast)
{
hasCircle = ture;
break;
}
}
return hasCircle;
}
3、求有环链表环的起点p,如下图:
假设快指针和慢指针在蓝色圆圈所表示结点p处相遇,则它们走过的路径长度分别为:
快指针fast = 非环段链表长度L + 环上链表结点个数C + 从p到交点的弧长S
慢指针slow = 非环段链表长度L + 从p到交点的弧长S
又有fast = 2*slow
故 L = C - S
因此:设两个指针,一个从表头开始遍历,一个从p开始遍历,相遇的结点就是环的起点。代码如下:
List beginOfCircle(List head)
{
assert(head);
List slow = head, fast = head;
while (fast && fast->link)
{
slow = slow->link;
fast = fast->link->link;
if (slow == fast)
break;
}
if (fast == NULL || fast->next == NULL) //此时链表无环;
return NULL;
fast = head;
while (slow != fast)
{
slow = slow->link;
fast = fast->link;
}
return fast;
}
4、判断链表是否相交
a、无环链表情况:
方法1:把链表的尾结点和另外一个链表的起点链接起来,然后判断是否有环(见编程之美)。代码如下:
bool isCross(List head1, List head2)
{
assert(head1 && head2);
List p1 = head1;
while (p1->link)
p1 = p1->link;
p1->link = head2;
if (hasCircle(head1))
return true;
return false;
}
方法2:如果两个无环链表相交,其尾节点必然相等!代码如下:
bool isCross(List head1, List head2)
{
assert(head1 && head2);
while (head1->link)
head1 = head1->link;
while (head2->link)
head2 = head2->link;
if (head1 == head2)
return true;
return false;
}
3 判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。
比较好的方法有两个:
一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。
二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。
这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。
b.有环链表相交
解法:有环链表相交,两必然有同一个环;可设一快指针,一慢指针,在绕环N圈后,快指针必然遇到慢指针。代码如下:
bool isCross_(List head1, List head2)
{
bool isCross = false;
assert(head1 && head2);
List fast = head1, slow = head2;
while (fast && fast->link)
{
fast = fast->link->link;
slow = slow->link;
if (slow && slow == fast)
{
isCross = true;
break;
}
}
return isCross;
}
5、求交点:
从上面的图像和推导可以看出,求交点都可以转化为求环的起点问题。
无环->链表首尾相接;
有环->交点一个或者两个;