數據結構之單鏈表(合並兩個單鏈表有序化、單鏈表反轉、頭插尾插,CRUD)


單鏈表

單鏈表是一種鏈式存取的數據結構,用一組地址任意的存儲單元存放線性表中的數據元素。鏈表中的數據是以結點來表示的,每個結點的構成:元素(數據元素的映象) + 指針(指示后繼元素存儲位置),元素就是存儲數據的存儲單元,指針就是連接每個結點的地址數據。

  看圖說話:

  上圖是單鏈表在內存中的存儲結構,也許我們常常熟悉的單鏈表是這樣的:

   但是不能單純的以為它是按順序存儲,這里只是為了形象的展示單鏈表罷了,它的next域指向的不一定是按某種順序的。

接下來通過一個小Demo來全面實踐單鏈表

  先來定義結點的屬性:假設以《水滸傳》中的一位英雄作為一個結點,no代表他的編號,name表示名字,nickname表示昵稱,next為堆中指向下一個英雄結點的地址。(請忽略屬性權限)

 1 class HeroNode {
 2     public int no;
 3     public String name;
 4     public String nickname;
 5     public HeroNode next;
 6 
 7     public HeroNode(int hNo, String hName, String hNickname) {
 8         this.no = hNo;
 9         this.name = hName;
10         this.nickname = hNickname;
11     }
12 
13     @Override
14     public String toString() {
15         return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
16     }
17 }

  有了結點類,就可以創建頭結點,進而實現一條單鏈表了

private HeroNode head = new HeroNode(0, "", "");

   首先可以以head結點為頭結點,來加入結點,添加的方法分為 :1、 按照編號no順序添加(即升序,但不能存在相同no) 2、尾插法   3、頭插法

1、 按照編號no順序添加(即升序,但不能存在相同no)

  具體操作由兩行代碼搞定(也是頭插法的代碼)

 heroNode.next = temp.next;
temp.next = heroNode;

 

   接着添加第二個結點(no小於第一個結點)


 

 

 具體代碼如下:

 1 public void addByOrder(HeroNode heroNode) {//傳入一個結點
 2         HeroNode temp = head;//用一個temp變量代替head,方便遍歷
 3         boolean flag = false;//標記單鏈表中是否存在與heroNode節點no相同的結點
 4         while (true) {//遍歷單鏈表進行判斷
 5             if (temp.next == null)//如果頭節點指向為空,則直接跳出循環
 6                 break;
 7             if (temp.next.no > heroNode.no)//傳入的結點no與單鏈表中的no比較
 8                 break;
 9             if (temp.next.no == heroNode.no) {//有重復,則標記true
10                 flag = true;
11                 break;
12             }
13             temp = temp.next;//遍歷,指向下一個結點
14         }
15         if (!flag) {//前兩個if跳出循環,都會執行,具體看下圖
16             heroNode.next = temp.next;
17             temp.next = heroNode;
18         } else {
19             System.out.println("存在" + heroNode.no);
20         }
21     }

2、尾插法

 1 public void addEnd(HeroNode heroNode) {
 2         // 尾插法需要遍歷單鏈表到最后一個結點的下一個結點為null的上一個結點
 3         HeroNode temp = head;
 4         while (true) {
 5             if (temp.next == null)
 6                 break;
 7             else
 8                 temp = temp.next;
 9         }
10         temp.next = heroNode;
11     }

3、頭插法

1 public void addFirst(HeroNode heroNode) {
2         HeroNode temp = head;
3         heroNode.next = temp.next;
4         temp.next = heroNode;
5     }

進行了單鏈表的添加后,就要進行簡單的更新(修改和刪除),這里就比較簡單了,注意這里的頭結點不可刪除

由於第二個結點在堆內存中沒有指向任何結點,而且沒有任何節點指向改no為2的結點,於是該對象隨后會被GC回收掉

