在鏈表中漫遊

  鏈表數據結構操做靈活,常常出如今各大公司的面試題中,所以,本文總結了常見的有關鏈表的面試題。node

  首先定義鏈表的數據結構:面試

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

  1.基本操做算法

  雖然將其命名成「基本操做」,由於這些對鏈表的操做與咱們正常接觸的對鏈表的操做,完成的功能是同樣的,如刪除一個節點,增長一個節點。但與正常操做不同的地方是,增長了一些限制條件。數據結構

  1)刪除節點:只給定單鏈表中某個結點p(並不是最後一個結點,即p->next!=NULL)指針,刪除該結點函數

  由於不知道頭節點的位置,所以,沒法遍歷得到節點p的前一個節點指針,採用傳統的刪除手法,將會形成鏈表在p出斷開。爲此,咱們採用「假裝」的技術:將欲刪除的節點的下一個節點NextNode中的數據copy到,並刪除NextNode節點。oop

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前面插入一個結點ui

  相似與以前刪除節點同樣的問題,咱們依然採用「偷樑換柱」技術:將欲插入的節點數據和節點p互換數據,而後,將新插入節點Next指向p的Next,p的Next指向插入的新節點。spa

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)兩個指針同步移動,當第一個指針指向尾節點是,第二個指針位置,即是須要求的節點位置:code

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

  運行結果以下:

相關文章
相關標籤/搜索