链表节点操作的总结

前几天看到一个比较老的面试题,想了想还是总结一下记录下来吧,

首先我先定义一下了链表的节点,后面的操作都通用这个类型的节点

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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章