链表(一)用Java语言实现单向链表(有/无虚拟头节点)

单向链表结构

在这里插入图片描述

其中,引用变量next,head分别存储节点在堆区中的地址。

接口设计

在动态数组ArrayList和链表LinkedList中,有很多相同功能但不同实现的方法,因此将这些方法提取出来构成一个接口List,用于给这两个类实现。

方法功能
int size()返回元素数量
boolean isEmpty()是否为空
boolean contains(E element)是否包含element元素
int indexOf(E element)返回element元素(可以为空)在数组中的索引,不存在则返回-1
void clear()清空所有元素
void add(E element)将element插入到数组末端
void add(int index,E element)将element插入到索引index位置
E remove(int index)删除索引为index处的元素
E get(int index)获取索引为index处的元素
E set(int index,E element)将index处的元素修改为element

List接口:

package com.mj.LinkListP;

public interface List<E> {
	//表示未找到该元素的常量
    static final int ELEMENT_NOT_FOUND=-1;

    /**
     * 返回元素数量
     * @return
     */
    public int size();

    /**
     * 是否为空
     * @return
     */
    public boolean isEmpty();

    /**
     * 是否包含element元素
     * @param element
     * @return
     */
    public boolean contains(E element);

    /**
     * 返回element元素(可以为空)在数组中的索引,不存在则返回-1
     * @param element
     * @return
     */
    public int indexOf(E element);

    /**
     * 清空所有元素
     */
    public void clear();

    /**
     * 将element插入到数组末端
     * @param element
     */
    public void add(E element);

    /**
     * 将element插入到索引index位置
     * @param index
     * @param element
     */
    public void add(int index,E element);

    /**
     * 删除索引为index处的元素
     * @param index
     * @return 被删除的元素
     */
    public E remove(int index);

    /**
     * 获取索引为index处的元素
     * @param index
     * @return
     */
    public E get(int index);

    /**
     * 将index处的元素修改为element
     * @param index
     * @param element
     * @return 被替换的元素
     */
    public E set(int index,E element);
}fangf

抽象类设计

然而ArrayList和LinkedList还有一些相同功能且相同实现的方法或者变量可以共用,但接口中的方法不能有方法体,因此构建一个抽象类AbstractList继承List接口,实现一些共有的方法供子类使用。

方法、变量声明功能
int size元素数量
int size()返回动态数组大小
boolean isEmpty()是否为空
boolean contains(E element)是否包含element元素
void add(E element)将element插入到数组末端
void throwException(int index)抛出索引越界异常
void rangeCheck(int index)判断索引的合理性
void rangeCheckForAdd(int index)为Add方法检查索引合理性

AbstractList:

public abstract class AbstractList<E> implements List<E>{
    //元素数量
    protected int size=0;

    /**
     * 返回动态数组大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 是否为空
     * @return
     */
    public boolean isEmpty(){
        return size==0;
    }

    /**
     * 是否包含element元素
     * @param element
     * @return
     */
    public boolean contains(E element){
        return indexOf(element)!=ELEMENT_NOT_FOUND;
    }

    /**
     * 将element插入到数组末端
     * @param element
     */
    public void add(E element){
        add(size,element);
    }

    /**
     * 抛出索引越界异常
     * @param index
     */
    protected void throwException(int index){
        throw new IndexOutOfBoundsException("index:"+index+" , size:"+size);
    }

    /**
     * 判断索引的合理性
     * @param index
     */
    protected void rangeCheck(int index){
        if(index<0||index>=size)
            throwException(index);
    }

    //为Add方法检查索引合理性
    protected void rangeCheckForAdd(int index){
        if(index<0||index>size)//对于Add方法可以在size处添加元素
            throwException(index);
    }

在此的LinkedLlist的继承关系为:
在这里插入图片描述

LinkedList实现细节


public class LinkedList<E> extends AbstractList<E>{
    //定义普通内部类Node
    private class Node<E>{
        private E element;
        private Node<E> next;

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    private Node<E> first;//头节点

