在链表中漫游
2013-08-29 16:10 夜与周公 阅读(343) 评论(0) 编辑 收藏 举报链表数据结构操作灵活,经常出现在各大公司的面试题中,因此,本文总结了常见的有关链表的面试题。
首先定义链表的数据结构:
template<typename T> struct Node { T data; Node* next; };
1.基本操作
虽然将其命名成“基本操作”,因为这些对链表的操作与我们正常接触的对链表的操作,完成的功能是一样的,如删除一个节点,增加一个节点。但与正常操作不一样的地方是,增加了一些限制条件。
1)删除节点:只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点
因为不知道头节点的位置,因此,无法遍历获得节点p的前一个节点指针,采用传统的删除手法,将会造成链表在p出断开。为此,我们采用“伪装”的技术:将欲删除的节点的下一个节点NextNode中的数据copy到,并删除NextNode节点。
template <typename T> bool delete_node(Node<T>* pNode) { Node<T> pNext = pNode->next; if (!pNext) { return false; } //copy pNext所指节点中的数据 pNode->data = pNext->data; pNode->next = pNext->next; delete pNext; return true; }
2)插入节点:只给定单链表中某个结点p(非空结点),在p前面插入一个结点
类似与之前删除节点一样的问题,我们依然采用“偷梁换柱”技术:将欲插入的节点数据和节点p互换数据,然后,将新插入节点Next指向p的Next,p的Next指向插入的新节点。
template<typename T> void add_node(Node<T>* pNode, Node<T>* pNewNode) { //互换pNode与pNewNode内的数据 T data = pNode->data; pNode->data = pNewNode->data; pNewNode->data = data; //将互换数据后的节点链接起来 pNewNode->next = pNode->next; pNode->next = pNewNode; }
3)查找:查找链表中倒数第K个节点
一般的方法需要遍历两次:第一次就成链表的长度,第二次,遍历计数到遍历到第length-k个节点是结束。如何只在遍历一次的时候,就能找出倒数第K个节点?可以使用双指针技术:1)首先两个指针均从头指针开始,第一个节点先走K个位置;2)两个指针同步移动,当第一个指针指向尾节点是,第二个指针位置,便是需要求的节点位置:
template<typename T> Node<T>* find_last_k(Node<T>* pHead, int k) {
if (pHead->next == NULL)
return NULL;
Node<T>* pFirst = pHead->next; Node<T>* pSeconde = pHead->next; for (int i = 1; i < k; i++, pFirst = pFirst->next); while (pFirst->next) { pFirst = pFirst->next; pSeconde = pSeconde->next; } return pSeconde; }
4)链表就地转折
这个操作没有涉及到多少技巧,只需要主要操作顺序:
template<typename T> Node<T>* reverse(Node<T>* pHead) { Node<T>* pReverseHead = new Node<T>(); pReverseHead->next = NULL; Node<T>* pCurNode = pHead->next; Node<T>* pNext = NULL; while (pCurNode) { pNext = pCurNode->next; pCurNode->next = pReverseHead->next; pReverseHead->next = pCurNode; pCurNode = pNext; } return pReverseHead; }
2.技巧题
1)判断链表是否相交
判断链表相交有两个功能需求:仅判断链表是否相交;给出链表相交的第一个节点。第一个功能相对简单,如何两个链表相交,那么两个链表最后一个节点必然是相同的,实现代码如下:
template <typename T> bool is_intersected(Node<T>* pAHead, Node<T>* pBHead) { if (pAHead != NULL || pBHead != NULL) { return false; } else { Node<T>* pANode = pAHead; Node<T>* pBNode = pBHead; while (pANode->next) pANode = pANode->next; while (pBNode->next) pBNode = pBNode->next; return pANode == pBNode ? true : false; } }
第二个功能,求两个相交的那个节点。算法首先求出两个两边的长度之差|lenA-lenB|,让较长链表走|lenA-lenB|个步长;然后让这两个链表同时开始移动,并判断当前节点的指针是否指向同一节点template <typename T>
int list_len(Node<T>* pAHead) { Node<T>* pNode = pHead->next; int listLen = 0; while (pNode)
{
pNode = pNode->next; listLen++;
} return listLen; } template <typename T> Node<T>* find_intersect_node(Node<T>* pAHead, Node<T>* pBHead) { Node<T>* intersectNode = NULL; if (pAHead == NULL || pBHead == NULL) { return intersectNode; } int lenA = list_len(pAHead); int lenB = list_len(pBHead); int longerLen = 0; Node<T>* pShort = NULL; Node<T>* pLong = NULL; if (lenA > lenB) { longerLen = lenA - lenB; pLong = pAHead->next; pShort = pBHead->next; } else { longerLen = lenB - lenA; pLong = pBHead->next; pShort = pAHead->next; } while (longerLen-- > 0) pLong = pLong->next; while (pLong) { if (pLong == pShort) { intersectNode = pLong; break; }
pLong = pLong->next;
pShort = pShort->next;
}
return intersectNode; }
2)判断链表是否由环
在判断链表是否相交的一个前提条件是,链表不存在环。如果链表存在环,遍历这个链表将会是个死循环。那么如何判断一个链表是否存在一个环?第一种方法,记录链表中给每个节点的地址,当出现了两个相同的地址时,可以判断两个链表是相交的。
第二种方法,是采用快慢指针法,慢指针p每走一次步长一个节点,快指针q每走一次步长两个节点,当着两个指针相遇时,则链表存在环。算法的证明如下:
如图所示当p在环的入口节点时,q已经进入环中,令q=p-q, p=0,环的长度为m,当慢指针走过i次步长后,p与q相遇,需要则满足
(q+2*i) % m = i % m
上式成立的条件是:
q + 2*i = i + k * m(k为整数)
q + i = k*m
m与q是常量,而i和k是变量,上面的表达式一定存在某个(i, k)使表达式成立。
判断链表是否相交的代码如下:
/************************************************************************/ /* 函数功能: 判断链表是否有环 * 输入参数: pHead,指向链表的头指针 * 返回参数: 返回快慢指针相遇的节点指针, 如果没有换返回NULL /************************************************************************/ template <typename T> Node<T>* is_exist_loop(Node<T>* pHead) { Node<T>* pNode = NULL; if (pHead ==NULL) { return pNode } Node<T>* p = pHead; Node<T>* q = pHead; while (q->next && q->next->next) { q = q->next->next; p = p->next; if (p==q) { pNode = p; break; } } return pNode; }
那么如何找到环的入口节点呢?
假设在快慢指针相遇的节点pNode处“断开”(非常物理意义上的断开),那么将存在两天逻辑上的链表:以pHead为头指针pNode为结尾的链表A,以pNode为首节点,并以pNode为尾节点的两个链表,这两个链表相交与环的入口节点。
/************************************************************************/ /* 函数功能: 寻找有环链表的入口节点 * 输入参数: pHead,指向链表的头指针,pCrossNode快慢指针相遇的节点 * 返回参数: 链表的入口节点 /************************************************************************/ template<typename T> Node<T>* find_enter_node(Node<T>* pHead, Node<T>* pCrossNode) {
int lenA=0, lenB=1; //链表B从对首节点开始计算,len起始于1
for (Node<T>* pNode=pHead->next ; pNode != pCrossNode; pNode = pNode->next) //计算链表A的长度
lenA++;
for (Node<T>* pNode=pCrossNode->next; pNode!=pCrossNode; pNode = pNode->next)//计算链表B的长度
lenB++;
Node<T>* pLong = NULL; Node<T>* pShort = NULL; int longLen = 0; if (lenA > lenB) { pLong = pHead->next; pShort = pCrossNode; longLen = lenA - lenB; } else { pLong = pCrossNode; pShort = pHead->next; longLen = lenB - lenA; } while (longLen-- > 0) { pLong = pLong->next; } Node<T>* pEnterNode = NULL; while (pLong) { if (pLong == pShort) { pEnterNode = pLong; break; } pLong = pLong->next; pShort = pShort->next; } return pEnterNode; }
程序的主函数如下:
int main() { int myArray[] = {0, 1, 2, 3, 4, 5}; Node<int>* pHead = build_list(myArray, 6); Node<int>* pNode = pHead->next; while(pNode && pNode->data != 3) pNode = pNode->next; cout<<"Before Delete Element 3:"; print(pHead); cout<<"After Delete Element 3:"; delete_node(pNode); print(pHead); pNode = pHead->next; while(pNode && pNode->data != 4) pNode = pNode->next; Node<int>* pAddNode = new Node<int>(6); cout<<"After Insert Element 6 before element 4:"; add_node(pNode, pAddNode); print(pHead); pNode = find_last_k(pHead, 4); cout<<"The last 4 element:"<<pNode->data<<endl; cout<<"Reverse list:"; pHead = reverse(pHead); print(pHead); //Build list B via add list A; int myArrayB[] = {7, 8, 9}; Node<int>* pHeadB = build_list(myArrayB, 3); Node<int>* pBNode = pHeadB->next; while (pBNode->next) pBNode = pBNode->next; pBNode->next = pNode; cout<<"List A:"; print(pHead); cout<<"List B:"; print(pHeadB); Node<int>* pIntersetNode = find_intersect_node(pHead, pHeadB); cout<<"The intersect element is:"<<pIntersetNode->data<<endl; //Build cycle via list A at element 4 Node<int>* pLast3 = find_last_k(pHead, 3); Node<int>* pLast1 = find_last_k(pHead,1); pLast1->next = pLast3; // build a loop pNode = is_exist_loop(pHead); Node<int>* pEnterNode = find_enter_node(pHead, pNode); if (pNode) { cout<<"Exist loop, The enter node is:"<<pEnterNode->data<<endl; } else cout<<"Not Exist a loop"<<endl; system("pause"); return 0; }
运行结果如下: