題目總覽
- 從尾到頭打印單鏈表
- 刪除一個無頭單鏈表的非尾節點
- 在無頭單鏈表的一個節點前插入一個節點
- 單鏈表實現約瑟夫環
- 逆置/反轉單鏈表
- 單鏈表排序(冒泡排序&快速排序)
- 合併兩個有序鏈表,合併後依然有序
- 查找單鏈表的中間節點,要求只能遍歷一次鏈表
- 查找單鏈表的倒數第n個節點,要求只能遍歷一次鏈表
思路及代碼
從尾到頭打印單鏈表
遞歸法,若當前節點不爲空,在打印當前節點前打印上一個節點。
void ConversePrint(ListNode* pList)
{
if (pList != NULL)
{
ConversePrint(pList->next);
printf("%d ", pList->data);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
刪除一個無頭單鏈表的非尾節點
將當前節點的內容改爲下個節點的內容,釋放下個節點,讓當前節點指向下下個節點。
void popNode(ListNode* pList)
{
if (pList != NULL)
{
ListNode* tmp = pList->next;
pList->data = pList->next->data;
pList->next = pList->next->next;
free(tmp);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
在無頭單鏈表的一個節點前插入一個節點
新建一個節點保存當前節點的內容,並指向下一個節點。
將當前節點改爲插入的內容,指向新節點。
void InsertBefore(ListNode* pList, DataType x)
{
assert(pList);
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = pList->data;
newNode->next = pList->next;
pList->next = newNode;
pList->data = x;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
單鏈表實現約瑟夫環
- 接收一個環形鏈表的頭指針地址,將頭指針改爲剩下的最後一個節點。
- 處理空鏈表。
- 用一個節點指針指向頭節點,當前節點的下一節點不是當前節點(節點數大於一),循環,當前節點爲第一個節點,走 n-1 步,指向第n個節點,刪除當前節點。
- 將初始頭指針賦值最後節點。
void Joseph(ListNode** ppList, int n)
{
assert(ppList);
if (*ppList == NULL)return;
ListNode* pList = *ppList;
while (pList->next != pList)
{
int i;
for (i = 1; i < n; i++)
pList = pList->next;
popNode(pList);
}
*ppList = pList;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
逆置/反轉單鏈表
- 頭插法,建立新鏈表,用 cur 指向舊鏈表頭結點。
- 循環,用 tmp 保存要拿下來的原鏈表頭結點 cur, cur 指向下一節點,將 tmp 放到新鏈表的頭部(tmp的下一節點指向新鏈表頭結點,再將新鏈表的頭結點改爲 tmp),直到 cur 爲 NULL。
- 將原鏈表頭指針改爲新鏈表。
void ReverseList(ListNode** ppList)
{
assert(ppList);
ListNode* newList = NULL;
ListNode* cur = *ppList;
while (cur)
{
ListNode* tmp = cur;
cur = cur->next;
tmp->next = newList;
newList = tmp;
}
*ppList = newList;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
單鏈表排序(冒泡排序)
- 接收一個普通單鏈表,處理空鏈表和只有一個節點的情況。
- 用 tail 指示一次冒泡的終點,第一次要全排,賦值爲NULL。
- 循環冒泡比較,每次比較(交換)後,cur 和 next 都指向它們的下一個節點,直到 next == tail。交換中,修改交換標記 exchange。
- 外層循環,用 cur 表示比較的第一個節點,用 next 表示比較的下一個節點,exchange 記錄是否交換,一趟比較沒有交換,說明已排好,直接返回。每趟比較之後,此次最後一個值已經爲最大,tail 前移一個節點。
void ListBubbleSort(ListNode* pList)
{
if (pList == NULL || pList->next == NULL)return;
ListNode* tail = NULL;
while (tail != pList)
{
ListNode* cur = pList;
ListNode* next = pList->next;
int exchange = 0;
while (next != tail)
{
if (cur->data > next->data)
{
DataType num = cur->data;
cur->data = next->data;
next->data = num;
exchange = 1;
}
cur = next;
next = next->next;
}
if (exchange == 0)
return;
tail = cur;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
合併兩個有序鏈表,合併後依然有序
方法一
- 新鏈表段接法
- 接收兩個鏈表指針,處理有空鏈表的情況。
- 創建新鏈表頭指針,指向兩鏈表頭節內值點較小的的節點。
- 循環,節點小的指針後移,直到節點下一節點大於另一指針或節點爲空,tmp 保存下一節點,當前節點指向另一指針,當前指針賦值爲 tmp,如果當前指針爲 NULL,說明合併完成,結束循環。
- 返回新鏈表頭指針。
- 可以總是讓 p1 指向小的,p2 指向大的,循環挪 p1,接完後再把小的給 p1,大的給 p2。可以不用比較。(原方法 else 是複製 if 後,1 和 2 換,其實更簡單,看起來多)
思路圖
ListNode* MergeList(ListNode* List1, ListNode* List2)
{
if (List1 == NULL)return List2;
if (List2 == NULL)return List1;
ListNode* newList = List1->data <= List2->data ? List1 : List2;
while (true)
{
if(List1->data <= List2->data)
{
while (List1->next != NULL && List1->next->data <= List2->data)
{
List1 = List1->next;
}
ListNode* tmp = List1->next;
List1->next = List2;
List1 = tmp;
if (List1 == NULL)
break;
}
else
{
while (List2->next != NULL && List2->next->data <= List1->data)
{
List2 = List2->next;
}
ListNode* tmp = List2->next;
List2->next = List1;
List2 = tmp;
if (List2 == NULL)
break;
}
}
return newList;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
方法二
- 新鏈單節點表尾插法
- 新建 newList = NULL, 循環,每次比較兩指針內容,保存小的節點,小節點指針後移,小節點尾接到 newList。
- 如果有一個指針爲 NULL,另一個指針尾接到 newList,返回 newList。
方法三
- 介於上兩個方法之間,以頭結點小的爲 newList,另一個鏈表每次拿下一個節點接到 newList 的合適位置,直到其中一個指針爲空。
查找單鏈表的中間節點,要求只能遍歷一次鏈表
- 快慢指針法,初始快指針、慢指針都是頭指針。
- 循環,如果快指針能走兩步(快指針的下一個節點和下下一個節點不爲空),快指針走兩步,慢指針走一步。
- 返回慢指針。
ListNode* FindMidNode(ListNode* pList)//mid = total>>1
{
ListNode* fast = pList;
while (fast->next != NULL && fast->next->next != NULL)
{
fast = fast->next->next;
pList = pList->next;
}
return pList;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
查找單鏈表的倒數第n個節點,要求只能遍歷一次鏈表
- 當節點數少於n時,NULL。
- 快慢指針法,創建返回指針 ret ,快指針先走 n-1 步(此時 ret 爲快指針的倒數第 n 個節點)或到 NULL 爲止。
- 循環,快指針的下一個節點不爲空,快指針和 ret 都向後走一步。
- 返回返回指針。
ListNode* FindFromLast(ListNode* pList, int n)
{
ListNode* ret = pList;
while (--n)
{
if(ret == NULL)return NULL;
pList = pList->next;
}
while (pList->next != NULL)
{
ret = ret->next;
pList = pList->next;
}
return ret;
}