1. 程式人生 > >java對單向單向連結串列的操作

java對單向單向連結串列的操作

概述:眾所周知,資料對於資料的儲存時連續的,也就是說在計算機的記憶體中是一個整體的、連續的、不間斷的ADT資料結構。伴隨的問題也會隨之出現,這樣其實對於記憶體的動態分配是不靈活的。而連結串列具備這個優點。因此連結串列對於資料的插入和刪除是方便的,但是對於資料的查詢是麻煩的。因為需要遍歷連結串列,而對於連結串列的遍歷確實極度的麻煩。

1 單向連結串列的定義

連結串列主要用來儲存引用型別的資料。其結構可以由下圖清楚的表示:
這裡寫圖片描述
連結串列結點的定義

class Node{
    // 連結串列中儲存的資料
    public Object obj;
    // 下一個結點的應用
public Node next; // 該結點中儲存的資料 public Node(Object obj){ this.obj=obj; } public void setNext(Node next){ this.next=next; } public Node getNext(){ return this.next; } public Object getObj(){ return this.obj; } }

連結串列的設定和取出

/**
 * 對連結串列設定資料和取出資料
 */
public class LinkDemo{ public void static main(String[] args){ //設定資料 Node root= new Node("連結串列頭結點"); Node n1=new Node("連結串列1"); Node n2=new Node("連結串列2"); Node n3=new Node("連結串列3"); // 維護連結串列的關係 root.setNext(n1); n1.setNext(n2); n2.setNext(n3); // 取出連結串列的資料
Node currentNode=root;// 建立當前結點物件,將根結點賦值給當前結點 // 判斷當前結點是不是為null,如果當前結點為null,則終止迴圈,否則繼續輸出 while(currentNode != null){ // 打印出當前結點的資料,然後修改當前結點的引用 System.out.println(currentNode.getObj()); // 將下一個結點設定為當前結點 currentNode=currentNode.getNext(); } } }

結果測試如下:
這裡寫圖片描述
使用遞迴的方式優化輸出:

    /**
        *對連結串列設定資料和取出資料
     */
public class LinkDemo{
    public void static main(String[] args){
        //設定資料
        Node root= new Node("連結串列頭結點");
        Node n1=new Node("連結串列1");
        Node n2=new Node("連結串列2");
        Node n3=new Node("連結串列3");
        // 維護連結串列的關係
        root.setNext(n1);
        n1.setNext(n2);
        n2.setNext(n3);
        // 取出連結串列的資料
        Node currentNode=root;// 建立當前結點物件,將根結點賦值給當前結點
        // 判斷當前結點是不是為null,如果當前結點為null,則終止迴圈,否則繼續輸出
        print(root);
    }
    public static print(Node node){
        //遞迴的結束條件
        if(node == null){
            return;
        }else{
            System.out.println(node.getObj());
            print(node.getNext());
        }
    }
}

在實際的開發中,我們希望對於資料的儲存和輸出應該是由如下的形式出現的:

public class LinkDemo3{
    public static void main(){
        Link link=new Link();
        link.add("A");
        link.add("B");
        link.add("C");
        link.add("D");
        // 展示所有的資料
        link.print();
    }
}

此時我們希望讓Node結點類來負責對結點的操作,而Link類類負責對資料的操作。

// 節點類物件
class Node{
    // 結點中的資料
    public Object obj;
    // 結點的下一個引用
    public Node next;
    // 結點中儲存資料
    public Node(Object obj){
        return this.obj=obj;
    }
    public Node getNext(){
        return this.next;
    }
    public void setNext(Node next){
        this.next=next;
    }
    public Object getObj(){
        return this.obj;
    }
    // 新增結點
    public void addNode(Node node){
        // 對於新增結點來說,首先判斷頭節點是否存在下一個的引用,如果為null,此時就可以新增
        // 第一次呼叫該方法,this表示的就是Link.root物件
        if(this.next==null){
            //為空,那麼將該結點掛到頭結點的下一個引用上
            this.next=node;
        }else{
            //頭節點的下一個引用不為空,那麼應該是將當前結點的下一個引用為此node結點
            //遞迴迴圈
            this.next.addNode(node);
        }
    }    
    // 展示所有的結點
    public void printNode(){
        System.out.println(this.obj);
        if(this.next!=null){
            // 如果當前結點對於下一個結點的引用不為空,那麼當前結點物件遞迴呼叫列印方法。
            // 當前結點的下一個引用this.next
            this.next.printNode();
        }
    }
}
// 對連結串列的操作
class Link{
    // 初始化結點物件,即頭結點
    public Node root;
    // 新增資料
    public void add(Object obj){
        //建立當前結點物件,然後儲存資料
        Node currentNode=new Node(obj);
        // 如果要新增資料,就要首先判斷根結點是否為空
        if(root==null){
            //頭節點為空,則將建立的當前結點賦值給根結點
            root=currentNode;
        }else{
            //如果頭結點不為空,此時應該由結點自己來判斷
            //頭節點不為空值,要儲存的資料應該在之後的結點上,呼叫新增結點的方法
            root.addNode(currentNode);
        }
    }
    // 展示所有的資料
    public void print(){
            if(root!=null){
                root.printNode();
            }
    }
}

當我們建立結點物件的時候,對於儲存資料和輸出輸出,我們每次都會判斷根節點是不是空值。這個很重要。當前結點不是根節點這個應該注意到。進一步的優化,注意到我們不希望呼叫者直接操作結點物件,單純的Node類會被直接操作,這樣不符合Java的封裝思想,我們可以使用內部類,並且直接對Node內部類私有化

// 對連結串列的操作
class Link{
    // 初始化結點物件,即頭結點
    public Node root;
    // 新增資料
    public void add(Object obj){
        //建立當前結點物件,然後儲存資料
        Node currentNode=new Node(obj);
        // 如果要新增資料,就要首先判斷根結點是否為空
        if(root==null){
            //頭節點為空,則將建立的當前結點賦值給根結點
            root=currentNode;
        }else{
            //如果頭結點不為空,此時應該由結點自己來判斷
            //頭節點不為空值,要儲存的資料應該在之後的結點上,呼叫新增結點的方法
            root.addNode(currentNode);
        }
    }
    // 展示所有的資料
    public void print(){
            if(root!=null){
                root.printNode();
            }
    }
    // 節點類物件
    private class Node{
        // 結點中的資料
        public Object obj;
        // 結點的下一個引用
        public Node next;
        // 結點中儲存資料
        public Node(Object obj){
            return this.obj=obj;
        }
        // 新增結點
        public void addNode(Node node){
            // 對於新增結點來說,首先判斷頭節點是否存在下一個的引用,如果為null,此時就可以新增
            // 第一次呼叫該方法,this表示的就是Link.root物件
            if(this.next==null){
                //為空,那麼將該結點掛到頭結點的下一個引用上
                this.next=node;
            }else{
                //頭節點的下一個引用不為空,那麼應該是將當前結點的下一個引用為此node結點
                //遞迴迴圈
                this.next.addNode(node);
            }
        }   
        // 展示所有的結點
        public void printNode(){
            System.out.println(this.obj);
            if(this.next!=null){
                // 如果當前結點對於下一個結點的引用不為空,那麼當前結點物件遞迴呼叫列印方法。
                // 當前結點的下一個引用this.next
                this.next.printNode();
            }
        }
    }
}

確定連結串列的資料結構

class Link {
    // 需要結點物件
    private Node root;//根節點物件
    // 結點物件
    //***************內部類*******************
    private class Node {
        private Object obj;//結點中儲存的資料
        private Node next;//下一個結點的引用

