1. 程式人生 > >連結串列-------常見題型(面試題)

連結串列-------常見題型(面試題)

1.從尾頭到列印單鏈表

void PrintReverse(ListNode *first)
{
	ListNode *end = NULL;
	while (end != first)
	{
		ListNode *cur = first;
		
		//找到要列印的結點
		while (cur->next != end)
		{
			cur = cur->next;
		}
		printf("%d  ", cur->data);
		end = cur;
	}
	printf("\n");
}

用遞迴的方法求解:

a1    a2    a3    ........    an

遞迴:

  • a1 結果已知
  • a(k+1) 的結果可以通過 a(k) 結果推匯出來
//遞迴
void PrintReverseRecursion(ListNode *first)
{
	if (first->next == NULL){
		printf("%d  ", first->data);
	}
	else{
		PrintReverseRecursion(first->next);
		//連結串列中除了 first 之外的所有結點都逆序列印了
		printf("%d  ", first->data);
	}
}

2.逆轉/反轉單鏈表

傳入連結串列,進行逆置/反轉,返回逆置/反轉後的連結串列的第一個結點地址,原連結串列將無效 1-->2-->3-->4-->5 5-->4-->3-->2-->1 

頭刪---->頭插

ListNode *ReverseList(ListNode *first)
{
	ListNode *cur = first;
	ListNode *node;
	ListNode *result = NULL;

	while (cur != NULL)
	{
		//從原來連結串列中頭刪(光摘下來,沒有真正刪除)
		node = cur;
		cur = cur->next;

		//node 就是被摘下來的結點
		node->next = result;
		result = node;
	}
	return result;
}

方法二: 

不調整值的位置,直接改變連結串列指標指向的方向。即:

1-->2-->3-->4-->5-->NULL NULL<--1<--2<--3<--4<--5

ListNode *ReverseList2(ListNode *first)
{
	ListNode *p1 = NULL;
	ListNode *p2 = first;
	ListNode *p3 = first->next;

	while (p2 != NULL)
	{
		p2->next = p1;

		p1 = p2;
		p2 = p3;
		if (p3 != NULL)
		{
			p3 = p3->next;
		}
	}

	return p1;
}

3.刪除一個無頭單鏈表的非尾節點(不能遍歷連結串列)

void RemoveNoFirst(ListNode *pos)
{
	pos->data = pos->next->data;
	ListNode *del = pos->next;
	pos->next = pos->next->next;

	free(del);
}

4.在無頭單鏈表的一個節點前插入一個節點(不能遍歷連結串列)

void InsertNoFirst(ListNode *pos, DataType data)
{
	ListNode *cur = pos;
	cur->next = pos->next;
	pos->next = cur;

	pos->data = data;
}

5.約瑟夫環

ListNode *JosephCycle(ListNode *first, int k)
{
	//第一步,連結串列構成環
	ListNode *tail = first;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	//tail就是最後一個節點
	tail->next = first;

	//第二步
	ListNode *cur = first;
	//結束條件是連結串列中只有一個結點
	while (cur->next == NULL){
		ListNode *prev = NULL;
		for (int i = 0; i < k - 1; i++){
			prev = cur;
			cur = cur->next;
		}

		//cur就是我們要刪除的結點
		prev->next = cur->next;
		free(cur);

		//讓迴圈繼續
		cur = prev->next;
	}

	cur->next = NULL;
	return cur;

}

6.合併兩個有序連結串列,合併後依然有序(升序)

ListNode* MergeOrderedList(ListNode *list1, ListNode *list2)
{
	assert(list1);
	assert(list2);

	ListNode *p1 = list1;
	ListNode *p2 = list2;
	ListNode *result = NULL;

	while (p1 != NULL && p2 != NULL)
	{
		if (p1->data < p2->data){
			ListPushBack(&result, p1->data);
			p1 = p1->next;
		}
		else{
			ListPushBack(&result, p2->data);
			p2 = p2->next;
		}
	}

	// 一個連結串列為空了
	if (p1 == NULL) {
		while (p2 != NULL){
			ListPushBack(&result, p2->data);
			p2 = p2->next;
		}
	}

	if (p2 == NULL) {
		while (p1 != NULL){
			ListPushBack(&result, p1->data);
			p1 = p1->next;
		}
	}

	return result;
}

優化:

