1.單鏈表
在 Java 中沒有顯式的指針類型,然而實際上對象的訪問就是使用指針來實現的,即在Java 中是使用對象的引用來替代指針的。因此在使用 Java 實現該結點結構時,一個結點本身就是一個對象。結點的數據域 data 可以使用一個 Object 類型的對象來實現,用於存儲任何類型的數據元素,並通過對象的引用指向該元素;而指針域 next 可以通過節點對象的引用來實現。
單鏈表結點結構是結點的一種最簡單的形式,除此之外還有其他不同的結點結構,但是這些結點結構都有一個數據域,並均能完成數據元素的存取。爲此在使用 Java 定義單鏈表結點結構之前先給出一個結點接口,在接口中定義了所有結點均支持的操作,即對結點中存儲數據的存取。
結點接口
public interface Node {
//獲取結點數據域
public Object gerData();
//設置結點數據域
public void setData(Object obj);
}
單鏈表結點定義
public class SLNode implements Node {
public Object element;
public SLNode next;
public SLNode() {
this(null, null);
}
public SLNode(Object ele, SLNode next) {
this.element = ele;
this.next = next;
}
public SLNode getNext() {
return next;
}
public void setNext(SLNode next) {
this.next = next;
}
/** ************** Methods of Node Interface ************* */
public Object gerData() {
return element;
}
public void setData(Object obj) {
element = obj;
}
}
單鏈表結構 需要注意的是:在單鏈表結構中還需要注意的一點是,由於每個結點的數據域都是一個
Object 類的對象。
與數組類似,單鏈表中的結點也具有一個線性次序,即如果結點 P 的 next 引用指向結點 S,則 P 就是 S 的直接前驅, S 是 P
的直接後續。單鏈表的一個重要特性就是隻能通過前驅結點找到後續結點,而無法從後續結點找到前驅結點。在單鏈表中通常需要完成數據元素的查找、插入、刪除等操作。
(1)查找:在單鏈表中進行查找操作,只能從鏈表的首結點開始,通過每個結點的 next 引用來一次訪問鏈表中的每個結點以完成相應的查找操作。
在單鏈表中進行查找操作,只能從鏈表的首結點開始,通過每個結點的 next 引用來一次訪問鏈表中的每個結點以完成相應的查找操作。
除了單鏈表的首結點由於沒有直接前驅結點,所以可以直接在首結點之前插入一個新的結點之外,在單鏈表中的其他任何位置插入一個新結點時,都只能是在已知某個特定結點引用的基礎上在其後面插入一個新結點。並且在已知單鏈表中某個結點
引用的基礎上,完成結點的插入操作需要的時間是Θ(1)。由於在單鏈表中數據元素的插入是通過節點的插入來完成的,因此在單鏈表中完成數據元素的插入操作要比在數組中完成數據元素的插入操作所需Ο(n)的時間要快得多。
引用的基礎上,完成結點的插入操作需要的時間是Θ(1)。由於在單鏈表中數據元素的插入是通過節點的插入來完成的,因此在單鏈表中完成數據元素的插入操作要比在數組中完成數據元素的插入操作所需Ο(n)的時間要快得多。
(3)刪除
鏈表的刪除和插入相似,數據元素的刪除也是通過對結點的操作完成的。在不同的位置,操作會略有不同。
在單鏈表中刪除一個結點時,除首結點外都必須知道該結點的直接前驅結點的引用。並且在已知單鏈表中某個結點引用的基礎上,完成其後續結點的刪除操作需要的時間是Θ (1)。由於在單鏈表中數據元素的刪除是通過節點的刪除來完成的,因此
在單鏈表中完成數據元素的刪除操作要比在數組中完成數據元素的刪除操作所需Ο (n)的時間要快得多。
(4)結論:在單鏈表中進行順序查找與在數組中完成相同操作具有相同的時間複雜度,而在單鏈表中在已知特定結點引用的前提下完成數據元素的插入與刪除操作要比在數組中完成相同操作快得多。
(4)結論:在單鏈表中進行順序查找與在數組中完成相同操作具有相同的時間複雜度,而在單鏈表中在已知特定結點引用的前提下完成數據元素的插入與刪除操作要比在數組中完成相同操作快得多。
<strong>2.單鏈表的實現
class LinkList {
private Node head = null;
private Node tail = null;
int Length = 0;
public Node getHead() {
return head;
}
public void setHead(Node head) {
this.head = head;
}
public Node getTail() {
return tail;
}
public void setTail(Node tail) {
this.tail = tail;
}
// 判斷鏈表是否爲空
public boolean isEmpty() {
return (Length == 0);
}
// 查找指定位置的結點
public Node FindKth(int k) {
Node temp = head;
int i = 1;
while (temp != null && i < k) { // 移動元素,直到找到相應位置
temp = temp.next;
i++;
}
if (i == k)
return temp;
else
return null;
}
// 查找指定元素所在的位置
public int Find(String str) {
Node temp = head;
int current = 1;
while (temp != null && temp.key.equals(str)) {
temp = temp.next;
current++;
}
if (temp.key.equals(str))
return current;
else
return -1;
}
// 頭插法
public void insertHead(String str) {
Node newLink = new Node(str);
newLink.next = head;
head = newLink;
Length++;
}
// 將結點插入指定位置
public void insert(int index, String str) {
Node newLink = new Node(str);
if (index > 1 && index < Length) {
newLink.next = FindKth(index - 1).next;
FindKth(index - 1).next = newLink;
// System.out.println(head.key); 此時head的值爲4
Length++;
} else if (index == 1) {
newLink.next = head;
head = newLink;
Length++;
} else {
System.out.println("超出鏈表長度,插入無效");
}
}
// 刪除指定位置的結點
public void delete(int index) {
if (index == 1) { // 刪除位置爲頭結點
head = head.next;
Length--;
} else if (index > 1 && index < Length) { // 刪除位置不爲頭結點或尾結點
Node temp = FindKth(index).next;
FindKth(index - 1).next = temp;
Length--;
} else {
System.out.println("超出鏈表長度,查找無效");
}
}
// 刪除指定元素
public void deleteNode(String str) {
int current = Find(str);
if (current > 1 && current < Length) { // 刪除的位置爲頭結點
Node temp = FindKth(current).next;
FindKth(current - 1).next = temp;
Length--;
} else if (current == 1) {
head = head.next;
Length--;
} else if (current == -1) {
System.out.println("要刪除的元素不存在,請重新選擇!");
}
}
// 初始化鏈表
public void initList(Node node) {
head = node;
head.next = tail;
}
// 打印單鏈表
public void display() {
Node current = head;
while (current != null) {
current.displayNode();
current = current.next;
}
}
}
public class NodeTest {
public static void main(String args[]) {
LinkList list = new LinkList();
list.insertHead("1");
list.insertHead("2");
list.insertHead("3");
list.insertHead("4");
System.out.println("頭插法操作後的單鏈表:");
list.display();
System.out.println();
// 把元素插入指定位置
int index = 1;
System.out.println("插入元素後的單鏈表:");
list.insert(index, "a");
list.display();
// 刪除元素
System.out.println();
System.out.println("刪除元素後的單鏈表(刪除指定元素):");
list.deleteNode("7");
list.display();
// 刪除元素
System.out.println();
System.out.println("刪除元素後的單鏈表(刪除結點位置):");
list.delete(4);
list.display();
}
}
class Node {
public String key;
public Node next;// 指向下一個元素的指針
// 初始化頭結點
public Node(String str) {
this.key = str;
this.next = null;
}
public void displayNode() {
System.out.print(key + " -> ");
}
}</strong>
3.基於時間的比較
線性表有查找,刪除,插入三類操作。
對於查找操作有基於序號的查找,即存取線性表中 i 號數據元素。由於數組有隨機存取的特性,在線性表的順序存儲實現中可以在Θ(1)的時間內完成;而在鏈式存儲中由於需要從頭結點開始順着鏈表才能取得,無法在常數時間內完成,因此順序存儲優於鏈式存儲。查找操作還有基於元素的查找,即線性表是否包含某個元素、元素的序號是多少,這類操作線性表的順序存儲與鏈式存儲都需要從線性表中序號爲 0 的元素開始依次查找,因此兩種實現的性能相同。綜上所述,如果在線性表的使用中主要操作是查找,那麼應當選用順序存儲實現的線性表。對於基於數據元素的插入、刪除操作而言,當使用數組實現相應操作時,首先需要採用順序查找定位相應數據元素,然後才能插入、刪除,並且在插入、刪除過程又要移動大量元素;相對而言鏈表的實現只需要在定位數據元素的基礎上,簡單的修改幾個指針即可完成,因此鏈式存儲優於順序存儲。對於基於序號的插入、刪除操作,因爲在順序存儲中平均需要移動一半元素;而在鏈式存儲中不能直接定位,平均需要比較一半元素才能定位。因此順序存儲與鏈式存儲性能相當。綜上所述,如果在線性表的使用中主要操作是插入、刪除操作,那麼選用鏈式存儲的線性表爲佳。
4.基於空間的比較
線性表的順序存儲,其存儲空間是預先靜態分配的,雖然在實現的過程中可以動態擴展數組空間,但是如果線性表的長度變化範圍較大,空間在使用過程中由於會存在大量空閒空間,使得存儲空間的利用率不高。而線性表的鏈式存儲,其結點空間是動態分配的,不會存在存儲空間沒有完全利用的情況。因此當線性表長度變化較大時,宜採用鏈式存儲結構。當線性表的數據元素結構簡單,並且線性表的長度變化不大時。由於鏈式存儲結構使用了額外的存儲空間來表示數據元素之間的邏輯關係,因此針對數據域而言,指針域所佔比重較大;而在線性表的順序存儲結構中,沒有使用額外的存儲空間來表示數據元素之間的邏輯關係,儘管有一定的空閒空間沒有利用,但總體而言由於線性表長度變化不大,因此沒有利用的空間所佔比例較小。所以當線性表數據元素結構簡單,長度變化不大時可以考慮採用順序存儲結構。