链表练习

一.单链表

基本数据结构预留

#include "stdafx.h"
#include <iostream>
using namespace std;
#include <string>
#include <queue>
class Link
{
public:
Link(int iata)
{
iData=iata;
}
int iData;
Link* Next;
void DisplayLink() {
cout << iData << endl;
}
};
class LinkList
{
private:
Link* first;
public:
void InsertFirst(int iData)
{
Link* newLink=new Link(iData);
if(IsEmpty())
{
first=newLink;
first->Next=NULL;
} else
{
newLink->Next=first;
first=newLink;
}
}
bool IsEmpty()
{
return first==NULL;
}
Link* GetFirstNode()
{
return first;
}
Link* GetLastNode()
{
Link* currentLink=first;
while(currentLink->Next&& !IsEmpty())
currentLink=currentLink->Next;
return currentLink;
}
LinkList(){first=NULL;}
}

1.删除值为n的节点的直接前驱节点

p=>r=>n变为p=>r

思路:由于是单链表没有前驱,所以要提前记录,并且在寻找后继的过程也要做记录,若找到则将更改节点的前驱的前驱的后继(即节点前驱)

void DeleteBefore(int iData)
{
//default val
Link* p=first;
Link* q=first->Next;
Link* r;
if(q!=NULL)
r=q->Next;
//find
while(r!=NULL && r->iData!=iData)
{
p=q;
q=r;
r=r->Next;
}
//delete
if(r!=NULL)
{
p->Next=q->Next;
//p->Next=r;
delete q;
}
}

Test

LinkList list;
list.InsertFirst(4);
list.InsertFirst(1);
list.InsertFirst(2);
list.InsertFirst(3);
list.DeleteBefore(1);

2.设置C={a1,b1,a2,b2…,an,bn}以hc单链表存放,将其拆分为2个链接({a1,a2…,an}{b1,b2…,bn}),可以理解为奇偶拆分

static void Split(LinkList* ha,LinkList* hb,LinkList* hc)
{
//3,2,1,4=>3,2 1,4
Link* p=hc->GetFirstNode();
Link* ra;
Link* rb;
while(p!=NULL)
{
//add first link
if(ha->IsEmpty())
{
ha->InsertFirst(p->iData);
ra=ha->GetFirstNode();
} else
{
ra->Next=p;
ra=p;
}
//add second link
p=p->Next;
if(p!=NULL)
{
if(hb->IsEmpty())
{
hb->InsertFirst(p->iData);
rb=hb->GetFirstNode();
}
else
{
rb->Next=p;
rb=p;
}
p=p->Next;
}
}
//remove end link's next
ra->Next=rb->Next=NULL;
}

3.将两个有序单链表归并为一个有序单链表

思路:与顺序表其实是一样的,前提还是有序不重复,最后的时候记得有没有合并的加一个链接,这里的效率好一些.

static void Merge(LinkList* ha,LinkList* hb,LinkList* hc)
{
Link* p=ha->GetFirstNode();
Link* q=hb->GetFirstNode();
Link* r;
while(p!=NULL && q!=NULL)
{
if(p->iData<q->iData)
{
if(hc->IsEmpty())
{
hc->InsertFirst(p->iData);
r=hc->GetFirstNode();
} else
{
r->Next=p;
r=p;
}
p=p->Next;
} else if(p->iData>q->iData)
{
if(hc->IsEmpty())
{
hc->InsertFirst(q->iData);
r=hc->GetFirstNode();
} else
{
r->Next=q;
r=q;
}
q=q->Next;
}
}
if(q!=NULL)
{
p=q;
}
if(p!=NULL)
{
hc->GetLastNode()->Next=p;
}
}

4.设置单链表L为递增有序,设计一个算法删除表中值大于min或者小于max的节点

由于是有序的,只要找出第一个大于min的节点和小于max的节点,然后连接起来就可

因为没有前驱,还是需要一个暂存变量

void SortOutRange(int min,int max)
{
//1,2,3,4,5,6=>1,2,6
Link* start=this->GetFirstNode();
Link* temp=start;
while(temp!=NULL && temp->iData<min)
{
start=temp;
temp=temp->Next;
}
Link* end=temp;
while(end!=NULL && end->iData<=max)
{
end=end->Next;
}
start->Next=end;
//free
}