ListNode * MergeOrderedListAd(ListNode *list1, ListNode *list2)
{
	ListNode *cur1 = list1;
	ListNode *cur2 = list2;
	ListNode *result = NULL;  //結果連結串列
	ListNode *tail = NULL;    //結果連結串列的最後一個結點,方便尾插
	ListNode *next;           //儲存遍歷過程的下一個結點
	ListNode *node;

	while (cur1 != NULL && cur2 != NULL)
	{
		if (cur1->data <= cur2->data)
		{
			node = cur1;
		}
		else
		{
			node = cur2;
		}
		next = node->next;
		if (result != NULL)
		{
			//連結串列不為空,則做尾插處理
			tail->next = node;
		}
		else
		{
			//儲存連結串列的下一個結點,讓迴圈繼續
			result = node;
		}

		node->next = NULL;
		//儲存新的最後一個結點
		tail = node;

		if (node == cur1)
		{
			cur1 = next;
		}
		else
		{
			cur2 = next;
		}

		//一個連結串列空了
		if (cur1 == NULL)
		{
			tail->next = cur2;
		}
		if (cur2 == NULL)
		{
			tail->next = cur1;
		}

		return result;
	}
}

7.求兩個已排序單鏈表中相同的資料。

void Unionset(ListNode *list1, ListNode *list2)
{
	ListNode *cur1 = list1;
	ListNode *cur2 = list2;

	while (cur1 != NULL && cur2 != NULL)
	{
		if (cur1->data < cur2->data)
		{
			cur1 = cur1->next;
		}
		else if (cur1->data > cur2->data)
		{
			cur2 = cur2->next;
		}
		else
		{
			printf("%d  ", cur1->data);
			cur1 = cur1->next;
			cur2 = cur2->next;
		}
	}

	printf("\n");
}

另一種方法:

void UnionsetDup(ListNode *list1, ListNode *list2)
{
	ListNode *cur1 = list1;
	ListNode *cur2 = list2;
	DataType data;

	while (cur1 != NULL && cur2 != NULL)
	{
		if (cur1->data < cur2->data)
		{
			cur1 = cur1->next;
		}
		else if (cur1->data > cur2->data)
		{
			cur2 = cur2->next;
		}
		else
		{
			printf("%d  ", cur1->data);
			data = cur1->data;

			while (cur1 != NULL && cur1->data == data)
			{
				cur1 = cur1->next;
			}
			while (cur2 != NULL && cur2->data == data)
			{
				cur2 = cur2->next;
			}
		}
	}

	printf("\n");
}

8.查詢單鏈表的中間結點,要求只遍歷一遍

void FindMid(ListNode *first)
{
	ListNode *fast = first;
	ListNode *slow = first;

	while (1)
	{
		fast = fast->next;
		if (fast == NULL)
		{
			break;
		}
		fast = fast->next;
		if (fast == NULL)
		{
			break;
		}
		slow = slow->next;
	}
	printf("%d\n", slow->data);
}

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

void FindTailK(ListNode *first, int k)
{
	ListNode *forward = first;
	ListNode *backward = first;

	while (k--)
	{
		forward = forward->next;
	}

	while (forward != NULL)
	{
		forward = forward->next;
		backward = backward->next;
	}

	printf("%d\n", backward->data);
}

10.列印連結串列

void PrintResult(ListNode* result)
{
	ListNode *cur;

	for (cur = result; cur != NULL; cur = cur->next)
	{
		printf("%d->", cur->data);
	}
	printf("NULL\n");
}

11.測試函式和主函式:

//測試
void TestPrintReverse()
{
	ListNode *first = NULL;
	ListPushBack(&first, 1);
	ListPushBack(&first, 2);
	ListPushBack(&first, 3);
	ListPushBack(&first, 4);
	ListPushBack(&first, 5);

	printf("倒序列印:");
	PrintReverse(first);
	printf("遞迴:");
	PrintReverseRecursion(first);
	printf("\n");

	ListNode *result = ReverseList(first);
	printf("逆置列印:");
	PrintResult(result);

	ListNode *sur = JosephCycle(first, 4);
	printf("約瑟夫環:%d \n", sur->data);

	ListNode *list1 = NULL;
	ListPushBack(&list1, 1);
	ListPushBack(&list1, 2);
	ListPushBack(&list1, 3);
	ListPushBack(&list1, 5);
	ListPushBack(&list1, 7);

	ListNode *list2 = NULL;
	ListPushBack(&list2, 4);
	ListPushBack(&list2, 6);
	ListPushBack(&list2, 8);
	ListPushBack(&list2, 9);

	printf("合併連結串列:");
	PrintResult(MergeOrderedList(list1, list2));

	printf("找相同資料:");
	ListNode *list3 = NULL, *list4 = NULL;
	ListPushBack(&list3, 1);
	ListPushBack(&list3, 3);
	ListPushBack(&list3, 4);
	ListPushBack(&list3, 5);

	ListPushBack(&list4, 1);
	ListPushBack(&list4, 2);
	ListPushBack(&list4, 4);

	Unionset(list3, list4);
	UnionsetDup(list3, list4);

	printf("找中間結點:");
	FindMid(first);

	printf("倒數第k個結點:");
	FindTailK(list1, 3);

}

