下面来写一写链表的笔试题
整个工程基于这篇博客下的代码
下面上代码,题目和解题思路都在注释里交待……
//1.从尾到头打印链表
void LinkListPrintReverse(LinkNode* head)//思路:递归
{
if (head == NULL)
{
return;
}
LinkListPrintReverse(head->next);
printf("[ %c ]", head->data);
}
//2.删除一个无头单链表的非尾节点(不能遍历链表)
void LinkListErase2(LinkNode** phead, LinkNode* pos)
{
if (phead == NULL)
{//非法输入
return;
}
if (*phead == NULL)
{//空链表
return;
}
/*
if (pos->next == NULL)
{//此时是尾部删除,此处不做判断,题中说明为非尾节点
//尾删
}
*/
//下面思路:a b c ,删b时,b的位置是pos,把c的值赋给pos位置,覆盖掉b,再删除c节点即可。
//为什么删除pos后一个比较好操作?答:若直接删除pos,pos还保存着pos->next这个信息,删除pos后,此信息丢失
//若删除pos后一个,也就是直接free掉pos->next;
if (pos == NULL||pos->next==NULL)
{//以下对pos解引用了,此处必须先对pos指针进行非空判断
return;
}
pos->data = pos->next->data;
//记录要删除的位置
LinkNode* to_erase = pos->next;
pos->next = pos->next->next;//这一步一定要,要把pos的next置为之前pos的next的next,这样才把要删除的节点完全的孤立出来。
DestroyNode(to_erase);
}
//3.在节点前插入值,不能遍历
void LinkListMyBefore(LinkNode* pos, LinkNodeType value)
{
if (pos == NULL)
{
return;
}
// 链表 a b c d ,在c之前插入e
// (1) 在pos后创建新节点: a -> b-> c-> d
// new_node : c -> NULL
LinkNode* new_node = CreateNode(pos->data);
// (2) new_node->d
new_node->next = pos->next;
//(3) pos->new_node
pos->next = new_node;
//pos的值改为value
pos->data = value;
}
//4.约瑟夫环问题,规定一次走M步,淘汰每一次指向的元素,看幸存者是谁。带环
LinkNode* JospehCircle(LinkNode* head, int M)
{
if (head == NULL)
{//空链表
return;
}
if (head->next == head)
{//只有一个元素
return head;
}
LinkNode* cur = head;
while (cur->next != head)
{
int i = 1;
for (; i < M; ++i)
{
cur = cur->next;
}
//此时cur指向的元素即为要淘汰的
printf("[ %c ]", cur->data);
cur->data = cur->next->data;
LinkNode* to_erase = cur->next;
cur->next = to_erase->next;
DestroyNode(to_erase);
}
//此时剩下的cur即为幸存者,lucky boy
return cur;
}
//5.单链表逆置
void LinkListReverse1(LinkNode** phead)
{
//1.判断phead是否合法
if (phead == NULL)
{
return;
}
//2.判断是否为空链表
if (*phead == NULL)
{
return;
}
//3.判断链表是否只有一个元素
if ((*phead)->next == NULL)
{
return;
}
//4.思路:遍历单链表,删除当前元素后,进行头插。时间复杂度:O(n)
// (1)定义一个cur,指向头指针,用来遍历单链表
LinkNode* cur = *phead;
//(2)进行遍历,直到最后一个元素
while (cur->next != NULL) //这里的条件必须是cur的下一个不为空,若为cur不为空,程序崩溃。多画图
{
//(3)定义一个节点来保存要删除的元素的节点信息
LinkNode* to_delete = cur->next;
cur->next = to_delete->next;
//(4)把要删除的to_delete插入到头指针之前
to_delete->next = *phead;
//(5)重置头指针
*phead = to_delete;
//cur = cur->next;//不需要修改cur的位置,上面已经将cur的next改变
}
//此时已将整个单链表遍历头插完成,cur指向最后一个元素
return;
}
void LinkListReverse2(LinkNode** phead)
{
//以下几步是常规判断
//1.判断phead是否合法
if (phead == NULL)
{
return;
}
//2.判断是否为空链表
if (*phead == NULL)
{
return;
}
//3.判断链表是否只有一个元素
if ((*phead)->next == NULL)
{
return;
}
//4.思路:定义一个pre指针代表每次交换后的头指针位置,cur指向头指针的下一个元素,next保存cur的下一个的节点信息
//(1) 定义这三个节点
LinkNode* cur = (*phead)->next;
LinkNode* pre = *phead;
//[ 思考 ??]
pre->next=NULL;//pre的next指向的是随机地址,此处必须将pre的next置为NULL,否则打印单链表时,条件不成立,将出错。
while (cur != NULL)
{
LinkNode* next = cur->next;
//(2)修改cur的指向
cur->next = pre;
//(3)更新pre、cur的指向
pre = cur;
cur = next;
}
//修改头指针指向
*phead = pre;
return;
}
//6.单链表排序(冒泡&快速)
//交换函数
void Swap(LinkNodeType *a, LinkNodeType * b)
{
LinkNodeType tmp = *a;
*a = *b;
*b = tmp;
}
void LinkListBubbleSort(LinkNode* head)
{
if (head == NULL)
{//空链表
return;
}
if (head->next==NULL)
{//只有一个元素
return;
}
LinkNode* count = head;
//第一重循环,排序趟数
for (; count != NULL; count = count->next)
{
LinkNode* cur = head;
LinkNode* tail = NULL;//tail是提供个cur的结束标志
//第二重循环,将最大的与当前值进行交换,一直冒到最后
for (; cur->next != tail; cur = cur->next)
{
if (cur->data > cur->next->data)
{
Swap(&cur->data, &cur->next->data);
}
}
//此时cur已经指向最后一个元素,也就是最大的元素
//更新tail
tail = cur; //tail指向最后一个元素,也就是最后的最大的元素
}
return;
}
//7.合并两个有序链表,合并后仍然有序
LinkNode* LinkListMerge(LinkNode* head1, LinkNode* head2)
{
if (head1 == NULL)
{
return head2;
}
if (head2 == NULL)
{
return head1;
}
LinkNode* cur1 = head1;
LinkNode* cur2 = head2;
LinkNode* new_head = NULL;//指向新链表的头结点
LinkNode* new_tail = NULL;//新链表尾节点
while (cur1 != NULL&&cur2 != NULL)
{
if (cur1->data < cur2->data)
{
if (new_tail == NULL)
{
new_head = new_tail = cur1;
}
else
{
new_tail->next = cur1;
new_tail = new_tail->next;
}
cur1 = cur1->next;
}
else//此处包含相等和cur1->data>cur2->data两种情况
{
if (new_tail == NULL)
{
new_head = new_tail = cur2;
}
else
{
new_tail->next = cur2;
new_tail = new_tail->next;
}
cur2 = cur2->next;
}
}
//循环跳出,来判断是因为什么跳出
//有一方已经到达NULL,要把剩下的一方追加到新链表中
if (cur1 == NULL)
{
new_tail->next = cur1;
}
else
new_tail->next = cur2;
return new_head;
}
//8、查找链表中间节点,只能遍历一遍
LinkNode* LinkListFindMidNode(LinkNode* head)
{//定义一个快指针,一个慢指针
//慢指针走一步,快指针走两步
//当快指针走到NULL时,慢指针就是中间节点,返回慢指针即可
LinkNode* slow = head;
LinkNode* fast = head;
while (fast != NULL&&fast->next != NULL) //两个条件的顺序必须这么写,前一个条件不成立时,直接跳出,短路求值
{ //如果交换顺序,fast->next不为空,而fast为空时,就完蛋了。
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
//9、查找单链表的倒数第K个节点,要求只能遍历一次链表。
LinkNode* LinkListFindLastKNode(LinkNode* head, int k)
{
if (head == NULL)
{//非法输入
return NULL;
}
//思路:快指针、慢指针
//快指针先走K步后,慢指针和快指针再一起走,直到快指针走到尾部,返回慢指针。
LinkNode* fast = head;
LinkNode* slow = head;
//让fast先走K步
int i = 0;
for (; i < k; ++i)
{
if (fast == NULL)
break;
fast = fast->next;
}
//判断fast走没走完K步
if (i != k)
{//没走完的话,说明k长度超过链表长度
return NULL;
}
while (fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
//10、删除链表的倒数第K个节点
void LinkListEraseLastKNode(LinkNode** phead, int k)
{
if (phead == NULL)
{//非法输入
return;
}
if (*phead == NULL)
{//空链表
return;
}
//思路:找到倒数第K+1个节点,直接删除
int len = LinkListSize(*phead);
if (k > len)
{//K就非法了,。没意义
return;
}
if (k == len)
{//直接就头删,头删需要修改头指针位置,所以函数传参要**
LinkListPopFront(phead);
return;
}
//接下来,来找到倒数第K+1个节点
LinkNode* pre = *phead;
int i = 0;
for (; i < len - (k + 1); ++i)
{
pre = pre->next;
}
//此时pre指向倒数第K+1个节点,我们来执行删除动作
LinkNode* to_delete = pre->next;
pre->next = to_delete->next;
free(to_delete);
}
//11、判定链表是否带环?若带环求环长度?求环入口?
//法一:遍历链表,走一个节点,放入顺序表中,走下一个节点时,与顺序表中保存的节点作对比
// 相同就跳出,带环;不相同就继续下一个。
// 时间复杂度:O(n^2); 空间复杂度O(n);
//法二:优化:定义两个指针,快指针、慢指针,快指针走两步,慢指针走一步,
// 如果快慢指针相遇,则证明有环。
//时间复杂度:O(n);空间复杂度 :O(1);
//下面来实现一下方法二
int LinkListHasCycle(LinkNode* head)
{
if (head == NULL)
{//非法输入
return 0;
}
LinkNode* fast = head;
LinkNode* slow = head;
while (fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
return 1;
}
return 0;
}
}
LinkNode* LinkListHasCycle2(LinkNode* head)
{
if (head == NULL)
{//非法输入
return NULL;
}
LinkNode* fast = head;
LinkNode* slow = head;
while (fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
return slow;
}
return NULL;
}
}
//求环的长度
size_t LinkListCycleLen(LinkNode* head)
{
LinkNode* meet_node = LinkListHasCycle2(head);
if (meet_node == NULL)
{
return 0;
}
LinkNode* cur = meet_node->next;
size_t len = 1;
while (cur != meet_node)
{
cur = cur->next;
++len;
}
return len;
}
//求环的入口
//思路:一个指针记录环的相遇点,一个指针记录链表的头结点
//两个指针同时走,相等时就是换环的入口点
LinkNode* LinkListCycleEnter(LinkNode* head)
{
if (head == NULL)
{
return NULL;
}
LinkNode* meet_node = LinkListHasCycle2(head);
LinkNode* cur1 = head;
LinkNode* cur2 = meet_node;
while (cur1 != cur2)
{
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}
//12、判断两个链表是否相交,若相交,求交点。(假设链表不带环)
//思路:(1)求len1,len2
// (2)求差值;(3)长的先走差值;(4)一样长后,两个指针再每次走一步
// (5)循环(4),在循环中判断指针指向的值是否相等,如果相等,就证明有交点;不相等就返回NULL
LinkNode* LinkListCrossPos(LinkNode* head1, LinkNode* head2)
{
if (head1 == NULL || head2 == NULL)
{//非法输入
return NULL;
}
size_t len1 = LinkListSize(head1);
size_t len2 = LinkListSize(head2);
LinkNode* cur1 = head1;
LinkNode* cur2 = head2;
if (len1 > len2)
{//head1比较长
size_t i = 0;
for (; i < len1 - len2; ++i)
{
cur1 = cur1->next;
}
}
else
{
size_t i = 0;
for (; i < len2 - len1; ++i)
{
cur2 = cur2->next;
}
}
while (cur1 != NULL || cur2 != NULL)
{
if (cur1 == cur2)
{
return cur1;
}
cur1 = cur1->next;
cur2 = cur2->next;
}
return NULL;
}