边学边记——Java数据结构☞LinkedList(链表)的介绍及自我实现

目录

复习回顾(预先了解):List(线性表)的介绍及自我实现

一.定义

1.概念

2.结点

(1).元素(val)

(2)线索(next)

(3).头结点(head)

3.关系

二.LinkedList的使用

1.LinkedList的构造

2.LinkedList的其他常用方法的介绍

(1).链表的遍历

(2).统计链表中的元素个数 

(3).查找链表中某个元素所在的结点 

(4).查找链表中所有和某元素相等的结点

(5).找到链表中第n个结点

补充:找到链表中的第n个结点但不保证链表长度

(6).头插法

        a.头插元素

        b.头插结点 

7.尾插法

        a.尾插元素

        b.尾插结点

8.删除

        a.头删法

        b.尾删法

四.自定义实现List和LinkedList

1.合法的链表需要满足什么条件

2.MyList 接口的实现

3.MyNode类的实现

4.MyLinkedList类的实现

4. MyLinkedList的检查


复习回顾(预先了解):List(线性表)的介绍及自我实现

一.定义

1.概念

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

  • 理论:链表也是一种线性表。
  • 线性表集合:逻辑上元素和元素有前后顺序,但不保证这些元素按照逻辑的顺序存储。

若想了解更多,请查阅:LinkedList的官方文档 

2.结点

结点(Node):装元素的一种结构,至少包括元素本身和指向下一个结点的线索。

public class Node {
    public int val; //元素
    public Node next;   //引用
    //当next=null时表示不存在下一个结点,即当前结点就是链表的最后一个结点
}

(1).元素(val)

类型不定,逻辑上有序的元素序列。

(2)线索(next)

引用(结点类型的引用)。

(3).头结点(head)

一般,我们用头结点表示一条链表。 

Node head=null;    //头结点

        当头结点head=null时,表示头结点不存在,链表中一个结点都没有,一个元素也没有,即表示一条空(empty)链表。

3.关系

  • 元素和结点:一一对应。
  • 链表和结点:一条链表是由多个结点(0个也行)组成的 。

二.LinkedList的使用

1.LinkedList的构造

方法解释
LinkedList()无参构造
public LinkedList(Collection<? extends E> c)使用其他集合容器中元素构造List

2.LinkedList的其他常用方法的介绍

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)截取部分 list

PS:以下操作需要提前创建好链表。

(1).链表的遍历

// 链表的遍历操作
public static void loopLinkedList(Node head) {
    // 参数 head,是我们手上的唯一线索,如何完成链表的遍历?
    // 需要循环完成,拿什么作为遍历时的 内容 进行

    Node cur = head;        // 让 cur 指向 head 指向的对象(链表的头结点对象)
    while (cur != null) {
        cur = cur.next;
    }

    // cur 会经历链表中的每个结点:按照从前往后的顺序,每个结点只会经过一次
}

(2).统计链表中的元素个数 

private static int countLinkedList(Node head){
    int count=0;
    for (Node cur=head;cur!=null;cur=cur.next){
       count++;
    }
    return count;
}

(3).查找链表中某个元素所在的结点 

private static Node findNode(Node head,int target){
    Node cur=head;
    while(cur!=null){
        if(target==cur.val){
            return cur;
        }
        cur=cur.next;
    }
    return null;
}

(4).查找链表中所有和某元素相等的结点

private static List<Node> findAllNode(Node head,int target){
    List<Node> ans=new ArrayList<>();

    Node cur=head;
    while(cur.next!=null){
        if(cur.val==target){
            ans.add(cur);
        }
        cur=cur.next;
    }

    return ans;
}

(5).找到链表中第n个结点

private static Node findNodeLocation(Node head,int n){
    Node cur=head;
    for (int i=0;i<n-1;i++){
        cur=cur.next;
    }
    return cur;
}

补充:找到链表中的第n个结点但不保证链表长度

private static Node findNodeLocation1(Node head,int n){
    Node cur=head;
    for (int i=0;i<n-1&&cur!=null;i++){
        cur=cur.next;
    }
    //两种可能:
    //cur==null,链表长度<n
    //cur!=null,找到了
    return cur;
}

(6).头插法

a.头插元素

//返回值类型是Node,返回链表新的头结点
private static Node headAddElement(Node head,int e){
    Node node=new Node(e);
    node.next=head;
    return node;
}

