在介紹順序存儲結構之前,我們先來看看動態數組的內容。
首先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