复制数据结构 - 单链表及图
来源:互联网 发布:mysise php爬虫 编辑:程序博客网 时间:2024/06/05 12:49
这次依然是两道算法题,复制单链表,及复制图。先后对其加以分析,最后会给出一些粗浅总结。
题目一,复制一个单链表(SLL - single linked list), 其节点除了一个基本的后向指针(next), 还有一个指向链表中任意一个节点的随机指针(random)。
struct ranSLLNode{ char* cValue; ranSLLNode* pNext; ranSLLNode* pRandom; ranSLLNode(): pNext(0), pRandom(0){ cValue = new char[CSIZE]; memset(cValue, 0, CSIZE); } ~ranSLLNode(){ delete cValue; cValue=0; pNext=0; pRandom=0; }};
如果是普通单链表,很简单:遍历原始链表,同时逐个创建节点拷贝,总共一次遍历;现在每个节点多了一个随机指针,假设对于原节点pnode, 其复制拷贝节点cpnode, 问题在于如果pnode中的random指向的是之后的节点px, 则由于px的拷贝节点尚未创建,cpnode的random目前无法赋值。
解法一:利用map, 存放每个原节点和其复制节点的关联。这样,当遍历走到某个节点时,如果随机指针指向的节点还未创建,那就直接创建它!反正在map中,每个节点和其拷贝只会存在一份。恩,是个办法。
ranSLLNode* clone_01(ranSLLNode* phead){ map<ranSLLNode*, ranSLLNode*> mnodes; ranSLLNode *curr = phead; while(1){ if(mnodes.find(curr) == mnodes.end()){ ranSLLNode *p = new ranSLLNode; p->cValue = curr->cValue; mnodes[curr] = p; } if(mnodes.find(curr->pRandom) == mnodes.end()){ ranSLLNode *p = new ranSLLNode; p->cValue = curr->pRandom->cValue; mnodes[curr->pRandom] = p; mnodes[curr]->pRandom = p; }else{ mnodes[curr]->pRandom = mnodes[curr->pRandom]; } if(curr->pNext != 0){ if(mnodes.find(curr->pNext) == mnodes.end()){ ranSLLNode *p = new ranSLLNode(); p->cValue = curr->pNext->cValue; mnodes[curr->pNext] = p; mnodes[curr]->pNext = p; }else{ mnodes[curr]->pNext = mnodes[curr->pNext]; } }else{ //reach tail, exit mnodes[curr]->pNext = 0; break; } } return mnodes[phead];}
遍历到每一个节点时,分别检查本节点、next指向节点、random指向节点等三个节点的拷贝,如果尚无,创建它,保证在这一步结束前,本节点和其完整拷贝在map里有存在。
空间上,使用一个map;时间上,总计只有一次遍历链表。
另:如果要求不使用额外空间map,怎么办?
本着时间和空间互换的思想,显然我们只能设法利用原单链表,在本题中,可选项是两个指针:random指针无序,没法用;next指针有序并支持遍历,可以使用。
解法二:我们在原链表中每一个原始节点后,插入其相应的拷贝节点,这时,对random指针不赋值;然后重新遍历整个链表,将每个新拷贝节点的random指针,赋值为其前任节点(也就是其原节点)random指针指向节点的新拷贝节点;最后,解开原节点和新拷贝节点,得到原单链表和复制后的拷贝链表。
ranSLLNode* clone_02(ranSLLNode* srcHeader){ ranSLLNode* curr = srcHeader; while(curr != 0){ //create new node one after each source node ranSLLNode* nNode = new ranSLLNode(); strcpy(nNode->cValue, curr->cValue); nNode->cValue[strlen(curr->cValue)]='\0'; nNode->pRandom = curr->pRandom; nNode->pNext = curr->pNext; curr->pNext = nNode; curr = nNode->pNext; nNode = 0; } curr=srcHeader; while(curr != 0){ //set pRandom of new node be new node curr->pNext->pRandom = curr->pRandom->pNext; curr = curr->pNext->pNext; } curr = srcHeader; ranSLLNode* next = curr->pNext; ranSLLNode* nHeader = next; while(next != 0){ //unplug new node and source node curr->pNext = next->pNext; curr = next; next = curr->pNext; } curr=0; next = 0; return nHeader;}该解法空间上没有使用额外结构,时间上总共遍历三次链表。
-------------------------------------------------我是分隔线-----------------------------------------------------
题目二,复制一个图(graph),图中的顶点由以下结构表示,输入为一个顶点,输出其相应的拷贝顶点。(来自leetcode)
struct Node{ int val; vector<Node*> neighbors;};解法一:该解法来自leetcode上原题的作者,这里作个简单转载。
首先提示读者注意这个图是有向还是无向?根据该图的数据结构,只有顶点的数据结构,每条边由一个顶点P和其一个相邻顶点中Q表示,Q会出现在P的neighbors中,这符合有向图的特征。如果是无向图,每条边的两个顶点应该是地位平等的,但我们这道题中,顶点相邻关系显然仅仅取决于某个顶点的neighbours。所以,这是个有向图。
其次,直观的方法是对所有节点作有序遍历,那么选择队列queue, 使用广度优先BFS进行遍历。需要注意的是,如果图中存在回路,我们应力求避免回路中的某个节点重复进入队列,即程序出现死循环!!对策是使用一个map存放原节点和相应拷贝节点的对应,保证每个原节点只被复制一次。
Node* clonegraph_02(Node *graph){ if(!graph) return NULL; map<Node*, Node*> gmap; //[initial, copy] queue<Node*> q; q.push(graph); Node *graphCopy = new Node; graphCopy->val = graph->val; gmap[graph] = graphCopy; while(!q.empty()){ Node *node = q.front(); q.pop(); int n = node->neighbors.size(); for(int i=0;i<n;++i){ Node *neighbor = node->neighbors[i]; if(gmap.find(neighbor) == gmap.end()){ //no copy exist in map Node *p = new Node; p->val = neighbor->val; gmap[node]->neighbors.push_back(p); gmap[neighbor] = p; }else{ gmap[node]->neighbors.push_back(gmap[neighbor]); } } } return graphCopy;}
与题目一类似,如果不用额外空间map,怎么做?
解法二:这个解法是我受题目一的解法二启发而得。既然不能使用map记录原顶点和拷贝顶点的对应,那么只能在原顶点上做文章。
首先,遍历每个顶点V[i],将V[i]的拷贝顶点存入V[i]的neighbors列尾;然后,再次遍历每个顶点V[i], 为它的拷贝顶点的neighbors都填充上相应的拷贝顶点。
在代码实现中,由于要作遍历(BFS),queue的使用不可避免。另外同样需要避免在回路存在时,顶点重复进入队列的问题,这里额外使用一个set来处理。以下是一份实现代码:
Node* clonegraph_01(Node* pnode){ set<Node*> snodes; queue<Node*> qnodes; qnodes.push(pnode); snodes.insert(pnode); while(!qnodes.empty()){ //1st iteration to append cloned A' to neighbors of A Node* curr = qnodes.front(); qnodes.pop(); vector<Node*>::const_iterator iter = curr->neighbors.begin(); for(;iter != curr->neighbors.end();++iter){ if(snodes.find(*iter) == snodes.end()){ //Node* not pushed yet qnodes.push(*iter); snodes.insert(*iter); } } Node* clone = new Node; clone->val = curr->val; curr->neighbors.push_back(clone); //append clone at tail } snodes.clear(); qnodes.push(pnode); snodes.insert(pnode); Node* npnode = pnode->neighbors.back(); //pointer to return while(!qnodes.empty()){ Node* curr = qnodes.front(); qnodes.pop(); Node* clone = curr->neighbors.back(); vector<Node*>::iterator iter = curr->neighbors.begin(); for(;iter != curr->neighbors.end()-1;++iter){ if(snodes.find(*iter) == snodes.end()){ qnodes.push(*iter); snodes.insert(*iter); } clone->neighbors.push_back((*iter)->neighbors.back()); //push clone to neighbors of clone } curr->neighbors.erase(iter); //now iter points to A' of A } snodes.clear(); return npnode; }
相对于解法一,该解法空间上少使用一个map,不过多了一个set,扯平了;时间上多了一次全图遍历(BFS),所以似乎没什么优势。。。
总结:
1. 对于这类复制容器的问题,时间复杂度一般均为O(n), 提升余地表现在遍历的次数; 空间上的优化一般包括关联容器的使用(map)与否。
2. 算法的基本哲学“空间和时间可以互换”在这里的两种不同思路中得到了很好的展示。
3. 对于图的处理,一定要小心回路的情况,因为如果存在回路,普通的遍历会出现死循环!
- 复制数据结构 - 单链表及图
- 【数据结构】单链表--复杂链表的复制
- 数据结构----单链表分析及实现
- 数据结构题典005:单链表的复制(ANSI C)
- 数据结构示例之复制字符串
- 数据结构--单链表及静态链表
- 数据结构之单链表及python实现
- 数据结构及算法(Python)---单链表
- 复制文件及文件夹
- 复制文件夹及内容
- 数据结构--------图的遍历算法及实现
- 数据结构(16)--图的存储及实现
- 数据结构:图相关概念及遍历算法
- 图的基本数据结构及算法汇总
- 图--分类及存储结构(数据结构)
- 图的数据结构及遍历算法
- 数据结构及算法-何谓数据结构
- Java复制文件及复制文件夹
- TNS-12535:TNS:操作超时
- ZOJ-1949
- __thread 和 __typeof__关键字
- 图像腐蚀
- 基于OpenCV的读取摄像头实现单个人脸验证MFC程序
- 复制数据结构 - 单链表及图
- android ndk知识汇总——1
- wpa_supplicant更新问题
- Leetcode: Balanced Binary Tree
- Java Web获取Web应用根路径
- Hadoop版本选择探讨
- 使用JMF实现音乐播放(java多媒体编程)
- 析构函数不能抛出异常
- 一起做开源在线IDE(mokide=nodejs+jQueryUI+CodeMirror)