數據結構和算法(6)-----鏈表

1.鏈表和數組的比較

1).數組作爲數據存儲的一種結構,有一定的缺陷,比如無序數組搜索效率低,有序數組插入效率低,兩者刪除效率都比較低。而且在創建數組的時候需要指定數組的大小,如果無法提前預知大小,數組的動態擴展也是件麻煩事(netty的bytebuf是動態擴展的數組,有時間可以看看怎麼實現的),給的值足夠大的話,會造成不必要的內存開銷。那麼鏈表呢,可以有效解決擴容的問題。

2).數組可以用來實現棧、隊列(例如PriorityBlokingQueue內部用的就是一個Object[])等其他數據結構,是一種通用數據結構。鏈表也是一種通用數據結構,同樣也可以實現棧和隊列。

3).頻繁的通過下標來訪問數據,顯然數組的優勢更好

2.鏈表的種類

單向鏈表、雙端鏈表、有序鏈表、雙向鏈表、循環鏈表。我們就每種鏈表研究下

3.鏈表定義

鏈表(Linked List):一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針連接次序決定的。鏈表由一系列結點組成,結點可以在運行時動態生成。結點由兩部分組成:一個是存儲數據的數據域,一個是存儲下一個結點的指針域。

4.單向鏈表(Single Linked List)

單向鏈表的結點包含兩部分:一部分是存儲數據,一部分是存儲下一個結點的位置;最後一個結點存儲的位置是null。單向鏈表的結構導致只能從一個方向開始遍歷,查找一個結點,一般從第一個結點開始向下搜尋,直到搜尋到想要的結點;刪除一個結點,先找到這個結點的上一個結點和下一個結點,然後將該結點的上一下結點指向該節點的下一個節點;增加一個結點,一般只提供在表頭插入(在尾部插入也是可以實現,不過需要從頭開始遍歷結點,找到尾部結點,然後該尾部結點指向新的結點。在其他位置插入,因爲沒有下標,無法指定位置,只能是一定規則隨機插入)

在表頭增加節點:

刪除節點:

4.1單向鏈表的代碼實現:

public class SingleLinkedList {
    private int size;//鏈表節點的個數
    private Node head;//頭節點
    
    public SingleLinkedList(){
        size = 0;
        head = null;
    }
    
    //鏈表的每個節點類
    private class Node{
        private Object data;//每個節點的數據
        private Node next;//每個節點指向下一個節點的連接
        
        public Node(Object data){
            this.data = data;
        }
    }
    
    //在鏈表頭添加元素
    public Object addHead(Object obj){
        Node newHead = new Node(obj);
        if(size == 0){
            head = newHead;
        }else{
            newHead.next = head;
            head = newHead;
        }
        size++;
        return obj;
    }
    
    //在鏈表頭刪除元素
    public Object deleteHead(){
        Object obj = head.data;
        head = head.next;
        size--;
        return obj;
    }
    
    //查找指定元素,找到了返回節點Node,找不到返回null
    public Node find(Object obj){
        Node current = head;
        int tempSize = size;
        while(tempSize > 0){
            if(obj.equals(current.data)){
                return current;
            }else{
                current = current.next;
            }
            tempSize--;
        }
        return null;
    }
    
    //刪除指定的元素,刪除成功返回true
    public boolean delete(Object value){
        if(size == 0){
            return false;
        }
        Node current = head;
        Node previous = head;
        while(current.data != value){
            if(current.next == null){
                return false;
            }else{
                previous = current;
                current = current.next;
            }
        }
        //如果刪除的節點是第一個節點
        if(current == head){
            head = current.next;
            size--;
        }else{//刪除的節點不是第一個節點
            previous.next = current.next;
            size--;
        }
        return true;
    }
    
    //判斷鏈表是否爲空
    public boolean isEmpty(){
        return (size == 0);
    }
    
    //顯示節點信息
    public void display(){
        if(size >0){
            Node node = head;
            int tempSize = size;
            if(tempSize == 1){//當前鏈表只有一個節點
                System.out.println("["+node.data+"]");
                return;
            }
            while(tempSize>0){
                if(node.equals(head)){
                    System.out.print("["+node.data+"->");
                }else if(node.next == null){
                    System.out.print(node.data+"]");
                }else{
                    System.out.print(node.data+"->");
                }
                node = node.next;
                tempSize--;
            }
            System.out.println();
        }else{//如果鏈表一個節點都沒有,直接打印[]
            System.out.println("[]");
        }
        
    }

}

測試:

@Test
public void testSingleLinkedList(){
    SingleLinkedList singleList = new SingleLinkedList();
    singleList.addHead("A");
    singleList.addHead("B");
    singleList.addHead("C");
    singleList.addHead("D");
    //打印當前鏈表信息
    singleList.display();
    //刪除C
    singleList.delete("C");
    singleList.display();
    //查找B
    System.out.println(singleList.find("B"));
}

打印結果:

4.2單向鏈表實現棧

棧的pop()方法和push()方法,對應於鏈表的在頭部刪除元素deleteHead()以及在頭部增加元素addHead()。

public class StackSingleLink {
    private SingleLinkedList link;
    
    public StackSingleLink(){
        link = new SingleLinkedList();
    }
    
    //添加元素
    public void push(Object obj){
        link.addHead(obj);
    }
    
