實習整理(十四)

前面一篇博文簡要地講了下我在筆試準備過程中碰到過的有關字符串的題目,現在再提下有關鏈表的題目

做有關鏈表的題目最好畫圖,這樣有助於理解
1、單向鏈表逆序
2、有序鏈表合併
3、雙向鏈表刪除結點
4、尋找鏈表中間結點
5、判斷一個鏈表是否有環

1、單向鏈表逆序
(1) 具有鏈表頭的單鏈表

student *reverse(student *stu)
{
   student *p1,*p2,*p3;
    if(stu == NULL ||stu->next == NULL)
        return stu;
   p1=stu->next;                           //p1指向鏈表頭節點的下一個節點
    p2=p1->next;
    p1->next=NULL;
    while(p2)
    {
        p3=p2->next;
        p2->next = p1;
        p1=p2;
        p2=p3;
    }
    printf("p1 = %d,next = %dn ",p1->number,p1->next->number);
   stu->next=p1;                           //將鏈表頭節點指向p1
    return stu;
}
核心思想:
p=head->next->next;
s=head->next;
head->next->next=NULL;
while(p)
{
    q=p->next;
    p->next=s;
    s=p;
    p=q;
}

(2) 無鏈表頭單鏈表
typedef struct student
{
    int number;
    char name[20];
    int score;
    struct student *next;
}student;
student *reverse2(student *stu)
{
        student *p1,*p2,*p3;
        if(stu == NULL ||stu->next == NULL)
                return stu;
        p1=stu;                                   //p1指向鏈表的第一個節點                                                  
        p2=p1->next;
        p1->next = NULL;       
        while(p2)
        {
                p3=p2->next;
                p2->next = p1;
                p1=p2;
                p2=p3;        }
        printf("p1 = %d,next = %d\n ",p1->number,p1->next->number);
        stu=p1;                                  //將鏈表第一個節點指向p1
        return stu;
}

(3) 只用一個變量,遞歸
List* recurReverse(List* p, List* head)
 {
     if(p == NULL || p->next == NULL)
     {
         head = p;
         return p;
     }
     else
     {
         List* q = reverse(p->next, head);
         q->next = p;
         return p;
     }
 }

node* reverse(node * head)
{

       if(head==NULL || head->next==NULL)
              return head;
       node* tail= head->next;

       node* newHead= reverse(head->next);
       tail->next=head;
       head->next=NULL;
       return newHead;
}


2、有序鏈表合併
(1)遞歸方法。
比如有下面兩個鏈表:
鏈表1:1->3->5
鏈表2:2->4->6
遞歸方法的步驟如下:
(1)比較鏈表1和鏈表2的第一個節點數據,由於1<2,因此把結果鏈表頭節點指向鏈表1中的第一個節點,即數據1所在的節點。
(2)對剩餘的鏈表1(3->5)和鏈表2再調用本過程,比較得到結果鏈表的第二個節點,即2與3比較得到2。此時合併後的鏈表節點爲1->2。
接下來的過程類似(2),如此遞歸直到兩個鏈表的節點都被加到結果鏈表中


node * MergeRecursive(node *head1, node *head2)
{
         node *head = NULL;

         if (head1 == NULL)
         {
                  return head2;
         }
         if (head2 == NULL)
         {
                  return head1;
         }
 
         if ( head1->data < head2->data )
         {
                  head = head1 ;
                  head->next = MergeRecursive(head1->next,head2);
         }
         else
         {
                 head = head2 ;
                 head->next = MergeRecursive(head1,head2->next);

          }

          return head ;
}

(2)非遞歸方法。有鏈表頭

Node* Merge2(Node* head1, Node* head2) 

    Node *res, *ret, *p, *q; 
    if(head1 == NULL) return head2; 
    if(head2 == NULL) return head1;
    ret = res = head1;
    p = head1->next; 
    q = head2->next; 
 
    while(p && q) 
    { 
        if(p->value < q->value) 
        { 
            res->next = p; 
            res = p; 
            p = p->next; 
        } 
        else 
        { 
            res->next = q; 
            res = q; 
            q = q->next; 
        } 
    } 
    res->next = p ? p : q;     //p或q有剩,把剩下的鏈表插入到結果鏈表後
    return ret; 
}

