1. 程式人生 > >C語言實現單鏈表面試題_基礎篇

C語言實現單鏈表面試題_基礎篇

1.比較順序表和連結串列的優缺點,說說它們分別在什麼場景下使用?

順序表中的資料儲存在連續的儲存空間內,所以查詢的效率更高,但是插入和刪除需要移動大量的資料,效率較低,適用於儲存經常查詢但很少插入和刪除的資料;
連結串列因為儲存在不連續的儲存空間內,所以查詢起來效率較低,但是插入或刪除資料不需要移動其他資料,故插入和刪除操作效率較高。適用於儲存經常插入和刪除而很少查詢的資料。

2.從尾到頭列印單鏈表

使用遞迴很容易就能實現逆序列印,程式碼如下:

void tail_to_head(ListNode* pList)
{
    if(pList==NULL)
    {
        printf
("%s",pList); return; } else tail_to_head(pList->next ); printf("->%d",pList->data ); }

用此函式雖然能夠實現從尾到頭列印單鏈表的目的,但是打印出來的結果最後面沒有換行符,如果我們在從尾到頭列印單鏈表的同時還需要列印其他東西,就會導致螢幕輸出的結果相當混亂,最後我想出來的解決辦法再寫一個函式,在此函式中呼叫上面的那個函式,然後輸出換行符。程式碼如下:

void tail_to_headPrintList(ListNode* pList)
{ tail_to_head(pList); printf("\n"); }

3.刪除一個無頭單鏈表的非尾節點

此函式與Erase函式(刪除指定節點)的不同之處是此函式沒有給出單鏈表的起始位置指標,而只給出了需要刪除的節點的指標pos,我們知道,要刪除pos,不僅要釋放pos所指向的空間,而且要讓pos的前一個節點的next指標指向pos的下一個節點,但是此函式只給出了pos指標,我們沒有辦法找到pos的前一個節點,就無法直接刪除pos,但是通過pos,我們可以刪除pos的後一個節點,我們只需將pos後一個節點的內容挪動到pos指向的節點裡,再刪除pos的下一個節點,就相當於刪除了pos。由於此函式在操作過程中要刪除pos的下一個節點,所以pos不能為尾節點。
程式碼如下:

void Non_tail_Erase(ListNode* pos)
{
    ListNode* ptmp;
    assert(pos&&(pos->next ));
    pos->data =pos->next->data ;
    ptmp=pos->next;
    pos->next =pos->next ->next ;
    free(ptmp);
}

4.在無頭單鏈表的一個節點前插入一個節點

和第三題一樣,我們無法直接在pos的前面插入一個節點,但是我們可以在pos的後面插入一個節點,然後把pos的資料挪過去,然後在pos原來的位置上寫入新的資料就好了。程式碼如下:

void Insert2(ListNode* pos, DataType x)
{
    ListNode* p=malloc(sizeof(ListNode));
    p->data =pos->data ;
    p->next =pos->next ;
    pos->data =x;
    pos->next =p;
}

5.單鏈表實現約瑟夫環

約瑟夫環是一個數學的應用問題:已知n個人(以編號1,2,3…n分別表示)圍在一張圓桌周圍,以編號為k的人開始報數數到m的那個人出列,他的下一個人有從1開始報數,數到m的人又出列,直至圓桌周圍的人全部處理完。我們讓單鏈表最後一個節點指向第一個節點,就可以模擬實現約瑟夫環問題了。程式碼如下:

ListNode* JosephCircle(ListNode* pList,int k,int m)
{
    int i;
    assert(pList);
    for(i=1;i<k;i++)
        pList=pList->next ;
    //使pList向後挪動k-1個節點,指向第k個節點
    while(1)
    {
        ListNode* ptmp;
        if(pList==pList->next )
            return pList;//只有一個節點時,返回這個節點的指標
        for(i=1;i<m;i++)
            pList=pList->next ;
        //使pList向後挪動m-1個節點,指向報數為m的節點
        ptmp=pList;//使ptmp指向報數為m的節點
        while(pList->next !=ptmp)
            pList=pList->next ;//迴圈一圈,使pList指向ptmp的前一個節點
        pList->next =ptmp->next ;
        //使報數為m的前一個節點指向m的後一個節點
        free(ptmp);//刪除報數為m的節點
        pList=pList->next ;
        //使pList指向m的下一個節點,作為下一輪迴圈的第一個節點
    }
}

6.逆置/反轉單鏈表

若單鏈表為空或只有一個節點,直接返回,若單鏈表含有多個節點:
1、將第一個節點的next指標指向NULL
2、將以後每個節點的next指標指向前一個節點
程式碼如下:

void ReverseList(ListNode** ppList)
{
    ListNode* pnext;
    ListNode* ptmp;
    assert(ppList);
    if((*ppList==NULL)||((*ppList)->next ==NULL))
        return;//連結串列為空或只有一個節點,直接返回
    pnext=(*ppList)->next ;
    //*ppList指向第一個節點,pnext指向第二個節點
    (*ppList)->next =NULL;//使第一個節點的next指向NULL
    while(pnext)//使以後每個節點的next指向前一個節點
    {
        ptmp=pnext->next;//儲存第三個節點位置
        pnext->next =*ppList;//使第二個節點next指向第一個節點
        *ppList=pnext;//*ppList挪動到第二個節點
        pnext=ptmp;//pnext挪動到第三個節點
    }
}

7.單鏈表排序(氣泡排序&快速排序)

在這裡我只寫了從小到大的排序,從大到小方法是一樣的。
氣泡排序從第一個節點開始處理,比較第一個和第二個的值,如果第一個節點大於第二個節點,交換他們的值,然後處理第二個節點和第三個節點,知道處理完倒數第二個節點和最後一個節點時,所有資料中最大的值就到了最後一個節點,然後開始進行第二輪迴圈,由於最後一個節點已經是最大的值了,所以不用參加第二輪迴圈。
程式碼如下:

void BubbleSortList(ListNode* pList)
{
    ListNode* p1=pList;
    ListNode* p2=NULL;
    if(NULL==pList)
        return;
    while(p1->next !=p2)
    {
        while(p1->next !=p2)
        {
            if((p1->data )>(p1->next ->data ))
            {
                DataType tmp=p1->data ;
                p1->data =p1->next ->data ;
                p1->next ->data =tmp;
                //如果p1節點的值大於下一個節點的值,交換它們的位置
            }
            p1=p1->next ;//p1指向下一個節點
        }/*迴圈結束時,最大的一個數被移動到了此次迴圈的
        最後一個節點,p1指向p2前一個節點*/
        p2=p1;//p2向前挪動一個節點
        p1=pList;//p1重新指向第一個節點
    }
}

快速排序是選取序列中的一個數作為基準數,把比它小的數都放在它左邊,比它大的數都放在它右邊,然後再對這兩部分依次進行快速排序,直至每部分只有一個元素。
程式碼如下:

void QuickSortList(ListNode* pList)
{
    if((NULL==pList)||(NULL==pList->next ))
        return;//如果連結串列為空或只有一個節點,直接返回
    else
    {
        ListNode* pkey=pList;//將第一個節點的值設為基準數
        ListNode* p=pList->next;
        while(p)//從第二個節點開始遍歷
        {
            if(p->data<pkey->data)
            {//如果p的值小於關鍵字,將它放在基準數的左邊
                DataType tmp=pkey->data;
                pkey->data =p->data;//p的值放到pkey
                p->data =pkey->next->data;//pkey後一個節點的值放到p
                pkey->next->data =tmp;//pkey的值往後挪動一格
                pkey=pkey->next;//pkey指向跟隨pkey的值往後移動一格,始終指向關鍵字
            }
            p=p->next;
        }
        //迴圈結束後pkey左邊的值都小於基準數,右邊的值都大於基準數
        p=pkey->next;
        pkey->next=NULL;//通過將pkey的next指標置為空將連結串列拆為兩部分
        QuickSortList(pList);
        QuickSortList(p);//對兩部分分別進行快速排序
        pkey->next=p;//重新將連結串列拼在一起
    }
}

8.合併兩個有序連結串列,合併後依然有序

ListNode* MergeList(ListNode* p1,ListNode* p2)
{
    ListNode* pList;
    if(NULL==p1)//p1為空返回p2
        return p2;
    if(NULL==p2)//p2為空返回p1
        return p1;
    if(p1->data<p2->data)
    {
        pList=BuyNode(p1->data);
        p1=p1->next;
    }
    else
    {
        pList=BuyNode(p2->data);
        p2=p2->next;
    }
    while(p1&&p2)
    {
        if(p1->data<p2->data)
        {
            PushBack(&pList,p1->data);
            p1=p1->next;
        }
        else
        {
            PushBack(&pList,p2->data);
            p2=p2->next;
        }
    }
    //出此迴圈後,p1和p2至少有一個指向空
    while(p1)
    {
        PushBack(&pList,p1->data);
        p1=p1->next;
    }
    while(p2)
    {
        PushBack(&pList,p2->data);
        p2=p2->next;
    }
    return pList;
}

9.查詢單鏈表的中間節點,要求只能遍歷一次連結串列

此題可用快慢指標解決,即建立兩個指標一起玩後走,一個每次移動一個節點,一個每次移動兩個節點,這樣快指標到達連結串列尾時,慢指標剛好指向連結串列的中間節點。
程式碼如下:

ListNode* FindMidList(ListNode* pList)
{
    ListNode* pfast=pList;
    if(NULL==pList)
        return NULL;//如果單鏈表為空,返回NULL
    while(pfast&&pfast->next)
    {
        pList=pList->next;
        pfast=pfast->next->next;
    }
    return pList;
}

10.查詢單鏈表的倒數第k個節點,要求只能遍歷一次連結串列

建立兩個指標,一個先往後移動k個節點,然後一起往後移動,先走的到達NULL時,後面那個剛好指向倒數第k個。
程式碼如下:

ListNode* InverseList(ListNode* pList,int k)
{
    ListNode* ptail=pList;
    while(k--)
    {
        ptail=ptail->next;
    }
    while(ptail)
    {
        pList=pList->next;
        ptail=ptail->next;
    }
    return pList;
}