鏈表是線性表的其中之一既鞠,線性表又是我們要學(xué)的數(shù)據(jù)結(jié)構(gòu)的一部分,所以非常有學(xué)習(xí)價值,我們今天專門分析單鏈表和雙鏈表饭弓。
一双饥、單鏈表
存儲結(jié)構(gòu)
上圖就是單鏈表的存儲結(jié)構(gòu)原理圖,由圖中我們可以看出每個節(jié)點都由兩部分構(gòu)成弟断,一個是data數(shù)據(jù)域咏花、另一個是指向下一個節(jié)點的next指針域,指針域不存放任何數(shù)據(jù)阀趴。然后一直通過這樣的方式一直指下去昏翰,最后就形成了一條類似鐵鏈的結(jié)構(gòu),所以稱為鏈表刘急。我們看到最后的next指針為null,說明到了最后一個節(jié)點棚菊,最后一個節(jié)點的指針域不指向任何節(jié)點,所以next=null排霉。代碼實現(xiàn)
/**
* Created by bryanrady on 2017/12/7.
* 單鏈表的實現(xiàn)
*/
public class SinglyLinkedList<E>{
private Node<E> first; //單鏈表的頭結(jié)點
private Node<E> last; //單鏈表的最后一個節(jié)點
private int size;
public SinglyLinkedList(){
}
public SinglyLinkedList(SinglyLinkedList c){
this();
addAll(c);
}
static class Node<E>{
E data; //數(shù)據(jù)域
Node<E> next; //指針域
public Node(E data){
this.data = data;
}
}
/**
* 返回單鏈表的長度
* @return
*/
public int size(){
return size;
}
/**
* 在單鏈表尾部添加一個元素節(jié)點
* @param element
*/
public void add(E element){
Node<E> node = new Node<E>(element);
Node<E> l = last;
if(l == null){ //表明之前沒有節(jié)點
first = node;
}else{
l.next = node;
}
last = node;
size++;
}
/**
* 刪除節(jié)點
*/
public E remove(){
return removeFirst();
}
/**
* 刪除第一個節(jié)點
* @return
*/
private E removeFirst(){
Node<E> f = first;
if(f == null){
throw new NoSuchElementException();
}
return unlinkFirst(f);
}
/**
* 刪除第一個節(jié)點
* @param node
* @return
*/
private E unlinkFirst(Node<E> node) {
E element = node.data;
Node<E> next = node.next;
node.data = null;
node.next = null;
first = next;
if(next == null){
last = null;
}
size--;
return element;
}
/**
* 在鏈表尾部添加一個集合
* @param list
* @return
*/
public boolean addAll(SinglyLinkedList<E> list){
return addAll(size,list);
}
/**
* 返回單鏈表的Object[]數(shù)組
* @return
*/
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
Node<E> e = first;
while(e!=null){
result[i++] = e.data;
e = e.next;
}
return result;
}
/**
* 在指定位置添加集合
* @param index
* @param list
* @return
*/
private boolean addAll(int index,SinglyLinkedList<E> list){
if(index<0 || index > size){
throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
}
Object[] a = list.toArray();
int numNew = a.length;
if(numNew == 0){
return false;
}
for(Object o : a){
E e = (E)o;
Node<E> newNode = new Node<>(e);
if(last==null){
first = newNode;
}else{
last.next = newNode;
}
last = newNode;
}
size += numNew;
return true;
}
/**
* 根據(jù)傳入的Index查找節(jié)點
* @param index
* @return
*/
private Node<E> node(int index){
Node<E> x = first;
for(int i=0;i<index;i++){
x = x.next;
}
return x;
}
public E get(int index){
if(index<0||index>=size){
throw new IndexOutOfBoundsException("Index:"+index);
}
return node(index).data;
}
}
我這里沒有像其他實現(xiàn)者一樣定義頭結(jié)點是空的數(shù)據(jù)域窍株,但是實現(xiàn)思路和方法基本都是一模一樣的。我是采用的LinkedList代碼的思想來進(jìn)行實現(xiàn)攻柠,后面有時間再添加其他有關(guān)于集合的方法球订。
二、雙向鏈表
雙向鏈表與單鏈表比較就是多了一個指向前驅(qū)節(jié)點的指針瑰钮,這樣來構(gòu)成有序性冒滩。我們常用的LinkedList就是在雙向鏈表的數(shù)據(jù)結(jié)構(gòu)上進(jìn)行實現(xiàn)的,現(xiàn)在我們直接來分析源代碼浪谴。
LinkedList簡介 LinkedList是基于雙向循環(huán)鏈表(從源碼中可以很容易看出)實現(xiàn)的开睡,除了可以當(dāng)作鏈表來操作外,它還可以當(dāng)作棧苟耻,隊列和雙端隊列來使用篇恒。LinkedList同樣是非線程安全的,只在單線程下適合使用凶杖。
-
繼承關(guān)系與實現(xiàn)的接口
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList繼承了AbstractSequentialList胁艰,而AbstractSequentialList又是AbstractList<E>的一個子類,這就是集合框架架構(gòu)圖智蝠,這里不詳細(xì)介紹腾么,后面再總結(jié)。
LinkedList實現(xiàn)了Deque(雙端隊列)接口杈湾,即LinkedList可以作為一個雙端隊列來使用解虱。
LinkedList實現(xiàn)了Serializable接口,因此它支持序列化漆撞,能夠通過序列化傳輸殴泰,實現(xiàn)了Cloneable接口于宙,能被克隆。
-
數(shù)據(jù)結(jié)構(gòu)艰匙,就是一個雙向鏈表
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; //指向下一個節(jié)點 this.prev = prev; //指向上一個節(jié)點 }
}
3.屬性字段
transient int size = 0; //鏈表中的節(jié)點個數(shù)
transient Node<E> first; //定義鏈表頭結(jié)點
transient Node<E> last; //定義最后一個節(jié)點
4.構(gòu)造函數(shù)
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
- 增刪改查操作
(1)添加操作
/**
* 添加元素
* @param e
* @return
*/
public boolean add(E e){
linkLast(e);
return true;
}
/**
* 在指定的位置往集合中添加元素e
* @param index
* @param e
*/
public void add(int index,E e){
checkPositionIndex(index);
if(index == size){
linkLast(e);
}else{
linkBefore(e,node(index));
}
}
/**
* 添加元素作為第一個節(jié)點
* @param e
*/
public void addFirst(E e){
linkFirst(e);
}
/**
* 添加元素作為最后一個節(jié)點
* @param e
*/
public void addLast(E e){
linkLast(e);
}
/**
* 在鏈表頭部添加元素e,并將元素e作為第一個元素
* @param e
*/
private void linkFirst(E e){
Node<E> f = first;
Node<E> newNode = new Node<>(null,e,f);
first = newNode;
if(f==null){
last = newNode;
}else{
f.prev = newNode;
}
size++;
}
/**
* 在鏈表尾部添加元素e,并將e作為最后一個元素
* @param e
*/
private void linkLast(E e){
Node<E> l = last;
Node newNode = new Node(l,e,null);
if(l == null){
first = newNode;
}else{
l.next = newNode;
newNode.prev = l;
}
last = newNode;
size++;
}
/**
* 在非空節(jié)點succ之前插入元素e
* @param e
* @param succ
*/
private void linkBefore(E e,Node<E> succ){
Node<E> pred = succ.prev;
Node<E> newNode = new Node<>(pred,e,succ);
if(pred == null){
first = newNode;
}else{
pred.next = newNode;
}
succ.prev = newNode;
size++;
}
/**
* 將集合c追加到LinkedList中
* @param c
* @return
*/
public boolean addAll(Collection<? extends E> c){
return addAll(size,c);
}
/**
* 將集合c添加到LinkedList的指定位置
* @param index
* @param c
* @return
*/
public boolean addAll(int index,Collection<? extends E> c){
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if(numNew==0){
return false;
}
Node<E> pred,succ; //設(shè)置當(dāng)前要插入節(jié)點的前后節(jié)點
if(index==size){
succ = null;
pred = last;
}else{
succ = node(index);
pred = succ.prev;
}
/****插入開始***/
for(Object o : a){
E e = (E)o;
Node<E> newNode = new Node<>(pred,e,null);
if(pred==null){
first = newNode;
}else{
pred.next = newNode;
}
//此時把下一個要插入的節(jié)點的上一個節(jié)點變成剛剛插入的節(jié)點
pred = newNode;
}
/****插入完成***/
//設(shè)置前后節(jié)點的關(guān)系
if(succ == null){
last = pred;
}else{
pred.next = succ;
succ.prev = pred;
}
size += numNew;
return true;
}
(2)刪除操作
/**
* 刪除元素限煞,默認(rèn)刪除第一個,并返回
* @return
*/
public E remove(){
return removeFirst();
}
/**
* 刪除集合中指定位置的元素
* @param index
* @return
*/
public E remove(int index){
checkElementIndex(index);
return unlink(node(index));
}
/**
* 在集合中刪除指定的對象
* @param o
* @return
*/
public boolean remove(Object o){
if(o == null){
for(Node<E> x=first;x!=null;x=x.next){
if(x.element == null){
unlink(x);
return true;
}
}
}else{
for(Node<E> x=first;x!=null;x=x.next){
if(x.element.equals(o)){
unlink(x);
return true;
}
}
}
return false;
}
/**
* 刪除鏈表的第一個元素并返回
* @return
*/
public E removeFirst(){
Node<E> f = first;
if(f == null){
throw new NoSuchElementException();
}
return unlinkFirst(f);
}
/**
* 刪除鏈表的最后一個元素抹恳,并返回
* @return
*/
public E removeLast(){
Node<E> l = last;
if(l == null){
throw new NoSuchElementException();
}
return unlinkLast(l);
}
/**
* 清空鏈表 循環(huán)遍歷全部置為null
*/
public void clear(){
for(Node<E> x=first;x!=null;x=x.next){
x.element =null;
x.prev = null;
x.next = null;
x = null;
}
first = last = null;
size = 0;
}
/**
* 刪除一個非空節(jié)點x员凝,并返回
* @param x
* @return
*/
private E unlink(Node<E> x){
E element = x.element;
Node<E> pred = x.prev;
Node<E> succ = x.next;
if(pred == null){
first = succ;
}else{
pred.next = succ;
x.prev = null;
}
if(succ == null){
last = pred;
}else{
succ.prev = pred;
x.next = null;
}
x.element = null;
size--;
return element;
}
/**
* 刪除不為空的第一個節(jié)點
* @param f
*/
private E unlinkFirst(Node<E> f){
E element = f.element;
Node<E> succ = f.next;
f.element = null; //help gc
f.next = null;
first = succ;
if(succ == null){
last = null;
}else{
succ.prev = null;
}
size--;
return element;
}
/**
* 刪除不為空的最后一個節(jié)點
* @param l
* @return
*/
private E unlinkLast(Node<E> l){
E element = l.element;
Node<E> pred = l.prev;
l.element = null;
l.prev = null;
last = pred;
if(pred == null){
first = null;
}else{
pred.next = null;
}
size--;
return element;
}
(3)更新操作
/**
* 修改在鏈表中指定位置的元素
* @param index
* @param e
* @return
*/
public E set(int index,E e){
checkElementIndex(index);
Node<E> oldNode = node(index);
E oldVal = oldNode.element;
oldNode.element = e;
return oldVal;
}
(4)查詢操作
/**
* 根據(jù)index獲取集合中的指定的元素
* @param index
* @return
*/
public E get(int index){
checkElementIndex(index);
return node(index).element;
}
/**
* 獲取第一個節(jié)點上的元素
* @return
*/
public E getFirst(){
Node<E> f = first;
if(f == null){
throw new NoSuchElementException();
}
return f.element;
}
/**
* 獲取最后一個節(jié)點的元素
* @return
*/
public E getLast(){
Node<E> l = last;
if(l == null){
throw new NoSuchElementException();
}
return l.element;
}
/**
* 根據(jù)index位置獲取節(jié)點
* 利用雙向鏈表的特點提高查找效率
* @param index
* @return
*/
private Node<E> node(int index){
if(index < (size>>1)){ //說明要查找的節(jié)點在前半部分
Node<E> x = first;
for(int i=0;i<index;i++){
x = x.next;
}
return x;
}else{ //說明要查找的節(jié)點在前半部分
Node<E> x = last;
for(int i=size-1;i>index;i--){
x = x.prev;
}
return x;
}
}
6.集合的一些常規(guī)方法
/**
* 返回LinkedList的大小
* @return
*/
public int size(){
return size;
}
/**
* 判斷集合是否為空
* @return
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 判斷鏈表集合中是否包含此對象
* @param o
* @return
*/
public boolean contains(Object o){
return indexOf(o) >= 0;
}
/**
* 獲取對象o在鏈表集合中的索引位置
* @param o
* @return
*/
public int indexOf(Object o){
int index = 0;
if(o == null){
for(Node<E> x=first;x!=null;x=x.next){
if(x.element == null){
return index;
}
index++;
}
}else{
for(Node<E> x=first;x!=null;x=x.next){
if(x.element.equals(o)){
return index;
}
index++;
}
}
return -1;
}
/**
* 返回此對象在鏈表中最后出現(xiàn)的位置
* @param o
* @return
*/
public int lastIndexOf(Object o){
int index = size-1;
if(o == null){
for(Node<E> x=last;x!=null;x=x.prev){
if(x.element == null){
return index;
}
index--;
}
}else{
for(Node<E> x=last;x!=null;x=x.prev){
if(x.element.equals(o)){
return index;
}
index--;
}
}
return -1;
}
/**
* 添加元素的時候檢查索引位置
* @param index
*/
private void checkPositionIndex(int index){
if(index<0 || index>size) {
throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
}
}
/**
* 在刪除和獲取元素時判斷檢查下標(biāo)位置
* @param index
*/
private void checkElementIndex(int index){
if(index<0 || index>=size) {
throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
}
}
/**
* 返回LinkedList的Object數(shù)組
* @return
*/
public Object[] toArray(){
Object[] o = new Object[size];
int i=0;
for(Node<E> x=first;x!=null;x=x.next){
o[i++] = x.element;
}
return o;
}
7.LinkedList作為雙端隊列的方法
/*** LinkedList實現(xiàn)了Deque<E>接口,Deque<E>接口對Queue<E>接口進(jìn)行了擴(kuò)展</>
* </> LinkedList作為隊列使用
* 一般用LinkedList作為隊列使用較多奋献,使用棧的時候最好還是使用Stack<E>這個類
* </>*****/
/**
* 往隊列尾部添加元素
* @param e
* @return
*/
public boolean offer(E e){
return add(e);
}
/**
* 從隊列頭部取元素,并刪除
* @return
*/
public E poll(){
Node<E> f = first;
if(f == null){
return null;
}
return unlinkFirst(f);
}
/**
* 從隊列頭部讀取元素健霹,但是不刪除
* @return
*/
public E peek(){
Node<E> f = first;
if(f == null){
return null;
}
return f.element;
}
public boolean offerFrist(E e){
addFirst(e);
return true;
}
public boolean offerLast(E e){
addLast(e);
return true;
}
public E pollFirst(){
Node<E> f = first;
if(f == null){
return null;
}
return unlinkFirst(f);
}
public E pollLast(){
Node<E> f = first;
if(f == null){
return null;
}
return unlinkLast(f);
}
public E peekFirst(){
Node<E> f = first;
if(f == null){
return null;
}
return f.element;
}
public E peekLast(){
Node<E> l = last;
if(l == null){
return null;
}
return l.element;
}
- LinkedList作為棧的使用方法
/*** LinkedList還可以當(dāng)成是棧來使用 *****/
/**
* 往棧頂添加元素,就是在鏈表前面添加元素作為第一個節(jié)點
* @param e
*/
public void push(E e){
addFirst(e);
}
/**
* 往棧頂取元素并刪除,即刪除鏈表前面刪除元素瓶蚂,這樣在就滿足了棧的先進(jìn)后出的原則
* 還有個不刪除的就是和peek()方法一樣
* @return
*/
public E pop(){
return removeFirst();
}
看了這么多LinkedList的方法糖埋,現(xiàn)在我們來總結(jié)一下ArrayList和LinkedList的區(qū)別(也是順序表和鏈表的區(qū)別):
(1)ArrayList是基于動態(tài)數(shù)組實現(xiàn)的,LinkedList是基于雙向鏈表實現(xiàn)的窃这。
(2)ArrayList支持隨機(jī)訪問(通過下標(biāo))瞳别,LinkedList不支持。
(3)ArrayList的查詢和尾部插入元素效率較高杭攻,中間插入和刪除元素效率較低祟敛,因為有大量的數(shù)組復(fù)制操作。
LinkedList的插入和刪除效率較高兆解,只需要把節(jié)點指針改變一下就行馆铁,但是查詢效率較低,即使有雙向鏈表的特性可以從兩個方向查找锅睛,但還是需要使用蠻力法的方式進(jìn)行遍歷埠巨,所以效率較低。
(4)ArrayList會造成一定的空間浪費现拒,因為隨時需要探測數(shù)組容量然后擴(kuò)容辣垒;LinkedList通過節(jié)點方式進(jìn)行存放數(shù)據(jù),不存在空間浪費印蔬。