复杂链表的复制

转自:http://zhedahht.blog.163.com/blog/static/254111742010819104710337/


     题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下:

struct ListNode
{
	int m_nvalue;
	ListNode* m_pNext;
	ListNode* m_pSibling;
};

   下图是一个含有5个结点的该类型复杂链表。图中实线箭头表示m_pNext指针,虚线箭头表示m_pSibling指针。为简单起见,指向NULL的指针没有画出。

                                     程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题

                

请完成函数 ListNode* ComplexListCopy(ListNode* pHead) ,以复制一个复杂链表。

                分析:在常见的数据结构上稍加变化,这是一种很新颖的面试题。要在不到一个小时的时间里解决这种类型的题目,我们需要较快的反应能力,对数据结构透彻的理解以及扎实的编程功底。

           看到这个问题,我的第一反应是分成两步:第一步是复制原始链表上的每个节点,并用m_pNext链接起来。第二步,假设原始链表中的某节点Nm_pSibling指向结点S。由于S的位置在链表上有可能在N的前面也可能在N的后面,所以要定位N的位置我们需要从原始链表的头结点开始找。假设从原始链表的头结点开始经过s步找到结点S。那么在复制链表上结点Nm_pSiblingS’,离复制链表的头结点的距离也是s。用这种办法我们就能为复制链表上的每个结点设置m_pSibling了。

代码如下:

#include <iostream>
#include <stdio.h>

using namespace std;

struct ListNode
{
	int m_nvalue;
	ListNode* m_pNext;
	ListNode* m_pSibling;
};

void CopySibling(ListNode* pHead1,ListNode* pHead2);

ListNode* ComplexListCopy(ListNode* pHead)
{
	//第一步是复制原始链表上的每个节点,并用m_pNext链接起来,m_pSibling先置为NULL。

	if(NULL == pHead)
		return NULL;
	ListNode* pCopyHead;
	ListNode* pNode = pHead;
	ListNode* pCopyNode = new ListNode();
	if(NULL == pCopyNode)
	{
		printf("No more memory\n");
		return NULL;
	}
	pCopyNode->m_nvalue = pNode->m_nvalue;
	pCopyNode->m_pNext = pCopyNode->m_pSibling = NULL;
	pCopyHead = pCopyNode;
	ListNode* tmpNode = pCopyHead;
	pNode = pNode->m_pNext;
	while(NULL != pNode)
	{
		ListNode* pCopyNode = new ListNode();
		if(NULL == pCopyNode)
		{
			printf("No more memory\n");
			return NULL;
		}
		pCopyNode->m_nvalue = pNode->m_nvalue;
		pCopyNode->m_pNext = pCopyNode->m_pSibling = NULL;
		tmpNode->m_pNext = pCopyNode;
		tmpNode = pCopyNode;
		pNode = pNode->m_pNext;
	}

	//第二步,复制m_pSibling域的值
	CopySibling(pHead,pCopyHead);
	
	return pCopyHead;
}

/*第二步,假设原始链表中的某节点N的m_pSibling指向结点S。由于S的位置在链表上有可能在N的前面也可能在N的后面,
所以要定位N的位置我们需要从原始链表的头结点开始找。假设从原始链表的头结点开始经过s步找到结点S。
那么在复制链表上结点N的m_pSibling的S’,离复制链表的头结点的距离也是s。用这种办法我们就能为复制链表上的每个结点设置m_pSibling了。*/
void CopySibling(ListNode* pHead1,ListNode* pHead2)
{
	if(NULL == pHead1 || NULL == pHead2)
		return;
	ListNode* tmpNode1;
	ListNode* tmpNode2;
	ListNode* pNode1 = pHead1;
	ListNode* pNode2 = pHead2;
	while(NULL != pNode1)
	{
		if(pNode1->m_pSibling != NULL)
		{/*复制m_pSibling域的值时,每次都是从链表的头部开始查找*/
			tmpNode1 = pHead1;
			tmpNode2 = pHead2;
			while(tmpNode1->m_nvalue != pNode1->m_pSibling->m_nvalue)
			{
				//复制链表的指针与原始链表的指针同时移动。
				tmpNode1 = tmpNode1->m_pNext;
				tmpNode2 = tmpNode2->m_pNext;
			}
			pNode2->m_pSibling = tmpNode2;
		}
		pNode1 = pNode1->m_pNext;
		pNode2 = pNode2->m_pNext;
	}
}

