自己实现链表的基本操作方法(详细)(Java实现)

目录

1.add( ) 方法:

(1)boolean add( Long e) :链表尾插元素 e

(2)boolean add(int index, Long e):根据下标插入元素 e 

2. remove () 方法: 

(1)public Long remove (int index) 删除指定下标位置结点,返回被删除的结点元素值:

 (2)public boolean remove (Long e) 从前向后找,删除第一个遇到的 e 的结点:

3. public Long get ( int index) 获取指定下标元素值:

4. public Long set ( int index, Long e ) 将指定下标位置的元素值改为 e ,并返回原来的元素值:

5. public void clear () 清空链表中的所有结点:

6.  public int indexOf ( Long e) 从前向后,查找链表中指定元素的下标:

7. public int lastindexOf ( Long e) 从后向前,查找链表中指定元素的下标:

8. public boolean contains ( Long e) 判断链表中是否包含元素 e :

9. public boolean isEmpty () 判断链表是否为空链表:


因为链表类也实现了线性表接口的方法,所以链表的基本操作方法和顺序表一样。这里我们定义链表中的元素类型为包装类(体现对象的形式而不是基本类型)

  这里提前定义了 MyNode 类,其中的属性为:Long val;MyNode prev;MyNode next;三个属性,一个元素值 val ,一个指向前一个结点的引用 prev,一个指向后一个结点的引用 next。

public class MyNode {
    public Long val;
    MyNode prev;
    MyNode next;

    public MyNode(Long val) {
        this.val = val;
        this.next = this.prev = null;
    }

}

  在 MyLinkedList 类(自定义链表类)中实现自定义基本方法,其中包括属性 MyNode head;MyNode last;int size;三个属性,一个是引用 head 用于指向链表第一个结点,一个是引用 last 用于指向链表最后一个结点。

(代码放在最后了)

1.add( ) 方法:

(1)boolean add( Long e) :链表尾插元素 e

 先分析尾插操作的主要步骤:

 ① 将链表的最后一个结点的 next 指向要插入的结点对象 node 。 

 ② 将要插入的结点的 prev 指向链表最后一个结点对象。

 ③ 将要插入的结点的 next 指向 null 。(也可以不写,因为初始化时候已经定义过了)

 ④ 更新链表的 last 引用,要让 last 始终指向链表中的最后一个结点。

 ⑤ 最后链表内结点数 size 加 1。

再考虑可能发生的情况: 

① 链表 size = 0 时,链表中没有结点:尾插后链表中只有被插入的结点,直接将链表的头结点引用和尾结点引用指向被插入结点即可,此时 node 结点的 prev 和 next 都指向 null。

② 链表 size > 0 时,使用主要步骤进行操作即可。

代码: 

public boolean add(Long e) {
        MyNode node = new MyNode(e);

        if (size == 0){
            this.head = this.last = node;
            node.prev = null; //可以不写,初始化已经置空过
        }else{
            this.last.next = node;
            node.prev = this.last;
            this.last = node;
        }
        size++;
        return true;
    }

(2)boolean add(int index, Long e):根据下标插入元素 e 

主要步骤:改变 4 个引用

① curNode 要移动到指定下标处。目标下标为 0 ,跳 0 下;目标下标为 2 ,跳 2 下;目标下标为 3 ,跳 3 下。总结规律可得,跳 index 下即可到目标下标处。(循环次数)

② 因为要将 e 插入指定下标处,所以需要指定下标处的上一个结点,所以提前记录 prevNode 指向指定下标的上一个结点。

③ 进行 prevNode 和 curNode 引用指向的改变。

④ 进行 node 引用指向的改变 。

考虑可能的情况:

头插:(和尾插思考方式一样)

代码:

public void add(int index, Long e) {
        MyNode node = new MyNode(e);
        node.prev = null;
        if (index < 0 || index > size){
            throw new RuntimeException("下标不合法");
        }
        if (size == 0){
            this.head = this.last = node;
            node.next = null;
            return;
        }
        if (size == 1){
            if (index == 0){
                addaFirst(e);
                return;
            }else {
                add(e);
                return;
            }
        }
        //代码如果执行到这里,只剩下一种情况,就是 size > 1
        //所以可以不使用 if 语句进行判断 size 的情况
        if (index == 0){
            addaFirst(e);
            return;
        }else if(index == size){
            add(e);
            return;
        }
        //代码执行到这里,说明以上的情况都不是。则此时的情况应使用改变 4 个引用的操作。
        MyNode curNode = this.head;
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }
        MyNode prevNode = curNode.prev;
        prevNode.next = node;
        curNode.prev = node;
        node.prev = prevNode;
        node.next = curNode;

        size++;
        return;
    }

其中的 addFirst ()方法:头插

//头插(为了代码的方便)
    public boolean addaFirst(Long e){
        MyNode node = new MyNode(e);
        node.prev = null;
        if (size == 0){
            this.head = this.last = node;
            node.next = null;
        }else {
            this.head.prev = node;
            node.next = this.head;
            this.head = node;
        }
        this.size++;
        return true;
    }

2. remove () 方法: 

(1)public Long remove (int index) 删除指定下标位置结点,返回被删除的结点元素值:

主要思路:

① 删除这个结点,需要让它的前一个结点越过它,指向它的后一个结点。所以需要定义三个引用

:curNode(指向指定位置结点)、prevNode(指向它的前一个结点)、nextNode(指向它的后一个结点)。

② 改变 2 个引用:让 prevNode 的 next 指向 nextNode ;让 nextNode 的 prev 指向 prevNode 。

③ size - 1。

④ 注意:因为要返回被删除的元素值,所以在改变引用前,需要提前把原来的元素值存起来。 

头删和尾删:

 

考虑可能出现的情况: 

 

代码: 

//删除指定下标位置的结点,返回被删除的元素值
    @Override
    public Long remove(int index) {
        if (index < 0 ||index >= size){
            //包括 size = 0 的情况
            throw new RuntimeException("当前下标不合法");
        }
        if (size == 1){
            Long e = this.head.val;
            this.head = this.last = null;
            size = 0;
            return e;
        }

        if (index == 0){
            //头删
            Long e = this.head.val;
            this.head = this.head.next;
            this.head.prev = null;
            size--;
            return e;
        }
        if (index == size - 1){
            Long e = this.last.val;
            this.last = this.last.prev;
            this.last.next = null;
            size--;
            return e;
        }
        //代码执行到这里,说明以上情况都不是,此时:
        // size > 1 && index > 0 && index < size - 1
        MyNode curNode = this.head;
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }
        MyNode prevNode = curNode.prev;
        MyNode nextNode = curNode.next;
        Long e = curNode.val;

        prevNode.next = nextNode;
        nextNode.prev = prevNode;

        size--;
        return e;
    }

 (2)public boolean remove (Long e) 从前向后找,删除第一个遇到的 e 的结点:

主要思路:跟上面的大同小异,主要是可能的情况不同。

① 注意:在元素结点个数 size = 1 时,进行主要思路的改变 2 个引用的操作时,要特别注意 尾结点 last 的改变。

可能出现的情况:

 代码:

//从前向后找,删除第一个遇到的 e
    @Override
    public boolean remove(Long e) {
        MyNode curNode = this.head;
        //将有元素 e 的情况都放在循环里,如果循环结束后,还没有返回,则说明该链表中没有元素 e ,删除失败,返回 false
        for (int i = 0; i < size; i++) {
            if (curNode.val.equals(e)) {
                if (i == 0){
                    //进行头删
                    this.head = this.head.next;
                    //此时 size 可能 = 1,也可能 > 1
                    //本来头删的下一步为: this.head.prev = null ;
                    //但是如果 size = 1 的话,this.head = this.head.next 后,this.head = null
                    //如果执行这句话,就会发生空指针异常的错误。所以我们还要分一次情况分别讨论
                    if (this.head != null){
                        this.head.prev = null;
                        size--;
                        return true;
                    }else {
                        // size = 1  链表中只有一个元素的情况,执行完 this.head = this.head.next 后为空链表
                        this.last = null;
                        size--;
                        return true;
                    }
                }

                if (i == size - 1){
                    //进行尾删
                    this.last = this.last.prev;
                    //这里也要思考,有没有上面的情况出现
                    //不会出现上面的情况,因为如果 size = 1 的话,e 只能出现在 i = 0 的时候。即 e 的下标为 0,进行头删操作
                    //所以如果代码走到这里,size 至少为 2 并且 i = 1,即尾删操作
                    this.last.next = null;
                    size--;
                    return true;
                }

                //代码走到这里,只剩下一种情况,要删的元素在中间结点。
                //中间结点的特点是,既有前驱元素,也有后继元素,所以不用担心会出现空指针异常的错误出现
                MyNode prevNode = curNode.prev;
                MyNode nextNode = curNode.next;

                prevNode.next = nextNode;
                nextNode.prev = prevNode;
                size--;
                return true;
            }
            curNode = curNode.next;
        }
        return false;
    }

