没有最细,只有更细——全网最易懂的Java链表教程之一(算法村第一关总结)

讲解链表之前你必须知道的知识概要:

Java采用面向对象设计,如果我们把一个引用first赋给current(current = first),那么会变成这个样子:

 可以看到,赋值后current和first存储了相同的引用值,因而指向同一个对象。这是链表中各种引用变量指来指去的基础,务必搞清楚这一点,避免绕晕!

1.单链表

1)结点定义:

public class ListNode {
    public int val;
    public ListNode next;
//这里的val是当前节点的值,next指向下一个节点。
因为两个变量都是public的,所以创建对象ListNode listNode = new ListNode(1)后,能直接使用listNode.val和listNode.next 来操作.
虽然违背了面向对象的设计要求,但是更精简,在算法题中使用广泛。
    public ListNode(int val) {
        this.val = val;
        next = null;//这句不写也可
    }

}

所谓单链表就是每个节点有数据域val和指向同类型对象的指针next, 多个节点之间通过引用变量(next)相互连接,最后一个节点的next指针值为null。

image.png

2)单链表长度计算:

public static int getListLength(ListNode head){
    int length = 0;
    ListNode node = head;
    while (node != null){
        //只要指向的是一个非空节点,就将长度++
        length++;
        node = node.next;
    }
    return length;
}

3)增加元素

首先我们要清楚地知道,为了避免前驱结点的丢失,应该用node.next来判断是否到了插入位置,让变量node指向插入位置的前驱节点,然后在进行插入

需要注意的是,插入节点时需要考虑两种情况:第一种是插入在链表的表头位置(即position==1),这个时候插入位置没有前驱节点;第二种是插入在非表头位置,有前驱节点。

public static ListNode insertNode(ListNode head,ListNode nodeInsert, int position){
    if (head == null){
        return nodeInsert;
        //如果链表是空的话,那么插入的节点就作为链表的头结点,返回插入的节点
    }
    if (position < 1 || position > getListLength(head) + 1){
        //因为一共有length个节点,可以在尾部插入,那么位置就最大可以是length+1,所以position做了上面的限制
        System.out.println("插入位置越界");
        return head;
    }
    if (position == 1){
        nodeInsert.next = head;
        return nodeInsert;//这时nodeInsert就是头结点
    }
    ListNode node = head;
    int count = 1;
    while (count < position - 1){
        node = node.next;
        count++;
    }
我知道while循环这一段会有人纠结,今天来详细解释一下为什么要用count < position - 1作为控制条件。
我们知道,刚开始count==1,而node也==head,也就是说node指向了链表的第一个结点。注意,第一个结点,count==1.然后每一次node = node.next,也就是指向下一个节点的时候,count总会同步的++。
然后我们就有了一个很好的发现:node指向第几个节点,count就一定等于几。
那么再看这个判断条件就很清晰了。我们是要在第position个位置插入节点,因此我们需要找到它的前驱节点,第position-1个节点。
while循环在count < position - 1时会不断执行,各变量都是整数,那么什么时候while循环结束呢?答案呼之欲出:count == position - 1的时候循环终止。
再参考我们前面的结论,node总指向链表的第count个节点。这也就是说,当node指向第position-1个节点的时候循环终止。
此时跳出循环,node就指向了前驱节点。接下来的代码就很好懂了,让新节点的next和前驱结点的next都指向后继结点,再让前驱结点的next指向新节点。插入完成。

image.png

    nodeInsert.next = node.next;
    node.next = nodeInsert;
    return head;//尾结点插入情况也考虑在内了
}

4)删除节点(与插入节点类似,不再赘述)

/**
 *
 * @param head 链表头结点
 * @param position 删除元素位置(从1开始计算)
 * @return 删除操作之后的链表头结点
 */
public static ListNode deleteNode(ListNode head, int position){
    if (head == null){
        return null;
    }
    if (position < 1 || position > getListLength(head)){//这里大于head就越界了而不是head+1,因为链表只有head个元素可删除
        System.out.println("删除节点的索引越界");
        return head;
    }

image.png

    //正常删除所用的node.next = node.next.next中,删除的节点至少也是第二个了,因此需要考虑删除第一个的情况
    if (position == 1){
        return head.next;
    }
    int count = 1;//count仍然表示node所指第几个节点
    ListNode node = head;
    //要删除第p个节点,首先node要指向第p-1个节点
    while (count < position - 1){
        node = node.next;
        count++;
    }
    node.next = node.next.next;
    return head;

}

 2.双向链表

