LinkedList&链表

一. 链表

1.1 链表的概念

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

 

 

 可以看到,链表是在堆上申请的,各对象的地址不一定是连续的

1.2 链表的结构

链表有三种分类:单向/双向,有头/无头,循环/非循环

组合起来一共有八种链表

1.单向/双向

 2.有头/无头

 3.循环/非循环

 本篇文章的主角是无头单向非循环链表/无头双向循环链表

1.3 无头单向非循环链表的实现

 下面模拟实现一下无头单向非循环链表

单向链表需要有一个头节点,保存第一个对象的地址

public class SingleList {

    //定义结点为链表的内部类
    class ListNode {

        int val;
        ListNode next;
        public ListNode(int data) {
            this.val = data;
        }
    }
    //保存第一个结点,否则无法找到该链表
    ListNode head;
    //头插法
    public void addFirst(int data){
        ListNode newnode=new ListNode(data);
        if(head==null) {
            head=newnode;
            return;
        }
        newnode.next=head;
        head=newnode;
    }
    //尾插法
    public void addLast(int data){
        ListNode newnode=new ListNode(data);
        if(head==null) {
            head=newnode;
            return;
        }
        ListNode cur=head;
        while(cur.next!=null) {
            cur=cur.next;
        }
        cur.next=newnode;
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if(index>this.size()||index<0) {
            throw new IllegalIndex("插入下标不合法");
        }
        if(index==0) {
            addFirst(data);
            return;
        }
        ListNode cur=head;
        index--;
        while(index>0) {
            cur=cur.next;
        }
        cur.next=new ListNode(data);
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur=head;
        while(cur!=null) {
            if(cur.val==key) { //如果要判断的类型为引用类型,要使用equals
                return true;
            }
        }
        return false;
    }
    //删除第一次出现关键字为key的节点
    public void remove(int key){
        if(head==null) return;
        if(head.val==key) {
            head=head.next;
            return;
        }
        ListNode cur=head;
        while(cur.next!=null) {
            if(cur.next.val==key) {
                cur.next=cur.next.next;
                break;
            }
            cur=cur.next;
        }
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        if(head==null) return;
        while(head!=null&&head.val==key) {
            head=head.next;
        }
        ListNode prev=head,cur=head.next;
        while(cur!=null) {
            if(cur.val==key) {
                prev.next=cur.next;
                cur=prev.next;
            } else {
                prev=cur;
                cur=prev.next;
            }
        }
    }
    //得到单链表的长度
    public int size(){
        ListNode cur=head;
        int num=0;
        while(cur!=null) {
            num++;
            cur=cur.next;
        }
        return num;
    }
    public void clear() {
        head=null;
    }
    public void display() {
        ListNode cur=head;
        while(cur!=null) {
            System.out.print(cur.val+" ");
            cur=cur.next;
        }
        System.out.println();
    }
}
//下标不合法异常的定义
public class IllegalIndex extends RuntimeException{
    public IllegalIndex() {
    }

    public IllegalIndex(String message) {
        super(message);
    }
}

1.4 无头双向非循环链表的实现

下面模拟实现无头双向非循环链表

双向链表需要有头结点和尾结点,方便进行双向遍历

public class MyLinkedList {
       class LinkNode {
           int val;
           LinkNode next;
           LinkNode prev;

           public LinkNode(int val) {
               this.val = val;
           }
       }
       LinkNode head;
       LinkNode last;
        //头插法
        public void addFirst(int data){
            LinkNode node=new LinkNode(data);
            if(head==null) {
                head=last=node;
                return;
            }
            node.next=head;
            head.prev=node;
            head=node;
        }
        //尾插法
        public void addLast(int data){
            LinkNode node=new LinkNode(data);
            if(last==null) {
                head=last=node;
                return;
            }
            last.next=node;
            node.prev=last;
            last=node;
        }
        //任意位置插入,第一个数据节点为0号下标
        public void addIndex(int index,int data){
            if(index<0) {
                throw new IllegalIndex("插入下标不合法");
            }
            if(index==0) {
                addFirst(data);
                return;
            }
            LinkNode prev=head;
            index--;
            while(index>0) {
                if(prev==null) {
                    throw new IllegalIndex("插入下标不合法");
                }
                prev=prev.next;
                index--;
            }
            if(prev==null) {
                throw new IllegalIndex("插入下标不合法");
            }
            LinkNode node=new LinkNode(data);
            prev.next.prev=node;
            node.next=prev.next;
            node.prev=prev;
            prev.next=node;
        }
        //查找是否包含关键字key是否在单链表当中
        public boolean contains(int key){
            LinkNode cur=head;
            while(cur!=null) {
                if(cur.val==key) {
                    return true;
                }
                cur=cur.next;
            }
            return false;
        }
        //删除第一次出现关键字为key的节点
        public void remove(int key){
            LinkNode cur=head;
            while(cur!=null) {
                if(cur.val==key) {
                    if(cur==head) {
                        head=head.next;
                        head.prev=null;
                        if(head==null)
                            last=null;
                        return;
                    }
                    if(cur==last) {
                        last=last.prev;
                        last.next=null;
                        if(last==null)
                            head=null;
                        return;
                    }
                    LinkNode prev=cur.prev;
                    LinkNode next=cur.next;
                    prev.next=next;
                    next.prev=prev;
                    return;
                }
                cur=cur.next;
            }
        }
        //删除所有值为key的节点
        public void removeAllKey(int key){
            LinkNode cur=head;
            while(cur!=null) {
                if(cur.val==key) {
                    if(cur==head) {
                        head=head.next;
                        if(head==null)
                            last=null;
                        return;
                    }
                    if(cur==last) {
                        last=last.prev;
                        if(last==null)
                            head=null;
                        return;
                    }
                    LinkNode prev=cur.prev;
                    LinkNode next=cur.next;
                    prev.next=next;
                    next.prev=prev;
                }
                cur=cur.next;
            }
        }
        //得到链表的长度
        public int size(){
            LinkNode cur=head;
            int len=0;
            while(cur!=null){
                cur=cur.next;
                len++;
            }
            return len;
        }
        public void display(){
            LinkNode cur=head;
            while(cur!=null) {
                System.out.print(cur.val+" ");
                cur=cur.next;
            }
            System.out.println();
        }
        public void clear(){
            head=last=null;
        }

}

