Java进阶:自定义单向链表

目录

1. 关于ArrayList和LinkedList(顺序表和链表)

2. 自定义单向链表

3. 自定义链表Q&A


1. 关于ArrayList和LinkedList(顺序表和链表)

ArrayListLinkedList
别名线性顺序表线性链表
所属包java.util
支持数据类型引用类型
数据存储结构顺序存储链式存储
优点便于直接查找和修改便于新增和删除元素
缺点增删元素可能需要搬动大量的其余元素查找元素可能需要扫描较多的其余元素

解释:

顺序存储:ArrayList是用数组来存放数据的,故逻辑上相邻的元素,存储位置上必定相邻。元素的索引正是数组的下标。这也就决定了ArrayList可以随意且直接地读取其中任意一个元素,并可以修改,但增删元素在最糟糕情况之下,也就是在首位新增元素或删除元素时,后面所有元素都要搬家。

链式存储:LinkedList是用一个个结点对象来存放数据的,类库中的LinkedList是双向链表,故结点里面包含前驱指针(存放前一结点的内存地址)、元素值、后继指针(存放后一结点的内存地址)。故增加或删除元素时只需要修改指针指向的结点即可。但问题就出在必须通过头/尾结点“顺藤摸瓜”才能看到要找的元素。

2. 自定义单向链表

整体设计思路:

1. 链表元素都是由结点构成的,这种结点有两个用处:

1. 存数据

2. 存逻辑上相邻结点的指针

本次要做一个单向链表,结点只需要两个属性:

1. 当前元素值

2. 后驱指针

public class Node {
    Object data;//由于元素是引用类型,所以不管是什么类型都是Object的子类型
    Node next;
    public Node() {}
    public Node(Object elem) {//本次为了凸显结点指针移动的过程,没有写全参构造
        data = elem;
    }
}

2. 创建链表类继承List接口,属性中加入一个头结点(引导访问指针)和一个计数器(统计元素个数)

3. 运用所学知识尝试实现contains()、add()、get()等简单的方法

其中,add()、remove()是本次学习链表的重头戏,相信学过C语言版数据结构的同学们好多都被指针的移动给转懵了(包括本Reno),借此机会好好梳理下如何移动元素指针把新结点穿起来

package com.reno.d0811;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class LinkList implements List {
    Node head = new Node();//头结点可以引导我们开始访问数组元素,所以不用存数据
    private int size;//计数器,仅可通过函数读取链表长度
    //返回计数器的值
    @Override
    public int size() {
        return size;
    }

    //判断链表是不是空的
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    //判断链表中是否有与参数相匹配的元素
    @Override
    public boolean contains(Object o) {
        Node p = head;
        for (int i = 0; i < size; i++) {
            p = p.next;
            if (o.equals(p.data)) {
                return true;
            }
        }
        return false;
    }

 
    //此函数用于在链表尾部位置新增结点,属于插入结点的特殊情形
    @Override
    public boolean add(Object o) {
        add(size, o);
        return true;
    }

    //删除与参数列表匹配的第一个结点
    @Override
    public boolean remove(Object o) {
        if (!this.contains(o)) {//如果不包含与参数匹配的目标元素,就不用执行删除了
            return false;
        }
        Node p = head;
        //查找元素是否与参数中的值匹配
        for (int i = 0; i < size && !o.equals(p.next.data); i++) {
            p = p.next;//后移指针直到找到目标元素的前一位
        }
        p.next = p.next.next;//将待删除位置后一位的地址作为待删除元素前一位的后驱,以后访问时就可以跳过待删除元素了
        size--;//更新元素个数
        return true;
    }

   
    //查找指定元素的位置
    @Override
    public Object get(int index) {
        Node p = head;//创建一个指针指向头结点
        for (int i = 0; i <= index; i++) {
            p = p.next;//依次向后移动指针直到目标位置
        }
        return p.data;//指针刚好移到目标位置,直接返回元素的值
    }

    @Override
    public void add(int index, Object element) {
        Node p = head;//定义一个Node型指针默认指向头结点
        //找到上一个结点
        for (int i = 0; i < index; i++) {
            p = p.next;//移动指针
        }
        //1. 新建结点对象
        //2. 把元素存到结点data部分
        Node n = new Node(element);


        //3. 将后面结点的地址作为新结点的后驱指针,也就是把新结点和后面结点连起来
        n.next = p.next;
        //4. 当前结点地址作为前一结点的后驱指针,也就是把新结点和前面结点连起来
        p.next = n;
        size++;
    }
     /*实现List接口里面的其余函数就先不展示了*/
    
}

 4. 创建测试类,测试下自己定义的链表

package com.reno.d0811;

public class Runner {
    public static void main(String[] args) {
        LinkList l = new LinkList();
        l.add("Bob");
        l.add("Mary");
        l.add("Mike");
        l.add("Rich");
        l.add(0, "Reno");
        for (int i = 0; i < l.size(); i++) {
            System.out.println(l.get(i));
        }
        System.out.printf("当前链表长度%d\n", l.size());
        System.out.printf("-------------------------------------------------------\n");
        System.out.println(l.remove("Mike"));
        System.out.printf("-------------------------------------------------------\n");
        for (int i = 0; i < l.size(); i++) {
            System.out.println(l.get(i));
        }
        System.out.printf("当前链表长度%d\n", l.size());
        System.out.printf("-------------------------------------------------------\n");
        System.out.println(l.contains("Bob"));
        System.out.println(l.contains("Mike"));

    }
}


预期输出: 

 

3. 自定义链表Q&A

Q:在链表中插入或删除结点时,扫描链表的指针p为何要停在目标位置前面一位,而不正好是在目标位置?

A:对于单向链表,插入和删除结点时一定要修改目标位置前一位的指针

Q:在链表中插入元素时,为何要先把新结点和后面部分连起来?先让新结点和前面部分连起来可以吗?

A:对于单向链表,表示用来指向后续结点的指针是必不可少的,如果先改了目标前面一位的next指针,就没有可以表示用来指向后面结点的指针了,链表就会断掉。在以往的学习中特别强调对象一定要有指向它的指针,否则对象就成为垃圾从而被回收

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值