數據結構之表結構【數組,鏈表的實現方式】(數據結構讀後感 一)

最近除了在看Java虛擬機外,我還在學習數據結構與算法分析(Java語言),這是因爲前段時間去公司面試的時候被問到一些數據結構和算法都有點不知所措,程序不就是=數據結構+算法嗎,我想要作爲一名合格的程序員,所以我準備啃下這本書,不管再難都得啃下,人生不亦如此嗎。嘻嘻,我是一個打不倒的元氣妹

首先要學習的是最簡單和最基本的三種數據結構:表,棧和隊列。每一個有意義的程序都將顯示的至少使用一種這樣的數據結構。今天我要記錄的是學習 表 結構。

抽象數據類型(abstract data type,ADT)是帶有一組操作的一些對象的集合,以及我們接下來要討論的表,棧,隊列都是一種ADT結構。

諸如表,集合,圖以及與他們各自的操作一起形成的這些對象都可以被看做是抽象數據類型,這是一種什麼樣的概念呢?就好比我們說整數,實數和布爾數是數據類型一樣,當然他們都有自己對應的一系列操作,抽象數據類型(ADT)也一樣,就拿集合ADT來說,可以有添加(add),刪除(delete),以及包含(contain)這樣的一些操作。Java類考慮到了ADT的實現,但是隱藏了一些細節,如果我們在程序中需要對ADT進行一些操作,就可以調用一些合適的方法來執行,當他們被正確執行或實現之後,使用他們的程序卻沒有必要知道他們是如何實現的。

表ADT:

形如A0,A1,A2,A3...An-1,An的一般的表,說這個表的大小是N,將大小爲0的表稱爲空表。

在表ADT上進行的操作有:printList和makeEmpty,find:返回某一項首次出現的位置;findKth:返回某個位置上的元素;insert(x,b):在位置b上插入x元素;remove(a):移除a元素;

以上舉的一系列操作都可以通過數組來實現,只是存在一個效率問題;

我們需要知道的是:數組雖然是固定容量的,但是在需要的時候也可以用雙倍的容量創建一個不同的數組,下面是解釋數組arr在必要的時候如何被擴展的,其初始容量爲10:

package ADT;

//測試對數組擴容
public class ArrayTest {
	public static void main(String[] args) {
		
		int[] arr = new int[10];
		System.out.println(arr.length);//10
		//..
		//下面我們對數組arr進行擴容
		int[] newArr = new int[arr.length*2];
		for (int i = 0; i < arr.length; i++) {
			newArr[i] = arr[i];
		}
		arr = newArr;
		System.out.println(arr.length);//20
	}
}

最壞的情況下:在位置0的插入(在表前端插入)和刪除第一個元素(在表前端刪除)都需要移動表中的所有元素,則最壞情況花費時間O(N);

最壞的情況下:在最後插入(在表的末尾插入)和在表末尾插入都不需要移動表的元素,則最好的情況下花費時間O(1);

所以,對於不同的操作,我們選擇不同的數據結構

當在表末尾(高端)進行插入操作,其後做的最多的是對數組的訪問,我們選擇數組來做表的實現;

當經常對錶做一些插入和刪除操作,特別是在表前端進行,那麼數組效率不高,我們選擇鏈表作爲表的適合實現。

簡單鏈表:

1234

爲了執行printList或find(x)只需要從表的第一個節點開始然後用一些後繼的next鏈遍歷該表即可,其中findKth()沒有數組的執行效率高, 但是remove()可以直接修改一個next鏈來實現,插入insert()使用new操作符創建一個新的節點,然後兩次修改next引用,效率較數組實現會更高。典型的鏈表擁有到該表兩端的鏈,刪除最後一項時,要先找到指向最後節點的項,把它的next改爲null。

Java Collections API 中的表:

Collection接口:位於java.util包中,集合的概念在Collection接口中變得抽象,存儲一組類型相同的對象,下面是Collection接口中常用的方法:

public interface Collection<AnyType> extends Iterable<AnyType>{
	
	int size();
	boolean isEmpty();
	void clear();
	boolean contains(AnyType x);
	boolean add(AnyType x);
	boolean remove(AnyType x);
	java.util.Iterator<AnyType> iterator();
}

Collection接口擴展了Iterable接口,實現了Iterable接口的類可以擁有增強for循環就,如下:

//實現了Iterable接口的類可以使用增強for循環
	public static <AnyType> void print(Collection<AnyType>  coll){
		for (AnyType item : coll) {
			System.out.println(item);
		}
	}
	
	//上面的增強for循環代碼等價於此段代碼
	public static <AnyType> void print(Collection<AnyType> coll){
		Iterator<AnyType> itr = coll.iterator();
		while(itr.hasNext()){
			AnyType item = itr.next();
			System.out.println(item);
		}
	}

Iterator接口:

實現了Iterable接口的類必須實現iterator()方法獲得一個Iterator對象,如上代碼,不同的集合會得到不同的Iterator對象,Iterator是一個在java.util包下的接口,如下:

public interface Iterator<AnyType>{
	boolean hasNext();
	AnyType next();
	void remove();
}

