链表 相关操作

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;
               }
          }
     }
}                

  

原文地址:https://www.cnblogs.com/claremore/p/4722654.html