题目:请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在复杂链表中,每个节点除了有一个m_pNext指针指向下一个节点外,还有一个m_pSibling指向链表中的任意节点或者NULL。节点的定义如下。
typedef struct ComplexListNode_
{
char m_nValue;
ComplexListNode* m_pNext;
ComplexListNode* m_pSibling;
}ComplexListNode;
分析:这个问题的重点在于,如何正确的复制m_pSibling这个随机指针。要知道,我们原先复制简单链表的时候,只是关心链表中的m_pNext指针所指向的节点,也就是说,在新的副本链表中,我们只要把各个节点的副本按照m_pNext指针给出的关系串联起来即可。可以说,链表之所以称为链表,就是依赖于m_pNext指针给出的这层拓扑关系。然而随机指针在原链表中并没有严格的先行后续关系,也对链表的拓扑关系没有任何的影响。所以我们如果还是先按m_pNext指针给出的关系复制一个副本链表,再在新的副本链表中找出m_pSibling关系的话,那么每次定位一个节点的m_pSibling指针,就要从原链表及其副本链表的头部开始顺序的找过去。就和原书中描述的一样,这是一个O(N^2)的算法。
问题当然已经得到解决了,但是显然存在优化的空间。下面给出两种思路。
思路1(原书的解法):就地复制,然后解开(Unwind)两个链表。我们首先设原链表的节点依次为A-B-C...,副本链表的节点依次为a-b-c...。原链表如图1(即下图)所示。
这种思路的具体做法如下所述:
1.对于原链表中的每一个节点A,我们将节点A的副本a放在A的下一个位置,即A->m_pNext为a,并且a->m_pNext为A在原链表中的下一个节点B,这样就可以保持原链表的连续性,维持原链表所依赖的由m_pNext指针提供的拓扑关系,此时我们并未对m_pSibling指针的关系进行复制,但也没有破坏这层关系,如图2(即下图);
2.随后再参照原链表节点之间的m_pSibling关系,对副本节点进行操作,形成的新链表我们成为纠缠链表,如图3(即下图);
3.最后从纠缠链表的头部开始遍历,将这个复制后的链表解开,形成两个链表,一个为原链表,另一个就是副本链表,具体的技巧是,第奇数个节点属于原链表,第偶数个节点属于副本链表,如图4(即下图)。
思路1最大的问题不是效率问题,而是安全性问题。如果我们的待复制链表作为一个const的输入参数,那么我们是不能在原先的结构上进行任何更改的。因此我们就需要另外一种方法来解决这种不能原地更改的情况。
思路2:使用一个地址表,第一列存储原链表中每个节点的地址,第二列中存储副本链表中对应的每个节点的地址。具体操作时,可以先按照m_pNext指针给出的关系顺序复制副本链表,并在复制副本链表的过程中,记录下原链表与副本链表中每一个节点的地址,并将其按照一一对应的关系存入地址表的同一行中。在复制结束后,我们再从两个链表的头结点开始遍历,每次遇到不为空的m_pSibling指针时,我们就可以通过查表来找到副本节点应当指向的位置,其实就是将地址表第二列中对应的地址值赋给m_pSibling变量。
这样做的最大问题就是,查表的过程将会直接的影响整个算法的时间复杂度。如果我们只使用简单的二维数组来实现这个地址对应关系表的话,那么每次查表都要消耗O(N)的时间(无论怎么设计这个表)。为了能使查表的时间复杂度尽量降低,我们可以使用哈希的方式来实现这个地址对应关系表,这样做的缺点也是显而易见的,那就是大量的空间开销。
思路讲了半天,下面给出按照两种思路的实现。下面是算法一:
ComplexListNode* Clone_1(ComplexListNode* L){
ComplexListNode* pCloneList = NULL;
ComplexListNode* pT = L;
ComplexListNode* pNew = NULL;
while(pT != NULL){
if((pNew = (ComplexListNode*)malloc(sizeof(ComplexListNode))) == NULL)
return NULL; //Not safe. Will change the original list in this case.
pNew->m_nValue = pT->m_nValue + 0x20;
pNew->m_pSibling = NULL;
pNew->m_pNext = pT->m_pNext;
pT->m_pNext = pNew;
pT = pNew->m_pNext;
}
pT = L;
while(pT != NULL){
pNew = pT->m_pNext;
if(pT->m_pSibling != NULL)
pNew->m_pSibling = pT->m_pSibling->m_pNext;
pT = pNew->m_pNext;
}
pT = L;
pCloneList = L->m_pNext;
while(pT != NULL){
pNew = pT->m_pNext;
//pNew definitely would NOT be NULL now.
pT->m_pNext = pNew->m_pNext;
if(pT->m_pNext != NULL)
pNew->m_pNext = pT->m_pNext->m_pNext;
pT = pT->m_pNext;
pNew = pNew->m_pNext;
}
return pCloneList;
}
下面是算法二:
ComplexListNode* Clone_2(const ComplexListNode* L){
ComplexListNode* pCloneList = NULL;
const ComplexListNode* pT = L;
ComplexListNode* pNew = NULL;
ComplexListNode* pPre = NULL;
hash_map<const ComplexListNode*, ComplexListNode*> AddTab;
//Init the pCloneList, set the Next pointers and fill the AddTab.
while(pT != NULL){
if((pNew = (ComplexListNode*)malloc(sizeof(ComplexListNode))) == NULL)
return NULL;
pNew->m_nValue = pT->m_nValue + 0x20;
pNew->m_pSibling = NULL;
pNew->m_pNext = NULL;
if(pPre == NULL){
pCloneList = pNew;
}
else{
pPre->m_pNext = pNew;
}
pPre = pNew;
AddTab.insert(pair<const ComplexListNode*, ComplexListNode*>(pT, pNew));
pT = pT->m_pNext;
}
//Set the m_pSibling pointer
pT = L;
pNew = pCloneList;
while(pT != NULL){
if(pT->m_pSibling != NULL){
pNew->m_pSibling = AddTab[pT->m_pSibling];
}
pT = pT->m_pNext;
pNew = pNew->m_pNext;
}
return pCloneList;
}
最后给出一些测试上述算法使用的代码:
#include <stdlib.h>
#include <iostream>
#include <hash_map>
#define LISTLEN 5
using namespace std;
using namespace stdext;
int InitTestList(ComplexListNode** L){
ComplexListNode* pNewList = NULL;
if((pNewList = (ComplexListNode*)malloc(sizeof(ComplexListNode) * LISTLEN)) == NULL)
return 1;
*L = pNewList;
ComplexListNode* pT = pNewList;
//Init Node A
pT->m_nValue = 0x41;
pT->m_pNext = &pNewList[1];
pT->m_pSibling = &pNewList[2];
pT = pT->m_pNext;
//Init Node B
pT->m_nValue = 0x42;
pT->m_pNext = &pNewList[2];
pT->m_pSibling = &pNewList[4];
pT = pT->m_pNext;
//Init Node C
pT->m_nValue = 0x43;
pT->m_pNext = &pNewList[3];
pT->m_pSibling = NULL;
pT = pT->m_pNext;
//Init Node D
pT->m_nValue = 0x44;
pT->m_pNext = &pNewList[4];
pT->m_pSibling = &pNewList[1];
pT = pT->m_pNext;
//Init Node E
pT->m_nValue = 0x45;
pT->m_pNext = NULL;
pT->m_pSibling = NULL;
return 0;
}
void PrintComplexList(const ComplexListNode* L){
const ComplexListNode* pT = L;
while(pT != NULL){
cout<<"Value:"<<pT->m_nValue<<"\t";
if(pT->m_pSibling != NULL)
cout<<"Sibling:"<<pT->m_pSibling->m_nValue;
cout<<endl;
pT = pT->m_pNext;
}
}
void DestroyTestList(ComplexListNode* L){
ComplexListNode* pT = L;
ComplexListNode* pPre = NULL;
while(pT != NULL){
pPre = pT;
pT = pT->m_pNext;
free(pPre);
}
}
int main(){
ComplexListNode* pSrc = NULL;
ComplexListNode* pDst_1 = NULL;
ComplexListNode* pDst_2 = NULL;
if(InitTestList(&pSrc) == 0){
cout<<"Original List:"<<endl;
PrintComplexList(pSrc);
cout<<endl<<endl;
pDst_1 = Clone_1(pSrc);
pDst_2 = Clone_2(pSrc);
cout<<"DestList1 List:"<<endl;
PrintComplexList(pDst_1);
cout<<endl<<endl;
cout<<"DestList2 List:"<<endl;
PrintComplexList(pDst_2);
cout<<endl<<endl;
free(pSrc);//pSrc points to a memory block capable for LISTLEN nodes
DestroyTestList(pDst_1);
DestroyTestList(pDst_2);
}
return 0;
}