JAVA数据结构——链表

832 阅读5分钟

1.我们知道数组是连续存储,这样一来会大大降低内存使用率,而如果采用链表的话,由于链表是采用分散存储,这样就大大提升了内存使用率,而且在数组中的元素叫做元素,而在链表中的元素叫做节点。而元素和节点的差距就在于——节点不仅包括节点内容,还包括了下一个节点的地址。如图所示 image.png 2.那么我想手搓一个链表的话,就直接看代码吧。

package com.example.main;
//首先创建一个链表类。
public class Node {
    private int content;
    private Node next;

    public Node(int content, Node next) {
        this.content = content;
        this.next = next;
    }

    public int getContent() {
        return content;
    }

    public void setContent(int content) {
        this.content = content;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}

以下就是主方法创建一个包含9,2,4三个数字的链表,并且返回链表中的第一个节点。

package com.example.main;

public class Main {
    public static Node CreateLinkedList(int[] array){
        //首先链表的根节点肯定是空的,所以先创建一个根节点。
        Node root = null;
        //然后就是从末尾元素依次开始创建Node节点。
        for (int i = array.length - 1; i >= 0; i--) {
            Node node = new Node(array[i],null);
            //将链表连接。
            node.setNext(root);
            root = node;
        }
        return root;
    }
    public static void main(String[] args) {
        int[] array = new int[]{9,2,4};
        //打印第一个链表的节点。
        System.out.println(CreateLinkedList(array).getContent());
        //打印第二个,可以以此类推。
        System.out.println(CreateLinkedList(array).getNext().getContent());
    }
}

3.我们创建了链表后呢,就该实现链表的一些基本功能了,最常见的肯定就是读取查找功能了。直接看代码吧:

package com.example.main;

public class Main {
    Node root = null;
    int size = 0;
    public Main(int[] array){
        this.root = CreateLinkedList(array);
        this.size = array.length;
    }
    public static Node CreateLinkedList(int[] array){
        //首先链表的根节点肯定是空的,所以先创建一个根节点。
        Node root = null;
        //然后就是从末尾元素依次开始创建Node节点。
        for (int i = array.length - 1; i >= 0; i--) {
            Node node = new Node(array[i],null);
            //将链表连接。
            node.setNext(root);
            root = node;
        }
        return root;
    }
    //获取长度。
    public int getSize(){
        return this.size;
    }
    //获取某个索引节点内容
    public int getContent(int index){
        Node node = this.root;
        for (int i = 0; i < index; i++) {
            node = node.getNext();
        }
        return node.getContent();
    }
    //查找某个值的索引值,默认不存在为-1。
    public int getIndex(int content){
        if (this.root == null){
            return -1;
        }
        Node node = this.root;
        int index = 0;
        while (index <= this.size - 1){
            if (node.getContent() == content){
                return index;
            }
            node = node.getNext();
            index++;
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] array = {9, 2, 4};
        Main linkedList = new Main(array);
        System.out.println(linkedList.getIndex(2));
        System.out.println(linkedList.getIndex(5));
        System.out.println(linkedList.getContent(2));
    }
}

4.进阶还要有一些插入删除功能,废话不多说,咱们直接看代码:

package com.example.main;

public class Main {
    private Node root = null;
    int size = 0;
    public Main(){}
    public Main(int[] array){
        this.root = CreateLinkedList(array);
        this.size = array.length;
    }
    public static Node CreateLinkedList(int[] array){
        //首先链表的根节点肯定是空的,所以先创建一个根节点。
        Node root = null;
        //然后就是从末尾元素依次开始创建Node节点。
        for (int i = array.length - 1; i >= 0; i--) {
            Node node = new Node(array[i],null);
            //将链表连接。
            node.setNext(root);
            root = node;
        }
        return root;
    }
    //获取长度。
    public int getSize(){
        return this.size;
    }
    //获取某个索引节点内容
    public int getContent(int index){
        Node node = this.root;
        for (int i = 0; i < index; i++) {
            node = node.getNext();
        }
        return node.getContent();
    }
    //查找某个值的索引值,默认不存在为-1。
    public int getIndex(int content){
        if (this.root == null){
            return -1;
        }
        Node node = this.root;
        int index = 0;
        while (index <= this.size - 1){
            if (node.getContent() == content){
                return index;
            }
            node = node.getNext();
            index++;
        }
        return -1;
    }
    public boolean addLast(int value){
       return add(value,this.size - 1);
    }
    //索引值为-1则代表在头部插入节点。
    public boolean addFirst(int value){
        return add(value,-1);
    }
    /*如果索引值为-1,则表示在头部添加节点。
      如果索引值为0,则代表在第一位元节点后面添加。
      而如果为n,则代表在第n位节点后面添加。
     */
    public boolean add(int value,int index){
        if (index < -1 || index > this.size - 1){
            return false;
        } else if (index == -1) {
            if (this.root == null){
                this.root = new Node(value,null);
            } else {
                this.root = new Node(value,this.root);
            }
        } else {
            Node node = this.root;
            while (index > 0){
                if (node.getNext() == null) {
                    return false;
                }
                node = node.getNext();
                index--;
            }
            if (node.getNext() == null){
                node.setNext(new Node(value,null));
            } else {
                Node newNode = new Node(value,node.getNext());
                node.setNext(newNode);
            }
        }
        this.size++;
        return true;
    }
    //删除最后一个元素
    public boolean removeLast(){
        return remove(this.size - 1);
    }
    //删除第一个元素
    public boolean removeFirst(){
        return remove(0);
    }

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    /*
        index值为0时,则代表要删除第1个链表节点,
        index值为n时,则代表要删除第n+1个链表节点。
         */
    public boolean remove(int index){
        if (index < 0 || index > this.size - 1 || this.root == null){
            return false;
        } else if (index == 0) {
            Node node = this.root;
            this.root = node.getNext();
            node.setNext(null);
        } else {
            Node node = this.root;
            while (index > 1){
                if (node.getNext() == null){
                    return false;
                }
                node = node.getNext();
                index--;
            }
            if (node.getNext() == null){
                return false;
            }
            Node newNode = node.getNext();
            node.setNext(node.getNext().getNext());
            newNode.setNext(null);
        }
        this.size--;
        return true;
    }
    //此处重写toString方法就是为了能够在控制台打印出链表节点内容。
    @Override
    public String toString(){
        if (this.root == null) {
            return "";
        }
        Node node = this.root;
        StringBuilder builder = new StringBuilder();
        while (node != null) {
            builder.append(node.getContent()).append(" ");
            node = node.getNext();
        }
        return builder.toString();
    }
    public static void main(String[] args) {
       Main main = new Main();
       boolean result = main.addFirst(9);
       boolean bag = main.addLast(2);
       boolean re = main.remove(1);
       System.out.println(main.toString());
    }
}

这里有个知识点大家必须知道,就是我在里面重写toString方法首先String类是final修饰的终极类,因此它不是基本数据类型,所以不能够修改,但是StringBuffer类和StringBuilder类可以修改或添加删除元素,但是前者是线程安全的,后者不是。因此,如果想要多线程安全的修改字符串,那么应该使用前者,而如果要单线程高效率的修改则需要调用后者。

这里的add()方法和remove()方法一些Java入门小白可能看的有点懵,我并没有修改this.root,那么这个插入还有用吗?其实,这里运用了JVM的栈堆知识,因为this.root是Node类型,即是一个对象,因此它的地址存放在栈里面,而内容存放在堆里面。我们创建一个Node类型的变量node,它和this.root指向的是同一个在堆里面的对象,即共用同一个对象。

5.那么编写完,我们需要知道我们写的是单向链表,这种是最简单的,但是jdk里面就已经提供了现成的链表API,那就是LinkedList类,它和ArrayList类十分相似,前者的增加和删除效率强于后者,但是查找和修改效率要弱于后者。另外,LinkedList是双向链表。直接看代码吧:

6.经过了上面的编写,大家应该能看出链表数组之间的区别了,直接看图吧:

image.png 从上面的图可以明显地看出,只要是在频繁插入和删除的情况下,链表明显更优于数组。