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;
}