//依据m_pNext域的值从头打印链表
void Print(ListNode* pHead)
{
	if(NULL == pHead)
		return;
	while(pHead)
	{
		printf("%d ",pHead->m_nvalue);
		pHead = pHead->m_pNext;
	}
	printf("\n");
}
//依据m_pSibling域的值从头打印链表
void PrintSiblingOrder(ListNode* pHead)
{
	if(NULL == pHead)
		return;
	while(pHead)
	{
		if(pHead->m_pSibling != NULL)
			printf("%d ",pHead->m_pSibling->m_nvalue);
		pHead = pHead->m_pNext;
	}
	printf("\n");
}

int main()
{	
	/*构造复杂链表*/
	ListNode* pHead = NULL;
	ListNode* pHead2 = NULL;
	ListNode* tmpNode1 = new ListNode();
	if(NULL == tmpNode1)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode1->m_nvalue = 1;
	tmpNode1->m_pNext = tmpNode1->m_pSibling = NULL;
	pHead = tmpNode1;
	ListNode* tmpNode2 = new ListNode();
	if(NULL == tmpNode2)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode2->m_nvalue = 2;
	tmpNode2->m_pNext = tmpNode2->m_pSibling = NULL;
	tmpNode1->m_pNext = tmpNode2;
	ListNode* tmpNode3 = new ListNode();
	if(NULL == tmpNode3)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode3->m_nvalue = 3;
	tmpNode3->m_pNext = tmpNode2->m_pSibling = NULL;
	tmpNode2->m_pNext = tmpNode3;
	ListNode* tmpNode4 = new ListNode();
	if(NULL == tmpNode4)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode4->m_nvalue = 4;
	tmpNode4->m_pNext = tmpNode4->m_pSibling = NULL;
	tmpNode3->m_pNext = tmpNode4;

	tmpNode1->m_pSibling = tmpNode3;
	tmpNode2->m_pSibling = tmpNode4;
	tmpNode3->m_pSibling = tmpNode1;
	tmpNode4->m_pSibling = tmpNode2;
	
	//依据m_pSibling域的值从头打印原始链表
	PrintSiblingOrder(pHead);
	
	pHead2 = ComplexListCopy(pHead);
	//依据m_pSibling域的值从头打印复制链表
	PrintSiblingOrder(pHead2);

	return 0;
}

                对一个含有n个结点的链表,由于定位每个结点的m_pSibling,都需要从链表头结点开始经过O(n)步才能找到,因此这种方法的总时间复杂度是O(n2)

                由于上述方法的时间主要花费在定位结点的m_pSibling上面,我们试着在这方面去做优化。我们还是分为两步:第一步仍然是复制原始链表上的每个结点N,并创建N’,然后把这些创建出来的结点链接起来。这里我们对<NN’>的配对信息放到一个哈希表中。第二步还是设置复制链表上每个结点的m_pSibling。如果在原始链表中结点Nm_pSibling指向结点S,那么在复制链表中,对应的N’应该指向S’。由于有了哈希表,我们可以用O(1)的时间根据S找到S’

                第二种方法相当于用空间换时间,以O(n)的空间消耗实现了O(n)的时间效率。

                接着我们来换一种思路,在不用辅助空间的情况下实现O(n)的时间效率。第三种方法的第一步仍然是根据原始链表的每个结点N,创建对应的N’。这一次,我们把N’链接在N的后面。实例中的链表经过这一步之后变成了:

                                                     程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题
