代码改变世界

在链表中漫游

2013-08-29 16:10  夜与周公  阅读(342)  评论(0编辑  收藏  举报

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

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

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

  运行结果如下: