自学内容网 自学内容网

Java链表(1)

🐵本篇文章将对单链表进行讲解,模拟实现单链表中常见的方法


一、什么是链表

链表是一种逻辑结构上连续而物理结构上不一定连续的线性表,链表由一个一个节点组成:

每一个节点中都由数据域(val)和指针域(next)组成,数据域用于存放要存放在该节点的数据,指针域用于存放节点的地址,上图中链表的每一个节点的指针域存放的是下一个节点的地址,最后一个节点的指针指向空

二、链表的种类

链表细分可以分为八种,分别为:

1. 单向不带头非循环链表 2. 单向不带头循环链表 

3. 单向带头非循环链表     4. 单向带头循环链表

5. 双向不带头非循环链表 6. 双向不带头循环链表 

7. 单向带头非循环链表     8. 双向带头循环链表

三、链表的模拟实现

首先对第一种单链表的增删查改等操作用Java进行模拟实现,先将要模拟实现的方法写到IList接口中:

public interface IList {
    //头插法
    public void addFirst(int data);

    //尾插法
    public void addLast(int data);

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data);

    //查找关键字key是否在单链表当中
    public boolean contains(int key);

    //删除第一次出现关键字为key的节点
    public void remove(int key);

    //删除所有值为key的节点
    public void removeAllKey(int key);

    //得到单链表的长度
    public int size();

    //清空链表
    public void clear();

    //遍历并展示链表中的数据
    public void display();
}

之后再创建一个MySingleList类实现上述接口并重写接口中所有的方法

public class MySingleList implements IList{
    static class ListNode {
        public int val;
        public ListNode next;
        public ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode head; //指向第一个节点
    
    /*以下是要重写IList接口中的方法*/
    ...

}

在MySingleList类中创建一个静态内部类ListNode用于描述一个节点对象,val用于存放数据,next用于存放下一个节点的地址,构造方法用于初始化数据va,接下来对上述方法进行模拟实现

3.1 方法的实现

public void addFirst(int data);

在链表的头部新增一个节点newNode

很明显要在头部新增一个节点就应让newNode的next指向head,并让newNode成为head

    public void addFirst(int data) {
        ListNode newNode = new ListNode(data);
        newNode.next = head;
        head = newNode;
    }

public void addLast(int data);

在链表的尾部新增一个节点

应让最后一个节点的next引用指向newNode,链表无法像顺序表一样可以直接找到某个位置的节点,所以应该通过循环先找到最后一个节点:

ListNode cur = head; //通常一个链表的头指针不动,需要定义一个cur来遍历链表、

while (cur.next != null) {
    cur = cur.next; //只要cur的next不是空,cur就指向下一个节点
}

//循环完成后cur就是最后一个节点
cur.next = newNode; 

完成以上操作后还要考虑一个特殊情况:如果链表为空,上述代码能否完成尾插操作?答案是不能的,因为如果链表为空,则head为空,cur为空,在循环条件处cur.next != null就会抛出一个NullPointerException的空指针异常,所以要单独处理链表为空的情况:

    public void addLast(int data) {
        ListNode newNode = new ListNode(data);
        if (head == null) { //如果链表为空,则直接让head指向newNode
            head = newNode;
        } else {
            ListNode cur = head;
            while (cur.next != null) {
                cur = cur.next;
            }
            cur.next = newNode;
        }
    }

public int size();

求链表的长度也就是该链表有几个节点,只要直接遍历链表即可

    public int size() {
        ListNode cur = head;
        int count = 0;
        while(cur != null) { //注意这里和尾插的循环条件不一样
            count++;
            cur = cur.next;
        }

        return count;
    }

public boolean contains(int key);

判断链表中是否有节点的val值等于key,也是只要直接遍历链表即可

    public boolean contains(int key) {
        ListNode cur = head;
        while(cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

public void addIndex(int index,int data);

在任意位置处插入一个节点,第一个节点的索引为0

首先要判断一下index是否合法:

public class IndexException extends RuntimeException{
    public IndexException(String message) {
        super(message);
    }
}

=======================================================

public void addIndex(int index, int data) {
    if (index < 0 || index > size()) {
        throw new IndexException("下标错误");
    }
    ...
}

在任意位置插入节点,所以如果index为0或等于链表长度,就可以直接使用刚刚实现过的头插和尾插方法

public void addIndex(int index, int data) {
    if (index < 0 || index > size()) {
        throw new IndexException("下标错误");
    }
    if (index == 0) {
        addFirst(data);
    }
    if (index == size()) {
        addLast(data);
    }

    ...
}

然后就是一般情况下:

如果要在1位置处插入新节点,那就应该先找到1位置处的节点的前驱节点,这里定义为cur,令newNode.next = cur.next;这样newNode的next就指向1位置处的节点,之后令cur.next = newNode这样就插入完成了

    public void addIndex(int index, int data) {
        if (index < 0 || index > size()) {
            throw new IndexException("下标错误");
        }
        if (index == 0) {
            addFirst(data);
        }
        if (index == size()) {
            addLast(data);
        }

        ListNode newNode = new ListNode(data);
        ListNode cur = head;

        for (int i = 0; i < index - 1; i++) { 找到插入节点的前驱节点
            cur = cur.next;
        }

        newNode.next = cur.next;
        cur.next = newNode;
    }

public void remove(int key);

假如要删除val = 20的节点(这里先定义为del)如果要删除该节点就应让del的前驱节点的next指向del的后继节点:

    public void remove(int key) {
        ListNode cur = head;

        while(cur.next.val != key) { //通过循环找到del的前驱节点
            cur = cur.next; 
        }
        cur.next = cur.next.next;
    }

写完后还要考虑两种特殊情况:

第一,如果链表为空,上述代码不能满足要求,因为在循环条件处会抛出空指针异常

第二,如果要删除的节点是第一个节点,上述代码也不能满足要求,因为该循环是从第一个节点的下一个节点开始判断的

以上两种情况都需要单独处理:

    public void remove(int key) {
        if (head == null) {
            return;
        }
        if (head.val == key) {
            head = head.next;
            return;
        }
        ListNode cur = head;
        while(cur.next.val != key) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
    }

public void removeAllKey(int key);

删除所有val = key的节点,那就需要遍历整个链表,在遍历过程中删除节点:

    public void removeAllKey(int key) {

        ListNode cur = head;
        while (cur.next != null) {
            if(cur.next.val == key) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }
        }

    }

在循环的结束也要判断一下第一个节点是否为要删除的节点,因为上述循环条件为cur.next != null没有判断第一个节点:

    public void removeAllKey(int key) {
        if (head == null) { //如果链表为空则直接返回
            return;
        }

        ListNode cur = head;
        while (cur.next != null) {
            if(cur.next.val == key) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }
        }
        if (head.val == key) {
            head = head.next;
        }
    }

public void clear();

清空链表,由于对象在没有被引用时,就会自动被回收,所以只需要将head置为空就行了

public void clear() {
    head = null;
}

🙉本篇文章到此结束,下篇文章将对LinkedList类进行讲解


原文地址:https://blog.csdn.net/m0_74270127/article/details/135554215

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!