        // 結點中的資料
        public Node(Object obj) {
            this.obj = obj;
        }
    }
    //***************內部類*******************
}

新增資料 public void add(Object obj);

class Link {
    // 需要結點物件
    private Node root;//根節點物件
    // 結點物件
    //***************內部類*******************
    private class Node {
        private Object obj;//結點中儲存的資料
        private Node next;//下一個結點的引用

        // 結點中的資料
        public Node(Object obj) {
            this.obj = obj;
        }
        //結點的新增
        public void addNode(Node node){
            if(this.next==null){
            //把當前結點賦值給this.next
                this.next=node;
            }else{
                //新增一個結點
                this.next.addNode(node);
            }
        }
    }
    //***************內部類*******************
    public void add(Object obj){
        //對於資料為null,是可以儲存的,在這裡我假設資料為null是不可以儲存的
        if(obj==null){
            return;
        }
        Node node=new Node(obj);
        if(this.root==null){
            root=node;
        }else{
            //注意:Link類負責根節點的維護和結點的建立,對於結點的具體操作,應該是Node類操作
            this.root.addNode(node);
        }
    }
}

獲取連結串列的長度 public int size();

每一次的資料儲存都需要長度加1.因此我們在Link類中新增組成員變數size.儲存時讓它自增運算;

class Link {
    // 需要結點物件
    private Node root;//根節點物件
    private int size;//連結串列的長度
    // 結點物件
    //***************內部類*******************
    private class Node {
        private Object obj;//結點中儲存的資料
        private Node next;//下一個結點的引用

        // 結點中的資料
        public Node(Object obj) {
            this.obj = obj;
        }
        //結點的新增
        public void addNode(Node node){
            if(this.next==null){
            //把當前結點賦值給this.next
                this.next=node;
            }else{
                //新增一個結點
                this.next.addNode(node);
            }
        }
    }
    //***************內部類*******************
    /**資料的新增*/
    public void add(Object obj){
        //對於資料為null,是可以儲存的,在這裡我假設資料為null是不可以儲存的
        if(obj==null){
            return;
        }
        Node node=new Node(obj);
        if(this.root==null){
            root=node;
        }else{
            //注意:Link類負責根節點的維護和結點的建立,對於結點的具體操作,應該是Node類操作
            this.root.addNode(node);
        }
        this.size++;//長度自增運算
    }
    /**
        *連結串列的長度
        */
    public int size(){
        return this.size;
    }

}

判斷連結串列是否為null,public boolean isEmpty();

  1. 原理1:如果root為null,則連結串列的長度為null

  2. 原理2:如果連結串列的size==0.則連結串列的長度為空

class Link {
    // 需要結點物件
    private Node root;//根節點物件
    private int size;//連結串列的長度
    // 結點物件
    //***************內部類*******************
    private class Node {
        private Object obj;//結點中儲存的資料
        private Node next;//下一個結點的引用

        // 結點中的資料
        public Node(Object obj) {
            this.obj = obj;
        }
        //結點的新增
        public void addNode(Node node){
            if(this.next==null){
            //把當前結點賦值給this.next
                this.next=node;
            }else{
                //新增一個結點
                this.next.addNode(node);
            }
        }
    }
    //***************內部類*******************
    /**資料的新增*/
    public void add(Object obj){
        //對於資料為null,是可以儲存的,在這裡我假設資料為null是不可以儲存的
        if(obj==null){
            return;
        }
        Node node=new Node(obj);
        if(this.root==null){
            root=node;
        }else{
            //注意:Link類負責根節點的維護和結點的建立,對於結點的具體操作,應該是Node類操作
            this.root.addNode(node);
        }
        this.size++;//長度自增運算
    }
    /**
        *連結串列的長度
        */
    public int size(){
        return this.size;
    }
    /**
        *判讀連結串列是否為空
    */
    public boolean isEmpty(){
        return this.size==0?true:flase;
    } 
}

判斷連結串列中是否存在某個元素 public boolean contains(Object obj);

物件的匹配可以使用equals()方法,但是對於自定義物件而言,需要寫compare()方法來自定義匹配結果。
這裡寫圖片描述

class Link {
    // 需要結點物件
    private Node root;//根節點物件
    private int size;//連結串列的長度

