單鏈表常見面試題(基礎篇)

1.比較順序表和鏈表的優缺點,說說它們分別在什麼場景下使用?
2.從尾到頭打印單鏈表
3.刪除一個無頭單鏈表的非尾節點
4.在無頭單鏈表的一個節點前插入一個節點
5.單鏈表實現約瑟夫環
6.逆置/反轉單鏈表
7.單鏈表排序(冒泡排序&快速排序)
8.合併兩個有序鏈表,合併後依然有序
9.查找單鏈表的中間節點,要求只能遍歷一次鏈表

10.查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表 


1、比較順序表和鏈表的優缺點,說說它們分別在什麼場景下使用?

給順序表設定容量的時候,如果每次分配的太小則要不斷地申請內存來給它使用,如果太大了則會造成內存浪費。假如當一個順序表每次增容100個字節,現在順序表裏剛剛存了100個數據,如果要是再想增加一個數據,那麼就要再申請100個字節的內存,而現在只需要一個,則剩下的99個字節就會被浪費。

在順序表裏如果要是前插一個數,就要先把後面的數據全部向後移動一位,然後再插入,這樣做會大大增加時間複雜度。

但是順序表在查找起來則比鏈表方便許多!鏈表在大量增加數據時比較方便!


2.從尾到頭打印單鏈表 

要將單鏈表從後向前打印輸出有很多種方法,這裏我們用常規的方法和遞歸來寫一下

遞歸

void Printinverse(ListNode *plist)//從尾到頭打印鏈表
{
	ListNode *tmp = plist;
	while(tmp->next != NULL)
	{
		Printinverse(tmp->next);
		break;
	}
	printf("%d->", tmp->data);
}
常規

void test(ListNode *plist)
{
	ListNode *tail = NULL;//創造一個尾節點,當遍歷到尾時,讓tail想起移位,直到收尾相等
	while(plist != tail)
	{
		ListNode *tmp = plist;
		while(tmp->next != tail)
		{
			tmp = tmp->next;
		}
		tail = tmp;
		printf("%d->", tail->data);
	}
}

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

要刪除一個

void EraseNonHead(ListNode *pos)//.刪除一個無頭單鏈表的非尾節點
{//1->2->3->4->null
	assert(pos);
	ListNode *next = pos->next;
	if(next != NULL)
	{
		pos->next = next->next;
		pos->data = next->data;
	}
	free(next);
	next = NULL;
}


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

void InsertNonHead(ListNode *pos, Datatype x)//無頭鏈表插入
{//1->2->3->4->NULL
	assert(pos);
	ListNode *cur = pos->next;
	ListNode *tmp = BuyNode(x);
	pos->next = tmp;
	tmp->next = cur;
	Datatype data = pos->data;
	pos->data = tmp->data;
	tmp->data = data;
}

5.單鏈表實現約瑟夫環 

先來看看什麼是約瑟夫環


void JosephRing(ListNode **pplist, int x, int y, int z)//總共x個人,從第y個開始數,每次數z個人
{
	int i = 0;
	assert(pplist);
	ListNode *tmp = NULL;
	for(i=1; i<=x; i++)
	{
		PushBack(pplist, i);
	}
	tmp = Find(*pplist, x);
	tmp->next = *pplist;
	while(--y)
	{
		*pplist = (*pplist)->next;
	}
	while(*pplist != (*pplist)->next)
	{	
		int count = z;
		while(--count)
			*pplist = (*pplist)->next;
		printf("%d ", (*pplist)->data);
		EraseNonHead(*pplist);
	}
	printf("\n");
	printf("%d\n",(*pplist)->data);
}

6.逆置/反轉單鏈表 
逆置一個單鏈表和從尾到頭打印不一樣,打印不改變節點的位置,只是將數據反着打印一遍,而逆置就是要改變節點的位置

可以先創建一個空節點,然後從第一個開始每次在單鏈表上拿一個節點,前插給創建的節點,單鏈表的頭結點往後移,創建的新節點往前移。當單鏈表爲空的時候,也就逆置完了。

ListNode *reverseList(ListNode **pplist)
{//1、當鏈表爲空或者只有一個節點的時候,直接返回 2、多個節點
	if((*pplist == NULL) || ((*pplist)->next == NULL))
	{
		return *pplist;
	}
	else
	{
		ListNode *newlist = NULL;
		ListNode *cur = *pplist;
		while(cur)
		{
			ListNode *tmp = cur;
			cur = cur->next;
			tmp->next = newlist;
			newlist = tmp;
		}
		return newlist;
	}
}

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

