概念:
鏈式存儲結構是基於指針實現的。我們把一個數據元素和一個指針稱爲結點。
數據域:存儲數據元素信息的域。
指針域:存儲直接後繼位置的域。
1.鏈式存儲結構是用指針把相互直接關聯的結點(即直接前驅結點或直接後繼結點)鏈接起來。鏈式存儲結構的線性表稱爲鏈表。
2.單鏈表:鏈表的每個結點中只包含一個指針域,叫做單鏈表(即構成鏈表的每個結點只有一個指向直接後繼結點的指針)
單鏈表中每個結點的結構:
a.單鏈表的操作:
1.添加:上圖可以看出 單向鏈表只有一個指向,原來head爲p, p指向s, 添加結點只需要把p指向q, q指向s就可以了,即:p--->q; q--->s;這樣就實現了單向鏈表的參加;
2.刪除:原理與添加相反,若此時鏈表爲p--->q--->s;若刪除q結點只需要更改p的指向就可以了 p--->s,這樣就刪掉了;
3.查找:查找操作需要對整個單鏈表進行遍歷,直到滿足查找條件爲止;
4.修改:此操作一般建立在查找之上進行,找到結點之後對值進行修改;
b.頭指針和頭結點:
單鏈表有帶頭結點結構和不帶頭結點結構兩種。
"鏈表中第一個結點的存儲位置叫做頭指針",如果鏈表有頭結點,那麼頭指針就是指向頭結點的指針。
頭指針所指的不存放數據元素的第一個結點稱作頭結點(頭結點指向首元結點)。頭結點的數據域一般不放數據(當然有些情況下也可存放鏈表的長度,用做監視哨等)
存放第一個數據元素的結點稱作第一個數據元素結點,或稱首元結點。
如下圖所示:
c.不帶頭結點的單鏈表如下:
d.帶頭結點的單鏈表如下:
e.不帶頭結點的單鏈表的插入操作:
上圖中,是不帶頭結點的單鏈表的插入操作。如果我們在非第一個結點前進行插入操作,只需要a(i-1)的指針域指向s,然後將s的指針域指向a(i)就行了;
如果我們在第一個結點前進行插入操作,頭指針head就要等於新插入結點s,這和在非第一個數據元素結點前插入結點時的情況不同。另外,還有一些不同情況需要考慮。因此,算法對這兩種情況就要分別設計實現方法。
f.帶頭結點的單鏈表的插入操作:(操作統一,推薦)
上圖中,如果採用帶頭結點的單鏈表結構,算法實現時,p指向頭結點,改變的是p指針的next指針的值(改變頭結點的指針域),而頭指針head的值不變。因此,算法實現方法比較簡單,其操作與對其他結點的操作統一。
問題1:頭結點的好處:
頭結點即在鏈表的首元結點之前附設的一個結點,該結點的數據域中不存儲線性表的數據元素,其作用是爲了對鏈表進行操作時,可以對空表,非空表的情況以及對首元結點進行統一處理,編程更方便。
問題2:頭結點的數據域內裝的是什麼?
頭結點的數據域可以爲空,也可存放線性表長度等附加信息,但此結點不能計入鏈表長度值。
問題3:如何表示空表:
有頭結點時,當頭結點的指針域爲空時表示空表。
二、單項鍊表的代碼實現:
1.結點類:
單鏈表是由一個一個結點組成的,因此,要設計單鏈表類,必須先設計結點類。結點類的成員變量有兩個:一個是數據元素,另一個是表示下一個結點的對象引用(即指針)。
步驟如下:
(1)頭結點的構造(設置指針域即可)
(2)非頭結點的構造
(3)獲得當前結點的指針域
(4)獲得當前結點數據域的值
(5)設置當前結點的指針域
(6)設置當前結點數據域的值
注:類似於get和set方法,成員變量是數據域和指針域。
2.代碼實現:
(1)List.java:(鏈表本身也是線性表,只不過物理存儲上不連續)
//線性表接口
public interface List {
//獲得線性表長度
public int size();
//判斷線性表是否爲空
public boolean isEmpty();
//插入元素
public void insert(int index, Object obj) throws Exception;
//刪除元素
public void delete(int index) throws Exception;
//獲取指定位置的元素
public Object get(int index) throws Exception;
}
(2)Node.java:結點類
//結點類
public class Node {
Object element; //數據域
Node next; //指針域
//頭結點的構造方法
public Node(Node nextval) {
this.next = nextval;
}
//非頭結點的構造方法
public Node(Object obj, Node nextval) {
this.element = obj;
this.next = nextval;
}
//獲得當前結點的指針域
public Node getNext() {
return this.next;
}
//獲得當前結點數據域的值
public Object getElement() {
return this.element;
}
//設置當前結點的指針域
public void setNext(Node nextval) {
this.next = nextval;
}
//設置當前結點數據域的值
public void setElement(Object obj) {
this.element = obj;
}
public String toString() {
return this.element.toString();
}
}
2.單鏈表類:
單鏈表類的成員變量至少要有兩個:一個是頭指針,另一個是單鏈表中的數據元素個數。但是,如果在增加一個表示單鏈表當前結點位置的成員變量,則有些成員函數的設計將更加方便。
代碼實現:
(3)LinkList.java:單項鍊表類(核心代碼)
//單向鏈表類
public class LinkList implements List {
Node head; //頭指針
Node current;//當前結點對象
int size;//結點個數
//初始化一個空鏈表
public LinkList()
{
//初始化頭結點,讓頭指針指向頭結點。並且讓當前結點對象等於頭結點。
this.head = current = new Node(null);
this.size =0;//單向鏈表,初始長度爲零。
}
//定位函數,實現當前操作對象的前一個結點,也就是讓當前結點對象定位到要操作結點的前一個結點。
//比如我們要在a2這個節點之前進行插入操作,那就先要把當前節點對象定位到a1這個節點,然後修改a1節點的指針域
public void index(int index) throws Exception
{
if(index <-1 || index > size -1)
{
throw new Exception("參數錯誤!");
}
//說明在頭結點之後操作。
if(index==-1) //因爲第一個數據元素結點的下標是0,那麼頭結點的下標自然就是-1了。
return;
current = head.next;
int j=0;//循環變量
while(current != null&&j<index)
{
current = current.next;
j++;
}
}
@Override
public void delete(int index) throws Exception {
// TODO Auto-generated method stub
//判斷鏈表是否爲空
if(isEmpty())
{
throw new Exception("鏈表爲空,無法刪除!");
}
if(index <0 ||index >size)
{
throw new Exception("參數錯誤!");
}
index(index-1);//定位到要操作結點的前一個結點對象。
current.setNext(current.next.next);
size--;
}
@Override
public Object get(int index) throws Exception {
// TODO Auto-generated method stub
if(index <-1 || index >size-1)
{
throw new Exception("參數非法!");
}
index(index);
return current.getElement();
}
@Override
public void insert(int index, Object obj) throws Exception {
// TODO Auto-generated method stub
if(index <0 ||index >size)
{
throw new Exception("參數錯誤!");
}
index(index-1);//定位到要操作結點的前一個結點對象。
current.setNext(new Node(obj,current.next));
size++;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return size==0;
}
@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}
}
3.測試類:(單鏈表的應用)
使用單鏈表建立一個,依次輸入十個0-99之間的隨機數,刪除第5個元素,打印輸出該線性表。
(4)Test.java:
public class Test {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
LinkList list = new LinkList();
for (int i = 0; i < 10; i++) {
int temp = ((int) (Math.random() * 100)) % 100;
list.insert(i, temp);
System.out.print(temp + " ");
}
list.delete(4);
System.out.println("\n------刪除第五個元素之後-------");
for (int i = 0; i < list.size; i++) {
System.out.print(list.get(i) + " ");
}
}
}
三、雙鏈表
鏈式結構的另一種實現就是雙向鏈表。雙向鏈表維護兩個引用:一個指向鏈表的第一個結點,另一個指向最後一個結點。鏈表中的每個結點存儲兩個引用:一個指向下一個結點,另一個指向前一個結點。
public class LinkList {
Node head;
Node tail;
int count;
public LinkList(Node head,Node tail,int count){
this.head=null;
this.tail=null;
this.count=0;
}
public LinkList(){
}
//尾插法添加節點
public void addHeadNode(NodeData data){
Node node = new Node(data,null,null);
if(head==null&&tail==null){
head=node;
head.setFront(null);
tail=node;
tail.setNext(null);
}else{
head.setFront(node);
node.setNext(head);
head=node;
head.setFront(null);
}
count++;
}
//頭插法添加節點
public void addTailNode(NodeData data){
Node node = new Node(data,null,null);
if(head==null&&tail==null){
head=node;
head.setFront(null);
tail=node;
tail.setNext(null);
}else{
tail.setNext(node);
node.setFront(tail);
tail=node;
tail.setNext(null);
}
count++;
}
//查找節點
public Node findNode(NodeData data){
Node temp=head;
if(head!=null){
while(temp!=null){
if(temp.data.compare(data)){
return temp;
}
temp=temp.getNext();
}
}
return null;
}
//刪除節點
public void delNode(NodeData data){
Node temp=findNode(data);
if(temp!=null){
if(temp.getFront()==null){
head=temp.getNext();
head.setFront(null);
}else if(temp.getNext()==null){
tail=tail.getFront();
tail.setNext(null);
}else{
temp.getFront().setNext(temp.getNext());
temp.getNext().setFront(temp.getFront());
}
count--;
}
}
//修改更新
public void updNode(NodeData data){
Node temp=findNode(data);
if(temp!=null){
temp.setNodeData(data);
}
}
//打印鏈表
public void printNode(){
Node temp = head;
while(temp!=null){
temp.print();
temp=temp.getNext();
}
}
}