b.头插结点 

private static Node headAddNode(Node head,Node node){
    node.next=head;
    return node;
}

7.尾插法

a.尾插元素

//方法返回值Node,原因是尾插也有可能修改头结点(当原来的链表是空链表时)
private static Node lastAddElement(Node head,int e){
    Node node=new Node(e);

    if(head==null){
        node.next=null; //可以省略
        return node;
    }

    Node last=head;
    while(last.next!=null){
        last=last.next;
    }

    node.next=null; //可以省略
    last.next=node;

    return head;
}

b.尾插结点

private static Node lastAddNode(Node head,Node node){
    if(head==null){
        node.next=null; //可以省略
        return node;
    }

    Node last=head;
    while(last.next!=null){
        last=last.next;
    }

    node.next=null; //可以省略
    last.next=node;

    return head;
}

8.删除

a.头删法

private static Node headRemove(Node head){
    if(head==null){
        throw new RuntimeException("链表是空的(empty),没法删除结点");
    }

}

b.尾删法

private static Node lastRemove(Node head){
    if(head==null){
        //空链表,异常
        throw new RuntimeException("空链表!");
    }

    if(head.next==null){
        //没有第二个结点,说明只有一个结点
        return null;
    }

    Node lastOfLast=head;
    while (lastOfLast.next.next!=null){
        lastOfLast=lastOfLast.next;
    }

    lastOfLast.next=null;

    return head;
}

四.自定义实现List和LinkedList

自定义实现线性表(接口MyList)和链表表(类MyLinkedList)。 

1.合法的链表需要满足什么条件

  • 1.合法的线性表 。
  • 2.当head!=null则last!=null;反之亦然,当head==null则last==null 。
  • 3.当head!=null,size>0 ;同理,当head==null时,size==0 。
  • 4.size的值应该==通过遍历数出来结点个数 。
  • 5.除了head和last之外,所有其他结点(node),node.prev!=null&&node.next!=null 。
  • 6.head!=null时,则head.prev==null,但head.next不确定 ;last!=null时,则last.next==null,但last.prev不确定 。
  • 7.除了head和last之外,所有其他结点(node),node.prev.next==node&&node.next.prev==node
  • 8.当head.next!=null时,head.next.prev==head ;当last.prev!=null时,last.prev.next==last 。
  • 9.size==1,head==last&&head!=null。

后续检查自定义链表是否正确需要根据这些要求进行检测。 

2.MyList 接口的实现

/*
    1.元素类型 Long
    2.线性表
    3.无元素的位置 null
 */
public interface MyList {
    /**
     * 返回线性表中的元素个数
     * @return
     */
    int size();

    /**
     * 将 e 尾插到线性表中,一定返回 true
     * @param e
     * @return
     */
    boolean add(Long e);

    /**
     * 将 e 插入到线性表的 index 位置,从 [index, size()) 向后移
     * index 的合法下标 [0, size()]
     * 如果下标不合法:抛出一个 ArrayIndexOutOfBoundsException
     * @param index
     * @param e
     */
    void add(int index,Long e);

    /**
     * 删除 index 位置的元素
     * index 的合法下标:[0, size())
     * 如果下标不合法:抛出一个 ArrayIndexOutOfBoundsException
     * @param index
     * @return 从线性表中删除掉的元素
     */
    Long remove(int index);

    /**
     * 从前到后,删除第一个遇到的 e( equals() == true)
     * @param e
     * @return 删除成功:true,没有该元素:false
     */
    boolean remove(Long e);

    /**
     * 直接返回 index 位置的元素
     * index: [0, size())
     * @param index
     * @return
     */
    Long get(int index);

    /**
     * 使用 e 替换 index 位置的元素
     * @param index [0, size())
     * @param e
     * @return 原来 index 位置的元素
     */
    Long set(int index,Long e);

    /**
     * 返回第一次遇到 e 的下标(equals() == true)
     * @param e
     * @return 如果没有找到,返回 -1
     */
    int indexOf(Long e);

    /**
     * 从后往前,返回第一次遇到 e 的下标(equals() == true)
     * @param e
     * @return 如果没有找到,返回 -1
     */
    int lastIndexOf(Long e);

    /**
     * 线性表中是否包含 e(equals)
     * @param e
     * @return
     */
    boolean contains(Long e);

    /**
     * 清空数组
     */
    void clear();

