链表的经典问题
鏈表的經典問題
?
?
如何判斷兩個單鏈表是否相交,如果相交,找出交點(兩個鏈表都不存在環)
如果兩個單鏈表相交,那應該呈“Y”字形,也就是從交點以后的部分是兩個鏈表的公共節點。
所以,判斷是否相交只要看兩個鏈表的最后一個節點是否為同一個即可。
那么如何找到交點呢?設兩個單鏈表的長度分別為L1、L2,(假設L1>L2),則(L1-L2)的值就是交匯之前兩個鏈表的長度差;
因此,只有讓更長的鏈表先走L1-L2步,然后兩個鏈表開始一起走,如果某次走到一個相同的節點,該節點即為交點。
?
C代碼實現:
typedef struct _ListNode {int data;struct _ListNode *next; } ListNode;static int GetListLength(ListNode *T) {int n;for(n=0; T; n++) {T = T->next; } return n; }static ListNode* FindFirstCommonNode(ListNode *T1, ListNode *T2) {int i;int n1 = GetListLength(T1);int n2 = GetListLength(T2);// T1 always own longer listif (n1 < n2) {return FindFirstCommonNode(T2, T1);} for (i=0; i<n1-n2; i++) {T1 = T1->next;} while (T1 && T1 != T2) {T1 = T1->next;T2 = T2->next; } return T1; }?
該問題還有一種思路,就是將其中一個鏈表首尾相連,然后檢測另外一個鏈表是否有環,如果存在環,則兩個鏈表相交。
?
判斷一個鏈表是否有環,并找到環的入口點
如果一個單鏈表有環,那應該呈“6”字形。
設置兩個指針(fast, slow),初始值都指向頭節點,slow每次前進一步,fast每次前進二步,如果鏈表存在環,則fast必定先進入環,而slow后進入環,兩個指針必定 相遇:如果鏈表是呈"O"字形,則slow剛好遍歷完一次的時候,與fast相遇;如果呈“6”字形,則更早相遇。
當fast若與slow相遇時,slow還沒有遍歷完鏈表,而fast已經在環內循環了n圈(1<=n)。假設slow走了s步,則 fast走了2s步(fast步數還等于s 加上在環上多轉的n圈),設環長為r,則:
2s = s + nr,簡化為?s= nr
s = x + y,x為鏈表起點到環入口點的距離,y是slow在環內走過的距離;
可以得到 x = y - s = y - nr,從鏈表頭、相遇點分別設一個指針(p1, p2),每次各走一步,當p1走過距離x時到達入口點,而p2走過的距離為y-nr,y是相遇點與入口點的距離,因此y也走到了入口點,也就是說p1、p2在環入口點相遇了。
?
C代碼實現:
static ListNode* FindLoopPort(ListNode *Head) { ListNode *slow = Head; ListNode *fast = Head; // 找到相遇點while ( fast && fast->next ) { slow = slow->next; fast = fast->next->next; if ( slow == fast ) break; } if (fast == NULL || fast->next == NULL) return NULL; // 找到環入口點slow = Head; while (slow != fast) { slow = slow->next; fast = fast->next; } return slow; }?
?
求一個單鏈表(無環)的中間節點
設置兩個指針(fast, slow),初始值都指向頭節點,slow每次前進一步,fast每次前進二步,當fast走到末尾時,slow剛好指向中間節點。
?
假如鏈表長度為N,如何返回鏈表的倒數第K個結點
思路:用兩個指針,指針P1先走K-1步,然后指針P2才開始走,當指針P1遍歷完鏈表時,P2還剩K個結點沒有遍歷。
?
實現如下:
ListNode *FindLastKNode(ListNode *Head, int K) {if (NULL == Head)return NULL;ListNode *T1 = Head;ListNode *T2 = Head;while (T1 && --K) T1 = T1->next; if (K) // here, K must be 0 return NULL; while (T1->next) { T1 = T1->next; T2 = T2->next; } return T2; }?
?
在O(1)時間刪除鏈表結點
在鏈表中刪除一個結點,最常規的做法是遍歷鏈表,找到要刪除的結點后再刪除,這種做法的時間復雜度是O(n);
換一種思路,根據待刪除結點A,可以知道其下一個結點是B=A->next,將結點B值拷貝給A,然后刪除B即可。
這種方法需要考慮一種特殊情況,A如果是尾結點,則B不存在,此時仍需要遍歷鏈表一次。
?
C代碼實現:
void DeleteNode(ListNode* Head, ListNode *pDel) {if (NULL == pDel || NULL == Head)return;ListNode *p = Head;if (NULL == pDel->next) { // pDel is the last nodewhile (pDel != p->next)p = p->next;p->next = NULL;free(pDel);} else {p = pDel->next;pDel->next = p->next;pDel->data = p->data;free(p);} }
?
?
如何逆序輸出一個單鏈表
方法一:從頭到尾遍歷鏈表,每經過一個結點的時候,把該結點放到一個棧中;當遍歷完整個鏈表后,再從棧頂開始輸出結點的值。
該方法需要維護一個額外的棧,實現起來比較麻煩。我們注意到遞歸本質上就是一個棧結構,所以,也可以用遞歸來實現反向輸出鏈表。
方法二:也就是說,每訪問到一個結點的時候,先遞歸輸出它后面的結點,再輸出該結點自身。
?
C代碼實現:
void ReversePrint(ListNode *Head) {if (Head) {if (Head->next) {ReversePrint(Head->next);}printf("%d ", Head->data);} }?
?
?
如何反轉一個單鏈表
?利用輔助指針就地修改節點的next域,代碼如下:
static ListNode *ReverseList(ListNode *Head) {ListNode *pNode = Head;ListNode *pNext = NULL;ListNode *pPrev = NULL;while (pNode) {pNext = pNode->next; if (NULL == pNext) // meet the endHead = pNode;pNode->next = pPrev;pPrev = pNode;pNode = pNext;} return Head; }?
遞歸 的實現方法:
static void ReverseList2(ListNode** Head) {ListNode *p = *Head;if (!p) return;ListNode* rest = p->next;if (!rest) return;ReverseList2(&rest);rest->next = p;p->next = NULL; }?
?
鏈表的排序
歸并排序實現的時間復雜度為 nlgn,
struct ListNode* Merge(struct ListNode* p1, struct ListNode *p2) {if (!p1) return p2;if (!p2) return p1;if (p1->val > p2->val) {p2->next = Merge(p1, p2->next);return p2;} else {p1->next = Merge(p1->next, p2);return p1;}}struct ListNode* sortList(struct ListNode* head) {if (!head || !head->next) return head;struct ListNode *h1, *h2;struct ListNode *p1 = head, *p2 = head, *p = head;while (p2 && p2->next) {p = p1;p1 = p1->next;p2 = p2->next->next;}p->next = NULL;h1 = sortList(p1);h2 = sortList(head);return Merge(h1, h2); }以上遞歸的實現會占用lgN的空間(遞歸壓棧),非遞歸的實現如下:
?
?
復雜鏈表的復制
假設有一個復雜鏈表,它除了有一個next指針外,還有一個other指針,指向鏈表中的任一結點或者NULL,
typedef struct _ListNode {int data;struct _ListNode *next;struct _ListNode *other; } ListNode;?
如下圖,是一個含義5個節點的該類型的復雜鏈表,實線表示next指針,虛線表示other指針,NULL指針未標出。
?
最簡單的方法是,先復制所有節點,并用next指針鏈接起來,然后假設原始鏈表的某節點N的other指針指向節點S,由于S的位置可能在N的前面,也可能在N的后面,所以要定位N的位置需要從原始鏈表的頭節點開始找,直到確認節點S在鏈表中的位置為s;然后在復制鏈表上節點N的other指針也要指向距離鏈表頭的第s個節點。這種方法的時間復雜度是O(n2)。
上面這種方法的主要缺點在于無法快速定位N節點的other所指向的S節點的位置,
?
下面將介紹一種時間復雜度是O(n)的方法,首先把復制的節點串到原節點后面,如下圖:
?
然后設置復制節點的other指針(例如 A'->other = A->other->next),如下圖
最后,把偶數順序的節點和奇數節點的指針分開。
?
// 逐個節點復制,并串到原節點后面 static void CloneNodes(ListNode *Head) {ListNode *p = Head;while (p) {ListNode *pCloned = malloc(sizeof(ListNode));pCloned->data = p->data;pCloned->next = p->next;pCloned->other = NULL;p->next = pCloned;p = pCloned->next;} }// 設置新節點的other指針 static void ConnectNodes(ListNode *Head) {ListNode *pCloned;ListNode *p = Head;while(p){pCloned = p->next;if (p->other) {pCloned->other = p->other->next;}p = pCloned->next;} }?
轉載于:https://www.cnblogs.com/chenny7/p/4113552.html
總結
- 上一篇: c#自动更新+安装程序的制作
- 下一篇: MySql 表分区