• 在链表中漫游


      链表数据结构操作灵活,经常出现在各大公司的面试题中,因此,本文总结了常见的有关链表的面试题。

      首先定义链表的数据结构:

    template<typename T>
    struct Node
    {
      T  data;
      Node* next;
    };

      1.基本操作

      虽然将其命名成“基本操作”,因为这些对链表的操作与我们正常接触的对链表的操作,完成的功能是一样的,如删除一个节点,增加一个节点。但与正常操作不一样的地方是,增加了一些限制条件。

      1)删除节点:只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点

      因为不知道头节点的位置,因此,无法遍历获得节点p的前一个节点指针,采用传统的删除手法,将会造成链表在p出断开。为此,我们采用“伪装”的技术:将欲删除的节点的下一个节点NextNode中的数据copy到,并删除NextNode节点。

    template <typename T>
    bool delete_node(Node<T>* pNode)
    {
        Node<T> pNext = pNode->next;
        if (!pNext)
        {
            return false;
        }
    
        //copy pNext所指节点中的数据
        pNode->data = pNext->data;
        pNode->next = pNext->next;
        
        delete pNext;
        return true;
    }

      2)插入节点:只给定单链表中某个结点p(非空结点),在p前面插入一个结点

      类似与之前删除节点一样的问题,我们依然采用“偷梁换柱”技术:将欲插入的节点数据和节点p互换数据,然后,将新插入节点Next指向p的Next,p的Next指向插入的新节点。

    template<typename T>
    void add_node(Node<T>* pNode, Node<T>* pNewNode)
    {
        //互换pNode与pNewNode内的数据
        T data = pNode->data;
        pNode->data = pNewNode->data;
        pNewNode->data = data;
    
        //将互换数据后的节点链接起来
        pNewNode->next = pNode->next;
        pNode->next = pNewNode;
        
    }

      3)查找:查找链表中倒数第K个节点

      一般的方法需要遍历两次:第一次就成链表的长度,第二次,遍历计数到遍历到第length-k个节点是结束。如何只在遍历一次的时候,就能找出倒数第K个节点?可以使用双指针技术:1)首先两个指针均从头指针开始,第一个节点先走K个位置;2)两个指针同步移动,当第一个指针指向尾节点是,第二个指针位置,便是需要求的节点位置:

    template<typename T>
    Node<T>* find_last_k(Node<T>* pHead, int k)
    {
      if (pHead->next == NULL)
        return NULL;
    Node
    <T>* pFirst = pHead->next; Node<T>* pSeconde = pHead->next; for (int i = 1; i < k; i++, pFirst = pFirst->next); while (pFirst->next) { pFirst = pFirst->next; pSeconde = pSeconde->next; } return pSeconde; }

       4)链表就地转折

      这个操作没有涉及到多少技巧,只需要主要操作顺序:

    template<typename T>
    Node<T>* reverse(Node<T>* pHead)
    {
        Node<T>* pReverseHead = new Node<T>();
        pReverseHead->next = NULL;
        Node<T>* pCurNode = pHead->next;
        Node<T>* pNext = NULL;
    
        while (pCurNode)
        {
            pNext = pCurNode->next;
            pCurNode->next = pReverseHead->next;
            pReverseHead->next = pCurNode;
            pCurNode = pNext;
        }
    
        return pReverseHead;
    }

      2.技巧题

      1)判断链表是否相交

      判断链表相交有两个功能需求:仅判断链表是否相交;给出链表相交的第一个节点。第一个功能相对简单,如何两个链表相交,那么两个链表最后一个节点必然是相同的,实现代码如下:

    template <typename T>
    bool is_intersected(Node<T>* pAHead, Node<T>* pBHead)
    {
        if (pAHead != NULL || pBHead != NULL)
        {
            return false;
        }
        else
        {
            Node<T>* pANode = pAHead;
            Node<T>* pBNode = pBHead;
    
            while (pANode->next)
                pANode = pANode->next;
    
            while (pBNode->next)
                pBNode = pBNode->next;
    
            return pANode == pBNode ? true : false;
        }
    }

      第二个功能,求两个相交的那个节点。算法首先求出两个两边的长度之差|lenA-lenB|,让较长链表走|lenA-lenB|个步长;然后让这两个链表同时开始移动,并判断当前节点的指针是否指向同一节点template <typename T>

    int list_len(Node<T>* pAHead)
    {
        Node<T>* pNode = pHead->next;
        int listLen = 0;
    
        while (pNode)
      {
       pNode = pNode->next; listLen
    ++;
      }
    return listLen; } template <typename T> Node<T>* find_intersect_node(Node<T>* pAHead, Node<T>* pBHead) { Node<T>* intersectNode = NULL; if (pAHead == NULL || pBHead == NULL) { return intersectNode; } int lenA = list_len(pAHead); int lenB = list_len(pBHead); int longerLen = 0; Node<T>* pShort = NULL; Node<T>* pLong = NULL; if (lenA > lenB) { longerLen = lenA - lenB; pLong = pAHead->next; pShort = pBHead->next; } else { longerLen = lenB - lenA; pLong = pBHead->next; pShort = pAHead->next; } while (longerLen-- > 0) pLong = pLong->next; while (pLong) { if (pLong == pShort) { intersectNode = pLong; break; }

          pLong = pLong->next;
          pShort = pShort->next;

        }

    return intersectNode;
    }

      2)判断链表是否由环

      在判断链表是否相交的一个前提条件是,链表不存在环。如果链表存在环,遍历这个链表将会是个死循环。那么如何判断一个链表是否存在一个环?第一种方法,记录链表中给每个节点的地址,当出现了两个相同的地址时,可以判断两个链表是相交的。

      第二种方法,是采用快慢指针法,慢指针p每走一次步长一个节点,快指针q每走一次步长两个节点,当着两个指针相遇时,则链表存在环。算法的证明如下:

      如图所示当p在环的入口节点时,q已经进入环中,令q=p-q, p=0,环的长度为m,当慢指针走过i次步长后,p与q相遇,需要则满足

                                                                                                (q+2*i) % m = i % m

      上式成立的条件是:

      q + 2*i = i + k * m(k为整数)

                                                                                                 q + i = k*m

      m与q是常量,而i和k是变量,上面的表达式一定存在某个(i, k)使表达式成立。

      判断链表是否相交的代码如下:

    /************************************************************************/
    /* 函数功能: 判断链表是否有环    
     * 输入参数: pHead,指向链表的头指针
     * 返回参数: 返回快慢指针相遇的节点指针,
                 如果没有换返回NULL
    /************************************************************************/
    template <typename T>
    Node<T>* is_exist_loop(Node<T>* pHead)
    {
        Node<T>* pNode = NULL;
        if (pHead ==NULL)
        {
            return pNode
        }
    
        Node<T>* p = pHead;
        Node<T>* q = pHead;
    
        while (q->next && q->next->next)
        {
            q = q->next->next;
            p = p->next;
            if (p==q)
            {
                pNode = p;
                break;
            }
        }
    
        return pNode;
    }

      那么如何找到环的入口节点呢?

      假设在快慢指针相遇的节点pNode处“断开”(非常物理意义上的断开),那么将存在两天逻辑上的链表:以pHead为头指针pNode为结尾的链表A,以pNode为首节点,并以pNode为尾节点的两个链表,这两个链表相交与环的入口节点。

    /************************************************************************/
    /* 函数功能: 寻找有环链表的入口节点    
     * 输入参数: pHead,指向链表的头指针,pCrossNode快慢指针相遇的节点
     * 返回参数: 链表的入口节点
    /************************************************************************/
    template<typename T>
    Node<T>* find_enter_node(Node<T>* pHead, Node<T>* pCrossNode)
    {
    

        int lenA=0, lenB=1; //链表B从对首节点开始计算,len起始于1
        for (Node<T>* pNode=pHead->next ; pNode != pCrossNode; pNode = pNode->next) //计算链表A的长度
          lenA++;

    
    

        for (Node<T>* pNode=pCrossNode->next; pNode!=pCrossNode; pNode = pNode->next)//计算链表B的长度
          lenB++;

        Node<T>* pLong = NULL;
        Node<T>* pShort = NULL;
        int longLen = 0;
        if (lenA > lenB)
        {
            pLong = pHead->next;
            pShort = pCrossNode;
            longLen = lenA - lenB;
        }
        else
        {
            pLong = pCrossNode;
            pShort = pHead->next;
            longLen = lenB - lenA;
        }
    
        while (longLen-- > 0)
        {
            pLong = pLong->next;
        }
    
        Node<T>* pEnterNode = NULL;
        while (pLong)
        {
            if (pLong == pShort)
            {
                pEnterNode = pLong;
                break;
            }
    
            pLong = pLong->next;
            pShort = pShort->next;
        }
    
        return pEnterNode;
    
    }

       程序的主函数如下:

    int main()
    {
        int myArray[] = {0, 1, 2, 3, 4, 5};
    
        Node<int>* pHead = build_list(myArray, 6);
    
        Node<int>* pNode = pHead->next;
        while(pNode && pNode->data != 3)
            pNode = pNode->next;
    
        cout<<"Before Delete Element 3:";
        print(pHead);
        cout<<"After Delete Element 3:";
        delete_node(pNode);
        print(pHead);
    
        pNode = pHead->next;
        while(pNode && pNode->data != 4)
            pNode = pNode->next;
        Node<int>* pAddNode = new Node<int>(6);
        cout<<"After Insert Element 6 before element 4:";
        add_node(pNode, pAddNode);
        print(pHead);
    
        pNode = find_last_k(pHead, 4);
        cout<<"The last 4 element:"<<pNode->data<<endl;
    
        cout<<"Reverse list:";
        pHead = reverse(pHead);
        print(pHead);
    
        //Build list B via add list A;
        int myArrayB[] = {7, 8, 9};
        Node<int>* pHeadB = build_list(myArrayB, 3);
        Node<int>* pBNode = pHeadB->next;
        while (pBNode->next)
            pBNode = pBNode->next;
        pBNode->next = pNode;
    
        cout<<"List A:";
        print(pHead);
        cout<<"List B:";
        print(pHeadB);
        Node<int>* pIntersetNode = find_intersect_node(pHead, pHeadB);
        cout<<"The intersect element is:"<<pIntersetNode->data<<endl;
    
        //Build cycle via list A at element 4
        Node<int>* pLast3 = find_last_k(pHead, 3);
        Node<int>* pLast1 = find_last_k(pHead,1);
        pLast1->next = pLast3;                   // build a loop
    
        pNode = is_exist_loop(pHead);
        Node<int>* pEnterNode = find_enter_node(pHead, pNode);
        if (pNode)
        {
            cout<<"Exist loop, The enter node is:"<<pEnterNode->data<<endl;
        }
        else
            cout<<"Not Exist a loop"<<endl;
    
        system("pause");
        return 0;
    }

      运行结果如下:

  • 相关阅读:
    SQL Server 2014忘记SA密码或禁用而且Windows身份验证也无法登录的解决办法
    深入解密.NET(Tuple元祖)
    DbContext 和 ObjectContext两者的区别
    开源的监控软件
    进程kswapd0与events/0消耗大量CPU的问题
    loadrunner---<二>---菜鸟对cookie的思考
    替换linux下的rm命令,并对-rf进行判断
    linux下恢复误删除的文件方法(ext2及ext3)
    oracle闪回使用以及删除存储过程恢复
    eclipse中新建maven项目-转
  • 原文地址:https://www.cnblogs.com/wangbogong/p/3281719.html
Copyright © 2020-2023  润新知