1. 程式人生 > >關於連結串列面試題的一些理解

關於連結串列面試題的一些理解

    面試題一.刪除無頭單鏈表的非尾結點

    Del_Nottail(pLinkNode pos);

       顧名思義刪除非尾結點就是刪除的結點不是最後一個結點,之前在實現連結串列功能的時候就有一個函式是 Erase_LInkList  它的功能就是在連結串列中刪除指定位置的結點,乍一看這兩個函式沒有什仫區別可是仔細看就會發現我們要實現的函式功能沒有給出頭指標啊?這可怎仫辦?我們知道要刪除一個結點如果傳過來頭指標那就好辦了:只要遍歷連結串列就可以了,通過記住要刪除結點的前一個結點,再將這個結點的指標域指向要刪除結點的後一個結點,最後將要刪除的結點釋放就達成目標啦!如果沒有傳過來頭指標呢?如果此時我們還用之前那種遍歷的思路就會進入誤區,首先並沒有頭指標給我們去遍歷啊?此時我們是不是可以通過交換的方式呢?當然可以啦!在這裡我總結為四步刪除無頭單鏈表的非尾結點:
      1).建立指向結點的指標del,該指標指向要刪除結點的下一個結點;       2).將要刪除結點的資料域修改為下一個結點的資料;       3).將要刪除結點的指標域修改為下下一個結點的地址;       4).釋放del結點指標刪除該結點;       程式碼實現如下:
void Del_Nottail(pLinkNode pos)          //刪除無頭單鏈表的非尾結點
{
	pLinkNode del=pos->next;
	assert(pos != NULL);
	pos->data=pos->next->data;
	pos->next=pos->next->next;
	free(del);
	del=NULL;
}

      那仫程式碼是不是可以達到我們想要的結果呢?如果有一個單鏈表它的原始資料為1->2->3->4->5->over,此時我們刪除其中的一個非尾結點是不是可以達到目標呢?請看如下結果:             看3被刪除了吧!如果我們不小心刪除的是尾結點也就是最後一個結點呢?請看下圖:             看程式奔潰了吧!其實這正是達到了我們的目的,因為我們知道這道面試題的功能就是刪除非尾結點,你刪除了尾結點程式當然會奔潰了啊!當然現實中這種問題最好不要出現,因為當程式奔潰了人們自然想到程式出了問題,但是實際上呢?程式確實沒有問題,這是不是讓人糾結又鬱悶,所以最好避免這種問題的出現.在斷言時也要斷言pos->next,下面我們來看一道和它相關的連結串列面試題吧!
  面試題二.刪除倒數第k個結點,只允許遍歷一遍連結串列 Del_KNode(pLinkList plist,DataType k);          既然要刪除倒數第k個結點首先我們要找到這個倒數第k個結點然後刪除.那仫如何去尋找呢?在這裡我提供一種思路:建立兩個指標都指向單鏈表的第一個結點,使得後指標先不動,前指標先指向正數第k個結點,此時兩個指標一起移動,直到前指標為NULL,此時後指標指向的就是倒數第k個結點啦! 這就有點快慢指標的味道了,有興趣的同學可以自己下去畫圖理解;          接下來的問題就是如何刪除這個倒數第k個結點呢?如果我們使用 Erase_LinkList  的方法是不是也可以刪除第一個結點呢?我們知道 Erase_LinkList  它的思路就是要記住要刪除結點的前一個結點,如果使用這種思路正如之前所說的要是刪除第一個結點那仫它的前一個結點是什仫呢?自然是找不到的啦~~~~如果我們採用面試題一的方法呢?具體分析見面試題一,此時不論要刪除的是連結串列的哪一個結點都是可以刪除的啦~~~~~
