数据结构之链表基本操作总结
2017年05月11日 18:22:11 Lily_whl 阅读数:19151
https://blog.csdn.net/Lily_whl/article/details/71662133
数组与链表的区别
数组(顺序存储)和链表(链式存储)是两种不同的数据存储方式。
数组是一组具有相同类型和名称的变量的集合,这些变量称为数组的元素,每个数组元素都有一个编号,这个编号称为数组的下标,可以通过下标来区别并访问这些元素,数组元素的个数称为数组的长度。
链表的特性是在中间任意位置添加、删除元素都非常快,不需要移动其他元素。对于单链表,链表中每一个元素都要保存一个指向下一个元素的指针;对于双链表,每个元素既要保存一个指向下一个元素的指针,还要保存一个指向上一个元素的指针;循环链表(单向、双项)则在最后一个元素中保存一个指向第一个元素的指针。
区别:
1、逻辑结构:数组必须事先定义固定的长度,不能适应数据动态地增减的情况,即在数组使用前,就必须对数组的大小进行固定;当数据增加时,可能超过原先定义的元素个数;当数据减少时,造成内存浪费。数组中插入、删除数据项时,需要移动其他数据项。而链表采用动态内存形式实现,可以适应数据动态地增减情况,需要时可以用new/malloc分配内存空间,不需要时用delete/free将已分配的空间释放,不会造成内存空间浪费,且可以方便的插入、删除数据项。
2、内存结构:(静态)数组从栈中分配内存空间,方便快捷,但是自由度小;链表从堆中分配内存,自由度大,但是申请管理比较麻烦;
3、数组中数据在物理内存中是顺序存储的,而链表是随机存储的。数组的随机访问效率很高,可以直接定位,但插入、删除操作的效率比较低。链表在插入、删除操作上相对数组有很高的效率,而如果要访问链表中的某个元素,那就得从表头逐个遍历,直到找到所需要的元素为止,所以链表的随机访问效率比数组低。
4、链表不存在越界问题(存储溢出),数组有越界问题。数组便于查询,链表便于插入、删除,数组节省空间但长度固定,链表虽然变长但占据更多存储空间。
数组存储效率高、存储速度快,如果需要频繁访问数据,很少插入删除操作,则用数组;反之,如果频繁插入删除,则应使用链表。
单链表
//单链表节点定义
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
/*
1.结点
链表中用来存储一个数据的存储单元。
一个链表至少需要由两部分组成,就是数据域和指针域
2.单向链表的特点:
(1)有且只有一个结点无前驱,即头结点。头结点通过head指针指向。
(2)有且只有一个结点无后继,即尾结点。尾结点的next域值为NULL。
(3)除了头结点尾结点之外剩下的所有结点有且只有一个前驱,有且只有一个后继。
3.使用指针变量p表示结点的成员
*p.data //错误,正确为(*p).data
p->data //正确
p->next //正确
*/
解决链表问题时,一定要画图!!!!只有画好图了,指针之间的相互关系才能够比较清晰地呈现出来,这样写的时候正确率就高多了。
链表之如何使用链表头
节点:数据域和指针域的和;数据域存储数据元素的信息,指针域指示直接后继存储位置。
头结点:单链表的开始节点之附设一个类型相同的节点,称之为头结点;
头结点的数据域可以不存储任何信息,也可以存放如线性表的长度等附加信息;
头节点的指针域存储指向开始结点的指针,即第一个元素结点的存储位置;
头结点的作用:
1、对带头结点的链表,在表的任何结点之前插入结点或删除表中任何结点,所要做的都是修改前一结点的指针域,因为任何元素结点都有前驱结点。若链表无头结点,则首元素结点无前驱结点,在其前插入结点或删除该结点时操作会复杂些;
2、对带头结点的链表,表头指针是指向首节点的非空指针,因此空表和非空表的处理是一样的;
开始结点:链表中第一个结点,无直接前驱借结点;
链表头指针;指向链表开始结点的指针(无头结点时),单链表由头指针唯一确定,所以单链表可以用头指 针的名字来命名;头指针的设置是的对链表第一个位置上的操作与在表其他位置上的操作一 致
头结点;人为的在链表的开始结点之前附加的一个结点。有了头结点,头指针指向头结点,无论链表是否为 空,头指针总是非空。
通常要对链表的开始节点进行操作时,最好添加个头结点。
单链表之建立、测长、打印、删除、插入
note:这里head为单链表的头节点,而非第一个节点
#include<iostream>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
/***************************************************************************************************/
//单链表的建立
ListNode *creat()
{
ListNode *head=new ListNode(0);
ListNode *p=head;
ListNode *node=nullptr;
int x=0;
int cycle=1;
while(cycle)
{
cout<<"please input the data:"<<endl;
cin>>x;
if(x!=0) //当数据为0时,表示插入结束
{
node=new ListNode(x);
p->next=node;
p=node;
}
else
cycle=0;
}
p->next=nullptr; //尾节点指针置为空
//head=head->next;
return head;
}
/***************************************************************************************************/
//单链表的打印
void printList(ListNode *head) //note:这里head为单链表的头节点,而非第一个节点
{
if(head->next==nullptr)
{
cout<<"list is empty!"<<endl;
return ;
}
ListNode *p=head->next;
int index=0;
while(p!=nullptr)
{
cout<<"第"<<++index<<"个元素为:"<<p->val<<endl;
p=p->next;
}
}
/****************************************************************************************************/
//单链表的测长
int getListLength(ListNode *head) //note:这里head为单链表的头节点,而非第一个节点
{
int len=0;
ListNode *p=head->next;
while(p!=nullptr)
{
++len;
p=p->next;
}
return len;
}
/****************************************************************************************************/
//单链表的插入
//将值为data的新节点插入到链表的第i(i=pos)个节点上
//单链表插入算法的时间耗费主要在查找第i-1个节点上;故单链表插入操作的复杂度为O(n);
//注;在链表中第i(i从0开始取值)个位置插入,分三种情况:插入到链表首部、中间、尾部
ListNode *insertList(ListNode *head,int pos,int data)//note:这里head为单链表的头节点,而非第一个节点
{
ListNode *newNode=new ListNode(data);
ListNode *p=head;
int index=1;
while(p!=nullptr&&index<pos)
{
p=p->next;
++index;
}
newNode->next=p->next;
p->next=newNode;
return head;
}
/**************************************************************************************************/
//单链表的删除:将单链表中第i个节点删除
//删除单链表的头元素、中间元素、尾元素
//注:单链表的长度为n,则单链表删除第i个节点时,必须保证1=<i<=n,否则不合法。而当i=n+1
//时。虽然被删节点不存在,但其前驱节点存在,它是终端节点。因此被删节点的直接前驱存在并不
//意味着被删节点就一定存在,仅当p存在,且p不是终端节点同时满足index<i时,才能确定被删节点存在;
//算法的时间复杂度是O(n);
ListNode *deleteNode(ListNode *head,int pos)//pos从1开始,1表示删除链表的头元素
{
ListNode *p=head;
if(p->next==nullptr) //链表为空时
{
cout<<"链表为空"<<endl;
return nullptr;
}
ListNode *node=nullptr;
int index=1;
while(p!=nullptr&&index<pos)
{
p=p->next;
++index;
}
if(p!=nullptr&&p->next!=nullptr)
{
node=p->next;
p->next=node->next;
delete node;
}
return head;
}
/***************************************************************************************************/
int main()
{
ListNode *head=new ListNode(0);
head=creat();
cout<<"输入的链表为:"<<endl;
printList(head);
int len=getListLength(head);
cout<<"单链表的长度为:"<<len<<endl;
int pos=1;
int data=50;
insertList(head,pos,data);
cout<<"插入后的链表为:"<<endl;
printList(head);
int i=5;
deleteNode(head, i);
cout<<"删除后的链表为:"<<endl;
printList(head);
system("pause");
return 0;
}
单链表之删除单链表的头元素
要删除头元素,首先需要通过头结点定位头元素,并将头结点指向头元素的下一个元素,然后释放头元素的空间;
ListNode * deleteHead(ListNode *head)//这里head为链表的头元素,而非头结点
{
ListNode *beginNode=new ListNode(0);
beginNode->next=head;
ListNode *node=head;
beginNode->next=head->next;
delete node;
ListNode *result=beginNode->next;
delete beginNode;
return result; //返回删除后链表的头元素
}
void deleteHead(ListNode *head)// 这里head为链表的头结点
{
ListNode *node=head->next;
head->next=node->next;
delete node;
}
单链表之查找第i节点、中间节点、倒数第k个节点
查找链表中第i个节点
ListNode *searchNode(ListNode *head,int pos)//这里head为链表的头节点,而非首元素
{
if(head->next==nullptr||pos<0)
return nullptr;
if(pos==0)
return head;
ListNode *p=head->next;
while(--pos)
{
p=p->next;
if(p==nullptr)
{
cout<<"incorrect postion to search node!"<<endl;
break;
}
}
return p;
}
/**************************************************************************************************/
查找单链表的中间节点
//方法一:先求链表的长度len,然后遍历len/2的距离即可查找到单链表的中间节点,需要遍历链表两次
//方法二:采用双指针,从链表头开始遍历,一个指针slow一次走一步,一个指针fast一次走两步,当fast
// 到达链表尾部时,slow恰好到达链表中间(fast到达链表尾部,当链表长度为奇数时,此时slow
// 指向的即为中间节点;当链表长度为偶数时,此时slow及slow的下一个元素均为中间节点)
// 只需遍历链表一次
//方法二:
ListNode *searchMidNode(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *slow=head->next;
ListNode *fast=head->next;
while(fast!=nullptr&&fast->next!=nullptr&&fast->next->next!=nullptr)//若len为偶数,得到的slow和slow下一个均为mid
//while(fast!=nullptr&&fast->next!=nullptr)若len为偶数,得到的slow和slow上一个均为mid
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
查找单链表中倒数第k个元素
//方法一:需遍历两次链表;先求链表的长度len,然后遍历len-k的距离即可查找到单链表的倒数第k个节点
//方法二:遍历一次链表,时间复杂度为O(n);设置两个指针p1、p2,让p2先走k-1步,然后p1、p2再同时走,
// 当p2走到链表尾时,p1所指位置就是所要找的节点
/***************************************************************************************************/
//方法二
ListNode *searchReverseKthNode(ListNode *head,int k)
{
if(head->next==nullptr||k==0)
return head;
ListNode *p1=head->next;
ListNode *p2=head->next;
while(p2!=nullptr&&--k)
{
p2=p2->next;
}
while(p2->next!=nullptr)
{
p1=p1->next;
p2=p2->next;
}
return p1;
}
单链表之实现单链表反转、从尾到头打印单链表(同剑指offerT5)
实现单链表反转:
注意:当单链表有环时,无法反转链表,因为如果单链表有环,则存在两个节点指向同一个
节点。如果反转就变成一个节点指向两个节点了,这对于单链表是不可能的;
/***************************************************************************************************/
//方法一:
ListNode *reverse(ListNode *head)
{
if(head->next==nullptr&&head->next->next==nullptr)
return head;
ListNode *tail=head->next;
ListNode *cur=tail->next;
while(cur!=nullptr)
{
//删除当前节点
tail->next=cur->next;
//头插法,插入节点
cur->next=head->next;
head->next=cur;
//更新当前节点
cur=tail->next;
}
return head;
}
//方法二:遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当节点的指针
// 反转后,利用已存储的指针继续向后遍历;
ListNode *reverse2(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *pre=head->next;
ListNode *cur=pre->next;
pre->next=nullptr; //原第一个节点为末节点;
ListNode *tmp=nullptr;
while(cur!=nullptr)
{
tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
head->next=pre;// 新的第一个节点为原末节点
return head;
}
逆序输出单链表元素:(同剑指offerT5)
//方法一:从头到尾遍历节点,每经过一个节点的时候,把该节点放到一个栈中,当遍历完整个链表后,再从栈顶
// 输出节点的值;只需遍历一遍链表,但需维护一个额外的栈空间;
//方法二:在递归函数之后输出的当前元素,这样能确保输出第n个节点的元素语句永远在第n+1个递归函数之后执行,也就是说第n个元素永远在第n+1个元素之后输出,最终先输出最后一个元素;
注意:递归本质上就是一个栈结构; 基于递归的代码看起来很简单,单链表非常长时,就会导致函数调用的层级很深 ,从而可能导致函数调用栈溢出。显式用栈基于循环的代码鲁棒性要好一些。
# include<iostream>
#include<stack>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
//单链表的建立
ListNode *creat()
{
ListNode *head=new ListNode(-1);
ListNode *p=head;
ListNode *node=nullptr;
int x=0;
int cycle=1;
while(cycle)
{
cout<<"please input the data:"<<endl;
cin>>x;
if(x!=0) //当数据为0时,表示插入结束
{
node=new ListNode(x);
p->next=node;
p=node;
}
else
cycle=0;
}
p->next=nullptr; //尾节点指针置为空
//head=head->next;
return head;
}
//单链表的打印
void printList(ListNode *head) //note:这里head为单链表的头节点,而非第一个节点
{
if(head->next==nullptr)
{
cout<<"list is empty!"<<endl;
return ;
}
ListNode *p=head->next;
int index=0;
while(p!=nullptr)
{
cout<<"第"<<++index<<"个元素为:"<<p->val<<endl;
p=p->next;
}
}
// 方法一:不会改变原链表的结构
void printReversely1(ListNode *head)
{
stack<ListNode *>stackNode;
ListNode *p=head->next;
while(p!=nullptr)
{
stackNode.push(p);
p=p->next;
}
int index=1;
while(!stackNode.empty())
{
p=stackNode.top();
cout<<"逆序输出第"<<index<<"个元素为"<<p->val<<endl;
stackNode.pop();
}
}
//方法二:不会改变原链表的结构
void printListReversely2(ListNode *head)
{
if(head->next!=nullptr)
{
printListReversely2(head->next);
int index=1;
cout<<"逆序输出第"<<index++<<"个元素为";
cout<<head->next->val<<endl;
}
}
int main()
{
ListNode *head=new ListNode(0);
head=creat();
cout<<"输入的链表为:"<<endl;
printList(head);
cout<<endl;
cout<<"逆序输出的链表1为:"<<endl;
printReversely1(head);
cout<<"原链表为:"<<endl;
printList(head);
cout<<endl;
cout<<"逆序输出的链表2为:"<<endl;
printListReversely2(head);
cout<<"原链表为:"<<endl;
printList(head);
cout<<endl;
system("pause");
return 0;
}
//方法二:
void printListReversely(ListNode *head)
{
if(head->next!=nullptr)
{
printListReversely(head->next);
cout<<head->next->val<<endl;
}
}
题目变形:
1、从尾到头输出一个字符串;
2、定义一个函数求字符串长度,要求该函数体中不能声明任何一个变量; 要求不使用额外变量,实现strlen函数:
//第二题:
//代码一:
#include <cstdlib>
#include <iostream>
using namespace std;
int getLen(char *str)
{
if(*str== '/0')
return 0;
return getLen(str + 1) + 1;
}
int main(int argc, char *argv[])
{
char str[] = {"abcdefghigkl"};
cout << getLen(str) << endl;
system("PAUSE");
return EXIT_SUCCESS;
}
//代码二:
int strlen(const char *str)
{
return *str?(strlen(str+1)+1):0;
}
//第二题:
//代码一:
#include <cstdlib>
#include <iostream>
using namespace std;
int getLen(char *str)
{
if(*str== '/0')
return 0;
return getLen(str + 1) + 1;
}
int main(int argc, char *argv[])
{
char str[] = {"abcdefghigkl"};
cout << getLen(str) << endl;
system("PAUSE");
return EXIT_SUCCESS;
}
//代码二:
int strlen(const char *str)
{
return *str?(strlen(str+1)+1):0;
}
strlen的源码实现:
size_t strlen(const char *str)
//strlen不做内存非法判断,如果是NULL,会core。
{
const char *eos=str;
while(*(eos++));
return (eos-str-1);
}
单链表之判断单链表是否存在环、求环的长度、求环的首节点(同leetcode(141、142))
单链表有环是指单链表中某个节点的next指针域指向的是链表中在它之前的某个节点,这样在链表的尾部形成一个环形结构。
给定一个链表,判断它是否包含一个环。
【注意】
1. 空链表不成环
2. 一个节点自环
3. 一条链表完整成环
【方法一】 空间复杂度 O(n),时间复杂度 O(N )
用一个哈希表 unordered_map<ListNode *, bool> visited,记录每个元素是否被访问过,一旦出现某个元素被重复访问,说明存在环。
。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head)
{
if(head==nullptr)
return false;
unordered_map<ListNode*,bool> visited(false);
bool isCycle=false;
while(head!=nullptr)
{
if(visited[head]==true)
{
isCycle=true;
break;
}
else
visited[head]=true;
head=head->next;
}
return isCycle;
}
};
【方法二】时间复杂度 O(n),空间复杂度 O(1) 的。
使用两个指针slow,fast。两个指针都从表头开始走,slow每次走一步,fast每次走两步,如果fast遇到null,则说明没有环,返回false;如果slow==fast,说明有环,并且此时fast超了slow一圈,返回true。
为什么有环的情况下二者一定会相遇呢?因为fast先进入环,在slow进入之后,如果把slow看作在前面,fast在后面每次循环都向slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况。
这里需要注意的一点是算法中循环的条件,这是一个很容易被忽略的细节。
1)因为fast指针比slow指针走得快,所以只要判断fast指针是否为空就好。由于fast指针一次走两步,fast.next可能已经为空(当fast为尾结点时),fast.next.next将会导致NullPointerException异常,所以在while循环中我们要判断fast.next是否为空;
2)考虑一个特殊情况,当输入的链表为空时,算法应该返回false,空链表肯定是不含有环的。如果没有fast != null,也会导致fast.next抛出NullPointerException异常。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head)
{
if(head==nullptr)
return false;
ListNode *slow=head;
ListNode *fast=head;
while(fast!=nullptr&&fast->next!=nullptr)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
return true;
}
return false;
}
};
题意: 如何找到环的第一个节点? 使用两个指针slow,fast。两个指针都从表头开始走,slow每次走一步,fast每次走两步,如果fast遇到null,则说明没有环,返回false;如果slow==fast,说明有环,并且此时fast超了slow一圈,返回true。 为什么有环的情况下二者一定会相遇呢?因为fast先进入环,在slow进入之后,如果把slow看作在前面,fast在后面每次循环都向slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况。 第一次相遇时slow走过的距离:a+b,fast走过的距离:a+b+c+b。 因为fast的速度是slow的两倍,所以fast走的距离是slow的两倍,有 2(a+b) = a+b+c+b,可以得到a=c(这个结论很重要!)。 发现L=b+c=a+b,也就是说,从一开始到二者第一次相遇,循环的次数就等于环的长度。 已经得到了结论a=c,那么让两个指针分别从X和Z开始走,每次走一步,那么正好会在Y相遇!也就是环的第一个节点。
struct ListNode { int val; ListNode *next; ListNode(int x):val(x),next(nullptr){} }; class Solution { public: ListNode *detectCycle(ListNode *head) { if(head==nullptr) return nullptr; ListNode *slow=head; ListNode *fast=head; ListNode *slow2=head; while(fast!=nullptr&&fast->next!=nullptr) { slow=slow->next; fast=fast->next->next; if(fast==slow) { while(slow!=slow2) { slow=slow->next; slow2=slow2->next; } return slow2; } } return nullptr; } };
第一次相遇后,让fast停着不走了,slow继续走,记录到下次相遇时循环了几次。 方法二: 第一次相遇时slow走过的距离:a+b,fast走过的距离:a+b+c+b。 因为fast的速度是slow的两倍,所以fast走的距离是slow的两倍,有 2(a+b) = a+b+c+b,可以得到a=c(这个结论很重要!)。 我们发现L=b+c=a+b,也就是说,从一开始到二者第一次相遇,循环的次数就等于环的长度。 2. 如何找到环中第一个节点(即Linked List Cycle II)?
单链表之合并两个有序单链表 题目:已知两个有序单链表head1、head2(假设链表元素为升序排列),把他们合并成一个链表,合并后依然有序;使用后递归和非递归方式实现;
//方法一:递归; // 则直接返回head1;如果head1链表的第一个数据小于head2链表的第一个数据,则把head1链表的 // 元素存储到新合并的链表中,递归遍历去掉第一个元素的head1链表和整个head2链表。如果head1 // 链表的第一个元素>=head2链表的第一个元素,则把head2链表的第一个元素存储到新合并的链表 // 中,递归遍历整个head1链表和去除第一个元素后的head2链表。直到两个链表的节点都被加入到 // 新合并的链表中。 // 递归终止条件:若head1为空,返回head2指针(head);若head2为空,返回head1指针(head); // 递归方法所使用的栈空间与链表的长度成正比。//方法一:递归 ListNode *mergeRecursive(ListNode *head1,ListNode *head2)//head1和head2均为链表的首节点,而非头结点 {
if(head1==nullptr) return head2; if(head2==nullptr) return head1; ListNode *newHead=nullptr; if(head1->val<head2->val) {
newHead=head1; newHead->next=mergeRecursive(head1->next,head2); } else { newHead=head2; newHead->next=mergeRecursive(head1,head2->next); } return newHead; }
方法二:非递归; 则将head1指向的节点归并入合并后的链表中;否则将head2指向的节点归并入合并后的链表中, 如果有一个链表遍历结束,则把未结束的链表连接到合并后的链表尾部;
//方法二:非递归 ListNode *mergeList(ListNode *head1,ListNode *head2)//head1和head2均为链表的首节点,而非头结点 { ListNode *newHead=new ListNode(0); ListNode *node=newHead; while(head1!=nullptr&&head2!=nullptr) { if(head1->val<head2->val) { node->next=head1; node=head1; head1=head1->next; } else { node->next=head2; node=head2; head2=head2->next; } } if(head1!=nullptr) { node->next=head1; } if(head2!=nullptr) { node->next=head2; } return newHead; //返回头结点 }
|
单链表之删除链表的重复元素
题目:一个无序链表 如list={1,8,10,2,3,4,4,2,5,6,3,12,4,7},要求去掉重复项,并保留原顺序,去重后list={1,8,10,2,3,4,5,6,12,7}。
方法一:使用hash表,时间复杂度O(n),空间复杂度O(n)
1、建立一个hash表,key为链表中已遍历的节点内容,开始时为空;
2、从头开始遍历链表中的节点
1)如果节点内容已经在hash中了,则删除此节点,继续向后遍历;
2)如果节点内容不在hash中,则保留此节点,将节点内容添加到hash中,继续遍历
//方法一:
ListNode * deleteDuplicateFromUnsortedList(ListNode *head)
{
if(head->next==nullptr&&head->next->next==nullptr)
return head;
unordered_map<int,bool> visited(false);
ListNode *pre=head;
ListNode *cur=pre->next;
ListNode *tmp=nullptr;
while(cur!=nullptr)
{
if(visited[cur->val])
{
tmp=cur; //tmp存储要删除的节点
pre->next=cur->next; //删除节点
cur=cur->next;
delete tmp; //释放删除节点的内存
}
else
{
visited[cur->val]=true;
pre=pre->next;
cur=cur->next;
}
}
return head;
}
方法二:不使用额外空间,直接在原链表上进行操作,时间复杂度O(n^2)。
// 1、建立指针cur用于遍历链表;
// 2、建立指针p,p遍历cur之后的节点,并与cur的内容进行比较;
// 3、建立指针tmp,tmp保存要删除的节点,把需要删除的节点前后相连,删除重复节点
//非递归实现
ListNode * deleteDuplicateFromUnsortedList2(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *cur=head->next;
ListNode *p=nullptr;
ListNode *tmp=nullptr;
while(cur!=nullptr)
{
p=cur;
while(p->next!=nullptr)
{
if(p->next->val==cur->val)
{
tmp=p->next;
p->next=p->next->next;
delete tmp;
}
else
{
p=p->next;
}
}
cur=cur->next;
}
return head;
}
//递归实现
ListNode * deleteDuplicateFromUnsortedList3(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *tmp=head; //递归过程中head不断变化,但初始时tmp都指向新的head;
head->next=deleteDuplicateFromUnsortedList3(head->next);//递归到head->next指向尾节点;此时head指向链表倒数第二个
//节点
ListNode *cur=head->next;//cur指向head的下一个节点;此时tmp=head;
while(cur!=nullptr)
{
if(cur->val==head->val)//单次递归中,head是不变的,每次都把head的内容与head之后所有节点的内容进项比较,
//若相同,则删除此节点
{
tmp->next=cur->next;
delete cur;
cur=tmp->next;
}
else
{
cur=cur->next;
tmp=tmp->next;//temp初始时是指向新的head的,之后作为临时变量,随着cur一起后移,始终作为cur的前驱节点,
//是为了当tmp节点的数据和head数据一样时,在删除cur节点时,用temp->next来保存cur的后继节点。
}
}
return head;
}
单链表之判断两个单链表有无公共节点(即是否交叉)
注意:单链表相交是指两个链表存在完全重合的部分(不是交叉到一个点)。
附:求两个链表的第一个公共节点
注意:输入的两个链表有三种情况:
1、一个链表有环,另一个链表无环,则两个链表不可能相交;
2、两个链表均有环;
3、两个链表均无环;
【分析】
//注意:在写测试用例时,需创建三个链表,其中一个做为公共部分,其他两个的尾部均指向他;注意
// 公共节点不仅数据域相同,指针域也相同!
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
class Solution
{
private:
//判断是否有环
bool hasCycle(ListNode *head) //head为链表的第一个节点,不是头节点
{
if(head==nullptr)
return false;
ListNode *slow=head;
ListNode *fast=head;
while(fast!=nullptr&&fast->next!=nullptr)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
return true;
}
return false;
}
private:
//寻找环的第一个节点(即环的入口)
ListNode *detectCycle(ListNode *head)//head为链表的第一个节点,不是头节点
{
if(head==nullptr)
return nullptr;
ListNode *slow=head;
ListNode *fast=head;
ListNode *slow2=head;
while(fast!=nullptr&&fast->next!=nullptr)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
while(slow!=slow2)
{
slow=slow->next;
slow2=slow2->next;
}
return slow2;
}
}
return nullptr;
}
private:
//求单链表的长度
int getListLength(ListNode *head) //note:这里head为单链表的头节点,而非第一个节点
{
int len=0;
ListNode *p=head->next;
while(p!=nullptr)
{
++len;
p=p->next;
}
return len;
}
public:
//当两个单链表均无环时,计算第一个公共节点
//方法一:暴力搜索
ListNode * findFristCommonNode1(ListNode *head1,ListNode *head2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
ListNode *p1=head1->next;
ListNode *p2=head2->next;
while(p1!=nullptr)
{
while(p2!=nullptr)
{
if(p1==p2)
{
cout<<"存在公共节点!"<<endl;
return p1;
}
else
p2=p2->next;
}
p1=p1->next;
}
cout<<"无公共节点!"<<endl;
return nullptr;
}
//方法二:
ListNode * findFristCommonNode2(ListNode *head1,ListNode *head2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
int len1=getListLength(head1);
int len2=getListLength(head2);
int k=len1-len2;
ListNode *plong=nullptr;
ListNode *pshort=nullptr;
if(k>0)
{
plong=head1->next;
pshort=head2->next;
while(k--)
{
plong=plong->next;
}
}
else
{
plong=head2->next;
pshort=head1->next;
k=(-k);
while(k--)
{
plong=plong->next;
}
}
while(plong!=nullptr&&pshort!=nullptr&&plong!=pshort)
{
plong=plong->next;
pshort=pshort->next;
}
if(plong==pshort)
{
cout<<"存在公共节点!"<<endl;
return plong;
}
cout<<"无公共节点!"<<endl;
return nullptr;
}
//方法三:
ListNode * findFristCommonNode3(ListNode *head1,ListNode *head2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
ListNode *p=head1->next;
ListNode *newHead=p;
while(p->next!=nullptr)
{
p=p->next;
}
p->next=head2->next;
if(hasCycle(newHead))
{
cout<<"存在公共节点" <<endl;
return detectCycle(newHead);
}
cout<<"无公共节点!"<<endl;
return nullptr;
}
//方法四:
ListNode * findFristCommonNode4(ListNode *head1,ListNode *head2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
stack<ListNode *>stack1;
stack<ListNode *>stack2;
ListNode *p1=head1->next;
ListNode *p2=head2->next;
ListNode *commonNode=nullptr;
while(p1!=nullptr)
{
stack1.push(p1);
p1=p1->next;
}
while(p2!=nullptr)
{
stack2.push(p2);
p2=p2->next;
}
while(!stack1.empty()&&!stack2.empty())
{
ListNode *node1=stack1.top();
ListNode *node2=stack2.top();
if(node1==node2)
{
commonNode=node1;
stack1.pop();
stack2.pop();
}
else
{
break;
}
}
return commonNode;
}
public:
//判断链表是否相交
bool isHasCommonNode(ListNode *head1,ListNode *head2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return false;
}
bool cycle1=hasCycle(head1->next);
bool cycle2=hasCycle(head2->next);
if((cycle1&&!cycle2)||(!cycle1&&cycle2))
{
cout<<"两个链表一个有环,一个无环,故无公共节点!"<<endl;
return false;
}
if(cycle1&&cycle2)
{
ListNode *R1=detectCycle(head1->next);
R1->next=nullptr;
ListNode *R2=detectCycle(head2->next);
R2->next=nullptr;
ListNode *commonNode1=findFristCommonNode2(head1,head2);
if(commonNode1!=nullptr)
{
cout<<"两个均有环单链表存在公共节点!" <<endl;
cout<<"第一个公共节点内容为:"<<commonNode1->val<<endl;
return true;
}
else
{
cout<<"两个均有环单链表无公共节点!" <<endl;
return false;
}
}
if(!cycle1&&!cycle2)
{
ListNode *commonNode2=findFristCommonNode2(head1,head2);
if(commonNode2!=nullptr)
{
cout<<"两个均无环单链表存在公共节点!" <<endl;
cout<<"第一个公共节点内容为:"<<commonNode2->val<<endl;
return true;
}
else
{
cout<<"两个均有环单链表无公共节点!" <<endl;
return false;
}
}
return false;
}
};
单链表之交换任意两个节点
对于单链表:
假设交换A、B两个节点,那么需要交换A与B的next指针以及A、B直接前驱的next指针。
需要考虑的情况:
1、当A与B相邻时,需做特殊处理;
2、当A与B元素相同时,没必要交换;
3、当A、B其中有一个头结点时,没必要交换;
4、一般情况:只需要找到节点的前驱,做相应的指针调整;
#include<iostream>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
/***************************************************************************************************/
//单链表的建立
ListNode *creat()
{
ListNode *head=new ListNode(-1);
ListNode *p=head;
ListNode *node=nullptr;
int x=0;
int cycle=1;
while(cycle)
{
cout<<"please input the data:"<<endl;
cin>>x;
if(x!=0) //当数据为0时,表示插入结束
{
node=new ListNode(x);
p->next=node;
p=node;
}
else
cycle=0;
}
p->next=nullptr; //尾节点指针置为空
//head=head->next;
return head;
}
/***************************************************************************************************/
//单链表的打印
void printList(ListNode *head) //note:这里head为单链表的头节点,而非第一个节点
{
if(head->next==nullptr)
{
cout<<"list is empty!"<<endl;
return ;
}
ListNode *p=head->next;
int index=0;
while(p!=nullptr)
{
cout<<"第"<<++index<<"个元素为:"<<p->val<<endl;
p=p->next;
}
}
/***************************************************************************************************/
//查找链表中第i个节点
ListNode *searchNode(ListNode *head,int pos)//这里head为链表的头节点,而非首元素
{
if(head->next==nullptr||pos<0)
return nullptr;
if(pos==0)
return head;
ListNode *p=head->next;
while(--pos)
{
p=p->next;
if(p==nullptr)
{
cout<<"incorrect postion to search node!"<<endl;
break;
}
}
return p;
}
/***************************************************************************************************/
//返回一个节点的前驱节点
ListNode *findPre(ListNode *head,ListNode *node)
{
ListNode *p=head;
while(p->next!=node)
{
p=p->next;
}
return p;
}
/***************************************************************************************************/
//交换单链表的任意两个节点
//需要考虑的情况:
//1、当A与B相邻时,需做特殊处理;
//2、当A与B元素相同时,没必要交换;
//3、当A、B其中有一个头结点时,没必要交换;
//4、一般情况:只需要找到节点的前驱,做相应的指针调整;
ListNode *swap(ListNode *head,ListNode *node1,ListNode *node2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head->next==nullptr||node1==nullptr||node2==nullptr)
{
cout<<"invalid parameter:nullptr"<<endl;
return head;
}
//有一个链表为空,则不交换
if(node1==head||node2==head)
return head;
//当两个节点元素相同时,则不交换
if(node1->val==node2->val)
return head;
//当两个节点相邻时
ListNode *preNode1=findPre(head,node1);
ListNode *postNode1=node1->next;
ListNode *preNode2=findPre(head,node2);
ListNode *postNode2=node2->next;
if(postNode1==node2)
{
node1->next=postNode2;
preNode1->next=node2;
node2->next=node1;
}
else if(postNode2==node1)
{
node2->next=postNode1;
preNode2->next=node1;
node1->next=node2;
}
//一般情况,直接交换
else if(node1!=node2)
{
preNode1->next=node2;
node2->next=postNode1;
preNode2->next=node1;
node1->next=postNode2;
}
return head;
}
/**************************************************************************************************/
int main()
{
ListNode *head=new ListNode(0);
head=creat();
cout<<"输入的链表为:"<<endl;
printList(head);
int pos1=0;
ListNode *node1=searchNode(head,pos1);
cout<<"查找的第"<<pos1<<"个节点元素为:"<<node1->val<<endl;
int pos2=6;
ListNode *node2=searchNode(head,pos2);
cout<<"查找的第"<<pos2<<"个节点元素为:"<<node2->val<<endl;
ListNode *newHead=swap(head,node1,node2);
cout<<"交换节点后的链表为:"<<endl;
printList(newHead);
system("pause");
return 0;
}
单链表之基本操作总结
#include<iostream>
#include<unordered_map>
#include<stack>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
/**************/
//单链表的建立
ListNode *creat()
{
ListNode *head=new ListNode(-1);
ListNode *p=head;
ListNode *node=nullptr;
int x=0;
int cycle=1;
while(cycle)
{
cout<<"please input the data:"<<endl;
cin>>x;
if(x!=0) //当数据为0时,表示插入结束
{
node=new ListNode(x);
p->next=node;
p=node;
}
else
cycle=0;
}
p->next=nullptr; //尾节点指针置为空
//head=head->next;
return head;
}
/*****************/
//单链表的打印
void printList(ListNode *head) //note:这里head为单链表的头节点,而非第一个节点
{
if(head->next==nullptr)
{
cout<<"list is empty!"<<endl;
return ;
}
ListNode *p=head->next;
int index=0;
while(p!=nullptr)
{
cout<<"第"<<++index<<"个元素为:"<<p->val<<endl;
p=p->next;
}
}
/*********************/
//单链表的测长
int getListLength(ListNode *head) //note:这里head为单链表的头节点,而非第一个节点
{
int len=0;
ListNode *p=head->next;
while(p!=nullptr)
{
++len;
p=p->next;
}
return len;
}
/*************/
//单链表的插入
//将值为data的新节点插入到链表的第i(i=pos)个节点上
//单链表插入算法的时间耗费主要在查找第i-1个节点上;故单链表插入操作的复杂度为O(n);
//注;在链表中第i(i从0开始取值)个位置插入,分三种情况:插入到链表首部、中间、尾部
ListNode *insertList(ListNode *head,int pos,int data)//note:这里head为单链表的头节点,而非第一个节点
{
ListNode *newNode=new ListNode(data);
ListNode *p=head;
int index=1;
while(p!=nullptr&&index<pos)
{
p=p->next;
++index;
}
newNode->next=p->next;
p->next=newNode;
return head;
}
/*******************/
//单链表的删除:将单链表中第i个节点删除
//删除单链表的头元素、中间元素、尾元素
//注:单链表的长度为n,则单链表删除第i个节点时,必须保证1=<i<=n,否则不合法。而当i=n+1
//时。虽然被删节点不存在,但其前驱节点存在,它是终端节点。因此被删节点的直接前驱存在并不
//意味着被删节点就一定存在,仅当p存在,且p不是终端节点同时满足index<i时,才能确定被删节点存在;
//算法的时间复杂度是O(n);
ListNode *deleteNode(ListNode *head,int pos)//pos从1开始,1表示删除链表的头元素
{
ListNode *p=head;
if(p->next==nullptr) //链表为空时
{
cout<<"链表为空"<<endl;
return nullptr;
}
ListNode *node=nullptr;
int index=1;
while(p!=nullptr&&index<pos)
{
p=p->next;
++index;
}
if(p!=nullptr&&p->next!=nullptr)
{
node=p->next;
p->next=node->next;
delete node;
}
return head;
}
/*********************/
//查找链表中第i个节点
ListNode *searchNode(ListNode *head,int pos)//这里head为链表的头节点,而非首元素
{
if(head->next==nullptr||pos<0)
return nullptr;
if(pos==0)
return head;
ListNode *p=head->next;
while(--pos)
{
p=p->next;
if(p==nullptr)
{
cout<<"incorrect postion to search node!"<<endl;
break;
}
}
return p;
}
/**********************/
//查找单链表的中间节点
//方法一:先求链表的长度len,然后遍历len/2的距离即可查找到单链表的中间节点,需要遍历链表两次
//方法二:采用双指针,从链表头开始遍历,一个指针slow一次走一步,一个指针fast一次走两步,当fast
// 到达链表尾部时,slow恰好到达链表中间(fast到达链表尾部,当链表长度为奇数时,此时slow
// 指向的即为中间节点;当链表长度为偶数时,此时slow及slow的下一个元素均为中间节点)
ListNode *searchMidNode(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *slow=head->next;
ListNode *fast=head->next;
while(fast!=nullptr&&fast->next!=nullptr&&fast->next->next!=nullptr)//若len为偶数,得到的slow和slow下一个均为mid
//while(fast!=nullptr&&fast->next!=nullptr)若len为偶数,得到的slow和slow上一个均为mid
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
/*************************/
// 查找单链表中倒数第k个元素
//方法一:需遍历两次链表;先求链表的长度len,然后遍历len-k的距离即可查找到单链表的倒数第k个节点
//方法二:遍历一次链表,时间复杂度为O(n);设置两个指针p1、p2,让p2先走k-1步,然后p1、p2再同时走,
// 当p2走到链表尾时,p1所指位置就是所要找的节点
ListNode *searchReverseKthNode(ListNode *head,int k)
{
if(head->next==nullptr||k==0)
return head;
ListNode *p1=head->next;
ListNode *p2=head->next;
while(p2!=nullptr&&--k)
{
p2=p2->next;
}
while(p2->next!=nullptr)
{
p1=p1->next;
p2=p2->next;
}
return p1;
}
/********************/
//实现单链表反转;注意:当单链表有环时,无法反转链表,因为如果单链表有环,则存在两个节点指向同一个
// 节点。如果反转就变成一个节点指向两个节点了,这对于单链表是不可能的;
//方法一:
ListNode *reverse(ListNode *head)
{
if(head->next==nullptr&&head->next->next==nullptr)
return head;
ListNode *tail=head->next;
ListNode *cur=tail->next;
while(cur!=nullptr)
{
//删除当前节点
tail->next=cur->next;
//头插法,插入节点
cur->next=head->next;
head->next=cur;
//更新当前节点
cur=tail->next;
}
return head;
}
//方法二:遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当节点的指针
// 反转后,利用已存储的指针继续向后遍历;
ListNode *reverse2(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *pre=head->next;
ListNode *cur=pre->next;
pre->next=nullptr; //原第一个节点为末节点;
ListNode *tmp=nullptr;
while(cur!=nullptr)
{
tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
head->next=pre;// 新的第一个节点为原末节点
return head;
}
/****************/
//逆序输出单链表元素:
//方法一:从头到尾遍历节点,每经过一个节点的时候,把该节点放到一个栈中,当遍历完整个链表后,再从栈顶
// 输出节点的值;只需遍历一遍链表,但需维护一个额外的栈空间;
//方法二:在递归函数之后输出的当前元素,这样能确保输出第n个节点的元素语句永远在第n+1个递归函数之后执行,
// 也就是说第n个元素永远在第n+1个元素之后输出,最终先输出最后一个元素;
void printListReversely(ListNode *head)
{
if(head->next!=nullptr)
{
printListReversely(head->next);
cout<<head->next->val<<endl;
}
}
/********************/
//将两个有序链表合并成一个有序链表
//方法一:递归;设两个链表的头结点分别为head1和head2,如果head1为空,则直接返回head2,;如果head2为空
// 则直接返回head1;如果head1链表的第一个数据小于head2链表的第一个数据,则把head1链表的
// 元素存储到新合并的链表中,递归遍历去掉第一个元素的head1链表和整个head2链表。如果head1
// 链表的第一个元素>=head2链表的第一个元素,则把head2链表的第一个元素存储到新合并的链表
// 中,递归遍历整个head1链表和去除第一个元素后的head2链表。直到两个链表的节点都被加入到
// 新合并的链表中。
// 递归终止条件:若head1为空,返回head2指针(head);若head2为空,返回head1指针(head);
// 递归方法所使用的栈空间与链表的长度成正比。
ListNode *mergeRecursive(ListNode *head1,ListNode *head2)//head1和head2均为链表的首节点,而非头结点
{
if(head1==nullptr)
return head2;
if(head2==nullptr)
return head1;
ListNode *newHead=nullptr;
if(head1->val<head2->val)
{
newHead=head1;
newHead->next=mergeRecursive(head1->next,head2);
}
else
{
newHead=head2;
newHead->next=mergeRecursive(head1,head2->next);
}
return newHead; //返回首节点,而非头结点
}
// 方法二:非递归;分别用两个指针head1和head2遍历两个链表,如果当前head1指向的数据小于head2指向的数据
// 则将head1指向的节点归并入合并后的链表中;否则将head2指向的节点归并入合并后的链表中,
// 如果有一个链表遍历结束,则把未结束的链表连接到合并后的链表尾部;
ListNode *mergeList(ListNode *head1,ListNode *head2)//head1和head2均为链表的首节点,而非头结点
{
ListNode *newHead=new ListNode(0);
ListNode *node=newHead;
while(head1!=nullptr&&head2!=nullptr)
{
if(head1->val<head2->val)
{
node->next=head1;
node=head1;
head1=head1->next;
}
else
{
node->next=head2;
node=head2;
head2=head2->next;
}
}
if(head1!=nullptr)
{
node->next=head1;
}
if(head2!=nullptr)
{
node->next=head2;
}
return newHead; //返回头结点
}
/********************/
//删除无序单链表的重复元素
//方法一:使用hash表,时间复杂度O(n),空间复杂度O(n)
// 1、建立一个hash表,key为链表中已遍历的节点内容,开始时为空;
// 2、从头开始遍历链表中的节点
// 1)如果节点内容已经在hash中了,则删除此节点,继续向后遍历;
// 2)如果节点内容不在hash中,则保留此节点,将节点内容添加到hash中,继续遍历
ListNode * deleteDuplicateFromUnsortedList(ListNode *head)
{
if(head->next==nullptr&&head->next->next==nullptr)
return head;
unordered_map<int,bool> visited(false);
ListNode *pre=head;
ListNode *cur=pre->next;
ListNode *tmp=nullptr;
while(cur!=nullptr)
{
if(visited[cur->val])
{
tmp=cur; //tmp存储要删除的节点
pre->next=cur->next; //删除节点
cur=cur->next;
delete tmp; //释放删除节点的内存
}
else
{
visited[cur->val]=true;
pre=pre->next;
cur=cur->next;
}
}
return head;
}
//方法二:不使用额外空间,直接在原链表上进行操作,时间复杂度O(n^2)。
// 1、建立指针cur用于遍历链表;
// 2、建立指针p,p遍历cur之后的节点,并与cur的内容进行比较;
// 3、建立指针tmp,tmp保存要删除的节点,把需要删除的节点前后相连,删除重复节点
//非递归实现
ListNode * deleteDuplicateFromUnsortedList2(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *cur=head->next;
ListNode *p=nullptr;
ListNode *tmp=nullptr;
while(cur!=nullptr)
{
p=cur;
while(p->next!=nullptr)
{
if(p->next->val==cur->val)
{
tmp=p->next;
p->next=p->next->next;
delete tmp;
}
else
{
p=p->next;
}
}
cur=cur->next;
}
return head;
}
//递归实现
ListNode * deleteDuplicateFromUnsortedList3(ListNode *head)
{
if(head->next==nullptr)
return head;
ListNode *tmp=head; //递归过程中head不断变化,但初始时tmp都指向新的head;
head->next=deleteDuplicateFromUnsortedList3(head->next);//递归到head->next指向尾节点;此时head指向链表倒数第二个节点
ListNode *cur=head->next;//cur指向head的下一个节点;此时tmp=head;
while(cur!=nullptr)
{
if(cur->val==head->val)//单次递归中,head是不变的,每次都把head的内容与head之后所有节点的内容进项比较,若相同,则删除此节点
{
tmp->next=cur->next;
delete cur;
cur=tmp->next;
}
else
{
cur=cur->next;
tmp=tmp->next;//temp初始时是指向新的head的,之后作为临时变量,随着cur一起后移,始终作为cur的前驱节点,是为了当tmp节点的数据和head数据一样时,在删除cur节点时,用temp->next来保存cur的后继节点。
}
}
return head;
}
/*************************/
//判断是否有环
bool hasCycle(ListNode *head) //head为链表的第一个节点,不是头节点
{
if(head==nullptr)
return false;
ListNode *slow=head;
ListNode *fast=head;
while(fast!=nullptr&&fast->next!=nullptr)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
return true;
}
return false;
}
/**********************/
//寻找环的第一个节点(即环的入口)
ListNode *detectCycle(ListNode *head)//head为链表的第一个节点,不是头节点
{
if(head==nullptr)
return nullptr;
ListNode *slow=head;
ListNode *fast=head;
ListNode *slow2=head;
while(fast!=nullptr&&fast->next!=nullptr)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
while(slow!=slow2)
{
slow=slow->next;
slow2=slow2->next;
}
return slow2;
}
}
return nullptr;
}
/*********************/
//当两个链表均无环时,求他们的第一个公共节点
//方法一:暴力搜索
ListNode * findFristCommonNode1(ListNode *head1,ListNode *head2)
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
ListNode *p1=head1->next;
ListNode *p2=head2->next;
while(p1!=nullptr)
{
while(p2!=nullptr)
{
if(p1==p2)
{
cout<<"存在公共节点!"<<endl;
return p1;
}
else
p2=p2->next;
}
p1=p1->next;
}
return nullptr;
}
//方法二:利用单链表的性质+两个指针
ListNode * findFristCommonNode2(ListNode *head1,ListNode *head2)
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
int len1=getListLength(head1);
int len2=getListLength(head2);
int k=len1-len2;
ListNode *plong=nullptr;
ListNode *pshort=nullptr;
if(k>0)
{
plong=head1->next;
pshort=head2->next;
while(k--)
{
plong=plong->next;
}
}
else
{
plong=head2->next;
pshort=head1->next;
k=(-k);
while(k--)
{
plong=plong->next;
}
}
while(plong!=nullptr&&pshort!=nullptr&&plong!=pshort)
{
plong=plong->next;
pshort=pshort->next;
}
if(plong==pshort)
return plong;
return nullptr;
}
//方法三:两个单链表首尾相连得到新链表,判断新链表是否存在环,若有环,则存在公共节点;否则,无公共节点;
ListNode * findFristCommonNode3(ListNode *head1,ListNode *head2)
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
ListNode *p=head1->next;
ListNode *newHead=p;
while(p->next!=nullptr)
{
p=p->next;
}
p->next=head2->next;
if(hasCycle(newHead))
{
return detectCycle(newHead);
}
return nullptr;
}
//方法四:利用单链表的性质+两个栈辅助空间
ListNode * findFristCommonNode4(ListNode *head1,ListNode *head2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return nullptr;
}
stack<ListNode *>stack1;
stack<ListNode *>stack2;
ListNode *p1=head1->next;
ListNode *p2=head2->next;
ListNode *commonNode=nullptr;
while(p1!=nullptr)
{
stack1.push(p1);
p1=p1->next;
}
while(p2!=nullptr)
{
stack2.push(p2);
p2=p2->next;
}
while(!stack1.empty()&&!stack2.empty())
{
ListNode *node1=stack1.top();
ListNode *node2=stack2.top();
if(node1==node2)
{
commonNode=node1;
stack1.pop();
stack2.pop();
}
else
{
break;
}
}
return commonNode;
}
/****************************/
//判断链表是否相交
bool isHasCommonNode(ListNode *head1,ListNode *head2)//note:这里head为单链表的头节点,而非第一个节点
{
if(head1->next==nullptr||head2->next==nullptr)
{
cout<<"存在空链表,无公共节点!"<<endl;
return false;
}
bool cycle1=hasCycle(head1->next);
bool cycle2=hasCycle(head2->next);
if((cycle1&&!cycle2)||(!cycle1&&cycle2))
{
cout<<"两个链表一个有环,一个无环,故无公共节点!"<<endl;
return false;
}
if(cycle1&&cycle2)
{
ListNode *R1=detectCycle(head1->next);
R1->next=nullptr;
ListNode *R2=detectCycle(head2->next);
R2->next=nullptr;
ListNode *commonNode1=findFristCommonNode2(head1,head2);
if(commonNode1!=nullptr)
{
cout<<"两个均有环单链表存在公共节点!" <<endl;
cout<<"第一个公共节点内容为:"<<commonNode1->val<<endl;
return true;
}
else
{
cout<<"两个均有环单链表无公共节点!" <<endl;
return false;
}
}
if(!cycle1&&!cycle2)
{
ListNode *commonNode2=findFristCommonNode2(head1,head2);
if(commonNode2!=nullptr)
{
cout<<"两个均无环单链表存在公共节点!" <<endl;
cout<<"第一个公共节点内容为:"<<commonNode2->val<<endl;
return true;
}
else
{
cout<<"两个均无环单链表无公共节点!" <<endl;
return false;
}
}
return false;
}
/*********************/
int main()
{
/*
ListNode *head=new ListNode(0);
head=creat();
cout<<"输入的链表为:"<<endl;
printList(head);
int len=getListLength(head);
cout<<"单链表的长度为:"<<len<<endl;
int pos=1;
int data=50;
insertList(head,pos,data);
cout<<"插入后的链表为:"<<endl;
printList(head);
int i=5;
deleteNode(head, i);
cout<<"删除后的链表为:"<<endl;
printList(head);
int pos2=0;
ListNode *snode=searchNode(head,pos2);
cout<<"查找节点元素为;"<<snode->val<<endl;
ListNode *mid=searchMidNode(head);
cout<<"中间节点的元素为:"<<mid->val<<endl;
int k=1;
ListNode *kNode=searchReverseKthNode(head,k);
cout<<"倒数第k个元素为:"<<kNode->val<<endl;
ListNode *rhead=reverse(head);
cout<<"反转后的链表为:"<<endl;
printList(head);
ListNode *rhead=reverse2(head);
cout<<"反转后的链表为:"<<endl;
printList(head);
cout<<"反序打印列表:"<<endl;
printListReversely(head);
*/
/*
ListNode *head1=new ListNode(0);
head1=creat();
cout<<"输入的链表为:"<<endl;
printList(head1);
ListNode *head2=new ListNode(0);
head2=creat();
cout<<"输入的链表为:"<<endl;
printList(head2);
ListNode *newHead=new ListNode(0);
newHead=mergeRecursive(head1->next,head2->next);
cout<<"合并后的链表为:"<<endl;
ListNode *newHead1=new ListNode(0);
newHead1->next=newHead;
printList(newHead1);
*/
/*
ListNode *head3=new ListNode(0);
head3=creat();
cout<<"输入的链表为:"<<endl;
printList(head3);
ListNode *head4=new ListNode(0);
head4=creat();
cout<<"输入的链表为:"<<endl;
printList(head4);
ListNode *newHead2=new ListNode(0);
newHead2=mergeList(head3->next,head4->next);
cout<<"合并后的链表2为:"<<endl;
printList(newHead2);
*/
/*
ListNode *head5=new ListNode(0);
head5=creat();
cout<<"输入的链表为:"<<endl;
printList(head5);
ListNode *newHead5=new ListNode(0);
newHead5=deleteDuplicateFromUnsortedList(head5);
cout<<"去重后的链表为1:"<<endl;
printList(newHead5);
*/
/*
ListNode *head6=new ListNode(0);
head6=creat();
cout<<"输入的链表为:"<<endl;
printList(head6);
ListNode *newHead6=new ListNode(0);
newHead6=deleteDuplicateFromUnsortedList2(head6);
cout<<"去重后的链表为2:"<<endl;
printList(newHead6);
*/
/*
ListNode *head7=new ListNode(0);
head7=creat();
cout<<"输入的链表为:"<<endl;
printList(head7);
ListNode *newHead7=new ListNode(0);
newHead7=deleteDuplicateFromUnsortedList3(head7);
cout<<"去重后的链表为3:"<<endl;
printList(newHead7);
*/
/*
ListNode *head3=new ListNode(0);
head3=creat();
cout<<"输入的链表为:"<<endl;
printList(head3);
ListNode *head1=new ListNode(0);
head1=creat();
ListNode *newHead1=head1;
while(head1->next!=nullptr)
{
head1=head1->next;
}
head1->next=head3->next;
cout<<"输入的链表为:"<<endl;
printList(newHead1);
ListNode *head2=new ListNode(0);
head2=creat();
ListNode *newHead2=head2;
while(head2->next!=nullptr)
{
head2=head2->next;
}
head2->next=head3->next;
cout<<"输入的链表为:"<<endl;
printList(newHead2);
ListNode *newHead7=new ListNode(0);
newHead7=findFristCommonNode1(head1,head2);
if(newHead7!=nullptr)
cout<<"第一个公共节点为:"<<newHead7->val;
else
cout<<"no!"<<endl;
bool hasCom=isHasCommonNode(head1,head2);
if(hasCom)
cout<<"yes"<<endl;
else
cout<<"No!"<<endl;
*/
system("pause");
return 0;
}
单循环链表 循环链表是首尾相接的链表,他与单链表的唯一区别在于对尾节点的处理,在单链表中尾节点的指针域nullptr改为指向头结点就得到了单循环链表。在单循环链表上的操作基本与非循环链表相同,只是将原来判断指针是否为nullptr变为是否是头指针,没有其他较大变化。 单向循环链表与单向链表相比,其尾结点的下一个结点指向首元素,这样就构成了一个环: 对于单链表只能从头节点开始遍历整个链表,而对于单循环链表则可以从表中任意节点开始遍历整个链表。因为有时对链表常做的操作在表尾、表头进行,此时改变一下链表的标识方法,不用头指针而用一个指向尾节点的rear来标识,可以使操作效率得以提高。 1、用尾指rear标识的单循环链表查找开始节点和尾节点很方便,此时查找时间复杂度均为O(1)。 2、对单循环链表H1和H2的连接操作,是将H2的第一个数据节点接到H1的尾节点,若用头指针标识,则需要找到第一个链表的尾节点,其时间复杂度为O(n);而表若用尾指针R1、R2来标识,则时间复杂度为O(1)。
p=R1->next; //保存R1的头结点指针 R1->next=R2->next->next;//头尾相连; delete R2->next;//释放第二个表的头结点 R2->next=p;//组成循环链表
// 无头结点、无头指针的单循环链表中,删除一个节点的前驱节点 void deletePreNodeInSingleLoopList(ListNode *head,ListNode *s) { if(head==nullptr||head->next==nullptr) return; ListNode *p=s; ListNode *q=nullptr; while(p->next->next!=s) { q=p; p=p->next; } q->next=s; delete p; }
|
单循环链表之约瑟夫问题1
// 编号为1,2,...,N的N个人按顺时针方向围坐一圈,每个人持有一个密码(正整数),一开始任选一个正整数作为
//报数上限值M,从第一个人开始按顺时针方向自1开始按顺序报数,报到M时就停止报数。报M的人出列,将他的密码
//作为新的M值,从他在顺时针方向上的下一个人开始重新报数,如此下去,直到所有人全部出列为止。求出出列顺序;
#include <iostream>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
//构建含有n个节点的单循环链表
ListNode * creatSingleLoopList(int n) //无头结点,有尾指针
{
ListNode *rear=nullptr;
if(n!=0)
{
ListNode *beginNode=new ListNode(-1);
rear=beginNode;
int value=1;
ListNode *node=nullptr;
while(value<=n)
{
node=new ListNode(value);
rear->next=node;
node->next=beginNode;
rear=node;
++value;
}
rear->next=beginNode->next;
delete beginNode;
}
return rear;
}
void printSingleLoopList(ListNode *rear) // //有无节点,有尾指针
{
if(rear->next==nullptr)
{
cout<<"list is empty!"<<endl;
}
ListNode *p=rear;
int i=1;
while(p->next!=rear)
{
p=p->next;
cout<<i++<<"个元素是:"<<p->val<<endl;
}
p=p->next; //输出最后一个元素
cout<<i++<<"个元素是:"<<p->val<<endl;
}
int main()
{
/*
int n=15;
ListNode *rear=creatSingleLoopList (n);
printSingleLoopList(rear);\
*/
int n=10;
int m=6;
ListNode *rear=creatSingleLoopList (n);
printSingleLoopList(rear);
ListNode *prear=creatSingleLoopList (n);
ListNode *pcur=prear->next;
m%=n;
while(pcur!=pcur->next)
{
for(int i=1;i<m-1;++i)
{
pcur=pcur->next;
}
cout<<pcur->next->val<<endl;//输出第m个节点的值
//m=pcur->next->val; //m每次都更新,报M的人出列,将他的密码作为新的M值
//删除第m个节点
ListNode *tmp=pcur->next;
pcur->next=pcur->next->next;
pcur=pcur->next;
delete tmp;
}
cout<<pcur->val<<endl; //输出最后一个删除的节点的值
system("pause");
return 0;
}
双向链表之基本操作
双向链表与单向链表相比,各个结点多了一个指向前一个结点的指针。结构如图所示:
#include<iostream>
using namespace std;
struct ListNode
{
int val;
ListNode *prior;
ListNode *next;
ListNode(int x):val(x),prior(nullptr),next(nullptr){}
};
//创建一个带头结点的双链表
ListNode *creatDList()
{
ListNode *beginNode=new ListNode(-1);
beginNode->prior=nullptr;
beginNode->next=nullptr;
ListNode *p=beginNode;
int x=0;
int cycle=1;
while(cycle)
{
cout<<"please input the data:"<<endl;
cin>>x;
if(x!=0) //当数据为0时,表示插入结束
{
ListNode *node=new ListNode(x);
p->next=node;
node->prior=p;
node->next=nullptr;
p=node;
}
else
cycle=0;
}
return beginNode;
}
//打印带头节点的双向链表
void printDList(ListNode *head)
{
if(head->next==nullptr)
{
cout<<"list is empty!"<<endl;
return;
}
ListNode *q=head->next;
int index=1;
while(q!=nullptr)
{
cout<<index++<<"个元素为:"<<q->val<<endl;
q=q->next;
}
}
//求带头节点的双向链表的长度
int getDListLength(ListNode *head)
{
if(head->next==nullptr)
{
cout<<"list is empty!"<<endl;
return 0;
}
int index=0;
ListNode *p=head->next;
while(p!=nullptr)
{
++index;
p=p->next;
}
return index;
}
//判断带头结点双向链表是否为空
bool isEmpty(ListNode *head)
{
if(head->prior==head->next)
return true;
else
return false;
}
//查找带头结点的双向链表中第i个节点
//使用next指针遍历链表
ListNode *searchNode(ListNode *head,int pos)
{
if(head->next==nullptr||pos<0)
return nullptr;
if(pos==0)
return head;
ListNode *p=head->next;
while(--pos)
{
p=p->next;
if(p==nullptr)
{
cout<<"incorrect postion to search node!"<<endl;
break;
}
}
return p;
}
//将值为data的新节点插入到带头结点的双向链表的第i(i=pos)个节点上
//分四种情况,插入到链表头部、链表中间、链表尾部,插入的位置越界
ListNode *insertDList(ListNode *head,int pos,int data)
{
int len=getDListLength(head);
ListNode *newNode=new ListNode(data);
ListNode *p=nullptr;
if(pos==0) //在链表头部插入
{
newNode->next=head->next;
newNode->prior=head;
head->next->prior=newNode;
head->next=newNode;
}
else if(pos>0&&pos<len+1)//在链表中间插入
{
p=searchNode(head,pos-1); //第i-1个位置的节点
newNode->next=p->next;
newNode->prior=p;
p->next->prior=newNode;
p->next=newNode;
}
else if(pos==len+1) //在链表尾部插入
{
p=searchNode(head,pos-1);
newNode->next=nullptr;
newNode->prior=p;
p->next=newNode;
}
else //插入的位置越界
{
cout<<"incorrect postion to insert node!"<<endl;
}
return head;
}
//删除带头结点的双向链表的第i个位置的节点
//分五种情况:头结点、中间节点、尾节点、链表为空、删除元素位置为空;
//注:链表的长度为n,则链表删除第i个节点时,必须保证1=<i<=n,否则不合法。而当i=n+1
//时。
ListNode *deleteNode(ListNode *head,int pos)
{
ListNode *p=searchNode(head,pos);//查找第i个位置的节点
if(p==nullptr) //删除的元素位置越界或者链表为空
{
cout<<"incorrect postion to delete node!"<<endl;
}
else if(p->prior==nullptr)//删除头结点
{
if(p->next!=nullptr)//链表不为空
{
p->next->prior=nullptr;
ListNode *newBeginNode=new ListNode(-2);
newBeginNode->next=p->next;
delete p;
return newBeginNode;
}
}
else if(p->next==nullptr)// 删除尾节点;
{
p->prior->next=nullptr;
delete p;
}
else //删除第一个节点至倒数第二个节点
{
p->prior->next=p->next;
p->next->prior=p->prior;
delete p;
}
return head;
}
//清空带头结点的双向链表
void clearDList(ListNode *head)
{
ListNode *tmp=nullptr;
ListNode *p=head->next;
while(p!=nullptr)
{
tmp=p->next;
delete p;
p=tmp;
}
head->next=head->prior=nullptr;
}
//逆序打印带头双向链表
void printReversely(ListNode *head)
{
if(head->next!=nullptr)
{
int index=1;
printReversely(head->next);
cout<<index++<<"个元素为"<<head->next->val<<endl;
}
else
cout<<"list is empty!"<<endl;
}
int main()
{
ListNode *head=creatDList();
cout<<"创建的带头双向链表为:"<<endl;
printDList(head);
int len=getDListLength(head);
cout<<"双向链表的长度为:"<<len<<endl;
int pos1=7;
ListNode *sNode=searchNode(head,pos1);
if(sNode!=nullptr)
{
cout<<"查找的第"<<pos1<<"个节点元素为:"<<sNode->val<<endl;
}
int pos2=7;
int data=88;
ListNode *head1=insertDList(head,pos2,data);
cout<<"插入新节点后的带头双向链表为:"<<endl;
printDList(head1);
int pos3=0;
ListNode *head2=deleteNode(head,pos3);
cout<<"删除节点后的带头双向链表为:"<<endl;
printDList(head2);
//测试删除头结点
if(pos3==0)
{
cout<<endl;
cout<<head2->val;
}
bool flag=isEmpty(head);
if(flag)
{
cout<<"list is empty!"<<endl;
}
else
{
cout<<"list is not empty!"<<endl;
}
clearDList(head);
cout<<"清空后的带头双向链表为:"<<endl;
printDList(head);
cout<<"逆序打印的带头双向链表为:"<<endl;
printReversely(head);
cout<<"原带头双向链表为:"<<endl;
printDList(head);
system("pause");
return 0;
}