定義一個頭結點和尾節點,冒泡停止條件爲首節點的下個節點不爲尾節點,每次比較頭結點和下一個幾點,當頭結點大於下一個節點時交換數據,然後頭結點向後移位,直到頭結點的下個節點爲尾節點時,第一次排序完成然後把尾節點向前移動一位。

在冒泡的時候要注意什麼時候停下了,每次交換的次數!


ListNode *BubbleList(ListNode *plist)
{/*定義一個頭結點和尾節點,冒泡停止條件爲首節點的下個節點不爲尾節點,每次比較頭結點和下一
個幾點,當頭結點大於下一個節點時交換數據,然後頭結點向後移位,直到頭結點的下個節點爲
尾節點時,第一次排序完成然後把尾節點向前移動一位。*/
	if(plist == NULL || plist->next == NULL)
	{
		return plist;
	}
	ListNode *tail = NULL;
	ListNode *head = plist;
	while(head->next != tail)
	{
		ListNode *tmp = head;
		while(tmp->next != tail)
		{
			ListNode *next = tmp->next;
			if(tmp->data >= next->data)
			{
				Datatype k = tmp->data;
				tmp->data = next->data;
				next->data = k;
			}
			tmp = next;
		}
		tail = tmp;
	}
	return head;
}

8.合併兩個有序鏈表,合併後依然有序 
要合併兩個有序的鏈表就先把兩個鏈表頭結點的較小者先拿出來,然後頭結點向後移,依次比較他們的頭結點,不斷地往新鏈表的後面插,直到有一個爲空的時候就把另一個鏈表連接到新鏈表後面


ListNode *Merge(ListNode *plist1, ListNode *plist2)//摘節點法,創建新鏈表
{//鏈表爲空,兩個鏈表中只有一個有數據, 都有數據
	if(plist1 == NULL && plist2 == NULL)//當全爲空時返回任意一個
	{
		return plist1;
	}
	if(plist1 == NULL && plist2 != NULL)//一個爲空就返回另一個
	{
		return plist2;
	}
	if(plist1 != NULL && plist2 == NULL)
	{
		return plist1;
	}
	else
	{
		ListNode *newlist = NULL;
		ListNode *cur = NULL;
		if(plist1->data >= plist2->data)//先判斷兩個頭結點哪一個大,把小的那個頭結點給新鏈表的頭
		{
			cur = plist2;
			plist2 = plist2->next;
		}
		else
		{
			cur = plist1;
			plist1 = plist1->next;
		}
		newlist = cur;//保存新鏈表的頭節點
		while(plist1 != NULL && plist2 != NULL)//當兩個鏈表都不爲空時合併
		{
			if(plist1->data <= plist2->data)//把小的節點往新鏈表的後面串,直到有一個爲空
			{
				cur->next = plist1;
				plist1 = plist1->next;
			}
			else
			{
				cur->next = plist2;
				plist2 = plist2->next;
			}
			cur = cur->next;
		}
		if(plist1 == NULL)//當其中一個爲空之時,另一個鏈表直接連到新鏈表的後面
			cur->next = plist2;
		if(plist2 == NULL)
			cur->next = plist1;
		return newlist;
	}
}

9.查找單鏈表的中間節點,要求只能遍歷一次鏈表 
如果有兩個人,一個人走路一次走一步,另一個一次走兩步,則走兩步的人走過的步數一定時走一步人步數的兩倍。那麼就利用這個原理定義兩個節點,一個快節點一次走兩步,慢節點一次走一步,當快節點走完後,慢節點就是所要求的中間節點,但是要考慮當鏈表的節點個數爲奇數和偶數的時候。


ListNode *SearchmidNode(ListNode *plist)
{
	assert(plist);
	ListNode *slow = plist;
	ListNode *fast = plist;
	while(fast->next)
	{
		if(fast->next->next == NULL)//如果爲偶數個,在快指針的下下步爲空的時候只讓慢的走一步就返回
		{
			slow = slow->next;
			break;
		}
		slow = slow->next;
		fast = (fast->next)->next;
	}
	return slow;
}

10.查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表 

求倒數第k個節點時,和上面的問題分析一樣,當慢指針和快指針在沒有走之前就讓兩個相距K的距離,然後再一起走,當快節點走到最後一個時,慢節點的位置恰好就是倒數第K個節點
ListNode *LastKNode(ListNode *plist, Datatype k)
{
	assert(plist);
	ListNode *slow = plist;
	ListNode *fast = plist;
	while(--k)
		fast = fast->next;
	while(fast->next)
	{
		slow = slow->next;
		fast = fast->next;
	}
	return slow;
}




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