转载:链表经典练习题
一、从尾到头打印单链表
因为单链表是有指向的,前一节点只能指向下一节点,因此为了实现从尾到头打印链表可以使用一种非常简单的方法就是递归,先遍历再输出。
代码实现:
void LinkListReversePrint(LinkNode* head){
85 if(head == NULL){
86 return;//空链表
87 }
88 LinkListReversePrint(head -> next);
89 printf("[%c | %p]",head ->data,head);
}
二、删除一个无头单链表的非尾节点(不能遍历)
删除一个无头单链表的非尾节点通过遍历来实现时很好实现的,但是在本题目中它最关键的一个要求就是不能遍历。
因为单链表只有next指针,只指向下一个元素,所以我们如果要删除一个节点,我们可以从要删除的节点的下一个节点入手,我们把要删除节点的下一个节点的值赋给删除节点,然后再修改删除节点的指向,再删除下一个节点,就可以实现题目所说。
具体代码实现如下:
void LinkListPop(LinkNode** phead, LinkNode* pos){
if(phead == NULL || pos == NULL){
return ;//非法输入
}
if(*phead == NULL){
return;//空链表
}
pos -> data = pos -> next -> data;
pos -> next = pos -> next -> next;
LinkNodeDestory(pos->next);
}
三、在一个无头单链表的节点前插入一个节点(不允许遍历)
这道题其实跟上一道题很相像,我们不能对单链表节点的前一个节点进行操作,但是我们可以对其下一个节点进行处理,因此具体思路就跟上题一样,在要插入节点的后面创造一个新节点,然后把要插入节点的值赋给新节点,然后再将需要插入节点的值赋给插入节点,再改变两节点的指向就可完成实现。
具体代码实现如下:
void LinkListInsertBefore(LinkNode** phead, LinkNode* pos, LinkNodeType value){
if(phead == NULL || pos == NULL){
return;//非法输入
}
LinkNode* new_node = LinkNodeCreate(pos -> data);
pos -> data = value;
new_node -> next = pos -> next;
pos -> next = new_node;
}
四、在单链表实现约瑟夫环
首先我们了解下什么是约瑟夫环:
讲一个比较有意思的故事:约瑟夫是犹太军队的一个将军,在反抗罗马的起义中,他所率领的军队被击溃,只剩下残余的部队40余人,他们都是宁死不屈的人,所以不愿投降做叛徒。一群人表决说要死,所以用一种策略来先后杀死所有人。
于是约瑟夫建议:每次由其他两人一起杀死一个人,而被杀的人的先后顺序是由抽签决定的,约瑟夫有预谋地抽到了最后一签,在杀了除了他和剩余那个人之外的最后一人,他劝服了另外一个没死的人投降了罗马。
我们这个规则是这么定的:
在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命。
按照如下规则去杀人:
所有人围成一圈
顺时针报数,每次报到q的人将被杀掉
被杀掉的人将从房间内被移走
然后从被杀掉的下一个人重新报数,继续报q,再清除,直到剩余一人
你要做的是:当你在这一群人之间时,你必须选择一个位置以使得你变成那剩余的最后一人,也就是活下来。
实现约瑟夫环首先需要判断选择的那个人是否是最后一个幸存的人,不是的话再进行约瑟夫环行动。
具体代码实现如下:
LinkNode* JosephCycle(LinkNode* head, size_t food){
if(head == NULL){
return NULL;
}
if(food == 0){
return NULL;
}
LinkNode* cur = head;
while(cur != cur -> next){
size_t i = 0;
for(; i < food - 1; ++i){
cur = cur -> next;
}
printf("food[%c]\n",cur -> data);
cur -> data = cur -> next -> data;
cur -> next = cur -> next -> next;
}
return cur;
}
五、逆置单链表
逆置单链表就不简单的是逆序打印单链表了,我们不仅要逆置它的值还要逆置它的地址。
逆置单链表我们可以通过改变各节点的指向来处理,就拿第一个和第二个节点来说,首先我们创建一个头结点指针使其指向第一个节点,额庵后我们可以改变第二个节点的指向,使其指向第一个节点,然后将头结点指针赋给第二个节点,以此循环,从而逆置单链表。
具体代码实现如下:
void LinkListReverse(LinkNode** phead){
if(phead == NULL){
return;
}
if(*phead == NULL){
return;
}
LinkNode* cur = *phead;
while(cur -> next != NULL){
LinkNode* to_delete = cur -> next;
cur -> next = to_delete -> next;
to_delete -> next = *phead;
*phead = to_delete;
}
}
六、单链表冒泡排序
冒泡排序基本算法思路:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。
3.针对所有的元素重复以上的步骤,直到没有任何一对数字需要比较。
要用单链表实现,我们同样可以使用节点和它后面一个节点互相交换值来实现。
具体代码实现如下:
void Swap(LinkNodeType* a, LinkNodeType* b){
LinkNodeType tmp = *a;
*a = *b;
*b = tmp;
}
void LinkListBubbleSort(LinkNode* head){
LinkNode* count = head;
LinkNode* tail = NULL;
for(; count != NULL; count = count -> next){
LinkNode* cur = head;
for(; cur ->next != tail; cur = cur -> next){
if(cur -> data > cur -> next -> data){
Swap(&cur->data, &cur->next->data);
}
}
tail = cur;
}
}
七、合并两个有序链表,合并之后依然有序。
合并两个有序链表,并且需要其合并之后依然有序,该操作的大概思路如下:
- 创建一个新的链表,用于存放合并后的链表。
- 将两个链表的头结点的值进行比较,然后将小的节点放在新链表。
- 然后将两个链表改变了的链表,让其后一个值在与第二个链表比较,依次类推。
- 最后那个链表先结束,就将另外一个链表没有进行比较的部分拷贝在新链表后。
- 此时得到的新链表就是合并后的链表。
具体代码实现如下:
LinkNode* LinkListMerge(LinkNode* head1, LinkNode* head2){
if(head1 == NULL || head2 == NULL){
return NULL;
}
LinkNode* new_head = NULL;
LinkNode* new_tail = NULL;
LinkNode* cur1 = head1;
LinkNode* cur2 = head2;
while(cur1 != NULL && cur2 != NULL){
if(cur1 -> data <= cur2 -> data){
if(new_head == NULL){
new_head = new_tail = cur1;
}
else{
new_tail -> next = cur1;
new_tail = cur1;
}
cur1 = cur1 -> next;
}
else if(cur1 -> data > cur2 -> data){
if(new_head == NULL){
new_head = new_tail = cur2;
}
else{
new_tail -> next = cur2;
new_tail = cur2;
}
cur2 = cur2 -> next;
}
}
return new_head ;
}
八、查找单链表的中间节点,要求只能遍历一次
查找单链表的指定节点,我们可以通过不断遍历的到,但是题目要求只能遍历一次,那么我们原来的方法就不行了,这样我们就需要寻找新的途径。要解决这个问题,就需要应用快慢指针问题。
什么是快慢指针,就拿这个题目来说,我们定义两个指针,他们是fast和slow,开始他们都指向头节点,然后让他们同时遍历,但是每次fast走两步,即fast=fast->next->next,让slow走一步,即slow=slow->next,然后当fast走到最后的时候,slow所指的节点就是中间节点,这样我们就实现了查找单链表的中间节点。但需要注意一点,就是快慢指针要考虑好快针的结束条件。
具体代码实现如下:
LinkNode* FindMidNode(LinkNode* head){
LinkNode* slow = head;
LinkNode* fast = head;
while(fast != NULL && fast -> next != NULL){
fast = fast -> next -> next;
slow = slow -> next;
}
return slow;
}
九、查找单链表倒数第k个节点,只允许遍历一次
这道题也是要求,只能遍历一次,这样的话我们也可以使用快慢指针问题。
首先我们现实块指针fast走K步,然后再让慢指针slow在开始走,这样的话当fast走结束的时候,slow所指的位置就是题目所求。
具体代码实现如下:
LinkNode* FindLastKNode(LinkNode* head,size_t K){
LinkNode* fast = head;
LinkNode* slow = head;
size_t i = 0;
for(; i < K && fast != NULL; ++i){
fast = fast -> next;
}
if(i < K){
return NULL;//链表节点小于K,直接返回空
}
while(fast != NULL){
fast = fast -> next;
slow = slow -> next;
}
return slow;
}
十、删除单链表倒数第K个节点
要删除倒数第K个节点,首先我们需要知道该链表有几个节点,然后再使指向头结点的指针走size-k-1步,删除该指针所指向的节点即可。本题需要注意的是,它的约束条件较多,需要认真思考。
具体代码实现如下:
void EraseLastKNode(LinkNode** head, size_t K){
if(head == NULL){
return;//非法输入
}
if(*head == NULL){
return;//空链表
}
size_t size = LinkListSize(*head);
if(size < K){
return;//要删除的节点不存在
}
if(size == K){
LinkNode* to_delete = *head;
*head = (*head)->next;
LinkNodeDestory(to_delete);
return;
}
LinkNode* cur = *head;
size_t i = 0;
for(; i < size - (K+1); ++i){
cur = cur -> next;
}
LinkNode* to_delete = cur -> next;
cur -> next = to_delete -> next;
LinkNodeDestory(to_delete);
}
十一、判断单链表是否带环,如果带环的话,求环长度,环的入口点,然后计算每个算法的时间复杂度和空间复杂度
所谓的单链表带环,就是单链表的一个尾节点没有指向NULL,而是指向了单链表内的另一节点。
如上图4号节点本应指向NULL但是它却指向了2号节点,这样这个单链表就带环了。
要判断链表是否带环,我们同样可以使用快慢指针,让fast和slow的绝对速度相差一,当他们两个相遇的时候,就可以说明链表啊带环。
具体代码实现如下:
//链表是否带环,时间复杂度O(n),没有开辟新空间,所以空间复杂度为O(1)
LinkNode* HasCycle(LinkNode* head){
LinkNode* fast = head;
LinkNode* slow = head;
while(fast != NULL && fast -> next != NULL){
fast = fast -> next -> next;
slow = slow -> next;
if(fast == slow){
return fast;
}
}
return NULL;
}
//环的长度,时间复杂度O(n),空间复杂度O(1)
size_t GetCycleLen(LinkNode* head){
LinkNode* meet_node = HasCycle(head);
if(meet_node == NULL){
return 0;//链表无环
}
size_t count = 1;
LinkNode* cur = meet_node;
for(; cur -> next != meet_node; cur = cur -> next){
++count;
}
return count;
}
//环的入口点,时间复杂度O(n),空间复杂度O(1)
LinkNode* GetCycleEntry(LinkNode* head){
LinkNode* meet_node = HasCycle(head);
if(meet_node == NULL){
return NULL;//没有环直接返回
}
LinkNode* cur1 = head;
LinkNode* cur2 = meet_node;
while(cur1 != cur2){
cur1 = cur1 -> next;
cur2 = cur2 -> next;
}
return cur1;
}
十三、判断两个链表是否相交,若相交求交点(链表不带环)
两个链表相交是什么情况,也就是一个链表的尾节点指向另一个节点的任意一个节点,则说明两个链表相交
如何判断两个链表相交,我们同样可以使用快慢指针来解决。
- 先求两个链表的节点数,再创建两个指针,让他们分别指向两个链表的头节点,指向长链表的为fast,指向短链表的为slow。
- 然后让fast先走两链表节点数之差步。
- 判断两个指针是否会相等,相等则说明两个链表相交,此时相交点就是fast和slow所指的节点。
具体代码实现如下:
LinkNode* HasCross(LinkNode* head1,LinkNode* head2){
if(head1 == NULL || head2 == NULL){
return NULL;
}
size_t size1 = LinkListSize(head1);
size_t size2 = LinkListSize(head2);
LinkNode* cur1 = head1;
LinkNode* cur2 = head2;
if(size1 < size2){
size_t i = 0;
for(; i < size2 - size1; i++){
cur1 = cur1 -> next;
}
}else{
size_t i = 0;
for(; i < size1 - size2; i++){
cur2 = cur2 -> next;
}
}
while(cur1 != cur2){
cur1 = cur1 -> next;
cur2 = cur2 -> next;
}
return cur1;
}
十四、判断两个链表是否相交,若相交则求相交点(两个链表带环)
两个链表带环,此时就要分好几种情况来说:
- 交点没在环上,这种情况和链表不带环相同
- 交点在环上,并且在环的入口点上。此时只要先求出两个链表的环的入口点,只要两个入口点相同,他们就相交。
- 交点在环上,但没在入口点。此时,我们可以创建一个指针指向一个链表的环的入口点,然后再让遍历,找到另外一个链表的环的入口点。
- 如果是一个链表带环,一个链表不带环,则这两个链表一定不想交。
具体代码实现如下:
int HasCrossWithCycle(LinkNode* head1,LinkNode* head2){
if(head1 == NULL || head2 == NULL){
return 0;//非法输入
}
LinkNode* entry1 = GetCycleEntry(head1);
LinkNode* entry2 = GetCycleEntry(head2);
if(entry1 == NULL && entry2 == NULL){
//两个链表不带环
return HasCross(head1,head2) != NULL ? 1 : 0;
}else if(entry1 != NULL && entry2 != NULL){
//两个链表都带环
//1.相交,交点在环上
if(entry1 == entry2){
return 1;
}
LinkNode* cur = entry1 -> next;
while(cur != entry1){
//2.相交,交点在环上
if(cur == entry2){
return 1;
}
cur = cur ->next;
}
//3.不想交
return 0;
}else{
//一个带环一个不带环,一定不想交
return 0;
}
return 0;
}
十五、求两个已排序单链表中相同的数据
因为两个单链表已经排过序了,所以我们要求他们相同的数据,即可以创建两个指针,分别指向两个单链表的头,然后然他们比较,相同就将其节点拷贝到一个已经创建好了的新链表里面。
具体代码实现如下:
LinkNode* UnionSet(LinkNode* head1, LinkNode* head2){
if(head1 == NULL || head2 == NULL){
return NULL;
}
LinkNode* cur1 = head1;
LinkNode* cur2 = head2;
LinkNode* new_head = NULL;
LinkNode* new_tail = NULL;
while(cur1 != NULL && cur2 != NULL){
if(cur1 -> data < cur2 -> data){
cur1 = cur1 -> next;
}else if(cur1 -> data > cur2 -> data){
cur2 = cur2 -> next;
}else{
LinkNode* new_node = LinkNodeCreate(cur1 -> data);
if(new_head == NULL){
new_head = new_tail = new_node;
}else{
new_tail -> next = new_node;
new_tail = new_tail -> next;
}
cur1 = cur1 -> next;
cur2 = cur2 -> next;
}
}
return new_head;
}
十六、复杂链表的复制。
首先我们需要了解下什么是复杂链表,就是链表不仅仅只有一个指向next的指针,还有一个指向随机节点的random指针。
这里关于复杂链表的复制提供两个方法
方法1、
- 先把链表进行简单的复制,即就是先不管random指针指向,只考虑next指针指向
- 然后计算每个节点random指针的偏移量
- 根据偏移量,再修改每个节点的random指针
方法2、
在链表的每个节点后插入一个新节点,然后使该新节点的值和random指向相同,然后再将该新节点移除,放在一个新链表里,这样也就实现了复杂链表的复制
代码实现如下:
ComplexNode* CreateComplexNode(LinkNodeType value){
ComplexNode* ptr = (ComplexNode*)malloc(sizeof(ComplexNode*));
ptr -> data = value;
ptr -> next = NULL;
ptr -> random = NULL;
return ptr;
}
size_t Diff(ComplexNode* src, ComplexNode* dest){
if(src == NULL || dest == NULL){
return (size_t)-1;
}
size_t count = 0;
while(src != NULL){
if(src == dest){
return count;
}
++count;
src = src -> next;
}
return (size_t)-1;
}
ComplexNode* Step(ComplexNode* pos, size_t offset){
size_t i = 0;
for(; i < offset; ++i){
if(pos == NULL){
return NULL;
}
pos = pos -> next;
}
return pos;
}
//方法1、
ComplexNode* CopyComplex1(ComplexNode* head){
if(head == NULL){
return NULL;//空链表
}
//1.先把链表进行简单复制
ComplexNode* new_head = NULL;
ComplexNode* new_tail = NULL;
ComplexNode* cur = head;
for(; cur != NULL; cur = cur -> next){
ComplexNode* new_node = CreateComplexNode(cur -> data);
if(new_head == NULL){
new_head = new_tail = new_node;
}else{
new_tail -> next = new_node;
new_tail = new_tail -> next;
}
}
//2.再去依次求每个random指针相对于头结点的偏移量
ComplexNode* new_cur = new_head;
for(cur = head; cur != NULL && new_cur != NULL; cur = cur -> next, new_cur = new_cur -> next){
if(cur -> random == NULL){
new_cur -> random = NULL;
continue;
}
size_t offset = Diff(head,cur -> random);
//3.根据偏移量,修改每个新链表节点的random指针
new_cur -> random = Step(new_head,offset);
}
return new_head;
}
//方法2、
ComplexNode* CopyComplex2(ComplexNode* head){
if(head == NULL){
return NULL;//空链表
}
ComplexNode* cur = head;
for(; cur != NULL; cur = cur -> next){
ComplexNode* new_node = CreateComplexNode(cur -> data);
new_node -> next = cur -> next;
cur -> next = new_node;
}
for(; cur != NULL; cur = cur -> next -> next){
cur -> next -> random = cur -> random;
}
ComplexNode* new_head = NULL;
ComplexNode* new_tail = NULL;
for(; cur != NULL; cur = cur -> next -> next){
ComplexNode* to_delete = cur -> next;
if(new_head == NULL){
new_head = new_tail = to_delete;
}
else{
to_delete -> next = new_tail -> next;
new_tail -> next = to_delete;
new_tail = to_delete;
}
}
return new_head;
}