文章目錄
(一)鏈表(Linked List)的簡介
(二)鏈表(Linked List)的設計
(三)動態數組(Dynamic Array)的優化
(四)鏈表(Linked List)的實現:clear
(五)鏈表(Linked List)的實現:add
(六)鏈表(Linked List)的實現:get&set
(七)鏈表(Linked List)的實現:remove
(八)鏈表(Linked List)的實現:indexOf
(九)鏈表(Linked List)的代碼彙總&測試
(十)補充
(十一)虛擬頭結點
(一)鏈表(Linked List)的簡介
動態數組有個明顯的缺點:可能會造成內存空間的大量浪費
能否用到多少就申請多少內存?鏈表可以辦到這一點
鏈表是一種鏈式存儲的線性表,所有元素的內存地址不一定是連續的
(二)鏈表(Linked List)的設計
我們直接把方法都抽取到List父類是沒用的,因爲ArrayList和LinkedList的實現有所差別(有相同,有不同)
正確的做法是讓List成爲接口,把方法的聲明都放在List接口,由AbstractList去實現這個接口,對於ArrayList和LinkedList的實現都相同的方法可以直接抽取到AbstractList中,然後讓ArrayList和LinkedList都繼承AbstractList,並且實現抽象方法
List接口代碼如下:
問題:爲什麼static final int ELEMENT_NOT_FOUND = -1;
要寫在List接口中?
當我們使用多態的寫法創建ArrayList時,執行list.indexOf(20)
時,如果查詢不到,相當於list.indexOf(20) == List.ELEMENT_NOT_FOUND
,所以要寫在List接口中
其實也可以寫在AbstractList抽象類中,但是從設計的角度思考,AbstractList是不可見的,只是起到抽取公共方法的作用,最好不要加在這裏
AbstractList抽象類代碼如下:
最後讓LinkedList繼承AbstractList並實現相應方法(方法體先留空),如下:
(三)動態數組(Dynamic Array)的優化
public class ArrayList<E> extends AbstractList<E> {
private E[] elements;//所有的元素
private static final int DEFAULT_CAPACITY = 10;
/**
* 指定數組的容量
*
* @param capaticy 容量
*/
public ArrayList(int capaticy) {
capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
elements = (E[]) new Object[capaticy];
}
/**
* 不指定的話默認容量是10
*/
public ArrayList() {
this(DEFAULT_CAPACITY);
}
/**
* 保證要有capacity的容量
*
* @param capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity) return;//不需要擴容,直接返回
//位運算比浮點運算(oldCapacity * 2)效率高
int newCapacity = oldCapacity + (oldCapacity >> 1);//相當於乘1.5(每次擴容1.5倍)
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;//舊的數組指向新的數組
System.out.println(oldCapacity + "擴容爲:" + newCapacity);
}
/**
* 清除所有元素
*/
@Override
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
/**
* 元素的數量
*
* @return
*/
@Override
public int size() {
return size;
}
/**
* 是否爲空
*
* @return
*/
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* 獲取index位置的元素
*
* @param index
* @return
*/
@Override
public E get(int index) {
rangeCheck(index);
return elements[index];
}
/**
* 設置index位置的元素
*
* @param index
* @param element
* @return 原來的元素ֵ
*/
@Override
public E set(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
/**
* 在index位置插入一個元素
*
* @param index
* @param element
*/
@Override
public void add(int index, E element) {
if (element == null) return;
rangeCheckForAdd(index);
ensureCapacity(size + 1);//capacity取值爲size+1,就是保證比size多一個,不怕不夠用
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = element;
size++;
}
/**
* 刪除index位置的元素
*
* @param index
* @return
*/
@Override
public E remove(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
elements[--size] = null;
return old;
}
/**
* 查看元素的索引
*
* @param element
* @return
*/
@Override
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;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();//使用StringBuilder拼接字符串效率會更高
stringBuilder.append("size=").append(size).append(",[");
for (int i = 0; i < size; i++) {
if (i != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(elements[i]);
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
(四)鏈表(Linked List)的實現:clear
clear()
方法清空所有元素,過程如下:
注意:不需要將next設置爲null,因爲first=null
後,0號元素第一個被銷燬,接着是1號元素,直至所有元素被銷燬
代碼如下:
@Override
public void clear() {
size = 0;
first = null;
}
(五)鏈表(Linked List)的實現:add
add(int index,E element)
方法添加元素,過程如下:
現在有一個元素,想添加到1號元素的位置
首先讓新元素指向1號元素
然後找到1號元素的前一個元素,讓它指向新元素
最後索引發生調整
最後size+1,添加完成
首先要寫一個私有方法,private Node<E> node(int index)
,實現根據index獲取對應的Node,代碼如下:
private Node<E> node(int index) {
rangeCheck(index);
Node<E> node = first;
//從first開始,next index次 就可以找到要找的結點
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
public void add(int index, E element)
方法的代碼如下:
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
//要特殊處理index=0的情況
if (index == 0) {
first = new Node<>(element, first);
} else {
Node<E> prev = node(index - 1);
prev.next = new Node<>(element, prev.next);
}
size++;
}
注意:在編寫鏈表過程中,要注意邊界測試,比如index爲0、size-1、size時
(六)鏈表(Linked List)的實現:get&set
藉助於剛纔寫的private Node<E> node(int index)
方法可以很簡單的實現get()
和set()
方法,代碼如下:
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
注意:get()
和set()
方法都調用了private Node<E> node(int index)
方法,該方法內部已經做了索引的檢查,所以無需再檢查索引
(七)鏈表(Linked List)的實現:remove
remove(int index)
方法刪除元素,過程如下:
比如想要刪除index=1的元素
直接讓0號元素指向2號元素
此時1號元素就會被銷燬了
最後調整元素序號,刪除完成
代碼如下:
@Override
public E remove(int index) {
Node<E> node = first;
if (index == 0) {
first = first.next;
} else {
Node<E> prev = node(index - 1);
node = prev.next;
// prev.next = prev.next.next;
prev.next = node.next;
}
size--;
return node.element;
}
(八)鏈表(Linked List)的實現:indexOf
@Override
public int indexOf(E element) {
if (element == null) {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
(九)鏈表(Linked List)的代碼彙總&測試
public class LinkedList<E> extends AbstractList<E> {
private Node<E> first;
private static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
@Override
public void clear() {
size = 0;
first = null;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
//要特殊處理index=0的情況
if (index == 0) {
first = new Node<>(element, first);
} else {
Node<E> prev = node(index - 1);
prev.next = new Node<>(element, prev.next);
}
size++;
}
private Node<E> node(int index) {
rangeCheck(index);
Node<E> node = first;
//從first開始,next index次 就可以找到要找的結點
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
@Override
public E remove(int index) {
Node<E> node = first;
if (index == 0) {
first = first.next;
} else {
Node<E> prev = node(index - 1);
node = prev.next;
// prev.next = prev.next.next;
prev.next = node.next;
}
size--;
return node.element;
}
@Override
public int indexOf(E element) {
if (element == null) {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();//使用StringBuilder拼接字符串效率會更高
stringBuilder.append("size=").append(size).append(",[");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(node.element);
node = node.next;
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
測試如下:
(十)補充
完善remove()
方法
修改後如下:
注意:後面的node(int index)
雖然會調用rangeCheck(int index)
,但是在那之前就有拋出異常的風險,不同於get()
和set()
方法一開始就調用node(int index)
(十一)虛擬頭結點
我們之前的add(int index, E element)
和remove(int index)
方法都對index=0做了特殊處理,如下:
有時候爲了讓代碼更加精簡,統一所有節點的處理邏輯,可以在最前面增加一個虛擬的頭結點(不存儲數據)
首先要增加一個構造函數,如下:
public LinkedList2() {
first = new Node<>(null, null);
}
然後修改Node<E> node(int index)
方法,如下:
修改add(int index, E element)
方法,如下:
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
//當index=0時,會過不了node方法裏面的索引判斷,所以也要特殊處理
Node<E> prev = (index == 0) ? first : node(index - 1);
prev.next = new Node<>(element, prev.next);
size++;
}
修改remove(int index)
方法,如下:
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> prev = (index == 0) ? first : node(index - 1);
Node<E> node = prev.next;
prev.next = node.next;
size--;
return node.element;
}
最後修改toString()
方法,如下: