鏈表
線型數據結構
爲什麼鏈表很重要
-
鏈表是真正的動態數據結構
-
鏈表是最簡單的動態數據結構
-
可以更深入的理解引用(指針)
-
可以更深入的理解遞歸
-
可以輔助組成其他的數據結構
什麼是鏈表(LinkedList)
-
數據存儲在"節點"(Node)中
class Node {
/**
* 所存儲的真正的數據
*/
E e;
/**
* 指向當前節點的下一個節點
*/
Node next;
}
-
優點:真正的動態數據結構,不需要處理固定容量的問題
-
缺點:喪失了隨機訪問的能力
數組和鏈表的對比
-
數組最好用於索引有語意的情況,如:scores[2]
-
數組最大的優點就是:支持快速查詢
-
鏈表不適合於索引有語意的情況
-
鏈表最大的優點:動態
鏈表的基本實現
package com.ldc.datastructures.linkedlist;
/**
* @author lengdongcheng
*/
public class LinkedList<E> {
/**
* 內部類
*/
private class Node{
/**
* 存放的真實的數據
*/
public E e;
/**
* 指向該節點的下一個節點
*/
public Node next;
/**
* 構造函數
* @param e
* @param next
*/
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
/**
* 只傳一個e的構造函數
* @param e
*/
public Node(E e) {
this(e, null);
}
/**
* 默認構造函數
*/
public Node() {
this(null, null);
}
/**
* 重寫toString方法
* @return
*/
@Override
public String toString() {
return e.toString();
}
}
}
鏈表頭添加元素
圖示
-
將新增加的節點掛接到原來的節點上,讓新增的Node節點的next指向將要掛在的節點的頭部
-
維護head,將head頭部指向新增的節點
-
最終效果
代碼演示
package com.ldc.datastructures.linkedlist;
/**
* @author lengdongcheng
*/
public class LinkedList<E> {
/**
* 內部類
*/
private class Node {
/**
* 存放的真實的數據
*/
public E e;
/**
* 指向該節點的下一個節點
*/
public Node next;
/**
* 構造函數
*
* @param e
* @param next
*/
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
/**
* 只傳一個e的構造函數
*
* @param e
*/
public Node(E e) {
this(e, null);
}
/**
* 默認構造函數
*/
public Node() {
this(null, null);
}
/**
* 重寫toString方法
*
* @return
*/
@Override
public String toString() {
return e.toString();
}
}
/**
* 頭部元素
*/
private Node head;
/**
* 鏈表中元素的個數
*/
private int size;
/**
* 默認的構造函數
*/
public LinkedList() {
//頭部元素爲空
head = null;
//元素的個數爲0
size = 0;
}
/**
* 獲取鏈表中元素的個數
*
* @return
*/
public int getSize() {
return size;
}
/**
* 判斷鏈表是否爲空
*
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在鏈表的頭部添加元素
*/
public void addFirst(E e) {
/* //將元素e包裝成一個Node對象
Node node = new Node(e);
//將Node的next指向將要掛載的Node對象
node.next = head;
//維護一下head的指向,指向新添加的Node節點
head = node;*/
//上面三行代碼可以用下面一行代碼來表示
head = new Node(e, head);
//維護一下size
size++;
}
}
在鏈表中間添加元素
圖示
-
需求
-
將node節點的next指向prev的下一個節點:node.next = prev.next
-
將prev節點的next指向node節點:prev.next = node
-
最後效果
-
需要注意下順序
代碼實現
/**
* 在鏈表的index(從0開始)位置添加元素
* 在鏈表的中間添加元素不是一個常用的操作,這裏只是練習用
* @param index
* @param e
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
}
//新添加的元素添在鏈表的頭部,則調用addFirst就好了(添加到0的位置需要特殊處理)
if (index == 0) {
addFirst(e);
} else {
//剛開始將prev初始化爲head的位置
Node prev = head;
//一直遍歷,剛開始指向的是head的這個位置,然後一直將head.next賦值給prev
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//將新添加的元素包裝成Node對象
/* Node node = new Node(e);
//將node的下一個節點指向prev的下一個節點
node.next = prev.next;
//將prev的下一個節點指向node
prev.next = node;*/
//同樣,可以將上面三行代碼合成一行代碼
//new Node(e, prev.next); ----->將node這個節點於後面的節點掛接在一起
prev.next = new Node(e, prev.next);
size++;
}
}
/**
* 在鏈表的末尾位置添加元素
* @param e
*/
public void addLast(E e) {
add(size, e);
}
爲鏈表設置虛擬頭節點
-
代碼實現
/**
* 在鏈表的index(從0開始)位置添加元素
* 在鏈表的中間添加元素不是一個常用的操作,這裏只是練習用
* @param index
* @param e
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//將虛擬頭節點的next指向新節點與虛擬頭節點後面節點掛載後的節點
prev.next = new Node(e, prev.next);
size++;
}
/**
* 在鏈表的頭部添加元素
*/
public void addFirst(E e) {
add(0, e);
}
/**
* 在鏈表的末尾位置添加元素
* @param e
*/
public void addLast(E e) {
add(size, e);
}
查詢以及更新鏈表中的元素
/**
* 根據索引獲取鏈表中的元素(根據索引獲取元素不常用,只是練習用)
* @param index
* @return
*/
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("get failed,index < 0 || index > size.");
}
//虛擬節點後面的節點
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
//獲取index位置的Node節點
cur = cur.next;
}
//獲取index位置的Node節點的元素
return cur.e;
}
/**
* 獲取第一個節點的元素
* @return
*/
public E getFirst(){
return get(0);
}
/**
* 獲取最後一個節點的元素
* @return
*/
public E getLast(){
return get(size-1);
}
/**
* 修改鏈表中的第index位置的元素
* @param index
* @param e
*/
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("set failed,index < 0 || index > size.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
/**
* 查看鏈表是否包含元素e
* @param e
* @return
*/
public boolean contains(E e) {
Node cur = dummyHead.next;
//如果cur爲空的話,那麼則表示整的鏈表都已經遍歷完了一遍
while (cur != null) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
sb.append(cur + "->");
cur = cur.next;
}
sb.append("NULL");
return sb.toString();
}
測試案例
package com.ldc.datastructures.linkedlist;
/**
* @author lengdongcheng
*/
public class Main {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 5; i++) {
linkedList.addFirst(i);
System.out.println(linkedList);
}
linkedList.add(2, 666);
System.out.println(linkedList);
}
}
測試結果
0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
4->3->666->2->1->0->NULL
從鏈表中刪除元素
圖示
-
需求
-
實現
代碼實現
/**
* 刪除指定索引位置的元素
* @param index
* @return
*/
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("remove failed,index < 0 || index > size.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
//找到待刪除節點之前的節點
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
retNode = null;
size--;
return retNode.e;
}
/**
* 刪除鏈表中的第一個元素
* @return
*/
public E removeFirst() {
return remove(0);
}
/**
* 刪除鏈表中的最後一個元素
* @return
*/
public E removeLast() {
return remove(0);
}