void Del_KNode(pLinkList plist,DataType k)        //刪除倒數第k個結點(只允許遍歷一遍連結串列) 
{
	pLinkNode front=NULL;
	pLinkNode back=NULL;
	pLinkNode del=NULL;
	assert(plist);
	front=plist->phead;
	back=plist->phead;
	if(NULL == plist->phead)   //空連結串列
	{
		return ;
	}
	else
	{
		while(front)   //back指向倒數第k個結點
		{
			k--;
			if(k < 0)
			{
				back=back->next;
			}
			front=front->next;
		}
	}
	if(k <= 0)   //刪除back所指向的結點,借用Del_Nottail函式的方法
	{
		Del_Nottail(back);   
		//back->data=back->next->data;   //Del_Nottail的具體實現方法
		//del=back->next;
		//back->next=del->next;
		//free(del);
		//del=NULL;
	}
}

       普通的情況:刪除不是第一個結點的結點              特殊的情況:刪除第一個結點:                ~~~~   面試題三.查詢連結串列的中間結點(只允許遍歷一遍連結串列) pLinkNode Find_MidNode(pLinkList plist);       拿到這道問題我剛開始的想法就是如果能先遍歷一遍連結串列統計得到連結串列的總結點數,然後再次遍歷連結串列每當遍歷一個結點統計個數就加1直到和剛開始的總結點數/2相等,此時指標所指向的結點就是連結串列的中間結點;但是這種想法遍歷了兩遍連結串列並沒有滿足題意~~~        如果要滿足題意應該如何去做呢?在面試題二中我們在找倒數第K個結點時影影約約感覺到了快慢指標的身影,那仫如何用快慢指標的思路去解決查詢連結串列的中間結點問題呢?當然我們可以設定兩個指標快指標,慢指標,快指標每次走兩步,慢指標每次只走一步,當快指標指向NULL時慢指標就指向的是連結串列的中間結點啦~此時還存在一個問題如果單鏈表的結點個數是偶數呢?那仫我們返回前一個結點還是後一個結點呢?按理說兩個應該都可以的,關鍵看程式碼如何編輯了:
pLinkNode Find_MidNode(pLinkList plist)          //查詢連結串列的中間結點(只允許遍歷一遍連結串列)
//快慢指標的問題
{
	pLinkNode fast=NULL;
	pLinkNode slow=NULL;
	assert(plist);
	fast=plist->phead;
	slow=plist->phead;
	while(fast != NULL && fast->next->next != NULL)
		//如果連結串列是偶數,判斷條件為:fast->next->next則找到的前一箇中間結點,
		//若判斷條件為:fast->next,則找到的是後一箇中間結點
	{
		fast=fast->next->next;
		slow=slow->next;
	}
	return slow;
}

        返回前一箇中間結點:                  返回後一箇中間結點:          面試題四.逆序單鏈表 void Reverse_list(pLinkList plist);         看到逆序這兩個字相信大家都不會陌生的,在之前我們逆序字串和整形陣列的時候,我們的想法就是兩個指標一個指向第一個元素,一個指向最後一個元素,只要兩個指標不相遇,我們就交換兩個指標指向的內容;這樣的想法是不是可以化用到連結串列中去呢?我們可以很輕鬆的找到連結串列的尾結點,需要注意的是給出的是單鏈表:只允許從前往後遍歷,不允許從後往前遍歷.如果給出的是雙向迴圈連結串列這樣的思路就可以嘗試了,此時卻是萬萬不可以的;        那仫如何逆序連結串列呢?此時我們需要設定新的頭指標了,下面我總結了逆序連結串列的四步曲:~~        1).tmp儲存結點~cur指向下一個結點~        2).tmp指向新的頭指標~如果是第一個結點,剛開始的頭指標為NULL,即tmp->next=NULL~        3).新的頭結點向後移動,指向tmp~        4).逆序完成後要更新頭指標~
void Reverse_list(pLinkList plist)       //逆序連結串列
{
	pLinkNode newhead=NULL;
	pLinkNode tmp=NULL;
	pLinkNode cur=NULL;
	assert(plist);
	cur=plist->phead;
	while(cur != NULL)
	{
		tmp=cur;
		cur=cur->next;
		tmp->next=newhead;
		newhead=tmp;
	}
	plist->phead=newhead;   //更新頭
}

          面試題五.在當前結點前插入一個結點(無頭單鏈表)        void Insert_FrontNode(pLinkNode pos,DataType x);      這單題類似於我們之前實現的連結串列功能Insert_Front  唯一的區別就是此面試題沒有傳遞過來頭指標也就是說是不允許遍歷連結串列的,那仫如何刪除呢?方法類似面試題1,先為資料x開闢結點空間將該結點連線到當前結點pos之後,看到這裡大家或許會疑惑,題目不是要求插入到當前結點pos之前嗎?你插入到結點後面能達到目標嗎?我們知道題目中並沒有給出頭指標我們是不可能找到要插入結點的前一個結點的,可是我們將新結點插入到當前結點的後面就滿足題意了嗎?彆著急,先連線起來,接下來我們將當前結點pos資料域和新結點的資料域交換不就可以了嗎?不信???你繼續向後看~~~~
void Insert_FrontNode(pLinkNode pos,DataType x)   //無頭單鏈表前插入一個結點
{
	pLinkNode newNode=NULL;
	DataType tmp=0;
	assert(pos != NULL);
	newNode=Create_Node(x);
	newNode->next=pos->next;
	pos->next=newNode;
	tmp=newNode->data;
	newNode->data=pos->data;
	pos->data=tmp;
}

        我們是在資料域為3的結點前面插入結點資料域為6的結點:                   相信自己你就是最棒的!!!