刪除結點代碼:

 1 public void delete(HeroNode heroNode) {
 2         HeroNode temp =head;
 3         while (true) {
 4             if (temp.next == null) {
 5                 System.out.println("鏈表為空");
 6                 break;
 7             }
 8             if (temp.next == heroNode) {
 9                 temp.next = heroNode.next;
10                 heroNode.next = null;
11                 break;
12             }
13             temp = temp.next;
14         }
15     }

修改結點代碼:

 1 public void update(HeroNode heroNode, String name) {
 2         HeroNode temp = head;
 3         while (true) {
 4             if (temp.next == null) {
 5                 System.out.println("鏈表為空");
 6                 break;
 7             }
 8             if (temp.next == heroNode) {
 9                 heroNode.name = name;
10                 break;
11             }
12             temp = temp.next;
13         }
14     }

 

  ok,到此單鏈表的增刪改查結束了,接下來進行進階:單鏈表反轉、合並兩個單鏈表並有序

單鏈表反轉(其實就是頭插法,不難)

 借助一個新的頭結點來生成反向單鏈表,最后再由原來的頭結點指向這個新的頭結點的下一個結點即可,分析圖如下:

 temp用來代替頭結點進行移動操作,next用於保存temp的下一個結點,reverseNode為輔助頭結點

 

   得到:

 

  進行頭插法

 

 

 

 

 具體反轉單鏈表代碼:

 1 public void revList() {
 2         // 關鍵就是借助了下面這個輔助頭節點
 3         HeroNode reverseNode = new HeroNode(0, "", "");
 4         // 下面這個是要保存當前節點的下一個節點的
 5         HeroNode next = null;
 6         HeroNode temp = head.next;// 將head后的一大串單鏈表copy一份交給temp管理
 7         while (true) {
 8             if (temp == null) {
 9                 break;// 單鏈表為空
10             }
11             next = temp.next;// 保存下一個節點,比如當前temp->1,那么next就等於temp->2
12             temp.next = reverseNode.next;// 1的next賦值為null,下一次就是2的next賦值為1
13             reverseNode.next = temp;// 這一步就是上面“下一次里的next”賦值為1的操作
14             temp = next;// 到這一步的時候,本次temp已經和原來的單鏈表斷開了,所以需要next提前保存temp.next
15         }
16         head.next = reverseNode.next;
17     }

合並兩個單鏈表並有序

  思路:將按照升序加入結點的兩個單鏈表進行升序合並。

 以此規律可得到結果:

 

 具體升序合並單鏈表代碼:

 1 public SingleLinkedList merge(SingleLinkedList sl, SingleLinkedList sl2) {
 2         SingleLinkedList ss = new SingleLinkedList();
 3         HeroNode temp1 = sl.head.next;
 4         HeroNode temp2 = sl2.head.next;
 5         HeroNode next = null;
 6         while (true) {
 7             if (temp1 == null && temp2 == null) {//當兩個鏈表都遍歷完時
 8                 break;
 9             }
10             if (temp1 == null && temp2 != null) {//當其中一個鏈表還有結點,但是另一個已經遍歷完,則將此鏈表剩余部分依次加入新鏈表ss
11                 next=temp2.next;
12                 ss.addByOrder(temp2);
13                 temp2 = next;
14             } else if (temp2 == null && temp1 != null) {//同上
15                 next=temp1.next;
16                 ss.addByOrder(temp1);
17                 temp1 = next;
18             }
19             if (temp1 != null && temp2 != null) {//都沒有遍歷完
20                 if (temp1.no < temp2.no) {//把no小的加入新鏈表
21                     next = temp1.next;//保存下一節點
22                     ss.addByOrder(temp1);//這一步會導致temp1指向改變,導致鏈表丟失
23                     temp1 = next;//所以需要恢復到temp1得下一個
24                 } else {
25                     next = temp2.next;
26                     ss.addByOrder(temp2);
27                     temp2 = next;
28                 }
29             }
30         }
31         return ss;
32     }

所有隨筆,皆為復習鞏固。如有錯誤,歡迎指正。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM