您好,登錄后才能下訂單哦!
這篇文章主要介紹Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
鏈表通常由一連串節(jié)點組成,每個節(jié)點包含任意的實例數(shù)據(jù)(data fields)和一或兩個用來指向上一個/或下一個節(jié)點的位置的鏈接("links")
鏈表(Linked list)是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),是一種線性表,但是并不會按線性的順序存儲數(shù)據(jù),而是在每一個節(jié)點里存到下一個節(jié)點的指針(Pointer)。
使用鏈表結(jié)構(gòu)可以克服數(shù)組鏈表需要預(yù)先知道數(shù)據(jù)大小的缺點,鏈表結(jié)構(gòu)可以充分利用計算機內(nèi)存空間,實現(xiàn)靈活的內(nèi)存動態(tài)管理。但是鏈表失去了數(shù)組隨機讀取的優(yōu)點,同時鏈表由于增加了結(jié)點的指針域,空間開銷比較大。
單鏈表是鏈表中結(jié)構(gòu)最簡單的。一個單鏈表的節(jié)點(Node)分為兩個部分,第一個部分(data)保存或者顯示關(guān)于節(jié)點的信息,另一個部分存儲下一個節(jié)點的地址。最后一個節(jié)點存儲地址的部分指向空值。
單向鏈表只可向一個方向遍歷,一般查找一個節(jié)點的時候需要從第一個節(jié)點開始每次訪問下一個節(jié)點,一直訪問到需要的位置。而插入一個節(jié)點,對于單向鏈表,我們只提供在鏈表頭插入,只需要將當前插入的節(jié)點設(shè)置為頭節(jié)點,next指向原頭節(jié)點即可。刪除一個節(jié)點,我們將該節(jié)點的上一個節(jié)點的next指向該節(jié)點的下一個節(jié)點?! ?/p>
在表頭增加節(jié)點:
刪除節(jié)點:
package com.ys.datastructure; public class SingleLinkedList { private int size;//鏈表節(jié)點的個數(shù) private Node head;//頭節(jié)點 public SingleLinkedList(){ size = 0; head = null; } //鏈表的每個節(jié)點類 private class Node{ private Object data;//每個節(jié)點的數(shù)據(jù) private Node next;//每個節(jié)點指向下一個節(jié)點的連接 public Node(Object data){ this.data = data; } } //在鏈表頭添加元素 public Object addHead(Object obj){ Node newHead = new Node(obj); if(size == 0){ head = newHead; }else{ newHead.next = head; head = newHead; } size++; return obj; } //在鏈表頭刪除元素 public Object deleteHead(){ Object obj = head.data; head = head.next; size--; return obj; } //查找指定元素,找到了返回節(jié)點Node,找不到返回null public Node find(Object obj){ Node current = head; int tempSize = size; while(tempSize > 0){ if(obj.equals(current.data)){ return current; }else{ current = current.next; } tempSize--; } return null; } //刪除指定的元素,刪除成功返回true public boolean delete(Object value){ if(size == 0){ return false; } Node current = head; Node previous = head; while(current.data != value){ if(current.next == null){ return false; }else{ previous = current; current = current.next; } } //如果刪除的節(jié)點是第一個節(jié)點 if(current == head){ head = current.next; size--; }else{//刪除的節(jié)點不是第一個節(jié)點 previous.next = current.next; size--; } return true; } //判斷鏈表是否為空 public boolean isEmpty(){ return (size == 0); } //顯示節(jié)點信息 public void display(){ if(size >0){ Node node = head; int tempSize = size; if(tempSize == 1){//當前鏈表只有一個節(jié)點 System.out.println("["+node.data+"]"); return; } while(tempSize>0){ if(node.equals(head)){ System.out.print("["+node.data+"->"); }else if(node.next == null){ System.out.print(node.data+"]"); }else{ System.out.print(node.data+"->"); } node = node.next; tempSize--; } System.out.println(); }else{//如果鏈表一個節(jié)點都沒有,直接打印[] System.out.println("[]"); } } }
測試:
@Test public void testSingleLinkedList(){ SingleLinkedList singleList = new SingleLinkedList(); singleList.addHead("A"); singleList.addHead("B"); singleList.addHead("C"); singleList.addHead("D"); //打印當前鏈表信息 singleList.display(); //刪除C singleList.delete("C"); singleList.display(); //查找B System.out.println(singleList.find("B")); }
打印結(jié)果:
棧的pop()方法和push()方法,對應(yīng)于鏈表的在頭部刪除元素deleteHead()以及在頭部增加元素addHead()。
package com.ys.datastructure; public class StackSingleLink { private SingleLinkedList link; public StackSingleLink(){ link = new SingleLinkedList(); } //添加元素 public void push(Object obj){ link.addHead(obj); } //移除棧頂元素 public Object pop(){ Object obj = link.deleteHead(); return obj; } //判斷是否為空 public boolean isEmpty(){ return link.isEmpty(); } //打印棧內(nèi)元素信息 public void display(){ link.display(); } }
對于單項鏈表,我們?nèi)绻朐谖膊刻砑右粋€節(jié)點,那么必須從頭部一直遍歷到尾部,找到尾節(jié)點,然后在尾節(jié)點后面插入一個節(jié)點。這樣操作很麻煩,如果我們在設(shè)計鏈表的時候多個對尾節(jié)點的引用,那么會簡單很多?! ?/p>
注意和后面將的雙向鏈表的區(qū)別?。?!
package com.ys.link; public class DoublePointLinkedList { private Node head;//頭節(jié)點 private Node tail;//尾節(jié)點 private int size;//節(jié)點的個數(shù) private class Node{ private Object data; private Node next; public Node(Object data){ this.data = data; } } public DoublePointLinkedList(){ size = 0; head = null; tail = null; } //鏈表頭新增節(jié)點 public void addHead(Object data){ Node node = new Node(data); if(size == 0){//如果鏈表為空,那么頭節(jié)點和尾節(jié)點都是該新增節(jié)點 head = node; tail = node; size++; }else{ node.next = head; head = node; size++; } } //鏈表尾新增節(jié)點 public void addTail(Object data){ Node node = new Node(data); if(size == 0){//如果鏈表為空,那么頭節(jié)點和尾節(jié)點都是該新增節(jié)點 head = node; tail = node; size++; }else{ tail.next = node; tail = node; size++; } } //刪除頭部節(jié)點,成功返回true,失敗返回false public boolean deleteHead(){ if(size == 0){//當前鏈表節(jié)點數(shù)為0 return false; } if(head.next == null){//當前鏈表節(jié)點數(shù)為1 head = null; tail = null; }else{ head = head.next; } size--; return true; } //判斷是否為空 public boolean isEmpty(){ return (size ==0); } //獲得鏈表的節(jié)點個數(shù) public int getSize(){ return size; } //顯示節(jié)點信息 public void display(){ if(size >0){ Node node = head; int tempSize = size; if(tempSize == 1){//當前鏈表只有一個節(jié)點 System.out.println("["+node.data+"]"); return; } while(tempSize>0){ if(node.equals(head)){ System.out.print("["+node.data+"->"); }else if(node.next == null){ System.out.print(node.data+"]"); }else{ System.out.print(node.data+"->"); } node = node.next; tempSize--; } System.out.println(); }else{//如果鏈表一個節(jié)點都沒有,直接打印[] System.out.println("[]"); } } }
package com.ys.link; public class QueueLinkedList { private DoublePointLinkedList dp; public QueueLinkedList(){ dp = new DoublePointLinkedList(); } public void insert(Object data){ dp.addTail(data); } public void delete(){ dp.deleteHead(); } public boolean isEmpty(){ return dp.isEmpty(); } public int getSize(){ return dp.getSize(); } public void display(){ dp.display(); } }
在介紹抽象數(shù)據(jù)類型的時候,我們先看看什么是數(shù)據(jù)類型,聽到這個詞,在Java中我們可能首先會想到像 int,double這樣的詞,這是Java中的基本數(shù)據(jù)類型,一個數(shù)據(jù)類型會涉及到兩件事:
①、擁有特定特征的數(shù)據(jù)項
②、在數(shù)據(jù)上允許的操作
比如Java中的int數(shù)據(jù)類型,它表示整數(shù),取值范圍為:-2147483648~2147483647,還能使用各種操作符,+、-、*、/ 等對其操作。數(shù)據(jù)類型允許的操作是它本身不可分離的部分,理解類型包括理解什么樣的操作可以應(yīng)用在該類型上。
那么當年設(shè)計計算機語言的人,為什么會考慮到數(shù)據(jù)類型?
我們先看這樣一個例子,比如,大家都需要住房子,也都希望房子越大越好。但顯然,沒有錢,考慮房子沒有意義。于是就出現(xiàn)了各種各樣的商品房,有別墅的、復(fù)式的、錯層的、單間的……甚至只有兩平米的膠囊房間。這樣做的意義是滿足不同人的需要。
同樣,在計算機中,也存在相同的問題。計算1+1這樣的表達式不需要開辟很大的存儲空間,不需要適合小數(shù)甚至字符運算的內(nèi)存空間。于是計算機的研究者們就考慮,要對數(shù)據(jù)進行分類,分出來多種數(shù)據(jù)類型。比如int,比如float。
雖然不同的計算機有不同的硬件系統(tǒng),但實際上高級語言編寫者才不管程序運行在什么計算機上,他們的目的就是為了實現(xiàn)整形數(shù)字的運算,比如a+b等。他們才不關(guān)心整數(shù)在計算機內(nèi)部是如何表示的,也不管CPU是如何計算的。于是我們就考慮,無論什么計算機、什么語言都會面臨類似的整數(shù)運算,我們可以考慮將其抽象出來。抽象是抽取出事物具有的普遍性本質(zhì),是對事物的一個概括,是一種思考問題的方式。
抽象數(shù)據(jù)類型(ADT)是指一個數(shù)學(xué)模型及定義在該模型上的一組操作。它僅取決于其邏輯特征,而與計算機內(nèi)部如何表示和實現(xiàn)無關(guān)。比如剛才說得整型,各個計算機,不管大型機、小型機、PC、平板電腦甚至智能手機,都有“整型”類型,也需要整形運算,那么整型其實就是一個抽象數(shù)據(jù)類型?!?/p>
更廣泛一點的,比如我們剛講解的棧和隊列這兩種數(shù)據(jù)結(jié)構(gòu),我們分別使用了數(shù)組和鏈表來實現(xiàn),比如棧,對于使用者只需要知道pop()和push()方法或其它方法的存在以及如何使用即可,使用者不需要知道我們是使用的數(shù)組或是鏈表來實現(xiàn)的。
ADT的思想可以作為我們設(shè)計工具的理念,比如我們需要存儲數(shù)據(jù),那么就從考慮需要在數(shù)據(jù)上實現(xiàn)的操作開始,需要存取最后一個數(shù)據(jù)項嗎?還是第一個?還是特定值的項?還是特定位置的項?回答這些問題會引出ADT的定義,只有完整的定義了ADT后,才應(yīng)該考慮實現(xiàn)的細節(jié)。
這在我們Java語言中的接口設(shè)計理念是想通的。
前面的鏈表實現(xiàn)插入數(shù)據(jù)都是無序的,在有些應(yīng)用中需要鏈表中的數(shù)據(jù)有序,這稱為有序鏈表。
在有序鏈表中,數(shù)據(jù)是按照關(guān)鍵值有序排列的。一般在大多數(shù)需要使用有序數(shù)組的場合也可以使用有序鏈表。有序鏈表優(yōu)于有序數(shù)組的地方是插入的速度(因為元素不需要移動),另外鏈表可以擴展到全部有效的使用內(nèi)存,而數(shù)組只能局限于一個固定的大小中。
package com.ys.datastructure; public class OrderLinkedList { private Node head; private class Node{ private int data; private Node next; public Node(int data){ this.data = data; } } public OrderLinkedList(){ head = null; } //插入節(jié)點,并按照從小打到的順序排列 public void insert(int value){ Node node = new Node(value); Node pre = null; Node current = head; while(current != null && value > current.data){ pre = current; current = current.next; } if(pre == null){ head = node; head.next = current; }else{ pre.next = node; node.next = current; } } //刪除頭節(jié)點 public void deleteHead(){ head = head.next; } public void display(){ Node current = head; while(current != null){ System.out.print(current.data+" "); current = current.next; } System.out.println(""); } }
在有序鏈表中插入和刪除某一項最多需要O(N)次比較,平均需要O(N/2)次,因為必須沿著鏈表上一步一步走才能找到正確的插入位置,然而可以最快速度刪除最值,因為只需要刪除表頭即可,如果一個應(yīng)用需要頻繁的存取最小值,且不需要快速的插入,那么有序鏈表是一個比較好的選擇方案。比如優(yōu)先級隊列可以使用有序鏈表來實現(xiàn)。
比如有一個無序數(shù)組需要排序,前面我們在講解冒泡排序、選擇排序、插入排序這三種簡單的排序時,需要的時間級別都是O(N2)。
現(xiàn)在我們講解了有序鏈表之后,對于一個無序數(shù)組,我們先將數(shù)組元素取出,一個一個的插入到有序鏈表中,然后將他們從有序鏈表中一個一個刪除,重新放入數(shù)組,那么數(shù)組就會排好序了。和插入排序一樣,如果插入了N個新數(shù)據(jù),那么進行大概N2/4次比較。但是相對于插入排序,每個元素只進行了兩次排序,一次從數(shù)組到鏈表,一次從鏈表到數(shù)組,大概需要2*N次移動,而插入排序則需要N2次移動,
效率肯定是比前面講的簡單排序要高,但是缺點就是需要開辟差不多兩倍的空間,而且數(shù)組和鏈表必須在內(nèi)存中同時存在,如果有現(xiàn)成的鏈表可以用,那么這種方法還是挺好的。
我們知道單向鏈表只能從一個方向遍歷,那么雙向鏈表它可以從兩個方向遍歷。
具體代碼實現(xiàn):
package com.ys.datastructure; public class TwoWayLinkedList { private Node head;//表示鏈表頭 private Node tail;//表示鏈表尾 private int size;//表示鏈表的節(jié)點個數(shù) private class Node{ private Object data; private Node next; private Node prev; public Node(Object data){ this.data = data; } } public TwoWayLinkedList(){ size = 0; head = null; tail = null; } //在鏈表頭增加節(jié)點 public void addHead(Object value){ Node newNode = new Node(value); if(size == 0){ head = newNode; tail = newNode; size++; }else{ head.prev = newNode; newNode.next = head; head = newNode; size++; } } //在鏈表尾增加節(jié)點 public void addTail(Object value){ Node newNode = new Node(value); if(size == 0){ head = newNode; tail = newNode; size++; }else{ newNode.prev = tail; tail.next = newNode; tail = newNode; size++; } } //刪除鏈表頭 public Node deleteHead(){ Node temp = head; if(size != 0){ head = head.next; head.prev = null; size--; } return temp; } //刪除鏈表尾 public Node deleteTail(){ Node temp = tail; if(size != 0){ tail = tail.prev; tail.next = null; size--; } return temp; } //獲得鏈表的節(jié)點個數(shù) public int getSize(){ return size; } //判斷鏈表是否為空 public boolean isEmpty(){ return (size == 0); } //顯示節(jié)點信息 public void display(){ if(size >0){ Node node = head; int tempSize = size; if(tempSize == 1){//當前鏈表只有一個節(jié)點 System.out.println("["+node.data+"]"); return; } while(tempSize>0){ if(node.equals(head)){ System.out.print("["+node.data+"->"); }else if(node.next == null){ System.out.print(node.data+"]"); }else{ System.out.print(node.data+"->"); } node = node.next; tempSize--; } System.out.println(); }else{//如果鏈表一個節(jié)點都沒有,直接打印[] System.out.println("[]"); } } }
我們也可以用雙向鏈表來實現(xiàn)雙端隊列,這里就不做具體代碼演示了。
以上是“Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。