1. 单链表反转
思路1:O(n^2).
“狸猫换太子”,不进行改动链表结构,只首尾交换len/2次。但是在本函数中用到了定位函数,定位函数实际上是遍历了一遍整个链表,所以综合效率很低,达到O(n^2).
//单链表反转(O(n^2))
void reverseList(Node* Head)
{
int count = numOfNodes(Head);
//首尾交换
for(int i=1; i<=count/2; i++){
Node* p1 = locateNodeI(Head, i);
Node* p2 = locateNodeI(Head, count+1-i);
swap(p1->value, p2->value);
}
}
思路2:O(n).
就最一般的情况而言(没有之前写的辅助函数,即条件单纯为只有Head指向一个单链表)。可以实现O(n)效率。
做法是用三个相邻的指针进行遍历,在遍历的途中,更改指针方向。当然要注意链表数目分情况,和拆链的处理。
//单链表反转(O(n))
Node* reverseList2(Node* Head)
{
if(Head==NULL || Head->next==NULL) //空链和单节点
return Head;
Node* p1 = Head;
Node* p2 = Head->next;
Node* p3 = Head->next->next;
if(p3==NULL){ //只有两个节点
p1->next = NULL;
p2->next = p1;
Head = p2;
return Head;
}
else{ //至少三个节点
p1->next = NULL;
while(p3!=NULL){
p2->next = p1;
//三个指针依次向后移一位
p1=p2;
p2=p3;
p3=p3->next;
}
p2->next = p1;
Head = p2;
return Head;
}
}
2. 找单链表倒数第4个元素
思路1:O(2N)
//查找倒数第四个元素,传入ans中 O(2N)
bool findLast4th1(Node* Head, int& ans)
{
//先确定节点个数:
int count = numOfNodes(Head);
//定位count-4
Node* p = locateNodeI(Head, count-3);
if(p!=NULL){
ans = p->value;
return true;
}
else{
return false;
}
}
思路2:O(N)
//查找倒数第四个元素,传入ans中 O(N),只遍历一遍
bool findLast4th2(Node* Head, int& ans)
{
Node* p1 = Head;
Node* p2 = Head;
//p1先走4步。
for(int i=0;i<4;i++){
if(p1!=NULL){
p1=p1->next;
}
else{
return false; //肯定链表长度不够
}
}
//同步移动
while(p1!=NULL){
p1 = p1->next;
p2 = p2->next;
}
ans=p2->value;
return true;
}
思路3:O(N)
//查找倒数第四个元素,传入ans中 O(N)
bool findLast4th3(Node* Head, int& ans)
{
int arr[4];
Node* p=Head;
int i=0;
int count=0;
while(p!=NULL){
arr[i]=p->value;
p = p->next;
i = (i+1)%4; //周期为4,则 i+1 和 i-3 是同一个位置
count++;
}
if(count<4){
return false;
}
else{
ans=arr[i];
return true;
}
}
3. 找单链表的中间元素
思路1:O(2n)
//获取中间元素O(2n)
bool getMiddleOne1(Node* Head, int& ans)
{
int count = numOfNodes(Head);
if(count==0){
return false;
}
else{
Node* p = locateNodeI(Head,(count+1)/2);
ans = p->value;
return true;
}
}
思路2:O(n)
//获取中间元素O(n)
//使用两个指针first和second,first每次走一步,second每次走两步:
bool getMiddleOne2(Node* Head, int& ans)
{
if(Head==NULL)//空链表
return false;
else{
Node* first=Head;
Node* second=Head->next;
while(second!=NULL && second->next!=NULL){
first = first->next;
second = second->next;
second = second->next;
}
ans = first->value;
return true;
}
}
4. 增加删除无头单链表的一个节点
增加无头单链表的一个节点
//增加无头单链表的一个节点,current指针指向该链表某节点(可以为首尾),在其之前增加节点insertNode
void addNoHeadList(Node* Head, Node* Current, Node* insertNode)
{
insertNode->next = Current->next;
Current->next = insertNode;
swap(Current->value, insertNode->value);
}
删除无头单链表的一个节点
//删除无头单链表的非首尾节点:"狸猫换太子";
void deleteNoHeadList(Node* Head, Node* Current)
{
Node* p = Current->next;
//一定是非首尾节点,否则会出错
Current->value = Current->next->value;
Current->next = Current->next->next;
free(p);
}
5. 两个不交叉的有序链表的合并
思路:O(len1+len2)
1)用 Node*& 方式传入两个链表的头指针Head1,Head2。
//用指针引用是因为最后要将Head1和Head2修改为NULL
2)定义一个合并后的链表的头指针和尾指针Head和Tail。
3)不断比较Head1和Head2的首元素,加入到新的合并的链表中。
4)注意:这里的加入并不是先增加申请一个节点分配,然后删除释放原来的节点。而是直接将指针指向。也就是说在合并的过程中只是指针指向改变了,完全没有申请新的内存和释放节点空间。最后如果有一个Head1或Head2的已经空了,则直接将剩余链表连接到Head即可。
//合并两个有序链表
Node* mergeTwoList(Node*& Head1, Node*& Head2)
{
Node* Head = NULL; //合并后的链表
Node* Tail = NULL; //合并后链表的尾指针
//p1,p2遍历两个链表
Node* p1 = Head1;
Node* p2 = Head2;
while(p1 && p2){
if(p1->value <= p2->value){
if(Head==NULL){ //p1所指作合并后的第一个节点
Head=p1;
Tail=Head;
}
else{
Tail->next=p1;
Tail=Tail->next;
}
p1=p1->next;
}
else{
if(Head==NULL){ //p2所指作合并后的第一个节点
Head=p2;
Tail=Head;
}
else{
Tail->next=p2;
Tail=Tail->next;
}
p2=p2->next;
}
}
//p1或p2遍历完链表,连接为遍历完的链表的剩余结点
if(p1){
if(Head!=NULL)
Tail->next=p1;
else
Head=p1;
}
if(p2){
if(Head!=NULL)
Tail->next=p2;
else
Head=p2;
}
Head1=NULL;
Head2=NULL;
return Head;
}
8.判断单链表是否有环(6形状)?如何找到环的“起始”点?如何知道环的长度?
思路:
注意分析题意,题意并非是说单链表完全成O形状的环,而是说单链表成6形状的环。
首先判断是否有环:为此我们建立两个指针,从Head一起向前跑,一个步长为1,一个步长为2,用
while(直到步长2的跑到结尾) { 检查两个指针是否相等,直到找到为止。} 来进行判断。
原因是,在这场跑步中,结束循环有两种可能,其一是原来无环,所以2先跑到结尾,因为2比1快,二者不可能相等。其二是原来是有环的,因为这场赛跑永远没有z终点,但是在环中,由于2比1快,因而必定套圈,也即2追上了1,这在无环中是不可能出现的情况。
而进行计算环的长度,只要找到交汇点,然后在圈中跑一次就可以了。
int getCircleLength(Node* cross)
bool judgeCircleExists(Node* Head)
//单链表成环,计算环的长度(输入的参数为成环的交汇点)
int getCircleLength(Node* cross)
{
int len=1;
Node* p=cross;
while(p->next!=cross){ //不能写作 p->next!=p
len++;
p=p->next;
}
return len;
}
//判断单链表是否有环,并且返回环的长度
bool judgeCircleExists(Node* Head,int &len)
{
if(Head==NULL) //空链
return false;
else if(Head->next==Head) //1个节点且成环
return true;
else if(Head->next==NULL) //1个节点不成环
return false;
//至少两个节点情形
//初始化跑步机
Node* p1=Head; //跑步者1号,跑到第1个节点
Node* p2=Head->next; //跑步者2号,跑到第2个节点
while(p2!=NULL&&p2->next!=NULL){ //利用了&&短路
p1=p1->next;
p2=p2->next->next;
if(p1==p2){
//此时p1(p2)即为交汇点
len=getCircleLength(p1);
return true;
}
}
return false;
}
9.判断两个单链表是否相交
注意这里是判断是否相交。对于判断问题来讲,相对还是比较简单的。注意单链表并非不能有重复元素。
思路1:O(len1*len2)
把第一个链表的指针值逐项存在hashtable中,遍历第2个链表的每一项的指针值,如果能在第一个链表中找到,则必然相交。但是C++的STL模板中的hash不太会用。所以我使用了set集合,不过貌似set集合是使用遍历的方式来查找元素是否在集合中的,所以效率是比较低的,至少在O(len1*len2)级别。
bool judgeIntersectList1(Node* Head1,Node* Head2)
//判断两个单链表是否相交(Y型)
bool judgeIntersectList1(Node* Head1,Node* Head2)
{
set<Node*>s;
Node* p1=Head1;
Node* p2=Head2;
while(p1!=NULL){
s.insert(p1);
p1=p1->next;
}
while(p2!=NULL){
if(s.find(p2)!=s.end()){
s.clear();
return true;
}
p2=p2->next;
}
s.clear();
return false;
}
思路2:O(len1+len2)
把一个链表A接在另一个链表B的末尾,如果有环,则必然相交。如何判断有环呢?从A开始遍历,如果能回到A的表头,则肯定有环。
注意,在返回结果之前,要把刚才连接上的两个链表断开,恢复原状。
bool judgeIntersectList2(Node* Head1,Node* Head2)
//判断两个单链表是否相交(Y型)
bool judgeIntersectList2(Node* Head1,Node* Head2)
{
if(Head1==NULL||Head2==NULL){
return false;
}
Node* p1=Head1;
Node* p2=Head2;
while(p2->next!=NULL){ //先找到链表2的末尾,由p2指向
p2=p2->next;
}
p2->next=p1; //将链表1的表头与链表2的表尾连接
while(p1!=NULL){ //遍历链表1,如果回到了链表1表头,则相交
if(p1->next==Head1){
p2->next=NULL; //恢复原状
return true;
}
p1=p1->next;
}
p2->next=NULL; //恢复原状
return false;
}
思路3:O(len1+len2)
如果两个链表的末尾元素相同(指针相同,即为同一个元素,而非值相等),则必相交。
bool judgeIntersectList3(Node* Head1,Node* Head2)
//判断两个单链表是否相交(Y型)
bool judgeIntersectList3(Node* Head1,Node* Head2)
{
if(Head1==NULL || Head2==NULL){
return false;
}
Node* p1=Head1;
Node* p2=Head2;
while(p1->next!=NULL) //p1与p2记录两链表的尾指针
p1=p1->next;
while(p2->next!=NULL)
p2=p2->next;
if(p1==p2){
return true;
}
return false;
}
10.两个单链表相交,计算相交点
思路1:
分别遍历两个单链表,计算出它们的长度M和N,假设M比N大,则长度M的链表先前进M-N,然后两个链表同时以步长1前进,前进的同时比较当前的元素,如果相同,则必是交点。
Node* getIntersectPoint(Node* Head1,Node* Head2)
//两链表相交,计算相交点
Node* getIntersectPoint(Node* Head1,Node* Head2)
{
int len1=numOfNodes(Head1);
int len2=numOfNodes(Head2);
int initMove=abs(len1-len2);
Node* p1=Head1;
Node* p2=Head2;
if(len1>len2){
for(int i=0; i<initMove; i++)
p1=p1->next;
}
else{
for(int i=0; i<initMove; i++)
p2=p2->next;
}
while(p1!=NULL && p2!=NULL){
if(p1==p2){
return p1;
}
p1=p1->next;
p2=p2->next;
}
return NULL;
}
思路2:
将指针p1,p2定位到两个链表的尾部,然后同时将两个指针前移(不可以,因为是单向链表)
12.单链表排序
思路:
参见基本函数13://冒泡排序链表,具体的做法是“狸猫换太子”,即只交换节点中的值,对链表结构不做改动。
void sortList(Node*& Head);
//链表排序
//排序的方法是不破坏结构,有“狸猫换太子”的意思,只进行value的交换,不破坏链表结构
void sortList(Node*& Head)
{
int count=numOfNodes(Head);
if(count==0||count==1){
return ;
}
//冒泡排序
bool exchange;
for(int i=2;i<=count;i++){
exchange=false;
for(int j=count;j>=i;j--){
Node* p1=locateNodeI(Head,j);
Node* p2=locateNodeI(Head,j-1);
if(p1->value<p2->value){
exchange=true;
swap(p1->value,p2->value);
}
}
if(!exchange)
break;
}
}
13.删除单链表中重复的元素
思路:
用Hashtable辅助,遍历一遍单链表就能搞定。同高级函数9的原因,我不太会使用C++STL中的hash。而如果使用set集合来存储链表中的所有的值,实际上效率和每次重新遍历单链表是一样的。“用了c++标准库中的set来保存访问过的元素,所以很方便的就可以判断当前节点是否在set集合中,直接使用set提供的find函数就可以了。而且使用set的查找在时间复杂度上比较低。”我不太清楚STL中set集合的实现方式,如果是基于类似hash结构的话,那自然效率O(1),而如果是数组的话,实际在遍历一遍,所以效率O(n)。不过貌似后者的可能性大一些。
void DeleteDuplexElements(Node*Head);
//删除单链表中的重复元素(使用set集合来实现)
void DeleteDuplexElements(Node*Head)
{
if(Head==NULL||Head->next==NULL){ //链表为空或者只有一个元素
return ;
}
//以下至少有两个元素
set<int>s;
Node* p1=Head;
Node* p2=Head->next;
s.clear();
s.insert(p1->value);
while(p2!=NULL){ //要删除的不可能是链表头,因为如果是链表头,则集合还为空
if(s.find( p2->value)==s.end() ){ //没有
s.insert(p2->value);
p2=p2->next;
p1=p1->next;
}
else{ //已经有,则要删除该节点
if(p2->next==NULL){ //如果是链表尾
p1->next=NULL;
free(p2);
p2=NULL;
}
else{
p1->next=p2->next;
free(p2);
p2=p1->next;
}
}
}
}