【資料結構與演算法-java實現】三 Java陣列類實現
- 上一篇文章學習了:最好、最壞、平均、均攤時間複雜度的計算與分析方法.
- 本片文章學習陣列這種結構。由於陣列這種結構比較簡單,本文直接簡單介紹,然後給出兩種實現陣列類的Java程式碼:整形陣列類與通用性的陣列類
由於陣列是相比於其他資料結構實在太簡單,這裡我們只做簡單的介紹,然後直接給出實現的程式碼。
文章目錄
1 陣列的概念
陣列(Array)是一種線性表資料結構。它用一組連續的記憶體空,來儲存一組具有相同型別的資料。對於線性表和非線性表這裡不再多說。
我們要注意陣列它是連續的記憶體空間和相同的資料型別。
還有一點就是我們要明白陣列是如何做到根據下標來訪問陣列的元素的。我們拿一個長度為10的int型別的陣列int[] a=new int[10];來說明。如下圖是申請記憶體後陣列元素的儲存方式。
假設上述陣列的起始地址為1000.當CPU要訪問某一個數組的元素時,將會通過下面的公式進行訪問元素:
a[i]_address = base_address + i * data_type_size
其中,base_address 為陣列的起始地址。data_type_size為陣列元素的型別的大小。
計算機就是通過上述的公式進行陣列的隨機訪問的。不像連結串列等非線性結構,無法隨機訪問元素。
- 陣列這種結構,雖然具有隨機訪問的高效性,但是它的插入和刪除確實非常的低效。
- 以及陣列的訪問也是很容易越界的
以上插入和刪除的低效,這裡不再贅述。
我們注意一下陣列訪問的越界。
1.1 Java中陣列的越界訪問
在講述Java中陣列的越界訪問之前,先來看一下C語言的越界訪問。如下程式碼:
int main(int argc, char* argv[]){
int i = 0;
int arr[3] = {0};
for (; i<=3; i++){
arr[i] = 0;
printf("hello world\n");
}
return 0;
}
上述程式碼執行後,並非是列印3次hello world,而是無限次的列印hello world。這是為什麼?
陣列arr的大小是3,但是上面的for迴圈由於書寫錯誤,訪問了arr[3],這就是越界訪問。在C語言中,只要不是訪問記憶體受限的地方,所有的記憶體都是可以訪問的。所以由上面的陣列訪問的公式,a[3]也被定位到一塊記憶體上,而這塊記憶體中儲存的剛好是區域性變數i。那麼arr[3]實際上就是i,這就導致此時又將i賦值為0。此時回到for迴圈發現i是0,繼續迴圈。。。。。
陣列越界在C語言中是一種未決行為,並沒有規定陣列越界訪問時應該如何處理。因為陣列訪問的本質是訪問一段連續的記憶體,只要陣列通過偏移計算得到的記憶體是可用的,那麼程式就可能不會報任何錯誤。C語言中這種行為,很難debug到錯誤。
但是不像c語言,Java語言不允許這種越界訪問。Java編譯器會做越界檢查,如果有越界訪問,將會丟擲異常。如:java.lang.ArrayIndexOutOfBoundsException。
2 整形陣列類的實現
由於陣列比較簡單,上面的內容我覺的就夠了。下面直接給出int陣列類的實現。可以直接在eclipse編譯執行的程式碼。程式碼如下:
- Array.java
package Array;
/**
* 1) 陣列的插入、刪除、按照下標隨機訪問操作;
* 2)陣列中的資料是int型別的;
*
*/
public class Array {
//定義整形資料儲存資料
public int data[];
//陣列的長度
private int len;
//陣列中的實際個數
private int cnt;
//構造方法,定義陣列大小
public Array(int capacity) {
this.data=new int[capacity];
this.len=capacity;
this.cnt=0;//一開始一個數據都誒呦,所以為0
}
//根據索引找到陣列中的元素並返回
public int find(int index) {
if(index<0 || index>=len)return -1;
return data[index];
}
//插入元素,包括頭部插入,尾部插入,中間插入
public boolean insert(int index, int value) {
//陣列空間已滿
if(cnt==len) {
System.out.println("陣列空間已滿,無法插入!");
return false;
}
//插入位置不合法
if(index<0 || index>=len) {
System.out.println("插入位置不合法!");
return false;
}
//位置合法
for(int i=cnt;i>index;--i) {
data[i]=data[i-1];
}
data[index]=value;
++cnt;
return true;
}
//根據索引,刪除陣列中的元素
public boolean delete(int index) {
if(index<0 || index>=len)return false;
//從刪除的位置開始,將後面的元素向前移動一位
for(int i=index+1;i<len;i++) {
data[i-1]=data[i];
}
--cnt;
return true;
}
public void printAll() {
for(int i=0;i<len;i++) {
System.out.print(data[i]+" ");
}
System.out.println();
}
public static void main(String args[]) {
Array array = new Array(5);
array.printAll();
array.insert(0,3);
array.insert(0, 4);
array.insert(1, 5);
array.insert(2, 2);
array.insert(3, 6);
array.insert(4, 8);
array.printAll();
}
}
- 執行結果如下:
以上程式碼只實現了陣列類的插入和刪除。更多的方法在下面的通用性陣列的實現裡面。
3 通用型陣列類的實現
下面是實現通用的陣列類,也就是大家所知的泛型。C++中叫做模板。
下面的程式碼已經經過測試,是可以正常使用的。如果你發現有其他bug,請在下方留言評論。
程式碼如下:
- GenericArray.java
package Array;//這個可能你的不一樣
public class GenericArray<T>{
private T[] data;
private int size;
//建構函式
public GenericArray(int capacity) {
data = (T[])new Object[capacity];
size=0;
}
//無參構造方法,預設陣列容量為10
public GenericArray() {
this(10);
}
//獲取陣列容量
public int getCapacity() {
return data.length;
}
//獲取當前元素個數
public int count() {
return size;
}
//判斷陣列是否為空
public boolean isEmpty() {
return size==0;
}
//修改index位置的元素
public void set(int index, T e) {
checkIndex(index);
data[index]=e;
}
//獲取對應index位置的元素
public T get(int index) {
checkIndex(index);
return data[index];
}
//檢視陣列是否包含元素
public boolean contains(T e) {
for(int i=0;i<this.size;++i) {
if(data[i].equals(e))
return true;
}
return false;
}
//獲取對應元素的下標,未找到則返回-1
public int find(T e) {
for(int i=0;i<this.size;++i) {
if(data[i].equals(e))
return i;
}
return -1;
}
//在index位置插入元素e,時間複雜度是O(m+n)
public void add(int index, T e) {
checkIndex(index);
//如果當前元素的個數等於陣列的容量,則將陣列擴容為原來的兩倍
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++;
}
//向陣列頭插入元素
public void addFirst(T e) {
add(0, e);
}
//向陣列尾插入元素
public void addLast(T e) {
add(size, e);
}
// 刪除index位置的元素並返回
public T remove(int index) {
chaeckIndexForRemove(index);
T ret =data[index];
for(int i=index+1;i<size;++i) {
data[i-1]=data[i];
}
--size;
data[size]=null;
//縮小陣列容量
if(size == (data.length/4) && (data.length/2)!=0) {
resize(data.length/2);
}
return ret;
}
//刪除第一個元素
public T removeFirst() {
return remove(0);
}
//刪除末尾元素
public T removeLast() {
return remove(size-1);
}
//從陣列中刪除指定的元素
public void removeElement(T e) {
int index=find(e);
if(index!=-1) {
remove(index);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("Array size = %d, capacity = %d \n", size, data.length));
builder.append('[');
for (int i = 0; i < size; i++) {
builder.append(data[i]);
if (i != size - 1) {
builder.append(", ");
}
}
builder.append(']');
return builder.toString();
}
private void chaeckIndexForRemove(int index) {
// TODO Auto-generated method stub
if(index < 0 || index >= size) {
throw new IllegalArgumentException("remove failed! Require index >=0 and index < size.");
}
}
//擴容方法,時間複雜度O(n)
private void resize(int capacity) {
// TODO Auto-generated method stub
T[] newData= (T[])new Object[capacity];
for(int i=0;i<size;++i) {
newData[i]=data[i];
}
data=newData;
}
private void checkIndex(int index) {
// TODO Auto-generated method stub
if(index<0 || index>size) {
throw new IllegalArgumentException("Add failed! Require index >=0 and index <= size.");
}
}
//測試用的main,你可以自己寫測試函式
public static void main(String args[]) {
GenericArray<Integer> a = new GenericArray<Integer>(5);
a.add(0, 2);
a.add(1, 4);
a.add(2, 3);
a.add(3, 7);
a.add(4, 9);
for(int i=0;i<a.size;++i) {
System.out.print(a.get(i)+" ");
}
System.out.println();
a.remove(1);
for(int i=0;i<a.size;++i) {
System.out.print(a.get(i)+" ");
}
a.addFirst(23);
System.out.println();
for(int i=0;i<a.size;++i) {
System.out.print(a.get(i)+" ");
}
a.addLast(24);
System.out.println();
for(int i=0;i<a.size;++i) {
System.out.print(a.get(i)+" ");
}
}
}
- 上述程式碼在eclipse中執行結果如下:
4 總結
- 注意陣列的插入刪除的效率以及越界訪問
學習交流加
- 個人qq: 1126137994
- 個人微信: liu1126137994
- 學習交流資源分享qq群: 962535112