    //移除棧頂元素
    public Object pop(){
        Object obj = link.deleteHead();
        return obj;
    }
    
    //判斷是否爲空
    public boolean isEmpty(){
        return link.isEmpty();
    }
    
    //打印棧內元素信息
    public void display(){
        link.display();
    }

}

5.雙端鏈表

單向鏈表,如果我們想要在尾部插入一個結點,那麼只能從頭部結點開始遍歷,找到最後一個結點,然後尾部結點指向新的結點,我們發現,這樣性能會很低。如果我們在設計鏈表的時候多一個對尾部結點的引用,向上面的操作,會變得特別簡單。

5.1雙端鏈表的代碼實現:

public class DoublePointLinkedList {
    private Node head;//頭節點
    private Node tail;//尾節點
    private int size;//節點的個數
    
    private class Node{
        private Object data;
        private Node next;
        
        public Node(Object data){
            this.data = data;
        }
    }
    
    public DoublePointLinkedList(){
        size = 0;
        head = null;
        tail = null;
    }
    
    //鏈表頭新增節點
    public void addHead(Object data){
        Node node = new Node(data);
        if(size == 0){//如果鏈表爲空,那麼頭節點和尾節點都是該新增節點
            head = node;
            tail = node;
            size++;
        }else{
            node.next = head;
            head = node;
            size++;
        }
    }
    
    //鏈表尾新增節點
    public void addTail(Object data){
        Node node = new Node(data);
        if(size == 0){//如果鏈表爲空,那麼頭節點和尾節點都是該新增節點
            head = node;
            tail = node;
            size++;
        }else{
            tail.next = node;
            tail = node;
            size++;
        }
    }
    
    //刪除頭部節點,成功返回true,失敗返回false
    public boolean deleteHead(){
        if(size == 0){//當前鏈表節點數爲0
            return false;
        }
        if(head.next == null){//當前鏈表節點數爲1
            head = null;
            tail = null;
        }else{
            head = head.next;
        }
        size--;
        return true;
    }
    //判斷是否爲空
    public boolean isEmpty(){
        return (size ==0);
    }
    //獲得鏈表的節點個數
    public int getSize(){
        return size;
    }
    
    //顯示節點信息
    public void display(){
        if(size >0){
            Node node = head;
            int tempSize = size;
            if(tempSize == 1){//當前鏈表只有一個節點
                System.out.println("["+node.data+"]");
                return;
            }
            while(tempSize>0){
                if(node.equals(head)){
                    System.out.print("["+node.data+"->");
                }else if(node.next == null){
                    System.out.print(node.data+"]");
                }else{
                    System.out.print(node.data+"->");
                }
                node = node.next;
                tempSize--;
            }
            System.out.println();
        }else{//如果鏈表一個節點都沒有,直接打印[]
            System.out.println("[]");
        }
    }

}

6.雙向鏈表

單向鏈表只能從一個方便遍歷,那麼雙向鏈表可以從兩個方向遍歷

6.1雙向鏈表的代碼實現:

public class TwoWayLinkedList {
    private Node head;//表示鏈表頭
    private Node tail;//表示鏈表尾
    private int size;//表示鏈表的節點個數
    
    private class Node{
        private Object data;
        private Node next;
        private Node prev;
        
        public Node(Object data){
            this.data = data;
        }
    }
    
    public TwoWayLinkedList(){
        size = 0;
        head = null;
        tail = null;
    }
    
    //在鏈表頭增加節點
    public void addHead(Object value){
        Node newNode = new Node(value);
        if(size == 0){
            head = newNode;
            tail = newNode;
            size++;
        }else{
            head.prev = newNode;
            newNode.next = head;
            head = newNode;
            size++;
        }
    }
    
    //在鏈表尾增加節點
    public void addTail(Object value){
        Node newNode = new Node(value);
        if(size == 0){
            head = newNode;
            tail = newNode;
            size++;
        }else{
            newNode.prev = tail;
            tail.next = newNode;
            tail = newNode;
            size++;
        }
    }
    
    //刪除鏈表頭
    public Node deleteHead(){
        Node temp = head;
        if(size != 0){
            head = head.next;
            head.prev = null;
            size--;
        }
        return temp;
    }
    
    //刪除鏈表尾
    public Node deleteTail(){
        Node temp = tail;
        if(size != 0){
            tail = tail.prev;
            tail.next = null;
            size--;
        }
        return temp;
    }
    
    //獲得鏈表的節點個數
    public int getSize(){
        return size;
    }
    //判斷鏈表是否爲空
    public boolean isEmpty(){
        return (size == 0);
    }
    
    //顯示節點信息
    public void display(){
        if(size >0){
            Node node = head;
            int tempSize = size;
            if(tempSize == 1){//當前鏈表只有一個節點
                System.out.println("["+node.data+"]");
                return;
            }
            while(tempSize>0){
                if(node.equals(head)){
                    System.out.print("["+node.data+"->");
                }else if(node.next == null){
                    System.out.print(node.data+"]");
                }else{
                    System.out.print(node.data+"->");
                }
                node = node.next;
                tempSize--;
            }
            System.out.println();
        }else{//如果鏈表一個節點都沒有,直接打印[]
            System.out.println("[]");
        }
        
    }
}

可以發現相對於單向鏈表,不僅擁有單向鏈表的功能,還簡化和擴展了其他操作

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