Java数据结构和算法(四)

Wesley13
• 阅读 714

日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要

插入索引后面的所以元素全部后移一位。

而本文会详细讲解链表,可以解决数组的部分问题,相比数组的大小不可更改,链表更加灵活,在学习LinkedList源码对链表有了一个大致的

了解。

ArrayList和LinkedList源码请参考:

Java集合(四)--基于JDK1.8的ArrayList源码解读

Java集合(五)--LinkedList源码解读

本文我们会学习:单链表、双端链表、有序链表、双向链表和有迭代器的链表,并且会讲解一下抽象数据类型(ADT)的思想,如何用 ADT 描述

栈和队列,如何用链表代替数组来实现栈和队列。

链节点:

在链表中,每个元素都被包含在链节点Link中。一个链节点是某个类的对象,这个类可以叫做Link。每个Link对象都包含对下一个Link引用的

字段(通常叫next)。但是链表本身有个字段指向对第一个Link的引用。

 Java数据结构和算法(四)

 代码示例:

public class Link {
    private Object data;
    private Link next;
}

单链表:

单链表的机构比较简单,每个Node包含data和next(指向下个Node),最后一个Node的next指向null

图例:

Java数据结构和算法(四)

代码实现:

public class SingleLinkList<E> {
    private int size;   //链表长度大小
    private Node head;  //头结点

    public SingleLinkList() {
        size = 0;
        head = null;
    }

    //添加元素到head
    public void addFirst(E data) {
        Node newNode = new Node(data);
        if (size == 0) {
            head = newNode;
        } else {
            newNode.next = head;
            head = newNode;
        }
        size++;
    }

    //删除头结点
    public E deleteFirst() {
        final E data = (E)head.data;
        head = head.next;
        size--;
        return data;
    }

    //查询某个元素是否存在
    public E find(E object) {
        Node current = head;
        int tempSize = size;
        while (tempSize > 0) {
            if (object == current.data) {
                return (E)current.data;
            } else {
                current = current.next;
            }
            tempSize--;
        }
        return null;
    }

    //删除链表中某个元素
    public boolean delete(E object) {
        if (null != object) {
            Node current = head;
            Node previous = head;
            while (!object.equals(current.data)) {
                if (current.next == null) {
                    return false;
                } else {
                    previous = current;
                    current = current.next;
                }
            }
            if (current == head) {
                head = current.next;
            } else {
                previous.next = current.next;
            }
            size--;
        }
        return true;

    }

