單鏈表原地逆置
給定一個帶頭結點的單鏈表,編寫演算法將其原地逆置。所謂“原地”是指空間複雜度為O(1)。有兩種方法,頭插法和冒泡法。這兩種方法的時間複雜度均為O(n)。
頭插法
思路
我們知道,用頭插法建立連結串列,得到的連結串列中元素的順序和出入的順序相反,所以利用這一特點,可以將連結串列的逆置。
給定一個帶頭結點的單鏈表L,如下圖所示。
首先用指標p儲存連結串列第一個結點,然後將頭結點從連結串列中剝離下來,如下圖所示,此時連結串列L只有一個頭結點。
另設一指標r儲存p的後繼,將p指向的結點N1用尾插法插入到連結串列L中,
此時p指向N2,儲存p的後繼N3,再將N2尾插到連結串列L中,
以此類推,直至儲存後繼的指標r為空,退出迴圈。
頭插法的實現程式碼
void Reverse_L1(Linklist L) { /* p 為工作指標, r 為 p 的後繼, 以防斷鏈. */ LNode *p, *r; /* 從第一個元素開始. */ p = L->next; /* 先將頭結點剝離出來. */ L->next = NULL; /* 依次將元素摘下. */ while (p != NULL) { /* 暫存 p 的後繼. */ r = p->next; /* 用頭插法插入 p. */ p->next = L->next; L->next = p; /* 更新 p, 指向下一個結點. */ p = r; } return; }
冒泡法
我把這種方法稱為“冒泡法”,是因為演算法流程和氣泡排序類似,只不過在氣泡排序中是相鄰的元素出現逆序才交換,而連結串列逆置則要求每對結點之間都要交換順序。
思路
冒泡法和頭插法不同,頭插法只有一個工作指標p指向一個操作物件——被摘下來的結點,以及儲存其後繼的指標r。而冒泡法有兩個工作指標,即一對工作指標,以及儲存其後繼的指標r,共計3個指標。考慮如下一般情況,
指標pre和p分別指向兩個結點,將其看作一對結點,它們是每次迴圈操作的物件。迴圈中,讓N2的後繼指向N1,即完成了(N1,N2)的逆置。之後三個指標進一,pre=p,p=r,r=r->next,重複上述逆置操作,連結串列變成了下圖。
顯而易見,如果指標r!=NULL
,則迴圈還要繼續下去,若r==NULL
,迴圈結束,連結串列逆置完成,而指標p指向逆置後的一個元素。
上述步驟發生的前提是連結串列中除了頭結點以外,至少有兩個結點,而為了將判斷是否繼續迴圈和是否進入迴圈結合起來,應將指標r初始置為指向第一個結點的後繼,p指向第一個結點,即p=L->next,r=p->next
,指標pre無強制要求。
注意初始p指向的結點在完成逆置之後將成為最後一個結點,所以應該在逆置之前將p的後繼置為空,否則逆置之後p的後繼會指向倒數第二個結點,而倒數第二個結點的後繼指向倒數第一個結點,即逆置前的p,兩個結點之間形成環。
冒泡法的實現程式碼
void Reverse_L2(Linklist L)
{
/**
* 三個指標, p 為要反轉的結點, pre 為 p 前面的結點, r 是儲存 p 後繼的指標.
* 初始狀態 p 指向第一個元素, r 指向第二個元素.
*/
LNode *pre = L, *p = L->next, *r = p->next;
/**
* 將要第一個結點後繼連結斷開. 因為它將成為逆置後連結串列的最後一個結點, 否則
* 將在逆置後的連結串列中的最後一個元素和倒數第二個元素之間形成環.
*/
p->next = NULL;
/**
* 如果 r 不為空, 三個指標前進一個單位.
* 否則連結串列只有一個結點, 直接執行 while 後的 L->next = p.
* 迴圈中將 p 反轉指向其前面的結點 pre, r 是 p 的後繼.
* 假設連結串列有 n 個元素, 則迴圈可將前 n - 1 個元素逆置,
* 之後 p 指向第 n 個元素.
*/
while (r != NULL)
{
/* 指標依次進一. */
pre = p;
p = r;
r = r->next;
/* 逆置. */
p->next = pre;
}
/* 頭結點指向逆置之後的第一個結點. */
L->next = p;
return;
}