Javase学习13-链表

Javase学习13-链表

1. 单向链表

1.1 单向链表的结构

单向链表中的节点由两部分组成:

  • 节点储存的数据 data
  • 指向下一个节点的地址 next

节点类:

public class Node {
    //为了不让外部类使用Node类,使用private修饰data和next
    /**
     * 节点储存的数据
     */
    private Object data;
    /**
     * 节点存储的指向下一个节点的地址,默认为null
     */
    private Node next;
    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
}

1.2 单向链表的特点

1.2.1 单向链表的优点:

相比于数组,链表的增删操作不会影响过多数据的位置,易于进行增删操作

1.2.2 单向链表的缺点:

链表的各个节点在内存中的位置是无序的,每次进行查找时只能从头节点开始依次搜寻,检索效率慢

1.3 单向链表节点的搜索

在对链表进行增删时,必须要先找到目标节点,才能进行下一步的操作

1.3.1 通过值找到目标节点

/**
 * 3.1找到目标节点
 */
public Node findNode(Object value) {
    Node tempNode = head;
    while (tempNode != null) {
        //如果临时节点的值等于目标值,那么就返回该节点
        if (tempNode.equals(value) {
            return tempNode;
        }
        tempNode = tempNode.getNext();
    }
    //如果没有找到,返回null
    return null;
}

1.3.2 通过下标找到目标节点

/**
 * 3.2通过下标找到目标节点
 */
public Node getNode(int index) {
    Node tempNode = head;
    for (int i = 0; i < index; i++) {
        tempNode = tempNode.getNext();
    }
    return tempNode;
}

通过递归找到尾节点:

/**
 * 找到尾节点
 */
public Node findEnd(Node node) {
    if(node.getNext() == null) {
        return node;
    }
    //通过递归调用实现遍历
    return findEnd(node.getNext());
}

1.4 单向链表节点的插入

1.4.1 在指定位置插入新节点

插入新节点时,先将新节点指向目标位置的原节点,再将要插入位置的前一个节点指向新节点

public void add(int index,Object data){
    //检查是下标是否越界
    rangeCheck(index);
    Node newNode = new Node(data,null);
    //先将新节点指向目标位置的原节点
    newNode.setNext(getNode(index));
    //再将目标节点的前一个节点指向新节点
    getNode(index - 1).setNext(newNode);
    size++;
}

1.4.2 在链表头部插入新节点

这种方法是在链表前面添加节点,使每次添加的新节点成为新的头节点

public void addHead(Object data) {
    //创建新节点
    Node newNode = new Node(data,null);
    //当链表不为空时,使新节点指向旧的头节点
    if (head != null) {
        newNode.setNext(head);
    }
    //使新节点成为新的头节点
    head = newNode;
    //链表长度加1
    size++;
}

1.4.3 在链表尾部插入新节点

这种方法是在链表后面添加节点,使每次添加的新节点成为新的尾节点,该方法需要找到当前链表的尾节点

public void addEnd(Object data) {
    //创建新节点
    Node newNode = new Node(data,null);
    if (head == null) {
        //当链表为空时,使新节点成为头节点
        head = newNode;
    } else {
        //当链表不为空时,找到当前链表的末节点,使其指向新节点,新节点成为新的末节点
        getEnd(head).setNext(newNode);
    }
    //链表长度加1
    size++;
}

1.5 单向链表节点的删除

删除节点时,仅需要把目标节点的前一个节点指向目标节点后的一个节点即可,未被指向的节点会被自动处理.

代码实现:

public void delete(int index) {
    //判断链表中是否有数据
    if (size > 0) {
        //检查下标是否越界
        rangeCheck(index);
        //将目标节点赋给一个临时节点
        Node tempNode = getNode(index);
        //当链表中只有一个节点时
        if (size == 1) {
            head = null;
        } else {
            //当目标节点是头节点时
            if (tempNode == head) {
                head = tempNode.getNext();
            }
            //使目标节点的前一个节点指向目标节点的后一个结点
            getNode(index - 1).setNext(tempNode.getNext());
        }
        //链表长度减一
        size--;
    }
}

1.6 实现一个可进行增删查改的单向链表

Node节点类:

package com.tsccg.practice;

/**
 * @author: TSCCG
 * @date: 2021/5/29
 */
public class Node {
    //为了不让外部类使用Node类,使用private修饰data和next
    /**
     * 节点储存的数据
     */
    private Object data;
    /**
     * 节点存储的指向下一个节点的地址,默认为null
     */
    private Node next;
    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
}

主类:

package com.tsccg.practice;

/**
 * @author: TSCCG
 * @date: 2021/5/29
 */
public class LinkedListDemo01 {
    //定义头节点
    private Node head;
    //定义链表长度
    private int size = 0;

    /**
     * 1.1从链表头部添加节点
     */
    public void addHead(Object data) {
        //创建新节点
        Node newNode = new Node(data,null);
        //当链表不为空时,使新节点指向旧的头节点
        if (head != null) {
            newNode.setNext(head);
        }
        //使新节点成为新的头节点
        head = newNode;
        //链表长度加1
        size++;
    }

    /**
     * 1.2从链表尾部添加节点
     */
    public void addEnd(Object data) {
        //创建新节点
        Node newNode = new Node(data,null);
        if (head == null) {
            //当链表为空时,使新节点成为头节点
            head = newNode;
        } else {
            //当链表不为空时,找到当前链表的末节点,使其指向新节点,新节点成为新的末节点
            getEnd(head).setNext(newNode);
        }
        //链表长度加1
        size++;
    }

    /**
     * 1.3在指定位置插入新节点,
     */
    public void add(int index,Object data){
        rangeCheck(index);
        Node newNode = new Node(data,null);
        //先将新节点指向目标位置的原节点
        newNode.setNext(getNode(index));
        //再将目标节点的前一个节点指向新节点
        getNode(index - 1).setNext(newNode);
        size++;
    }

    /**
     * 2.通过下标删除节点
     */
    public void delete(int index) {
        //判断链表中是否有数据
        if (size > 0) {
            //检查下标是否越界
            rangeCheck(index);
            //将目标节点赋给一个临时节点
            Node tempNode = getNode(index);
            //当链表中只有一个节点时
            if (size == 1) {
                head = null;
            } else {
                //当目标节点是头节点时
                if (tempNode == head) {
                    head = tempNode.getNext();
                }
                //使目标节点的前一个节点指向目标节点的后一个结点
                getNode(index - 1).setNext(tempNode.getNext());
            }
            //链表长度减一
            size--;
        }
    }

    /**
     * 3.1通过值找到目标节点
     */
    public Node getNode(Object value) {
        Node tempNode = head;
        while (tempNode != null) {
            if (tempNode.equals(value)) {
                return tempNode;
            }
            tempNode = tempNode.getNext();
        }
        //如果没有找到,返回null
        return null;
    }
    /**
     * 3.2通过下标找到目标节点
     */
    public Node getNode(int index) {
        rangeCheck(index);
        Node tempNode = head;
        for (int i = 0; i < index; i++) {
            tempNode = tempNode.getNext();
        }
        return tempNode;
    }
    /**
     * 3.3找到尾节点
     */
    public Node getEnd(Node node) {
        if (node.getNext() == null) {
            return node;
        }
        //使用递归
        return getEnd(node.getNext());
    }

    /**
     * 4.更改目标节点的值
     */
    public void update(int index, Object newData) {
        //首先根据值找到目标节点,然后将新的值赋给它
        getNode(index).setData(newData);
    }
    /**
     * 5.size返回链表长度
     */
    public int size() {
        return size;
    }
    //6.isEmpty
    public boolean isEmpty() {
        return size() == 0;
    }
    /**
     * 7.显示所有节点信息
     */
    public void print() {
        Node tempNode = head;
        while (tempNode != null) {
            System.out.println(tempNode.getData());
            tempNode = tempNode.getNext();
        }
    }

    //8.下标越界抛出异常
    private void rangeCheck(int index) {
        if (index < 0 || index > this.size()) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+this.size();
    }

}

测试类:

package com.tsccg.practice;


/**
 * @author: TSCCG
 * @date: 2021/5/29
 */
public class LinkedListTest01 {
    public static void main(String[] args) {
        LinkedListDemo01 link = new LinkedListDemo01();
        System.out.println("此时链表是否为空? " + link.isEmpty());
        link.addEnd("AA");
        link.addEnd("bb");
        link.addEnd("CC");
        link.addEnd("DD");
        System.out.println("---------添加后---------");
        link.print();
        System.out.println("此时链表是否为空? " + link.isEmpty());
        link.add(1,"FFF");
        System.out.println("---------插入后---------");
        link.print();
        link.update(2,"BB");
        System.out.println("---------变更后---------");
        link.print();
        link.delete(1);
        System.out.println("---------删除后---------");
        link.print();
    }

}

结果:

此时链表是否为空? true
---------添加后---------
AA
bb
CC
DD
此时链表是否为空? false
---------插入后---------
AA
FFF
bb
CC
DD
---------变更后---------
AA
FFF
BB
CC
DD
---------删除后---------
AA
BB
CC
DD

2. 双向链表

2.1 双向链表的结构

双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向直接后继(next)和直接前驱(pre)。

双向链表的节点由三部分组成:

  • 指向前一个节点的地址 pre
  • 储存的数据 data
  • 指向后一个节点的地址 next

01双向链表的结构.PNG

节点类:

/**
 * @Author TSCCG
 * @Date 2021/5/30 9:30
 */

public class Node2<T>{
    //指向前一个节点的地址 pre
    private Node2<T> pre;
    //节点储存的数据 data
    private T data;
    //指向下一个节点的地址
    private Node2<T> next;
	
    //构造方法
    public Node2(T data) {
        this.data = data;
    }
    public Node2<T> getPre() {
        return pre;
    }
    public void setPre(Node2<T> pre) {
        this.pre = pre;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    public Node2<T> getNext() {
        return next;
    }
    public void setNext(Node2<T> next) {
        this.next = next;
    }
}

2.2 双向链表的特点

有两个指针,一个指向前一个节点,一个后一个节点。

2.2.1 双向链表的优点:

双向链表的每个数据结点中都有两个指针,分别指向直接后继(next)和直接前驱(pre)。故从双向链表中的任意一个结点开始,都可以很方便地访问前驱结点和后继结点。

2.2.2 双向链表的缺点:

增加删除节点复杂,需要多分配一个指针存储空间。

2.3 检查数据是否越界

在进行各种操作的时候,需要检查检索的下标或输入的数据是否越界

2.3.1 检查下标是否越界

//下标越界抛出异常
private void indexCheck(int index) {
    if (index < 0 || index >= this.size()) {
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}
private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+this.size();
}

2.3.2 检查数据是否为null

//数据为null抛出异常
private void dataCheck(T data) {
    if (data == null) {
        throw new NullPointerException();
    }
}

2.4 双向链表的查找

在进行增删改操作时,需要先找到目标节点。

2.4.1 通过下标找到目标节点

private Node2<T> getNode(int index) {
    indexCheck(index);
    Node2<T> tempNode = head;
    for (int i = 0; i < index; i++) {
        tempNode = tempNode.getNext();
    }
    return tempNode;
}

2.4.2 通过值找到目标节点

private Node2<T> getNode(T data) {
    dataCheck(data);
    Node2<T> tempNode = head;
    while (tempNode != null) {
        if (tempNode.getData() == data) {
            return tempNode;
        }
        tempNode = tempNode.getNext();
    }
    //如果没有找到,返回null
    return null;
}

2.4.3 找到尾节点

private Node2<T> getEnd(Node2<T> node) {
    if (node.getNext() == null) {
        return node;
    }
    //使用递归
    return getEnd(node.getNext());
}

2.4.4 查询目标节点的值

public T get(int index) {
    indexCheck(index);
    return getNode(index).getData();
}

2.5 双向链表的插入

在指定位置插入节点,过程如下:

  1. 首先,我们需要创建一个新节点newNode,然后找到目标位置的节点getNode(index),将该节点赋给一个临时节点tempNode.
02节点的插入01.PNG
  1. 先将新节点的后继next指向目标位置节点
03节点的插入02.PNG
  1. 再将目标位置节点的前一个节点的后继next指向新节点
04节点的插入03.PNG
  1. 然后将新节点的前驱pre指向目标位置节点的前一个节点
05节点的插入04.PNG
  1. 最后将目标位置节点的前驱pre指向新节点
06节点的插入05.PNG
/**
 * 在指定位置插入新节点
 */
public void add(int index,T data){
    indexCheck(index);
    //创建新节点
    Node2<T> newNode = new Node2<>(data);
    //将目标位置的节点赋给一个临时节点
    Node2<T> tempNode = getNode(index);
    //当要在头节点前插入时
    if (index == 0) {
        newNode.setNext(tempNode);
        tempNode.setPre(newNode);
        head = newNode;
    } else {
        //先将新节点的后继next指向目标位置节点
        newNode.setNext(tempNode);
        //再将目标位置节点的前一个节点的后继next指向新节点
        tempNode.getPre().setNext(newNode);
        //然后将新节点的前驱pre指向目标位置节点的前一个节点
        newNode.setPre(tempNode.getPre());
        //最后将目标位置节点的前驱pre指向新节点
        tempNode.setPre(newNode);
    }
    size++;

}

2.6 双向链表的添加

链表的添加一般向链表末尾添

添加过程:

  1. 当链表为空时,使新添加的节点成为头节点
  2. 当链表不为空时,找到当前链表的末节点,将末节点赋给一个临时节点
  3. 将末节点的后继next指向新节点
  4. 再将新节点的前驱pre指向末节点
  5. 如此,新节点就成为了新的末节点
/**
 * 1.从链表尾部添加节点
 */
public void add(T data) {
    //创建新节点
    Node2<T> newNode = new Node2<>(data);
    if (head == null) {
        //当链表为空时,使新节点成为头节点
        head = newNode;
    } else {
        //当链表不为空时,找到当前链表的末节点,将末节点赋给一个临时节点
        Node2<T> tempNode = getEnd(head);
        //使末节点的后继next指向新节点
        tempNode.setNext(newNode);
        //将新节点的前驱pre指向旧的末节点,新节点成为新的末节点
        newNode.setPre(tempNode);
    }
    //链表长度加1
    size++;
}

2.7 双向链表的删除

/**
 * 2.通过下标删除节点
 */
public void delete(int index) {
    //判断链表中是否有数据
    if (size > 0) {
        //检查下标是否越界
        indexCheck(index);
        //将目标节点赋给一个临时节点
        Node2<T> tempNode = getNode(index);
        //当链表中只有一个节点时
        if (size == 1) {
            head = null;
        } else {
            //当目标节点是头节点时
            if (tempNode == head) {
                //将头节点的下一个节点的前驱pre指向null
                tempNode.getNext().setPre(null);
                //使头节点的下一个节点成为新的头节点
                head = tempNode.getNext();
            } else {
                //使目标节点的前一个节点的后继next指向目标节点的后一个结点
                tempNode.getPre().setNext(tempNode.getNext());
                //使目标节点的后一个节点的前驱pre指向目标节点的前一个节点
                tempNode.getNext().setPre(tempNode.getPre());
            }
        }
        //链表长度减一
        size--;
    }
}

2.8 实现一个可进行增删查改的双向链表

节点类:

/**
 * @Author TSCCG
 * @Date 2021/5/30 9:30
 */

public class Node2<T>{
    private Node2<T> pre;
    private T data;
    private Node2<T> next;

    public Node2(T data) {
        this.data = data;
    }

    public Node2<T> getPre() {
        return pre;
    }

    public void setPre(Node2<T> pre) {
        this.pre = pre;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Node2<T> getNext() {
        return next;
    }

    public void setNext(Node2<T> next) {
        this.next = next;
    }
}

主类:

/**
 * @Author TSCCG
 * @Date 2021/5/30 9:28
 */

public class LinkedList2<T> {
    //定义头节点
    private Node2<T> head;
    //定义链表长度
    private int size = 0;
    
    /**
     * 1.从链表尾部添加节点
     */
    public void add(T data) {
        //创建新节点
        Node2<T> newNode = new Node2<>(data);
        if (head == null) {
            //当链表为空时,使新节点成为头节点
            head = newNode;
        } else {
            //当链表不为空时,找到当前链表的末节点,将末节点赋给一个临时节点
            Node2<T> tempNode = getEnd(head);
            //使末节点的后继next指向新节点,新节点成为新的末节点
            tempNode.setNext(newNode);
            //将新节点的前驱pre指向旧的末节点
            newNode.setPre(tempNode);
        }
        //链表长度加1
        size++;
    }

    /**
     * 1.2在指定位置插入新节点
     */
    public void add(int index,T data){
        indexCheck(index);
        //定义新节点
        Node2<T> newNode = new Node2<>(data);
        //将目标位置的节点赋给一个临时节点
        Node2<T> tempNode = getNode(index);
        //当要在头节点前插入时
        if (index == 0) {
            newNode.setNext(tempNode);
            tempNode.setPre(newNode);
            head = newNode;
        } else {
            //先将新节点的后继next指向目标位置节点
            newNode.setNext(tempNode);
            //再将目标位置节点的前一个节点的后继next指向新节点
            tempNode.getPre().setNext(newNode);
            //然后将新节点的前驱pre指向目标位置节点的前一个节点
            newNode.setPre(tempNode.getPre());
            //最后将目标位置节点的前驱pre指向新节点
            tempNode.setPre(newNode);
        }
        size++;

    }

    /**
     * 2.通过下标删除节点
     */
    public void delete(int index) {
        //判断链表中是否有数据
        if (size > 0) {
            //检查下标是否越界
            indexCheck(index);
            //将目标节点赋给一个临时节点
            Node2<T> tempNode = getNode(index);
            //当链表中只有一个节点时
            if (size == 1) {
                head = null;
            } else {
                //当目标节点是头节点时
                if (tempNode == head) {
                    //将头节点的下一个节点的前驱pre指向null
                    tempNode.getNext().setPre(null);
                    //使头节点的下一个节点成为新的头节点
                    head = tempNode.getNext();
                } else {
                    //使目标节点的前一个节点的后继next指向目标节点的后一个结点
                    tempNode.getPre().setNext(tempNode.getNext());
                    //使目标节点的后一个节点的前驱pre指向目标节点的前一个节点
                    tempNode.getNext().setPre(tempNode.getPre());
                }
            }
            //链表长度减一
            size--;
        }
    }

    /**
     * 3.1通过值找到目标节点
     */
    private Node2<T> getNode(T data) {
        dataCheck(data);
        Node2<T> tempNode = head;
        while (tempNode != null) {
            if (tempNode.getData() == data) {
                return tempNode;
            }
            tempNode = tempNode.getNext();
        }
        //如果没有找到,返回null
        return null;
    }
    /**
     * 3.2通过下标找到目标节点
     */
    private Node2<T> getNode(int index) {
        indexCheck(index);
        Node2<T> tempNode = head;
        for (int i = 0; i < index; i++) {
            tempNode = tempNode.getNext();
        }
        return tempNode;
    }
    /**
     * 3.3找到尾节点
     */
    private Node2<T> getEnd(Node2<T> node) {
        if (node.getNext() == null) {
            return node;
        }
        //使用递归
        return getEnd(node.getNext());
    }
    /**
     * 3.4查询目标节点的值
     */
    public T get(int index) {
        indexCheck(index);
        return getNode(index).getData();
    }
    /**
     * 4.1使用下标更改目标节点的值
     */
    public void update(int index, T newData) {
        //首先根据值找到目标节点,然后将新的值赋给它
        getNode(index).setData(newData);
    }

    /**
     * 4.2 通过值更改目标节点的值
     */
    public void update(T oldData, T newData) {
        //检查值是否为空
        dataCheck(newData);
        //首先根据值找到目标节点,然后将新的值赋给它
        getNode(oldData).setData(newData);
    }
    /**
     * 5.size
     */
    public int size() {
        return size;
    }
    //6.isEmpty
    public boolean isEmpty() {
        return size() == 0;
    }
    /**
     * 7.显示所有节点信息
     */
    public void print() {
        Node2<T> tempNode = head;
        while (tempNode != null) {
            System.out.println(tempNode.getData());
            tempNode = tempNode.getNext();
        }
    }

    //8.下标越界抛出异常
    private void indexCheck(int index) {
        if (index < 0 || index >= this.size()) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+this.size();
    }
    //8.2检查值是否为null
    private void dataCheck(T data) {
        if (data == null) {
            throw new NullPointerException();
        }
    }
}

测试类:

/**
 * @Author TSCCG
 * @Date 2021/5/30 10:02
 */

public class LinkedListTest {
    public static void main(String[] args) {
        LinkedList2<Object> link = new LinkedList2<>();
        System.out.println("此时链表是否为空? " + link.isEmpty());
        link.add("张三");
        link.add("李四");
        link.add("王五");
        link.add("赵六");
        System.out.println("---------添加数据后---------");
        link.print();
        System.out.println("此时链表是否为空? " + link.isEmpty());

        link.add(1,"阿珍");
        System.out.println("---------在下标为1的位置插入阿珍后---------");
        link.print();

        link.update(2,"阿强");
        System.out.println("---------将下标为2的节点数据更改为阿强后---------");
        link.print();

        link.delete(0);
        System.out.println("---------删除下标为0的节点后---------");
        link.print();

        System.out.println("此时链表中元素的个数为:" + link.size());
        System.out.println("下标为0的数据是:" + link.get(0));

    }
}

结果:

此时链表是否为空? true
---------添加数据后---------
张三
李四
王五
赵六
此时链表是否为空? false
---------在下标为1的位置插入阿珍后---------
张三
阿珍
李四
王五
赵六
---------将下标为2的节点数据更改为阿强后---------
张三
阿珍
阿强
王五
赵六
---------删除下标为0的节点后---------
阿珍
阿强
王五
赵六
此时链表中元素的个数为:4
下标为0的数据是:阿珍

3. 总结

3.1 单向链表和双向链表的区别:

3.1.1 单向链表:

只有一个指向下一个节点的指针。

优点:单向链表增加删除节点简单。遍历时候不会死循环;

缺点:只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进。

适用于节点的增加删除。

3.1.2 双向链表:

有两个指针,一个指向前一个节点,一个后一个节点。

优点:可以找到前驱和后继,可进可退;

缺点:增加删除节点复杂,需要多分配一个指针存储空间。

适用于需要双向查找节点值的情况。

posted @ 2021-05-29 15:50  TSCCG  阅读(69)  评论(0编辑  收藏  举报