02-java实现单链表

02-手撸链表

本篇是恋上数据结构第一季个人总结
借鉴https://juejin.im/post/6844904001478066183#heading-0
本人git https://github.com/bigeyes-debug/Algorithm

一丶链表定义

链表是一种链式存储的线性表, 所有节点的内存地址不一定是连续的。

二丶链表设计

  • 创建LinkedList类,用来管理链表数据,其中的size属性记录存储数据的数量,first属性引用链表的第0个节点。

  • 创建私有类Node,其中的element属性用于存储节点,next属性用于指向链表中的下一个节点。

public class SIngleLinkedList<E>  extends 
AbstractList<E>{
    private Node<E> first;
    private static class Node<E>{
        E element;
        Node<E> next;
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }       
    }

接口

    // 节点的数量
    int size(); 
    // 是否为空
    boolean isEmpty();
    // 是否包含某个节点
    boolean contains(E element); 
    // 添加节点到最后面
    void add(E element); 
    // 返回index位置对应的节点
    E get(int index); 
    // 设置index位置的节点
    E set(int index, E element); 
    // 往index位置添加节点
    void add(int index, E element); 
    // 删除index位置对应的节点 
    E remove(int index); 
    // 查看节点的位置
    int indexOf(E element); 
    // 清除所有节点
    void clear();

与动态数组的接口类似,我们可以抽取接口,然后实现,后面我们会提到

三丶链表的实现

3.1 构造方法

  • 链表的创建与动态数组不同,动态数组在构造时需要传入一个空间属性,来决定这个数组的容量。但链表节点是在添加时才创建的,内存地址不一定是连续的。所以链表不需要在单独设计构造方法,使用默认构造方法即可。

3.2 添加节点

  • 添加节点时候只需要到将最后节点的next指针指向新添加的节点
  • 当指定位置添加节点的时候,需要插入位置的前驱,
  • 然后用前驱的next指针指向新添加的节点,并且该结点next指向原来的结点
  • 需要特别注意的地方是当前插入位置是0时,因为他没没有前驱,需要做特殊处理


代码如下

    @Override
    public void add(int index, E element) {
    //检查越界 和动态数组类似
        rangeCheck(index);
        //特殊处理
        if(index ==0) {
            first=new Node<>(element, first);
        }else {
        //  获得前驱
           Node <E>prev=node(index-1);
            //上图的①②
            prev.next=new Node<>(element, prev.next);
        }
        // TODO Auto-generated method stub    
        size++;
    }
    public void add(E element) {
    // 节点添加到size位置, 即添加到最后面
    add(size, element);
}

3.3 删除节点

  • 删除指定位置的节点,需要找到前驱
  • 用前驱next指向该位置节点的后继
  • 由于头结点没有前驱,所以我们仍需要对头结点的删除做特殊处理,只需要将头结点指向头结点的后继
   //这里我们用到了一个node函数,node是根据索引获取该节点的节点
    public E remove(int index) {
        rangeCheck(index);//检查越界
        Node <E> node =first;
        // TODO Auto-generated method stub
        if(index==0) {
            first=first.next;
        }else {
        //或得前驱
            Node <E>prev=node(index -1);
            //index位置的节点
            node =prev.next;
            //前驱的后继是index位置的后继
            prev.next=prev.next.next;
        }
        size--;
        return node.element;
    }   

    private Node<E> node(int index){
        rangeCheck(index);
       Node<E> node=first;
       for(int i=0;i<index;i++) {
            node=node.next;
        }
        return node;
    }

3.4 修改节点

修改节点首先根据节点的索引找到节点,然后修改节点的元素,最后返回原来节点的元素的值

    public E set(int index, E element) {
        Node <E> node=node(index);
        E old=node.element;
        node.element=(E) element;
        return old;
        // TODO Auto-generated method stub
    }

3.5查找节点

3.5.1 根据下标查找

找到对应的节点, 取出元素即可。

public E get(int index) {
        // TODO Auto-generated method stub
        return node(index).element;
    }

