看了这篇,手撕链表不再是难事

链表的概念

链表是一种物理存储结构上非连续存储结构 ,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。

不像数组,相邻的两个元素不仅逻辑上连续,物理上也连续

换句话说,由若干个链表节点构成的对象就称为链表

我们可以把链表类比成火车,每一列火车由若干个车厢组成,每个车厢就是一个Node结点,由多个Node结点组成的对象就是链表对象。火车的不同车厢之间都是通过挂钩连接的,当两个车厢之间脱钩之后,两个车厢就没有任何关系了。

在这里插入图片描述

那么怎么这一个一个的结点是怎样链接起来呢?在Java中他是通过引用所指向的地址来链接起来的。

假设咱们现在每节火车车厢只保存一个int值,这个值就是我们要存储的数据,每节车厢还要保存下一节车厢的地址

那么在JAVA中怎么定义结点呢?

class Node{
    //存放结点的值
    int val;
    //存放下一结点地址
    Node next;
    public Node(int val) {
        this.val = val;
    }

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

实际中链表链表的结构非常多样包括单向,双向,带头,不带头 ,接下来就让我们看看一个不带头的单链表如何实现?

单链表的创建

public class SingleLinedList {
    //链表的头结点
    private Node head=null;
    //当前链表中Node节点的个数 = 有效数值的个数
    private int size=0;
    class Node{
        //存放结点的值
        int val;
        //存放下一结点地址
        Node next;
        public Node(int val) {
            this.val = val;
        }

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

    }
}

如图所示:我们定义了一个单链表类,同时将结点类作为链表的内部类。这样就创建了一个基本的链表结构。

此外,我们还可以通过重写toString方法,遍历链表,实现链表的打印操作

那么如何遍历一个链表的每个节点?

从当前头节点出发。一次取出每个节点值,然后通过next引用走到下一个节点,直到走到链表的末尾(next = null)

public String toString(){
    //遍历链表
    String ret="";
    Node x=head;
    while (x!=null){
    ret+=x.val;
    ret+="->";
    x=x.next;
    }
    ret+="Null";
    return ret;
}

注意:

遍历过程中,不能直接使用head引用,因为直接使用head引用在一次遍历后链表就丢了,我们可以使用一个临时变量x暂存head的地址

在这里插入图片描述

单链表的增删改查

在创建好一个单链表后,我们就可以对单链表进行增删改查的操作了。

 
    // 在在链表头插入元素
    public void addFirst(int val) { }
 
    //在链表的指定下标中插入元素
    public void addIndex(int index, int val) { }
   
    //删除指定下标的元素,返回删除前元素
    public void remove(int index) { }
 
    // 删除链表中第一个值为val的元素
    public void removeValueOnce(int val) { }

	//删除链表中所有值为val的元素
	public void removeAllValue(int val) { }
        
    //修改索引为index位置的结点值为newVal,返回修改前的节点值
    public int set(int index,int newVal){ }

	//查找第一个值为val的结点索引
	public int getByValue(int val) { }
	
	//查找索引为index位置的结点值
	public int get(int index){ }

    // 判断元素key是否在当前链表中
    public boolean contains(int val) { }
 

接下来让我们看看这些操作是如何实现的吧

新增结点

  • 头插
public void addFirst(int val){
    Node newNode=new Node(val);
    newNode.next = head;    // 直接将该结点node变成新的头结点
    head = newNode;
    size++;
}
  • 指定下标插入

想要在index位置插入新结点,因为单链表只能从前往后遍历,我们需要找到要插入结点的前驱结点。我们可以让prev引用从头结点开始走index-1步,这样就找到了待插入结点的前驱结点

 public void addIndex(int index,int val) {
        // 1.若index位置非法的
        if(index<0 || index>size){
            System.out.println("index is illegal");
            return;
        }

        // 2.插入任意位置要找到前驱结点,但是链表中有一个结点没有前驱
        // 头结点没有前驱!!!
        if(index==0){
            addFirst(val);
        }else {
            // 3.当前索引合法且不是在数组的头部插入,就要找到插入位置的前驱结点
            Node prev=head;
            Node newNode=new Node();
            newNode.val=val;

            for (int i = 0; i < index-1; i++) {
                prev=prev.next;

            }
            // 此时prev一定指向了待插入节点的前驱
            newNode.next=prev.next;
            prev.next=newNode;
            size++;
		}

}

在这里插入图片描述

删除结点

在链表中,想要删除一个结点其实就是让该结点从链表中分离出来(没有其他任何的结点指向他 )

比如我们想删除1下标的结点,只需要找到1下标的前一个结点,也就是0下标。然后将0下标的next域里不再储存1下标的结点地址,而改成储存1下标的下一个结点->2下标的结点地址。这样就相等于1下标的结点被孤立下来了(就相等于是删除了)

