双向链表的模拟实现

一:什么是双向链表

1:什么是双向链表

双向链表,顾名思义,通过一个节点,可以知道它的前一个节点和后一个节点
如下图所示:
在这里插入图片描述一个节点有三个域:
数值域:用来存放具体的数值;
prev域:用来存放当前节点的前一个节点的地址;
next域:用来存放当前节点的下一个节点的地址;

一个节点的实现:
当我们定义一个节点的时候,并不知道该节点的前一个节点和后一个节点是谁,所以构造方法中只有一个val参数.

  static class ListNode{
        public int val;//数值域
        public ListNode prev;//prev域
        public ListNode next;//next域
        public ListNode(int val) {
            this.val = val;
        }
    }

2:双向链表的结构

与单链表相似,双向链表也是由一个一个的节点组合的,但不同的是:单链表只有一个属性:头结点(head),而双向链表有两个属性:头结点(head)和尾节点(last).

 static class ListNode{
        public int val;
        public ListNode prev;
        public ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
    ListNode head;
    ListNode last;

二:IList接口

public interface IList {
    //头插法
    void addFirst(int data);
    //尾插法
    void addLast(int data);
    //任意位置插入,第一个数据节点为0号下标
     void addIndex(int index,int data);
    //查找是否包含关键字key是否在单链表当中
     boolean contains(int key);
    //删除第一次出现关键字为key的节点
    void remove(int key);
    //删除所有值为key的节点
     void removeAllKey(int key);
    //得到双向链表的长度
    int size();
    //打印双向链表的数值域
    void display();
    //清空双向链表
     void clear();
}

在这里定义了一些双向链表的常用方法,下面我们将逐步实现这些方法。

三:方法的具体实现:

1:display()

思路:
遍历双向链表的每个节点,并打印每个节点的val域;

在这里有两点:
1:什么时候链表遍历完了?
当cur==null时,说明链表遍历完了
2:当前节点如何走向下一个节点:
cur=cur.next

    @Override
    public void display() {
        ListNode cur=head;
        while(cur!=null){
            System.out.print(cur.val+" ");
            cur=cur.next;
        }
        System.out.println();

    }

2:size()

思路
定义一个计数器:count,
还是遍历链表,每遍历一个节点,count++;
当链表遍历完了,返回的count值就是链表的长度

public int size() {
        ListNode cur=head;
        int count=0;
        while(cur!=null){
           count++;
            cur=cur.next;
        }
    return count;

3:contains()

思路:
遍历链表,并比较节点的值是否与key相同,相同返回true;不同cur=cur.next(遍历下一个节点)

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

4:addFirst(int data)

头插法:
顾名思义:就是把节点插入到头结点的位置处;
如果链表为空(head==null):插入的节点既是头结点又是尾结点,所以head和last都指向要插入的节点;
如果链表不为空:node.next=head;head.prev=node;head=node;

public void addFirst(int data) {
        ListNode node =new ListNode(data);
        if(head==null){
           head=node ;
            last=node ;

        }else{
            node.next = head;
            head.prev=node;
            head=node;

        }

    }

4:addLast(int data)

尾插法:
就是把新增加的元素放在最后一个节点
与头插法相似:
如果链表为空(head==null):插入的节点既是头结点又是尾结点,所以head和last都指向要插入的节点;
如果链表不为空:last.next=node;node.prev=last;last=node;

  public void addLast(int data) {
        ListNode node =new ListNode(data);
        if(head==null){
            head=node ;
            last=node ;

        }else{
           last.next=node;
           node.prev=last;
          last=node;
        }
    }

4:addIndex(int index, int data):

任意位置插入:
index表示要插入的位置,data表示要插入的数据
首先要判断index是否合法:index0代表头插;indexindexsize(),表示要插入的位置是最后一个节点,可以想象成链表是从0下标开始的(虽然链表没有下标这个概念),如果index不在这个范围内,就是不合法的.

如果index0;直接调用头插法addFirst(data)即可;
如果index
size(),直接调用尾插法addLast(data)即可;
其他情况下:就代表在链表的中间位置插入:
首先我们定义一个cur节点:代表要插入的节点位置,但我们定义的时候,cur=head,那么我们就让cur走index步,比如:index=2;就让cur走到2位置处:具体实现代码:

 private ListNode findIndex(int index){
        ListNode cur =head;
        while(index!=0){
            cur=cur.next;
            index--;
        }
        return cur;
    }

当cur走到要增加节点的位置处:我们然后只需修改四个指向即可;
总结:当插入数据的时候,先绑定后面的,先修改next域,再修改prev域
在这里插入图片描述

public void addIndex(int index, int data) {
        if(index<0||index>size()){
            System.out.println("index错误"+index);
            return;
        }
        if(index==0){
            addFirst(data);
            return;
        }
        if(index==size()){
            addLast(data);
            return;
        }

        ListNode node = new ListNode(data);
        ListNode cur=findIndex(index);
        node.next=cur;
        cur.prev.next=node;
        node.prev=cur.prev;
        cur.prev=node;
    }
    private ListNode findIndex(int index){
        ListNode cur =head;
        while(index!=0){
            cur=cur.next;
            index--;
        }
        return cur;
    }

5:remove(int key)

删除第一次出现关键字为key的节点
思路:遍历链表的每个节点,当当前节点的val==key时,执行删除操作,删除完成返回即可,如果当前节点的val!=key,cur=cur.next,继续寻找即可.
1:如果要删除的节点是头结点:只需head.next.prev=null;head=head.next;
即可
在这里插入图片描述2:如果要删除的节点是尾节点:
只需cur.prev.next=null;last=last.prev;即可
在这里插入图片描述

如果要删除的节点是在链表中间的数据:
比如:(如下图)删除34所在的节点
只需修改两个指向即可
在这里插入图片描述

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

                }
                return;
            }else{
                cur = cur.next ;
            }
        }

    }

