• 链表 相关操作


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

      

  • 相关阅读:
    启用div作为编辑器 添加contentEditalbe属性
    AngularJs 通过 ocLazyLoad 实现动态(懒)加载模块和依赖-转
    angularjs的懒加载
    JavaScript 中的this指向问题
    Project Euler:Problem 41 Pandigital prime
    Android 消息机制
    新西兰天维网登录发送明文password
    使用Reveal来查看别人的APP界面+白苹果不刷机解决方式
    Android中List循环遍历性能对照
    2016年最新苹果开发人员账号注冊申请流程最强具体解释!
  • 原文地址:https://www.cnblogs.com/claremore/p/4722654.html
Copyright © 2020-2023  润新知