单链表的CRUD(Java版)

我们知道数组需要一块连续的内存空间来存储数据,数组中逻辑上相邻的两个元素在物理位置上也相邻。而链表就不一样了,它不需要一块连续的存储空间,它通过指针将一组零散的内存块串起来。我们把内存块称为结点(Node)。

链表为了将所有的结点串起来,链表的每个结点除了存储数据外,还需要存储一个指向其下一个结点位置的指针。所以链表有两块存储区域,其中存储数据的域称为数据域,存储下一个结点存储位置的域称为指针域。

单链表

单链表

单链表
我们在第一个结点之前增设一个结点,叫做头结点,头结点的数据域可以不存储任何信息,也可以存储如链表的长度等附加信息。头结点用来记录链表的基地址,有了头结点我们就可以遍历得到整条链表。最后一个结点叫做尾结点,它的指针域next不是指向下一个结点,而是指向空地址NULL。

在Java中结点的表示方式

class Node {
  Item item;
  Node next;
}

一个Node对象含有两个实例变量,分别为数据域Item和指针域Node。

与数组一样,链表也支持查找、插入、和删除操作。

单链表的查找操作

由于链表中的数据并非连续存储的,所以我们无法像数组那样,通过首地址和下标就可以直接计算出对应的内存地址。链表只能根据头结点一个一个向后遍历找到对应结点。

比如我们要在上图的链表(a、b和c结点索引依次为0、1、2)中找到索引为1的结点,我们只需要根据头结点依次向后遍历2次即可。

Node node(int index) {
    Node tmp = head;
    for(int i = 0; i <= index; i++) {
        tmp = tmp.next;
    }
    return tmp;
}
单链表的插入操作

一般情况下,我们会在链表的尾部追加元素,过程比较简单,如下:

首先根据头结点依次向后遍历找到尾结点,将尾结点的指针域指向新的结点即可。

public boolean add(Integer item) {
    Node newNode = new Node(item, null);
    // tmp 指向头结点
    Node tmp = head;

    while (tmp.next != null) {
        tmp = tmp.next;
    }
    tmp.next = newNode;
    size++;

    return true;
}

如果我们需要在链表指定索引的位置插入一个结点,比如在索引为1的位置上插入一个结点,即在a结点和b结点中间插入一个结点,过程如下:

单链表
首先根据头结点依次向后遍历找到a结点,将a结点的指针域指向aa结点,再将aa结点的指针域指向b结点。

public boolean add(int index, Integer item) {
    if(index < 0 || index > size) {
        throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
    }
    Node newNode = new Node(item, null);

    Node tmp = head;
    // 找到指定索引的前一个索引的结点
    for(int i = 0; i < index; i++) {
        tmp = tmp.next;
    }
    // 指定索引位置上原来的结点
    Node node = tmp.next;

    tmp.next = newNode;
    newNode.next = node;
    size++;

    return true;
}
单链表的删除操作

删除指定索引位置上的结点,比如删除b结点,过程如下:

根据头结点依次向后遍历,找到b结点的前一个结点a,将a结点的指针域next指向b结点的后一个结点c。

单链表

public Integer remove(int index) {
    this.checkIndex(index);
    // 找到要删除结点的前一个结点
    Node tmp = head;
    for(int i = 0; i < index; i++) {
        tmp = tmp.next;
    }
    // 待删除的结点
    Node node = tmp.next;
    // 前一个结点的指针域指向带删除结点的后一个结点
    tmp.next = node.next;

    size--;

    Integer val = node.item;
    node.item = null;

    return val;
}

上述单链表的查找、插入和删除操作完整代码如下:

public class SingleLinkedList {

    // 头结点
    private Node head;

    // 链表中实际元素个数
    private int size;

    public SingleLinkedList() {
        this.head = new Node();
        this.size = 0;
    }

    /**
     * 在链表尾部插入元素
     * @param item
     * @return
     */
    public boolean add(Integer item) {
        Node newNode = new Node(item, null);
        // tmp 指向头结点
        Node tmp = head;

        while (tmp.next != null) {
            tmp = tmp.next;
        }
        tmp.next = newNode;
        size++;

        return true;
    }

    /**
     * 在指定的索引上插入元素
     * @param index 从0开始
     * @param item
     * @return
     */
    public boolean add(int index, Integer item) {
        if(index < 0 || index > size) {
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
        }
        Node newNode = new Node(item, null);

        Node tmp = head;
        // 找到指定索引的前一个索引的结点
        for(int i = 0; i < index; i++) {
            tmp = tmp.next;
        }
        // 指定索引位置上原来的结点
        Node node = tmp.next;

        tmp.next = newNode;
        newNode.next = node;
        size++;

        return true;
    }

    /**
     * 返回指定位置的元素
     * @param index
     * @return
     */
    public Integer get(int index) {
        this.checkIndex(index);

        Node node = this.node(index);
        return node.item;
    }

    /**
     * 删除指定索引位置的结点
     * @param index
     * @return
     */
    public Integer remove(int index) {
        this.checkIndex(index);
        // 找到要删除结点的前一个结点
        Node tmp = head;
        for(int i = 0; i < index; i++) {
            tmp = tmp.next;
        }
        // 待删除的结点
        Node node = tmp.next;
        // 前一个结点的指针域指向带删除结点的后一个结点
        tmp.next = node.next;

        size--;

        Integer val = node.item;
        node.item = null;

        return val;
    }


    /**
     * 修改指定位置结点的值
     * @param index
     * @param item
     * @return
     */
    public Integer set(int index, Integer item) {
        this.checkIndex(index);
        Node node = this.node(index);
        Integer oldVal = node.item;
        // 替换为新值
        node.item = item;

        return oldVal;
    }

    /**
     * 遍历元素
     */
    public void list() {
        Node tmp = head;
        while(tmp.next != null) {
            tmp = tmp.next;
            System.out.println(tmp.item);
        }
    }

    /**
     * 返回链表元素个数
     * @return
     */
    public int size() {
        return size;
    }

    Node node(int index) {
        Node tmp = head;
        for(int i = 0; i <= index; i++) {
            tmp = tmp.next;
        }
        return tmp;
    }

    private void checkIndex(int index) {
        if(index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
        }
    }

    private static class Node {

        private Integer item;

        private Node next;

        public Node() {
        }

        public Node(Integer item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

}

「更多精彩内容请关注公众号geekymv,喜欢请分享给更多的朋友哦」
geekymv

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值