数据结构c语言篇 《二》带头双向循环链表实现以及链表相关面试题(下)
Posted 程序猿是小贺
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构c语言篇 《二》带头双向循环链表实现以及链表相关面试题(下)相关的知识,希望对你有一定的参考价值。
循环链表实现及相关面试题(下)
链表
借图镇楼
双向循环链表的实现
SHList.h
#pragma once
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x);
//初始化链表
void DListInit(ListNode** pHead);
// 双向链表销毁
void ListDestory(ListNode** pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
SHList.c
#define _CRT_SECURE_NO_WARNINGS
#include"DList.h"
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
#include<stdlib.h>
// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (NULL == newNode)
{
assert(0);
return NULL;
}
newNode->data = x;
newNode->prev = NULL;
newNode->next= NULL;
return newNode;
}
//初始化链表
void DListInit(ListNode** pHead)
{
assert(pHead);
*pHead = ListCreate(0);
(*pHead)->next = *pHead;
(*pHead)->prev = *pHead;
}
// 双向链表销毁
void ListDestory(ListNode** pHead)
{
assert(pHead);
ListNode *cur=(*pHead)->next;
while (cur != (*pHead))
{
(*pHead)->next = cur->next;
free(cur);
cur = (*pHead)->next;
}
free(*pHead);
*pHead = NULL;
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode *cur = pHead->next;
while (cur != pHead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
ListInsert(pHead, x);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
ListErase(pHead->prev);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
ListInsert(pHead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead) ;
ListErase(pHead->next);
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
ListNode* newNode = NULL;
if (pos == NULL)
return;
newNode = ListCreate(x);
newNode->prev = pos->prev;
newNode->next = pos;
pos->prev->next = newNode;
pos->prev= newNode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
if (pos == NULL)
return;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
void TestDList()
{
ListNode* head = NULL;
DListInit(&head);
ListPushBack(head, 1);
ListPushBack(head, 2);
ListPushBack(head, 3);
ListPushBack(head, 4);
ListPushBack(head, 5);
ListPushBack(head, 6);
ListPrint(head);
ListPushFront(head, 0);
ListPrint(head);
ListPopFront(head);
ListPrint(head);
ListPopBack(head);
ListPopBack(head);
ListPopBack(head);
ListPrint(head);
ListDestory(&head);
}
test.c
偷懒ing~~~
链表面试题(下)
最近实在是太忙了,很多事情挤在一起,所以我下面题目就不用图解 来展示给大家了,必要的注释在代码里面我都有写到,有问题的朋友可以自己画图理一下思路或者直接私我,有时间我一定会回复的,感谢~
1. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
核心代码段
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
/*
合并两个有序链表会有如下三种情况
1.两个链表都为空,返回NULL
2.有一个链表为空,返回另一条链表
第一和第二种可视为一种情况,即一条为空返回另一条链表
*/
if(l1==NULL)
return l2;
if(l2==NULL)
return l1;
struct ListNode newHead;
struct ListNode* cur1=l1;
struct ListNode* cur2=l2;
struct ListNode* cur=&newHead;
/*
3.都不为空
遍历两个链表,比较两个节点值大小
给定一个带头的链表,较小者尾插进新链表,
直到循环结束
*/
while(cur1&&cur2)
{
if(cur1->val>cur2->val)
{
//将cur的next域指向cur2即为将cur2尾插进新链表节点的next
cur->next=cur2;
cur2=cur2->next;
}
else
{
cur->next=cur1;
cur1=cur1->next;
}
cur=cur->next;
}
//循环结束后,必有一条链表为空,即有一条链表尾插完毕
if(cur1==NULL)
{
cur->next=cur2;
}
else
{
cur->next=cur1;
}
return newHead.next;
}
2.链表分割
题目描述:
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
核心代码段
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
if(NULL==pHead)
return NULL;
//这里应用带头结点的链表的方式会简单一些
//则此时创建两个带头结点的空链表
ListNode smallHead(0);
ListNode bigHead(0);
ListNode* cur=pHead;
ListNode* small=&smallHead;
ListNode* big=&bigHead;
while(cur)
{
pHead=cur->next;//此时相当于把第一个结点拆下来
if(cur->val<x)
{
small->next=cur;
small=small->next;
}
else{
big->next=cur;
big=big->next;
}
cur=cur->next;
}
//此时可能尾还指向的是原链表的next域,不一定为空
//最后记得需将big的next域置空,
big->next=NULL;
//将第一条链表的尾指向第二条链表的首元结点
small->next=bigHead.next;
return smallHead.next;
}
};
3.环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
核心代码段
//快慢指针,相遇的话即带环
/*
快的走两步,慢的走一步,每走一次距离减一,不会出现套圈情况
若是快的走三步,四步则有可能会出现套圈情况
*/
bool hasCycle(struct ListNode *head) {
if(NULL==head)
return NULL;
struct ListNode *fast=head;
struct ListNode *slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
return true;
}
}
return false;
}
4.环形链表2
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
解题思路:
1.首先先判断链表里面是否有环
我将判环情况封装进一个函数里
判断程序是从NULL跳出还是从break跳出,则只需要判断循环条件是否为真,若是从break跳出,则此时跳出的结点为相遇点
2.在此简单解释一下: 设从头结点到入口点的长度为L,从入口点到两指针的相遇点长度为X,环的长度为R,fast指针走过的长度为L+X+nR(n=1,2,3…)slow指针走过的长度为L+X;而fast指针走过的路程为slow指针的两倍,则化简一下会得到L=nR-X;
所以会得到以下结论:给定一个指针从链表的开始位置往后走,每次走一步,给定另一个指针从相遇点位置往后走,每次走一步,这两个指针相遇的位置即为入口点(开始入环的第一个结点)
核心代码段
//判断是否有环
struct ListNode *hasCycle(struct ListNode *head) {
if(NULL==head)
return NULL;
struct ListNode *fast=head;
struct ListNode *slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
return fast;
}
}
return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
//给一个指针接收上面两个指针的相遇位置
struct ListNode *pxy=hasCycle(head);
//给一个指针从头开始走
struct ListNode *pstart=head;
if(NULL==hasCycle(head))
{
return NULL;
}
while(pxy!=pstart)
{
pxy=pxy->next;
pstart=pstart->next;
}
return pxy;
}
5.复制带随机指针的链表
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
核心代码段
struct Node* copyRandomList(struct Node* head)
{
if(head==NULL)
return NULL;
struct Node*cur=head;
struct Node* newNode=NULL;
//1.在原链表的每个结点后面都插入一个值相等的结点
while(cur)
{
//申请一个新节点,这个结点必须是malloc出来的
newNode=(struct Node*)malloc(sizeof(struct Node));
//对这个结点进行判空
if(NULL==newNode)
return NULL;
newNode->next=newNode->random=NULL;
newNode->val=cur->val;
//将新结点插入链表
newNode->next=cur->next;
cur->next=newNode;
cur=newNode->next;
}
//2.给新插入的结点的随机指针域赋值
//注意:新节点的随机指针域指向刚好是前一个结点随机指针域的下一个
cur=head;//继续从头开始遍历
while (cur)
{
newNode=cur->next;
if(cur->random)
{
newNode->random=cur->random->next;
}
cur=newNode->next;
}
//3.将新插入的节点从原链表中拆下来
cur=head;
struct Node* newHead=cur->next;
newNode=newHead;//把newNode指针放在cur的next位置
while(newNode)
{
cur->next=newNode->next;
cur=newNode;
newNode=cur->next;
}
//cur->next=NULL;
return newHead;
}
6.对链表进行插入排序
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
核心代码段
struct ListNode* insertionSortList(struct ListNode* head){
if(head==NULL)
return head;
struct ListNode*cur1=head;//给一个指针遍历此链表
struct ListNode*newHead=NULL;
struct ListNode*cur2=NULL;
struct ListNode*prev=NULL;
while(cur1)
{
head=cur1->next;//依次拆节点
cur2=newHead;//让cur2指向新链表的头结点
prev=NULL;//每次遍历都要将prev置空
while(cur2)//遍历新链表
{
if(cur1->val<=cur2->val)
break;
prev=cur2;//保留cur2此时的结点
cur2=cur2->next;//让cur2走到下一个结点
}
//此时要插入的val值小于新链表第一个结点值或者新链表没有有效节点
//则需要将val头插进新链表
if(prev==NULL)
{
cur1->next=newHead;
newHead=cur1;
}
//此时表示val值应插在cur2之前且在prev之后;并且此时prev不为空
else
{
cur1->next=cur2;
prev->next=cur1;
}
cur1=head;
}
return newHead;
}
7.删除链表中重复的结点
题目描述:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
解题思路:
由于题目要删除节点,则我们需要至少两个指针
第一个指针cur指向该节点,来进行遍历
第二个指针prev指向Null,用来保存每次遍历之前的该节点
题目未说明重复的结点到底有多少,可能为1,2,3,4…
则我们还需要一个指针end来指向该节点的下一节点
让end指针一直往后走,直到end->val不等于cur->val
要是重复的结点不止两个,我们则需要逐一进行删除
所以还需要一个指针start来保存cur,然后让cur指向它的下一节点
循环删除start,直到end->val等于cur->val
然后让end指针继续往后走,此时会出现两种情况
1.所有结点都已被删除,此时prev==NULL,则需要让pHead=cur
2.后面还有结点,则让prev->next=cur;
核心代码段
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead) {
if(pHead==NULL||pHead->next==NULL)
return pHead;
ListNode *cur=pHead;
ListNode *end=pHead->next;
ListNode *prev=NULL;
while(end)//循环初始条件,要end不为空
{
//进入循环,先判断cur的val值与end的val值是否相等
if(cur->val==end->val)
{
while(end&&cur->val==end->val)
{
//当cur的val值与end的val值相等时,end指向end的nxrt
end=end->next;
}
while(end!=cur)
{
//当cur与end不相等时,此时我们找到了val值相等的重复区间
//然后对其进行删除
ListNode *start=cur;
cur=cur->next;
free(start以上是关于数据结构c语言篇 《二》带头双向循环链表实现以及链表相关面试题(下)的主要内容,如果未能解决你的问题,请参考以下文章