04數據結構和算法(Java描述)~雙鏈表
本文是上一篇文章的後續,詳情點擊該鏈接
雙鏈表的定義
雙鏈表也叫雙向鏈表,它依舊採用的是鏈式存儲結構。在雙鏈表中,每個節點中都有兩個指針, 分別指向直接前驅節點(保存前一個節點的地址值)和直接後繼節點(保存後一個節點的地址值), 如下圖所示
所以,從雙鏈表中的任意一個節點開始,都可以很方便地訪問它的直接前驅節點和直接後繼節 點,如下圖所示。
單鏈表和雙鏈表的區別
邏輯上沒有區別,他們均是完成線性表的內容,主要的區別是結構上的構造有所區別。
單鏈表
對於一個節點,有儲存數據的 data和指向下一個節點的next。也就是說,單鏈表的遍歷操作都 得通過前節點—>後節點。
雙鏈表
對於一個節點,有儲存數據的data和指向下一個節點的 next,還有一個指向前一個節點的 pre。 也就是說,雙鏈表不但可以通過前節點—>後節點,還可以通過後節點—>前節點。
定義List接口
public interface List <E>{
int size();
boolean isEmpty();
boolean contains(E element);
void add(E element);
E get(int index);
E set(int index,E element);
void add(int index,E element);
E remove(int index);
int indexOf(E element);
void clear();
String toString();
}
接口實現
public class LinkedList<E> implements List<E>{
private int size; //集合的長度
private Node first;
private Node last;
@Override
public int size() {
return size;
}
//判斷當前集合中是否有元素
@Override
public boolean isEmpty() {
return size == 0;
}
//判斷當前元素是否存在
@Override
public boolean contains(E element) {
return indexOf(element) > -1;
}
@Override
public void add(E element) {
//調用下面的add方法,根據size保證每次都添加在最後
add(size,element);
}
//取得座標位置上的節點
private Node<E> node(int index){
Node N = first; //指向頭
//先判斷要查找的index,是靠近頭還是靠近尾
//如果靠近頭就從頭開始查找,如果靠近尾就從尾開始查找
//方法: 根據index 和 size的一半去比較
if(index > (size >> 1)){
//靠近尾
N = last; //指向尾
for(int i = size-1; i > index; i--){
N = N.pre;
}
}else{
//靠近頭
for(int i = 0; i < index; i++){
N = N.next;
}
}
return N;
}
@Override
public E get(int index) {
//防止座標越界
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException("index: " + index + " size: " + size);
}
//調用方法
return node(index).element;
}
@Override
public E set(int index, E element) {
//獲得index上的node
Node<E> node = node(index);
//保存原來的值
E oldElement = node.element;
//新值覆蓋老值
node.element = element;
//返回老值
return oldElement;
}
@Override
public void add(int index, E element) {
//當需要添加到末尾時
if(index == size) {
//拿到last節點
Node l = last;
//構建node 完成指向關係
Node newNode = new Node(l,null,element);
//將原來的last 節點的next 修改成新構建出來的node
last = newNode;
if(l == null){
first = newNode;
}else {
l.next = newNode;
}
}else{
//獲得指定的index
Node<E> node = node(index);
//獲得前一個結點
Node<E> pre = node.pre;
//構建新的now 完成指向關係
Node<E> newNode = new Node(pre, node, element);
//改變指向
pre.next = newNode;
if (pre == null) {
first = newNode;
} else {
node.pre = newNode;
}
}
size++;
}
//鏈表刪除的主要原理就是將被刪除者的前一個元素指向後一個元素
//比如 A->B->C 當我要刪除B的時候,就讓A -> C
@Override
public E remove(int index) {
//防止座標越界
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException("index: " + index + " size: " + size);
}
//獲得要刪除的元素Node
Node<E>node = node(index);
//獲得前一個結點
Node<E> pre = node.pre;
//獲得後一個結點
Node<E> next = node.next;
if(pre == null){
//firest進行修改
first = next;
next.pre = null;
}else{
//改變前一個結點的next
pre.next = next;
}
if(next == null){
//last進行修改
last = pre;
}else{
next.pre = pre;
}
size--;
//返回老元素
return node.element;
}
@Override
public int indexOf(E element) {
//查找element元素是否存在,有返回索引,沒有返回-1
Node N = first;
int index = 0;
//遍歷
for(Node i = N; i != null; i = i.next){
if(element == i.element){
return index;
}
index++;
}
return -1;
}
@Override
public void clear() {
size = 0;
first = null;
last = null;
}
public String toString(){
Node N = first;
StringBuilder stringBuilder = new StringBuilder("[");
boolean flag = false; //判斷是否循環到了最後
for(Node i = N; i != null; i = i.next){
//說明已經到了最後一個元素
if(i.next == null) {
flag = true;
}
//如果沒到最後就加 逗號
if(flag == false){
stringBuilder.append(i.element + ",");
}else{ //到了最後就不加逗號
stringBuilder.append(i.element);
}
}
stringBuilder.append("]");
return stringBuilder.toString();
}
//內部Node節點類
private static class Node<E>{
Node<E> pre;
Node<E> next;
E element;
public Node(Node next,E element){
this.next = next;
this.element = element;
}
public Node(Node pre,Node next,E element){
this.pre = pre;
this.next = next;
this.element = element;
}
public Node() {
}
}
}