對於Iterator接口,因爲他的現有方法有限,所以很難使用Iterator做簡單遍歷Collection以外的任何工作,還有一個點需要注意的是,Iterator接口有一個remove方法,一般我們都使用Iterator接口的remove方法而不用Collection的remove,主要原因有兩點:

1. Collection的remove方法必須找出要被刪除的項(意味着,就算已經知道了要刪除項的準確位置,還是需要再去遍歷一遍)

2.如果對正在迭代的集合進行結構上的改變,如(增,刪),如果這個時候使用Collection的remove方法,則會報異常(ConcurrentModificationException)

List接口,ArrayList類和LinkedList類:

我們要學的是最大的集合就是表(list),它由java.util包中的List接口所指定,List接口繼承了Collection接口,因此它包含了Collection接口的所有方法和一些他自己的方法:

public interface List<AnyType> extends Collection<AnyType>{
	AnyType get(int index);
	AnyType set(int index,AnyType newVal);
	void add(int index,AnyType x);;
	void remove(int index);
	
	ListIterator<AnyType> listIterator(int pos);
}

List ADT有兩種流行的實現方式:ArrayList和LinkedList;

ArrayList提供了List ADT的一種可增長數組的實現,他的優點是便於查詢,get和set方法花費的時間爲常數,而缺點是不便insert和delete;

LinkedLis提供了List ADT的雙鏈表實現,他的優點在於假設變動項的位置已知,則他的新增和刪除開銷很小,在表的前端進行添加和刪除所花費的時間都是常數的,但是對get的調用花銷很大,除非調用的是接近表末尾的數據,這樣可以從表的末尾開始遍歷。

所以可以做個總結:

在表的前端進行添加,LinkedList花費的時間爲O(N),而ArrayList花費的時間爲O(N的平方);

在表的末尾進行添加,LinkedList和ArrayList所花費的時間都是O(N);

在表的前端進行查找,ArrayList花費的時間爲O(N),而LinkedList花費的時間爲O(N的平方),但是 如果是使用的增強for循環,則不論是哪一種List,所花費的時間都爲O(N);

在這篇文章的結尾有一個例子:

如果表包含有6,5,1,4,2,則在該方法被調用之後,表中僅有5,1,也就是說,我們要寫一個刪除表中偶數的方法;

思路一:我們首先淘汰ArrayList,因爲他對於刪除這個操作效率並不高,所以我們考慮用LinkedList,但需要知道是,LinkedList的get方法效率並不高,如下:

思路二:不是用get,而是用迭代器一步步遍歷,當找到我們需要刪除的元素時,就使用Collection的remove方法來刪除,但是這個方法是行不通的,會報異常:

思路三:在迭代器找到一個偶數值時,用迭代器來刪除這個值,但是這個對於LinkedList是可用的,效率也高,但是對於ArrayList而言,其remove方法仍然是昂貴的,因爲它仍然需要移動。

public static void main(String[] args) {
		System.out.println("第一種開始"+getDateTimeIN());
		List<Integer> list = new LinkedList<Integer>();
		list.add(6);
		list.add(5);
		list.add(1);
		list.add(4);
		list.add(2);
		//測試第一種
		//removeEvensVer1(list);
		
		
		 //第一種的結果如下:
		 /* 第一種開始2018/12/20-15:39:41:872
			刪除完畢
			第一種結束2018/12/20-15:39:41:881
		 */
		//測試第三種
		removeEvensVer3(list);
		System.out.println("第一種結束"+getDateTimeIN());
		//第三種結果
	
		/*  第一種開始2018/12/20-15:41:23:756
			刪除完畢
			第一種結束2018/12/20-15:41:23:763
		 */

	}
	/**
	 * 第一種,利用LinkedList
	 * @param list
	 */
	public static void removeEvensVer1(List<Integer> list){
		int i = 0;
		while(i<list.size()){
			if(list.get(i)%2 == 0){
				list.remove(i);
			}else{
				i++;
			}
		}
		System.out.println("刪除完畢");
	}
	
	/**
	 * 第二種
	 * @return
	 */
	public static void removeEvensVer2(List<Integer> list){
		for (Integer item : list) {
			if(item % 2 == 0){
				list.remove(item);//拋出異常
			}
		}
	}
	
	/**
	 * 第三種
	 * @return
	 */
	public static void removeEvensVer3(List<Integer> list){
		Iterator<Integer> itr = list.iterator();
		while(itr.hasNext()){
			if(itr.next() % 2 == 0){
				itr.remove();
			}
		}
		System.out.println("刪除完畢");
	}
	
	public static String getDateTimeIN(){
		{
	         
	        Calendar Cld = Calendar.getInstance();
	        int YY = Cld.get(Calendar.YEAR) ;
	        int MM = Cld.get(Calendar.MONTH)+1;
	        int DD = Cld.get(Calendar.DATE);
	        int HH = Cld.get(Calendar.HOUR_OF_DAY);
	        int mm = Cld.get(Calendar.MINUTE);
	        int SS = Cld.get(Calendar.SECOND);
	        int MI = Cld.get(Calendar.MILLISECOND);   
	        //由整型而來,因此格式不加0,如  2016/5/5-1:1:32:694
	        //func2
	        Calendar cal = Calendar.getInstance();
	        Date date = cal.getTime();
	        return new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(date);
	    }
	}

 

 

 

,

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章