    /**
     * 判断数组是否为空
     * @return
     */
    boolean isEmpty();
}

3.MyNode类的实现

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

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

4.MyLinkedList类的实现

public class MyLinkedList implements MyList{
    //维护3个属性
    //1.链表的头结点
    private MyNode head;
    //2.链表的尾结点
    private MyNode last;
    //3.链表中的元素个数
    private int size;   //不维护也可以通过遍历数出来,但维护好这个属性,可以让size的时间复杂度O(n)->O(1)

    //无参构造,空链表
    public MyLinkedList(){
        this.head=this.last=null;
        this.size=0;
    }

    @Override
    public int size() {
        return size;
    }

    //链表的尾插
    //时间复杂度:O(1)
    @Override
    public boolean add(Long e) {
        // 1) 先把元素装到结点中
        MyNode node=new MyNode(e);
        node.next=null;    // 这步可以省略
        // 2) 分情况讨论
        if(size>0){
            // 2.1) 链表中有尾结点:last != null 等价于 head != null 等价于 size > 0
            // 3) 找到尾结点 this.last
            this.last.next=node;
            node.prev=this.last;
            this.last=node;
        }else{
            // 2.2) 链表中没有尾结点
            node.prev=null;
            this.head=this.last=node;
        }
        this.size++;

        return true;
    }

    //链表的头插
    //时间复杂度:O(1)
    private boolean addFirst(Long e){
        // 1) 先把元素装到结点中
        MyNode node=new MyNode(e);
        node.prev=null;    // 这步可以省略
        if(size>0){
            this.head.prev=node;
            node.next=this.head;
            this.head=node;
        }else {
            node.next=null;
            this.head=this.last=node;
        }
        this.size++;

        return true;
    }

    //根据下标添加元素
    //时间复杂度:O(n)
    @Override
    public void add(int index, Long e) {
        if(index<0||index>size){
            throw new ArrayIndexOutOfBoundsException("下标越界");
        }

        if(size==0){
            add(e);
            //addFirst(e);
            return;
        }

        if(size==1){
            if(index==0){
                addFirst(e);
            }
            else {
                add(e);
            }
            return;
        }

        if(index==0){
            addFirst(e);
            return;
        }else if(index==size){
            add(e);
            return;
        }

        //先找到前驱节点
        MyNode prevNode=this.head;
        for (int i = 0; i < index-1; i++) {
            prevNode=prevNode.next;
        }

        MyNode curNode=prevNode.next;
        MyNode node=new MyNode(e);

        prevNode.next=node;
        curNode.prev=node;
        node.prev=prevNode;
        node.next=curNode;

        size++;
    }

    //根据下标删除
    //时间复杂度:O(n)
    @Override
    public Long remove(int index) {
        if(index<0||index>=size){
            //包含了size==0的情况
            throw new ArrayIndexOutOfBoundsException("下标越界");
        }

        if(size==1){
            Long e=this.head.val;
            this.head=this.last=null;
            this.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;
        }

        MyNode curNode=this.head;
        for (int i=0;i<index;i++){
            curNode=curNode.next;
        }

        Long e=curNode.val;
        MyNode prevNode=curNode.prev;
        MyNode nextNode=curNode.next;

        prevNode.next=nextNode;
        nextNode.prev=prevNode;
        size--;

        return e;
    }

