文本主要內(nèi)容:
一、鏈表結(jié)構(gòu):
概念:
鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)是基于指針實(shí)現(xiàn)的。我們把一個(gè)數(shù)據(jù)元素和一個(gè)指針稱為結(jié)點(diǎn)。
數(shù)據(jù)域:存數(shù)數(shù)據(jù)元素信息的域。
指針域:存儲(chǔ)直接后繼位置的域。
鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)是用指針把相互直接關(guān)聯(lián)的結(jié)點(diǎn)(即直接前驅(qū)結(jié)點(diǎn)或直接后繼結(jié)點(diǎn))鏈接起來。鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)的線性表稱為鏈表。
鏈表類型:
根據(jù)鏈表的構(gòu)造方式的不同可以分為:
二、單鏈表:
概念:
鏈表的每個(gè)結(jié)點(diǎn)中只包含一個(gè)指針域,叫做單鏈表(即構(gòu)成鏈表的每個(gè)結(jié)點(diǎn)只有一個(gè)指向直接后繼結(jié)點(diǎn)的指針)
單鏈表中每個(gè)結(jié)點(diǎn)的結(jié)構(gòu):

1、頭指針和頭結(jié)點(diǎn):
單鏈表有帶頭結(jié)點(diǎn)結(jié)構(gòu)和不帶頭結(jié)點(diǎn)結(jié)構(gòu)兩種。
“鏈表中第一個(gè)結(jié)點(diǎn)的存儲(chǔ)位置叫做頭指針”,如果鏈表有頭結(jié)點(diǎn),那么頭指針就是指向頭結(jié)點(diǎn)的指針。
頭指針?biāo)傅牟淮娣艛?shù)據(jù)元素的第一個(gè)結(jié)點(diǎn)稱作頭結(jié)點(diǎn)(頭結(jié)點(diǎn)指向首元結(jié)點(diǎn))。頭結(jié)點(diǎn)的數(shù)據(jù)域一般不放數(shù)據(jù)(當(dāng)然有些情況下也可存放鏈表的長(zhǎng)度、用做監(jiān)視哨等)
存放第一個(gè)數(shù)據(jù)元素的結(jié)點(diǎn)稱作第一個(gè)數(shù)據(jù)元素結(jié)點(diǎn),或稱首元結(jié)點(diǎn)。
如下圖所示:

不帶頭結(jié)點(diǎn)的單鏈表如下:

帶頭結(jié)點(diǎn)的單鏈表如下圖:

關(guān)于頭指針和頭結(jié)點(diǎn)的概念區(qū)分,可以參考如下博客:
http://blog.csdn.net/hitwhylz/article/details/12305021
2、不帶頭結(jié)點(diǎn)的單鏈表的插入操作:

上圖中,是不帶頭結(jié)點(diǎn)的單鏈表的插入操作。如果我們?cè)诜堑谝粋€(gè)結(jié)點(diǎn)前進(jìn)行插入操作,只需要a(i-1)的指針域指向s,然后將s的指針域指向a(i)就行了;如果我們?cè)诘谝粋€(gè)結(jié)點(diǎn)前進(jìn)行插入操作,頭指針head就要等于新插入結(jié)點(diǎn)s,這和在非第一個(gè)數(shù)據(jù)元素結(jié)點(diǎn)前插入結(jié)點(diǎn)時(shí)的情況不同。另外,還有一些不同情況需要考慮。
因此,算法對(duì)這兩種情況就要分別設(shè)計(jì)實(shí)現(xiàn)方法。
3、帶頭結(jié)點(diǎn)的單鏈表的插入操作:(操作統(tǒng)一,推薦)

上圖中,如果采用帶頭結(jié)點(diǎn)的單鏈表結(jié)構(gòu),算法實(shí)現(xiàn)時(shí),p指向頭結(jié)點(diǎn),改變的是p指針的next指針的值(改變頭結(jié)點(diǎn)的指針域),而頭指針head的值不變。
因此,算法實(shí)現(xiàn)方法比較簡(jiǎn)單,其操作與對(duì)其它結(jié)點(diǎn)的操作統(tǒng)一。
問題1:頭結(jié)點(diǎn)的好處:
頭結(jié)點(diǎn)即在鏈表的首元結(jié)點(diǎn)之前附設(shè)的一個(gè)結(jié)點(diǎn),該結(jié)點(diǎn)的數(shù)據(jù)域中不存儲(chǔ)線性表的數(shù)據(jù)元素,其作用是為了對(duì)鏈表進(jìn)行操作時(shí),可以對(duì)空表、非空表的情況以及對(duì)首元結(jié)點(diǎn)進(jìn)行統(tǒng)一處理,編程更方便。
問題2:如何表示空表:
無頭結(jié)點(diǎn)時(shí),當(dāng)頭指針的值為空時(shí)表示空表; 有頭結(jié)點(diǎn)時(shí),當(dāng)頭結(jié)點(diǎn)的指針域?yàn)榭諘r(shí)表示空表。
如下圖所示:

問題3:頭結(jié)點(diǎn)的數(shù)據(jù)域內(nèi)裝的是什么?
頭結(jié)點(diǎn)的數(shù)據(jù)域可以為空,也可存放線性表長(zhǎng)度等附加信息,但此結(jié)點(diǎn)不能計(jì)入鏈表長(zhǎng)度值。
三、單項(xiàng)鏈表的代碼實(shí)現(xiàn):
1、結(jié)點(diǎn)類:
單鏈表是由一個(gè)一個(gè)結(jié)點(diǎn)組成的,因此,要設(shè)計(jì)單鏈表類,必須先設(shè)計(jì)結(jié)點(diǎn)類。結(jié)點(diǎn)類的成員變量有兩個(gè):一個(gè)是數(shù)據(jù)元素,另一個(gè)是表示下一個(gè)結(jié)點(diǎn)的對(duì)象引用(即指針)。
步驟如下:
(1)頭結(jié)點(diǎn)的構(gòu)造(設(shè)置指針域即可)
(2)非頭結(jié)點(diǎn)的構(gòu)造
(3)獲得當(dāng)前結(jié)點(diǎn)的指針域
(4)獲得當(dāng)前結(jié)點(diǎn)數(shù)據(jù)域的值
(5)設(shè)置當(dāng)前結(jié)點(diǎn)的指針域
(6)設(shè)置當(dāng)前結(jié)點(diǎn)數(shù)據(jù)域的值
注:類似于get和set方法,成員變量是數(shù)據(jù)域和指針域。
代碼實(shí)現(xiàn):
(1)List.java:(鏈表本身也是線性表,只不過物理存儲(chǔ)上不連續(xù))
//線性表接口public interface List { //獲得線性表長(zhǎng)度 public int size(); //判斷線性表是否為空 public boolean isEmpty(); //插入元素 public void insert(int index, Object obj) throws Exception; //刪除元素 public void delete(int index) throws Exception; //獲取指定位置的元素 public Object get(int index) throws Exception;}(2)Node.java:結(jié)點(diǎn)類
//結(jié)點(diǎn)類public class Node { Object element; //數(shù)據(jù)域 Node next; //指針域 //頭結(jié)點(diǎn)的構(gòu)造方法 public Node(Node nextval) { this.next = nextval; } //非頭結(jié)點(diǎn)的構(gòu)造方法 public Node(Object obj, Node nextval) { this.element = obj; this.next = nextval; } //獲得當(dāng)前結(jié)點(diǎn)的指針域 public Node getNext() { return this.next; } //獲得當(dāng)前結(jié)點(diǎn)數(shù)據(jù)域的值 public Object getElement() { return this.element; } //設(shè)置當(dāng)前結(jié)點(diǎn)的指針域 public void setNext(Node nextval) { this.next = nextval; } //設(shè)置當(dāng)前結(jié)點(diǎn)數(shù)據(jù)域的值 public void setElement(Object obj) { this.element = obj; } public String toString() { return this.element.toString(); }}2、單鏈表類:
單鏈表類的成員變量至少要有兩個(gè):一個(gè)是頭指針,另一個(gè)是單鏈表中的數(shù)據(jù)元素個(gè)數(shù)。但是,如果再增加一個(gè)表示單鏈表當(dāng)前結(jié)點(diǎn)位置的成員變量,則有些成員函數(shù)的設(shè)計(jì)將更加方便。
代碼實(shí)現(xiàn):
(3)LinkList.java:單向鏈表類(核心代碼)
1 //單向鏈表類 2 public class LinkList implements List { 3 4 Node head; //頭指針 5 Node current;//當(dāng)前結(jié)點(diǎn)對(duì)象 6 int size;//結(jié)點(diǎn)個(gè)數(shù) 7 8 //初始化一個(gè)空鏈表 9 public LinkList()10 {11 //初始化頭結(jié)點(diǎn),讓頭指針指向頭結(jié)點(diǎn)。并且讓當(dāng)前結(jié)點(diǎn)對(duì)象等于頭結(jié)點(diǎn)。12 this.head = current = new Node(null);13 this.size =0;//單向鏈表,初始長(zhǎng)度為零。14 }15 16 //定位函數(shù),實(shí)現(xiàn)當(dāng)前操作對(duì)象的前一個(gè)結(jié)點(diǎn),也就是讓當(dāng)前結(jié)點(diǎn)對(duì)象定位到要操作結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)。17 //比如我們要在a2這個(gè)節(jié)點(diǎn)之前進(jìn)行插入操作,那就先要把當(dāng)前節(jié)點(diǎn)對(duì)象定位到a1這個(gè)節(jié)點(diǎn),然后修改a1節(jié)點(diǎn)的指針域18 public void index(int index) throws Exception19 {20 if(index <-1 || index > size -1)21 {22 throw new Exception("參數(shù)錯(cuò)誤!"); 23 }24 //說明在頭結(jié)點(diǎn)之后操作。25 if(index==-1) //因?yàn)榈谝粋€(gè)數(shù)據(jù)元素結(jié)點(diǎn)的下標(biāo)是0,那么頭結(jié)點(diǎn)的下標(biāo)自然就是-1了。26 return;27 current = head.next;28 int j=0;//循環(huán)變量29 while(current != null&&j<index)30 {31 current = current.next;32 j++;33 }34 35 } 36 37 @Override38 public void delete(int index) throws Exception {39 // TODO Auto-generated method stub40 //判斷鏈表是否為空41 if(isEmpty())42 {43 throw new Exception("鏈表為空,無法刪除!");44 }45 if(index <0 ||index >size)46 {47 throw new Exception("參數(shù)錯(cuò)誤!");48 }49 index(index-1);//定位到要操作結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)對(duì)象。50 current.setNext(current.next.next);51 size--;52 }53 54 @Override55 public Object get(int index) throws Exception {56 // TODO Auto-generated method stub57 if(index <-1 || index >size-1)58 {59 throw new Exception("參數(shù)非法!");60 }61 index(index);62 63 return current.getElement();64 }65 66 @Override67 public void insert(int index, Object obj) throws Exception {68 // TODO Auto-generated method stub69 if(index <0 ||index >size)70 {71 throw new Exception("參數(shù)錯(cuò)誤!");72 }73 index(index-1);//定位到要操作結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)對(duì)象。74 current.setNext(new Node(obj,current.next));75 size++;76 }77 78 @Override79 public boolean isEmpty() {80 // TODO Auto-generated method stub81 return size==0;82 }83 84 @Override85 public int size() {86 // TODO Auto-generated method stub87 return this.size;88 }89 90 91 }3、測(cè)試類:(單鏈表的應(yīng)用)
使用單鏈表建立一個(gè)線性表,依次輸入十個(gè)0-99之間的隨機(jī)數(shù),刪除第5個(gè)元素,打印輸出該線性表。
(4)Test.java:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 // TODO Auto-generated method stub 5 LinkList list = new LinkList(); 6 for (int i = 0; i < 10; i++) { 7 int temp = ((int) (Math.random() * 100)) % 100; 8 list.insert(i, temp); 9 System.out.PRint(temp + " ");10 }11 12 list.delete(4);13 System.out.println("/n------刪除第五個(gè)元素之后-------");14 for (int i = 0; i < list.size; i++) {15 System.out.print(list.get(i) + " ");16 }17 }18 19 }運(yùn)行效果:

四、開發(fā)可用的鏈表:
對(duì)于鏈表實(shí)現(xiàn),Node類是整個(gè)操作的關(guān)鍵,但是首先來研究一下之前程序的問題:Node是一個(gè)單獨(dú)的類,那么這樣的類是可以被用戶直接使用的,但是這個(gè)類由用戶直接去使用,沒有任何的意義,即:Node這個(gè)類有用,但是不能讓用戶去用,只能讓LinkList類去調(diào)用,內(nèi)部類Node中完成。
于是,我們需要把Node類定義為內(nèi)部類,并且在Node類中去完成addNode和delNote等操作。使用內(nèi)部類的最大好處是可以和外部類進(jìn)行私有操作的互相訪問。
注:內(nèi)部類訪問的特點(diǎn)是:內(nèi)部類可以直接訪問外部類的成員,包括私有;外部類要訪問內(nèi)部類的成員,必須先創(chuàng)建對(duì)象。
1、增加數(shù)據(jù):
代碼實(shí)現(xiàn):
(1)LinkList.java:(核心代碼)
1 public class LinkList { 2 private Node root; //定義一個(gè)根節(jié)點(diǎn) 3 4 //方法:增加節(jié)點(diǎn) 5 public boolean add(String data) { 6 7 if (data == null) { // 如果添加的是一個(gè)空數(shù)據(jù),那增加失敗 8 return false; 9 }10 11 // 將數(shù)據(jù)封裝為節(jié)點(diǎn),目的:節(jié)點(diǎn)有next可以處理關(guān)系12 Node newNode = new Node(data);13 // 鏈表的關(guān)鍵就在于根節(jié)點(diǎn)14 if (root == null) { //如果根節(jié)點(diǎn)是空的,那么新添加的節(jié)點(diǎn)就是根節(jié)點(diǎn)。(第一次調(diào)用add方法時(shí),根節(jié)點(diǎn)當(dāng)然是空的了)15 root = newNode;16 } else {17 root.addNode(newNode);18 19 }20 21 return true;22 23 }24 25 26 //定義一個(gè)節(jié)點(diǎn)內(nèi)部類(假設(shè)要保存的數(shù)據(jù)類型是字符串)27 //比較好的做法是,將Node定義為內(nèi)部類,在這里面去完成增刪、等功能,然后由LinkList去調(diào)用增、刪的功能28 class Node {29 private String data;30 private Node next; //next表示:下一個(gè)節(jié)點(diǎn)對(duì)象(單鏈表中)31 32 public Node(String data) {33 this.data = data;34 }35 36 public void addNode(Node newNode) {37 38 //下面這段用到了遞歸,需要反復(fù)理解39 if (this.next == null) { // 遞歸的出口:如果當(dāng)前節(jié)點(diǎn)之后沒有節(jié)點(diǎn),說明我可以在這個(gè)節(jié)點(diǎn)后面添加新節(jié)點(diǎn)40 this.next = newNode; //添加新節(jié)點(diǎn)41 } else {42 this.next.addNode(newNode); //向下繼續(xù)判斷,直到當(dāng)前節(jié)點(diǎn)之后沒有節(jié)點(diǎn)為止43 44 }45 }46 }47 }代碼解釋:
14行:如果我們第一次調(diào)用add方法,那根結(jié)點(diǎn)肯定是空的,此時(shí)add的是根節(jié)點(diǎn)。
當(dāng)繼續(xù)調(diào)用add方法時(shí),此時(shí)是往根節(jié)點(diǎn)后面添加數(shù)據(jù),需要用到遞歸(42行),這個(gè)遞歸需要在內(nèi)部類中去完成。遞歸這段代碼需要去反復(fù)理解。
(2)LinkListDemo.java:
public class LinkListDemo { public static void main(String[] args) { LinkList list = new LinkList(); boolean flag = list.add("haha"); System.out.println(flag); }}運(yùn)行效果:

2、增加多個(gè)數(shù)據(jù):
上面的操作是每次增加了一個(gè)對(duì)象,那么如果現(xiàn)在要求增加多個(gè)對(duì)象呢,例如:增加對(duì)象數(shù)組。可以采用循環(huán)數(shù)組的方式,每次都調(diào)用add()方法。
在上面的(1)LinkList.java中加入如下代碼:
1 //方法:增加一組數(shù)據(jù)2 public boolean addAll(String data[]) { // 一組數(shù)據(jù)3 for (int x = 0 ; x < data.length ; x ++) {4 if (!this.add(data[x])) { // 只要有一次添加不成功,那就是添加失敗5 return false ;6 }7 }8 return true ;9 }3、統(tǒng)計(jì)數(shù)據(jù)個(gè)數(shù):
在一個(gè)鏈表之中,會(huì)保存多個(gè)數(shù)據(jù)(每一個(gè)數(shù)據(jù)都被封裝為Node類對(duì)象),那么要想取得這些保存元素的個(gè)數(shù),可以增加一個(gè)size()方法完成。
具體做法如下:
在上面的(1)LinkList.java中增加一個(gè)統(tǒng)計(jì)的屬性count:
private int size ; // 統(tǒng)計(jì)個(gè)數(shù)
當(dāng)用戶每一次調(diào)用add()方法增加新數(shù)據(jù)的時(shí)候應(yīng)該做出統(tǒng)計(jì):(下方第18行代碼)
1 //添加節(jié)點(diǎn) 2 public boolean add(String data) { 3 4 if (data == null) { // 如果添加的是一個(gè)空數(shù)據(jù),那增加失敗 5 return false; 6 } 7 8 // 將數(shù)據(jù)封裝為節(jié)點(diǎn),目的:節(jié)點(diǎn)有next可以處理關(guān)系 9 Node newNode = new Node(data);10 // 鏈表的關(guān)鍵就在于根節(jié)點(diǎn)11 if (root == null) { //如果根節(jié)點(diǎn)是空的,那么新添加的節(jié)點(diǎn)就是根節(jié)點(diǎn)。(第一次調(diào)用add方法時(shí),根節(jié)點(diǎn)當(dāng)然是空的了)12 root = newNode;13 } else {14 root.addNode(newNode);15 16 }17 18 this.size++;19 return true;20 21 }而size()方法就是簡(jiǎn)單的將count這個(gè)變量的內(nèi)容返回:
//獲取數(shù)據(jù)的長(zhǎng)度 public int size() { return this.size; }4、判斷是否是空鏈表:
所謂的空鏈表指的是鏈表之中不保存任何的數(shù)據(jù),實(shí)際上這個(gè)null可以通過兩種方式判斷:一種判斷鏈表的根節(jié)點(diǎn)是否為null,另外一個(gè)是判斷保存元素的個(gè)數(shù)是否為0。
在LinkList.java中添加如下代碼:
//判斷是否為空鏈表 public boolean isEmpty() { return this.size == 0; }5、查找數(shù)據(jù)是否存在:
現(xiàn)在如果要想查詢某個(gè)數(shù)據(jù)是否存在,那么基本的操作原理:逐個(gè)盤查,盤查的具體實(shí)現(xiàn)還是應(yīng)該交給Node類去處理,但是在盤查之前必須有一個(gè)前提:有數(shù)據(jù)存在。
在LinkList.java中添加查詢的操作:
1 //查詢數(shù)據(jù)是否存在2 public boolean contains(String data) { // 查找數(shù)據(jù)3 // 根節(jié)點(diǎn)沒有數(shù)據(jù),查找的也沒有數(shù)據(jù)4 if (this.root == null || data == null) {5 return false; // 不需要進(jìn)行查找了6 }7 return this.root.containsNode(data); // 交給Node類處理8 }緊接著,在Node類之中,完成具體的查詢,查詢的流程: 判斷當(dāng)前節(jié)點(diǎn)的內(nèi)容是否滿足于查詢內(nèi)容,如果滿足返回true; 如果當(dāng)前節(jié)點(diǎn)的內(nèi)容不滿足,則向后繼續(xù)查,如果已經(jīng)沒有后續(xù)節(jié)點(diǎn)了,則返回false。
代碼實(shí)現(xiàn):
1 //判斷節(jié)點(diǎn)是否存在 2 public boolean containsNode(String data) { // 查找數(shù)據(jù) 3 if (data.equals(this.data)) { // 與當(dāng)前節(jié)點(diǎn)數(shù)據(jù)吻合 4 return true; 5 } else { // 與當(dāng)前節(jié)點(diǎn)數(shù)據(jù)不吻合 6 if (this.next != null) { // 還有下一個(gè)節(jié)點(diǎn) 7 return this.next.containsNode(data); 8 } else { // 沒有后續(xù)節(jié)點(diǎn) 9 return false; // 查找不到10 }11 }12 }6、刪除數(shù)據(jù):
在LinkList.java中加入如下代碼:
1 //方法:刪除數(shù)據(jù) 2 public boolean remove(String data) { //要?jiǎng)h除的節(jié)點(diǎn),假設(shè)每個(gè)節(jié)點(diǎn)的data都不一樣 3 4 if (!this.contains(data)) { //要?jiǎng)h除的數(shù)據(jù)不存在 5 return false; 6 } 7 8 if (root != null) { 9 if (root.data.equals(data)) { //說明根節(jié)點(diǎn)就是需要?jiǎng)h除的節(jié)點(diǎn)10 root = root.next; //讓根節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)成為根節(jié)點(diǎn),自然就把根節(jié)點(diǎn)頂?shù)袅寺铮ú幌駭?shù)組那樣,要將后面的數(shù)據(jù)在內(nèi)存中整體挪一位)11 } else { //否則12 root.removeNode(data);13 }14 }15 size--;16 return true;17 18 }注意第2代碼中,我們是假設(shè)刪除的這個(gè)String字符串是唯一的,不然就沒法刪除了。
刪除時(shí),我們需要從根節(jié)點(diǎn)開始判斷,如果根節(jié)點(diǎn)是需要?jiǎng)h除的節(jié)點(diǎn),那就直接刪除,此時(shí)下一個(gè)節(jié)點(diǎn)變成了根節(jié)點(diǎn)。
然后,在Node類中做節(jié)點(diǎn)的刪除:
//刪除節(jié)點(diǎn) public void removeNode(String data) { if (this.next != null) { if (this.next.data.equals(data)) { this.next = this.next.next; } else { this.next.removeNode(data); } } }7、輸出所有節(jié)點(diǎn):
在LinkList.java中加入如下代碼:
1 //輸出所有節(jié)點(diǎn)2 public void print() {3 if (root != null) {4 System.out.print(root.data);5 root.printNode();6 System.out.println();7 }8 }然后,在Node類中做節(jié)點(diǎn)的輸出:
1 //輸出所有節(jié)點(diǎn)2 public void printNode() {3 if (this.next != null) {4 System.out.print("-->" + this.next.data);5 this.next.printNode();6 }7 }8、取出全部數(shù)據(jù):
對(duì)于鏈表的這種數(shù)據(jù)結(jié)構(gòu),最為關(guān)鍵的是兩個(gè)操作:刪除、取得全部數(shù)據(jù)。
在LinkList類之中需要定義一個(gè)操作數(shù)組的腳標(biāo):
private int foot = 0; // 操作返回?cái)?shù)組的腳標(biāo)
在LinkList類中定義返回?cái)?shù)組,必須以屬性的形式出現(xiàn),只有這樣,Node類才可以訪問這個(gè)數(shù)組并進(jìn)行操作:
private String [] retData ; // 返回?cái)?shù)組
在LinkList類之中增加toArray()的方法:
1 //方法:獲取全部數(shù)據(jù) 2 public String[] toArray() { 3 if (this.size == 0) { 4 return null; // 沒有數(shù)據(jù) 5 } 6 this.foot = 0; // 清零 7 this.retData = new String[this.size]; // 開辟數(shù)組大小 8 this.root.toArrayNode(); 9 return this.retData;10 }修改Node類的操作,增加toArrayNode()方法:
1 //獲取全部數(shù)據(jù)2 public void toArrayNode() {3 LinkList.this.retData[LinkList.this.foot++] = this.data;4 if (this.next != null) {5 this.next.toArrayNode();6 }7 }不過,按照以上的方式進(jìn)行開發(fā),每一次調(diào)用toArray()方法,都要重復(fù)的進(jìn)行數(shù)據(jù)的遍歷,如果在數(shù)據(jù)沒有修改的情況下,這種做法是一種非常差的做法,最好的做法是增加一個(gè)修改標(biāo)記,如果發(fā)現(xiàn)數(shù)據(jù)增加了或刪除的話,表示要重新遍歷數(shù)據(jù)。
private boolean changeFlag = true ; // changeFlag == true:數(shù)據(jù)被更改了,則需要重新遍歷// changeFlag == false:數(shù)據(jù)沒有更改,不需要重新遍歷
然后,我們修改LinkList類中的toArray()方法:(其他代碼保持不變)
//方法:獲取全部數(shù)據(jù) public String[] toArray() { if (this.size == 0) { return null; // 沒有數(shù)據(jù) } this.foot = 0; // 清零 if (this.changeFlag == true) { // 內(nèi)容被修改了,需要重新取 this.retData = new String[this.size]; // 開辟數(shù)組大小 this.root.toArrayNode(); } return this.retData; }9、根據(jù)索引位置取得數(shù)據(jù):
在一個(gè)鏈表之中會(huì)有多個(gè)節(jié)點(diǎn)保存數(shù)據(jù),現(xiàn)在要求可以取得指定節(jié)點(diǎn)位置上的數(shù)據(jù)。但是在進(jìn)行這一操作的過程之中,有一個(gè)小問題:如果要取得數(shù)據(jù)的索引超過了數(shù)據(jù)的保存?zhèn)€數(shù),那么是無法取得的。
在LinkList類之中,增加一個(gè)get()方法:
1 //方法:根據(jù)索引取得數(shù)據(jù)2 public String get(int index) {3 if (index > this.size) { // 超過個(gè)數(shù)4 return null; // 返回null5 }6 this.foot = 0; // 操作foot來定義腳標(biāo)7 return this.root.getNode(index);8 }在Node類之中配置getNode()方法:
1 //根據(jù)索引位置獲取數(shù)據(jù)2 public String getNode(int index) {3 if (LinkList.this.foot++ == index) { // 當(dāng)前索引為查找數(shù)值4 return this.data;5 } else {6 return this.next.getNode(index);7 }8 }10、清空鏈表:
所有的鏈表被root拽著,這個(gè)時(shí)候如果root為null,那么后面的數(shù)據(jù)都會(huì)斷開,就表示都成了垃圾:
//清空鏈表 public void clear() { this.root = null; this.size = 0; }總結(jié):
上面的10條方法中,LinkList的完整代碼如下:
1 /** 2 * Created by smyhvae on 2015/8/27. 3 */ 4 5 public class LinkList { 6 7 private int size; 8 private Node root; //定義一個(gè)根節(jié)點(diǎn) 9 10 private int foot = 0; // 操作返回?cái)?shù)組的腳標(biāo) 11 private String[] retData; // 返回?cái)?shù)組 12 private boolean changeFlag = true; 13 // changeFlag == true:數(shù)據(jù)被更改了,則需要重新遍歷 14 // changeFlag == false:數(shù)據(jù)沒有更改,不需要重新遍歷 15 16 17 //添加數(shù)據(jù) 18 public boolean add(String data) { 19 20 if (data == null) { // 如果添加的是一個(gè)空數(shù)據(jù),那增加失敗 21 return false; 22 } 23 24 // 將數(shù)據(jù)封裝為節(jié)點(diǎn),目的:節(jié)點(diǎn)有next可以處理關(guān)系 25 Node newNode = new Node(data); 26 // 鏈表的關(guān)鍵就在于根節(jié)點(diǎn) 27 if (root == null) { //如果根節(jié)點(diǎn)是空的,那么新添加的節(jié)點(diǎn)就是根節(jié)點(diǎn)。(第一次調(diào)用add方法時(shí),根節(jié)點(diǎn)當(dāng)然是空的了) 28 root = newNode; 29 } else { 30 root.addNode(newNode); 31 32 } 33 34 this.size++; 35 return true; 36 37 } 38 39 40 //方法:增加一組數(shù)據(jù) 41 public boolean addAll(String data[]) { // 一組數(shù)據(jù) 42 for (int x = 0; x < data.length; x++) { 43 if (!this.add(data[x])) { // 只要有一次添加不成功,那就是添加失敗 44 return false; 45 } 46 } 47 return true; 48 } 49 50 //方法:刪除數(shù)據(jù) 51 public boolean remove(String data) { //要?jiǎng)h除的節(jié)點(diǎn),假設(shè)每個(gè)節(jié)點(diǎn)的data都不一樣 52 53 if (!this.contains(data)) { //要?jiǎng)h除的數(shù)據(jù)不存在 54 return false; 55 } 56 57 if (root != null) { 58 if (root.data.equals(data)) { //說明根節(jié)點(diǎn)就是需要?jiǎng)h除的節(jié)點(diǎn) 59 root = root.next; //讓根節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)成為根節(jié)點(diǎn),自然就把根節(jié)點(diǎn)頂?shù)袅寺铮ú幌駭?shù)組那樣,要將后面的數(shù)據(jù)在內(nèi)存中整體挪一位) 60 } else { //否則 61 root.removeNode(data); 62 } 63 } 64 size--; 65 return true; 66 67 } 68 69 //輸出所有節(jié)點(diǎn) 70 public void print() { 71 if (root != null) { 72 System.out.print(root.data); 73 root.printNode(); 74 System.out.println(); 75 } 76 } 77 78 79 //方法:獲取全部數(shù)據(jù) 80 public String[] toArray() { 81 if (this.size == 0) { 82 return null; // 沒有數(shù)據(jù) 83 } 84 this.foot = 0; // 清零 85 this.retData = new String[this.size]; // 開辟數(shù)組大小 86 this.root.toArrayNode(); 87 return this.retData; 88 } 89 90 91 //獲取數(shù)據(jù)的長(zhǎng)度 92 public int size() { 93 return this.size; 94 } 95 96 //判斷是否為空鏈表 97 public boolean isEmpty() { 98 return this.size == 0; 99 }100 101 //清空鏈表102 public void clear() {103 this.root = null;104 this.size = 0;105 }106 107 108 //查詢數(shù)據(jù)是否存在109 public boolean contains(String data) { // 查找數(shù)據(jù)110 // 根節(jié)點(diǎn)沒有數(shù)據(jù),查找的也沒有數(shù)據(jù)111 if (this.root == null || data == null) {112 return false; // 不需要進(jìn)行查找了113 }114 return this.root.containsNode(data); // 交給Node類處理115 }116 117 118 //方法:根據(jù)索引取得數(shù)據(jù)119 public String get(int index) {120 if (index > this.size) { // 超過個(gè)數(shù)121 return null; // 返回null122 }123 this.foot = 0; // 操作foot來定義腳標(biāo)124 return this.root.getNode(index);125 }126 127 128 //定義一個(gè)節(jié)點(diǎn)內(nèi)部類(假設(shè)要保存的數(shù)據(jù)類型是字符串)129 //比較好的做法是,將Node定義為內(nèi)部類,在這里面去完成增刪、等功能,然后由LinkList去調(diào)用增、刪的功能130 class Node {131 private String data;132 private Node next; //next表示:下一個(gè)節(jié)點(diǎn)對(duì)象(單鏈表中)133 134 public Node(String data) {135 this.data = data;136 }137 138 //添加節(jié)點(diǎn)139 public void addNode(Node newNode) {140 141 //下面這段用到了遞歸,需要反復(fù)理解142 if (this.next == null) { // 遞歸的出口:如果當(dāng)前節(jié)點(diǎn)之后沒有節(jié)點(diǎn),說明我可以在這個(gè)節(jié)點(diǎn)后面添加新節(jié)點(diǎn)143 this.next = newNode; //添加新節(jié)點(diǎn)144 } else {145 this.next.addNode(newNode); //向下繼續(xù)判斷,直到當(dāng)前節(jié)點(diǎn)之后沒有節(jié)點(diǎn)為止146 147 }148 }149 150 151 //判斷節(jié)點(diǎn)是否存在152 public boolean containsNode(String data) { // 查找數(shù)據(jù)153 if (data.equals(this.data)) { // 與當(dāng)前節(jié)點(diǎn)數(shù)據(jù)吻合154 return true;155 } else { // 與當(dāng)前節(jié)點(diǎn)數(shù)據(jù)不吻合156 if (this.next != null) { // 還有下一個(gè)節(jié)點(diǎn)157 return this.next.containsNode(data);158 } else { // 沒有后續(xù)節(jié)點(diǎn)159 return false; // 查找不到160 }161 }162 }163 164 165 //刪除節(jié)點(diǎn)166 public void removeNode(String data) {167 if (this.next != null) {168 if (this.next.data.equals(data)) {169 this.next = this.next.next;170 } else {171 this.next.removeNode(data);172 }173 }174 175 }176 177 //輸出所有節(jié)點(diǎn)178 public void printNode() {179 if (this.next != null) {180 System.out.print("-->" + this.next.data);181 this.next.printNode();182 }183 }184 185 //獲取全部數(shù)據(jù)186 public void toArrayNode() {187 LinkList.this.retData[LinkList.this.foot++] = this.data;188 if (this.next != null) {189 this.next.toArrayNode();190 }191 }192 193 194 //根據(jù)索引位置獲取數(shù)據(jù)195 public String getNode(int index) {196 if (LinkList.this.foot++ == index) { // 當(dāng)前索引為查找數(shù)值197 return this.data;198 } else {199 return this.next.getNode(index);200 }201 }202 203 204 }205 }四、單鏈表的效率分析:
在單鏈表的任何位置上插入數(shù)據(jù)元素的概率相等時(shí),在單鏈表中插入一個(gè)數(shù)據(jù)元素時(shí)比較數(shù)據(jù)元素的平均次數(shù)為:

刪除單鏈表的一個(gè)數(shù)據(jù)元素時(shí)比較數(shù)據(jù)元素的平均次數(shù)為:

因此,單鏈表插入和刪除操作的時(shí)間復(fù)雜度均為O(n)。另外,單鏈表讀取數(shù)據(jù)元素操作的時(shí)間復(fù)雜度也為O(n)。
2、順序表和單鏈表的比較:
順序表:
優(yōu)點(diǎn):主要優(yōu)點(diǎn)是支持隨機(jī)讀取,以及內(nèi)存空間利用效率高;
缺點(diǎn):主要缺點(diǎn)是需要預(yù)先給出數(shù)組的最大數(shù)據(jù)元素個(gè)數(shù),而這通常很難準(zhǔn)確作到。當(dāng)實(shí)際的數(shù)據(jù)元素個(gè)數(shù)超過了預(yù)先給出的個(gè)數(shù),會(huì)發(fā)生異常。另外,順序表插入和刪除操作時(shí)需要移動(dòng)較多的數(shù)據(jù)元素。
單鏈表:
優(yōu)點(diǎn):主要優(yōu)點(diǎn)是不需要預(yù)先給出數(shù)據(jù)元素的最大個(gè)數(shù)。另外,單鏈表插入和刪除操作時(shí)不需要移動(dòng)數(shù)據(jù)元素;
缺點(diǎn):主要缺點(diǎn)是每個(gè)結(jié)點(diǎn)中要有一個(gè)指針,因此單鏈表的空間利用率略低于順序表的。另外,單鏈表不支持隨機(jī)讀取,單鏈表取數(shù)據(jù)元素操作的時(shí)間復(fù)雜度為O(n);而順序表支持隨機(jī)讀取,順序表取數(shù)據(jù)元素操作的時(shí)間復(fù)雜度為O(1)。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注