十六、數據結構---鏈表

一、鏈表
 概念:
    鏈式存儲結構是基於指針實現的。我們把一個數據元素和一個指針稱爲結點。
    數據域:存儲數據元素信息的域。
    指針域:存儲直接後繼位置的域。
 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();
		}
	}
}















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章