上面clear的实现中使用了比较粗暴的解决方法---直接将头结点和尾结点置空,实际上源码是十分温柔的,分别遍历了每个对象并一一置空

Tip:

下面验证下本博主clear方法究竟实现了没有

你以为我只是简单地打印,当然不是!

 在cmd上输入下面的命令,会在d盘上的cookie.txt文件里生成实例对象

这是未使用clear方法时的实例

 

下面执行一下clear方法,在cookie.txt文件中再次查找LinkNode

 

二. LinkedList

LinkedList 的底层是双向链表结构 ,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高
下图为LinkedList的结构框架

 

 从这个框架中可以得出:

1. LinkedList没有实现RandomAccess接口,因此不支持随机访问

2. LinkedList实现了List接口,因此LinkedList也是线性表

3. LinkedList实现了Cloneable接口,因此支持克隆

4. LinkedList实现了Seriallizable接口,因此支持序列化

2.1 LinkedList的使用

1.LinkedList的构造

与ArrayList相同,LinkedList也支持一个实现了Collection接口的对象帮助构造

public static void main(String[] args) {
        //创造一个ArrayList对象
        ArrayList<Integer> array=new ArrayList<>();
        array.add(1);
        array.add(2);
        array.add(3);

        //帮助初始化的对象其泛型参数必须和LinkedList一致
        LinkedList<Integer> list=new LinkedList<>(array);
        System.out.println(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

 

 3. LinkedList的遍历

因为LinkedList不支持随机访问,LinkedList没有for+下标遍历的用法

1. for-each

public static void main(String[] args) {

        LinkedList<Integer> link=new LinkedList<>();
        for(int i=1;i<=10;i++)
            link.add(i);

        for(int e:link) {//增强for循环
            System.out.print(e+" ");
        }
    }

2. 使用迭代器

迭代器不仅可以实现正序访问,因为双向链表的底层结构,使用迭代器也可以进行反向访问

public static void main(String[] args) {

        LinkedList<Integer> link=new LinkedList<>();
        //给link添加元素
        for(int i=1;i<=10;i++)
            link.add(i);
      
        //使用迭代器正向访问
        ListIterator<Integer> it= link.listIterator();
        while(it.hasNext()){
            System.out.print(it.next()+" ");
        }
       
        //使用迭代器反向访问
        it= link.listIterator(link.size());//传入下标应为链表大小
        while(it.hasPrevious()) {
            System.out.print(it.previous()+" ");
        }

    }

输出结果

 为什么要传入链表大小呢?

按照正向迭代器的逻辑逻辑传入的应该是是最后一个元素的下标,即size-1

一起来看看LinkedList的迭代器吧

 

 下面是反向迭代器

 总结:

使用正向迭代器时,是包含下标为index的元素的迭代

使用反向迭代器时,不包含下标为Index的元素

下图验证

public static void main(String[] args) {

        LinkedList<Integer> link=new LinkedList<>();
        for(int i=1;i<=10;i++)
            link.add(i);

        ListIterator<Integer> it= link.listIterator(0);//传入第一个元素的下标
        
        while(it.hasNext()){
            System.out.print(it.next()+" ");
            //it.remove()
        }

        System.out.println();
        System.out.println("******************");

        it= link.listIterator(link.size()-1);//传入倒数第一个元素的下标
        
        while(it.hasPrevious()) {
            System.out.print(it.previous()+" ");
        }

    }

第一个输出结果包含第一个元素,第二个输出结果不包含最后一个元素

 

三. ArrayList  VS  LinkedList

比较项ArrayListLinkedList
增添只适合尾插,其他的插入需要移动元素不需要移动元素,插入时需要遍历
查找可以随机查找需要遍历链表
删除只适合删除末尾元素,其他删除需要移动元素不需要移动其他元素,需要遍历链表
物理实现物理空间连续,底层是数组物理空间不连续,底层是双向链表

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不 会敲代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值