注意:当链表只有一个节点,且该节点的val=key,那么删除头节点的时候就会空指针异常:
因为 **head=head.next;**此时由于只有一个节点,那么head=null; 而head=null,这个语句:**head.prev=null;**就会报异常;
所以只有当head!=null时,才会执行这个语句
所以:当链表只有一个节点,且该节点的val=key,head=head.next;last=null;(唯一的节点也被删除了,last也要为空)
当链表除了要删除的该节点还有其他节点时:head=head.next;head.prev=null;
综上:

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

                } else if (cur==last) {
                    last=last.prev;
                    last.next=null;
                    //cur.prev.next=cur.next;
                }else{
                    cur.prev.next=cur.next;
                    cur.next.prev=cur.prev;

                }
                return;
            }else{
                cur = cur.next ;
            }
        }

    }

6:removeAllKey(int key)

删除所有值为key的节点
有了删除一个节点的基础,删除全部节点就很简单了;在删除某个节点后不要返回,让节点继续往后走就行了.

  public void removeAllKey(int key) {
        ListNode cur=head;
        while(cur!=null) {
            if (cur.val == key) {
                if (cur == head) {
                    head = head.next;
                    if (head != null) {
                        head.prev = null;
                    } else {
                        last = null;
                    }

                } else if (cur == last) {
                    last = last.prev;
                    last.next = null;
                    //cur.prev.next=cur.next;
                } else {
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;

                }

            }
            cur = cur.next;


        }
    }

6: clear()

清空链表:
思路:
遍历,让每个节点的prev域和next域为空即可;
但当next域为null时,当前节点就无法走到下一个节点了,所以在置空之前,先保存cur.next,即ListNode curNext=cur.next;

public void clear() {
        ListNode cur=head;
        while(cur!=null){
            ListNode curNext=cur.next;
            cur.prev=null;
            cur.next=null;
            cur=curNext;
        }
        head=null;
        last=null;

    }
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十一.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值