連結串列-------常見題型(面試題)
阿新 • • 發佈:2018-12-13
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);
}