1、基本概念java
首先,仍是來回顧線性結構的基本概念。數組
線性結構:在這些數據元素中有一個能夠被稱爲「第一個」(元素01)的數據元素;還有一個能夠被稱爲「最後一個」(元素04)的數據元素;除第一個元素之外每一個數據元素有且僅有一個直接前驅元素,除最後一個元素之外每一個數據元素有且僅有一個直接後續元素。這種數據結構的特色是數據元素之間是1對1 的聯繫,即線性關係。數據結構
二元組表示:一種數據結構的二元組表示爲 linearity = (K,R),其中
K = {01, 02, 03, 04, 05}
R = {<02,04>, <03,05>, <05,02>, <01,03>} ide
接下來我必須強調的一點,咱們研究的線性表是類型相同有限序列。學習
2、線性表和數組的區別this
還有一點,線性表和數組要區別一下,具體區別以下:spa
從概念上區別:線性表是抽象數據結構,數組是一種具體的數據類型。code
從邏輯關係上區別:線性表描述的是元素和元素之間一對一的關係,而數組是下表索引和數組元素的一一對應的關係。orm
從物理存儲上區別:線性表相鄰的元素存儲在內存中能夠是連續的,也能夠是不連續的;而數組中相鄰的元素存儲在連續的內存空間中。對象
3、線性表抽象數據類型(ADT List)
抽象數據類型是描述啥東西,有啥關係,幹啥。關於啥東西。
啥東西:數據元素在Java裏面就是對象,什麼對象呢?爲了兼容全部對象,可使用Object,在這裏,我使用泛型佔位符E表示對象的類型。
啥關係:數據元素的邏輯關係是線性表
幹啥:線性表能夠完成對其中的元素訪問、增長、刪除等操做,還應該可以判斷是不是空的,此額外還應該判斷是否包含某個元素。
好的,說了再多也不如用代碼來的那麼明瞭。
ackage com.util.linear; /*** * <p>List是線性表的抽象定義ADT。<br/> * List 定義了一個線性表對象所擁有的行爲準則,List支持範型,可以在編譯器期肯定容器中元素的類型。 * </P> * @author huangqian(huangqian866@163.com) */ public interface List<E> { /*** * 返回線性表中元素的個數 * @return */ public int getSize(); /*** * 若是線性表爲空返回true,若是線性表非空返回false * @return */ public boolean isEmpty(); /*** * 判斷線性表中是否包含元素e。若是包含元素e,返回true,不然返回false * @param e * @return */ public boolean containes( E e); /*** * <p>獲取元素e在線性表中索引,<br/> * 若是線性表中不存在e,則返回-1,<br/> * 不然返回e所在位置的索引 * </p> * @param e * @return 若是線性表中不存在e,則返回-1,<br/> * 不然返回e所在位置的索引 */ public int indexOf(E e); /*** * 在指定的位置(index)處插入元素e * @param index * @param e */ public void insert(int index,E e)throws OutOfBoundaryException; /*** * 將元素e插入到e1前面 * @param e1 參考元素 * @param e 插入的元素 * @return */ public boolean insertBefore(E e1,E e ); /*** * 將元素e插入到e1後面 * @param e1 參考元素 * @param e 插入的元素 * @return */ public boolean insertAfter(E e1, E e); /*** * 刪除線性表中索引爲index的元素,並將其返回 * @param index * @return 返回下表索引爲index的元素 * @throws OutOfBoundaryException */ public E remove(int index) throws OutOfBoundaryException; /*** * 從集合中刪除元素e * @param e * @return 若是成功刪除則返回true,不然返回false。 */ public boolean remove(E e); /*** * 用元素e替換線性表中索引位置index處的元素,並將替換的元素返回。 * @param index 目標索引 * @param e 新元素 * @return 若是成功替換,將被替換的元素返回。 * @throws OutOfBoundaryException 索引越界異常 */ public E replace(int index,E e)throws OutOfBoundaryException; /*** * 獲取位置爲index處的元素 * @param index 目標元素的位置 * @return 若是index未超出線性表索引範圍,則返回index處的元素。 * @throws OutOfBoundaryException 索引位置越界異常 */ public E get(int index) throws OutOfBoundaryException; }
4、線性表的順序存儲的實現
線性表的順序存儲是用一組連續的存儲單元存儲數據元素,假如每一個元素佔k個存儲單元,那麼
location(a(n+1)) = location(a(n)) + k
所以:
location(a(n)) = location(a(0)) + n*k
由於數組的物理內存單元是連續的,在這裏我能夠藉助數組,去實現線性表的順序存儲。
package com.util.linear; /**** * 現行表順序存儲的實現 * @author huangqian * * @param <E> */ public class ArrayList<E> implements List<E> { //順訓表種元素的個數 private int size; private Object [] elements ; private int capacity; public ArrayList() { //capacity默認值爲8 this(8); } public ArrayList(int initCapacity){ this.capacity = initCapacity; elements = new Object[initCapacity]; } @Override public int getSize() { return size; } @Override public boolean isEmpty() { return size==0 || elements.length ==0; } /*** * 若是包含對應的元素,返回true,不然返回false */ @Override public boolean containes(E e) { return indexOf(e) >-1; } /*** * 查詢到了返回對應的索引值,不然返回-1 */ @Override public int indexOf(E e) { if(e ==null){ for(int i=0;i<elements.length;i++){ if(elements[i]==null){ return i; } } }else{ for(int i=0;i<elements.length;i++){ if(e.equals(elements[i])){ return i; } } } return -1; } @Override public void insert(int index, E e) throws OutOfBoundaryException { if(index >size){ throw new OutOfBoundaryException("插入的索引超過了數組的範圍"); } //空間不足,須要從新開闢空間 if(size == elements.length){ //倍增可以提升效率 extendSpace(elements.length*2); } for (int i = size; i > index; i--) { elements[i] = elements[i - 1]; } elements[index] = e; size++; } /*** * 擴展存儲空間 * @param newLen */ private void extendSpace(int newLen){ Object[] tmp = elements; elements = new Object[newLen]; for(int i=0;i<tmp.length;i++){ elements[i] = tmp[i]; } tmp = null; } /*** * 若是實際內存利用不超過當前空間得一半,而且當前存儲空間的長度爲capacity的2倍以上,則回收一半的存儲空間。不然,不會回收存儲空間 */ private void free(){ if(size * 2 < elements.length&& capacity * 2 < elements.length){ Object[] tmp = elements; int newLen; newLen = elements.length % 2 == 0 ? elements.length % 2 : (elements.length % 2) +1; elements = new Object[newLen]; for(int i = 0; i < size ; i++){ elements[i] = tmp[i]; } tmp = null; } } /*** * 在e1後面插入元素e.若是找到了e1,而且插入元素e返回true,不然返回false */ @Override public boolean insertBefore(E e1, E e) { int index = indexOf(e1); if(index == -1){ return false; }else{ insert(index+1,e); return true; } } @Override public boolean insertAfter(E e1, E e) { int index = indexOf(e1); if(index < 0){ return false; }else { insert(index,e); return true; } } @Override public E remove(int index) throws OutOfBoundaryException { if(index < 0 || index >= size){ throw new OutOfBoundaryException("指定的下表索引越界"); } E returnObj = (E)elements[index]; for(int i=index;i < size-1;i++){ elements[i] = elements[i+1]; } size--; //檢查是否回收空間 free(); return returnObj; } @Override public boolean remove(E e) { int index = indexOf(e); if(index < 0){ return false; }else{ remove(index); return true; } } @Override public E replace(int index, E e) throws OutOfBoundaryException { if(index < 0 || index >= size){ throw new OutOfBoundaryException("指定的下標索引越界"); } @SuppressWarnings("unchecked") E rst =(E)elements[index]; elements[index] = e; return rst; } @SuppressWarnings("unchecked") @Override public E get(int index) throws OutOfBoundaryException { if(index < 0 || index >= size){ throw new OutOfBoundaryException("指定的下標索引越界"); } return ((E)elements[index]); } }
在線性表的操做中確定會遇到內存不足,須要擴展內存空間的。我依然還記得大學學習C語言的時候,每次定義一個宏INC_SIZE。每次存儲空間不足的時候,就增長INC_SIZE。若是每次咱們增長的空間都是一個固定值,會有一個問題,好比個人INC_SIZE是100,我忽然須要插入1000元素,這個時候就作10次內存空間的從新開闢的工做,換句話說,我須要插入n個元素,我花在開闢空間的時間是n/INC_SIZE,我再次花銷的時間複雜度爲o(n)。比較友好一點的作法是倍增,時間複雜度變爲了log2(n)。
還有一點,當咱們的線性表的存儲空間使用不到一半的時候,應該考慮回收空間。個人代碼代碼裏面free在下一次GC以前,我其實是增長了內存。