鏈表
1,鏈表的實現
在實際開發之中對象數組是一項非常實用的技術,並且利用其可以描述出“多”方的概念,例如:一個人有多本書,則在人的類里面一定要提供有一個對象數組保存書的信息,但是傳統的對象數組依賴於數組的概念,所以數組里面最大的缺點在於:長度是固定的,正是因為如此在實際開發之中,傳統的數組應用是非常有限的(數組的接收與循環處理),如果想要實現靈活的數據保存,那么就必須自己來實現結構。
傳統的對象數組的開發依賴於腳標(索引)的控制,如果想要實現內容的動態控制,那么難度太高了,而且復雜度攀升,所以現在就可以,對於一成不變的數據可以使用對象數組來實現,但是對於可能隨時變化的數據就必須實現一個可以動態擴充的對象數組。
所謂的鏈表實質性的本質是利用引用的邏輯關系來實現類似於數組的數據處理操作,以保存多方數據的數組類似的功能
通過分析可得,如果想要實現鏈表處理,那么需要有一個公共的結構,這個結構可以實現數據的保存以及下一個數據的連接的指向,為了描述這樣一個邏輯,可以把每個存儲理解為一個節點類,所以此時應該准備出一個節點類出來,但是這個節點類里面可以保存各種類型類型的數據。
雖然已經清楚需要使用Node節點進行數據的保存,但是畢竟這里面需要牽扯到節點的引用處理關系,那么這個引用處理關系是由使用者來控制嗎?這樣肯定不可能,所以應該由一個專門的類來進行節點的引用關系的配置。
范例:直接操作node很麻煩
1 class Node<E>{ 2 private E data; 3 private Node next; 4 public Node(E data) { 5 this.data = data; 6 } 7 public E getData() { 8 return this.data; 9 } 10 public void setNext(Node<E> next) { 11 this.next = next; 12 } 13 public Node getNext() { 14 return this.next; 15 } 16 } 17 public class Main { 18 public static void main(String[] args) { 19 Node<String> n1=new Node<String>("火車頭"); 20 Node<String> n2=new Node<String>("車廂一"); 21 Node<String> n3=new Node<String>("車廂二"); 22 Node<String> n4=new Node<String>("車廂三"); 23 Node<String> n5=new Node<String>("車廂四"); 24 25 n1.setNext(n2); 26 n2.setNext(n3); 27 n3.setNext(n4); 28 n4.setNext(n5); 29 30 print(n1); 31 // System.out.println("Hello World!"); 32 } 33 public static void print(Node<?> node){ 34 if (node!=null){//存在節點 35 System.out.println(node.getData()); 36 print(node.getNext());//遞歸調用 37 } 38 } 39 }
這樣肯定不可能,所以應該由專門的類來進行節點的引用關系的配置。因為真實的使用者實際上關心的只是數據的存取與獲取,所以現在應該對Node類進行包裝處理。
2,數據增加
通過之前的分析可以發現在進行鏈表操作的過程之中為了避免轉型的異常處理應該使用泛型,同時也應該設計一個鏈表的執行標准的接口,同時具體實現該接口的時候還應該通過Node類做出節點的關系描述。
范例:基本結構
1 interface ILink<E>{ 2 public void add(E e); 3 } 4 class LinkImpl<E> implements ILink<E>{ 5 private class Node{//保存節點的數據關系,【內部類形式】 6 private E data;//保存的數據 7 public Node(E data){//有數據的情況下才有意義 8 this.data=data; 9 } 10 } 11 //--------------以下為Link類中定義的結構--------- 12 }
范例:實現數據增加現在在所定義的Node類之中並沒有出現setter和getter方法,是因為內部類中的私有屬性也方便外部類直接訪問。
1 interface ILink<E>{ 2 public void add(E e); 3 } 4 class LinkImpl<E> implements ILink<E>{ 5 private class Node{//保存節點的數據關系,【內部類形式】 6 private E data;//保存的數據 7 private Node next; 8 public Node(E data){//有數據的情況下才有意義 9 this.data=data; 10 } 11 //第一次調用:this = LinkImpl.root; 12 //第二次調用:this = LinkImpl.root.next; 13 //第三次調用:this = LinkImpl.root.next.next; 14 public void addNode(Node newNode){//保存新的node數據 15 if(this.next==null){//當前節點的下一個節點為空 16 this.next=newNode;//保存當前節點 17 }else{ 18 this.next.addNode(newNode); 19 } 20 } 21 } 22 //--------------以下為Link類中定義的成員--------- 23 private Node root;//保存根元素 24 //--------------以下為Link類中定義的方法--------- 25 26 @Override 27 public void add(E e) { 28 if(e==null){//保存的數據為空 29 return; 30 } 31 //數據本身不具有關聯特性的,只有Node類有,那么要想實現關聯處理就必須將數據封裝在Node類中 32 Node newNode=new Node(e); 33 if(this.root==null){//現在沒有根節點 34 this.root=newNode; 35 }else {//根節點存在 36 this.root.addNode(newNode);//將新節點保存在合適的位置 37 } 38 39 } 40 } 41 public class Main { 42 public static void main(String[] args) { 43 ILink<String> all=new LinkImpl<String>(); 44 all.add("wanyu"); 45 all.add("hello"); 46 all.add("world"); 47 all.add("great"); 48 } 49 50 }
Link類只是負責數據的操作與根節點的處理,而所有后續節點的處理全部都是由Node類負責完成的。
3,獲取集合個數 public int size()
在鏈表中往往需要保存有大量的數據,那么這些數據往往需要進行數據個數的統計操作,所以應該在LinkImpl子類里面追加有數據統計信息,同時增加與刪除時都應該對個數進行修改。
①在ILink接口里追加一個獲取數據個數的方法:
1 interface ILink<E>{//設置泛型避免安全隱患 2 public void add(E e);//增加數據 3 public int size();//獲取數據的個數 4 }
②在LinkImpl的子類中追加有一個個數統計的子類
1 private int count;
③在add()的方法里面進行數據個數的追加
1 @Override 2 public void add(E e) { 3 if(e==null){//保存的數據為空 4 return; 5 } 6 //數據本身不具有關聯特性的,只有Node類有,那么要想實現關聯處理就必須將數據封裝在Node類中 7 Node newNode=new Node(e); 8 if(this.root==null){//現在沒有根節點 9 this.root=newNode; 10 }else {//根節點存在 11 this.root.addNode(newNode);//將新節點保存在合適的位置 12 } 13 this.count++; 14 }
④在LinkImpl的子類中里面來返回數據的個數
1 public int size(){ 2 return this.count; 3 }
只是對於數據保存中的一個輔助功能。
范例:完整程序
1 interface ILink<E>{//設置泛型避免安全隱患 2 public void add(E e);//增加數據 3 public int size();//獲取數據的個數 4 } 5 class LinkImpl<E> implements ILink<E>{ 6 private class Node{//保存節點的數據關系,【內部類形式】 7 private E data;//保存的數據 8 private Node next; 9 public Node(E data){//有數據的情況下才有意義 10 this.data=data; 11 } 12 //第一次調用:this = LinkImpl.root; 13 //第二次調用:this = LinkImpl.root.next; 14 //第三次調用:this = LinkImpl.root.next.next; 15 public void addNode(Node newNode){//保存新的node數據 16 if(this.next==null){//當前節點的下一個節點為空 17 this.next=newNode;//保存當前節點 18 }else{ 19 this.next.addNode(newNode); 20 } 21 } 22 } 23 //--------------以下為Link類中定義的成員--------- 24 private Node root;//保存根元素 25 private int count; 26 //--------------以下為Link類中定義的方法--------- 27 @Override 28 public void add(E e) { 29 if(e==null){//保存的數據為空 30 return; 31 } 32 //數據本身不具有關聯特性的,只有Node類有,那么要想實現關聯處理就必須將數據封裝在Node類中 33 Node newNode=new Node(e); 34 if(this.root==null){//現在沒有根節點 35 this.root=newNode; 36 }else {//根節點存在 37 this.root.addNode(newNode);//將新節點保存在合適的位置 38 } 39 this.count++; 40 } 41 public int size(){ 42 return this.count; 43 } 44 } 45 public class Main { 46 public static void main(String[] args) { 47 ILink<String> all=new LinkImpl<String>(); 48 System.out.println("【增加之前:】數據個數:"+all.size()); 49 all.add("wanyu"); 50 all.add("hello"); 51 all.add("world"); 52 all.add("great"); 53 System.out.println("【增加之后:】數據個數:"+all.size()); 54 } 55 }
4,空集合判斷public boolean isEmpty()
鏈表里面可以保存有若干個數據,如果說現在鏈表還沒有保存數據,則就表示是一個空集合。
①在ILink接口里面追加有判斷方法:
1 public boolean isEmpty();//判斷是否為空集合
使用根節點和個數本質一樣。
②在LinkImpl子類里面覆寫此方法;
1 public boolean isEmpty(){ 2 // return this.root==null; 3 return this.count==0; 4 }
5,返回集合數據 public Object[] toArray()
鏈表本身就屬於一個動態對象數組,那么既然屬於一個對象數組,就應該可以把所有的數據以數組的形式返回,那么這個時候就可以定義一個toArray()方法,但是這個時候的方法只能夠返回Object型的數組。
①在ILink接口里面追加新的處理方法
1 public Object[] toArray();//將集合元素以數組的形式返回
②在LinkImpl子類里面追加有兩個屬性,
1 private int foo;//描述操作數組的腳標 2 private Object[] returnData;//返回的數據保存
③在Node類中根據遞歸獲取數據
1 //第一次調用:this=LinkImpl.root 2 //第二次調用:this=LinkImpl.root.next 3 //第三次調用:this=LinkImpl.root.next.mext 4 public void toArrayNode(){ 5 LinkImpl.this.returnData [foo++]=this.data; 6 if (this.next!=null){//還有下一個數據 7 this.next.toArrayNode(); 8 } 9 }
④在進行數據返回的時候一定要首先判斷是否為空集合;
1 @Override 2 public Object[] toArray() { 3 if(this.isEmpty()){//空集合 4 return null;//現在沒有數據 5 } 6 this.foo=0;//腳標清零 7 this.returnData=new Object[this.count]; 8 9 //根據已有的數據長度開辟數組 10 this.root.toArrayNode();//利用Node類進行遞歸獲取數據 11 return this.returnData; 12 }
集合的數據一般如果要返回肯定要以對象的形式返回。
6,根據索引取得數據public E get(int index)
鏈表可以向數組一樣處理,所以也應該可以向數組一樣進行索引數據的獲取,再這樣的情況下我們可以使用遞歸的形式來完成。
①在ILink接口中追加有新的方法
1 public E get(int index);//根據索引獲取數據
②在Node類里面追加有根據索引獲取數據的處理
1 @Override 2 public E get(int index) { 3 if (index>=this.count){//索引應該在指定的范圍之內 4 return null; 5 }//索引數據的獲取應該由Node類完成 6 this.foot=0;//重置索引的下標 7 return this.root.getNode(index); 8 }
這一特點和數組很相似,但是需要注意,數組獲取一個數據的時間復雜度為1,而鏈表獲取一個數據的時間復雜度為n。
7,鏈表(修改指定索引數據)
現在已經可以根據索引來獲取指定的數據了,但是既然可以獲取數據,那么也可以進行數據的修改。
①在ILink接口中追加新的方法
1 public void set(int index,E data);//修改索引數據
②在Node類之中應該提供有數據修改的處理支持
1 public void setNode(int index,E data){ 2 if(LinkImpl.this.foot++==index){//索引相同 3 this.data=data;//返回當前數據 4 }else{ 5 this.next.setNode(index,data); 6 } 7 }
③在LinkImpl中進行方法覆寫
1 @Override 2 public void set(int index, E data) { 3 if (index>=this.count){//索引應該在指定的范圍之內 4 return ;//方法結束 5 }//索引數據的獲取應該由Node類完成 6 this.foot=0;//重置索引的下標 7 this.root.setNode(index,data);//修改數據 8 }
這種操作的時間復雜度也是N,因為也是需要進行數據的遍歷處理
8,鏈表(判斷數據是否存在)public boolean contains(E data)
在一個集合里面往往會保存有大量的數據,有些時候需要判斷某個數據是否存在,這個時候就可以通過對象比較的模型(equals())進行判斷。
①在ILink接口中追加判斷方法
1 public boolean contains(E data);//判斷數據是否存在
②在Node類中進行依次判斷
1 public boolean containsNode(E data) { 2 if(this.data.equals(data)){//對象比較 3 return true; 4 }else { 5 if(this.next==null){//沒有后續節點了 6 return false;//找不到數據 7 }else { 8 return this.next.containsNode(data); 9 } 10 } 11 }
③在LinkImpl子類里面實現此方法
1 @Override 2 public boolean contains(E data) { 3 if(data==null){ 4 return false; 5 } 6 return this.root.containsNode(data);//交給Node類判斷 7 }
由於整個鏈表沒有null數據的存在,所以整體的程序在判斷的時候直接使用每個節點數據發出的equals()方法調用即可。
9,鏈表(數據刪除)public void remove(E data)
數據的刪除指的是可以從集合里面刪除掉指定的一個數據內容,也就是說此時傳遞的是數據內容,那么如果要實現這種的刪除操作,依然需要對象的比較的支持,但是對於集合數據的刪除需要考慮兩種情況:
·要刪除的是根節點(LinkImpl與根節點有關,所以這個判斷由根節點完成):
·要刪除的不是根節點(由Node類負責):
①在ILink接口中追加新的方法;
1 public void remove(E e);//數據刪除
②在LinkImpl子類里面實現根節點的判斷;
1 @Override 2 public void remove(E data) { 3 if(this.contains(data)){//判斷數據是否存在 4 if (this.root.data.equals(data)){//根節點為要刪除節點 5 this.root=this.root.next;//根的下一個節點 6 } 7 } 8 }
③如果現在根節點並不是要刪除的節點,那么就需要進行后續節點判斷,但是請一定要記住,此時的根節點已經判斷完成,在判斷應該從下一個節點開始判斷,在Node類中追加刪除處理。
1 public void removeNode(Node previous,E data){ 2 if(this.data.equals(data)){ 3 previous.next=this.next;//空出當前節點 4 }else { 5 if(this.next!=null){//有后續節點 6 this.next.removeNode(this,data);//向后繼續刪除 7 } 8 } 9 }
④完善LinkImpl子類中的remove方法
1 @Override 2 public void remove(E data) { 3 if(this.contains(data)){//判斷數據是否存在 4 if (this.root.data.equals(data)){//根節點為要刪除節點 5 this.root=this.root.next;//根的下一個節點 6 }else {//交由Node類負責刪除 7 this.root.next.removeNode(this.root,data); 8 } 9 this.count--; 10 } 11 }
刪除的邏輯依靠的就是引用的改變處理完成的。
10,鏈表(清空數據)public void clean()
有些時候需要進行鏈表的整體清空處理,這個時候就可以直接根據根節點元素來進行控制,只要root設置為null,那么后續節點就都不存在了。
①在ILink接口中里面追加有清空處理方法
1 public void clean();//清空集合
②在LinkImpl的子類里面覆寫方法
1 @Override 2 public void clean() { 3 this.root=null;//后續的所有節點都沒了 4 this.count=0;//個數清零 5 }
這些就是鏈表的基本功能,當然這只是一個最簡單、最基礎的單向鏈表
11,綜合實戰:寵物商店
現在假設有一個寵物商店里面可以出售各種寵物,要求可以實現寵物的上架處理、下架處理,也可以根據我們的關鍵字查詢出寵物的信息。
①應該定義出寵物的標准
1 interface Pet{//定義寵物的標准 2 public String getName();//獲得名字 3 public String getColor();//獲得顏色 4 }
②寵物商店應該以寵物的標准為主
1 class PetShop{//寵物商店 2 private ILink<Pet> allPets=new LinkImpl<Pet>();//保存多個寵物信息 3 public void add(Pet pet){//追加寵物,商品上架 4 this.allPets.add(pet);//集合中保存對象 5 } 6 public void delete(Pet pet){ 7 this.allPets.remove(pet);//集合中保存對象 8 } 9 public ILink<Pet> search(String keyword){ 10 ILink<Pet> searchResult=new LinkImpl<Pet>();//保存查詢結果 11 Object[] result=this.allPets.toArray();//獲取全部數據 12 if(result!=null){ 13 for(Object obj:result){ 14 Pet pet=(Pet) obj; 15 if(pet.getName().contains(keyword)||pet.getColor().contains(keyword)){ 16 searchResult.add(pet);//保存查詢結果 17 } 18 } 19 } 20 return searchResult; 21 } 22 }
③根據寵物的標准來定義寵物的信息
1 class Cat implements Pet{//實現寵物標准 2 private String name; 3 private String color; 4 5 public Cat(String name, String color) { 6 this.name = name; 7 this.color = color; 8 } 9 10 @Override 11 public String getName() { 12 return name; 13 } 14 15 @Override 16 public String getColor() { 17 return color; 18 } 19 20 @Override 21 public boolean equals(Object obj) { 22 if(obj==null){ 23 return false; 24 } 25 if(!(obj instanceof Cat)){ 26 return false; 27 } 28 if (this==obj){ 29 return true; 30 } 31 Cat cat=(Cat) obj; 32 return this.name.equals(cat.name) && this.color.equals(cat.color); 33 } 34 35 @Override 36 public String toString() { 37 return "【寵物貓】名字:"+this.name+",顏色:"+this.color; 38 } 39 }
④實現寵物商店的操作
1 public class Main { 2 public static void main(String[] args) { 3 PetShop shop=new PetShop();//開店 4 shop.add((new Dog("黃斑狗","綠色"))); 5 shop.add((new Cat("小強貓","深綠色"))); 6 shop.add((new Cat("黃貓","深色"))); 7 shop.add((new Dog("黃狗","黃色"))); 8 shop.add((new Dog("斑點狗","灰色"))); 9 Object[] result=shop.search("黃").toArray(); 10 if(result!=null){ 11 for(Object temp:result){ 12 System.out.println(temp.toString()); 13 } 14 } 15 } 16 }
所有的程序開發都是以接口為標准進行開發的,這樣在進行后期程序處理的時候就可以非常的靈活,只要符合標准的對象都可以保存。
12,綜合實戰:超市實戰
①定義一個商品的標准
1 interface IGoods{//定義商品的標准 2 public String getName(); 3 public double getPrice(); 4 }
②定義購物車處理標准
1 interface IShopCar{//購物車的標准 2 public void add(IGoods goods);//添加商品信息 3 public void delete(IGoods goods);//添加商品信息 4 public Object[] getAll();//獲得購物車中的全部商品信息 5 }
③定義購物車的實現類
1 class ShopCarImpl implements IShopCar{//購物車 2 private ILink<IGoods> allGoodses=new LinkImpl<IGoods>(); 3 public void add(IGoods goods){ 4 this.allGoodses.add(goods); 5 } 6 public void delete(IGoods goods){ 7 this.allGoodses.remove(goods); 8 } 9 public Object[] getAll(){ 10 return this.allGoodses.toArray(); 11 } 12 }
④定義收銀台
1 class Cashier{//收銀台 2 private IShopCar shopCar;//駝峰命名法 3 public Cashier(IShopCar shopCar) { 4 this.shopCar = shopCar; 5 } 6 public double allPrice(){//計算商品總價 7 double money=0.0; 8 Object[] result=this.shopCar.getAll(); 9 if(result!=null){ 10 for(Object obj:result){ 11 IGoods goods=(IGoods) obj; 12 money +=goods.getPrice(); 13 } 14 } 15 return money; 16 } 17 public int allCount(){//計算商品總個數 18 return this.shopCar.getAll().length; 19 } 20 }
⑤定義商品信息
1 class Book implements IGoods{ 2 private String name; 3 private double price; 4 public Book(String name, double price) { 5 this.name = name; 6 this.price = price; 7 } 8 @Override 9 public String getName() { 10 return this.name; 11 } 12 @Override 13 public double getPrice() { 14 return this.price; 15 } 16 public boolean equals(Object obj){ 17 if(obj==null){ 18 return false; 19 } 20 if(this==obj){ 21 return true; 22 } 23 if (!(obj instanceof Book)){ 24 return false; 25 } 26 Book book=(Book) obj; 27 return this.name.equals(book.name) && price==book.price; 28 } 29 }
⑥代碼測試的編寫
1 public class Main { 2 public static void main(String[] args) { 3 IShopCar car=new ShopCarImpl(); 4 car.add(new Book("Java開發",79.8)); 5 car.add(new Book("MySql開發",40.0)); 6 car.add(new Book("Spring開發",99.8)); 7 car.add(new Bag("黑色電腦包",201.0)); 8 Cashier cashier=new Cashier(car); 9 System.out.println("總價格:"+cashier.allPrice()); 10 System.out.println("總數量:"+cashier.allCount()); 11 Object[] result=car.getAll(); 12 if(result!=null){ 13 for(Object temp:result){ 14 System.out.println(temp.toString()); 15 } 16 } 17 } 18 }