    @Override
    public int indexOf(E element) {
        Node<E> node=first;
        //如果元素为空,只需要考虑链表元素为空
        if(element==null){
            for(int i=0;i<size;i++){
                if(node.element==null)return i;
                node=node.next;
            }
            /*while(node!=null){
                ...
                node=node.next;
            }*/
        }else{//元素不为空则调用equals方法判断
            for(int i=0;i<size;i++) {
                if (element.equals(node.element)) return i;
                node=node.next;
            }
        }
        //未找到则返回常量-1
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public void clear() {
        //将头节点设为null,则头节点指向Node对象将被销毁,
        // 导致存储地址被销毁,直至所有Node对象被销毁
        first=null;
        size=0;
    }

    @Override
    public void add(int index, E element) {
        //考虑边界
        if(index==0){
            //新建节点指向first节点,并更新first节点
            first= new Node<E>(element, first);
        }else{
            //获取插入节点的先前节点
            Node<E> prev=node(index-1);
            //新建节点指向prev的next节点,prev指向新节点
            prev.next=new Node<E>(element,prev.next);
        }
        size++;
    }

    @Override
    public E remove(int index) {
        rangeCheck(index);
        E oldElement= first.element;
        //判断是否是第一个节点
        if(index==0){
            first=first.next;
        }else{
            //获取前驱节点
            Node<E> prev=node(index-1);
            //保存被移除节点数据
            oldElement=prev.next.element;
            //跳过next指向next.next
            prev.next=prev.next.next;
        }
        size--;
        return oldElement;
    }

    @Override
    public E get(int index) {
       Node<E> node=node(index);
       return node.element;
    }

    @Override
    public E set(int index, E element) {
        Node<E> node=node(index);
        E oldElement=node.element;
        node.element=element;
        return oldElement;
    }

    //获取索引为index的节点
    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node=this.first;
       for(int i=0;i<index;i++)
           node=node.next;
        return node;
    }

    @Override
    public String toString() {
        StringBuilder sb=new StringBuilder("[");
        Node<E> node=first;
        while(node!=null){
            //判断是否是最后一个节点
            if(node.next!=null)
                sb.append(node.element).append(" ,");
            else
                sb.append(node.element);
            node=node.next;
        }
        sb.append("]");
        return sb.toString();
    }
}

测试类

public class LinkedListTest {
    public static void main(String[] args) {
        LinkedList<String> linkedList=new LinkedList<>();
        linkedList.add("one");
        linkedList.add("two");
        linkedList.add(0,"three");
        linkedList.remove(1);
        //[three ,two]
        System.out.println(linkedList);
        System.out.println(linkedList.get(1));
        linkedList.remove(0);
        System.out.println(linkedList);
        linkedList.add(0,"hello");
        System.out.println(linkedList);
    }
}

在这里插入图片描述

使用虚拟头节点的链表

有时候为了让代码更加精简,统一所有节点的处理逻辑,可以在最前面增加一个虚拟的头结点(不存储数据)。因此使用虚拟头节点需要对以上代码做出一些修改,改动的方法为:

方法功能
public LinkedList()构造方法中需要创建虚拟头节点
public int indexOf(E element)修改初始节点位置
public void clear()修改first.next=null
public void add(int index, E element)统一节点处理逻辑
public E remove(int index)统一节点处理逻辑
private Node node(int index)修改初始节点位置
public class LinkedList<E> extends AbstractList<E> {
    //定义普通内部类Node
    private class Node<E>{
        private E element;
        private Node<E> next;

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    private Node<E> first;//虚拟头节点

    public LinkedList(){//构造方法中需要创建虚拟头节点
        first=new Node<E>(null,null);
    }

    @Override
    public int indexOf(E element) {
        Node<E> node=first.next;
        //如果元素为空,只需要考虑链表元素为空
        if(element==null){
            for(int i=0;i<size;i++){
                if(node.element==null)return i;
                node=node.next;
            }
            /*while(node!=null){
                ...
                node=node.next;
            }*/
        }else{//元素不为空则调用equals方法判断
            for(int i=0;i<size;i++) {
                if (element.equals(node.element)) return i;
                node=node.next;
            }
        }
        //未找到则返回常量-1
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public void clear() {
        //将头节点设为null,则头节点指向Node对象将被销毁,
        // 导致存储地址被销毁,直至所有Node对象被销毁
        first.next=null;
        size=0;
    }

    @Override
    public void add(int index, E element) {
        //获取插入节点的先前节点
        Node<E> prev=(index==0)?first:node(index-1);
        //新建节点指向prev的next节点,prev指向新节点
        prev.next=new Node<E>(element,prev.next);
        size++;
    }

    @Override
    public E remove(int index) {
        rangeCheck(index);
        //获取前驱节点,如果索引为0则使用虚拟头节点
        Node<E> prev=index==0?first:node(index-1);
        //保存被移除节点数据
        E oldElement=prev.next.element;
        //跳过next指向next.next
        prev.next=prev.next.next;
        size--;
        return oldElement;
    }

    @Override
    public E get(int index) {
        Node<E> node=node(index);
        return node.element;
    }

    @Override
    public E set(int index, E element) {
        Node<E> node=node(index);
        E oldElement=node.element;
        node.element=element;
        return oldElement;
    }

    //获取索引为index的节点
    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node=this.first.next;
        for(int i=0;i<index;i++)
            node=node.next;
        return node;
    }

    @Override
    public String toString() {
        StringBuilder sb=new StringBuilder("[");
        Node<E> node=first.next;
        while(node!=null){
            //判断是否是最后一个节点
            if(node.next!=null)
                sb.append(node.element).append(" ,");
            else
                sb.append(node.element);
            node=node.next;
        }
        sb.append("]");
        return sb.toString();
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值