數據結構與算法-基礎(一)動態數組

摘要

日常開發中,會經常創建數組,並使用數組的添加、刪除等方法。現在就是要以數據結構的方式,來探究一下這些方法是怎麼實現的。

本文結構先總結 Array 常用的 API,接下來由簡單到複雜,由基礎到組合思路實現,最後優化細節。你可以按照文章的順序來梳理思路,去實現一下。

在文章的最後有完整的代碼實現,你可以實現完了作爲參考對照,或者不想看太多文字,直接跳到代碼,自己去看代碼理解也是可以的。

動態數組通俗些說就是可以無限的添加元素,不用考慮數組裝不下的問題。其本質就是時刻監控數組中的剩餘空間,及時的擴容和縮容,讓數組動態的保持適當的容量大小。

數組的數據結構和對外的 API 函數,是在常見的基礎上建立的,其中穿插着一些恰到好處的代碼處理。不同的代碼語言有不同的實現,但是數據結構是經歷了幾十年的檢視,設計邏輯是可以套用到大部分代碼語言上的。

Array 的常用 API

先來總結一下,日常使用 Array 的 API 大都逃不過下面代碼塊中羅列出的 11 個方法。

代碼中的 E 就是泛型類型

/**
 * 清除所有元素
 */
public void clear()
/**
 * 元素的數量
 * @return
 */
public int size()
/**
 * 是否爲空
 * @return
 */
public boolean isEmpty()
/**
 * 是否包含某個元素
 * @param element
 * @return
 */
public boolean contains(E element)
/**
 * 添加元素到尾部
 * @param element
 */
public void add(E element)
/**
 * 獲取index位置的元素
 * @param index
 * @return
 */
public E get(int index)
/**
 * 設置index位置的元素
 * @param index
 * @param element
 * @return 原來的元素ֵ
 */
public E set(int index, E element)
/**
 * 在index位置插入一個元素
 * @param index
 * @param element
 */
public void add(int index, E element)
/**
 * 刪除index位置的元素
 * @param index
 * @return
 */
public E remove(int index)
/**
 * 刪除元素
 * @param element
 */
public void remove(E element)
/**
 * 查看元素的索引
 * @param element
 * @return
 */
public int indexOf(E element)

數據結構

Array 的數據結構中主要包括構造函數、存放元素的成員變量、記錄元素數量的成員變量等。

public class ArrayList<E> {
	/**
	 * 默認數組大小
	 */
	private static final int DEFAULT_CAPATICY = 10;
	
	/**
	 * 默認標示
	 */
	private static final int ELEMENT_NOT_FOUND = -1;
	/**
	 * 所有元素
	 */
	private E[] elements;
	
	/**
	 * 元素數量
	 */
	private int size = 0;
	
	/**
	 * 初始化數組
	 */
	public ArrayList(int capaticy) {
		
		elements = (E[]) new Object[capaticy];
	}
	
	/**
	 * 初始化數組(無參)
	 */
	public ArrayList() {
		this(DEFAULT_CAPATICY);
	}
}

如果看到定義的屬性中,竟然還定義了一個elements數組屬性,就發出“?”。

這裏就簡單說一下元素在內存中的如何存放,看構造函數中用 new 創建數組,本質就是在堆空間申請了一個連續的空間準備存放數組,elements 中每個 index 是指向內存空間的指針(和 C++ 中的內存空間不同)。

爲什麼要申請堆空間來存放數據?

在內存管理中,有棧空間和堆空間可以存放一些臨時創建的數據,區別就是棧空間的創建和釋放是系統管理的,但是堆空間就可以開發人員自己管理。咱們創建的數組,肯定不希望自己無法控制,所以堆空間是最好的選擇。

那麼爲什麼 new 就是申請堆空間?這是代碼特性,沒有什麼道理的規定

實現方法

實現方法的思路是從簡單到複雜

看 Array 中定義的屬性,有元素數量sizesize是記錄數組中已經存在的元素數量,那麼就可以先快速實現元素的數量是否爲空兩個方法

/**
 * 元素的數量
 * @return
 */
public int size() {
  return size;
}

/**
 * 是否爲空
 * @return
 */
public boolean isEmpty() {
  return size == 0;
}

因爲元素是存放在 elements 這個數組中的,所以刪除元素的方法就可以通過遍歷方式快速實現

/**
 * 清除所有元素
 */
public void clear() {

  for (int i = 0; i < size; i++) {
    elements[i] = null;
  }
  size = 0;
}

獲取 index 位置上的元素方法,可以使用數組索引的方法實現

/**
 * 獲取index位置的元素
 * @param index
 * @return
 */
public E get(int index) {
  return elements[index];
}

設置 index 位置的元素方法,也可以直接在elements數組上直接操作。


