数据结构和算法(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("[]");
        }
        
    }
}

可以发现相对於单向链表,不仅拥有单向链表的功能,还简化和扩展了其他操作

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