點擊進入查看如何實現鏈表以及鏈表的一些基本操作函數
基於單鏈表的面試題——進階篇
1.比較順序表和鏈表的優缺點,說說它們分別在什麼場景下使用?
首先我們時間上來進行分析:
(1)對於順序表。不論是靜態的還是動態的,他們都是連續的存儲空間,在讀取上時間效率比較快,可以通過地址之間的運算來進行訪問,但是在插入和刪除操作會出現比較麻煩的負載操作。
(2)對於鏈表,因爲他是鏈式存儲。在我們需要的時候纔在堆上開闢空間,對於插入查找的方式比較便攜。但是對於遍歷的話需要多次的空間跳轉。
其次,我們從空間申請方式來看:
(1)順序表的空間開闢是在滿的時候進行多空間的申請開闢。往往存在着2^n的開闢原則。在開闢次數比較多的時候,會出現比較大的空間浪費。
(2)鏈表的空間開闢是針對於單個節點的空間開闢訪問,不存在多餘的空間浪費。並且在碎片內存池的機制下。可以有效地利用空間。
通過上面的總結,我們可以知道順序表和鏈表的不同主要是空間上和時間上的優勢與缺陷的不同。可以分析出順序表往往用於查找遍歷操作比較頻繁的情況下使用。鏈表則針對於數據刪除修改的多操作性上的情況下使用。
2.從尾到頭打印單鏈表
void reverse(ListNode* plist)
//要實現反過來輸出鏈表,我們每訪問到一個結點的時候
//先遞歸輸出它後面的結點,再輸出該結點自身
//問題:當鏈表非常長的時候,就會導致函數調用的層級很深
//從而有可能導致函數調用棧溢出。
{
if (NULL == plist)
{
printf("NULL");
return;
}
reverse(plist->next);
printf("<-%d", plist->data);
}
3.刪除一個無頭單鏈表的非尾節點
void DelHeadlessTail(ListNode *pos)//刪除無頭鏈表的非尾結點
//由於鏈表的單向性,我們只能知道一個結點所指向的下一個結點
//我們可以把問題想象成是此結點與後一個結點交換
//只要把兩個結點的數據域交換,把結點指向後一個結點所指向的結點
{
assert(pos);
ListNode *ppos = pos->next;
pos->data = ppos->data;
pos->next = ppos->next;
free(ppos);
ppos = NULL;
}
4.在無頭單鏈表的一個節點前插入一個節點
void InsertNotHeadNode(ListNode *pos,DataType num)
//4.在無頭單鏈表的一個節點前插入一個節點
//實現原理
//A->pcur->B->C.....
//A->pcur->new->B->C.....
//A->new->pcur->B->C.....
{
assert(pos);
ListNode *tmp = InitList(num);
ListNode *ppos = pos; //記住原來節點的位置
tmp->next = pos->next;
pos->next = tmp;
tmp->data = pos->data;
pos->data = num;
}
5.單鏈表實現約瑟夫環
問題描述:約瑟夫問題的一種描述是:編號爲1,2,…,n的n個人按順時針方向圍坐一圈,每人持一個密碼(正整數)。一開始任選一個正整數作爲報數上限值m,從第一個人開始按順時針方向自1開始順序報數,報到m時停止報數。報m的人出列,將他的密碼作爲新的m值,從他在順時針方向上的下一個人開始重新從1報數,如此下去,直至所有人全部出列爲止。
void YueSeFu(ListNode *plist, DataType num)//約瑟夫環問題,報數爲num的人出局
{
assert(plist);
ListNode *cur = plist;
ListNode *tmp = plist;
if (plist->next == NULL)
{
return;
}
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = tmp;//環形鏈表
cur = plist;
while (cur->next != cur)//頭不等於尾,也就是剩下1個人就終止循環
{
DataType n = num;
while (--n)
{
tmp = cur;
cur = cur->next;
}
tmp->next = cur->next;//刪除cur也就是報數爲num的人
printf("出局的爲%d\n", cur->data);
free(cur);
cur = tmp->next;//頭往後走,也就是下一個人開始報數
}
printf("剩下的是%d\n",cur->data);
}
6.逆置/反轉單鏈表
只要是修改頭指針則必須傳遞頭指針的地址,否則傳遞頭指針值即可(即頭指針本身)。這與普通變量類似,當需要修改普通變量的值,需傳遞其地址,否則傳遞普通變量的值即可(即這個變量的拷貝)。使用二級指針,很方便就修改了傳入的結點一級指針的值。 如果用一級指針,則只能通過指針修改指針所指內容,卻無法修改指針的值,也就是指針所指的內存塊。
void ReverseList(ListNode **pplist) //逆序單鏈表
{
ListNode *cur = *pplist;//cur當前結點
ListNode *prev = NULL;//前一個結點
ListNode *prear = NULL;//後一個結點
if (NULL == *pplist || NULL == (*pplist)->next)
return;
while (cur)
{
prear = cur->next;
cur->next = prev;
prev = cur;
cur = prear;
}
*pplist = prev;
}
7.單鏈表排序(冒泡排序&快速排序)
void SortList(ListNode *plist)//冒泡排序
{
if ((plist == NULL) || (plist->next == NULL))
{
return;
}
int exchange = 0;
ListNode *tail = NULL;
while (tail != plist->next)
{
ListNode *cur = plist;
ListNode *next = plist->next;
while (next != tail)
{
if (cur->data > next->data)
{
DataType num = cur->data;
cur->data = next->data;
next->data = num;
exchange = 1;
}
cur = cur->next;
next = next->next;
}
if (exchange == 0)//冒泡優化
{
break;
}
tail = cur;
}
}
8.合併兩個有序鏈表,合併後依然有序
ListNode *MergeList(ListNode *list, ListNode *plist)//合併有序鏈表並輸出有序
{//歸併法(尾插法)
if (list == NULL)//如果鏈表1爲空,則直接返回鏈表2的頭指針
{
return list;
}
else if (plist == NULL)//如果鏈表2爲空,則直接返回鏈表1的頭指針
{
return list;
}
else
{
//找出新鏈表的頭結點
ListNode *head = NULL;
if (list->data < plist->data)
{
head = list;
list = list->next;
}
else
{
head = plist;
plist = plist->next;
}
//尾插
ListNode *tail = head;
while (list && plist)
{
if (list->data < plist->data)
{
tail->next = list;
list = list->next;
}
else
{
tail->next = plist;
plist = plist->next;
}
tail = tail->next;
}
if (list)//循環結束,一個鏈表爲空,頭指針直接指向非空鏈表的開始結點
{
tail->next = list;
}
else
{
tail->next = plist;
}
return head;
}
}
9.查找單鏈表的中間節點,要求只能遍歷一次鏈表
ListNode *MidNode(ListNode *list)//查找單鏈表的中間節點,要求只能遍歷一次鏈表
{
//快慢指針問題,快指針每次走兩步,慢指針走一步。
//快指針停止時,慢指針剛好指向中間結點
//鏈表個數爲偶數時,輸出中間兩個結點的後一個,也就是slow
//當面試官說把兩個中間值都輸出來,那就把Slow和slow,這裏用大寫區分
//要輸出中間值的第一個,那就更改循環條件爲fast&&fast->next->next
ListNode *slow = list;
//ListNode *Slow = list;
ListNode *fast = list;
while (fast&&fast->next)//while (fast&&fast->next->next)
//要注意邊界問題
{
//Slow = slow; //Slow比slow後一個位置
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
10.查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表
//先讓快指針走N步,然後快指針不爲空的情況下,快慢指針依次向後挪動1位,始終差距N
ListNode* CountBackwards(ListNode *list, DataType num)
//查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表
{
ListNode *slow = list;
ListNode *fast = list;
while (--num)
{
fast = fast->next;
if (fast == NULL)
{
return NULL;
}
}
while (fast->next)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
提供全部代碼,包括所有測試:
singlelinkedlist.h頭文件
#ifndef __SINGLELINKEDLIST_H__
#include<stdio.h>
#include<windows.h>
#include<assert.h>
typedef int DataType;
typedef struct ListNode
{
DataType data;
struct ListNode *next;
}ListNode;
ListNode *InitList(DataType num);//初始化並賦值
void PushBack(ListNode **pplist, DataType num);//尾插
void PrintList(ListNode *plist);//輸出
void PopBack(ListNode **pplist);//尾刪
void PushFront(ListNode **pplist, DataType num);//頭插
void PopFront(ListNode **pplist);//頭刪
ListNode* Find(ListNode *plist, DataType num);//查找
void Insert(ListNode** pplist, ListNode* pos, DataType x);//插入
void Erase(ListNode** pplist, ListNode* pos);//刪除
void reverse(ListNode* pplist);//從尾到頭打印鏈表
void DelHeadlessTail(ListNode* pos);//刪除非尾結點
void InsertNotHeadNode(ListNode *pos, DataType num);//在一個節點前插入一個節點
void YueSeFu(ListNode *plist, DataType num);//約瑟夫環問題
void ReverseList(ListNode **pplist);//逆序單鏈表
void SortList(ListNode *plist);//冒泡排序
ListNode *MergeList(ListNode *list, ListNode *plist);//(尾插法)合併有序鏈表並輸出有序
ListNode *MidNode(ListNode *list);//查找單鏈表的中間節點,要求只能遍歷一次鏈表
ListNode* CountBackwards(ListNode *list, DataType num);//查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表
#endif//__SINGLELINKEDLIST_H__
singlelinkedlist.c實現部分
#include "singlelinkedlist.h"
ListNode *InitList(DataType num)//定義一個新的結點
{
ListNode *node = (ListNode*)malloc(sizeof(ListNode));
node->data = num;
node->next = NULL;
return node;
}
void PushBack(ListNode **pplist, DataType num)//尾插
{
if (*pplist == NULL)//空鏈表
{
*pplist = InitList(num);//定義一個結點
}
else if ((*pplist)->next == NULL)//只有一個結點
{
(*pplist)->next = InitList(num);
}
else//正常情況(多個結點)
{
ListNode *tail = *pplist;
while (tail->next)
{
tail = tail->next;//依次指向下一個結點,找到爲空的尾結點
}
tail->next = InitList(num);//找到以後直接添加一個結點
}
}
void PrintList(ListNode *plist)//打印鏈表
{
ListNode *tail = plist;
while (tail)
{
printf("%d->", tail->data);
tail = tail->next;
}
printf("NULL");
printf("\n");
}
void PopBack(ListNode **pplist)//尾刪
{
if (*pplist == NULL)//空鏈表
{
return;
}
else if ((*pplist)->next == NULL)//只有一個結點,直接釋放
{
free(*pplist);
*pplist = NULL;
}
else
{
ListNode* tail = *pplist;
ListNode* pos = tail;
while (tail->next)//tail指向pos的後一個結點
{
pos = tail;
tail = tail->next;
}
free(tail);//釋放最後一個結點就相當於刪除了尾結點
tail = NULL;
pos->next = NULL;
}
}
void PushFront(ListNode **pplist, DataType num)//頭插
{
if (*pplist == NULL)//空鏈表
{
*pplist = InitList(num);
}
else
{
ListNode *tmp = InitList(num);//開闢一個新的結點
tmp->next = *pplist;//讓它指向原先的開始結點
*pplist = tmp;//pplist依然開始結點
}
}
void PopFront(ListNode **pplist)//頭刪
{
if (*pplist == NULL)//空鏈表
{
return;
}
else if ((*pplist)->next == NULL)//只有一個結點
{
*pplist = NULL;
}
else
{
ListNode *tmp = (*pplist)->next;//tmp指向原先頭結點指向的下一個位置
free(*pplist);
*pplist = tmp;
}
}
ListNode* Find(ListNode *plist, DataType num)//查找
{
assert(plist);//斷言其是否爲空鏈表
while (plist)
{
if (plist->data == num)
{
return plist;
}
plist = plist->next;
}
return NULL;
}
void Insert(ListNode** pplist, ListNode* pos, DataType num)//插入
{
assert(*pplist&&pos);
if (((*pplist)->next == NULL) || (pos == *pplist))
//只有開始結點或者是要插入的正好在開始結點的前面
{
PushFront(pplist, num);
}
else
{
ListNode* tmp = NULL;
ListNode* tail = *pplist;
while (tail->next != pos)
{
tail = tail->next;
}
tmp = InitList(num);
tail->next = tmp;
tmp->next = pos;
}
}
void Erase(ListNode** pplist, ListNode* pos)//刪除
{
assert(*pplist&&pos);
if (((*pplist)->next == NULL) || (*pplist == pos))
{
PopFront(pplist);
}
else
{
ListNode* tmp = *pplist;
while (tmp->next != pos)
{
tmp = tmp->next;
}
tmp->next = pos->next;
free(pos);
pos = NULL;
}
}
void reverse(ListNode* plist)
//要實現反過來輸出鏈表,我們每訪問到一個結點的時候
//先遞歸輸出它後面的結點,再輸出該結點自身
//問題:當鏈表非常長的時候,就會導致函數調用的層級很深
//從而有可能導致函數調用棧溢出。
{
if (NULL == plist)
{
printf("NULL");
return;
}
reverse(plist->next);
printf("<-%d", plist->data);
}
void DelHeadlessTail(ListNode *pos)//刪除無頭鏈表的非尾結點
//由於鏈表的單向性,我們只能知道一個結點所指向的下一個結點
//我們可以把問題想象成是此結點與後一個結點交換
//只要把兩個結點的數據域交換,把結點指向後一個結點所指向的結點
{
assert(pos);
ListNode *ppos = pos->next;
pos->data = ppos->data;
pos->next = ppos->next;
free(ppos);
ppos = NULL;
}
void InsertNotHeadNode(ListNode *pos,DataType num)
//4.在無頭單鏈表的一個節點前插入一個節點
//實現原理
//A->pcur->B->C.....
//A->pcur->new->B->C.....
//A->new->pcur->B->C.....
{
assert(pos);
ListNode *tmp = InitList(num);
ListNode *ppos = pos; //記住原來節點的位置
tmp->next = pos->next;
pos->next = tmp;
tmp->data = pos->data;
pos->data = num;
}
void YueSeFu(ListNode *plist, DataType num)//約瑟夫環問題,報數爲num的人出局
{
assert(plist);
ListNode *cur = plist;
ListNode *tmp = plist;
if (plist->next == NULL)
{
return;
}
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = tmp;//環形鏈表
cur = plist;
while (cur->next != cur)//頭不等於尾,也就是剩下1個人就終止循環
{
DataType n = num;
while (--n)
{
tmp = cur;
cur = cur->next;
}
tmp->next = cur->next;//刪除cur也就是報數爲num的人
printf("出局的爲%d\n", cur->data);
free(cur);
cur = tmp->next;//頭往後走,也就是下一個人開始報數
}
printf("剩下的是%d\n",cur->data);
}
void ReverseList(ListNode **pplist) //逆序單鏈表
{
ListNode *cur = *pplist;//cur當前結點
ListNode *prev = NULL;//前一個結點
ListNode *pnext = NULL;//後一個結點
if (NULL == *pplist || NULL == (*pplist)->next)
return;
while (cur)
{
pnext = cur->next;
cur->next = prev;
prev = cur;
cur = pnext;
}
*pplist = prev;
}
void SortList(ListNode *plist)//冒泡排序
{
if ((plist == NULL) || (plist->next == NULL))
{
return;
}
int exchange = 0;
ListNode *tail = NULL;
while (tail != plist->next)
{
ListNode *cur = plist;
ListNode *next = plist->next;
while (next != tail)
{
if (cur->data > next->data)
{
DataType num = cur->data;
cur->data = next->data;
next->data = num;
exchange = 1;
}
cur = cur->next;
next = next->next;
}
if (exchange == 0)//冒泡優化
{
break;
}
tail = cur;
}
}
ListNode *MergeList(ListNode *list, ListNode *plist)//合併有序鏈表並輸出有序
{//歸併法(尾插法)
if (list == NULL)//如果鏈表1爲空,則直接返回鏈表2的頭指針
{
return list;
}
else if (plist == NULL)//如果鏈表2爲空,則直接返回鏈表1的頭指針
{
return list;
}
else
{
//找出新鏈表的頭結點
ListNode *head = NULL;
if (list->data < plist->data)
{
head = list;
list = list->next;
}
else
{
head = plist;
plist = plist->next;
}
//尾插
ListNode *tail = head;
while (list && plist)
{
if (list->data < plist->data)
{
tail->next = list;
list = list->next;
}
else
{
tail->next = plist;
plist = plist->next;
}
tail = tail->next;
}
if (list)//循環結束,一個鏈表爲空,頭指針直接指向非空鏈表的開始結點
{
tail->next = list;
}
else
{
tail->next = plist;
}
return head;
}
}
ListNode *MidNode(ListNode *list)//查找單鏈表的中間節點,要求只能遍歷一次鏈表
{
//鏈表個數爲偶數時,輸出中間兩個結點的後一個,也就是slow
//當面試官說把兩個中間值都輸出來,那就把Slow和slow,這裏用大寫區分
//要輸出中間值的第一個,那就更改循環條件爲fast&&fast->next->next
ListNode *slow = list;
//ListNode *Slow = list;
ListNode *fast = list;
while (fast&&fast->next)//while (fast&&fast->next->next)
//要注意邊界問題
{
//Slow = slow; //Slow比slow後一個位置
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* CountBackwards(ListNode *list, DataType num)
//查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表
{
ListNode *slow = list;
ListNode *fast = list;
while (--num)
{
fast = fast->next;
if (fast == NULL)
{
return NULL;
}
}
while (fast->next)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
test.c測試部分
#include "singlelinkedlist.h"
void test()
{
ListNode *list = NULL;
PushBack(&list, 1);
PushBack(&list, 2);
PushBack(&list, 3);
PushBack(&list, 4);
PrintList(list);
PopBack(&list);
PrintList(list);
PopBack(&list);
PrintList(list);
PopBack(&list);
PrintList(list);
PopBack(&list);
PrintList(list);
}
void test1()
{
ListNode *list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 3);
PushFront(&list, 4);
PushFront(&list, 5);
PrintList(list);
PopFront(&list);
PrintList(list);
PopFront(&list);
PrintList(list);
PopFront(&list);
PrintList(list);
PopFront(&list);
PrintList(list);
PopFront(&list);
PrintList(list);
}
void test2()
{
ListNode *list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 4);
PushFront(&list, 5);
PushFront(&list, 6);
PrintList(list);
ListNode *ret = Find(list, 2);
//測試使用
/*if (ret != NULL)
{
printf("%p\n", ret);
}
else
{
printf("沒有這個值!\n");
}*/
Insert(&list, ret, 3);
PrintList(list);
Erase(&list, ret);
PrintList(list);
}
void test3()
{
ListNode *list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 3);
PushFront(&list, 4);
PushFront(&list, 5);
PrintList(list);
reverse(list);//從尾到頭打印鏈表
printf("\n");
}
void test4()
{
ListNode *list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 3);
PushFront(&list, 4);
PushFront(&list, 5);
PrintList(list);
ListNode *ret = Find(list, 2);
DelHeadlessTail(ret);//刪除非尾結點
PrintList(list);
}
void test5()
{
ListNode *list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 4);
PushFront(&list, 5);
PushFront(&list, 6);
PrintList(list);
ListNode *ret = Find(list, 2);
InsertNotHeadNode(ret, 3);//在一個節點前插入一個節點
PrintList(list);
YueSeFu(list, 2);//約瑟夫環問題
}
void test6()
{
ListNode *list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 3);
PushFront(&list, 4);
PushFront(&list, 5);
PrintList(list);
ReverseList(&list);//單鏈表的逆置
PrintList(list);
}
void test7()
{
ListNode *list = NULL;
PushFront(&list, 3);
PushFront(&list, 2);
PushFront(&list, 5);
PushFront(&list, 4);
PushFront(&list, 8);
PrintList(list);
SortList(list);//冒泡排序
PrintList(list);
}
void test8()
{
ListNode *tmp = NULL;
PushFront(&tmp, 5);
PushFront(&tmp, 3);
PushFront(&tmp, 1);
PrintList(tmp);
ListNode *num = NULL;
PushFront(&num, 6);
PushFront(&num, 4);
PushFront(&num, 2);
PrintList(num);
ListNode *ret = MergeList(tmp, num);//尾插法
PrintList(ret);
}
void test9()
{
ListNode *list = NULL;
//PushFront(&list, 3);
//PushFront(&list, 2);
//PushFront(&list, 5);
//PushFront(&list, 4);
//PushFront(&list, 8);
//ListNode *ret = MidNode(list);//查找單鏈表的中間節點,要求只能遍歷一次鏈表
//printf("%d\n",ret->data);//鏈表個數如果是奇數個,輸出中間那個
PushFront(&list, 3);
PushFront(&list, 2);
PushFront(&list, 4);
PushFront(&list, 8);
ListNode *Ret = MidNode(list);//查找單鏈表的中間節點,要求只能遍歷一次鏈表
printf("%d\n", Ret->data);//鏈表個數如果是偶數個,輸出中間兩個結點的後一個
}
void test10()
{
ListNode *list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 3);
PushFront(&list, 4);
PushFront(&list, 5);
PrintList(list);
ListNode *ret = CountBackwards(list, 2);
printf("%d\n",ret->data);
}
int main()
{
//test();
//test1();
//test2();
//test3();
//test4();
//test5();
//test6();
//test7();
//test8();
//test9();
test10();
system("pause");
return 0;
}