    //删除从前到后第一个e
    @Override
    public boolean remove(Long e) {
        MyNode cur=this.head;
        for (int i = 0; i < size; i++) {
            if(cur.val.equals(e)){
                if(i==0){
                    //此时size可能>=1
                    this.head=this.head.next;
                    if(this.head!=null){    //size>1
                        this.head.prev=null;
                    }else { //size=1
                        this.last=null;
                    }
                    size--;
                    return true;
                }

                if(i==size-1){  //size>1
                    this.last=this.last.prev;
                    this.last.next=null;
                    size--;
                    return true;
                }
                MyNode prevNode=cur.prev;
                MyNode nextNode=cur.next;

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

            cur=cur.next;
        }
        return false;
    }

    //时间复杂度:O(n)
    @Override
    public Long get(int index) {
        if(index<0||index>=size){
            throw new ArrayIndexOutOfBoundsException("下标越界");
        }
        MyNode cur=this.head;
        for (int i = 0; i < index; i++) {
            cur=cur.next;
        }
        return cur.val;
    }

    //时间复杂度:O(n)
    @Override
    public Long set(int index, Long e) {
        if(index<0||index>=size){
            throw new ArrayIndexOutOfBoundsException("下标越界");
        }
        MyNode cur=this.head;
        for (int i = 0; i < index; i++) {
            cur=cur.next;
        }
        Long oldValue=cur.val;
        cur.val=e;
        return oldValue;
    }

    @Override
    public int indexOf(Long e) {
        MyNode cur=this.head;
        int i=0;
        while (cur!=null){
            if(cur.val.equals(e)){
                return i;
            }
            i++;
            cur=cur.next;
        }

        return -1;
    }

    @Override
    public int lastIndexOf(Long e) {
        int i=size-1;
        MyNode cur=this.last;
        while (cur!=null){
            if(cur.val.equals(e)){
                return i;
            }
            i--;
            cur=cur.prev;
        }
        return -1;
    }

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

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

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

4. MyLinkedList的检查

我们可以运用如下方法对我们自己编写的链表进行检查,这些检查序号对应着合法的链表需要满足什么条件中的要求:

private static void assertTrue(boolean condition,String message){
    if(!condition){
        throw new RuntimeException(message);
    }
}

private static void test2(MyLinkedList list){
    if (list.head == null) {
        assertTrue(list.last == null, "head 为 null 时,last 必须是 null");
    } else {
        assertTrue(list.last != null, "head 不为 null 时,last 必须不为 null");
    }
}

private static void test3(MyLinkedList list){
    assertTrue(list.size >= 0, "size 必须 >= 0");
    if (list.head == null) {
        assertTrue(list.size == 0, "head 为 null 时,size 必须是 0");
    } else {
        assertTrue(list.size > 0, "head 不为 null 时,size 必须大于 0");
    }
}

private static int countNode(MyLinkedList list){
    int size=0;
    for (MyNode cur = list.head; cur !=null; cur=cur.next) {
        size++;
    }
    return size;
}

private static void test4(MyLinkedList list){
    assertTrue(list.size == countNode(list), "记录的 size 应该和遍历出的 size 相等");
}

private static void test5(MyLinkedList list){
    if (list.size > 1) {
        MyNode cur = list.head.next;
        while (cur != list.last) {
            assertTrue(cur.prev != null, "非头尾结点的 prev 不能是 null");
            assertTrue(cur.next != null, "非头尾结点的 next 不能是 null");
        }
        cur=cur.next;
    }
}

private static void test6(MyLinkedList list){
    if (list.head != null) {
        assertTrue(list.head.prev == null, "头结点的 prev 一定是 null");
        assertTrue(list.last.next == null, "尾结点的 next 一定是 null");
        if (list.size == 1) {
            assertTrue(list.head.next == null, "size 为 1 时,头节点的 next 一定是 null");
            assertTrue(list.last.prev == null, "size 为 1 时,为节点的 prev 一定是 null");
        } else {
            assertTrue(list.head.next != null, "size > 1 时,头节点的 next 一定不是 null");
            assertTrue(list.last.prev != null, "size > 1 时,尾节点的 prev 一定不是 null");
        }
     }
}

private static void test7(MyLinkedList list){
    if (list.size > 1) {
        MyNode cur = list.head.next;    // 跳过头节点
        while (cur != list.last) {      // 跳过尾结点
            assertTrue(cur.prev.next == cur, "非头尾结点的 前驱的 后继是自己");
            assertTrue(cur.next.prev == cur, "非头尾结点的 后继的 前驱是自己");
        }
        cur=cur.next;
    }
}

private static void test8(MyLinkedList list){
    if (list.size > 1) {
        assertTrue(list.head.next.prev == list.head, "当 head.next != null 时,head.next.prev == head");
        assertTrue(list.last.prev.next == list.last, "last.prev != null 时,last.prev.next == last");
    }
}

//断言是一个合法的链表
private static void assertLinkedList(MyLinkedList list){
    test2(list);
    test3(list);
    test4(list);
    test5(list);
    test6(list);
    test7(list);
    test8(list);
}
//断言链表的某位置一定是某个值
private static void assertValue(MyLinkedList list,int index,long e){
    MyNode cur=list.head;
    for (int i = 0; i < index; i++) {
        cur=cur.next;
    }

    assertTrue(cur.val==e,"该位置的值必须是这个值");
}

如有建议或想法,欢迎一起讨论学习~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值