海涛老师的面试题-作业-链表专题代码及讲解
2012-06-28 18:32
387 查看
View Code
// 链表专题.cpp : 定义控制台应用程序的入口点。 // /************************************************* 设计者:cslave 版本说明:本代码免费用于商用,拷贝,转移,使用,但是 有本代码导致的问题,本人概不负责。 设计时间:2012.6.25 分发原则:遵守GNU规范。 本专题主要针对链表的相关面试题进行展开,中间涉及一些 经典的链表面试题,有相关代码设计,使用,学习,分发本 代码,请注明本博客的地址 http://www.cnblogs.com/cslave/ 该篇博文源于看何海涛老师的文章有感,去年何老师在CSDN举行 编程比赛送书活动,本人有幸得到一本,感恩何老师。 且代码中部分函数接口采用何老师的设计。 专题中存在很多不足,希望大家能够帮助改进,谢谢。 ****************************************************/ #include "stdafx.h" #include "List.h" #include <stdlib.h> #include <iostream> using namespace std; ListNode* CreateListNode(int Value) //链表节点创建 { ListNode* pNode= new ListNode(); pNode->Data=Value; pNode->pNextNode=NULL; return pNode; } /*************************************************** 下面这个函数接口能够非常方便的创建链表,尤其是带环链表 。一个函数将两个独立的节点联系起来,使用灵活方便。 其中的好处自己去体会吧。 ****************************************************/ void ConnectListNodes(ListNode* pCurrent,ListNode* pNext) { if(pCurrent==NULL) { throw new exception("Failed to Connect Nodes!"); } pCurrent->pNextNode=pNext; } /****************************************************** 打印节点函数 ******************************************************/ void PrintNode(ListNode* pNode) { if(pNode==NULL) { cout<<"The Node Is Empty!"<<endl; } else cout<<"The Value Of The Node Is:"<<pNode->Data<<endl; } /****************************************************** 打印链表函数 ******************************************************/ void PrintList(ListNode* pHead) { cout<<"List Print Begin!:"<<endl; if(!pHead) return; ListNode* pTemp=pHead; while(pTemp) { cout<<pTemp->Data<<"--->"; pTemp=pTemp->pNextNode; } cout<<"List Print End!:"<<endl; } /****************************************************** 为链表添加尾节点函数 ******************************************************/ void AddTail(ListNode** pHead,int Value) { ListNode *pNode=new ListNode(); pNode->Data=Value; pNode->pNextNode=NULL; if(*pHead==NULL) //无节点 *pHead=pNode; else { ListNode* pTemp=*pHead; while(pTemp->pNextNode!=NULL) { pTemp=pTemp->pNextNode; } pTemp->pNextNode=pNode; } } /****************************************************** 移除节点函数,移除定点值节点。复杂度O(n) ******************************************************/ void RemoveNode(ListNode** pHead,int Value) { if(pHead==NULL||*pHead==NULL) return; ListNode* pToBeDelete=NULL; if((*pHead)->Data==Value) { pToBeDelete=*pHead; *pHead=(*pHead)->pNextNode; } else { ListNode *pNode=*pHead; while(pNode->pNextNode!=NULL&&pNode->pNextNode->Data!=Value) { pNode=pNode->pNextNode; } if(pNode->pNextNode!=NULL&&pNode->pNextNode->Data==Value) { pToBeDelete=pNode->pNextNode; pNode->pNextNode=pNode->pNextNode->pNextNode; } } if(pToBeDelete!=NULL) { delete pToBeDelete; pToBeDelete=NULL; } } /****************************************************** 移除节点函数,移除定点值节点。复杂度O(1) 这个函数的思想是,将删除节点的下一个节点的值赋给自己,这样 狸猫换太子,将自己成为合法的节点,这样只需删除下一个节点。 还有一个问题:如果删除的结点位于链表的尾部,没有下一个结点, 怎么办?我们需要遍历得到删除结点的前序结点。这个时候时间复杂度是O(n)。 假设链表总共有n个结点, 我们的算法在n-1总情况下时间复杂度是O(1),只有当给定的结点处于链表 末尾的时候,时间复杂度为O(n)。那么平均时间复杂度[(n-1)*O(1)+O(n)]/n, 仍然为O(1)。 ******************************************************/ void DeleteNode(ListNode** pHead,ListNode* pToBeDelete) //删除链表定节点 O(1)算法 { if(!pHead||!pToBeDelete) return; if(pToBeDelete->pNextNode!=NULL) { ListNode *pNext=pToBeDelete->pNextNode; pToBeDelete->Data=pNext->Data; pToBeDelete->pNextNode=pNext->pNextNode; delete pNext; pNext=NULL; } else if(*pHead==pToBeDelete) { delete pToBeDelete; pToBeDelete=NULL; *pHead=NULL; } else { ListNode *pNode=*pHead; while(pNode->pNextNode!=pToBeDelete) { pNode=pNode->pNextNode; } pNode->pNextNode=NULL; delete pToBeDelete; pToBeDelete=NULL; } } /****************************************************** 查找链表倒数第K个节点,仅需遍历一遍链表。 遍历时维持两个指针,第一个指针从链表的头指针开始遍历, 在第k-1步之前,第二个指针保持不动;在第k-1步开始, 第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1, 当第一个(走在前面的)指针到达链表的尾结点时, 第二个指针(走在后面的)指针正好是倒数第k个结点。 ******************************************************/ ListNode* FindKthToTail(ListNode* pHead,unsigned int k)//倒数第k个节点 { if(!pHead||k==0) return NULL; ListNode *pFront=pHead; ListNode *pBack=pHead; for(unsigned int i=0;i< k-1;i++) { if( pFront->pNextNode!=NULL) pFront=pFront->pNextNode; else { return NULL; } } while(pFront->pNextNode!=NULL) { pFront=pFront->pNextNode; pBack=pBack->pNextNode; } return pBack; } /****************************************************** 求链表中间点函数,也很简单,设置两个指针,一个走两步,另外 一个走一步,这样第二个到链表尾时,第一个刚好一半,即为链表 中间点。 ******************************************************/ //求链表的中间节点 ListNode* FindMiddleList(ListNode *pHead) { if(pHead==NULL) return NULL; if(!(pHead->pNextNode)) return pHead; ListNode *pFront=pHead; ListNode *pBack=pHead; unsigned int i=1; while(pFront->pNextNode) { pFront=pFront->pNextNode; if(!(i&1)) pBack=pBack->pNextNode; i++; } return pBack; } /****************************************************** 判断链表是否有环函数,想法是设置两个指针,其中一个指针走两步, 另外一个指针走一步,这样这样如果有环,他们一定会相遇,如果没有环 则两步指针会在O(n)量级完成判断。有环也在同样量级,但是小于n, 比如环很大的时候。 ******************************************************/ //求链表是否有环 bool JudgeListRing(ListNode* pHead) { if(!pHead||pHead->pNextNode==NULL) //单节点可能是环 return false; if(pHead->pNextNode==pHead) //单节点环 return true; ListNode* pFront=pHead; ListNode* pBack=pHead; while(pFront->pNextNode->pNextNode!=NULL) { pBack=pBack->pNextNode; pFront=pFront->pNextNode->pNextNode; if(pFront==pBack) return true; if(pFront->pNextNode==NULL) break; } return false; } /****************************************************** 求环的起始点位置函数,这个我先来推导一个关系式。 设两个指针,同样一个一步,一个二步,设一步指针和二步指针相遇时 一步指针走了s 则二步指针走了2s ,他们相遇时必然在环里相遇,因为 环外无法相遇,设环长为r 则相遇时,二步指针比一步指针多走了nr, n为整数且大于等于1. 2s=s+nr; 再设环起点到出发点为a,相遇时一步指针在环内走了x,链表总长度为L 则有: s=a+x a=s-x=nr-x=(n-1)r+r-x=(n-1)r+L-a-x 好了 我得到这个式子: a=(n-1)r+L-a-x (L – a – x)为相遇点到环入口点的距离,由此可知, 从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点, 于是我们从链表头、与相遇点分别设一个指针,每次各走一步, 两个指针必定相遇,且相遇第一点为环入口点。 ******************************************************/ //求链表环起点位置 ListNode* CalculateListRingPos(ListNode* pHead) { if(!JudgeListRing(pHead)) return NULL; if(pHead->pNextNode==pHead||pHead->pNextNode->pNextNode==pHead) return pHead; ListNode *pFront=pHead; ListNode *pBack =pHead; ListNode *pComp =pHead; while(pFront->pNextNode->pNextNode!=NULL) { pBack=pBack->pNextNode; pFront=pFront->pNextNode->pNextNode; if(pFront==pBack) break; } if(pFront==pBack) { while(pFront!=pComp) { pFront=pFront->pNextNode; pComp=pComp->pNextNode; } return pFront; } return NULL; } //链表反转 ListNode* ReverseList(ListNode* pHead) { ListNode* pReverseNode=NULL; ListNode* pNode=pHead; ListNode* pPrev=NULL; while(pNode!=NULL) { ListNode* pNext=pNode->pNextNode; if(pNext==NULL) pReverseNode=pNode; pNode->pNextNode=pPrev; pPrev=pNode; pNode=pNext; } return pReverseNode; } /****************************************************** 判断两个链表相交的函数,两个链表相交分为4种情况 1 两个链表都没有环 只需要检查尾节点是否相等 2,3 其中一个链表有环,另外一个没有环,这种不可能相交 4 两个链表都有环,这里,两个链表相交地点必然是环入口点 或者入口点之前,想想为什么? 若链B和链A都有环,且链B在链A环内相交,则链A入口点到相交点的 元素不在环B上,那么链B不存在该环。 所以仅需判断两个链表环入口点是否相等,即可判断是否相交。 ******************************************************/ //判断两个链表是否相交 bool JudgeListCross(ListNode *pHead1,ListNode *pHead2) { if(!pHead1||!pHead2) return false; bool judge1=JudgeListRing(pHead1); bool judge2=JudgeListRing(pHead2); int Result=judge1*2+judge2; ListNode *pNode1=pHead1; ListNode *pNode2=pHead2; ListNode *pRing1=NULL; ListNode *pRing2=NULL; ListNode *pRing=NULL; switch(Result) { case 0: while(!pNode1->pNextNode) pNode1=pNode1->pNextNode; while(!pNode2->pNextNode) pNode2=pNode2->pNextNode; if(pNode1==pNode2) return true; else return false; break; case 1: return false; break; case 2: return false; break; case 3: pRing1=CalculateListRingPos(pHead1); pRing2=CalculateListRingPos(pHead2); pRing=pRing1; while(pRing1->pNextNode!=pRing) { if(pRing1==pRing2) return true; else pRing1=pRing1->pNextNode; } return false; break; default: break; } return false; } /****************************************************** 带环链表销毁函数,要注意是带环的,所以写的较复杂些。 ******************************************************/ void DestroyList(ListNode *pHead) { ListNode *pNode=pHead; ListNode *pTemp=NULL; int i=0; if(JudgeListRing(pHead)) pTemp=CalculateListRingPos(pHead); while(pNode!=NULL) { pHead=pHead->pNextNode; if(pNode!=pTemp) delete pNode; else { if(!i) { delete pNode; i++; } else return; } pNode=pHead; } } void Test1() { printf("=====Test1 测试链表的相关的操作=====\n"); ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); AddTail(&pNode1,6); RemoveNode(&pNode1,5); DeleteNode(&pNode1,pNode4); printf("expected result: 3.\n"); ListNode* pNode = FindKthToTail(pNode1, 2); PrintNode(pNode); AddTail(&pNode1,7); printf("expected result: 3.\n"); ListNode* Node=FindMiddleList(pNode1); PrintNode(Node); printf("链表反转后的结构为!\n"); Node=ReverseList(pNode1); PrintList(Node); DestroyList(pNode1); } void Test2() { printf("=====Test2 测试链表的是否存在环及位置=====\n"); ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode3); bool Exist= JudgeListRing(pNode1); ListNode * Node=NULL; if(Exist) { printf("链表存在环!入口点为:\n"); printf("期望的入口点为3:\n"); Node=CalculateListRingPos(pNode1); PrintNode(Node); } else printf("链表不存在环!\n"); DestroyList(pNode1); } void Test3() { printf("=====Test3 测试两个链表是否相交=====\n"); ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ListNode* pNode6 = CreateListNode(6); ListNode* pNode7 = CreateListNode(7); ListNode* pNode8 = CreateListNode(8); ListNode* pNode9 = CreateListNode(9); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode3); ConnectListNodes(pNode6, pNode7); ConnectListNodes(pNode7, pNode8); ConnectListNodes(pNode8, pNode9); ConnectListNodes(pNode9, pNode3); bool Exist=JudgeListCross(pNode1,pNode6); if(Exist) { printf("两个链表相交:\n"); } else printf("两个链表不相交!\n"); } int _tmain(int argc, _TCHAR* argv[]) { Test1(); Test2(); Test3(); return 0; }
相关文章推荐
- 海涛老师的面试题-作业5-从尾到头打印链表
- 海涛老师的面试题-作业3-二维数组中的查找
- 海涛老师的面试题-作业6-重建二叉树
- 海涛老师的面试题-作业9-也谈斐波那契数列
- 海涛老师的面试题-作业12-打印从1到最大的n位数
- 海涛老师的面试题-作业1-赋值运算符函数
- 海涛老师的面试题-作业7-栈与队列之间的转换
- 海涛老师的面试题-作业28-字符串的排列组合问题。
- 海涛老师的面试题-作业21-包含min函数的栈
- 海涛老师的面试题-作业27-二叉搜索树与双向链表
- 海涛老师的面试题-作业22-栈的压入、弹出序列
- Asp.net 2.0 自定义控件开发专题讲解[为用户控件增加DataSource属性, 能够自动识别不同数据源](示例代码下载)
- 剑指offer代码分析——面试题13在O(1)内删除链表结点
- 剑指offer代码解析——面试题17合并两个排序的链表
- Java面试题-实现复杂链表的复制代码分享
- Asp.net 2.0 自定义控件开发专题讲解[为用户控件增加DataSource属性, 能够自动识别不同数据源](示例代码下载)
- 剑指Offer 面试题25:合并两个排序的链表(递归+非递归) Java代码实现
- 剑指Offer 面试题22:链表中倒数第k个节点 Java代码实现
- 【Java面试题】15 String s="Hello"; s=s+“world!”;这两行代码执行后,原始的String对象中的内容到底变了没有?String与StringBuffer的超详细讲解!!!!!
- Asp.net 2.0 自定义控件开发专题讲解[为用户控件增加DataSource属性, 能够自动识别不同数据源](示例代码下载)