代码如下:
//第一步仍然是根据原始链表的每个结点N,创建对应的N’。这一次,我们把N’链接在N的后面,复制的节点的m_pSibling域置为NULL
bool CopyAndMergeList(ListNode* pHead)
{
	if(NULL == pHead)
	{
		invaluedInput = true;
		return false;
	}

	ListNode* pTmpNode = NULL;
	ListNode *pNode = pHead->m_pNext;
	ListNode* newNode = new ListNode();
	assert(NULL != newNode);
	newNode->m_nvalue = pHead->m_nvalue;
	newNode->m_pNext = pHead->m_pNext;
	newNode->m_pSibling = NULL;
	pHead->m_pNext = newNode;
	pTmpNode = pNode;
	while(pNode != NULL)
	{
		pNode = pNode->m_pNext;
		newNode = new ListNode();
		assert(NULL != newNode);
		newNode->m_nvalue = pTmpNode->m_nvalue;
		newNode->m_pNext = pTmpNode->m_pNext;
		newNode->m_pSibling = NULL;
		pTmpNode->m_pNext = newNode;
		pTmpNode = pNode;
	}
	return true;
}

       第二步是设置我们复制出来的链表上的结点的m_pSibling。假设原始链表上的Nm_pSibling指向结点S,那么其对应复制出来的程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题N’N->m_pNext,同样S’也是S->m_pNext。这就是我们在上一步中把每个结点复制出来的结点链接在原始结点后面的原因。有了这样的链接方式,我们就能在O(1)中就能找到每个结点的m_pSibling了。例子中的链表经过这一步,就变成如下结构了:

                                                                程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题
//设置上一步复制出来的链表上的结点的m_pSibling域,返回值为复制链表的头结点指针
ListNode* Copym_pSibling(ListNode* pHead)
{
	if(NULL == pHead)
	{
		invaluedInput = true;
		return NULL;
	}

	ListNode* pNode = pHead;
	ListNode* pCopyNode = pHead->m_pNext;
	while(pNode != NULL)
	{
		pCopyNode->m_pSibling = pNode->m_pSibling->m_pNext;
		pNode = pNode->m_pNext->m_pNext;
		if(NULL == pNode)
			break;
		pCopyNode = pCopyNode->m_pNext->m_pNext;
	}
	return pHead->m_pNext;
}

      第三步是把这个长链表拆分成两个:把奇数位置的结点链接起来就是原始链表,把偶数位置的结点链接出来就是复制出来的链表。上述例子中的链表拆分之后的两个链表如下:

                                                程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题
//第三步是把这个长链表拆分成两个:把奇数位置的结点链接起来就是原始链表,把偶数位置的结点链接出来就是复制出来的链表。返回复制链表的头指针
ListNode* BreakList(ListNode* pHead)
{
	if(NULL == pHead)
	{
		invaluedInput = true;
		return NULL;
	}

	ListNode* pNode = pHead;
	ListNode* pCopyHead = pHead->m_pNext;
	ListNode* pCopyNode = pCopyHead;
	ListNode* pTmpNode = NULL;
	while(pCopyNode->m_pNext != NULL)
	{
		pTmpNode = pCopyNode->m_pNext;
		pNode->m_pNext = pTmpNode;
		pCopyNode->m_pNext = pTmpNode->m_pNext;
		pNode = pTmpNode;
		pCopyNode = pTmpNode->m_pNext;
	}
	pNode->m_pNext = NULL;
	pCopyNode->m_pNext = NULL;

	return pCopyHead;
}
  我们把上面三步合起来,就是复制链表的完整过程。

下面是完整的代码(虽然将上面的代码重复贴出来显的啰嗦,但是方便自己以后复习时可以更快的回忆起整体思路。):

#include <iostream>
#include <assert.h>
#include <stdio.h>

using namespace std;

bool invaluedInput = false;

struct ListNode
{
	int m_nvalue;
	ListNode* m_pNext;
	ListNode* m_pSibling;
};

//第一步仍然是根据原始链表的每个结点N,创建对应的N’。这一次,我们把N’链接在N的后面,复制的节点的m_pSibling域置为NULL
bool CopyAndMergeList(ListNode* pHead)
{
	if(NULL == pHead)
	{
		invaluedInput = true;
		return false;
	}

	ListNode* pTmpNode = NULL;
	ListNode *pNode = pHead->m_pNext;
	ListNode* newNode = new ListNode();
	assert(NULL != newNode);
	newNode->m_nvalue = pHead->m_nvalue;
	newNode->m_pNext = pHead->m_pNext;
	newNode->m_pSibling = NULL;
	pHead->m_pNext = newNode;
	pTmpNode = pNode;
	while(pNode != NULL)
	{
		pNode = pNode->m_pNext;
		newNode = new ListNode();
		assert(NULL != newNode);
		newNode->m_nvalue = pTmpNode->m_nvalue;
		newNode->m_pNext = pTmpNode->m_pNext;
		newNode->m_pSibling = NULL;
		pTmpNode->m_pNext = newNode;
		pTmpNode = pNode;
	}
	return true;
}

