大話資料結構(四)——雙向連結串列的java實現
阿新 • • 發佈:2018-11-10
在實現了單向連結串列後,我們在使用單向連結串列中會發現一個問題:在單向連結串列中查詢某一個結點的下一個結點的時間複雜度是O(1),但是查詢這個結點的上一個結點的時候,時間複雜度的最大值就變成了O(n),因為在查詢這個指定結點的上一個結點時又需要從頭開始遍歷。
那麼該如何解決這個困難呢?我們可以在單鏈表的每個結點中,再設定一個指向前驅結點的指標域,這就構成了雙向連結串列。在雙向連結串列中含有兩個指標域,一個指向直接前驅,一個指向直接後繼。
雙向連結串列是單向連結串列的拓展,所以程式大致與與單向連結串列的程式相似。
這裡需要注意的是結點的插入和刪除步驟:
將結點s插入到結點p和p->next之間時
(1)將s前驅指向結點p,如圖中①
(2)將s的後繼指向結點p->next,如圖中②
(3)將p->next的前驅指向結點s,如圖中③
(4)將p的後繼指向結點s,如圖中④
將結點p刪除
(1)將p->prior結點的後驅指標指向p->next結點
(2)將p->next結點的前驅指標指向p->prior結點
(3)解除p結點和其前驅後繼的關係
上程式碼
package likend;
/**
* Created by yxf on 2018/4/5.
* 雙向連結串列
*/
public class DoubleLink<T> {
private Node header; //連結串列頭結點
private Node tail; //連結串列尾結點
private int size; //儲存已經有的結點
public class Node<T> {
private T data; //資料
private Node prev; //指向上一個節點的引用
private Node next; //指向下一個節點的引用
public Node() {
}
public Node(T data, Node prev, Node next) {
this .data = data;
this.prev = prev;
this.next = next;
}
}
public DoubleLink() {
}
/**
* 在尾部新增
*
* @param element
* @return
*/
public boolean add(T element) {
linkLast(element);
return true;
}
/**
* 獲取指定索引處的元素
*
* @param index
* @return
*/
public T getElement(int index) {
return (T) getNodeByIndex(index).data;
}
/**
* 獲取指定位置的結點
*
* @param index
* @return
*/
public Node getNodeByIndex(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("獲取位置超過了連結串列長度範圍");
Node currentNode = header;
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
return currentNode;
}
/**
* 獲取指定位置前驅的結點
*
* @param index
* @return
*/
public Node getNodeByIndexBefore(int index) {
Node preNode = header;
for (int i = 0; i < index - 1; i++) {
preNode = preNode.next; //獲得前驅結點
}
return preNode;
}
/**
* 獲取指定元素的前驅
*
* @param currentElem
* @return
*/
public T priorElement(T currentElem) {
int index = getIndex(currentElem);
if (index == -1)
return null;
else {
if (index == 0) {
return null;
} else {
return (T) getNodeByIndex(index - 1).data;
}
}
}
/**
* 獲取指定元素的後驅
*
* @param currentElem
* @return
*/
public T nextElement(T currentElem) {
int index = getIndex(currentElem);
if (index == -1)
return null;
else {
if (index == size - 1) {
return null;
} else {
return (T) getNodeByIndex(index + 1).data;
}
}
}
//查詢連結串列中指定元素所在的位置
public int getIndex(T element) {
Node current = header;
for (int i = 0; i < size && current != null; i++, current = current.next) {
if (current.data.equals(element))
return i;
}
return -1;
}
/**
* 在頭部插入
*
* @param element
* @return
*/
public boolean addFirst(T element) {
linkFirst(element);
return true;
}
/**
* 在指定位置插入元素
*
* @param index
* @param element
* @return
*/
public boolean insert(int index, T element) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("插入位置超出連結串列範圍");
if (index == 0)
linkFirst(element);
else {
//獲取插入位置的前驅結點
Node preNode = getNodeByIndexBefore(index);
//獲取插入位置的後繼結點
Node nextNode = preNode.next;
Node newNode = new Node(element, preNode, nextNode);
//讓前驅結點的後繼指向插入結點
preNode.next = newNode;
//讓後驅結點的前繼指向插入結點
nextNode.prev = newNode;
size++;
}
return true;
}
/**
* 刪除元素
*
* @param index
* @return
*/
public T delete(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("刪除位置超出連結串列範圍");
Node currentNode = header;
//刪除的是頭結點
if (index == 0) {
header = header.next;
currentNode.next = null;
header.prev = null;
} else {
Node currentNodeBefore = null;
for (int i = 0; i < index; i++) {
currentNodeBefore = currentNode;//前置結點
currentNode = currentNode.next; //要刪除的當前結點
}
//刪除的是尾結點
if (index == size - 1) {
tail = currentNodeBefore; //尾結點變為刪除結點的前置結點
tail.next = null;
} else {
//將前置結點的後繼指標指向當前結點的後繼指標
currentNodeBefore.next = currentNode.next;
//將當前刪除結點的後繼結點的前驅指標指向刪除結點的前驅結點
currentNode.next.prev = currentNodeBefore;
}
currentNode.prev = null;
currentNode.next = null;
}
size--;
return (T) currentNode.data;
}
//刪除最後一個元素
public T remove() {
return delete(size - 1);
}
/**
* 尾部插入
*
* @param e
*/
private void linkLast(T e) {
final Node<T> l = tail;
final Node<T> newNode = new Node<>(e, null, null);
if (l == null) {
header = newNode;
tail = header;
} else {
tail.next = newNode; //尾結點指向新結點
newNode.prev = tail;
tail = newNode; //新結點作為尾結點
}
size++;
}
/**
* 在頭部插入
*
* @param e
*/
private void linkFirst(T e) {
final Node<T> l = header;
Node<T> newNode = new Node<>(e, null, null);
if (l == null) {
header = newNode;
tail = header;
} else {
newNode.next = header;
header.prev = newNode;
header = newNode;
}
size++;
}
public boolean isEmpty() {
return size == 0;
}
//清空線性表
public void clear() {
//將頭結點和尾結點設為空
header = null;
tail = null;
size = 0;
}
@Override
public String toString() {
if (isEmpty())
return "[]";
else {
StringBuilder sb = new StringBuilder("[");
for (Node current = header; current != null; current = current.next)
sb.append(current.data.toString() + "->");
sb.append("]");
int len = sb.length();
return sb.delete(len - 3, len - 1).toString();
}
}
}
測試程式碼
package likend;
/**
* Created by yxf on 2018/4/5.
*/
public class DoubleLinkTest {
public static void main(String[] args) {
DoubleLink ls = new DoubleLink();
ls.add(2);
ls.add(4);
ls.add(5);
ls.addFirst(1);
System.out.println("新增元素後的連結串列為: " + ls);
ls.insert(2, 3);
System.out.println("在連結串列位置2插入元素: " + ls);
ls.delete(2);
System.out.println("在連結串列位置2刪除元素: " + ls);
ls.remove();
System.out.println("刪除連結串列中的一個元素: " + ls);
System.out.println("獲得連結串列位置為2處的元素: " + ls.getElement(2));
System.out.println("獲取元素2的前驅元素: " + ls.priorElement(2));
System.out.println("獲取元素2的後驅元素: " + ls.nextElement(2));
ls.clear();
System.out.println(ls);
}
}
新增元素後的連結串列為: [1->2->4->5]
在連結串列位置2插入元素: [1->2->3->4->5]
在連結串列位置2刪除元素: [1->2->4->5]
刪除連結串列中的一個元素: [1->2->4]
獲得連結串列位置為2處的元素: 4
獲取元素2的前驅元素: 1
獲取元素2的後驅元素: 4
[]
Process finished with exit code 0
借鑑 https://blog.csdn.net/u013393958/article/details/72650496 程式碼有所改變。