链表:
struct ListNode{ int value; ListNode * next; }
一般链表要注意的特殊输入是链表为空,链表只有一个结点等。
一、在链表末尾添加一个节点
void addToTail(ListNode ** pHead,int value){ ListNode newListNode = new ListNode(); newListNode -> value = value; newListNode -> next = null; if(pHead == null || *pHead == null) *pHead = newListNode; else{ ListNode *pNode = *pHead; while(pNode -> next != null){ pNode = pNode ->next; } pNode ->next = newListNode; } }
为什么这里的参数是指向指针的指针?因为如果参数只是一个指针,而它指向的是null,那么如果在函数中将pHead=newListNode,那么出了这个函数pHead仍旧为null。
例如:如果函数为void addToTail(ListNode * pHead,int value); 传递来的参数为(pRoot,value); 那么实际上我们传进来的实际上是pRoot的一个副本,这个副本等于newListNode,而pRoot实际上指向的还是null。所以,如果一个函数涉及到头指针的变动就要以头指针的指针作为函数参数。
二、从尾到头打印链表
方法一、利用递归的方法实现,缺点:当链表很长时,会导致函数调用的层级很深,从而有可能导致函数调用栈溢出。
void printReverseList(ListNode * pHead){ if(pHead != null){ ListNode * pNode = pHead; if(pHead -> next == null) printf("%d\t",pNode -> value); printReverseList(pHead -> next); } }
方法二、利用栈实现
void printReverseList(ListNode * pHead){ std::stack<ListNode*> nodes; ListNode * pNode = pHead; while(pNode != null){ nodes.push(pNode); pNode = pNode -> next; } while(!nodes.empty()){ pNode = nodes.top(); printf("%d\t",pNode -> value); nodes.pop(); } }
三、打印倒数第k个节点
方法一:如果链表共有n个节点,倒数第1个节点是链表中的第n个节点,倒数第2个节点是链表中的第n-1个节点,所以倒是第k个节点就是第n-k+1个节点,所以我们可以先遍历一次链表求出链表的长度n,然后再遍历一次找到第n-k+1个节点;
方法二:将链表从头到尾入栈,然后出栈,第k个出栈的就是倒数第k个节点,代码如下:
int nodeK(ListNode* pHead,int k){ if(pHead == null) throw exception("there is less then k nodes"); int count = 0; ListNode * pNode = pHead; stack<ListNode> stackOfListNode; while(pNode != null){ count++; pNode = pNode -> pNext; stackOfListNode.push(pNode); } if(count < k) throw exception("there is less then k nodes"); int index = k; while(index > 0){ stackOfListNode.pop(); index--; } return stackOfListNode.top()->value; }
方法三:如果只能遍历一次,那么可以用两个链表来实现,这里我们不知道链表的节点数n。
第一个指针pFirst = pHead ------>先走x步------->走到最后一个节点n
第二个指针pSecond = pHead------->不动--------->到第n-k+1个节点
我们要算的是两个指针之间的距离x,x = n - (n-k+1) = k - 1;
LinkNode * findKToTail(LinkNode * pHead,int k){ //如果pHead为NULL或k=0那么没有倒数第k个节点 if(pHead == NULL && k == 0) return NULL; LinkNode * pFirst = pHead; LinkNode * pSecond = pHead; int i = k; while(i > 0 && pFirst -> next != NULL){ pFirst = pFirst -> next; } //如果i>1说明总的节点个数小于k if(i > 1) return NULL; while(pFirst -> next != NULL){ pFirst = pFirst -> next; pSecond = pSecond -> next; } return pSecond; }
延伸:求链表的中间节点,如果节点总数是奇数个返回中间的那个,如果节点总数是偶数个返回中间任意一个即可:
while(pSecond -> next != NULL && pSecond -> next - >next != NULL){ pSecond = pSecond -> next -> next; pFirst = pFirst -> next; }
四、判断链表是否有环,如果有,判断环开始的节点
定义两个指针均指向链表头,一个指针每次走一步,两外一个每次走两步,如果第二个指针能够追上第一个指针那么链表存在环,如果第二个指针走到NULL也没有追上第一个指针那么链表不存在环。如果存在环,相遇点必然在环内,并且第一个指针必定还没有在环内走完一圈,第二个指针在环内可能走了不止一圈,设由起点到入环点由p步,第一个指针在相遇处距离入环点为c,那么第一个指针走的步数n = p + c;第二个指针走了2n = p + c + k * L,其中k为第二个指针走的环数,L为环的长度;因此n
= p + c = k * L;这个时候如果让第二个指针从起点开始再走n步,第一个指针从现在相遇处走n步,那么它们最后走的c步是相同的,它们下次相遇的地方一定在环入口处。
LinkNode * findCircle(LinkNode * pHead){ if(pHead == NULL) return NULL; LinkNode * pFirst = pHead; LinkNode * pSecond = pHead; int count = 0; bool flag = false; while(pSecond -> next != NULL && pSecond -> next - >next != NULL) { pSecond = pSecond -> next -> next; pFirst = pFirst -> next; count++; if(pFirst == pSecond){ flag = true; break; } } //这里也可以不用flag //if(pSecond -> next == NULL && pSecond -> next -> next == NULL) //return NULL; if(!flag) return NULL; pSecond = pHead; while(pFirst != pSecond){ pFirst = pFirst -> next; pSecond = pSecond -> next; } return pSecond; }
五、反转链表
输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
如有一个链表A->B->C->D->E,我们对其进行翻转。假设我们已经进行了一些操作,是的一部分链表反转了,即A<-B<-C D->E,我们接下来反转D,因为我们要将D的pNext指
向C,所以我们必须先记录下C,同时我们将D的pNext指向C后还必须要能找到下一个结点E,所以我们需要三个结点——当前结点,当前结点的前一个结点和当前结点的后一
个结点。
LinkNode * reverseLink(LinkNode * pHead){ //注意点,链表只有一个节点或为NULL LinkNode * reverseLinkNode = NULL; LinkNode * pNode = pHead; LinkNode * pPrev = NULL; while(pNode != NULL){ LinkNode * pNext = pNode -> next; if(pNext == NULL) reverseLinkNode = pNode; pNode -> next = pPrev; pPrev = pNode; pNode = pNext; } return reverseLinkNode; }
或者这样:
LinkNode * reverseLink(LinkNode * pHead){ //注意点,链表只有一个节点或为NULL if(pHead == NULL) return NULL; LinkNode * pNode = pHead; LinkNode * pPrev = NULL; LinkNode * pNext = NULL; while(pNode -> next != NULL){ pNext = pNode -> next; pNode -> next = pPrev; pPrev = pNode; pNode = pNext; } pNode -> next = pPrev; return pNode; }
递归实现:待续
六、合并两个排序的链表
思路一:假设我们有两个链表1->3->5->7和2->4->6->8。我们采用递归的方式,假设我们通过函数MergeLinks已经合并了一部分链表1->2->3,还剩下5->7和4->6->8,那么我
们该如何做呢,比较剩下的链表的第一个结点的值,谁小就把谁返回放到3的后边。这个函数中必须有一个变量pMergeHead用来接收这个比较小的结点,同时它的next来接收
下一次递归调用MergeLinks返回的头结点。那么函数的终止条件是什么呢,那就是当结点指向的是NULL的时候,如果某个head结点指向的是NULL了,那么就把剩下的那个
head结点链接到已经排序好的链表。
LinkNode * MergeLinks(LinkNode * pHead1,LinkNode * pHead2){ if(pHead1 == NULL) return pHead2; if(pHead2 == NULL) return pHead1; LinkNode * pMergeHead = NULL; if(pHead1 -> value < pHead2 -> value){ pMergeHead = pHead1; pMergeHead -> next = MergeLinks(pHead1 -> next,pHead2); } if(pHead1 -> value >= pHead2 -> value){ pMergeHead = pHead2; pMergeHead -> next = MergeLinks(pHead1,pHead2 -> next); } return pMergeHead; }
思路二:利用归并排序的思想
LinkNode * MergeLinks(LinkNode * pHead1,LinkNode * pHead2){ if(pHead1 == NULL) return pHead2; if(pHead2 == NULL) return pHead1; LinkNode * pHead = NULL; LinkNode * pNode = NULL; if(pHead1 -> value < pHead2 -> value){ pHead = pHead1; pHead1 = pHead1 -> next; pNode = pHead; }else{ pHead = pHead2; pHead2 = pHead2 -> next; pNode = pHead; } while(pHead1 != NULL && pHead2 != NULL){ if(pHead1 -> value < pHead2 -> value){ pNode -> next = pHead1; pHead1 = pHead1 -> next; }else{ pNode -> next = pHead2; pHead2 = pHead1 -> next; } } if(pHead1 != NULL) pNode -> next = pHead1; if(pHead2 != NULL) pNode -> next = pHead2; return pHead; }