/**
 * 設置index位置的元素
 * @param index
 * @param element
 * @return 原來的元素ֵ
 */
public E set(int index, E element) {

  E oldElement = elements[index];
  elements[index] = element;
  return oldElement;
}

添加元素

簡單的方法實現完了,接下來就實現複雜的方法。那麼在複雜的方法中首先實現基礎的方法。

那麼現在首要實現的方法是add(int index, E element)(在 index 位置插入一個元素)。爲什麼要首要實現呢?這個問題咱先放放,先實現

當在 index 位置插入元素時,index 位置的元素開始都要往後移動一個位置,然後把這個元素放到 index 位置。不要忘記把記錄已經存放元素數量的size加 1 操作。

/**
 * 在index位置插入一個元素
 * @param index
 * @param element
 */
public void add(int index, E element) {

  for (int i = size; i > index; i--) {
    elements[i] = elements[i-1];
  }
  elements[index] = element;
  size++;
}

接下來在這個方法基礎上實現add(E element)(添加元素到尾部),就是在 size 位置上插入一個元素。這就是首要實現在 index 位置插入一個元素的原因。

/**
 * 添加元素到尾部
 * @param element
 */
public void add(E element) {
  add(size, element);
}

查看元素

循着添加元素的實現邏輯,首要實現indexOf(E element)(查看元素的索引)方法。該方法需要分 element 不爲 null 和爲 null 兩種情況處理。若 element 爲 null,那麼就沒法進行比較

/**
 * 查看元素的索引
 * @param element
 * @return
 */
public int indexOf(E element) {

  if (element == null) {
    for (int i = 0; i < size; i++) {
      if (elements[i] == null) {
        return i;
      }
    }
  }
  else {
    for (int i = 0; i < size; i++) {
      if (element.equals(elements[i])) {
        return i;
      }
    }
  }
  return ELEMENT_NOT_FOUND; // 常量:-1,數組中沒有該元素
}

在這基礎上,可以實現contains(E element)(是否包含某個元素)。方法中直接判斷 element 元素的 index 是否等於 -1 來判斷返回。

/**
 * 是否包含某個元素
 * @param element
 * @return
 */
public boolean contains(E element) {
  return indexOf(element) != ELEMENT_NOT_FOUND;
}

刪除元素

繼續首要實現基礎方法思路,先實現remove(int index)(刪除 index 位置的元素)方法。

數組從 index 位置到尾部遍歷,後面的元素不斷覆蓋前面的元素。然後把最後一個元素設置爲 null。size 的大小也要減 1.

/**
 * 刪除index位置的元素
 * @param index
 * @return
 */
public E remove(int index) {
  rangeCheck(index);

  E oldElement = elements[index];
  for (int i = index; i < size; i++) {
    elements[i] = elements[i+1];
  }
  size --;

  elements[size] = null;
  return oldElement;
}

接下來實現remove(E element)(刪除元素)方法。它就可以先獲取 element 元素的 index,然後再刪除 index 位置的元素。通過這兩個方法實現。

/**
 * 刪除元素
 * @param element
 */
public void remove(E element) {
  remove(indexOf(element));
}

數組越界

截止到現在,動態數組的11個方法已經實現完了。暢快淋漓之後,要開始補補漏洞。

使用數組的方法,不怕元素不存在,就要座標越界,所以就需要在傳入 index 參數的方法中先要判斷一下是否越界,如果越界,就不能再進行下面的代碼實現。

/**
 * 判斷座標是否越界
 * @param index
 */
private void rangeCheck(int index) {
  if (index < 0 || index >= size) {
    outOfBound(index);
  }
}

private void outOfBound(int index) {
  throw new IndexOutOfBoundsException("Index"+ index +", size" + size);
}

但是添加元素到尾部的時候,是把元素放到 size 的位置,那麼就需要排除 index == size 的判斷。

private void rangeCheckOfAdd(int index) {
  if (index < 0 || index > size) {
    outOfBound(index);
  }
}

擴容

補了數組越界的洞之後,但是 elements 開始設置容量是 10 個元素,如果添加元素時,超過 elements 的容量時,就需要進行擴容操作。

執行擴容方法時,先要判斷容量是否夠用,不夠用時,就創建一個1.5倍之前 elements 容量的新數組,然後遍歷老數組元素放置到新的數組中。

/**
 * 擴容
 * @param capacity
 */