    //遍历打印链表
    public void displayList() {
        Node current = head;
        int tempSize = size;
        if (tempSize == 0) {
            System.out.print("[]");
        } else {
            System.out.print("[");
            while (current != null) {
                if (current.next == null) {
                    System.out.print(current.data);;
                } else {
                    System.out.print(current.data + "-->");;
                }
                current = current.next;
            }
            System.out.println("]");
        }
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private static class Node<E>{
        E data;
        Node<E> next;
        Node(E data) {
            this.data = data;
        }
    }
}

public static void main(String[] args) {    SingleLinkList<Integer> singleList = new SingleLinkList<Integer>();    singleList.addFirst(22);   //添加节点    singleList.addFirst(44);    singleList.addFirst(66);    singleList.addFirst(88);    singleList.displayList();   //打印链表结构    singleList.delete(44);   //删除某个节点    singleList.displayList();    System.out.println(singleList.find(66));   //查询某个节点}

打印结果:
[88-->66-->44-->22]
[88-->66-->22]
66

双端链表:

双端链表和单向链表很相似,但是增加了一个新特性:就是对最后一个节点的引用,最后一个节点定义为tail

PS:双端链表不是双向链表,只能单向遍历,只是可以在双端添加/删除数据

图例:

Java数据结构和算法(四)

代码实现:

public class DoubleLinkList<E> {
    private int size;   //链表长度大小
    private Node head;  //头结点
    private Node tail;  //尾结点

    public DoubleLinkList() {
        size = 0;
        head = null;
        tail = null;
    }

    //添加元素到head
    public void addFirst(E data) {
        Node newNode = new Node(data);
        if (size == 0) {
            head = newNode;
            tail = newNode;
        } else {
            newNode.next = head;
            head = newNode;
        }
        size++;
    }

    //添加元素到tail
    public void addLast(E data) {
        Node newNode = new Node(data);
        if (size == 0) {
            head = newNode;
            tail = newNode;
        } else {
            tail.next = newNode;
            tail = newNode;
        }
        size++;
    }

    //删除头结点
    public E deleteFirst() {
        if (isEmpty()) {
            return null;
        }
        final E data = (E)head.data;
        if (head.next == null) {
            tail = null;
        }
        head = head.next;
        size--;
        return data;
    }

    //删除尾结点
    public E deleteLast() {
        if (isEmpty()) {
            return null;
        }
        final E data = (E)tail.data;
        if (head.next == null) {
            head = null;
        }
        int tempSize = size;
        Node current = head;
        Node previous = head;
        while (tempSize > 0) {
            if (current.next == null) {
                previous.next = null;
                break;
            }
            previous = current;
            current = current.next;
            tempSize--;
        }
        tail = previous;
        size--;
        return data;
    }

    //查询某个元素是否存在
    public E find(E object) {
        Node current = head;
        int tempSize = size;
        while (tempSize > 0) {
            if (object == current.data) {
                return (E)current.data;
            } else {
                current = current.next;
            }
            tempSize--;
        }
        return null;
    }

    //删除链表中某个元素
    public boolean delete(E object) {
        if (null != object) {
            Node current = head;
            Node previous = head;
            while (!object.equals(current.data)) {
                if (current.next == null) {
                    return false;
                } else {
                    previous = current;
                    current = current.next;
                }
            }
            if (current == head) {
                head = current.next;
            }else {
                previous.next = current.next;
            }
            size--;
        }
        return true;

    }

    //遍历打印链表
    public void displayList() {
        Node current = head;
        int tempSize = size;
        if (tempSize == 0) {
            System.out.print("[]");
        } else {
            System.out.print("[");
            while (current != null) {
                if (current == tail) {
                    System.out.print(current.data);
                    break;
                } else {
                    System.out.print(current.data + "-->");
                }
                current = current.next;
            }
            System.out.println("]");
        }
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private static class Node<E>{
        E data;
        Node<E> next;
        Node(E data) {
            this.data = data;
        }
    }
}

public static void main(String[] args) {
    DoubleLinkList<Integer> doubleLinkList = new DoubleLinkList<Integer>();
    doubleLinkList.addFirst(22);   //添加节点
    doubleLinkList.addFirst(44);
    doubleLinkList.addLast(66);
    doubleLinkList.addLast(88);
    doubleLinkList.addFirst(101);
    doubleLinkList.displayList();   //打印链表结构
    doubleLinkList.delete(44); //删除某个节点
    doubleLinkList.displayList();
    doubleLinkList.deleteLast();    //删除尾节点
    doubleLinkList.displayList();
    doubleLinkList.deleteFirst();   //删除头结点
    doubleLinkList.displayList();
    System.out.println(doubleLinkList.find(66));   //查询某个节点
}

输出结果:
[101-->44-->22-->66-->88]
[101-->22-->66-->88]
[101-->22-->66]
[22-->66]
66

链表的效率:

表头插入和删除的速度很快,时间复杂度O(1)

平均下来,定点插入、删除、查询都需要搜索链表中一半的节点,需要O(N)次比较,相比而言,数组执行这些操作也需要O(N)次比较,但是

链表不需要移动数据,只需要改变前后引用,而数组只能整体复制,效率会好很多

链表的另一个优点体现在内存使用上,需要多少内存就使用多少内存,而数组一开始的内存空间都是确定的

有序链表:

对于某些应用来说,在链表中保持数据的有序很很有用的。有序链表中,数据都是按照关键值有序排列的。

在大多数使用有序数组的场景也可以使用有序链表,在插入速度方面有很大优势

有序链表和一般单向链表只是添加方法有区别,其余方法都是相同的

代码示例:

public class SortedLinkList {
    private int size;   //链表长度大小
    private Node head;  //头结点

    public SortedLinkList() {
        size = 0;
        head = null;
    }

    //添加元素
    public void add(int data) {
        Node newNode = new Node(data);
        Node previoue = null;
        Node current = head;
        while (current != null && data > current.data) {
            previoue = current;
            current = current.next;
        }
        if (previoue == null) {
            head = newNode;
            head.next = current;
        } else {
            previoue.next = newNode;
            newNode.next = current;
        }
        size++;
    }

    //删除头结点
    public int deleteFirst() {
        final int data = head.data;
        head = head.next;
        size--;
        return data;
    }

    //查询某个元素是否存在
    public int find(int object) {
        Node current = head;
        int tempSize = size;
        while (tempSize > 0) {
            if (object == current.data) {
                return current.data;
            } else {
                current = current.next;
            }
            tempSize--;
        }
        return -1;
    }

    //删除链表中某个元素
    public boolean delete(int object) {
        Node current = head;
        Node previous = head;
        while (object != current.data) {
            if (current.next == null) {
                return false;
            } else {
                previous = current;
                current = current.next;
            }
        }
        if (current == head) {
            head = current.next;
        } else {
            previous.next = current.next;
        }
        size--;
        return true;

    }

    //遍历打印链表
    public void displayList() {
        Node current = head;
        int tempSize = size;
        if (tempSize == 0) {
            System.out.print("[]");
        } else {
            System.out.print("[");
            while (current != null) {
                if (current.next == null) {
                    System.out.print(current.data);;
                } else {
                    System.out.print(current.data + "-->");;
                }
                current = current.next;
            }
            System.out.println("]");
        }
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private static class Node{
        int data;
        Node next;
        Node(int data) {
            this.data = data;
        }
    }
}

public static void main(String[] args) {
    SortedLinkList sortedLinkList = new SortedLinkList();
    sortedLinkList.add(5);   //添加节点
    sortedLinkList.add(1);
    sortedLinkList.add(8);
    sortedLinkList.add(2);
    sortedLinkList.add(101);
    sortedLinkList.displayList();   //打印链表结构
    sortedLinkList.delete(8); //删除某个节点
    sortedLinkList.displayList();
    sortedLinkList.deleteFirst();   //删除头结点
    sortedLinkList.displayList();
    System.out.println(sortedLinkList.find(101));   //查询某个节点
}

输出结果:
[1-->2-->5-->8-->101]
[1-->2-->5-->101]
[2-->5-->101]
101

双向链表:

双向链表就是为了解决单向链表只能单向遍历而效率慢的问题,因为只能current=current.next进行遍历,只能获得下一个节点,而不能

获取上一个节点

在学习LinkedList源码的时候,我们已经详细了解过双向链表了,Java集合(五)--LinkedList源码解读

图例:

Java数据结构和算法(四)

代码示例:

public class DoubleLinkedList<E> {
    private int size;   //链表长度大小
    private Node head;  //头结点
    private Node tail;  //尾结点

    public DoubleLinkedList() {
        size = 0;
        head = null;
        tail = null;
    }

    //添加元素到head
    public void addFirst(E data) {
        Node newNode = new Node(data);
        if (size == 0) {
            tail = newNode;
        } else {
            head.previous = newNode;
            newNode.next = head;
        }
        head = newNode;
        size++;
    }

    //添加元素到tail
    public void addLast(E data) {
        Node newNode = new Node(data);
        if (size == 0) {
            head = newNode;
        } else {
            tail.next = newNode;
            newNode.previous = tail;
        }
        tail = newNode;
        size++;
    }

    //删除头结点
    public E deleteFirst() {
        if (isEmpty()) {
            return null;
        }
        final E data = (E)head.data;
        if (head.next == null) {
            tail = null;
        }
        head = head.next;
        head.previous = null;
        size--;
        return data;
    }

    //删除尾结点
    public E deleteLast() {
        if (isEmpty()) {
            return null;
        }
        final E data = (E)tail.data;
        if (head.next == null) {
            head = null;
        }
        tail = tail.previous;
        tail.next = null;
        size--;
        return data;
    }

    //查询某个元素是否存在
    /*public E find(E object) {
        if (object == null) {
            for (Node<E> x = head; x != null; x = x.next) {
                if (x.data == null) {
                    return x.data;
                }
            }
        } else {
            for (Node<E> x = head; x != null; x = x.next) {
                if (object.equals(x.data)) {
                    return x.data;
                }
            }
        }
        return null;
    }*/

    //查询某个索引下标的数据
    public E find(int index) {
        return (E)node(index).data;
    }

    //删除链表中某个元素
    public boolean delete(E object) {
        if (object == null) {
            for (Node<E> x = head; x != null; x = x.next) {
                if (x.data == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = head; x != null; x = x.next) {
                if (object.equals(x.data)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;

    }

    E unlink(Node<E> x) {
        final E element = x.data;
        final Node<E> next = x.next;
        final Node<E> prev = x.previous;

        if (prev == null) {
            head = next;
        } else {
            prev.next = next;
            x.previous = null;
        }

        if (next == null) {
            tail = prev;
        } else {
            next.previous = prev;
            x.next = null;
        }

        x.data = null;
        size--;
        return element;
    }

    Node node(int index) {
        if (index < (size >> 1)) {
            Node x = head;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = tail;
            for (int i = size - 1; i > index; i--)
                x = x.previous;
            return x;
        }
    }

    //遍历打印链表
    public void displayList() {
        Node current = head;
        int tempSize = size;
        if (tempSize == 0) {
            System.out.print("[]");
        } else {
            System.out.print("[");
            while (current != null) {
                if (current == tail) {
                    System.out.print(current.data);
                    break;
                } else {
                    System.out.print(current.data + "-->");
                }
                current = current.next;
            }
            System.out.println("]");
        }
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private static class Node<E>{
        E data;
        Node<E> previous;
        Node<E> next;
        Node(E data) {
            this.data = data;
        }
    }
}

public static void main(String[] args) {
    DoubleLinkedList<Integer> doubleLinkList = new DoubleLinkedList<Integer>();
    doubleLinkList.addFirst(22);   //添加节点
    doubleLinkList.addFirst(44);
    doubleLinkList.addLast(66);
    doubleLinkList.addLast(88);
    doubleLinkList.addFirst(101);
    doubleLinkList.displayList();   //打印链表结构
    doubleLinkList.delete(44); //删除某个节点
    doubleLinkList.displayList();
    doubleLinkList.deleteLast();    //删除尾节点
    doubleLinkList.displayList();
    doubleLinkList.deleteFirst();   //删除头结点
    doubleLinkList.displayList();
    System.out.println(doubleLinkList.find(66));   //查询某个节点
    System.out.println(doubleLinkList.find(33));   //查询某个节点
}

输出结果:
[101-->44-->22-->66-->88]
[101-->22-->66-->88]
[101-->22-->66]
[22-->66]
66

拓展1、用链表实现:

public class MyStackByLinkedList<E> {
    private SingleLinkList linkList;

    public MyStackByLinkedList() {
        linkList = new SingleLinkList();
    }

    public void push(E e) {
        linkList.addFirst(e);
    }

    public E pop(){
        E e = (E)linkList.deleteFirst();
        return e;
    }

}

拓展2:用双端链表实现队列

public class MyQueueByLinkedList<E> {
    private DoubleLinkedList linkedList;

    public MyQueueByLinkedList() {
        linkedList = new DoubleLinkedList();
    }

    public void add(E e) {
        linkedList.addFirst(e);
    }

    //移除数据
    public E remove(){
        return (E)linkedList.deleteFirst();
    }

}

内容参考:<Java数据结构和算法>

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
50道Java集合经典面试题(收藏版)
前言来了来了,50道Java集合面试题来了!1\.Arraylist与LinkedList区别可以从它们的底层数据结构、效率、开销进行阐述哈ArrayList是数组的数据结构,LinkedList是链表的数据结构。随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这