前言:
適逢妹紙在復習,順便見我也在學習,便問了這樣一個問題【她是怎麼想到這個的】。
有一個很多層的鏈表,嗯,雙向的吧,然後每個節點還有個指針指向了它的子鏈表,單獨的,如果有的話;同一層的子鏈表,嗯,也是雙向鏈接的吧;然後怎麼將它們比較快的變成只有一條主鏈表呢?關系還是保持不變的哦。對了,可以展開的話,順便也還原回去吧。。。
為了不在妹紙面前慫,毅然決然拿起紙幣就左圖右畫了起來,經過快二十分鐘的左移右移,加上點必要代碼,算是給妹紙解釋好了,差點就跪了。
過程:
首先是雙向的,那就好辦多了,之前遇到的很多問題都是單向的,先畫個多層鏈表示意圖好了;問妹紙是不是這樣的,她說差不多。。。

然後想一下以什麼樣的順序存放吧,可以是父子父子這樣一直連下去的,也可以是橫著過去,一行一行的放的,感覺一層一層來改變的順序要少一點,那就那樣吧。
肯定是要從頭開始遍歷的,所以時間復雜度最少是O(n);如果有子鏈表就把它追加到現有的尾指針那裡;那麼新的尾指針應該在哪裡呢?新加的那一個子節點?不,不是那個,因為我們要把整一層都放在一起,那樣的話,就要子鏈表的最後一個節點作為新的尾指針了,那樣一整塊就被追加過去了。
接著next下去繼續遍歷,同樣是有子鏈表就追加,原第一層的完了就從追加的節點繼續,然後一層一層就連起來了。

展開了,是時候考慮一下怎麼變回去了?!
逆向思維的話就是從尾部開始遍歷回去,反正是雙向的,但是從尾部開始的話,該怎麼判定這個節點是不是子節點呢?要確認的話估計只能從頭指針開始遍歷匹配是誰的子節點了,那樣務必要遍歷多次,不太科學;
也可以是用東西把所有的子節點都存起來,那樣從尾部遍歷就不用每次都遍歷了,但是又要多用東西耶,可能還有更好的方法;
要不還是從頭開始!
遍歷鏈表,如果有子鏈表就將它從主鏈表那裡切斷開來,但是又不能就這樣返回了,不然到了原來第一層那裡就沒後續節點了,那就只能分離第一層的子鏈表了;對了,那就遞歸,從子鏈表那繼續切斷,如果有它也有子鏈表的話。
那麼尾指針怎麼安放好呢?想想因為它會不斷從後面分離,不影響前面的,那就直接將遍歷到的最後一個作為尾指針好了,到時候就是到原第一層的末尾了。
既然想好了,那就試試實現吧。
代碼:
/*********************
Author:AnnsShadow
Time = 2015-12-03
*********************/
#include <stdio.h>
#include <stdlib.h>
//每個節點的定義
typedef struct chainNode
{
struct chainNode *next;
struct chainNode *previous;
struct chainNode *child;
int value;
} chainNode;
/**
為什麼tail要用指向指針的指針,因為要修改它啊!
不然在函數裡面改了影響不了原來的哦!
**/
//在鏈表末尾追加鏈接
void appendChain(chainNode *child, chainNode **tail);
//展開多層鏈表,使之成為一層
void expandChain(chainNode *head, chainNode **tail);
//將子鏈表從一層鏈表中分離
void seperateChild(chainNode *childHead);
//將一層鏈表還原成多層鏈表
void unexpandChain(chainNode *head, chainNode **tail);
int main()
{
chainNode *head, *tail;
//構造對應的測試多層鏈表
/**
Some codes;
**/
expandChain(head, &tail);
unexpandChain(head, &tail);
return 0;
}
void appendChain(chainNode *child, chainNode **tail)
{
//將當前指針賦值為傳入的子鏈表
chainNode *currentNode = child;
//原尾指針的下一個賦值為傳入的子鏈表
(*tail)->next = child;
//子鏈表的上一個賦值為原尾指針
child->previous = *tail;
//遍歷當前子鏈表直到最後
for(; currentNode->child; currentNode = currentNode->next);
//賦值產生新的尾指針
*tail = currentNode;
}
void expandChain(chainNode *head, chainNode **tail)
{
//將當前指針指向鏈表頭指針
chainNode *currentNode = head;
//當前指針不為NULL
while(currentNode)
{
//如果當前指針有子鏈表
if(currentNode->child)
{
//將其追加到鏈表的尾部
appendChain(currentNode->child, tail);
}
//指向下一個指針
currentNode = currentNode->next;
}
}
void seperateChild(chainNode *childHead)
{
//將當前指針賦值為子鏈表的頭指針
chainNode *currentNode = childHead;
//當前指針不為空
while(currentNode)
{
//如果當前指針有子鏈表
if(currentNode->child)
{
//將之前追加到指針‘下一個’的值取消
//也就是切斷鏈表
currentNode->child->previous->next = NULL;
//子鏈表從主鏈表分離
currentNode->child->previous = NULL;
//如果當前指針有子鏈表則繼續分離
seperateChild(currentNode->child);
}
//指向下一個指針
currentNode = currentNode->next;
}
}
void unexpandChain(chainNode *head, chainNode **tail)
{
//將當前指針賦值為主鏈表頭指針
chainNode *currentNode = head;
//開始分離
seperateChild(head);
//遍歷主鏈表到末尾
for(; currentNode->next; currentNode = currentNode->next);
//重新賦值尾指針
*tail = currentNode;
}
後記:
妹紙覺得答案挺滿意的,然後就問,要是是單向呢?
我就答,那就記住每次遍歷到哪裡好了;
再問,要是多層的結構是這樣的呢?

我再答:那樣連起來啊,也是有子鏈表就追加,而且可以一次追加整一層,不過要記住其中的有子鏈表的位置,那樣好像又有開銷了,诶,記住第一個就行了,反正一層都連起來了。
妹紙會心一笑。