  • 指定下标删除,返回删除前的元素
public int remove(int index){
    if (index>=0 && index<size){
        //删除头结点
        if(index==0){
            Node x=head;
            int oldVal=head.val;
            head=head.next;
            x.next=null;//可以省略
            size--;

            return oldVal;
        }else {
            Node prev=head;
            for (int i = 0; i < index-1; i++) {
                prev=prev.next;
            }
            Node x=prev.next;
            int oldVal=x.val;
            prev.next=x.next;
            x.next=null;//可以省略
            size--;

            return oldVal;
        }


    }else {
        System.out.println("index is illegal");
        return -1;
    }



}

在这里插入图片描述

  • 删除链表中第一个值为val的元素
public void removeValueOnce(int val) {
        //链表为空
        if(head==null){
            System.out.println("LinedList is empty");
            return;
        }
        //要删除的是头结点
        if(head.val==val){
            Node x=head;
            head=head.next;
            x.next=null;
        }else {
            //找前驱结点
            Node prev=head;
            while (prev.next!=null){
                //有后继结点
                if(prev.next.val==val){
                    //此时后继结点就是要删除的结点
                    Node x=prev.next;
                    prev.next=x.next;
                    x.next=null;
                    return;
                }
                prev=prev.next;

            }
        }
        size--;

    }
  • 删除链表中所有值为val的元素
 public void removeAllValue(int val) {
     while (head != null && head.val == val) {
         // 头节点就是待删除节点
         Node x = head;
         head = head.next;
         x.next = null;
         size --;
     }
     // 头节点一定不是待删除的节点
     // 判空
     if (head == null) {
         // 链表删完了
         return;
     }else {
         Node prev = head;
         while (prev.next != null) {
             // 至少还有后继结点
             if (prev.next.val == val) {
                 // 此时prev.next就是待删除的结点
                 Node node = prev.next;
                 prev.next = node.next;
                 node.next = null;
                 size --;
             }else {
                 // 只有当prev.next.val != val才能移动prev指针!
                 prev = prev.next;
             }
         }
     }
 }

修改链表

  • 修改索引为index位置的结点值为newVal,返回修改前的节点值
public int set(int index,int newVal){
    if(index>=0 && index<size){
        Node x=head;
        for (int i = 0; i < index; i++) {
            x=x.next;
        }
        int oldVal=x.val;
        x.val=newVal;
        return oldVal;

    }
    System.out.println("index is illegal");
    return -1;
}

查找链表

  • 查找第一个值为val的结点索引
public int getByValue(int val) {
    int index=0;
    Node pre=head;
    while (pre.next!=null){
        if(pre.next.val==val){
            return index;
        }
        pre=pre.next;
        index++;
    }

    // 说明当前链表中根本就没有值为val的结点,返回-1,表示不存在
    return -1;
}
  • 查找索引为index位置的结点值
public int get(int index) {
    // 1.判断index的合法性
    if(index>=0 && index<size){
        Node x=head;
        for (int i = 0; i < index; i++) {
            x=x.next;
        }
        return x.val;
    }
    return -1;
}
  • 查找值为val的结点是否在链表中
public boolean contains(int val) {
    int index = getByValue(val);
    return index != -1;
}

双向链表

单向链表默认只能从链表的头部遍历到链表的尾部,实际的应用较为少见,太局限。

双向链表:对于该链表的任意节点,既可以向后走,也可以向前走。双向链表在实际工程中应用非常广泛,是使用链表这个结构的首选。

为什么双向链表既能往后遍历也能往前遍历呢?让我们看看双向链表结点的定义

class DoubleNode{
    //前驱结点
    DoubleNode prev;
    //结点值
    int val;
    //后继结点
    DoubleNode next;

    public DoubleNode() {
    }