3.5.2 根据元素值查找

  • 查找指定元素的索引,需要遍历所有节点,找到节点对应的元素与执行元素相等即可。
  • 如果需要支持节点element为null,则需要分两种情况处理。
    一定要分开处理,,如果传入的element为null,调用equals方法会报错, 至于为什么用equlas方法,而不用==,自行百度Java基础
	public int indexOf(E element) {
		if(element == null) {
			Node <E> node= first;
			for(int i=0;i<size;i++) {
				if(node.element==null) {
					return i;
				}
				node=node.next;
			}
		}else {
			Node <E> node= first;
			for(int i=0;i<size;i++) {
				if(element.equals(node.element)) {
					return i;
				}
				node=node.next;

			}
		}
		return ELEMENT_NOT_FOUND;
	}

3.6 获取链表元素的个数

public int size() {
    return size;
}

3.7 链表是否为空

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

3.7 元素是否存在

public boolean contains(E element) {
    return indexOf(element) != ELEMENT_ON_FOUND;
}

3.8 打印链表中存储的数据

@Override
public String toString() {
    StringBuilder string = new StringBuilder();
    string.append("size = ").append(size).append(", [");
    Node<E> node = first;
    for (int i = 0; i < size; i++) {
        if (i != 0) {
            string.append(",");
        }
        string.append(node.element);
        node = node.next;
    }
    string.append("]");
    return string.toString();
}

四丶链表的复杂度

五丶代码优化

通过编写代码发现,链表和动态数组的接口一样,部分代码共用,他俩都属于线性表
我们可以优化代码,具体如下
实现list接口

package com.bigeyes;



public interface List<E> {

    static final int ELEMENT_NOT_FOUND = -1;

    /**

     * 清除所有元素

     */

    void clear();
    /**

     * 元素的数量

     * @return

     */

    int size();
    /**

     * 是否为空

     * @return

     */

    boolean isEmpty();

    /**

     * 是否包含某个元素

     * @param element

     * @return

     */

    boolean contains(E element);

    /**

     * 添加元素到尾部

     * @param element

     */

    void add(E element);

    /**

     * 获取index位置的元素

     * @param index

     * @return

     */

    E get(int index);

    /**

     * 设置index位置的元素

     * @param index

     * @param element

     * @return 原来的元素ֵ

     */

    E set(int index, E element);

    /**

     * 在index位置插入一个元素

     * @param index

     * @param element

     */

    void add(int index, E element);

    /**

     * 删除index位置的元素

     * @param index

     * @return

     */

    E remove(int index);

    /**

     * 查看元素的索引

     * @param element

     * @return

     */

    int indexOf(E element);

}

定义AbstractList抽象类,实现list接口,并且共用代码在这实现

package com.bigeyes;



public abstract class AbstractList<E> implements 
    List<E>  {

    /**

     * 元素的数量

     */

    protected int size;

    /**

     * 元素的数量

     * @return

     */

    public int size() {

        return size;

    }
    /**

     * 是否为空

     * @return

     */

    public boolean isEmpty() {

         return size == 0;

    }

    /**

     * 是否包含某个元素

     * @param element

     * @return

     */

    public boolean contains(E element) {

        return indexOf(element) != 
ELEMENT_NOT_FOUND;

    }


    /**

     * 添加元素到尾部

     * @param element

     */

    public void add(E element) {

        add(size, element);

//      System.out.println(siArrayList.javaze);

    }

    

    protected void outOfBounds(int index) {

        throw new 
IndexOutOfBoundsException("Index:" + index + ", 
Size:" + size);

    }

    

    protected void rangeCheck(int index) {

        if (index < 0 || index >= size) {

            outOfBounds(index);

        }

    }

    

    protected void rangeCheckForAdd(int index) {

        if (index < 0 || index > size) {

            outOfBounds(index);

        }

    }

}

posted @ 2020-08-07 16:18  大眼侠  阅读(232)  评论(0编辑  收藏  举报