日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

单链表操作大全

發布時間:2024/4/15 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 单链表操作大全 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

明,本文所有13道算法題目,覆蓋了基本上所有常見的單鏈表問題,全都用C#實現,并測試通過,代碼下載:TestLink.zip

?

1.單鏈表反轉

2.找出單鏈表的倒數第4個元素

3.找出單鏈表的中間元素

4.刪除無頭單鏈表的一個節點

5.兩個不交叉的有序鏈表的合并

6.有個二級單鏈表,其中每個元素都含有一個指向一個單鏈表的指針。寫程序把這個二級鏈表稱一級單鏈表。

7.單鏈表交換任意兩個元素(不包括表頭)

8.判斷單鏈表是否有環?如何找到環的“起始”點?如何知道環的長度?

9.判斷兩個單鏈表是否相交

10.兩個單鏈表相交,計算相交點

11.用鏈表模擬大整數加法運算

12.單鏈表排序

13.刪除單鏈表中重復的元素

?

首先寫一個單鏈表的C#實現,這是我們的基石:

public class Link {public Link Next;public string Data;public Link(Link next, string data){this.Next = next;this.Data = data;} }

?

其中,我們需要人為地在單鏈表前面加一個空節點,稱其為head。例如,一個單鏈表是1->2->5,如圖所示:

對一個單鏈表的遍歷如下所示:

static void Main(string[] args) {Link head = GenerateLink(); Link curr = head; while (curr != null){Console.WriteLine(curr.Data);curr = curr.Next;} }

?

1.單鏈表反轉

這道題目有兩種算法,既然是要反轉,那么肯定是要破壞原有的數據結構的:

算法1:我們需要額外的兩個變量來存儲當前節點curr的下一個節點next、再下一個節點nextnext:

public static Link ReverseLink1(Link head) {Link curr = head.Next;Link next = null;Link nextnext = null;//if no elements or only one element existsif (curr == null || curr.Next == null){return head;}//if more than one elementwhile (curr.Next != null){next = curr.Next; //1nextnext = next.Next; //2next.Next = head.Next; //3head.Next = next; //4curr.Next = nextnext; //5}return head; }

?

算法的核心是while循環中的5句話,畫一個圖來表示這5個步驟:

我們發現,curr始終指向第1個元素。

此外,出于編程的嚴謹性,還要考慮2種極特殊的情況:沒有元素的單鏈表,以及只有一個元素的單鏈表,都是不需要反轉的。

?

算法2:自然是遞歸

如果題目簡化為逆序輸出這個單鏈表,那么遞歸是很簡單的,在遞歸函數之后輸出當前元素,這樣能確保輸出第N個元素語句永遠在第N+1個遞歸函數之后執行,也就是說第N個元素永遠在第N+1個元素之后輸出,最終我們先輸出最后一個元素,然后是倒數第2個、倒數第3個,直到輸出第1個:

public static void ReverseLink2(Link head) {if (head.Next != null){ReverseLink2(head.Next);Console.WriteLine(head.Next.Data);} }

但是,現實應用中往往不是要求我們逆序輸出(不損壞原有的單鏈表),而是把這個單鏈表逆序(破壞型)。這就要求我們在遞歸的時候,還要處理遞歸后的邏輯。

首先,要把判斷單鏈表有0或1個元素這部分邏輯獨立出來,而不需要在遞歸中每次都比較一次:

public static Link ReverseLink3(Link head) {//if no elements or only one element existsif (head.Next == null || head.Next.Next == null)return head;head.Next = ReverseLink(head.Next);return head; } 我們觀測到: head.Next = ReverseLink(head.Next); 這句話的意思是為ReverseLink方法生成的逆序鏈表添加一個空表頭。 接下來就是遞歸的核心算法ReverseLink了: static Link ReverseLink(Link head) {if (head.Next == null)return head;Link rHead = ReverseLink(head.Next);head.Next.Next = head;head.Next = null;return rHead; }

?

算法的關鍵就在于遞歸后的兩條語句:

head.Next.Next = head; //1 head.Next = null; //2

啥意思呢?畫個圖表示就是:

這樣,就得到了一個逆序的單鏈表,我們只用到了1個額外的變量rHead。

?

2.找出單鏈表的倒數第4個元素

這道題目有兩種算法,但無論哪種算法,都要考慮單鏈表少于4個元素的情況:

第1種算法,建立兩個指針,第一個先走4步,然后第2個指針也開始走,兩個指針步伐(前進速度)一致。

static Link GetLast4thOne(Link head) {Link first = head;Link second = head;for (int i = 0; i < 4; i++){if (first.Next == null)throw new Exception("Less than 4 elements");first = first.Next;}while (first != null){first = first.Next;second = second.Next;}return second; }

?

第2種算法,做一個數組arr[4],讓我們遍歷單鏈表,把第0個、第4個、第8個……第4N個扔到arr[0],把第1個、第5個、第9個……第4N+1個扔到arr[1],把第2個、第6個、第10個……第4N+2個扔到arr[2],把第3個、第7個、第11個……第4N+3個扔到arr[3],這樣隨著單鏈表的遍歷結束,arr中存儲的就是單鏈表的最后4個元素,找到最后一個元素對應的arr[i],讓k=(i+1)%4,則arr[k]就是倒數第4個元素。

static Link GetLast4thOneByArray(Link head) {Link curr = head;int i = 0;Link[] arr = new Link[4];while (curr.Next != null){arr[i] = curr.Next;curr = curr.Next;i = (i + 1) % 4;}if (arr[i] == null)throw new Exception("Less than 4 elements");return arr[i]; }

?

本題目源代碼下載:

推而廣之,對倒數第K個元素,都能用以上2種算法找出來。

?

3.找出單鏈表的中間元素

算法思想:類似于上題,還是使用兩個指針first和second,只是first每次走一步,second每次走兩步:

static Link GetMiddleOne(Link head) {Link first = head;Link second = head;while (first != null && first.Next != null){first = first.Next.Next;second = second.Next;}return second; }

但是,這道題目有個地方需要注意,就是對于鏈表元素個數為奇數,以上算法成立。如果鏈表元素個數為偶數,那么在返回second的同時,還要返回second.Next也就是下一個元素,它倆都算是單鏈表的中間元素。

下面是加強版的算法,無論奇數偶數,一概通殺:

static void Main(string[] args) {Link head = GenerateLink();bool isOdd = true;Link middle = GetMiddleOne(head, ref isOdd);if (isOdd){Console.WriteLine(middle.Data);}else{Console.WriteLine(middle.Data);Console.WriteLine(middle.Next.Data);}Console.Read(); } static Link GetMiddleOne(Link head, ref bool isOdd) {Link first = head;Link second = head;while (first != null && first.Next != null){first = first.Next.Next;second = second.Next;}if (first != null)isOdd = false;return second; }

?

4.一個單鏈表,很長,遍歷一遍很慢,我們僅知道一個指向某節點的指針curr,而我們又想刪除這個節點。

這道題目是典型的“貍貓換太子”,如下圖所示:

?

如果不考慮任何特殊情況,代碼就2行:

curr.Data = curr.Next.Data; curr.Next = curr.Next.Next;

上述代碼由一個地方需要注意,就是如果要刪除的是最后一個元素呢?那就只能從頭遍歷一次找到倒數第二個節點了。

?

此外,這道題目的一個變身就是將一個環狀單鏈表拆開(即刪除其中一個元素),此時,只要使用上面那兩行代碼就可以了,不需要考慮表尾。

相關問題:只給定單鏈表中某個結點p(非空結點),在p前面插入一個結點q。

話說,交換單鏈表任意兩個節點,也可以用交換值的方法。但這樣就沒意思了,所以,才會有第7題霸王硬上工的做法。

?

5.兩個不交叉的有序鏈表的合并

有兩個有序鏈表,各自內部是有序的,但是兩個鏈表之間是無序的。

算法思路:當然是循環逐項比較兩個鏈表了,如果一個到了頭,就不比較了,直接加上去。

注意,對于2個元素的Data相等(僅僅是Data相等哦,而不是相同的引用),我們可以把它視作前面的Data大于后面的Data,從而節省了算法邏輯。

static Link MergeTwoLink(Link head1, Link head2) {Link head = new Link(null, Int16.MinValue);Link pre = head;Link curr = head.Next;Link curr1 = head1;Link curr2 = head2;//compare until one link run to the endwhile (curr1.Next != null && curr2.Next != null){if (curr1.Next.Data < curr2.Next.Data){curr = new Link(null, curr1.Next.Data);curr1 = curr1.Next;}else{curr = new Link(null, curr2.Next.Data);curr2 = curr2.Next;}pre.Next = curr;pre = pre.Next;}//if head1 run to the endwhile (curr1.Next != null){curr = new Link(null, curr1.Next.Data);curr1 = curr1.Next;pre.Next = curr;pre = pre.Next;}//if head2 run to the endwhile (curr2.Next != null){curr = new Link(null, curr2.Next.Data);curr2 = curr2.Next;pre.Next = curr;pre = pre.Next;}return head; }

?

如果這兩個有序鏈表交叉組成了Y型呢,比如說:

這時我們需要先找出這個交叉點(圖中是11),這個算法參見第9題,我們這里直接使用第10道題目中的方法GetIntersect。

然后局部修改上面的算法,只要其中一個鏈表到達了交叉點,就直接把另一個鏈表的剩余元素都加上去。如下所示:

static Link MergeTwoLink2(Link head1, Link head2) {Link head = new Link(null, Int16.MinValue);Link pre = head;Link curr = head.Next;Link intersect = GetIntersect(head1, head2);Link curr1 = head1;Link curr2 = head2;//compare until one link run to the intersectwhile (curr1.Next != intersect && curr2.Next != intersect){if (curr1.Next.Data < curr2.Next.Data){curr = new Link(null, curr1.Next.Data);curr1 = curr1.Next;}else{curr = new Link(null, curr2.Next.Data);curr2 = curr2.Next;}pre.Next = curr;pre = pre.Next;}//if head1 run to the intersectif (curr1.Next == intersect){while (curr2.Next != null){curr = new Link(null, curr2.Next.Data);curr2 = curr2.Next;pre.Next = curr;pre = pre.Next;}}//if head2 run to the intersectelse if (curr2.Next == intersect){while (curr1.Next != null){curr = new Link(null, curr1.Next.Data);curr1 = curr1.Next;pre.Next = curr;pre = pre.Next;}}return head; }

?

6.有個二級單鏈表,其中每個元素都含有一個指向一個單鏈表的指針。寫程序把這個二級鏈表展開稱一級單鏈表。

這個簡單,就是說,這個二級單鏈表只包括一些head:

public class Link {public Link Next;public int Data;public Link(Link next, int data){this.Next = next;this.Data = data;} } public class CascadeLink {public Link Next;public CascadeLink NextHead;public CascadeLink(CascadeLink nextHead, Link next){this.Next = next;this.NextHead = nextHead;} }

?

下面做一個二級單鏈表,GenerateLink1和GenerateLink2方法在前面都已經介紹過了:

public static CascadeLink GenerateCascadeLink() {Link head1 = GenerateLink1();Link head2 = GenerateLink2();Link head3 = GenerateLink1();CascadeLink element3 = new CascadeLink(null, head3);CascadeLink element2 = new CascadeLink(element3, head2);CascadeLink element1 = new CascadeLink(element2, head1);CascadeLink head = new CascadeLink(element1, null);return head; } 就是說,這些單鏈表的表頭head1、head2、head3、head4……,它們組成了一個二級單鏈表head:null –> head1 –> head2 –> head3 –> head4 –>

?

我們的算法思想是: 進行兩次遍歷,在外層用curr1遍歷二級單鏈表head,在內層用curr2遍歷每個單鏈表:

public static Link GenerateNewLink(CascadeLink head) {CascadeLink curr1 = head.NextHead;Link newHead = curr1.Next;Link curr2 = newHead;while (curr1 != null){curr2.Next = curr1.Next.Next;while (curr2.Next != null){curr2 = curr2.Next;}curr1 = curr1.NextHead;}return newHead; } 其中,curr2.Next = curr1.Next.Next; 這句話是關鍵,它負責把上一個單鏈表的表尾和下一個單鏈表的非空表頭連接起來。

?

7.單鏈表交換任意兩個元素(不包括表頭)

先一次遍歷找到這兩個元素curr1和curr2,同時存儲這兩個元素的前驅元素pre1和pre2。

然后大換血

public static Link SwitchPoints(Link head, Link p, Link q) {if (p == head || q == head)throw new Exception("No exchange with head");if (p == q)return head;//find p and q in the linkLink curr = head;Link curr1 = p;Link curr2 = q;Link pre1 = null;Link pre2 = null;int count = 0;while (curr != null){if (curr.Next == p){pre1 = curr;count++;if (count == 2)break;}else if (curr.Next == q){pre2 = curr;count++;if (count == 2)break;}curr = curr.Next;}curr = curr1.Next;pre1.Next = curr2;curr1.Next = curr2.Next;pre2.Next = curr1;curr2.Next = curr;return head; } 注意特例,如果相同元素,就沒有必要交換;如果有一個是表頭,就不交換。

?

8.判斷單鏈表是否有環?如何找到環的“起始”點?如何知道環的長度?

算法思想:

先分析是否有環。為此我們建立兩個指針,從header一起向前跑,一個步長為1,一個步長為2,用while(直到步長2的跑到結尾)檢查兩個指針是否相等,直到找到為止。

static bool JudgeCircleExists(Link head) {Link first = head; //1 step each timeLink second = head; //2 steps each timewhile (second.Next != null && second.Next.Next != null){second = second.Next.Next;first = first.Next;if (second == first)return true;}return false; }

?

那又如何知道環的長度呢?

根據上面的算法,在返回true的地方,也就是2個指針相遇處,這個位置的節點P肯定位于環上。我們從這個節點開始先前走,轉了一圈肯定能回來:

static int GetCircleLength(Link point) {int length = 1;Link curr = point;while (curr.Next != point){length++;curr = curr.Next;}return length; }

?

繼續我們的討論,如何找到環的“起始”點呢?

延續上面的思路,我們仍然在返回true的地方P,計算一下從有環單鏈表的表頭head到P點的距離

static int GetLengthFromHeadToPoint(Link head, Link point) {int length = 1;Link curr = head;while (curr != point){length++;curr = curr.Next;}return length; }

如果我們把環從P點“切開”(當然并不是真的切,那就破壞原來的數據結構了),那么問題就轉化為計算兩個相交“單鏈表”的交點(第10題):

一個單鏈表是從P點出發,到達P(一個回圈),距離M;另一個單鏈表從有環單鏈表的表頭head出發,到達P,距離N。

我們可以參考第10題的GetIntersect方法并稍作修改。

private static Link FindIntersect(Link head) {Link p = null;//get the point in the circlebool result = JudgeCircleExists(head, ref p);if (!result) return null;Link curr1 = head.Next;Link curr2 = p.Next;//length from head to pint M = 1;while (curr1 != p){M++;curr1 = curr1.Next;}//circle lengthint N = 1;while (curr2 != p){N++;curr2 = curr2.Next;}//recover curr1 & curr2curr1 = head.Next;curr2 = p.Next;//make 2 links have the same distance to the intersectif (M > N){for (int i = 0; i < M - N; i++)curr1 = curr1.Next;}else if (M < N){for (int i = 0; i < N - M; i++)curr2 = curr2.Next;}//goto the intersectwhile (curr1 != p){if (curr1 == curr2){return curr1;}curr1 = curr1.Next;curr2 = curr2.Next;}return null; }

?

9.判斷兩個單鏈表是否相交

這道題有多種算法。

算法1:把第一個鏈表逐項存在hashtable中,遍歷第2個鏈表的每一項,如果能在第一個鏈表中找到,則必然相交。

static bool JudgeIntersectLink1(Link head1, Link head2) {Hashtable ht = new Hashtable();Link curr1 = head1;Link curr2 = head2;//store all the elements of link1while (curr1.Next != null){ht[curr1.Next] = string.Empty;curr1 = curr1.Next;}//check all the elements in link2 if exists in Hashtable or notwhile (curr2.Next != null){//if existsif (ht[curr2.Next] != null){return true;}curr2 = curr2.Next;}return false; }

算法2:把一個鏈表A接在另一個鏈表B的末尾,如果有環,則必然相交。如何判斷有環呢?從A開始遍歷,如果能回到A的表頭,則肯定有環。

注意,在返回結果之前,要把剛才連接上的兩個鏈表斷開,恢復原狀。

static bool JudgeIntersectLink2(Link head1, Link head2) {bool exists = false;Link curr1 = head1;Link curr2 = head2;//goto the end of the link1while (curr1.Next != null){curr1 = curr1.Next;}//join these two linkscurr1.Next = head2;//iterate link2while (curr2.Next != null){if (curr2.Next == head2){exists = true;break;}curr2 = curr2.Next;}//recover original status, whether exists or notcurr1.Next = null;return exists; }

算法3:如果兩個鏈表的末尾元素相同,則必相交。

static bool JudgeIntersectLink3(Link head1, Link head2) {Link curr1 = head1;Link curr2 = head2;//goto the end of the link1while (curr1.Next != null){curr1 = curr1.Next;}//goto the end of the link2while (curr2.Next != null){curr2 = curr2.Next;}if (curr1 != curr2)return false;elsereturn true; }

?

10.兩個單鏈表相交,計算相交點

分別遍歷兩個單鏈表,計算出它們的長度M和N,假設M比N大,則長度M的鏈表先前進M-N,然后兩個鏈表同時以步長1前進,前進的同時比較當前的元素,如果相同,則必是交點。

public static Link GetIntersect(Link head1, Link head2) {Link curr1 = head1;Link curr2 = head2;int M = 0, N = 0;//goto the end of the link1while (curr1.Next != null){curr1 = curr1.Next;M++;}//goto the end of the link2while (curr2.Next != null){curr2 = curr2.Next;N++;}//return to the begining of the linkcurr1 = head1;curr2 = head2;if (M > N){for (int i = 0; i < M - N; i++)curr1 = curr1.Next;}else if (M < N){for (int i = 0; i < N - M; i++)curr2 = curr2.Next;}while (curr1.Next != null){if (curr1 == curr2){return curr1;}curr1 = curr1.Next;curr2 = curr2.Next;}return null; }

?

11.用鏈表模擬大整數加法運算

例如:9>9>9>NULL + 1>NULL =>? 1>0>0>0>NULL

肯定是使用遞歸啦,不然沒辦法解決進位+1問題,因為這時候要讓前面的節點加1,而我們的單鏈表是永遠指向前的。

此外對于999+1=1000,新得到的值的位數(4位)比原來的兩個值(1個1位,1個3位)都多,所以我們將表頭的值設置為0,如果多出一位來,就暫時存放到表頭。遞歸結束后,如果表頭為1,就在新的鏈表外再加一個新的表頭。

//head1 length > head2, so M > N public static int Add(Link head1, Link head2, ref Link newHead, int M, int N) {// goto the endif (head1 == null)return 0;int temp = 0;int result = 0;newHead = new Link(null, 0);if (M > N){result = Add(head1.Next, head2, ref newHead.Next, M - 1, N);temp = head1.Data + result;newHead.Data = temp % 10;return temp >= 10 ? 1 : 0;}else // M == N{result = Add(head1.Next, head2.Next, ref newHead.Next, M - 1, N - 1);temp = head1.Data + head2.Data + +result;newHead.Data = temp % 10;return temp >= 10 ? 1 : 0;} }

?

這里假設head1比head2長,而且M、N分別是head1和head2的長度。

?

12.單鏈表排序

無外乎是冒泡、選擇、插入等排序方法。關鍵是交換算法,需要額外考慮。第7題我編寫了一個交換算法,在本題的排序過程中,我們可以在外層和內層循環里面,捕捉到pre1和pre2,然后進行交換,而無需每次交換又要遍歷一次單鏈表。

在實踐中,我發現冒泡排序和選擇排序都要求內層循環從鏈表的末尾向前走,這明顯是不合時宜的。

所以我最終選擇了插入排序算法,如下所示:

先給出基于數組的算法:

代碼 ??????? staticint[] InsertSort(int[] arr) ??????? { ??????????? for (int i =1; i < arr.Length; i++) ??????????? { ??????????????? for (int j = i; (j >0) && arr[j] < arr[j -1]; j--) ??????????????? { ??????????????????? arr[j] = arr[j] ^ arr[j -1]; ??????????????????? arr[j -1] = arr[j] ^ arr[j -1]; ??????????????????? arr[j] = arr[j] ^ arr[j -1]; ??????????????? } ??????????? }
???????????
return arr; ??????? }

?

仿照上面的思想,我們來編寫基于Link的算法:

public static Link SortLink(Link head) {Link pre1 = head;Link pre2 = head.Next;Link min = null;for (Link curr1 = head.Next; curr1 != null; curr1 = min.Next){if (curr1.Next == null)break;min = curr1;for (Link curr2 = curr1.Next; curr2 != null; curr2 = curr2.Next){//swap curr1 and curr2if (curr2.Data < curr1.Data){min = curr2;curr2 = curr1;curr1 = min;pre1.Next = curr1;curr2.Next = curr1.Next;curr1.Next = pre2;//if exchange element n-1 and n, no need to add reference from pre2 to curr2, because they are the same oneif (pre2 != curr2)pre2.Next = curr2;}pre2 = curr2;}pre1 = min;pre2 = min.Next;}return head; }

?

值得注意的是,很多人的算法不能交換相鄰兩個元素,這是因為pre2和curr2是相等的,如果此時還執行pre2.Next = curr2; 會造成一個自己引用自己的環。

?

交換指針很是麻煩,而且效率也不高,需要經常排序的東西最好不要用鏈表來實現,還是數組好一些。

?

13.刪除單鏈表中重復的元素

用Hashtable輔助,遍歷一遍單鏈表就能搞定。

實踐中發現,curr從表頭開始,每次判斷下一個元素curr.Netx是否重復,如果重復直接使用curr.Next = curr.Next.Next; 就可以刪除重復元素——這是最好的算法。唯一的例外就是表尾,所以到達表尾,就break跳出while循環。

public static Link DeleteDuplexElements(Link head) {Hashtable ht = new Hashtable();Link curr = head;while (curr != null){if (curr.Next == null){break;}if (ht[curr.Next.Data] != null){curr.Next = curr.Next.Next;}else{ht[curr.Next.Data] = "";}curr = curr.Next;}return head; }

?

?

結語:

單鏈表只有一個向前指針Next,所以要使用1-2個額外變量來存儲當前元素的前一個或后一個指針。

盡量用while循環而不要用for循環,來進行遍歷。

哇塞,我就是不用指針,照樣能“修改地址”,達到和C++同樣的效果,雖然很煩~

遍歷的時候,不要在while循環中head=head.Next;這樣會改變原先的數據結構。我們要這么寫:Link curr=head;然后curr=curr.Next;

有時我們需要臨時把環切開,有時我們需要臨時把單鏈表首尾相連成一個環。

究竟是玩curr還是curr.Next,根據不同題目而各有用武之地,沒有定論,不必強求。

轉載于:https://www.cnblogs.com/binyao/archive/2013/04/30/3051839.html

總結

以上是生活随笔為你收集整理的单链表操作大全的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。