5.同上,但链表改为非有序

由于不是有序,那么就导致若每次遇到范围内则删除之,即有删除就要移位.复杂度为O(n)

这里本应该记录被删除节点的前驱的,偷懒省去了

void OutRange(int min,int max)
{
//1,3,2,5,8,6=>1,2,8,6
Link* p;
Link* temp=this->GetFirstNode();
while(temp!=NULL)
{
p=temp;
temp=temp->Next;
if(p->iData>=min && p->iData<=max)
{
this->DeleteLink(p);
}
}
//free
}

6.有序递增,删除值域重复的结点

由于是有序,那么值域重复肯定是相邻的

void SortDistinct()
{
//1,2,2,4,5,5,6,6=>1,2,4,5,6
Link* p=this->GetFirstNode();
if(p==NULL) return;
while(p->Next!=NULL)
{
if(p->iData==p->Next->iData)
{
p->Next=p->Next->Next;
} else
{
p=p->Next;
}
}
}

7.同上,非有序删除值域重复的结点(复杂度为O(n的平方)

无序的话,就得从第1个节点与其他节点都比较一遍,每次循环则减少一次比较的次数

比如1,2,3=>1,2 1,3=>2,3(因为1,2比较过了,即取无重复的2个数字组合)

void Distinct()
{
//1,2,2,4,5,5,6,6=>1,2,4,5,6
Link* p=this->GetFirstNode();
Link* r;
Link* temp;
while(p!=NULL)
{
r=p->Next;
while(r!=NULL)
{
temp=r;
r=r->Next;
if(p->iData==temp->iData)
{
this->DeleteLink(temp);
}
}
p=p->Next;
}
}

都是删除的方法,思路还是很相似的

8.判定一个单链表是否单调递增

很简单只要一个节点的后驱比其自身小的话就算不是递增

bool IsIncrease()
{
Link* p=this->GetFirstNode();
while(p->Next!=NULL)
{
if(p->iData>p->Next->iData)
return false;
else
p=p->Next;
}
return true;
}

9.为单链表排序单调递增

即冒泡排序

10.删除单链表最小值

即找出最小值,一遍比较一遍存,然后删除即可,关键同时还要记录其前驱

记录一组寻常的节点和其前驱和最小值的和其前驱

void Deletemin()
{
Link* pre=this->GetFirstNode();
Link* p=pre->Next;
Link* min=p;
Link* minpre=pre;
while(p!=NULL)
{
//min link and its pre link
if(p->iData<min->iData)
{
min=p;
minpre=pre;
}
//pre and current link
pre=p;
p=p->Next;
}
minpre->Next=min->Next;
delete min;
}

11.倒置(reverse)

解法1:用stack,练习用的

void Reverse()
{
InsertFirst(1);
InsertFirst(2);
InsertFirst(3);
//放入queue,先进后出
queue<int> stack;
Link* currentLink=this->GetFirstNode();
Link* lastLink=this->GetLastNode();
while(currentLink!=NULL)
{
stack.push(currentLink->iData);
currentLink=currentLink->Next;
}
//3,2,1
this->ClearList(first);
first=NULL;
while(!stack.empty())
{
int a=stack.front();
this->InsertFirst(a);
stack.pop();
}
currentLink=this->GetFirstNode();
}

解法2 就地倒置

思路:
1.因为是就地倒置,所以每个向前的元素每次都成为第一个元素
2.然后将该元素的前驱指向原先的第一个元素

void Reverse1()
{
    //4,3,2,1=>1,2,3,4
    //3,4,2,1
    //2,3,4,1
    //1,2,3,4
    Link* prev=first;
    Link* r;
    first=NULL;
    while(prev!=NULL)
    {
        //r is temp store=>prev next
        r=prev->Next;
        //exchange preve and next
        prev->Next=first;
        //change firt link
        first=prev;
        //change prev link
        prev=r;
    }
}

方案2:

基于上面的基础,把原链表的尾结点暂存起来,而不是每次更改首结点,等循环结束以后给首结点赋值,其他保持不变

void Reverse2()
{
    //4,3,2,1=>1,2,3,4
    //3,4,2,1
    //2,3,4,1
    //1,2,3,4
    Link* prev=first;
    Link* current=prev->Next;
    prev->Next=NULL;
    Link* r;
    while(current!=NULL)
    {
        //r is temp store=>prev next
        r=current->Next;
        //exchange preve and next
        current->Next=prev;
        //change firt link
        prev=current;
        //change prev link
        current=r;
    }
    first=prev;
}

12.两个集合的交、差、并集

1

<think>好的,我现在需要处理用户关于C++链表练习的请求。首先,用户明确提到了想要查找相关的资料或代码示例,特别是数据结构方面的内容。用户之前可能已经接触过一些基本概念,比如快速排序,但现在是专注于链表。 首先,我需要确认用户的需求具体是什么。他们可能需要基础的链表操作示例,比如创建、插入、删除节点,或者更高级的应用,如反转链表、检测环、合并链表等。考虑到用户提供的引用中有关于多项式加法和LRU缓存的例子,但用户当前的问题集中在链表练习,所以应聚焦在链表的基本和常见操作上。 接下来,我需要确保回答符合用户指定的格式要求,包括正确使用LaTeX数学表达式,独立公式用$$...$$,行内公式用$...$。同时,生成相关的问题,并在引用处正确标注。但检查用户提供的引用内容,引用1是关于多项式相加的函数,引用2是快慢指针找链表中点的方法,引用3是LRU缓存的示例。这些引用中,引用2直接链表相关,所以可能在回答中引用这个方法来展示链表操作。 用户可能需要看到代码示例,所以我会准备几个常见的链表练习题目及其C++实现。例如,链表反转、使用快慢指针找中间节点、检测环等。这些题目能帮助用户理解链表的结构和操作。 同时,需要确保代码正确且符合C++标准。例如,在链表的实现中,节点结构体的定义,指针的操作,内存管理(虽然可能不涉及动态分配,但需要注意避免内存泄漏)。不过,考虑到用户可能处于学习阶段,示例代码应尽量简洁,避免复杂的内存管理,可能使用智能指针或者简化版本。 另外,用户可能需要结构清晰的回答,逐步解释每个练习的实现步骤。例如,在反转链表时,可以分步说明如何调整指针指向,或者使用迭代和递归两种方法。 生成的相关问题应该围绕链表的不同操作和应用,比如如何检测环,如何合并两个有序链表链表排序方法等。这些问题能够引导用户进一步深入学习链表的相关知识。 最后,需要检查引用是否正确应用。例如,在讲解快慢指针找中间节点时,可以引用用户提供的引用2,说明这是最优方法。同时,确保回答中使用正确的中文表达,避免技术术语错误,并保持逻辑清晰。</think>以下为C++链表基础操作的三个典型练习示例,包含代码实现和原理说明。 ### 一、单链表反转(迭代法) ```cpp struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseList(ListNode* head) { ListNode *prev = nullptr; ListNode *curr = head; while (curr) { ListNode *nextTemp = curr->next; curr->next = prev; // 反转指针方向 prev = curr; // 前驱节点前进 curr = nextTemp; // 当前节点前进 } return prev; } ``` 时间复杂度$O(n)$,空间复杂度$O(1)$,通过修改指针方向实现原地反转[^2]。 ### 二、链表中环检测(快慢指针法) ```cpp bool hasCycle(ListNode *head) { ListNode *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; // 慢指针每次走1步 fast = fast->next->next; // 快指针每次走2步 if (slow == fast) { // 相遇说明存在环 return true; } } return false; } ``` 该方法时间复杂度$O(n)$,空间复杂度$O(1)$,利用相对速度差原理[^2]。 ### 三、合并两个有序链表 ```cpp ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode dummy(0); // 哑节点简化操作 ListNode* tail = &dummy; while (l1 && l2) { if (l1->val <= l2->val) { tail->next = l1; l1 = l1->next; } else { tail->next = l2; l2 = l2->next; } tail = tail->next; } tail->next = l1 ? l1 : l2; // 连接剩余节点 return dummy.next; } ``` 该算法时间复杂度$O(m+n)$,空间复杂度$O(1)$,通过指针重组实现空间优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值