數據結構——順序存儲結構(線性表)

在介紹順序存儲結構之前,我們先來看看動態數組的內容。

首先Java內置數組的特點:數組的長度一旦確定則不可更改、只能存儲同一類型的數據、每個存儲空間大小一致且地址連續、提供角標的方式訪問元素。

      Java內置數組的潛在問題:容量不夠(只能定義一個新的數組進行一一賦值,達到擴容的目的)、指定位置插入或刪除元素帶來的不便(在尾端插入方便,如果在頭或者中間插入元素,則要進行元素的移動)、數組對象只有length屬性是否夠用。

因此,能否用面向對象的思想將數組進行再度封裝,可以把數組的相關屬性和行爲封裝在類中,類似字符串String類,然後創建其方法進行調用,這樣就方便了很多。因此我們沒有必要每次遇到一個問題就要重複那些操作步驟。

那麼如何封裝動態數組?——屬性方面:int size  即數組的有效元素個數 ; int capacity 獲取數組的最大容量data.length;E[] data  數組的存儲容器(這裏的E指的是泛型,因爲我們在封裝一個動態數組的時候,不能將其存儲的數據類型限制,這樣這個動態數組定義出來作用的範圍會非常小,那我們所做的意義不大,只有外界想要存儲什麼類型的數據,將其類傳進去即可。)

                                        —— 行爲方面:增、刪、改、查等等操作。
爲什麼要先介紹動態數組呢?因爲動態數組是順序存儲結構的具體實現,我們接下來要介紹的不管是線性表也好、棧結構也好、還是隊列,它都是基於動態數組實現的。

現在進入正題,前方高能預警!

線性表

什麼是線性表?—— 零個或多個數據元素的有限序列

        若將線性表記爲(a1,...,ai-1,ai,ai+1,...,an),則表中ai-1領先於ai,ai領先於ai+1,稱ai-1是ai的直接前驅元素,ai+1是ai的直接後繼元素。當i=1,2,...,n-1時,ai有且僅有一個直接後繼,當i=2,3,...,n時,ai有且僅有一個直接前驅。
        所以線性表元素的個數n(n>=0),定義爲線性表的長度,當n=0時,成爲空表

線性表首要的一個接口就是List,它是線性表的最終父類,我們首先來看一下它的接口的定義:

package 線性表;

public interface List<E> {  //list是一個接口,存儲的數據類型不能限定
	
	/**
	 * 獲取線性表中元素的個數(線性表的長度)
	 * @return 線性表中有效元素的個數
	 * */
	public int getSize();
	
	/**
	 * 判斷線性表是否爲空
	 * @return 是否爲空的布爾類型值
	 * */
	public boolean isEmpty();
	
	/**
	 * 在線性表中指定的index角標處添加元素e
	 * @param index 指定的角標 0<=index<=size
	 * @param e	要插入的元素
	 * */
	public void add(int index,E e);
	
	/**
	 * 在線性表的表頭位置插入一個元素
	 * @param e 要插入的元素 指定在角標0處
	 * */
	public void addFirst(E e);
	
	/**
	 * 在線性表的表尾位置插入一個元素
	 * @param e 要插入的元素 指定在角標size處
	 * */
	public void addLast(E e);
	
	/**
	 * 在線性表中獲取指定index角標處的元素
	 * @param index 指定的角標 0<=index<size
	 * @return 該角標所對應的元素
	 * */
	public E get(int index);
	
	/**
	 * 獲取線性表中表頭的元素
	 * @return 表頭元素 index=0
	 * */
	public E getFirst();
	
	/**
	 * 獲取線性表中表尾的元素
	 * @return 表尾的元素 index=size-1
	 * */
	public E getLast();
	
	/**
	 * 修改線性表中指定index處的元素爲新元素e
	 * @param index 指定的角標
	 * @param e 新元素
	 * */
	public void set(int index,E e);
	
	/**
	 * 判斷線性表中是否包含指定元素e 默認從前往後找
	 * @param e 要判斷是否存在的元素
	 * @return 元素的存在性布爾類型值
	 * */
	public boolean contains(E e);
	
	/**
	 * 在線性表中獲取指定元素e的角標 默認從前往後找
	 * @param e 要查詢的數據
	 * @return 數據在線性表中的角標
	 * */
	public int find(E e);
	
	/**
	 * 在線性表中刪除指定角標處的元素 並返回
	 * @param index 指定的角標 0<=index<size
	 * @return 刪除掉的老元素
	 * */
	public E remove(int index);
	
	/**
	 * 刪除線性表中的表頭元素
	 * @return 表頭元素
	 * */
	public E removeFirst();
	
	/**
	 * 刪除線性表中的表尾元素
	 * @return 表尾元素
	 * */
	public E removeLast();
	
	/**
	 * 在線性表中刪除指定元素e
	 * */
	public void removeElement(E e);
	
	/**
	 * 清空線性表
	 * */
	public void clear();
	
}

現在來說說線性表的順序存儲結構:指的是用一段地址連續的存儲單元,依次存儲線性表的數據元素。

其實說白了就是用數組實現的,List接口下有一個用數組實現的子類叫ArrayList,它實現了List接口中的所有方法,並且根據自己的性質和屬性有自己特有的方法。下面是它與List接口之間的關係(類與接口之間是實現的關係,與類與類之間泛化關係類似)。

package 線性表;
/**
 * 用順序存儲結構實現的List - 叫順序線性表 - 也叫順序表
 * @author kddai
 *
 * @param <E>
 */
public class ArrayList<E> implements List<E>{
 
	private static int DEFAULT_SIZE=10;  //默認容量大小10
	private E[] data;  //存儲數據元素的容器
	private int size;  //線性表的有效元素的個數 data.length表示線性表的最大容量
	
	/**
	 * 創建一個容量默認爲10的一個線性表
	 */
	@SuppressWarnings("unchecked")
	public ArrayList() {
		this(DEFAULT_SIZE);
	}
	
	/**
	 * 創建一個容量爲指定capacity的一個線性表
	 */
	@SuppressWarnings("unchecked")
	public ArrayList(int capacity) {
		this.data=(E[]) new Object[capacity];
		this.size=0;
	}
	
	/**
	 * 將一個數組封裝成一個線性表
	 * @param arr
	 */
	public ArrayList(E[] arr) {  //爲了保證數據的穩定性
		data=(E[]) new Object[arr.length];  在內部創建一個新的數組
		for(int i=0;i<arr.length;i++) {  //將外部傳入的數組對內部的數組一一賦值
			data[i]=arr[i];
		}
		size=arr.length; //這樣在外部再去修改傳入的數組中的值,對內部的線性表的值是無影響的
	}
	
	@Override
	public int getSize() {  //獲取線性表中有效元素的長度
		return size;
	}

	@Override
	public boolean isEmputy() {  //判斷該線性表是否爲空表
		return size==0;
	}

	private void resize(int newLen){  //擴容/縮容
           //創建一個新的數組,新數組的長度由傳入的參數控制,再將原來的數組裏面的元素逐個賦值到新的數組裏面
		E[] newData = (E[]) new  Object[newLen]; 
		for(int i=0;i<size;i++) {
			newData[i]=data[i];
		}
		data = newData; //最後將這個新的數組的地址給內部原來數組對象的引用,達到擴容/縮容的目的
	}
	
	@Override
	public void add(int index, E e) { //在指定下標添加一個元素
		if(index<0 || index>size) { //如果指定的角標不在該數組的合理範圍內 
                          //拋出異常,角標越界
			throw new ArrayIndexOutOfBoundsException("角標越界");
		} 
		if(size==data.length) {  //如果能添加,且此時有效元素的個數達到線性表的存儲的最大容量
			resize(2*data.length); //調用函數進行擴容
		}
		for(int i=size-1;i>=index;i--) {//先將元素後移
			data[i+1]=data[i];
		}
		data[index]=e;  //再將指定的元素放入指定的下標位置
		size++;
	}

	@Override
	public void addFirst(E e) {  //在線性表表頭添加一個元素
		add(0,e); //也就是指定位置添加元素,只不過這個指定位置一直是表頭
	}

	@Override
	public void addLast(E e) {  //在尾部添加
		add(size,e);
	}

	@Override
	public E get(int index) {  //獲取指定角標的元素
		if(index<0 || index>size-1) { //首先判斷該角標是否越過有效元素的個數,或是數組最小下標位置
			throw new ArrayIndexOutOfBoundsException("get函數角標越界");
		}
		return data[index]; //沒有出錯,則直接返回指定下標的元素
	}

	@Override
	public E getFirst() {  //獲取頭元素
		return get(0); //就是獲取指定頭位置的下標的元素
	}

	@Override
	public E getLast() {  //獲取尾元素
		return get(size-1);//這裏的尾元素不是數組最後一個元素,而是有效元素的最後一個位置的元素
	}

	@Override
	public void set(int index, E e) {  //更改某處的元素
          //角標沒有越界則直接替換即可
		if(index<0 || index>size-1) {
			throw new ArrayIndexOutOfBoundsException("get函數角標越界");
		}
		data[index]=e;
	}

	@Override
	public boolean contains(E e) { //是否包含某元素
		if(isEmputy()) {  //首先判空,如果數組爲空,肯定不包含
			return false;
		}
		for(int i=0;i<size;i++) {//不爲空,則遍歷查找
			if(data[i]==e) {  //如果存在,則直接返回true,不再向下查找
				return true;
			}
		}
		return false;
	}

	@Override
	public int find(E e) { //查找某元素,與是否包含某元素類似做法
		if(isEmputy()) {
			return -1;
		}
		for(int i=0;i<size;i++) {
			if(data[i]==e) {
				return i;
			}
		}
		return -1;
	}

	@Override
	public E remove(int index) {  //刪除某一位置的元素
		if(index<0 || index>size-1) {  //首先角標不能越界
			throw new ArrayIndexOutOfBoundsException("刪除的角標越界");
		}
		E e=get(index); //其次要將要刪除的元素先提取出來
		for(int i=index;i<size-1;i++) {//然後將該元素位置後面的元素向前移動
			data[i]=data[i+1];
		}
		size--; //注意移動完了,你的有效元素也減少了一個喔!
//這裏提點一下,其實內部數組中有效元素並沒有減少,只是你有效元素位置指針指向了前一個位置,當前指向的元素和後一個元素是相同的,因爲在移動元素的時候,是覆蓋的移動,當下次再添加元素的時候,就將原來的覆蓋了。
		if(data.length>DEFAULT_SIZE&&size<=data.length/4) {
			resize(data.length/2);
		} //每次刪除完記得判斷一下,刪除的太多,空間開的太大會浪費喔。所以要進行縮容
		return e;//最後別忘了將刪除的元素返回,以便檢查刪除的對不對
	}

	@Override
	public E removeFirst() {  //刪除頭元素
		return remove(0);
	}

	@Override
	public E removeLast() {  //刪除尾元素
		return remove(size-1);
	}

	@Override
	public void removeElement(E e) { //刪除指定元素
		int index = find(e); //先找到該元素對應的下標
		if(index==-1) {
			throw new IllegalArgumentException("刪除元素不存在");
		}
		remove(index); //再根據下標刪除就行啦
	}
	
	

	@Override
	public void clear() { //清空
		size=0; //這裏直接將有效元素置空即可,管它原來數組裏面存了多少呢,反正後面在添加的時候還是會覆蓋
	}
	
	@Override
	public boolean equals(Object obj) {  //重寫equals方法,如果兩個線性表有效元素個數相同,元素也一樣視爲相等
		if(obj==null) {
			return false;
		}
		if(obj==this) {
			return true;
		}
		if(obj instanceof ArrayList) { //這裏先要判斷傳進來的對象是不是線性表,不是線性表就不用多此一舉的去比較啦
			ArrayList list = (ArrayList) obj; //還要將傳進來的對象進行強轉一下
			if(this.getSize()==list.getSize()) {  //這裏如果兩個線性表有效元素個數相同的話
				for(int i=0;i<size;i++) { //再逐個去比較每個元素是否一樣
					if(data[i]!=list.data[i]) { //一旦有一個不一樣。就沒有比下去的意義啦
						return false;
					}
				}
				return true;
			}
			return false;
		}
		return false;
	}

	 
	public String toString() {  //tostring方法,打印線性表的內容,方便查看
		StringBuilder sb = new StringBuilder();
		sb.append("ArrayList:szie="+size+",capacity="+data.length+"\n");
		if(isEmputy()) {
			sb.append("[]");
		}else {
			sb.append('[');
			for(int i=0;i<size;i++) {
				sb.append(data[i]);
				if(i==size-1) {
					sb.append(']');
				}else {
					sb.append(',');
				}
			}
		}
		return sb.toString();
	}

	public int getCapacity(){  //獲得該線性表當前的容量大小,也就是數組的大小
		return data.length;
	}

	public void swap(int i,int j){  //交換兩個元素,指定的是角標
	       if(i<0||i>size-1||j<0||j>size-1){
                   throw new ArrayIndexOutOfBoundsException("角標越界");
               }
		E temp=data[i];
		data[i]=data[j];
		data[j]=temp;
	}
}

至於它的測試,我已經測試過了。如果你看到這不放心的話,可以考過去進行方法的測試,注意導包倒正確喔,這裏寫的內容太多了,其實之前我有介紹這個類,可參考下面鏈接的這篇文章,也是我寫的,希望對你有所幫助,當然對我自己的幫助是最大的,嘻嘻(#^.^#)。

https://blog.csdn.net/weixin_45432742/article/details/100284589

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