    public DoubleNode(DoubleNode prev, int val, DoubleNode next) {
        this.prev = prev;
        this.val = val;
        this.next = next;
    }

    public DoubleNode(int val) {
        this.val = val;
    }
}

每个结点既保存了下一个节点的地址,又保存了上一个节点的地址,这样双向链表就既能往后遍历也能往前遍历

public class DoubleLinkedList {
    //有效结点个数
    private int size;
    //当前链表头结点
    private DoubleNode head;
    //当前链表尾结点
    private DoubleNode tail;
}
    

双向链表的增删改查

新增结点

  • 头插法
//头插法
public void addFirst(int val){
    DoubleNode node=new DoubleNode(null,val,head);
    //链表为空
    if(tail==null){

        tail=node;
    }else {//链表不为空

        head.prev=node;
    }
    // 对于头插来说,最终无论链表是否为空。head = node
    head=node;
    size++;

}
  • 尾插法
public void addLast(int val){
    DoubleNode node=new DoubleNode(tail,val,null);
    if(head==null){
        head=node;
    }else {
        tail.next=node;
    }
    //尾插最终tail都会等于node
    tail=node;
    size++;
}
  • 在index位置插入

都是找前驱结点,此时是不是可以灵活一点?之前只能从head开始向后遍历,假设我们现在有100个结点,我想在97号结点插入新元素,从尾结点向前遍历就会快很多,我们可以创建一个私有方法来根据索引找到结点

private DoubleNode node(int index){
    //根据索引找到对应结点
    DoubleNode x=null;
    if(index<=size/2){
        x=head;
        for (int i = 0; i < index; i++) {
            x=x.next;
        }
    }else {
        x=tail;
        for (int i=size-1; i>index; i--){
            x=x.prev;
        }
    }
    return x;
}
public void add(int index,int val){
    if(index < 0 || index > size){
    System.out.println("index is illegal");
    return;
    }
    if(index==0){
    addFirst(val);
    }else if (index==size){
    addLast(val);
    }else {
    //中间位置插入
    DoubleNode pre=node(index-1);
    DoubleNode next=pre.next;
    DoubleNode cur=new DoubleNode(pre,val,next);
    next.prev=cur;
    pre.next=cur;
    size++;
    }
}

在这里插入图片描述

注意:要把后继结点处理完才能移动前驱结点,操作1,2,3的顺序无所谓,4操作一定要最后执行

删除结点

这里运用到分治思想:我们可以先处理前驱节点的问题,完全不管后继,等前驱部分处理完毕再单独处理后继部分,由此我们创建一个私有方法,传入要删除的结点,将其从链表中删除

private void unlink(DoubleNode node){
    //1.前空后不空
    //2.前不空后空
    //3.前后都为空
    //4.前后都不空
    DoubleNode pre=node.prev;
    DoubleNode next=node.next;
    //先处理前驱
    if(pre==null){
        //前驱为空,node为头结点
        head=next;
    }else {
        pre.next=next;
        node.prev=null;
    }
    //处理后继
    if(next==null){
        tail=pre;
    }else {
        next.prev=pre;
        node.next=null;
    }
    size--;

}
  • 指定下标删除,返回删除前的元素
public void removeIndex(int index){
    if(index<0 || index>=size){
        System.out.println("index is illegal");
        return;
    }
    DoubleNode node=node(index);
    unlink(node);
}
  • 删除一次值为val的结点
public void removeOnce(int val){
    DoubleNode x=head;
    while (x!=null){
        if(x.val==val){
            unlink(x);
            break;
        }
        x=x.next;

    }
}
  • 删除所有值为val的结点
public void removeAll(int val){
    DoubleNode x=head;
    while (x!=null){
        if(x.val==val){
            //暂存一下x后继结点的地址
            DoubleNode next=x.next;
            unlink(x);
            x=next;

        }else {
            x=x.next;
        }
    }
}

修改链表

  • 修改index处的值为newVal,返回index
public int set(int index,int newVal) {
    if(index<0 || index>=size){
        System.out.println("index is illegal");
        return -1;
    }
    DoubleNode node=node(index);
    int oldVal=node.val;
    node.val=newVal;
    return oldVal;
}

查询链表