//设置上一步复制出来的链表上的结点的m_pSibling域,返回值为复制链表的头结点指针
ListNode* Copym_pSibling(ListNode* pHead)
{
	if(NULL == pHead)
	{
		invaluedInput = true;
		return NULL;
	}

	ListNode* pNode = pHead;
	ListNode* pCopyNode = pHead->m_pNext;
	while(pNode != NULL)
	{
		pCopyNode->m_pSibling = pNode->m_pSibling->m_pNext;
		pNode = pNode->m_pNext->m_pNext;
		if(NULL == pNode)
			break;
		pCopyNode = pCopyNode->m_pNext->m_pNext;
	}
	return pHead->m_pNext;
}

//第三步是把这个长链表拆分成两个:把奇数位置的结点链接起来就是原始链表,把偶数位置的结点链接出来就是复制出来的链表。返回复制链表的头指针
ListNode* BreakList(ListNode* pHead)
{
	if(NULL == pHead)
	{
		invaluedInput = true;
		return NULL;
	}

	ListNode* pNode = pHead;
	ListNode* pCopyHead = pHead->m_pNext;
	ListNode* pCopyNode = pCopyHead;
	ListNode* pTmpNode = NULL;
	while(pCopyNode->m_pNext != NULL)
	{
		pTmpNode = pCopyNode->m_pNext;
		pNode->m_pNext = pTmpNode;
		pCopyNode->m_pNext = pTmpNode->m_pNext;
		pNode = pTmpNode;
		pCopyNode = pTmpNode->m_pNext;
	}
	pNode->m_pNext = NULL;
	pCopyNode->m_pNext = NULL;

	return pCopyHead;
}

//依据m_pNext域的值从头打印链表
void Print(ListNode* pHead)
{
	if(NULL == pHead)
		return;
	while(pHead)
	{
		printf("%d ",pHead->m_nvalue);
		pHead = pHead->m_pNext;
	}
	printf("\n");
}
//依据m_pSibling域的值从头打印链表
void PrintSiblingOrder(ListNode* pHead)
{
	if(NULL == pHead)
		return;
	while(pHead)
	{
		if(pHead->m_pSibling != NULL)
			printf("%d ",pHead->m_pSibling->m_nvalue);
		pHead = pHead->m_pNext;
	}
	printf("\n");
}

int main()
{	
	/*构造复杂链表*/
	ListNode* pHead = NULL;
	ListNode* pCopyHead = NULL;
	ListNode* tmpNode1 = new ListNode();
	if(NULL == tmpNode1)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode1->m_nvalue = 1;
	tmpNode1->m_pNext = tmpNode1->m_pSibling = NULL;
	pHead = tmpNode1;
	ListNode* tmpNode2 = new ListNode();
	if(NULL == tmpNode2)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode2->m_nvalue = 2;
	tmpNode2->m_pNext = tmpNode2->m_pSibling = NULL;
	tmpNode1->m_pNext = tmpNode2;
	ListNode* tmpNode3 = new ListNode();
	if(NULL == tmpNode3)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode3->m_nvalue = 3;
	tmpNode3->m_pNext = tmpNode2->m_pSibling = NULL;
	tmpNode2->m_pNext = tmpNode3;
	ListNode* tmpNode4 = new ListNode();
	if(NULL == tmpNode4)
	{
		printf("No more memory\n");
		return NULL;
	}
	tmpNode4->m_nvalue = 4;
	tmpNode4->m_pNext = tmpNode4->m_pSibling = NULL;
	tmpNode3->m_pNext = tmpNode4;

	tmpNode1->m_pSibling = tmpNode3;
	tmpNode2->m_pSibling = tmpNode4;
	tmpNode3->m_pSibling = tmpNode1;
	tmpNode4->m_pSibling = tmpNode2;

	//复制链表的三部曲
	CopyAndMergeList(pHead);

	Copym_pSibling(pHead);

	pCopyHead = BreakList(pHead);

	//打印原始链表
	Print(pHead);
	PrintSiblingOrder(pHead);
	
	//打印复制链表
	Print(pCopyHead);
	PrintSiblingOrder(pCopyHead);

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值