1、線性表定義:
java
線性表是由n(n>=0)個類型相同的數據元素組成的有限序列,第一個元素無前驅元素,最後一個無後繼元素,其餘元素有且僅有一個前驅和一個後繼。數組
線性表接口LList的定義:app
package com.list; public interface LList<T> { boolean isEmpty(); //判斷線性表是否爲空 int length(); //返回線性表的長度 T get(int i); //返回第i個元素 void set(int i,T x); //設置第i個元素爲x void inset(int i,T x);//插入x做爲第i個元素 void append(T x); //在線性表最後插入x元素 T remove(int i); //刪除第i個元素並返回被刪除的對象 void removeAll(); //刪除線性表的全部元素 T search(T key); //查找,返回首次出現的關鍵字爲key元素 }
2、線性表的順序表示和實現函數
一、線性表的順序存儲結構:用一組連續的內存單元依次存放線性表的數據元素,元素內存的物理存儲次序與他們在線性表中的邏輯次序相同。順序存儲的線性表也稱爲順序表。this
順序表是一種隨機存取結構。存取任何一個元素的時間複雜度是O(1)。順序表一般採用數組存儲數據。指針
二、順序表實現類及其操做code
分析:順序表採用數組存儲數據元素,因此要有一個成員變量elements存放線性表的一維數組。同時還要有成員變量len表示順序表的長度,且len<=elements.length。對象
package com.list.impl; import com.list.LList; public class SeqList<T> implements LList<T> { /****************************初始化*************************************************/ private Object[] elements; //順序表採用數據存放數據元素 private int len; //順序表的長度,記錄實際元素個數 //無參的構造函數 public SeqList(){ this(64); //建立默認容量的數組 } //有參構造函數 public SeqList(int size){ this.elements=new Object[size]; //初始化存儲數組 this.len=0; //設順序表的初始長度爲0 } /***************************操做***********************************************/ /** * 判斷順序表是否爲空 */ public boolean isEmpty() { //根據線性表的實際長度來判斷 return this.len==0; } public int length() { return this.len; } /** * 取第i個元素在數組中是[i-1] */ public T get(int i) { if(i>=1 && i<=this.len){ return (T) elements[i-1]; } return null; } /** * 設置第i個元素的值爲x */ public void set(int i, T x) { if(x==null){ return; } if(i>=1 && i<=this.len){ elements[i-1]=x; } else{ throw new IndexOutOfBoundsException(i+""); } } /** * 返回順序表全部元素的描述字符串,形式爲(,)覆蓋Object中的toString()方法 */ public String toString(){ String str="("; if(this.len>0){ str+=this.elements[0].toString(); } for(int i=1;i<this.len;i++){ str+=","+elements[i].toString(); } return str+")"; } /** * 插入x做爲第i個元素 */ public void insert(int i, T x) { if(x==null){ return; } //若是數組已滿,則擴充順序表容量 if(this.len==elements.length){ Object[] temp=this.elements; //建立臨時temp變量引用elements數組 this.elements=new Object[temp.length*2]; //從新申請一個容量更大的數組 //複製數組元素 for(int j=1;j<=temp.length;j++){ this.elements[j-1]=temp[j-1]; } } //若是i小於1,則設i爲1 if(i<1){ i=1; } //若是插入位置大於線性表的長度,至關於在最後插入數據,則就設i爲len if(i>this.len){ i=this.len+1; } //元素後移,len+1 /* for(int j=i;j<=this.len-1;j++){ elements[j]=elements[j-1]; }*/ for(int j=this.len-1;j>=i;j--){ this.elements[j+1]=this.elements[j]; } this.elements[i-1]=x; this.len++; } //在順序表的最後插入元素x public void append(T x) { this.insert(this.len, x); } //刪除順序表的第i個元素,並返回此元素 public T remove(int i) { // TODO Auto-generated method stub return null; } //刪除線性表中的全部元素 public void removeAll() { this.len=0; } public T search(T key) { // TODO Auto-generated method stub return null; } }
三、順序表操做的效率分析接口
①、存取任何一個元素的時間複雜度爲O(1)。
內存
②、插入刪除效率很低,在等機率狀況下時間複雜度爲O(n)。
四、順序表的深拷貝和淺拷貝
拷貝構造方法:一個類的構造方法,若是其參數是該類對象,則稱爲拷貝構造方法。器主要做用是複製對象。
(1)、順序表的淺拷貝
將當前對象的各成員變量賦值爲實際參數對應各成員變量值,稱爲淺拷貝。
當成員變量的數據類型爲基本數據類型時,淺拷貝可以實現對象複製功能。當成員變量的數據類型時引用類型時,淺拷貝只複製了對象引用,並無真正實現對象複製功能。
public SeqList(SeqList<T> list){ this.elements=list.elements; this.len=list.len; }
兩個對象擁有同一個數組,形成修改、刪除、插入等操做結果相互影響。
(2)、順序表的深拷貝
當一個類包含引用類型的成員變量時,該類聲明的拷貝構造函數,不只要複製對象的全部基本類型成員變量值,還要從新申請引用類型變量佔用的動態存儲空間,並複製其中全部對象,這種複製方式成爲深拷貝。
public SeqList(SqeList<T> list){ this.len=list.len; this.elements=new Object[list.elements.length]; for(int i=0;i<list.elements.length;i++){ this.elements[i]=list.elements[i]; } }
五、順序表比較相等
比較兩個對象順序表是否相等,是指他們的長度相同而且各對應元素相等。覆蓋Object的equals()方法:
public boolean equals(Object obj){ if(this==obj){ return true; } if(obj instanceof SeqList){ SeqList<T> list=(SeqList)obj; if(this.length==list.length){ for(int i=0;i<this.length;i++) if(!(this.get(i)).equals(list.get(i))) return false; return true; } } return false; }
3、線性表的鏈式表示和實現
一、線性表的鏈式存儲:用若干地址分散的存儲單元存儲數據元素,邏輯上相鄰的數據元素在物理位置上不必定相鄰,必須採用附加信息表示數據元素之間的順序關係。所以,存儲一個數據元素的存儲單元至少包含兩部分:數據域和地址域。
二、單鏈表類實現及其操做:
分析:由於線性表的鏈式存儲結構,邏輯上相鄰的物理位置上不必定相鄰,因此存儲單元至少要包含數據域和地址域。
結點Node實現:
package com.list.impl; public class Node<T> { private T data; //數據域,保存數據元素 private Node<T> next;//地址與,引用後繼結點 public Node(){ this(null,null); } public Node(T data,Node<T> next){ this.data=data; this.next=next; } }
Node類時「自引用類」,指一個類聲明包含一個引用當前類的對象的成員變量。Node類的一個對象表示單鏈表中的一個結點,經過next鏈,將兩個結點連接到一塊兒。
創建連接:
Node<String> p=new Node<String>("A",null); Node<String> q=new Node<String>("B",null); p.next=q; 或者 Node<String> q=new Node<String>("B",null); Node<String> p=new Node<String>("A",q);
head存儲線性鏈表第一結點的地址,稱爲頭指針。head==null時,表示空鏈表。
單鏈表的遍歷:遍歷單鏈表是指從第一個結點開始,沿着結點的next鏈,依次訪問單鏈表中的每一個結點,而且每一個結點只訪問一次。遍歷單鏈表操做不能改變頭指針head,所以須要聲明一個變量p指向當前訪問結點。p從head指向的結點開始訪問,再沿着next鏈到達後繼結點,逐個訪問。
Node<T> p=head; while(p!=null){ System.out.println(p.data.toString()); p=p.next; }
三、帶頭結點的單鏈表
帶頭結點的單鏈表是指,在單鏈表的第一個結點以前增長一個特殊的結點,稱爲頭結點。頭結點的做用是使全部鏈表(包括空表)的頭指針非空,並使單鏈表的插入、刪除操做不須要區分是否爲空表或是否在第一個位置進行,從而與其餘位置的插入、刪除操做一致。
package com.list.impl; import com.list.LList; /** * 構造帶頭結點的單鏈表 * @author wangning * * @param <T> */ public class LinkList<T> implements LList<T>{ /****************構造鏈表**************************/ private Node<T> head; //頭指針,指向單鏈表的頭結點 //默認構造方法,構造空鏈表,建立頭結點,data和next均爲null public LinkList(){ this.head=new Node<T>(); } //由指定的數組中的多個對象構造單鏈表,採用尾插法 public LinkList(T[] elements){ this();//建立空鏈表 Node<T> rear=this.head; //rear指向單鏈表的最後一個結點 for(int i=0;i<elements.length;i++){ rear.next=new Node<T>(elements[i],null); //尾插入,建立結點鏈入rear結點以後 rear=rear.next; //rear指向新的鏈尾結點 } } /* * 頭查法構造鏈表 public LinkList(T[] elements){ this(); for(int i=0;i<elements.length;i++){ Node<T> p=new Node<T>(elements[i],null);//新增結點 p.next=head.next; //帶頭結點的寫法 head.next=p; //帶頭結點的寫法 } } */ /** * 判斷單鏈表是否爲空 */ public boolean isEmpty() { return this.head.next==null; } /** * 返回單鏈表的長度 */ public int length() { int i=0; Node<T> p=this.head.next; while(p!=null){ i++; p=p.next; } return i; } public String toString(){ String str="("; Node<T> p=this.head.next; while(p!=null){ str+=p.data.toString(); if(p.next!=null){ str+=","; } p=p.next; } return str+")"; } /** * 返回第i個元素 */ public T get(int i) { if(i>=0){ Node<T> p=this.head.next; //第一個結點 for(int j=0;p!=null && j<i; j++){ p=p.next; } if(p!=null){ return p.data; } } return null; } //設置第i個元素值爲x public void set(int i, T x) { if(x==null){ return; } if(i>=0){ Node<T> p=this.head.next; //第一個結點 for(int j=0;p!=null && j<i;j++){ p=p.next; } if(p!=null){ p.data=x; } } else throw new IndexOutOfBoundsException(); } //將x對象插入在序號爲i結點前 public void insert(int i, T x) { if(x==null){ return; } Node<T> p=this.head; //投加點 for(int j=0;p!=null && j<i;j++){ p=p.next; //i結點的前驅 } p.next=new Node<T>(x,p.next); } public void append(T x) { this.insert(Integer.MAX_VALUE, x); } public T remove(int i) { if(i>=0){ Node<T> p=this.head; //頭結點 for(int j=0;p!=null && j<i; j++){ p=p.next; //定位到待刪除結點的前去結點 } if(p.next!=null){ T d=p.next.data; p.next=p.next.next; return d; } } return null; } public void removeAll() { this.head.next=null; } public T search(T key) { // TODO Auto-generated method stub return null; } }
四、 單鏈表操做的效率分析
isEmpty()方法的時間複雜度是O(1);length()方法要遍歷單鏈表,時間複雜度爲O(n)。
單鏈表是一種順序存取結構,不是隨機存取結構。insert(p,x)。方法在單鏈表指定p結點以後插入一個結點,時間複雜度爲O(1)。insert(i,x)方法插入x做爲第i個結點,時間複雜度爲O(n),其花費時間視插入位置而定,若在單鏈表最後插入,則時間複雜度爲O(n)。
對單鏈表進行插入和刪除操做只要改變少許結點的鏈,不須要移動數據元素。單鏈表中的結點在刪除和插入過程當中,是動態釋放和申請的,不須要預先給單鏈表分配存儲空間,從而避免了順序表因存儲空間不足擴充空間和複製元素的過程,提供了運行效率和存儲空間利用率。
提升單鏈表操做效率的措施:
因爲單鏈表長度的length()方法須要遍歷整個單鏈表,因此在某些時候須要使用長度的狀況下,經歷避免兩次遍歷單鏈表。
public boolean append(T x){ return insert(this.length(),x); }
insert將遍歷單鏈表2次,改成rentrun insert(Integer.MAX_VALUE,x);則只需遍歷一次就將x結點插入在單鏈表以後。若是在單例表中添加某些私有成員變量,則可提升某些操做的效率,例如添加len變量表示單鏈表長度,添加rear做爲單鏈表的尾指針。