    public void add(Object obj) {
        // 建立結點物件,並且儲存資料
        Node newNode = new Node(obj);
        if (root == null) {
            root = newNode;
        } else {
            //此時根節點不為空,需要新增結點
            root.addNode(newNode);
        }
        this.size++;//每次儲存資料,連結串列自增
    }

    /**
     * 連結串列的長度
     * @return
     */
    public int size(){
        return this.size;
    }

    /**
     * 判斷連結串列是否為空
     * @return
     */
    public boolean isEmpty(){
       return this.size==0?true:false;
    }
    public boolean contains(Object obj){
        if(obj==null || this.root==null){
            return false;
        }else {
            // 此時的判斷應該交給Node結點完成
            return root.containsNode(obj);
        }
    }

    public void print() {
        if (root != null) {
            root.printNode();
        }
    }
    private class Node {
        private Object obj;//結點中儲存的資料
        private Node next;//下一個結點的引用

        // 結點中的資料
        public Node(Object obj) {
            this.obj = obj;
        }
        // 結點的儲存
        public void addNode(Node node) {
            if (this.next == null) {//當前結點的下一個結點為空,此時就可以新增結點
                this.next = node;
            } else {
                //否則,此時應該迴圈遞迴新增結點
                // 當前結點物件應該新增新的結點
                this.next.addNode(node);
            }
        }

        // 輸出一個結點
        public void printNode() {
            //列印資料
            System.out.println(this.obj);
            if (this.next != null) {
                this.next.printNode();//遞迴呼叫輸出
            }
        }

        /**
         * 判斷連結串列中受否包含某個元素
         * @param obj
         * @return
         */
        public boolean containsNode(Object obj) {
            if(this.next==null){
                return false;
            }else {
                // 連結串列結點不為空。此時要判斷元素是否匹配
                if(this.obj.equals(obj)){
                    return true;
                }else {
                    return this.next.containsNode(obj);
                }
            }
        }
    }
}

根據索引查詢元素

然後定義get方法;

  1. 查詢有多次,但是每一次的查詢都要將foot屬性設定為0;
  2. 如果當前查詢的索引大於Link類的編號size.此時查詢不到
public Object get(int index){
        //如果當前要查詢的索引大於連結串列的額size.那麼直接返回null
        if(index>this.size){
            return null;
        }
        // 每一的查詢都要將foot從0開始
        this.foot=0;
        // 此後交給Node結點來判斷
        return this.root.getNode(index);
}

getNode(index)的實現

public Object getNode(int index) {
            //外部類Link呼叫內部物件this.foot,外部類直接對內部類的訪問
            // 如果當前結點的編號自增==當前索引
            if(Link.this.foot++==index){
                // 返回資料
                return this.obj;
            }else {
                return this.next.getNode(index);
            }
}

修改連結串列元素,和上述的查詢實現基本一致 public void set(int index,Object obj);

public void  set(int index,Object obj){
        if(index>this.size){
            return;
        }
        this.foot=0;
        this.root.setNode(index,obj);
}
public void setNode(int index, Object obj) {
            if(Link.this.foot++==index){
                this.obj=obj;//資料的設定
            }else {
                this.next.setNode(index,obj);
            }
}

總結

NO 方法名稱 型別 備註
1 public void add(Object obj) 普通方法 向連結串列之中新增數
2 public int size() 普通方法 取得連結串列的長度
3 public boolean isEmpty() 普通方法 判斷連結串列是否為空
4 public boolean contains(Object obj) 普通方法 判斷連結串列是否存在某個元素
3 public Object get(int index) 普通方法 根據連結串列索引查詢某個元素
3 public void set(int index,Object obj) 普通方法 修改某個元素