1. 逆置
① 借助头结点
原始单链表
逆置后
void Reverse1(struct Node* plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
PNode p = plist->next;
PNode q;
plist->next = NULL;
while (p != NULL)
{
q = p->next;
p->next = plist->next;
plist->next = p;
p = q;
}
}
② 不借助头结点
第一步:先将头结点 剔除循环
第二步:通过pqr指针依次将所有节点方向颠倒过来
第三步:最后再将头结点连接过来即可
//逆置2(需要三个临时指针)
void Reverse2(struct Node* plist)
{
assert(plist != NULL);//确保头结点
assert(plist->next != NULL);//确保首节点存在
assert(plist->next->next != NULL);//确保第二个节点存在
PNode p = plist->next;
PNode q = p->next;
PNode r;
p->next = NULL;
while (q != NULL)//pqr中用q!=NULL来做判断依据
{
r = q->next;
q->next = p;
p = q;
q = r;
}
plist->next = p;
}
运行结果:
2. 判断两个单链表是否存在交点,如果存在交点,则找到相交的第一个点
相交图:
两个单链表发生交点,只可能是上述这种情况,不可能出现数学中的交叉相交。即a点为两个单链表p和q的相交点。
① 如果面试官没有让找相交的点,只是问是否相交(可以用一个非常简单的方法)
//确定两个单链表是否相交(用两个指针跑到两个单链表的尾结点,判断是否在同一个尾结点)
bool Intersect(PNode plist1, PNode plist2)
{
//assert()
PNode p1 = plist1->next;
PNode p2 = plist2->next;
for (p1; p1 != NULL; p1 = p1->next);//此时,for循环结束,p1在plist1这个单链表的尾结点
for (p2; p2 != NULL; p2 = p2->next);//此时,for循环结束,p2在plist2这个单链表的尾结点
return p1 == p2;
}
int main()
{
Node head1;
Init_list(&head1);
for (int i = 0; i < 10; i++)
{
Insert_pos(&head1, i, i + 1);
}
Show(&head1);
Node head2;
Init_list(&head2);
for(int i = 0; i < 20; i++)
{
Insert_pos(&head2, i, i + 101);
}
Show(&head2);
bool tag1 = Intersect(&head1, &head2);
if (tag1)
{
printf("相交\n");
}
else
{
printf("不相交\n");
}
//实现手动相交
PNode p = &head2;
for (int i = 0; i < 12; i++)
{
p = p->next;
}
PNode q = &head1;
for (int i = 0; i < 5; i++)
{
q = q->next;
}
p->next = q->next;
Show(&head1);
Show(&head2);//手动相交,发生数据丢失
bool tag3 = Intersect(&head1, &head2);
if (tag3)
{
printf("相交\n");
}
else
{
printf("不相交\n");
}
return 0;
}
最后一行是手动相交,可以看出来,手动相交之后,数据会发生丢失,不建议这种写法
② 如果面试官既要判断是否相交,还要问相交点在那(另外一种解题思路)
思路:首先,申请两个临时指针p和q,让p指向教长的单链表开头,让q指向较短的单链表开头(相等的话无所谓);然后,让p先走几步(两个单链表的长度的差值),这个时候,p和q分别到相交节点的距离就相同了;再用循环去判断(p和q是否为同一个节点,如果不是,则p和q分别向后走,直到退出)
//确定两个单链表是否相交,并且相交的话,返回第一个相交点
struct Node* Intersect_get_Node(PNode plist1, PNode plist2)
{
//assert;
int len1 = Getlength(plist1);
int len2 = Getlength(plist2);
//接下来,保证p执行较长的那个单链表,用三目元算符
PNode p = len1 >= len2 ? plist1 : plist2;
PNode q = len1 >= len2 ? plist2 : plist1;
for (int i = 0; i < abs(len1 - len2); i++)
{
p = p->next;
}
//此时,p已经将差值跑完,这个时候只需要循环判断p和q是否是同一个节点即可
while (p != q)//这里不会是死循环,要么p和q指向有效地址,要么p=q==null,退出循环
{
p = p->next;
q = q->next;
}
return p;
}
int main()
{
Node head1;
Init_list(&head1);
for (int i = 0; i < 10; i++)
{
Insert_pos(&head1, i, i + 1);
}
Show(&head1);
Node head2;
Init_list(&head2);
for(int i = 0; i < 20; i++)
{
Insert_pos(&head2, i, i + 101);
}
Show(&head2);
PNode p = &head2;
for (int i = 0; i < 12; i++)
{
p = p->next;
}
PNode q = &head1;
for (int i = 0; i < 5; i++)
{
q = q->next;
}
p->next = q->next;
Show(&head1);
Show(&head2);//手动相交,发生数据丢失
bool tag1 = Intersect(&head1, &head2);
if (tag1)
{
printf("相交\n");
}
else
{
printf("不相交\n");
}
struct Node* tmp1 = Intersect_get_Node(&head1, &head2);
if (tmp1 != NULL)
{
printf("相交点有效值为: %d\n", tmp1->data);
}
return 0;
}
运行结果:
3. 任意删除一个节点(要求时间复杂度为O(1),给的这个节点的地址不能是尾结点)
//删除任意一个节点(这个节点不能是尾结点)
bool Del_Node(struct Node* p)
{
assert(p != NULL);//确保p存在
assert(p->next != NULL);//确保p不是尾节点
PNode q = p->next;
p->data = q->data;
p->next = q->next;
free(q);
return true;
}
从运行结果可以看出,第一个有效数据节点删除了
4. 判断一个单链表是否存在环?如果确实存在环,则找到入环点
思路:① 需要两个指针,快指针和慢指针,快指针一次走两步,慢指针一次走一步
②让快指针向后走
③循环要么指针走到NULL退出,要么快慢指针在环中相遇
设头结点到入环点的距离为x;入环点到相遇点的距离为y;相遇点,接着向后走,再次遇到入环点的距离为z
其中存在数学关系:
fast: x+n(y+z) //n代表相遇前饶了多少圈
slow: x+y
slow*2=fast
即: 2(x+y)=x+n(y+z),化简后有 x=(n-1)(y+z)+z
所以,一个指针从头结点向后走,另一个指针从相遇点向后走,
struct Node* Is_circle(struct Node* plist)
{
//assert;
PNode fast = plist;
PNode slow = plist;
while (fast != NULL && fast != slow)//如果快指针没有走到空,且快慢指针还没有相遇,循环继续在里面运行
{
slow = slow->next;
//fast = fast->next->next;//这种一次走两步,可能会异常,害怕第一次指的next为空
fast = fast->next;
if (fast != NULL)
{
fast = fast->next;
}
}
//此时,循环退出,要么快指针走到了null,证明没有环,要么快慢指针相遇,证明有环
if (fast == NULL)//没有环
{
return NULL;
}
PNode A = plist;//头结点
PNode B = fast;//相遇点,这是fast=slow
while (A != B)
{
A = A->next;
B = B->next;
}
return A;//返回A和B都行,因为A和B相等,入环点
}
- 确定一个单链表是否回文
回文链表特点:类似这样:(12321 123321)
① 不是回文链表,因为正着读是12,反着读是21
②是回文链表,正着读为12,反着读也为12,并且是偶数回文链表
③是回文链表,正着读是123,反着读为123,但是是奇数回文链表
bool Ispail(PNode plist)
{
//1.最好两个指针,一个从前向后跑,另外一个从后向前跑(双向链表两个都行,单链表后面这个不行)
//因为单链表不存在一个指针从后向前,所以,处理方法为:将单链表中的数据存放到数组中,然后以同样的方式,去判断这个数组即可
int len = Getlength(plist);
ELEM_TYPE* arr = (ELEM_TYPE*)malloc(len * sizeof(ELEM_TYPE));
assert(arr != NULL);
//此时,等长数组申请好了,接下来将单链表中的数据放到数组中
int i = 0;
PNode p = plist;
for (p; p->next != NULL; p = p->next)
{
arr[i++] = p->data;
}
int left = 0;
int right = len - 1;
while (left < right)
{
if (arr[left] != arr[right])
{
free(arr);
return false;
}
left++;
right--;
}
free(arr);//malloc申请的,记得释放
return true;//中间没有发生左右不相等的情况,说明存在回文
}
- 找到单链表倒数第K个节点
思路:申请两个指针,比如找倒数第二个节点,就让其中一个指针提前走两步,接下来两个指针同时向后走,当刚才先走的那个指针走到NULL,则另一个指针就在我们要找的那个位置上
struct Node* Get_k_node(PNode plist, int K)
{
//申请两个指针,比如找倒数第二个节点,就让其中一个指针提前走两步,接下来两个指针同时向后走,当刚才先走的那个指针走到NULL,则另一个指针就在我们要找的那个位置上
assert(plist != NULL );
assert(K >= 1 && K <= Getlength(plist));
PNode p = plist;
PNode q = plist;
for (int i = 0; i < K; i++)
{
p = p->next;
}
while (p != NULL)
{
q = q->next;
p = p->next;
}
return q;
}