鏈表其實也就是 線性表的鏈式存儲結構,與以前講到的順序存儲結構不一樣。ide
咱們知道順序存儲結構中的元素地址都是連續的,那麼這就有一個最大的缺點:當作插入跟刪除操做的時候,大量的元素須要移動。
如圖所示,元素在內存中的位置是挨着的,當中有元素被刪除,就產生空隙,因而乎後面的元素須要向前挪動去彌補。
正是由於順序存儲有這這個缺點,因此鏈式存儲結構就變得很是的有意義。測試
首先,鏈表是有序的列表,可是在內存中它是這樣存儲的:
this
上圖所示中,各個結點不必定是連續存放的,最終會有N個節點連接成一個鏈表,因此就成了鏈式存儲結構。
另外,由於此鏈表的每一個結點中只包含一個next域,因此叫單鏈表。指針
上面提到了頭指針,它是鏈表的必要元素。
由於鏈表既然也是線性表,因此仍是要有頭有尾,頭指針就是鏈表中第一個結點的存儲位置。
而最後一個結點,指針指向空,一般用NULL表示或者'^'來表示。
code
與頭指針不一樣,頭結點是不必定要有的,得更具實際需求來定。
有時候爲了更加方便的操做鏈表,會在單鏈表的第一個結點前設一個結點,稱爲頭結點。對象
加了頭結點後,對於第一結點來講,在其以前插入結點或者刪除第一結點,操做方式就與其它的結點相同了,不須要進行額外的判斷處理。blog
頭結點跟其餘結點不一樣,它的數據域能夠不存儲任何信息,有必要的話,能夠存儲一些其餘的附加信息,好比線性表的長度等。
內存
如今咱們已經知道了單向鏈表的儲存形式以及其構成有哪些,那麼如今能夠用更直觀的圖來展現單向鏈表中數據元素之間的關係了。
it
好比,如今要用單鏈表來存儲LOL裏英雄的信息。若是不帶英雄排名順序的話,那麼能夠直接依次在鏈表的末尾增長新的結點便可。class
package linkedlist; public class SingleLinkedListDemo { public static void main(String[] args) { // 測試 HeroNode hero1 = new HeroNode(1, "易大師","無極劍聖"); HeroNode hero2 = new HeroNode(2, "李青","盲僧"); HeroNode hero3 = new HeroNode(3, "艾希","寒冰射手"); HeroNode hero4 = new HeroNode(4, "菲奧娜","無雙劍姬"); // 建立鏈表 SingleLinkedList singleLinkedList = new SingleLinkedList(); // 加入對象結點 singleLinkedList.addHero(hero1); singleLinkedList.addHero(hero2); singleLinkedList.addHero(hero3); singleLinkedList.addHero(hero4); // 顯示鏈表內容 singleLinkedList.linkList(); } } // 定義SingleLinkedList 管理英雄 class SingleLinkedList { // 初始化一個頭結點,不要動這個結點。 private HeroNode headNode = new HeroNode(0, "",""); // 添加結點 到 單向鏈表 // 當不考慮英雄順序時,找到當前鏈表的最後一個結點,再講此結點的next指向新的結點便可 public void addHero(HeroNode heroNode) { // 由於head結點不能動,因此新建一個臨時變量,幫助遍歷 HeroNode temp = headNode; // 開始遍歷鏈表,到最後,找最後的結點 while (true) { // 等於null時就是最後了 if (temp.next == null) { break; } // 不然就不是最後,將temp繼續向後移動 temp = temp.next; } // 直到退出循環,此時temp就指向了鏈表的最後 // 將最後的結點指向這個新的結點 temp.next = heroNode; } // 顯示鏈表內容的方法 public void linkList() { // 判斷鏈表是否爲空,空的話就不用繼續了 if (headNode.next == null) { System.out.println("鏈表爲空"); return; } HeroNode temp = headNode.next; while (true) { // 判斷是否已經到了鏈表最後 if (temp == null) { break; } // 輸出結點信息 System.out.println(temp); // 而後後移temp繼續輸出下一個結點 temp = temp.next; } } } // 定義HeroNode,每一個HeroNode對象就是一個結點 class HeroNode { public int no; public String name; public String nickname; public HeroNode next; // 指向下一個結點 // 構造器 public HeroNode(int heroNo, String heroName, String heroNickname) { this.no = heroNo; this.name = heroName; this.nickname = heroNickname; } // 爲了方便顯示,重寫toString方法 @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickname='" + nickname + '\'' + '}'; } }
運行一下
HeroNode{no=1, name='易大師', nickname='無極劍聖'} HeroNode{no=2, name='李青', nickname='盲僧'} HeroNode{no=3, name='艾希', nickname='寒冰射手'} HeroNode{no=4, name='菲奧娜', nickname='無雙劍姬'} Process finished with exit code 0
能夠看到,鏈表中的結點是按照添加的順序依次儲存的。
上面每一個英雄有本身的排名,那麼若是我想不關心添加的順序,在鏈表中最終均可以按照英雄的排名進行存儲,如何實現呢?
這裏的話就沒有上面直接在末尾添加那麼直接了,可是也不算難理解,看個示意圖。
如圖所示,如今有一個結點2要添加進來,那麼來梳理一下實現的思路:
是否是很簡單,不過爲了實現第2點,咱們仍是須要藉助一個輔助變量temp,能夠把它看做一個指針。
temp會從頭開始遍歷鏈表,來找到結點2應該添加到的位置,此時會停在結點1,那麼:
這樣咱們的目的就達成了,代碼也就知道怎麼去改了。
決定在SingleLinkedList類中,增長一個新方法,能夠跟據英雄的排名進行添加。
package linkedlist; public class SingleLinkedListDemo { public static void main(String[] args) { // 測試 HeroNode hero1 = new HeroNode(1, "易大師","無極劍聖"); HeroNode hero2 = new HeroNode(2, "李青","盲僧"); HeroNode hero3 = new HeroNode(3, "艾希","寒冰射手"); HeroNode hero4 = new HeroNode(4, "菲奧娜","無雙劍姬"); // 建立鏈表 SingleLinkedList singleLinkedList = new SingleLinkedList(); // 加入對象結點 singleLinkedList.addByNo(hero1); singleLinkedList.addByNo(hero4); singleLinkedList.addByNo(hero2); singleLinkedList.addByNo(hero3); // 顯示鏈表內容 singleLinkedList.linkList(); } } // 定義SingleLinkedList 管理英雄 class SingleLinkedList { // 初始化一個頭結點,不要動這個結點。 private HeroNode headNode = new HeroNode(0, "",""); // 添加結點 到 單向鏈表 // 當不考慮英雄順序時,找到當前鏈表的最後一個結點,再講此結點的next指向新的結點便可 public void addHero(HeroNode heroNode) { // 由於head結點不能動,因此新建一個臨時變量,幫助遍歷 HeroNode temp = headNode; // 開始遍歷鏈表,到最後,找最後的結點 while (true) { // 等於null時就是最後了 if (temp.next == null) { break; } // 不然就不是最後,將temp繼續向後移動 temp = temp.next; } // 直到退出循環,此時temp就指向了鏈表的最後 // 將最後的結點指向這個新的結點 temp.next = heroNode; } // 添加方法2:根據排名將英雄按照排名順序依次放到對應位置 public void addByNo(HeroNode heroNode) { // 藉助temp遍歷鏈表,找到添加位置的前一個結點 HeroNode temp = headNode; // 考慮一種狀況:當添加的位置已經存在對應排名的英雄,則不能添加 boolean flag = false; while (true) { if (temp.next == null) { break; } if (temp.next.no > heroNode.no) { // 位置找到,在temp的後面添加 break; } else if (temp.next.no == heroNode.no) { // 目標添加位置,已經存在對應編號,不能添加 flag = true; break; } temp = temp.next; // 繼續後移 } // 跳出循環,進行添加操做 if (flag) { System.out.printf("準備插入的英雄編號%d已存在,不可加入\n", heroNode.no); } else { // 能夠正常插入到鏈表 heroNode.next = temp.next; temp.next = heroNode; } } // 顯示鏈表內容的方法 public void linkList() { // 判斷鏈表是否爲空,空的話就不用繼續了 if (headNode.next == null) { System.out.println("鏈表爲空"); return; } HeroNode temp = headNode.next; while (true) { // 判斷是否已經到了鏈表最後 if (temp == null) { break; } // 輸出結點信息 System.out.println(temp); // 而後後移temp繼續輸出下一個結點 temp = temp.next; } } } // 定義HeroNode,每一個HeroNode對象就是一個結點 class HeroNode { public int no; public String name; public String nickname; public HeroNode next; // 指向下一個結點 // 構造器 public HeroNode(int heroNo, String heroName, String heroNickname) { this.no = heroNo; this.name = heroName; this.nickname = heroNickname; } // 爲了方便顯示,重寫toString方法 @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickname='" + nickname + '\'' + '}'; } }
在main方法中,咱們打亂結點添加的順序,運行一下,看看最終鏈表裏是否是按照影響的排名順序存儲的
HeroNode{no=1, name='易大師', nickname='無極劍聖'} HeroNode{no=2, name='李青', nickname='盲僧'} HeroNode{no=3, name='艾希', nickname='寒冰射手'} HeroNode{no=4, name='菲奧娜', nickname='無雙劍姬'} Process finished with exit code 0
結果正確,符合預期,無論先添加誰,最終在鏈表裏都是按照英雄的排名來存放。
繼續測試,我重複添加結點3,看下會如何。
// 加入對象結點 singleLinkedList.addByNo(hero1); singleLinkedList.addByNo(hero4); singleLinkedList.addByNo(hero2); singleLinkedList.addByNo(hero3); singleLinkedList.addByNo(hero3);
運行一下:
準備插入的英雄編號3已存在,不可加入 HeroNode{no=1, name='易大師', nickname='無極劍聖'} HeroNode{no=2, name='李青', nickname='盲僧'} HeroNode{no=3, name='艾希', nickname='寒冰射手'} HeroNode{no=4, name='菲奧娜', nickname='無雙劍姬'} Process finished with exit code 0
提示了已經存在了,不可加入。
下面會繼續單鏈表的修改和刪除等。