int main()
{
	TestPrintReverse();
	return 0;
}

11.附:連結串列實現的原始檔"List.c"

註明:上面的函式實現呼叫了這裡的函式

#include <stdlib.h>
#include <assert.h>

typedef int DataType;

typedef struct ListNode {
	DataType data;
	struct ListNode *next;
}	ListNode;

// 初始化/銷燬
void ListInit(ListNode **ppFirst)
{
	assert(ppFirst != NULL);
	*ppFirst = NULL;
}

void ListDestroy(ListNode **ppFirst)
{
	// TODO:
	*ppFirst = NULL;
}

// 增刪查改
static ListNode * CreateNode(DataType data)
{
	ListNode *newNode = (ListNode *)malloc(sizeof(ListNode));
	assert(newNode);
	newNode->data = data;
	newNode->next = NULL;

	return newNode;
}

//頭插
void ListPushFront(ListNode **ppFirst, DataType data)
{
	assert(ppFirst != NULL);
	// 考慮特殊情況,連結串列為空 *ppFirst == NULL

	// 正常情況
	// 1. 指標 vs 指向空間;從堆上申請空間
	ListNode *newNode = CreateNode(data);
	newNode->next = *ppFirst;

	*ppFirst = newNode;
}

//尾插
void ListPushBack(ListNode **ppFirst, DataType data)
{
	ListNode *newNode = CreateNode(data);

	// 特殊情況,找倒數第一個 -> 至少有一個,所以特殊情況是連結串列為空
	if (*ppFirst == NULL) {
		*ppFirst = newNode;
		return;
	}

	// 通常情況
	ListNode *cur = *ppFirst;
	while (cur->next != NULL) {
		cur = cur->next;
	}

	// cur 就是最後一個結點
	cur->next = newNode;
}

// 刪除
//頭刪
void ListPopFront(ListNode **ppFirst)
{
	assert(ppFirst != NULL);	// 變數地址不為 NULL
	assert(*ppFirst != NULL);	// 不能是空連結串列

	ListNode *del = *ppFirst;
	*ppFirst = del->next;

	free(del);	// 謹記
}


void ListPopBack(ListNode **ppFirst)
{
	assert(ppFirst != NULL);	// 變數地址不為 NULL
	assert(*ppFirst != NULL);	// 不能是空連結串列

	// 連結串列中只有一個結點
	if ((*ppFirst)->next == NULL) {
		free(*ppFirst);
		*ppFirst = NULL;
		return;
	}

	// 正常情況
	ListNode *del;
	ListNode *cur = *ppFirst;

	while (cur->next->next != NULL) {
		cur = cur->next;
	}

	del = cur->next;
	cur->next = NULL;
	free(del);
}


// 查詢
ListNode * ListFind(ListNode *first, DataType data)
{
	// 順序查詢,去遍歷
	for (ListNode *cur = first; cur != NULL; cur = cur->next) {
		if (data == cur->data) {
			return cur;
		}
	}

	return NULL;
}

// 在結點前做插入(結點 pos 肯定在連結串列中 && pos 不是空【連結串列不是空】)
void ListInsert(ListNode **ppFirst, ListNode *pos, DataType data)
{
	// 頭插
	if (*ppFirst == pos) {
		ListPushFront(ppFirst, data);
		return;	// 記得 return,否則下面加上 else
	}

	ListNode *cur = *ppFirst;
	// 找 pos 的前一個結點
	while (cur->next != pos) {
		cur = cur->next;
	}

	// 插入新結點
	ListNode *newNode = CreateNode(data);	// 一定要申請空間
	newNode->next = cur->next;	// or pos;
	cur->next = newNode;
}

// 刪除指定結點(結點 pos 肯定在連結串列中 && pos 不是空【連結串列不是空】
void ListErase(ListNode **ppFirst, ListNode *pos)
{
	// 頭刪
	if (*ppFirst == pos) {
		ListPopFront(ppFirst);
		return;	// 記得 return,否則下面加上 else
	}

	ListNode *cur = *ppFirst;
	// 找 pos 的前一個結點
	while (cur->next != pos) {
		cur = cur->next;
	}

	cur->next = pos->next;
	free(pos);	// 記得
}

void Test()
{
	ListNode *first;
	ListInit(&first);	// 傳值 1. first	傳地址 2. &first
	// 連結串列是一條空連結串列, first == NULL

	// first 會變化
	ListPushBack(&first, 1);

	// 以後 first 不變了
	ListPushBack(&first, 2);
	ListPushBack(&first, 3);

	ListNode *result = ListFind(first, 2);	// 傳地址 or 傳值?
	ListInsert(&first, result, 0);
}