1. 程式人生 > 其它 >100道必會程式碼(二)——連結串列

100道必會程式碼(二)——連結串列

目錄

1.兩個遞增連結串列合併為一個連結串列並保持遞增

題目:將兩個遞增的有序連結串列合併為一個遞增的有序連結串列。要求結果連結串列仍使用原 來兩個連結串列的儲存空間,不另外佔用其他的儲存空間。表中不允許有重複的數 據。
【演算法思想】不允許佔用其他的儲存空間,於是使用La的頭結點作為新連結串列Lc的頭結點,每次將La與Lb的結點元素兩兩比較,將較小值鏈入Lc中,指標不斷後移。最終剩餘的元素全部直接鏈入Lc中

void MergeList(LinkList &La,LinkList &Lb, LinkList &Lc)
{
    //pa 是連結串列 La 的工作指標,初始化為首元結點,即第一個存放元素的結點
    pa=La->next;
    //pb 是連結串列 Lb 的工作指標,初始化為首元結點
    pb=Lb->next;
    ///La 的頭結點作為 Lc 的頭結點
    pc=Lc=La;
    while(pa&&pb)
    {
        //取較小者 Lb 中的元素,將 pa 連結在 pc 的後面,pa 指標後移
        if(pa->data<pb->data)
        {
            pc->next=pa;
            pc=pa;
            pa=pa->next;
        }
        //取較小者 Lb 中的元素,將 pb 連結在 pc 的後面,pb 指標後移
        else if(pa->data>pb->data)
        {
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
        //元素相等,取La元素,釋放Lb中該元素,並注意不要讓Lb斷鏈
        else
        {
            pc->next=pa;
            pc=pa;
            pa=pa->next;
            q=pb->next;
            free(pb);
            pb=q;
        }
    }
    //將非空的剩餘元素直接連結在 Lc 表的即可
    pc->next=pa?pa:pb;
    //釋放Lb頭結點
    free(Lb);
}

2.兩個非遞減有序表合併為一個非遞增有序表

題目:將兩個非遞減的有序表合併為一個非遞增的有序表。要求結果連結串列仍然使用 原來兩個連結串列的儲存空間,不佔用另外的儲存空間。表中允許有重複的資料。
【演算法思想】與上述題目類似,從原有兩個連結串列中依次摘取結點,通過更改結點 的指標域重新建立新的元素之間的線性關係,得到一個新連結串列。

但與上面題目不 同的點有兩個:①為保證新表與原表順序相反,需要利用前插法建立單鏈表,而 不能利用後插法;②當一個表到達表尾結點為空時,另一個非空表的剩餘元素應 該依次摘取,依次連結在 Lc 表的表頭結點之後,而不能全部直接連結在 Lc 表的 最後。

演算法思想是:假設待合併的連結串列為 La 和 Lb,合併後的新表使用頭指標 Lc(Lc 的 表頭結點設為 La 的表頭結點)指向。pa 和 pb 分別是連結串列 La 和 Lb 的工作指標, 初始化為相應連結串列的首元結點。從首元結點開始進行比較,當兩個連結串列 La 和 Lb 均為到達表尾結點時,依次摘取其中較小者重新連結在 Lc 表的表頭結點之後, 如果兩個表中的元素相等,只摘取 La 表中的元素,保留 Lb 表中的元素。當一 個表到達表尾結點為空時,將非空表的剩餘元素依次摘取,連結在 Lc 表的表頭結 點之後。最後釋放連結串列 Lb 的頭結點。

void MergeList(LinkList &La, LinkList &Lb, LinkList &Lc) 
{
    //pa 是連結串列的 La 的工作指標,初始化為首元結點
    pa=La->next;
    //pb 是連結串列 Lb 的工作指標,初始化為首元結點
    pb=Lb->next;
    //用 La 的頭結點作為 Lc 的頭結點
    Lc=pc=La;
    Lc->next=NULL;
    //只要有一個表未到達表尾指標,用 q 指向待摘取的元素,而不是剩餘的可以直接鏈入
    while(pa||pb)
    {
        //La表為空,用q指向pb,pb指標後移
        if(!pa)
        {
            q=pb;
            pb=pb->next;
        }
        //Lb表為空,用q指向pa,pa指標後移
        else if(!pb)
        {
            q=pa;
            pa=pa->next;
        }
        //取較小者 La 中的元素,用 q 指向 pa, pa 指標後移
        else if(pa->data <= pb->data)
        {
            q=pa;
            pa=pa->next;
        }
        //取較小者 Lb 中的元素,用 q 指向 pb,pb 指標後移
        else
        {
            q=pb;
            pb=pb->next;
        }
        q->next=Lc->next;
        Lc->next=q;
    }
    free(Lb);
}

3.求兩個遞增排序連結串列的交集

題目:已知兩個連結串列 A 和 B 分別為兩個集合,其元素遞增排列。請設計一個演算法, 用於求出 A 與 B 的交集,並存放在 A 連結串列中。
【演算法思想】 A 與 B 的交集是指同時出現在兩個集合中的元素,因此,此題的關鍵點在於:依 次摘取兩個表中相等的元素重新進行連結,刪除其他不等的元素。

演算法思想是:假設待合併的連結串列為 La 和 Lb,合併後的新表使用頭指標 Lc(Lc 的 表頭結點設為 La 的表頭結點)指向。pa 和 pb 分別是連結串列 La 和 Lb 的工作指標, 初始化為相應連結串列的首元結點。從首元結點開始進行比較,當兩個連結串列 La 和 Lb 均為到達表尾結點時,如果兩個表中的元素相等,摘取 La 表中的元素,刪除 Lb 表中的元素;如果其中一個表中的元素較小,刪除此表中較小的元素,此表的工 作指標後移。當連結串列 La 和 Lb 有一個到達表尾結點為空時,依次刪除另一個非 空表中的所有元素。最後釋放連結串列 Lb 的頭結點。

void Intersection(LinkList &La, LinkList &Lb, LinkList &Lc)
{
    pa=La->next;//pa 是連結串列 La 的工作指標,初始化為首元結點
    pb=Lb->next;//pb 是連結串列 Lb 的工作指標,初始化為首元結點
    Lc=pc=La;   //用 La 的頭結點作為 Lc 的頭結點
    while(pa&&pb)
    {
        //pa與pb所指元素相等,則取La中元素加入交集
        if(pa->data==pb->data)
        {
            pc->next=pa;
            pc=pa;
            pa=pa->next;
            u=pb;
            pb=pb->next;
            free(u);
        }
        //La中元素更小,則刪除La中元素
        else if(pa->data<pb->data)
        {
            u=pa;
            pa=pa->next;
            free(u);
        }
        //Lb中元素更小,則刪除Lb中元素
        else
        {
            u=pb;
            pb=pb->next;
            free(u);
        }
    }
    //Lb篩選完畢,直接刪除La中所有元素,一個個刪
    while(pa)
    {
        u=pa;
        pa=pa->next;
        free(u);
    }
    //La篩選完畢,直接刪除Lb中所有元素
    while(pb)
    {
        u=pb;
        pb=pb->next;
        free(u);
    }
    pc->next=NULL;
    free(Lb);
}

4.求兩個遞增排序連結串列的差集

題目:已知兩個連結串列 A 和 B 分別表示兩個集合,其元素遞增排列。請設計演算法 求出兩個集合 A 和 B 的差集(即僅由在 A 中出現而不在 B 中出現的元素所構成 的集合),並且以同樣的形式儲存,同時返回該集合的元素個數。
【演算法思想】 求兩個集合 A 和 B 的差集是指在 A 中刪除 A 和 B 中共有的元素,即刪除連結串列中 的資料域相等的結點。由於要刪除結點,此題的關鍵點在於:遍歷連結串列時,需 要儲存待刪除結點的前驅。

演算法思想是:假設待求的連結串列為 La 和 Lb,pa 和 pb 分別是連結串列 La 和 Lb 的工作 指標,初始化為相應連結串列的首元結點。pre 為 La 中 pa 所指結點的前驅結點的指 針,初始化為 La。從首元結點開始進行比較,當兩個連結串列 La 和 Lb 均未到達表 尾結點時,如果 La 表中的元素小於 Lb 表中的元素,La 表中的元素即為待求差 集中的元素,差集元素個數增 1,pre 置為 La 表的工作指標 pa,pa 後移;如果 Lb 表中的元素小於 La 表中的元素,pb 後移;如果 La 表中的元素等於 Lb 表中的元素, 則在 La 表刪除該元素值對應的結點。

void Difference(LinkList &La, LinkList &Lb, int &n)
{
    pa=La->next;//pa 是連結串列 La 的工作指標,初始化為首元結點
    pb=Lb->next;//pb 是連結串列 Lb 的工作指標,初始化為首元結點
    pre=La; //pre 為 La 中 pa 所指結點的前驅結點的指標
    while(pa&&pb)
    {
        //La當前結點指標後移
        if(pa->data<pb->data)
        {
            n++;
            //時刻儲存前驅節點
            pre=pa;
            pa=pa->next;
        }
        //Lb當前結點指標後移
        else if(pa->data>pb->data)
        {
            pb=pb->next;
        }
        //找到並集元素了,刪除
        else
        {
            pre->next=pa->next;
            u=pa;
            pa=pa->next;
            free(u);
        }
    }
}

5.高效刪除帶頭結點單鏈表最小值結點

題目:試編寫在帶頭結點的單鏈表 L 中刪除一個最小值結點的高效率演算法(假設最 小值結點是唯一的)。
【演算法思想】:用 p 從頭至尾掃描單鏈表,pre 指向p 結點的前驅,用 minp 儲存值 最小的結點指標(初值為 p),minpre 指向minp 結點的前驅(初值為 pre)。一 遍掃描,一邊比較,若 p->data 小於 minp->data,則將 p、pre 分別賦值給 minp, minpre,如下圖所示。當 p 掃描完畢,minp 指向最小值結點,minpre 指向最小 值結點的前驅結點,再將 minp 所指結點刪除即可。

LinkList Delete_Min(LinkList &L)
{
    //L 是帶頭結點的單鏈表,本演算法是刪除其最小值結點
    LNode *pre=L;
    //p 為工作指標,pre 指向其前驅
    LNode *p=pre->next;
    //儲存最小值結點及其前驅
    LNode *minpre=pre;
    LNode *minp=p;
    while(p!=NULL)
    {
        if(p->data<minp->data)
        {
            minp=p;
            minpre=pre;
        }
        //繼續掃描下一結點
        pre=p;
        p=p->next;
    }
    //刪除最小值結點
    minpre->next=minp->next;
    free(minp);
    return L;
}

6.刪除帶頭結點在單鏈表中所有值為x的結點

題目:在帶頭結點的單鏈表 L 中,刪除所有值為 x 的結點,並釋放其空間,假設值 為 x 的結點不唯一,試編寫演算法實現上述操作。
【演算法思想】
解法一【刪除法】:用 p 從頭至尾掃描單鏈表,pre 指向*p 結點的前驅。若 p 所指結點的值 為 x,則刪除,並讓 p 移向下一個結點,否則讓 pre、p 同步後移一個結點。
提示:本演算法其實也算的上是一個刪除某結點相關題型的程式碼模板。此題是在無序單鏈表中刪除滿足某種條件的所有結點,這裡的條件是結點的值為 x,實際上, 這個條件是可以任意改寫的,只需要修改 if 條件即可。例如,我們要求刪除結 點 值 介 於 mink 和 maxk 之 間 的 所 有 結 點 , 則 只 需 要 將 if 語 句 修 改 為 if(p->data >mink && p->data <maxk)

void Delete_x(LinkList &L, ElemType x)
{
    //工作指標
    LNode *p=L->next;
    //工作指標前驅
    LNode *pre=L;
    //刪除結點
    LNode *q;
    while(p!=NULL)
    {
        if(p->data==x)
        {
            q=p;
            p=p->next;
            //刪除q結點並釋放空間
            pre->next=p;
            free(q);
        }
        //否則同步後移
        else
        {
            pre=p;
            p=p->next;
        }
    }
}

解法二【尾插法】:採用尾插法建立單鏈表。用 p 指標掃描 L 的所有結點,當其值不為 x 時將其連結到 L 之後,否則將其釋放。

void Delet_x(LinkList &L,ElemType x)
{
    //工作指標
    LNode *p=L->next;
    //指向尾結點,初始為頭節點
    LNode *r=L;
    //刪除結點
    LNode *q;  
    while(p!=NULL)
    {
        //鏈入
        if(p->data!=x)
        {
            r->next=p;
            r=p;
            p=p->next;
        }
        //釋放
        else
        {
            q=p;
            p=p->next;
            free(q);
        }
    }  
    r->next=NULL;
}

7.帶頭結點單鏈表原地逆置

題目:、試編寫演算法將帶頭結點的單鏈表就地逆置,所謂“就地”是輔助空間複雜度為 O(1)。
【演算法思想】將頭結點摘下,然後從第一結點開始,一次插入到頭結點的後面(頭 插法建立單鏈表),直到最後一個結點為止,這樣就實現了連結串列的逆置,如下圖 所示。

LinkList Reverse(LinkList L)
{
    ///p 為工作指標,r 為 p 的後繼,以防斷鏈
    LNode *p,*r;
    p=L->next;
    L->next=NULL;
    while(p!=NULL)
    {
        r=p->next;
        p->next=L->next;
        L->next=p;
        p=r;
    }
    return L;
}

8.單鏈表分解為奇數序號表和偶數序號表

題目:將一個帶頭結點的單鏈表 A 分解為兩個帶頭結點的單鏈表 A 和 B,使得 A 表 中中含有原標中序號為奇數的元素,而 B 表中含有原表中序號為偶數的元素, 且保持其相對順序不變。
【演算法思想】尾插法

LinkList DisCreat(LinkList &A)
{
    i=0;//i 記錄表 A 中結點的序號
    //B表的建立
    B=(LinkList) malloc (sizeof(LNode));
    B->next=NULL;
    //分別指向尾結點
    LNode *ra=A, *rb=B;
    p=A->next;
    A->next=NULL;
    while(p!=NULL)
    {
        i++;
        if(i%2==0)
        {
            rb->next=p;
            rb=p;
        }
        else
        {
            ra->next=p;
            ra=p;            
        }
        p=p->next;
    }
    ra->next=NULL;
    rb->next=NULL;
    return B;
}