單鏈表
單鏈表是一種鏈式存取的數據結構,用一組地址任意的存儲單元存放線性表中的數據元素。鏈表中的數據是以結點來表示的,每個結點的構成:元素(數據元素的映象) + 指針(指示后繼元素存儲位置),元素就是存儲數據的存儲單元,指針就是連接每個結點的地址數據。
看圖說話:
上圖是單鏈表在內存中的存儲結構,也許我們常常熟悉的單鏈表是這樣的:
但是不能單純的以為它是按順序存儲,這里只是為了形象的展示單鏈表罷了,它的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 }
所有隨筆,皆為復習鞏固。如有錯誤,歡迎指正。