資料結構 - 陣列
1.陣列基礎
陣列最一種存放資料的線性資料結構 ,最原始的陣列是一種靜態陣列,需要宣告陣列的容量,一旦new出來陣列物件大小不可改變,可以通過索引來進行資料的增刪改查。我們可以通過對靜態陣列的二次封裝來進行改進成動態陣列。
*陣列最大的優點:快速查詢。
*陣列最好用於“索引有語意”的情況。
*但並非所有有有語意的索引都適用於陣列。 例如當用陣列存放人員時,用身份證號來充當索引,此時能夠區別人員,但身份證號太長,極大浪費記憶體空間得不償失。
*陣列也可以處理索引沒有語意的情況。
基於靜態陣列實現的動態陣列原始碼如下:
/**
* 對靜態陣列的二次封裝,改進成一個動態陣列
*
* @author zhangtianci
*/
public class Array<E>{
private E[] data; //用來存放資料
private int size; //當前陣列中存放資料的個數
/**
* 構造方法
*
* @param capacity 陣列的容量
*/
public Array(int capacity){
data = (E[]) new Object[capacity];
size = 0;
}
/**
* 無參建構函式,預設陣列的容量為10
*/
public Array(){
this(10);
}
/**
*獲取當前陣列中存放資料的個數
*
*@return size of array
*/
public int getSize(){
return size;
}
/**
* 獲取陣列的大小
*
* @return capacity of array
*/
public int getCapacity(){
return data.length;
}
/**
* 判斷陣列是否為空
*
* @return true is empty,false is not
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 向所有元素後新增一個新元素
*
* @param e a new element of array
*/
public void addLast(E e){
add(size, e);
}
/**
* 向所有元素前新增一個新元素
*
* @param e
*/
public void addFirst(E e){
add(0, e);
}
/**
* 在index個位置插入一個元素
*
* @param index
* @param e
*/
public void add(int index, E e){
if(index < 0 || index >size){
throw new IllegalArgumentException("Add failed.Require index >= 0 and index <= size.");
}
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++;
}
/**
* 獲取索引為index上的元素
*
* @param index
* @return
*/
public E get(int index){
if (index < 0 || index >= size){
throw new IllegalArgumentException("Get failed.Index is Illggal.");
}
return data[index];
}
/**
* 修改索引為index上的元素
*
* @param index
* @param e
*/
public void set(int index,E e){
if (index < 0 || index >= size){
throw new IllegalArgumentException("Set failed.Index is Illggal.");
}
data[index] = e;
}
/**
* 查詢該陣列中是否存在該元素,若存在則返回true,反之false
*
* @param e
* @return
*/
public boolean contains(E e){
for(int i = 0; i < size; i++){
if (data[i].equals(e)) {
return true;
}
}
return false;
}
/**
* 查詢陣列中該元素的索引,若不存在則返回-1
*
* @param e
* @return
*/
public int find(E e){
for(int i = 0; i < size; i++){
if (data[i].equals(e)) {
return i;
}
}
return -1;
}
/**
* 刪除陣列指定索引的元素
*
* @param index
* @return
*/
public E remove(int index){
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Remove failed.Index is Illegal.");
}
E ret = data[index];
for(int i = index; i < size - 1; i++){
data[i] = data[i + 1];
}
size--;
data[size] = null;
if(size == data.length / 4 && data.length / 2 != 0){
resize(data.length / 2);
}
return ret;
}
/**
* 刪除陣列第一個元素
*
* @return
*/
public E removeFirst(){
return remove(0);
}
/**
* 刪除陣列最後一個元素
*
* @return
*/
public E removeLast(){
return remove(size -1);
}
/**
* 刪除陣列中的元素e
*
* @param e
*/
public void removeElement(E e){
int index = find(e);
if(index != -1){
remove(index);
}
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("Arrry: size = %d , capacity = %d\n", size,data.length));
res.append('[');
for(int i = 0; i < size; i++){
res.append(data[i]);
if (i != size-1) {
res.append(',');
}
}
res.append(']');
return res.toString();
}
/**
* 當陣列容量達到上限或者不足容量時,重新定義容量
*
* @param newCapacity
*/
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity];
for(int i = 0;i < size; i++){
newData[i] = data[i];
}
data = newData;
}
}
簡單的時間複雜度分析
*時間複雜度一般分為 O(1),O(n),O(lg),O(nlogn),O(n^2)
*大O描述的是演算法的執行時間和輸入資料之間的關係
*為什麼要用大O,叫做O(n)? 忽略常數,其實又叫做漸進時間複雜度,描述n趨近與無窮的情況下,所以常數幾乎可以忽略。
對封裝陣列的時間複雜度分析
*新增操作
addLast(e) : O(1) ,但一旦達到了陣列的容量進行擴容時,就需要進行resize()操作,所以在這種情況下時間複雜度也為O(n)
addFirst (e) : O(n)
addIndex(index,e) : O(n)
綜上所述在最壞的情況下,新增操作的時間複雜度為O(n)
*刪除操作
removeLast() : O(1),但一旦達到了陣列的容量進行縮容時,就需要進行resize()操作,所以在這種情況下時間複雜度也為O(n)
removeFirst() : O(n)
remove(index) : O(n)
綜上所述在最壞的情況下,新增操作的時間複雜度為O(n)
*修改操作
set(index,e) : O(1)
*查詢操作
get(index) : O(1)
contains(e) : O(n)
find(e) : O(n)
綜上:增:O(n),刪:O(n),改:當已知索引時O(1),當不知索引時即要首先進行一次遍歷查到該元素再進行修改則為O(n),查:已知索引O(1),未知索引O(n)
所以,當使用陣列時最好使用當陣列的索引具有語意的情況,那麼當進行查詢和修改時則會在效能上有非常大的優勢,對於增加和刪除時最好使用 addLast()和 removeLast() 操作,因為在這兩種操作的時間複雜度都為O(1)。
risize的時間複雜度分析 O(n)
從均攤時間複雜度來講,在capacity為n時addLast()和remove()操作平均下來為(2n+1)/(n+1)=2次操作,所以從這個角度來看addLast()和removeLast()的複雜度為
O(1)。
複雜震盪度
但是,當我們操作addLast()和removeLast()過於著急時,就會出現震盪複雜度這種情況
解決方案:Lazy,正如以上程式碼中removeLast()方法中當縮容時,當size==capacity/4時,才將capacity的容量減半。
擴容時