zoukankan      html  css  js  c++  java
  • 链表 相关操作

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

      

  • 相关阅读:
    解释机器学习模型的一些方法(一)——数据可视化
    机器学习模型解释工具-Lime
    Hive SQL 语法学习与实践
    LeetCode 198. 打家劫舍(House Robber)LeetCode 213. 打家劫舍 II(House Robber II)
    LeetCode 148. 排序链表(Sort List)
    LeetCode 18. 四数之和(4Sum)
    LeetCode 12. 整数转罗马数字(Integer to Roman)
    LeetCode 31. 下一个排列(Next Permutation)
    LeetCode 168. Excel表列名称(Excel Sheet Column Title)
    论FPGA建模,与面向对象编程的相似性
  • 原文地址:https://www.cnblogs.com/claremore/p/4722654.html
Copyright © 2011-2022 走看看