還可以藉助第三個鏈表,只是這樣的話需要的空間會大點

Node *head3,*temp;
head3->next=NULL;
temp=head3;//保存表頭
p=head1->next;
q=head2->next;
while(p!=NULL&&q!=NULL)
{
    if(p->value<q->value)
        {
            head3->next=p;
            head3=p;
            p=p->next;
        }
    else
       {
            head3->next=q;
            head3=q;
            q=q->next;
        }
    if(p==NULL)
        head3->next=q;
    else
        head3->next=p;
        head3=temp;
}

3、雙向鏈表刪除結點

dnode *deletenode(dnode *head,dnode *node)
{
        if(node->pre == NULL)
        {
                // 頭結點
                head = head->next;
                head->pre = NULL;
        }
        else if(node->next == NULL)
        {
                // 尾結點
                node->pre->next = NULL; 
        }
        else
        {
                // 中間結點
                node->pre->next = node->next;
                node->next->pre = node->pre;  
        }
        free(node);
        return head;
}


4、尋找鏈表中間結點
【方法1】普通的方法很簡單,首先遍歷一遍單鏈表以確定單鏈表的長度L。然後再次從頭節點出發循環L/2次找到單鏈表的中間節點。算法複雜度爲O(L+L/2)=O(3L/2)。
【方法2】使用快慢指針。設定兩個指向第一個結點的指針,一個(p)一次走一個,一個(q)一次走兩個。當走的快的到達鏈表尾部(q->next==NULL ||q->next->next==NULL)時,慢的就指向中間結點(快指針到鏈表尾部時,當鏈表長度爲奇數時,慢指針指向的即是鏈表中間指針,當鏈表長度爲偶數時,慢指針指向的結點和慢指針指向結點的下一個結點都是鏈表的中間結點)

node *findMid(node *head)    // 有鏈表頭
{
        if(NULL == head)
                return NULL;
        node *fast, *slow;
        fast = slow = head;
        while((fast != NULL) && (fast->next != NULL))
        {
                fast = fast->next->next;
                slow = slow->next;
        }
        return slow;
}

node *findMid(node *head)    // 無鏈表頭
{
        if(NULL == head)
                return NULL;
        if(head->next == NULL || head->next->next == NULL)
                return head;
        node *fast = head;
        node *slow = head;
        while(fast->next != NULL && fast->next->next != NULL)
        {
                fast = fast->next->next;
                slow = slow->next;
        }
        return slow;
}

5、判斷一個鏈表是否有環
(1)判斷是否有環。
設置兩個指針(fast, slow),初始值都指向頭,slow每次前進一步,fast每次前進二步,如果鏈表存在環,則fast必定先進入環,而slow後進入環,兩個指針必定 相遇。(當然,fast先行頭到尾部爲NULL,則爲無環鏈表)
(2)找到環的入口點

當fast若與slow相遇時,slow肯定沒有走遍歷完鏈表,而fast已經在環內循環了n圈(1<=n)。假設slow走了s步,則 fast走了2s步(fast步數還等於s 加上在環上多轉的n圈),設環長爲r,則:

2s = s + nr
s= nr

設整個鏈表長L,入口環與相遇點距離爲x,起點到環入口點的距離爲a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)

(L – a – x)爲相遇點到環入口點的距離,由此可知,從鏈表頭到環入口點等於(n-1)循環內環+相遇點到環入口點,於是我們從鏈表頭、與相遇點分別設一個指針,每 次各走一步,兩個指針必定相遇,且相遇第一點爲環入口點。

node *findCircle(node *head)
{
        if(NULL == head)
                return NULL;
        node *fast, *slow;
        fast = slow = head;
        while((fast != NULL) && (fast->next != NULL))
        {
                fast = fast->next->next;   //走兩步
                slow = slow->next;       //走一步
                if(fast == slow){         //有環
                        fast = head;
                        while(fast != slow){
                                fast = fast->next;
                                slow = slow->next;
                        }
                        return fast;
                }
        }
        return NULL;
}

PS:感覺鏈表經常在面試中遇到,一般面試官經常會要你進行紙上編程,考查你的動手能力等等。。。

未完待續。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章