鏈表節點操作的總結

前幾天看到一個比較老的面試題,想了想還是總結一下記錄下來吧,

首先我先定義一下了鏈表的節點,後面的操作都通用這個類型的節點

struct pNode{ 
       int p_mvalue; 
       pNode* p_next;
};


一:題目大意是:給兩個鏈表,找出是否相交:

已知的鏈表的頭結點 Head1, Head2

首先說第一種方法,很容易想到,就是兩個鏈表分別從頭到尾跑一遍,再看兩個節點的指針是否相同,簡單直接,但是不能直接返回交點節點的位置;

    pNode* tmp1= Head1;
    pNode* tmp2= Head2;
    
    while(tmp1->p_next !=NULL )
        tmp1= tmp1->p_next;
    while(tmp2->p_next !=NULL )
        tmp2= tmp2->p_next;
    if(tmp1 == tmp2)
        cout<<"相交"<<endl;</span>

解法二:就是利用棧,不過這個要求數據比較小,開兩個棧分別存放兩個鏈表的節點,首先比兩個棧中的頂端節點是否相同,如果相同,然後再逐個彈出,當頂端節點不相同時,那麼前一個彈出的節點就是該鏈表的交點。

    stack< pNode* > p1;
    stack< pNode* > p2;
    pNode* tmp1= Head1;
    pNode* tmp2= Head2;

    while(tmp1){ //鏈表一入棧
        p1.push(tmp1);
        tmp1= tmp1->p_next;
    }

    while(tmp2 ){//鏈表二入棧
         p2.push(tmp2);
         tmp2= tmp2->p_next;
    }
    if(p1.top() == p2.top()){ //查找交點
        while(!p1.empty() && !p2.empty()){
            if(p1.top() == p2.top()){
                cout<< "相交"<<endl;
                break;
            }
            p1.pop();
            p2.pop();
        }
    }


方法三:這是在知道兩個鏈表的長度的情況下,假設長度分別爲 len1, len2 ,再假設len1 <= len2, 那麼我們用兩個指針tmp1,  tmp2 分別指向 Head1, Head2 並且讓 tmp2先走(len2 - len1)格,再兩個指針同時往後走,直到第一次 tmp == tmp2 或者其中一個指針爲 NULL,在這樣的情況下,如果鏈表相交的話,當遇到交點時便利就會結束,效率還是比較可觀;

    pNode* tmp1= Head1;
    pNode* tmp2= Head2;
    
    int dif=len2- len1;
    while(dif-- && tmp2 ){
        tmp2= tmp2->p_next;
    }
    
    while(tmp1 && tmp2){
        if(tmp1 == tmp2 ){
            cout<< "相交"<<endl;
            break;
        }
        tmp1= tmp1->p_next;
        tmp2= tmp2->p_next;
    }<

二: 判斷單鏈表中是否有環,很簡單,給出兩個指針,一個每次跑一格,另一個每次跑兩格,如果有環那麼一定會相遇的,如果其中一個指針爲NULL了 說明跑到表尾了 ,一定沒有環的,

三:刪除鏈表中的節點,大意爲給定單鏈表表的頭結點Head1 和一個指向鏈表中一個節點的指針p,刪除p所指向的節點。

或許你會想這個簡單,一分鐘不到就會寫下如下代碼:

void deletNodep(pNode* Head , pNode* p){
    if( Head == NULL || p== NULL )
        throw new std::exception("Invalid parameters");

    pNode* ptmp=Head;
    while( ptmp->p_next != p)
        ptmp= ptmp->p_next;
    pNode* q=ptmp->p_next;
    ptmp->p_next = q->p_next;
    delete(q);
}
仔細看看沒問題啊,還判斷了輸入的有效性,但是再想想o(n)的效率還可以接受,那你就錯了,其實是有o(1)的解法的:

void deletNodep(pNode* Head , pNode* p){
    if( Head == NULL || p== NULL )
        throw new std::exception("Invalid parameters");

    pNode* q;
    if( p->p_next !=NULL ){
        q= p->p_next;
        p->p_mvalue = p->p_next->p_mvalue;
        p->p_next   = p->p_next->p_next;
    }else{
        pNode* ptmp=Head;
        while( ptmp->p_next != p)
            ptmp= ptmp->p_next;
        pNode* q=ptmp->p_next;
        ptmp->p_next = q->p_next;
    }
    delete(q);
}
在輸入合法的情況下,如果該節點不是鏈表的尾節點,那麼就將該節點的下一個節點的值付給該節點,把該節點的下一節點刪除,時間複雜度 o(1),如果是尾節點,那沒辦法只有遍歷一遍了找到它前面那一節點的next域,再算平均效率 還是o(1),這次便可以接受了。


三、鏈表的輸出操作,順序輸出就不用說了吧,那說說逆序輸出吧。

假設鏈表是沒有頭結點,首先或許你會想到,我們可以下先把鏈表反轉了,在順序輸出,就方便多了,但是這是最不好的解法,翻轉的耗費不說,你改變了鏈表原有的順序,

那怎麼辦那,很快你會想到用棧,正好棧的性質是先進後出,先把所有節點都入棧再逐個彈出,代碼也蠻簡單:

void printList(pNode *pHead){
    if(pHead == NULL )
        return ;
    stack< pNode* >pStack;
    pNode* ptmp=pHead;
    while(ptmp){
        pStack.push(ptmp);
        ptmp= ptmp->p_next;
    }
    while(!pStack.empty()){
        pNode* p;
        p=pStack.top();
        cout<< p->p_mvalue <<endl;
        pStack.pop();
    }
}
不過既然想到了棧,那爲什麼不用遞歸呢,那樣的話代碼更加簡潔,遞歸計算基於棧的啊,所以更好的方法應該是這樣的:

void printList(pNode *pHead){
    if(pHead == NULL )
        return ;
    printList(pHead->p_next );
    cout<< pHead->p_mvalue << endl;
}
哈哈是不是簡單多了


四、單鏈表翻轉,

就是已知一單鏈表的第一個節點的位置,將該鏈表翻轉,並返回翻轉後鏈表的第一個節點的指針

實現這個我們需要藉助幾個輔助指針依次爲,自己拿張紙劃一劃就知道了是思路很清晰,代碼如下:

pNode * revList(pNode *pHead){
    if(pHead == NULL )
        return NULL;
    pNode *p=NULL;
    pNode *q=pHead;
    pNode *revHead= NULL;
    while(q!=NULL){
        pNode *r= q->p_next;
        q->p_next=p;
        if(r== NULL)
            revHead= q;
        p= q;
        q= r;
    }
    return revHead;
}

還有一寫問題,如鏈表的合併啊,雙向鏈表的操作等等,感覺都不是特別的難,找張紙畫畫,思路想清楚了,代碼都很好實現的。








發佈了30 篇原創文章 · 獲贊 13 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章