private void ensureCapacity(int capacity) {
  int oldCapacity = elements.length;

  if (capacity <= oldCapacity) {
    return;
  }

  int newCapacity = oldCapacity + (oldCapacity >> 1); // 擴大 1.5 倍
  E[] newElements = (E[]) new Object[newCapacity];
  for (int i = 0; i < size; i++) {
    newElements[i] = elements[i];
  }
相對的,需要進行縮容嗎?如何實現縮容?這兩個問題可以根據實際情況來進行,思路和擴容是相似的。

整體實現

動態數組已經完美實現了。接下來就完整的貼一下代碼,整體的看一下代碼實現,再品味品味動態數組的實現邏輯。

@SuppressWarnings({"unused","unchecked"})
public class ArrayList<E> {
	/**
	 * 默認數組大小
	 */
	private static final int DEFAULT_CAPATICY = 10;
	
	/**
	 * 默認標示
	 */
	private static final int ELEMENT_NOT_FOUND = -1;
	
	/**
	 * 所有元素
	 */
	private E[] elements;
	
	/**
	 * 元素數量
	 */
	private int size = 0;
	
	/**
	 * 初始化數組
	 */
	public ArrayList(int capaticy) {
		// 忘記
		capaticy = (capaticy < DEFAULT_CAPATICY ? DEFAULT_CAPATICY: capaticy);
		
		elements = (E[]) new Object[capaticy];
	}
	
	/**
	 * 初始化數組(無參)
	 */
	public ArrayList() {
		this(DEFAULT_CAPATICY);
	}

	/**
	 * 清除所有元素
	 */
	public void clear() {
		
		for (int i = 0; i < size; i++) {
			elements[i] = null;
		}
		size = 0;
	}

	/**
	 * 元素的數量
	 * @return
	 */
	public int size() {
		return size;
	}

	/**
	 * 是否爲空
	 * @return
	 */
	public boolean isEmpty() {
		return size == 0;
	}

	/**
	 * 是否包含某個元素
	 * @param element
	 * @return
	 */
	public boolean contains(E element) {
		return indexOf(element) != ELEMENT_NOT_FOUND;
	}

	/**
	 * 添加元素到尾部
	 * @param element
	 */
	public void add(E element) {
		add(size, element);
	}

	/**
	 * 獲取index位置的元素
	 * @param index
	 * @return
	 */
	public E get(int index) {
		rangeCheck(index);
		return elements[index];
	}

	/**
	 * 設置index位置的元素
	 * @param index
	 * @param element
	 * @return 原來的元素ֵ
	 */
	public E set(int index, E element) {
		rangeCheck(index);
		
		E oldElement = elements[index];
		elements[index] = element;
		return oldElement;
	}

	/**
	 * 在index位置插入一個元素
	 * @param index
	 * @param element
	 */
	public void add(int index, E element) {
		rangeCheckOfAdd(index);
		ensureCapacity(size+1); 
		
		for (int i = size; i > index; i--) {
			elements[i] = elements[i-1];
		}
		elements[index] = element;
		size++;
	}

	/**
	 * 刪除index位置的元素
	 * @param index
	 * @return
	 */
	public E remove(int index) {
		rangeCheck(index);
		
		E oldElement = elements[index];
		for (int i = index; i < size; i++) {
			elements[i] = elements[i+1];
		}
		size --;
		
		elements[size] = null;
		return oldElement;
	}
	
	/**
	 * 刪除元素
	 * @param element
	 */
	public void remove(E element) {
		remove(indexOf(element));
	}

	/**
	 * 查看元素的索引
	 * @param element
	 * @return
	 */
	public int indexOf(E element) {
		
		if (element == null) {
			for (int i = 0; i < size; i++) {
				if (elements[i] == null) {
					return i;
				}
			}
		}
		else {
			for (int i = 0; i < size; i++) {
				if (element.equals(elements[i])) {
					return i;
				}
			}
		}
		return ELEMENT_NOT_FOUND;
	}
	
	/**
	 * 擴容
	 * @param capacity
	 */
	private void ensureCapacity(int capacity) {
		int oldCapacity = elements.length;
		
		if (capacity <= oldCapacity) {
			return;
		}
		
		int newCapacity = oldCapacity + (oldCapacity >> 1); // 擴大 1.5 倍
		E[] newElements = (E[]) new Object[newCapacity];
		for (int i = 0; i < size; i++) {
			newElements[i] = elements[i];
		}
		elements = newElements;
	}
	
	private void outOfBound(int index) {
		throw new IndexOutOfBoundsException("Index"+ index +", size" + size);
	}
	
	/**
	 * 判斷座標是否越界
	 * @param index
	 */
	private void rangeCheck(int index) {
		if (index < 0 || index >= size) {
			outOfBound(index);
		}
	}
	
	private void rangeCheckOfAdd(int index) {
		if (index < 0 || index > size) {
			outOfBound(index);
		}
	}
	
	@Override
	public String toString() {
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append("size:").append(size).append(" ").append("[");
		for (int i = 0; i < size; i++) {
			if (i != 0) {
				stringBuilder.append(",");
			}
			stringBuilder.append(elements[i]);
		}
		stringBuilder.append("]");
		return stringBuilder.toString();
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章