剑指Offer——面试题26:复杂链表的复制

题目:请实现函数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

这种思路的具体做法如下所述:

1.对于原链表中的每一个节点A,我们将节点A的副本a放在A的下一个位置,即A->m_pNext为a,并且a->m_pNext为A在原链表中的下一个节点B,这样就可以保持原链表的连续性,维持原链表所依赖的由m_pNext指针提供的拓扑关系,此时我们并未对m_pSibling指针的关系进行复制,但也没有破坏这层关系,如图2(即下图);

图2

2.随后再参照原链表节点之间的m_pSibling关系,对副本节点进行操作,形成的新链表我们成为纠缠链表,如图3(即下图);

图3

3.最后从纠缠链表的头部开始遍历,将这个复制后的链表解开,形成两个链表,一个为原链表,另一个就是副本链表,具体的技巧是,第奇数个节点属于原链表,第偶数个节点属于副本链表,如图4(即下图)。

图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;
}

  

posted @ 2014-12-15 22:21  Superpig0501  阅读(174)  评论(0编辑  收藏  举报