3. public Long get ( int index) 获取指定下标元素值:

主要思路:

① 涉及到下标传参,首先要考虑下标不合法的情况。

② 要将 curNode 结点移动到指定下标结点下,需要进行循环。循环后返回结点的 val 即可。

代码:

 //获取指定下标的元素值
    @Override
    public Long get(int index) {
        if (index < 0 || index >= size){
            throw new RuntimeException("下标不合法");
        }
        MyNode curNode = this.head;
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }
        return curNode.val;
    }

4. public Long set ( int index, Long e ) 将指定下标位置的元素值改为 e ,并返回原来的元素值:

主要思路:

① 考虑下标不合法的情况。

② 将引用 curNode 移动到指定下标结点处。

③ 将原来的 val 值提前记录,再改变结点对象的 val 值。

代码:

//将指定下标位置元素值换成 e ,并返回被修改的元素值
    @Override
    public Long set(int index, Long e) {
        if (index < 0 || index >= size){
            throw new RuntimeException("下标不合法");
        }
        MyNode curNode = this.head;
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }
        Long a = curNode.val;
        curNode.val = e;
        return a;
    }

5. public void clear () 清空链表中的所有结点:

主要思路:

将链表的头结点和尾结点都置空,同时结点个数 size 置零。

代码:

 @Override
    public void clear() {
        this.head = this.last = null;
        size = 0;
    }

6.  public int indexOf ( Long e) 从前向后,查找链表中指定元素的下标:

主要思路: 

① 因为链表和链表结点中,没有存储下标的属性,所以需要我们自己定义一个变量 i 来表示下标。

② 进行循环判断。

③ 每次在将 curNode 指向下一个结点的同时,i 要进行加 1 的操作。( i 从 0 开始)

④ 如果找不到的话,返回 -1 。

代码:

 @Override
    public int indexOf(Long e) {
        MyNode curNode = this.head;
        int i = 0; //表示 curNode 此时的下标
        while (curNode != null){
            if (curNode.val.equals(e)){
                return i;
            }
            i++;
            curNode = curNode.next;
        }
        return -1;
    }

7. public int lastindexOf ( Long e) 从后向前,查找链表中指定元素的下标:

主要思路:跟从前向后查找一样,唯一区别是:这里的 i 应该从 size - 1 开始,依次递减。

代码:

 @Override
    public int lastindexOf(Long e) {
        MyNode curNode = this.last;
        int i = size - 1; // 表示 curNode 此时的下标
        while (curNode != null){
            if (curNode.val.equals(e)){
                return i;
            }
            i--;
            curNode = curNode.prev;
        }
        return -1;
    }

8. public boolean contains ( Long e) 判断链表中是否包含元素 e :

主要思路:只要链表中有元素 e 就返回 true ,所以我们可以直接调用 indexOf () 方法进行判断。

代码:

 @Override
    public boolean contains(Long e) {
        return indexOf(e) != -1;

    }

9. public boolean isEmpty () 判断链表是否为空链表:

主要思路: 空链表代表链表结点个数为 0 。

代码:

@Override
    public boolean isEmpty() {
        return size == 0;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值