鏈表是一種遞歸類型的數據結構,也是比較簡單的數據結構。
今天實現一個單鏈表結構,主要實現其 從鏈表頭添加節點 / 尾部添加 /任意點添加 /頭部刪除節點 /尾部刪除節點 / 任意點刪除節點。
假設我們節點的類如下。
package com.fd.javabasic;
public class Node {
public Node next;
private String val;
public Node(String val){
this.val =val;
}
public String getVal(){
return this.val;
}
}
約定 next 的指針爲null 表示當前的節點是最後一個節點,往後是沒有數據的。
private Node head;
private int size;
public SingleLink(){
this.size =0;
this.head = null;
}
...
//
public void printNode(){
Node temp = this.head;
do{
System.out.println(temp.getVal());
temp = temp.next;
}while(this.head.next!=null);
}
public int size(){
return this.size;
}
}
一 : 頭部添加
思路:鏈表其實就是移動指針,所以需要一個臨時節點(oldHead)保持當前的頭部對象,使新加入的節點的next對象執行oldHead。
/**
* 從頭部插入
* @param node
* @return
*/
public void addHead(Node node){
if(node == null){
new Exception("node is not null point");
}
if(this.head == null ){
this.head = node;
this.head.next = null;
}else{
Node oldHead = this.head;
this.head = node;
this.head.next = oldHead;
}
this.size++;
}
測試:發現和預期的結果一樣。
SingleLink singleLink = new SingleLink();
//**倒序打印
Node node = new Node("0");
singleLink.addHead(node);
singleLink.addHead(new Node("1"));
singleLink.addHead(new Node("2"));
singleLink.printNode();
//result
2
1
0
二:頭部刪除
如果存在節點即刪除。不存在返回null值。
/***
* 從頭刪除一個節點
* @return
*/
public Node deleteHead(){
if(this.size== 0 ){
return null;
}
Node deleteNode = this.head;
this.head = this.head.next;
this.size--;
return deleteNode;
}
測試:
測試預期一樣,最後一個因爲不存在節點,所以報錯。
Node deleteNode = singleLink.deleteHead();
System.out.println("delete node :"+ deleteNode.getVal());
deleteNode = singleLink.deleteHead();
System.out.println("delete node :"+ deleteNode.getVal());
deleteNode = singleLink.deleteHead();
System.out.println("delete node :"+ deleteNode.getVal());
deleteNode = singleLink.deleteHead();
System.out.println("delete node :"+ deleteNode.getVal());
//result
delete node :2
delete node :1
delete node :0
Exception in thread "main" java.lang.NullPointerException
at com.fd.javabasic.SingleLinkTest.main(SingleLinkTest.java:21)
三、尾部添加節點
對於單鏈表來說,在尾部添加節點,就需要進行遍歷鏈表找到尾節點。所以鏈表複雜度O(n)。
首先報存一個節點,通過遍歷節點,不斷的改變temp 節點的指針值,找到尾節點,遍歷勢必會消耗性能。有沒有替代的方案呢。
/**
* 從鏈表尾部插入數據
* @param node
*/
public void addTail(Node node){
if(node == null){
new Exception("node is not null point");
}
if(this.head == null){
this.head = node;
}else{
Node temp = this.head;
while(temp.next!= null){
temp = temp.next;
}
temp.next=node;
}
this.size++;
}
方法二: 我們首先須保存尾節點指針。
這個方法中,我添加了tail 節點用於表示尾節點。這樣從尾部添加只需要改變tail節點的指針就可以了,顯然此方法比只使用一個head 節點,性能要好很多。付出的代價只是多保存了一個tail 節點。
public void addTail2(Node node){
if(node == null){
new Exception("node is not null point");
}
if(this.head == null ){
this.head = node;
this.tail = node;
}else{
Node temp =this.tail;
temp.next=node;
this.tail = node;
}
this.size ++;
}
測試:
Node node = new Node("0");
singleLink.addHead(node);
singleLink.addHead(new Node("1"));
singleLink.addTail2(new Node("2"));
singleLink.addTail2(new Node("3"));
singleLink.addTail2(new Node("4"));
singleLink.printNode();
//result
1
0
2
3
4
四: 從尾部刪除一個節點
從尾部刪除一個節點,發現我做遍歷,找到最後一個元素的倒數第二個元素,進行刪除,很顯然這個方法的性能很差。
有沒有替代的辦法呢?
public Node deleteTail(){
if(size == 0){
return null;
}
Node temp = this.head;
for(int i=1;i<this.size-1;i++){
temp = this.head.next;
}
Node tail = temp.next;
temp.next = null;
this.size --;
return tail;
}
如果有的話,應該需要改變node 的數據結構了,需要添加一個prev 指向前一個的指針。這樣我們就可以實現了,目前在這裏不打算這樣做。
從上面的可以看出單鏈表在插入節點和刪除節點時性能非常高,時間複雜度都是O(1) ,但是任意位置插入和刪除的時候,就需要進行遍歷了,這裏涉及到了查找。
下面我給出任意節點查找和刪除的例子
/**
* 通過索引插入數據
* @param node
* @param index
* @return
*/
public void addIndex(Node node,int index){
if(node == null || index <0 || index>=this.size){
new Exception("node is not null or index is <0 >=size");
}
if(index == 0){
addHead(node);
return;
}
if(index == this.size-1){
addTail(node);
return;
}
int tempIndex=0;
//查找前一個
Node temp =this.head;
for(int i=0;i<index-1;i++){
temp = this.head.next;
}
Node insertNodeAfterTemp = temp.next;
temp.next = node;
node.next = insertNodeAfterTemp;
this.size ++;
}
測試:
SingleLink singleLink = new SingleLink();
//**倒序打印
Node node = new Node("0");
singleLink.addHead(node);
singleLink.addHead(new Node("1"));
singleLink.addTail2(new Node("2"));
singleLink.addTail2(new Node("3"));
singleLink.addTail2(new Node("4"));
// 5 應該排在1的後面
singleLink.addIndex(new Node("5"),1);
singleLink.printNode();
//result
1
0
5
2
3
4
刪除任意節點:
/**
* 刪除任意一個節點
*/
public Node deleteTail(int index){
if(index < 0 || index > size-1){
new Exception("index > size or index < 0");
}
if (index == 0) {return deleteHead();}
if(index == size -1) {return deleteTail();}
//查找前一個
Node temp =this.head;
for(int i=0;i<index-1;i++){
temp = this.head.next;
}
Node deleteNode = temp.next;
Node deleteAfterNode = deleteNode.next;
temp.next = deleteAfterNode;
this.size --;
return deleteNode;
}
測試:
//把添加的5 刪除
Node deleteNode = singleLink.deleteTail(1);
singleLink.printNode();
//result
1
0
2
3
4
總結:
單鏈表優點: 1、存儲的空間可以連續或者不連續,形成鏈表只需要next 指向下一個對象的引用即可。
2、長度大小初始可以不受限制,大小不受限制(不是絕對的,受物理內存,虛擬機分配限制)
3、在頭部和尾部插入元素或者刪除元素時間複雜度O(1),性能比較好。可以作爲棧,或者隊列的結構。
單鏈表缺點: 查找元素時間複雜度O(n),不是進行用於查找的數據結構。