  • 查找索引为index位置的结点值
//获取index处结点的值
public int get(int index) {
    if(index<0 || index>=size){
        System.out.println("index is illegal");
        return -1;
    }
    DoubleNode node=node(index);
    int val=node.val;
  return val;
}

完整代码

单链表:

package seq;

public class DoubleLinkedList {
    //有效结点个数
    private int size;
    //当前链表头结点
    private DoubleNode head;
    //当前链表尾结点
    private DoubleNode tail;

    //头插法
    public void addFirst(int val){
        DoubleNode node=new DoubleNode(null,val,head);
        //链表为空
        if(tail==null){

            tail=node;
        }else {//链表不为空

            head.prev=node;
        }
        // 对于头插来说,最终无论链表是否为空。head = node
        head=node;
        size++;

    }

    //尾插法
    public void addLast(int val){
        DoubleNode node=new DoubleNode(tail,val,null);
        if(head==null){
            head=node;
        }else {
            tail.next=node;
        }
        //尾插最终tail都会等于node
        tail=node;
        size++;
    }

    //在index位置插入元素val
    public void add(int index,int val){
        if(index < 0 || index > size){
            System.out.println("index is illegal");
            return;
        }
        if(index==0){
            addFirst(val);
        }else if (index==size){
            addLast(val);
        }else {
            //中间位置插入
            DoubleNode pre=node(index-1);
            DoubleNode next=pre.next;
            DoubleNode cur=new DoubleNode(pre,val,next);
            next.prev=cur;
            pre.next=cur;
            size++;
        }


    }
    //根据索引找到对应结点
    private DoubleNode node(int index){
        DoubleNode x=null;
        if(index<=size/2){
            x=head;
            for (int i = 0; i < index; i++) {
                x=x.next;
            }
        }else {
            x=tail;
            for (int i=size-1; i>index; i--){
                x=x.prev;
            }
        }
        return x;
    }
    //根据索引删除元素
    public void removeIndex(int index){
        if(index<0 || index>=size){
            System.out.println("index is illegal");
            return;
        }
        DoubleNode node=node(index);
        unlink(node);

    }
    //头删
    public void removeFirst(){
        removeIndex(0);
    }
    //尾删
    public void removeLast(){
        removeIndex(size-1);
    }
    //传入一个结点,将其从链表中删除
    private void unlink(DoubleNode node){
        //1.前空后不空
        //2.前不空后空
        //3.前后都为空
        //4.前后都不空
        DoubleNode pre=node.prev;
        DoubleNode next=node.next;
        //先处理前驱
        if(pre==null){
            //前驱为空,node为头结点
            head=next;
        }else {
            pre.next=next;
            node.prev=null;
        }
        //处理后继
        if(next==null){
            tail=pre;
        }else {
            next.prev=pre;
            node.next=null;
        }
        size--;

    }
    //删除一次值为val的结点
    public void removeOnce(int val){
        DoubleNode x=head;
        while (x!=null){
            if(x.val==val){
                unlink(x);
                break;
            }
            x=x.next;

        }
    }
    //删除所有值为val的结点
    public void removeAll(int val){
        DoubleNode x=head;
        while (x!=null){
            if(x.val==val){
                //暂存一下x后继结点的地址
                DoubleNode next=x.next;
                unlink(x);
                x=next;

            }else {
                x=x.next;
            }


        }
    }
    //获取index处结点的值
    public int get(int index) {
        if(index<0 || index>=size){
            System.out.println("index is illegal");
            return -1;
        }
        DoubleNode node=node(index);
        int val=node.val;
      return val;
    }
    //修改index处的值为newVal,返回index
    public int set(int index,int newVal) {
        if(index<0 || index>=size){
            System.out.println("index is illegal");
            return -1;
        }
        DoubleNode node=node(index);
        int oldVal=node.val;
        node.val=newVal;
        return oldVal;
    }
    public String toString(){
        String ret="";

        for (DoubleNode x=head;x!=null;x=x.next){
            ret+=x.val;
            ret+="->";

        }
        ret+="Null";
        return ret;
    }


}
class DoubleNode{
    //前驱结点
    DoubleNode prev;
    //结点值
    int val;
    //后继结点
    DoubleNode next;

    public DoubleNode() {
    }

    public DoubleNode(DoubleNode prev, int val, DoubleNode next) {
        this.prev = prev;
        this.val = val;
        this.next = next;
    }