构造定义双向链表节点,注意区分节点和双向链表对象。节点有数据域和两个指针,而一个双向链表对象只有头指针和尾指针两个字段

public class DoubleNode {//双向链表节点定义
    public int data;
    public DoubleNode prev;
    public DoubleNode next;

    public DoubleNode(int data) {
        this.data = data;
    }
}
public class DoublyLinkedList {//双向链表类定义
    public DoubleNode first;
    public DoubleNode last;

}

1)双向链表的增加元素(以下代码在DoublyLinkedList类中)

public boolean isEmpty(){
    return first == null;//如果头指针为空证明双向链表为空
}

//(1)插入链表最前面
public void insertFirst(int data){
    DoubleNode newNode = new DoubleNode(data);
    if (isEmpty()){
        //如果双向链表为空的话
        first = newNode;
        last = newNode;
    }else {
        first.prev = newNode;//原先的第一节点成了第二个,让他的prev指向newNode
        newNode.next = first;
        first = newNode;//头结点已经发生改变,因而改变first引用的对象
    }
}

//(2)插入链表尾部

public void insertLast(int data){
    DoubleNode newNode = new DoubleNode(data);
    if (isEmpty()){
        first = newNode;
        last = newNode;
    }else {
        last.next = newNode;//原来的尾结点成了现在的倒数第二节点,让它的next指向newNode
        newNode.prev = last;
        last = newNode;//改变尾结点指向
    }
}

//(3)插入值为key的节点后面

public void insertAfter(int key,int data){
    DoubleNode newNode = new DoubleNode(data);
    DoubleNode node = first;
    while (node != null && node.data != key){
        node = node.next;
    }
    //最后结束循环时,要么node.data == key,即node指向了值为Key的结点,要么没找到值为key的节点,即node == null
    //node == null又分两种情况,第一种是链表有元素但没找到,node是表尾的null;第二种是链表本身就为空所以没找到
    if (node == null){
        if (isEmpty()) {//本身为空
            first = newNode;
            last = newNode;
        }else {//非空但没有key值的节点,这个时候就在尾部插入该data节点
            last.next = newNode;
            newNode.prev = last;
            last = newNode;
        }
    }else {
        //else表明node指向了key节点,应该插在key节点后面,这时问题要讨论情况,需要考虑key节点是否有后继结点,如果key节点是最后一个节点就没有后继结点
        if (node.next == null){//node.next == null,即key节点没有后继结点时,等同于插入到表尾
            insertLast(data);
        }else {
            node.next.prev = newNode;
            newNode.next = node.next;
            node.next = newNode;
            newNode.prev = node;

image.png

        }
    }
}
 

2)双向链表删除元素:

public DoubleNode deleteFirst(){
    if (isEmpty()){
        throw new RuntimeException("空链表不支持删除");
    }
    //不为空时分两种情况:第一种是删除之后双向链表为空,即双向链表只有一个结点;第二种是双向链表有至少两个结点
    DoubleNode temp = first;//temp保存first节点便于返回
    if (first.next == null){
        first = null;
        last = null;
        return temp;
    }
    first.next.prev = null;
    first = first.next;//相同引用值指向同一对象
    return temp;
}

public DoubleNode deleteLast(){
    //从尾部删除节点
    if (isEmpty()){
        throw new RuntimeException("空链表不支持删除");
    }
    DoubleNode temp = last;//同上,保存尾部节点
    if (last.prev == null){
        //如果链表只有一个节点,删除后就成了空表,first,last均为null
        first = null;
        last = null;
        return temp;
    }
    last.prev.next = null;
    last = last.prev;
    return temp;
}

public DoubleNode deleteKey(int key){
    //删除对应值为key的结点
    DoubleNode current = first;
    while (current != null && current.data != key){
        current = current.next;
    }
    //找到值为key的结点,如果没有值为key的节点或者链表为空那current就为null,也返回null
    if (current == null){
        //没找到,返回null
        return null;
    }else {
        //找到了,current.data == key,current结点就是key结点,分三种情况讨论:
        //如果key节点是第一个结点
        if (current == first){
            deleteFirst();
        }else if (current == last){
            deleteLast();
        }else {
            //key结点是中间节点时
            current.prev.next = current.next;
            current.next.prev = current.prev;

image.png

        }
    }
    return current;//返回被删除节点
}

单双链表的教程到此结束了。如有启发,欢迎点个赞鼓励鼓励小萌新。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值