前言
鏈表是一種線性表,常見的線性表還有棧、隊列,本篇主要是分析雙向鏈表的數據結構,以及如何使用java自己實現一個雙向鏈表。在分析雙向鏈表前,有必要先看看單向鏈表的數據結構圖,方便對比雙向鏈表數據結構。
1、單向鏈表
百度百科:單向鏈表(單鏈表)是鏈表的一種,其特點是鏈表的鏈接方向是單向的,對鏈表的訪問要通過順序讀取從頭部開始;鏈表是使用指針進行構造的列表;又稱爲結點列表,因爲鏈表是由一個個結點組裝起來的;其中每個結點都有指針成員變量指向列表中的下一個結點。
刪除操作後:
2、雙向鏈表
百度百科:雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。一般我們都構造雙向循環鏈表。
刪除操作後:
3、手寫實現
public class CoolList<T> {
//節點個數
public int size;
//頭節點
private SummerNode<T> first;
//尾節點
private SummerNode<T> last;
//定義節點類型
private static class SummerNode<T> {
//節點元素
T t;
//前一個節點地址
SummerNode<T> pre;
//後一個節點地址
SummerNode<T> next;
SummerNode(SummerNode<T> pre,T t,SummerNode<T> next){
this.t = t;
this.pre = pre;
this.next = next;
}
}
/**
* 更新指定位置的元素
* @param index 位置
* @param t 元素
* @return 返回舊值
*/
public T replace(int index,T t){
//檢查節點是否存在
checkExistNode(index);
//獲取指定位置節點
SummerNode<T> exist = node(index);
T oldValue = exist.t;
//替換新值
exist.t = t;
return oldValue;
}
/**
* 在指定位置之前插入元素
* @param index
* @param t
* @return 成功返回true
*/
public boolean beforeIndexInsert(int index,T t){
//檢查節點是否存在
checkExistNode(index);
//獲取節點
SummerNode<T> node = node(index);
//前一個節點
SummerNode<T> pre = node.pre;
//插入的節點在pre節點和node節點之間
final SummerNode<T> newNode = new SummerNode<>(pre, t, node);
node.pre = newNode;
if(pre == null){
//如果插入的位置是第一個節點前
first = newNode;
} else {
pre.next = newNode;
}
//元素+1
size++;
return true;
}
//在末尾插入
public boolean lastInsert(T t){
final SummerNode<T> l = last;
final SummerNode<T> newNode = new SummerNode<>(l, t, null);
last = newNode;
if(l == null){
//如果是首次插入元素,設置爲第一個節點
first = newNode;
} else {
l.next = newNode;
}
size++;
return true;
}
private void checkExistNode(int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size+";");
}
}
/**
* 獲取指定位置節點元素
* @param index
* @return
*/
public T get(int index){
checkExistNode(index);
return node(index).t;
}
/**
* 獲取指定位置的節點
* @param index
* @return
*/
SummerNode<T> node(int index){
//二分法查找,小於size/2,則順序查找
if(index < (size >> 1)){
SummerNode<T> x = first;
for(int i=0; i<index; i++){
x = x.next;
}
return x;
} else {
SummerNode<T> x = last;
//倒序查找
for (int i = size-1; i > index; i--){
x = x.pre;
}
return x;
}
}
/**
* 移除指定位置的節點
* @param index
* @return
*/
public T remove(int index){
//查找節點是否存在
checkExistNode(index);
//刪除節點
return unlink(node(index));
}
/**
* 移除指定元素的節點
* @param t
* @return
*/
public boolean remove(T t){
//爲空
if(t == null){
for(SummerNode<T> x=first; x!=null; x=x.next){
//查出爲空的元素,再刪除
if(x.t == null){
unlink(x);
return true;
}
}
} else {
//不爲空,查出不爲空的元素,然後刪除
for(SummerNode<T> x=first; x!=null; x=x.next){
if(t.equals(x.t)){
unlink(x);
return true;
}
}
}
return false;
}
/**
* 刪除節點
* @param node
* @return
*/
private T unlink(SummerNode<T> node){
final T type = node.t;
//刪除節點的前一個節點
final SummerNode<T> pre = node.pre;
//刪除節點的後一個節點
final SummerNode<T> next = node.next;
//頭結點
if(pre == null){
first = next;
} else {
pre.next = next;
node.pre = null;
}
//尾結點
if(next == null){
last = pre;
} else {
next.pre = pre;
node.next = null;
}
node.t = null;
size--;
return type;
}
}
測試代碼:
public class CoolListTest {
public static void main(String[] args) {
CoolList<String> coolList = new CoolList<>();
//增加
coolList.lastInsert("cool");
coolList.lastInsert("moon");
coolList.lastInsert("單向鏈表");
coolList.lastInsert("的");
coolList.lastInsert("單向鏈表");
//刪除
coolList.remove(2);
//修改
coolList.replace(coolList.size - 1,"雙向鏈表");
//插入
coolList.beforeIndexInsert(1,"summer");
StringBuffer sb = new StringBuffer();
for (int i=0; i<coolList.size; i++){
//查找
sb.append(coolList.get(i)).append(" ");
}
System.out.println(sb);
}
}
控制檯輸出:
cool summer moon 的 雙向鏈表
結束語
java中LinkedList容器的底層數據結構也是雙向鏈表,直達地址:從源碼分析java容器之LinkedList