    public DoubleNode(int val) {
        this.val = val;
    }
}

双向链表:

package seq;

public class DoubleLinkedList {
    //有效结点个数
    private int size;
    //当前链表头结点
    private DoubleNode head;
    //当前链表尾结点
    private DoubleNode tail;

    //头插法
    public void addFirst(int val){
        DoubleNode node=new DoubleNode(null,val,head);
        //链表为空
        if(tail==null){

            tail=node;
        }else {//链表不为空

            head.prev=node;
        }
        // 对于头插来说,最终无论链表是否为空。head = node
        head=node;
        size++;

    }

    //尾插法
    public void addLast(int val){
        DoubleNode node=new DoubleNode(tail,val,null);
        if(head==null){
            head=node;
        }else {
            tail.next=node;
        }
        //尾插最终tail都会等于node
        tail=node;
        size++;
    }

    //在index位置插入元素val
    public void add(int index,int val){
        if(index < 0 || index > size){
            System.out.println("index is illegal");
            return;
        }
        if(index==0){
            addFirst(val);
        }else if (index==size){
            addLast(val);
        }else {
            //中间位置插入
            DoubleNode pre=node(index-1);
            DoubleNode next=pre.next;
            DoubleNode cur=new DoubleNode(pre,val,next);
            next.prev=cur;
            pre.next=cur;
            size++;
        }


    }
    //根据索引找到对应结点
    private DoubleNode node(int index){
        DoubleNode x=null;
        if(index<=size/2){
            x=head;
            for (int i = 0; i < index; i++) {
                x=x.next;
            }
        }else {
            x=tail;
            for (int i=size-1; i>index; i--){
                x=x.prev;
            }
        }
        return x;
    }
    //根据索引删除元素
    public void removeIndex(int index){
        if(index<0 || index>=size){
            System.out.println("index is illegal");
            return;
        }
        DoubleNode node=node(index);
        unlink(node);

    }
    //头删
    public void removeFirst(){
        removeIndex(0);
    }
    //尾删
    public void removeLast(){
        removeIndex(size-1);
    }
    //传入一个结点,将其从链表中删除
    private void unlink(DoubleNode node){
        //1.前空后不空
        //2.前不空后空
        //3.前后都为空
        //4.前后都不空
        DoubleNode pre=node.prev;
        DoubleNode next=node.next;
        //先处理前驱
        if(pre==null){
            //前驱为空,node为头结点
            head=next;
        }else {
            pre.next=next;
            node.prev=null;
        }
        //处理后继
        if(next==null){
            tail=pre;
        }else {
            next.prev=pre;
            node.next=null;
        }
        size--;

    }
    //删除一次值为val的结点
    public void removeOnce(int val){
        DoubleNode x=head;
        while (x!=null){
            if(x.val==val){
                unlink(x);
                break;
            }
            x=x.next;

        }
    }
    //删除所有值为val的结点
    public void removeAll(int val){
        DoubleNode x=head;
        while (x!=null){
            if(x.val==val){
                //暂存一下x后继结点的地址
                DoubleNode next=x.next;
                unlink(x);
                x=next;

            }else {
                x=x.next;
            }


        }
    }
    //获取index处结点的值
    public int get(int index) {
        if(index<0 || index>=size){
            System.out.println("index is illegal");
            return -1;
        }
        DoubleNode node=node(index);
        int val=node.val;
      return val;
    }
    //修改index处的值为newVal,返回index
    public int set(int index,int newVal) {
        if(index<0 || index>=size){
            System.out.println("index is illegal");
            return -1;
        }
        DoubleNode node=node(index);
        int oldVal=node.val;
        node.val=newVal;
        return oldVal;
    }
    public String toString(){
        String ret="";

        for (DoubleNode x=head;x!=null;x=x.next){
            ret+=x.val;
            ret+="->";

        }
        ret+="Null";
        return ret;
    }


}
class DoubleNode{
    //前驱结点
    DoubleNode prev;
    //结点值
    int val;
    //后继结点
    DoubleNode next;

    public DoubleNode() {
    }

    public DoubleNode(DoubleNode prev, int val, DoubleNode next) {
        this.prev = prev;
        this.val = val;
        this.next = next;
    }

    public DoubleNode(int val) {
        this.val = val;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值