文章還是關(guān)于鏈表,本次主要涉及幾個比較深入的問題:循環(huán)鏈表的判定、倒數(shù)第m個節(jié)點的數(shù)據(jù)獲取、多層次鏈表的設計、平鋪和取消平鋪。
/*單鏈表*/
typedef struct list
{
struct list *next;
int data;
} List_t, *List_handle_t;
/*雙向鏈表*/
typedef struct Dblist
{
struct Dblist *next;
struct Dblist *prev;
int data;
}DList_t, *DList_handle_t;
/*多層次鏈表*/
typedef struct mullevel_list
{
struct mullevel_list *prev;
struct mullevel_list *next;
struct mullevel_list *child;
int data;
}MList_t, *MList_handle_t;
關(guān)于鏈表主要還是搞清楚指針的相關(guān)問題。鏈表頭的更新操作也是指針操作的一部分,如何實現(xiàn)鏈表頭的自動更新也是需要注意的,如果每次都采用返回值的形式實現(xiàn)鏈表的頭的更新,這在實際的操作中非常的不放便,采用指向指針的指針實現(xiàn)鏈表頭的更新將是非常不錯的選擇。其實這也是內(nèi)存分配中經(jīng)常使用的技巧。
/*內(nèi)存分配*/
bool GetMemory(char ** str, int n)
{
*str = (char *) malloc(n);
if(*str)
return true;
return false;
}
/*鏈表頭的更新*/
bool insert_listnode(List_handle_t *head, int a)
{
List_handle_t newlistnode = (List_handle_t)malloc(sizeof(List_t)/sizeof(char));
if(*head == NULL && newlistnode != NULL)
{
*head = newlistnode;
newlistnode->data = a;
newlistnode->next = NULL;
return true;
}
else if(*head != NULL ** newlistnode != NULL)
{
newlistnode->data = a;
newlistnode->next = *head;
*head = newlistnode;
return true;
}
return false;
}
其中這種采用指向指針的指針的方式就能夠保證鏈表的自動更新,這種特性主要是C/C++中函數(shù)值傳遞的特性,形參不改變實參的值,但是當傳遞的是指針時,這時指針實質(zhì)上就是地址,作為參數(shù)時,地址并不會改變,但是地址中的內(nèi)容是可改變的,這是內(nèi)存分配問題中經(jīng)常使用的技巧,如上面的代碼所示。這種代碼的形式還有一些優(yōu)點,可以判斷判斷問題是否完成,通過判斷是否需要再次分配。
單鏈表的逆序問題:
逆序問題實質(zhì)上主要是完成每一個鏈表節(jié)點的操作,然后更新鏈表頭,這時需要三個指針,其中一個表示逆序鏈表的表頭,一個表示需要操作的節(jié)點,最后一個表示下一個即將操作的節(jié)點,也就是逆序操作需要保存三個節(jié)點才能保證一個逆序的完成。首先保證下一個即將操作的節(jié)點存在,然后實現(xiàn)逆序鏈表表頭與實際操作的節(jié)點進行指向操作,更新表頭。
bool reversed_list(List_handle_t *head)
{
List_handle_t mid ;
List_handle_t fir ;
List_handle_t last;
if(*head != NULL)
{
mid = last = head;
/*save the node next to be operated*/
fir = mid->next;
/*tail of the list*/
last->next = NULL;
while(fir != NULL)
{
/*get the node to be operated*/
mid = fir;
/*save the node next to be operated*/
fir = fir->next;
/*link to the head of list*/
mid->next = last;
/*update the head of list*/
last = mid;
}
/*return the actual list head*/
*head = last;
return true;
}
return false;
}
關(guān)于鏈表是否為循環(huán)鏈表的問題,這種問題是一個經(jīng)典的問題,因為鏈表操作實質(zhì)上就是指針的比較高級的操作。所以一般都需要依仗指針進行操作。如何判斷是否為循環(huán)呢?如果是像數(shù)組那種連續(xù)的內(nèi)存空間可以通過指針的值進行判斷,連續(xù)性就能使得指針的比較存在意義,但是鏈表是一個非連續(xù)的內(nèi)存空間,對指針進行比較就沒有任何的意義。通常采用快慢指針的形式進行判斷。
兩個指針,其中一個指針以每次一個對象的形式遍歷鏈表,另一個鏈表以每次多個對象的形式遍歷,如果是非循環(huán)的鏈表,那么快的指針會首先到達鏈表的尾部。但是如果是循環(huán)鏈表,這時快指針的遍歷速度快,因為存在循環(huán),就會存在快指針指向慢指針后面對象的時刻,如果快指針指向的對象就是慢指針指向的對象或者快指針的下一個對象就是慢指針指向的對象(這兩種情況都合適,這需要一句循環(huán)鏈表中的對象進行確定),就說明了鏈表是一個循環(huán)鏈表。快指針的訪問速度可以設置為每次兩個對象,這樣就能實現(xiàn)判斷。如下所示:
bool isTermination(List_handle_t list)
{
List_handle_t slow , fast;
slow = fast = list;
while(1)
{
if(!fast || !fast->next)
return false;
else
{
/*快指針以2倍速度循環(huán)*/
fast = fast->next->next;
/*慢指針以1倍速度循環(huán)*/
slow = slow->next;
if(fast == slow || fast->next == slow)
return false;
}
}
}
鏈表倒數(shù)m個節(jié)點的對象
這種問題的解決方式很多,但是如何保證復雜度上最小卻是一個重要的問題,最好是只遍歷一次鏈表就能找到對應的節(jié)點,實質(zhì)上采用類似于哨兵指針的形式就能實現(xiàn)。設置兩個指針,分別執(zhí)行鏈表頭和鏈表的第m個對象,然后兩個指針分別遍歷,當執(zhí)行第m個節(jié)點對象的指針指向了最后一個節(jié)點對象時,這時指向表頭的那個鏈表實質(zhì)上就指向了倒數(shù)第m個節(jié)點的對象。這個指向第m個節(jié)點的指針就起到了類似哨兵指針的作用。
List_handle_t findMlastnode(List_handle_t list, int m)
{
int n = 0;
List_handle_t temp = list;
List_handle_t mtemp = NULL;
if(temp != NULL)
{
/*find the mth node*/
while( temp != NULL && n != m)
{
temp = temp->next;
++ n;
}
if(n == m && temp != NULL)
{
/*point to the mth node*/
mtemp = temp;
/*point to the head*/
temp = list;
/*pass the list*/
while(mtemp->next != NULL)
{
mtemp = mtemp->next;
temp = temp->next;
}
return temp;
}
}
return NULL;
}
關(guān)于多層次鏈表的平鋪操作,因為多層次鏈表是類似于樹的結(jié)構(gòu),當然可以采用類似樹的遍歷形式進行平鋪,但是這種方式對節(jié)點的訪問形式往往都是多次遍歷。由于多層次的鏈表平鋪還需要取消平鋪操作,因此最好不要破壞每一個層次中的鏈接關(guān)系,如果破壞了每一層中的鏈接關(guān)系,就會使得每一層的還原操作非常復雜,我們可以按照層次逐層逐層的訪問。
多層次鏈表的平鋪最好的方式是充分利用尾節(jié)點,也就是將每一層的對象都接到平鋪鏈表的尾部,而且隨著平鋪鏈表長度的增長,下一層次的節(jié)點也能夠訪問到,這時候通過判斷節(jié)點是否存在子層,如果有就繼續(xù)添加到尾節(jié)點,這樣就能實現(xiàn)不同層次節(jié)點的平鋪。這種平鋪操作的優(yōu)點在于只遍歷了一次第一層的節(jié)點完成平鋪操作,而且沒有破壞每一層對象的鏈接關(guān)系,便于后期的還原。這種方法的關(guān)鍵在于如何控制鏈表的尾節(jié)點。
/*add sublevel listnode to the tail of first level*/
void appendtail(MList_handle_t head, MList_handle_t *tail)
{
MList_handle_t list = head;
/*update the list tail*/
(*tail)->next = head;
head->next = (*tail);
/*pass the node in this list*/
for(list; list->next != NULL; list= list->next);
/*updata the list tail*/
*tail = list;
}
void flattenList(MList_handle_t head, MList_handle_t *tail)
{
MList_handle_t list = head;
/*list will be growing*/
while(list)
{
if(list->child)
{
appendtail(list->child,tail);
}
list = list->next;
}
}
取消平鋪操作,主要是切斷每一層之間的前后鏈接關(guān)系,而保持子層鏈接關(guān)系,實質(zhì)上這可以采用遞歸的形式實現(xiàn),因為如果當前節(jié)點存在子節(jié)點,那么就將子節(jié)點的鏈接關(guān)系切斷,如果子節(jié)點也仍然存在子節(jié)點,那么先切斷子層的鏈接關(guān)系,因為平鋪沒有破壞每一層的鏈接關(guān)系,這樣只訪問第一層就能完成取消平鋪操作。實質(zhì)完成的操作就是講當前子節(jié)點的前一個節(jié)點的后一個節(jié)點設置為NULL,而將當前子節(jié)點的前一個對象設置為NULL,這樣就切斷了各層之間的關(guān)系。因為每一次切斷都會導致平鋪鏈表的縮短,當平鋪鏈表只有原始第一層的長度時,這時候就完成了鏈表的取消平鋪操作,當然仍然需要注意尾節(jié)點的管理問題。但是我們不能將取消平鋪操作直接設置成一個遞歸操作,平鋪操作最后肯定會管理鏈表尾,而子層與母層的鏈表斷裂關(guān)系并不需要設置鏈表尾。
void unflattensearch(MList_handle_t head)
{
MList_handle_t list = head;
while(list)
{
if(list->child)
{
/*break the link between two levels*/
list->child->prev->next = NULL;
list->child->prev = NULL;
/*break the link between other levels*/
unflattensearch(list->child);
}
/*next listnode*/
list = list->next;
}
}
/*************************************************************
this function can not be reserve
because the function must update tail
actual there is only one time to operate.
**************************************************************/
void unflattenList(MList_handle_t head, MList_handle_t * tail)
{
MList_handle_t list = head;
unflattensearch(list);
/*pass to the last of list*/
for(list; list->next; list = list->next);
/*update the tail of list*/
*tail = list;
}
總結(jié)
關(guān)于鏈表的操作我認為主要還是要設置恰當?shù)闹羔槪湵淼牟僮骶褪侵羔樀牟僮鳎且驗殒湵淼奶厥庑裕沟弥羔樀谋容^操作變得無效,但是可以通過指針的相等和不相等進行操作,設置哨兵指針等指針的重要操作。
同時需要注意鏈表是一個可能動態(tài)增長的對象,只要時刻理解這種動態(tài)特性就能比較好的理解鏈表中的多層次問題,平鋪過程就是利用了鏈表的動態(tài)增長過程,通過鏈表尾實現(xiàn)動態(tài)操作。而取消平鋪操作只是完成了切斷各層之間的連接關(guān)系而已,并不會更新鏈表尾,但是鏈表的長度也發(fā)生了動態(tài)變化。
把握鏈表的動態(tài)增長特性和指針的相關(guān)操作就能很好的完成鏈表的相關(guān)操作。