1,鏈表的實現node
在實際開發之中對象數組是一項很是實用的技術,而且利用其能夠描述出「多」方的概念,例如:一我的有多本書,則在人的類裏面必定要提供有一個對象數組保存書的信息,可是傳統的對象數組依賴於數組的概念,因此數組裏面最大的缺點在於:長度是固定的,正是由於如此在實際開發之中,傳統的數組應用是很是有限的(數組的接收與循環處理),若是想要實現靈活的數據保存,那麼就必須本身來實現結構。數組
傳統的對象數組的開發依賴於腳標(索引)的控制,若是想要實現內容的動態控制,那麼難度過高了,並且複雜度攀升,因此如今就能夠,對於一成不變的數據可使用對象數組來實現,可是對於可能隨時變化的數據就必須實現一個能夠動態擴充的對象數組。安全
所謂的鏈表實質性的本質是利用引用的邏輯關係來實現相似於數組的數據處理操做,以保存多方數據的數組相似的功能ide
經過分析可得,若是想要實現鏈表處理,那麼須要有一個公共的結構,這個結構能夠實現數據的保存以及下一個數據的鏈接的指向,爲了描述這樣一個邏輯,能夠把每一個存儲理解爲一個節點類,因此此時應該準備出一個節點類出來,可是這個節點類裏面能夠保存各類類型類型的數據。測試
雖然已經清楚須要使用Node節點進行數據的保存,可是畢竟這裏面須要牽扯到節點的引用處理關係,那麼這個引用處理關係是由使用者來控制嗎?這樣確定不可能,因此應該由一個專門的類來進行節點的引用關係的配置。this
範例:直接操做node很麻煩spa
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,數據增長3d
經過以前的分析能夠發如今進行鏈表操做的過程之中爲了不轉型的異常處理應該使用泛型,同時也應該設計一個鏈表的執行標準的接口,同時具體實現該接口的時候還應該經過Node類作出節點的關係描述。code
範例:基本結構
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 }