這是我參與新手入門的第3篇文章java
在平常開發中,通常在對於List的場景,基本上都是經過ArrayList去封裝數據的,而對於鏈表LinkedList相對來講用的比較少。對我而言,好像ArrayList熟練度高一些,因此基本上也不多用LinkedList,也就是在面試的時候去背過八股文。node
鏈表:數據分散的存儲在物理空間中,經過一根線保存着它們之間的邏輯關係,這種存儲結構稱爲鏈式存儲結構,簡稱鏈表。面試
數據結構中,一組數據中的每一個個體被稱爲「數據元素」(簡稱「元素」)。 另外,對於具備「一對一」邏輯關係的數據,咱們一直在用「某一元素的左側(前邊)或右側(後邊)」這樣不專業的詞,其實線性表中有更準確的術語:數組
概念:單鏈表,用於存儲邏輯關係爲 "一對一" 的數據。與順序表不一樣,鏈表不限制數據的物理存儲狀態,換句話說,使用鏈表存儲的數據元素,其物理存儲位置是隨機的。HashMap在1.7就是經過單鏈表來解決hash衝突的。 markdown
在上圖中沒法提現出元素之間的邏輯關係,對此,鏈表的解決方案是,每一個數據元素在存儲時都配備一個指針,用於指向本身的直接後繼元素。表示一個節點以下:數據結構
//節點信息
class Node {
E data;
Node next;
public Node(E element, Node next) {
this.data = element;
this.next = next;
}
public Node(E data) {
this.data = data;
}
}
複製代碼
所以,在鏈表中,每一個數據的存儲有如下2部分組成app
數據自己,其所在的區域叫作數據域ide
指向後繼元素的指針,叫指針域 oop
上圖所示的結構在鏈表中稱爲節點。也就是說,鏈表實際存儲的是一個一個的節點,真正的數據元素包含在這些節點中 源碼分析
/** * 頭部添加節點 * * @param e */
public void add(E e) {
//頭結點
Node cur = new Node(e, list);
list = cur;
size++;
}
/** * 指定位置添加節點 * * @param index * @param e 0 1 2 3 4 */
public void add(int index, E e) {
//越界檢查
checkElementIndex(index);
Node preNode = list;
for (int i = 0; i < index - 1; i++) {
//找到插入位置的前一個節點
preNode = preNode.next;
}
Node node = new Node(e);
node.next = preNode.next;
preNode.next = node;
size++;
}
複製代碼
/** * 刪除頭部節點 */
public void remove() {
if (list != null) {
Node node = list;
list = node.next;
//GC
node.next = null;
size--;
}
}
/** * 刪除指定位置節點 * 1 2 3 4 5 * 0 1 2 3 4 * * @param index */
public void remove(int index) {
checkElementIndex(index);
Node preNode = list;
for (int i = 0; i < index - 1; i++) {
//找到指定位置元素的前一個節點
preNode = preNode.next;
}
//指定位置的節點
Node next = preNode.next;
preNode.next = next.next;
//GC
next = null;
size--;
}
/** * 刪除最後一個節點 */
public void removeLast() {
if (list != null) {
//當前節點
Node cur = list;
//最後一個節點的前一個節點
Node preNode = list;
while (cur.next != null) {
preNode = cur;
cur = cur.next;
}
preNode = null;//此時cur已經爲null
size--;
}
}
複製代碼
/** * 修改指定索引的元素 * * @param index * @param e */
public void set(int index, E e) {
checkElementIndex(index);
Node cur = list;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.data = e;
}
複製代碼
/** * 獲取頭部節點 */
public E get() {
if (list != null) {
return list.data;
} else {
return null;
}
}
/** * 獲取指定位置的元素 * * @param index * @return */
public E get(int index) {
checkElementIndex(index);
Node cur = list;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.data;
}
複製代碼
完整代碼
/** * 單鏈表 * */
public class SingleLinkedList<E> {
int size = 0;
/** * 指向第一個節點的指針 */
Node list;
/** * 頭部添加節點 * * @param e */
public void add(E e) {
//頭結點
Node cur = new Node(e, list);
list = cur;
size++;
}
/** * 指定位置添加節點 * * @param index * @param e 0 1 2 3 4 */
public void add(int index, E e) {
checkElementIndex(index);
Node preNode = list;
for (int i = 0; i < index - 1; i++) {
//找到插入位置的前一個節點
preNode = preNode.next;
}
Node node = new Node(e);
node.next = preNode.next;
preNode.next = node;
size++;
}
/** * 刪除頭部節點 */
public void remove() {
if (list != null) {
Node node = list;
list = node.next;
//GC
node.next = null;
size--;
}
}
/** * 刪除指定位置節點 * 1 2 3 4 5 * 0 1 2 3 4 * * @param index */
public void remove(int index) {
checkElementIndex(index);
Node preNode = list;
for (int i = 0; i < index - 1; i++) {
//找到指定位置元素的前一個節點
preNode = preNode.next;
}
//指定位置的節點
Node next = preNode.next;
preNode.next = next.next;
//GC
next = null;
size--;
}
/** * 刪除最後一個節點 */
public void removeLast() {
if (list != null) {
//當前節點
Node cur = list;
//最後一個節點的前一個節點
Node preNode = list;
while (cur.next != null) {
preNode = cur;
cur = cur.next;
}
preNode = null;//此時cur已經爲null
size--;
}
}
/** * 修改指定索引的元素 * * @param index * @param e */
public void set(int index, E e) {
checkElementIndex(index);
Node cur = list;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.data = e;
}
/** * 獲取頭部節點 */
public E get() {
if (list != null) {
return list.data;
} else {
return null;
}
}
/** * 獲取指定位置的元素 * * @param index * @return */
public E get(int index) {
checkElementIndex(index);
Node cur = list;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.data;
}
class Node {
E data;
Node next;
public Node(E element, Node next) {
this.data = element;
this.next = next;
}
public Node(E data) {
this.data = data;
}
public Node() {
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", next=" + next +
'}';
}
}
/** * 判斷參數是否爲現有元素的索引 即邊界 * * @param index */
private void checkElementIndex(int index) {
if (!(index >= 0 && index < size))
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
@Override
public String toString() {
Node node = list;
for (int i = 0; i < size; i++) {
System.out.print(node.data + " ");
node = node.next;
}
return super.toString();
}
public static void main(String[] args) {
SingleLinkedList<Integer> list = new SingleLinkedList<>();
list.add(5);
list.add(4);
list.add(3);
list.add(2);
list.add(1);
list.toString();
System.out.println();
list.add(2, 5);
list.toString();
list.removeLast();
System.out.println();
list.toString();
list.set(1, 1);
System.out.println();
list.toString();
System.out.println();
System.out.println(list.get());
}
}
複製代碼
雙鏈表,指各節點之間的邏輯關係是雙向的。
所以,在雙向鏈表中各節點包含如下 3 部分信息
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
複製代碼
若是理解了單鏈表,雙項鍊表其實也沒有太多的差異,主要在於限制條件。不只僅是雙向鏈表,還有不少分類,好比靜態鏈表,動態鏈表,循環鏈表等等。這裏能夠就增刪給出對應的過程,源碼能夠本身去研究研究。
public void add(int index, E element) {
checkPositionIndex(index);
//若是索引和size相等直接尾部插入
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* first 指向第一個節點的指針
* @param e 要插入的元素
* @param succ index位置的節點
* a1 a3
* 在a3索引出插入新的元素 a2
* a1 a2 a3
*/
void linkBefore(E e, Node<E> succ) {
// succ:原a3的前置節點a1
final Node<E> pred = succ.prev;
//pred(a1) e(a2) succ(a3)造成新的節點
//即把e(a2)prev指向pred(a1)節點,把e(a2)next指向succ(a3)節點
final Node<E> newNode = new Node<>(pred, e, succ);
//把succ(a3)的prev指向新的節點newNode
succ.prev = newNode;
//pred爲空表明newNode爲首節點
if (pred == null)
first = newNode;
else
//a1的next節點由a3變爲a2
pred.next = newNode;
size++;
modCount++;
}
複製代碼
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
//獲取該節點的值
final E element = x.item;
//獲取該節點的next節點
final Node<E> next = x.next;
//獲取該節點的prev節點
final Node<E> prev = x.prev;
//把該節點的前節點的next指向該節點的next節點,並清除該節點的prev指向
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//把該節點的next節點的prev指向該節點的prev節點,並清除該節點的next指向
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;//gc清除
size--;
modCount++;
return element;
}
複製代碼
前面講過ArrayList源碼分析及擴容機制,若是你看了應該知道不論是用ArrayList仍是LinkedList主要是看場景,LinkedList增刪快,由於只用調整指向便可,對於ArrayList而言卻要移動整個數組,可是若是說是在尾部插入的話,使用二者均可以。而查找和修改卻要ArrayList只須要知道下標便可,而對於LinkedList卻要經過循環查找。
對於LinkendList,其中還有不少方法,例如addFirst,addLast,remove等,若是你學會了單鏈表,其實雙鏈表也是同樣的,主要在於思惟。
- ArrayList和LinkedList有什麼區別?
- 何時用ArrayList,何時用LinkedList?