之前我们实现过单链表的一些简单接口,而单链表还有一些面试题非常重要,今天来看一下一些常见面试题的实现
(注:本文调用的简单接口实现:http://blog.csdn.net/qq_34021920/article/details/76594389)
1.从尾到头打印单链表
void RPrintList(pList plist);//从尾到头打印链表(不改变链表)
从尾到头打印单链表,而不能改变链表本身,可以用递归很好的实现
void RPrintList(pList plist)
{
if (plist == NULL)
{
return;
}
else if (plist->next == NULL)
{
printf("%d-->", plist->data);
}
else
{
RPrintList(plist->next);
printf("%d-->", plist->data);
}
}
具体测试:
void test1()
{
pList plist = NULL;
InitLinkList(&plist);
PushFront(&plist, 1);
PushFront(&plist, 2);
PushFront(&plist, 3);
PushFront(&plist, 4);
PushFront(&plist, 5);
PrintList(plist);
RPrintList(plist);
}
int main()
{
test1();
system("pause");
return 0;
}
2. 删除一个无头单链表的非尾节点(不能遍历链表)
void EraseNotTail(pNode pos);//删除一个无头单链表的非尾节点
首先我们可以先把要删除节点的下一个节点保存下来,然后将该节点和它下一个节点的值进行交换,最后删除掉那个保存的下个节点
void EraseNotTail(pNode pos)
{
pNode del = NULL;
assert(pos->next);
del = pos->next;
pos->data = del->data;
pos->next = del->next;
free(del);
del = NULL;
}
具体测试:
void test2()
{
pList plist = NULL;
pNode pos = NULL;
InitLinkList(&plist);
PushFront(&plist, 1);
PushFront(&plist, 2);
PushFront(&plist, 3);
PushFront(&plist, 4);
PushFront(&plist, 5);
PrintList(plist);
pos = Find(plist, 2);
EraseNotTail(pos);
PrintList(plist);
}
int main()
{
test2();
system("pause");
return 0;
}
3. 在无头单链表的一个非头节点前插入一个节点
void InsertFrontNode(pNode pos, DataType d);//在无头单链表的一个非头结点前插入一个节点
同上一题类似,我们可以在指定位置的后面插入一个新节点,然后交换新节点和指定节点的数据即可
void InsertFrontNode(pNode pos, DataType d)
{
pNode newNode = BuyNode(d);
DataType tmp = 0;
newNode->next = pos->next;
pos->next = newNode;
//交换数据
tmp = pos->data;
pos->data = newNode->data;
newNode->data = tmp;
}
具体测试:
void test3()
{
pList plist = NULL;
pNode pos = NULL;
InitLinkList(&plist);
PushFront(&plist, 1);
PushFront(&plist, 2);
PushFront(&plist, 3);
PushFront(&plist, 4);
PushFront(&plist, 5);
PrintList(plist);
pos = Find(plist, 2);
InsertFrontNode(pos, 6);
PrintList(plist);
}
int main()
{
test3();
system("pause");
return 0;
}
4. 单链表实现约瑟夫环
void JosephCycle(pList plist, int k);//单链表实现约瑟夫环
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
void JosephCycle(pList plist, int k)
{
pNode cur = plist;
pNode del = NULL;
int num = 0;
while (1)
{
num = k;
//当链表只剩下一个元素时停止循环
if (cur == cur->next)
{
break;
}
while (--num)
{
cur = cur->next;
}
printf("%d ", cur->data);//打印删除的元素
//进行交换删除(将要删除节点的data和它下一个节点的data进行交换,删除下一个节点)
del = cur->next;
cur->data = del->data;
cur->next = del->next;
free(del);
}
printf("\n剩下人的编号:%d\n", cur->data);
}
具体测试:(在这里我们就用约瑟夫的小故事,给上41个人,每次报数报到3的人出列)
void test4()
{
pList plist = NULL;
int i = 0;
InitLinkList(&plist);
for (i = 41; i >= 1; i--)
{
PushFront(&plist, i);
}
Find(plist, 41)->next = plist;
JosephCycle(plist, 3);
}
int main()
{
test4();
system("pause");
return 0;
}
5. 逆置/反转单链表
void ReverseList(pList *pplist);//逆序链表
与第一题从尾到头打印单链表不同的是,逆序链表我们要改变的是链表本身。可以先将链表的第一个节点取下来,保存在一个newHead中,随后依次把剩下的节点取下来连在第一个节点的前面。最后将newHead赋给管理之前链表得到指针pplist
void ReverseList(pList *pplist)
{
pNode cur = *pplist;
pNode tmp = NULL;
pNode newHead = NULL;
assert(pplist);
//链表为空或者链表只有一个节点
if ((*pplist == NULL) || (cur->next == NULL))
{
return;
}
while (cur)
{
tmp = cur;
cur = cur->next;
tmp->next = newHead;
newHead = tmp;
}
*pplist = newHead;
}
逆序链表的测试我们在下一个题中一起展示
6. 单链表排序
void BubbleSort(pList *pplist);//排序链表
因为现在暂时还没有接触快速排序,所以在这里只实现冒泡法排序链表
void BubbleSort(pList *pplist)
{
pNode cur = *pplist;
pNode tail = NULL;
assert(pplist);
//当链表为空或者只有一个节点时
if ((cur == NULL) || (cur->next == NULL))
{
return;
}
//总趟数
while (cur->next != tail)
{
//一趟排序
while (cur->next != tail)
{
if ((cur->data) > (cur->next->data))
{
DataType tmp = cur->data;
cur->data = cur->next->data;
cur->next->data = tmp;
}
cur = cur->next;
}
tail = cur;
cur = *pplist;
}
}
具体测试
void test6()
{
pList plist = NULL;
pNode pos = NULL;
InitLinkList(&plist);
PushFront(&plist, 7);
PushFront(&plist, 2);
PushFront(&plist, 4);
PushFront(&plist, 3);
PushFront(&plist, 5);
PushFront(&plist, 1);
PushFront(&plist, 6);
printf("正常:");
PrintList(plist);
ReverseList(&plist);
printf("逆序后:");
PrintList(plist);
BubbleSort(&plist);
printf("排序后:");
PrintList(plist);
}
int main()
{
test6();
system("pause");
return 0;
}
7.合并两个有序链表,合并后依然有序
pList Merge(pList *pplist1, pList *pplist2);//合并两个有序单链表,合并后依然有序
pList DMerge(pList *pplist1, pList *pplist2);//递归实现两条链表合并
在这里我们分别用递归和非递归进行实现,直接上代码:
pList Merge(pList *pplist1, pList *pplist2)
{
pNode cur1 = *pplist1;
pNode cur2 = *pplist2;
pList newHead = NULL;
pNode tail = NULL;
assert(pplist1);
assert(pplist2);
if ((*pplist1 == NULL) && (*pplist2 == NULL))
{
return NULL;
}
if (*pplist1 == *pplist2)
{
return *pplist1;
}
if (*pplist1 == NULL)
{
return *pplist2;
}
if (*pplist2 == NULL)
{
return *pplist1;
}
if ((cur1->data) > (cur2->data))
{
newHead = cur2;
cur2 = cur2->next;
}
else
{
newHead = cur1;
cur1 = cur1->next;
}
newHead->next = NULL;
tail = newHead;
while (cur1&&cur2)
{
if ((cur1->data) > (cur2->data))
{
tail->next = cur2;
cur2 = cur2->next;
}
else
{
tail->next = cur1;
cur1 = cur1->next;
}
tail = tail->next;
tail->next = NULL;
}
if (cur1 == NULL)
{
tail->next = cur2;
}
else if (cur2 == NULL)
{
tail->next = cur1;
}
return newHead;
}
pList DMerge(pList *pplist1, pList *pplist2)//递归实现两条链表合并
{
pNode cur1 = *pplist1;
pNode cur2 = *pplist2;
pList newHead = NULL;
pNode tail = NULL;
assert(pplist1);
assert(pplist2);
if ((cur1 == NULL) && (cur2 == NULL))
{
return NULL;
}
if (cur1 == cur2)
{
return cur1;
}
if (cur1 == NULL)
{
return cur2;
}
if (cur2 == NULL)
{
return cur1;
}
if ((cur1->data) > (cur2->data))
{
newHead = cur2;
cur2 = cur2->next;
}
else
{
newHead = cur1;
cur1 = cur1->next;
}
newHead->next = NULL;
if (cur1 == NULL || cur2 == NULL);
{
tail = newHead;
tail->next = DMerge(&cur1, &cur2);
}
return newHead;
}
具体测试:
void test7()
{
pList plist1 = NULL;
InitLinkList(&plist1);
PushFront(&plist1, 9);
PushFront(&plist1, 7);
PushFront(&plist1, 5);
PushFront(&plist1, 3);
PushFront(&plist1, 1);
pList plist2 = NULL;
InitLinkList(&plist2);
PushFront(&plist2, 10);
PushFront(&plist2, 8);
PushFront(&plist2, 6);
PushFront(&plist2, 4);
PushFront(&plist2, 2);
PushFront(&plist2, 0);
pList newList1 = NULL;
newList1 = Merge(&plist1, &plist2);
PrintList(newList1);
pList newList2 = NULL;
newList2 = DMerge(&plist1, &plist2);
PrintList(newList2);
}
8. 查找单链表的中间节点,要求只能遍历一次链表
pNode FindMidNode(pList plist);//查找链表的中间节点(只能遍历一次链表)
查找链表中间节点,我们可以定义两个指针,快指针每次次走两步,慢指针每次走一步,当快指针到达链表尾部时,慢指针所在的位置就是中间节点的位置。
pNode FindMidNode(pList plist)
{
pNode fast = plist;
pNode slow = plist;
if (plist == NULL)
{
return NULL;
}
while (fast&&fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
具体测试:
void test8()
{
pNode pos = NULL;
pList plist1 = NULL;
InitLinkList(&plist1);
PushFront(&plist1, 9);
PushFront(&plist1, 7);
PushFront(&plist1, 5);
PushFront(&plist1, 3);
PushFront(&plist1, 1);
pList plist2 = NULL;
InitLinkList(&plist2);
PushFront(&plist2, 10);
PushFront(&plist2, 8);
PushFront(&plist2, 6);
PushFront(&plist2, 4);
PushFront(&plist2, 2);
PushFront(&plist2, 0);
pList newList1 = NULL;
newList1 = Merge(&plist1, &plist2);
PrintList(newList1);
pos = FindMidNode(newList1);
printf("中间节点:%d\n", pos->data);
}
int main()
{
test8();
system("pause");
return 0;
}
9. 删除链表的倒数第K个结点
void DelKNode(pList plist, int k);//删除链表的倒数第K个节点
同样的,定义两个指针。一个指针first先走上K 步,然后第二个指针second再和first指针同时走,当first指针到达链表尾部时,second指针所在的位置就为倒数第K个节点的位置,随后我们用之前讲过交换数据的方法删除即可
(注意,因为是用交换删除的方法,如果K=1,则它没有下一个节点。我们可以对这种情况进行单独处理,这时候函数就和尾删一样了,我在这里直接调用尾删的接口)
void DelKNode(pList plist, int k)
{
pNode first = plist;
pNode second = plist;
if (k == 1)
{
PopBack(&plist);
return;
}
while (first&&first->next)
{
//找到倒数第K个节点
first = first->next;
if (--k <= 0)
{
second = second->next;
}
}
//交换删除倒数第K个
if (k <= 0)
{
pNode del = second->next;
second->data = del->data;
second->next = del->next;
free(del);
del = NULL;
}
}
具体测试:
void test9()
{
pList plist = NULL;
InitLinkList(&plist);
PushFront(&plist, 1);
PushFront(&plist, 2);
PushFront(&plist, 3);
PushFront(&plist, 4);
PushFront(&plist, 5);
PushFront(&plist, 6);
PushFront(&plist, 7);
PrintList(plist);
DelKNode(plist, 1);
PrintList(plist);
DelKNode(plist, 3);
PrintList(plist);
}
int main()
{
test9();
system("pause");
return 0;
}
10. 判断单链表是否带环?若带环,求环的长度?求环的入口点?
pNode CheckCycle(pList plist);//判断链表是否带环
int GetCircleLength(pNode meet);//求环的长度
pNode GetCircleEntryNode(pNode meet, pList plist);//求环的入口点
(1)判断链表是否带环
还是定义上两个指针,快指针每次走两步,慢指针每次走一步,如果链表带环的话,两个指针必定会相遇,而且相遇点一定在环上。
pNode CheckCycle(pList plist)//判断链表是否带环
{
pNode fast = plist;
pNode slow = plist;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)//快指针和慢指针一定会在环上相遇
{
return slow;//相遇点
}
}
return NULL;
}
(2)求环的长度
定义一个指针,从相遇点(见链表判环问题)开始走,在定义一个计数器,每走一步计数器家家,当下次到达相遇点时计数器的大小就是环的长度了。
int GetCircleLength(pNode meet)//求环的长度
{
int count = 0;
pNode cur = meet;
do
{
cur = cur->next;
count++;
} while (cur != meet);
return count;
}
(3)求环的入口点
在这里我们介绍两种方法
方法一:从开始到环入口点的距离x = 环长度len的整数倍c*len - 从环入口点到相遇点的距离y
定义一个指针从开始出发,另一个指针从相遇点出发,两个指针再次相遇的点就为入口点
详见下图:
方法二:定义两个指针,一个先走环的长度步,另外一个在开始走,两个指针相遇的地方就为环的入口点
pNode GetCircleEntryNode(pNode meet, pList plist)//求环的入口点
{
//方法一:从开始到环入口点的距离x = 环长度len的整数倍c*len - 从环入口点到相遇点的距离y
//定义一个指针从开始出发,另一个指针从相遇点出发,两个指针再次相遇的点就为入口点
//pNode cur = plist;
//while (cur != meet)
//{
// cur = cur->next;
// meet = meet->next;
//}
//return cur;
//方法二:定义两个指针,一个先走环的长度步,另外一个在开始走,两个指针相遇的地方就为环的入口点
pNode first = plist;
pNode second = plist;
int num = GetCircleLength(meet);
while (num--)
{
first = first->next;
}
while (first != second)
{
first = first->next;
second = second->next;
}
return first;
}
具体测试:
void test10()
{
pList plist = NULL;
pNode pos = NULL;
pNode entry = NULL;
InitLinkList(&plist);
PushFront(&plist, 1);
PushFront(&plist, 2);
PushFront(&plist, 3);
PushFront(&plist, 4);
PushFront(&plist, 5);
PushFront(&plist, 6);
PushFront(&plist, 7);
PrintList(plist);
DelKNode(plist, 1);
PrintList(plist);
DelKNode(plist, 3);
PrintList(plist);
Find(plist, 1)->next = Find(plist, 4);
pos = CheckCycle(plist);
if (pos == NULL)
{
printf("不带环\n");
}
else
{
printf("带环\n");
printf("相遇点:%d\n", pos->data);
}
printf("环长度:%d\n", GetCircleLength(pos));
entry = GetCircleEntryNode(pos, plist);
printf("入口点:%d\n", entry->data);
}
int main()
{
test10();
system("pause");
return 0;
}
11. 判断两个链表是否相交,若相交,求交点。(假设链表不带环)
pNode CheckCross(pList p1, pList p2);//判断两条链表是否相交(两条链表都不带环)
在这里我们只讨论两条链表都不带环的情况
对于求交点的问题我们也给出两种实现方法:
方法一:将两条相交的链表连成一个带环的链表,将第一个链表尾部的next指向第二个链表的头部再利用之前实现过的接口,把问题转化成求环的入口点
方法二:第一条链表的长度为len1,第二条链表的长度为len2,定义一个指针从长度长的链表先走|len1-len2|步,第二个指针再从长度短的链表走,当两个指针第一次指向地址相同的那个节点,则该节点为相交节点。
pNode CheckCross(pList p1, pList p2)//判断两条链表是否相交(两条链表都不带环)
{
pNode cur1 = p1;
pNode cur2 = p2;
int len1 = 0;
int len2 = 0;
int num = 0;
//两条链表有任意一条为空则不相交
if ((cur1 == NULL) || (cur2 == NULL))
{
return NULL;
}
while (cur1->next)
{
cur1 = cur1->next;
len1++;
}
while (cur2->next)
{
cur2 = cur2->next;
len2++;
}
//相交返回交点
if (cur1 == cur2)
{
//方法一:将两条相交的链表连成一个带环的链表,将第一个链表尾部的next指向第二个链表的头部
//再利用之前实现过的接口,把问题转化成求环的入口点
//cur1->next = p2;
//pNode meet = CheckCycle(p1);
//pNode cross = GetCircleEntryNode(meet, p1);
//return cross;
//方法二:第一条链表的长度为len1,第二条链表的长度为len2,定义一个指针从长度长的链表先走|len1-len2|步,
//第二个指针再从长度短的链表走,当两个指针第一次指向地址相同的那个节点,则该节点为相交节点。
num = abs(len1 - len2);//求绝对值
if (len1 > len2)
{
cur1 = p1;
cur2 = p2;
}
else
{
cur2 = p1;
cur1 = p2;
}
for (int i = 0; i < num; i++)
{
cur1 = cur1->next;
}
while (cur1 != cur2)
{
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}
else
{
return NULL;
}
}
具体测试:
void test11()
{
pList plist1 = NULL;
pNode cross = NULL;
InitLinkList(&plist1);
PushFront(&plist1, 10);
PushFront(&plist1, 9);
PushFront(&plist1, 7);
PushFront(&plist1, 5);
PushFront(&plist1, 3);
PushFront(&plist1, 1);
pList plist2 = NULL;
InitLinkList(&plist2);
PushFront(&plist2, 6);
PushFront(&plist2, 4);
PushFront(&plist2, 2);
PushFront(&plist2, 0);
Find(plist2, 6)->next = Find(plist1, 5);
cross = CheckCross(plist1, plist2);
printf("相交点:%d\n", cross->data);
}
int main()
{
test11();
system("pause");
return 0;
}
12. 复杂链表的复制。
pComplexNode BuyComplexNode(DataType d);//创建复杂链表的节点
void PrintComplexList(pComplexNode p);//打印复杂链表
pComplexNode CopyComplexList(pComplexNode head);//复制复杂链表
复杂链表:一个链表的每个节点,有一个指向next指针指向下一个节点,还有一 个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表, 返回复制后的新链表
先来看看复杂链表的结构:
//复杂链表
typedef struct ComplexNode
{
DataType *data;
struct ComplexNode *next;
struct ComplexNode *random;
}ComplexNode, *pComplexNode;
我们可以把复杂链表的复制分为三个部分
(1)、复制复杂链表的每个节点,并插入到该节点的后面
(2)、调整复制节点的random指针指向
(3)、分离两条链表
贴一张我在实现过程中画的草图,希望能够帮助大家更好的理解(草图!草图!草图!)
紫色区域为random指针域,绿色区域为next指针域
我们可以看到,复制节点的random指向刚好就是指向它那个节点random指针的next的指向。
pComplexNode BuyComplexNode(DataType d)//创建复杂链表的节点
{
pComplexNode newNode = (pComplexNode)malloc(sizeof(ComplexNode));
if (newNode == NULL)
{
perror("malloc");
exit(EXIT_FAILURE);
}
newNode->data = d;
newNode->next = NULL;
newNode->random = NULL;
return newNode;
}
void PrintComplexList(pComplexNode p)//打印复杂链表
{
pComplexNode cur = p;
while (cur)
{
printf("[%d]-->(%d)-->", cur->data, cur->random->data);
cur = cur->next;
}
printf("NULL\n");
}
pComplexNode CopyComplexList(pComplexNode head)//复制复杂链表
{
pComplexNode pold = head;
pComplexNode cur = NULL;
pComplexNode newNode = NULL;
pComplexNode newHead = NULL;
//1.复制原来复杂链表的每个节点并插入到该节点后
while (pold)
{
cur = pold->next;
newNode = BuyComplexNode(pold->data);
pold->next = newNode;
newNode->next = cur;
pold = cur;
}
//2.调整random指针的指向
pold = head;
while (pold)
{
newNode = pold->next;
if (pold->random)
{
cur = pold->random->next;
newNode->random = cur;
}
pold = newNode->next;
}
//3.分离两条链表
pold = head;
if (pold != NULL)
{
newHead = pold->next;
}
while (pold->next->next)
{
newNode = pold->next;
pold->next = newNode->next;
pold = newNode->next;
newNode->next = pold->next;
}
pold->next = NULL;
return newHead;
}
具体测试:
void test12()
{
pComplexNode newList = NULL;
pComplexNode p1 = NULL;
pComplexNode p2 = NULL;
pComplexNode p3 = NULL;
pComplexNode p4 = NULL;
p1 = BuyComplexNode(1);
p2 = BuyComplexNode(2);
p3 = BuyComplexNode(3);
p4 = BuyComplexNode(4);
p1->next = p2;
p2->next = p3;
p3->next = p4;
p1->random = p3;
p2->random = p4;
p3->random = p2;
p4->random = p3;
PrintComplexList(p1);
newList = CopyComplexList(p1);
PrintComplexList(newList);
}
int main()
{
test12();
system("pause");
return 0;
}
我总结的面试题就这么多,在之后的学习中如果还遇到较好的面试题也会加进来。