恋上数据结构与算法第一季笔记(一)——单链表、双向链表、单向循环链表

单链表

在这里插入图片描述

链表设计

在这里插入图片描述

接口设计

在这里插入图片描述

LinkedList 类

构造方法和头结点

       /**
     * 头结点
     */
    private Node<E> head;

    /**
     * 构造方法
     * @param <E>
     */
    private static class Node<E>{
    	//节点元素
        E element;
        //下一个节点引用
        Node<E> next;
        public Node(E element, Node<E>  next) {
            this.element = element;
            this.next = next;
        }
    }

清空链表 clear()

头指针指向空,链表长度置为0

    /**
     * 清除所有元素
     */
    @Override
    public void clear() {
        size = 0;
        head = null;
    }

添加节点 add( int index, E e)

要注意边界情况,index = 0时,index = size - 1时

1、 插入位置为头结点后面
在这里插入图片描述

2 、 插入位置为头结点之后的位置
在这里插入图片描述

/**
     * 在index位置插入一个元素
     * @param index
     * @param e
     */
    @Override
    public void add(int index, E e) {
        //特判往头结点后插入元素的情况
        if( index == 0){
            Node<E> node = new Node(e, head);
            head = node;
        }else{
	         //获取前驱节点
	        Node<E> pre = get_Node(index - 1);
	        Node<E> node = new Node(e,pre.next);
	        pre.next = node;
        }
       size ++;

    }

尾部节点插入 元素的方法add(E element)也是使用add(int index, E e)方法,只需在AbstractList调用该方法并指定index = size即可。这是多态的表现,接口(List)声明的变量 (list)被赋值为该接口实现类(ArrayList)的对象的引用。则调用该接口的方法,就可以调用到接口的实现类的方法。

调用接口List的void add(E i),会调用其实现类AbstracList的void add(E element)

public void add(E element) {
add(size, element);
}

而该方法又会去调用 add(size, element);调用add(size, element)的过程与之类似,最后调用的是LinkedList的add(int index, E e)

public void add(E element) {
        add(size, element);
    }

删除节点 remove(int index)

在这里插入图片描述

注意边界情况,当index == 0时,一般情况是index >= 1

    /**
     * 按索引删除元素,返回被删除元素的值
     *
     * @param index
     */
    @Override
    public E remove(int index) {
        Node<E> node = head;
        if( index == 0){
            head = head.next;
        }else{
            Node<E> pre = get_Node(index - 1);
            node = pre.next;
            pre.next = node.next;
        }
        //size要减1
        size --;
        return node.element;
    }

indexOf(Element e) 返回元素所在节点下标

 /**
     *  返回元素所在节点下标
     *
     * @param element
     */
    @Override
    public int indexOf(E element) {
        Node<E> p = head;
        //i记录下标
        int i = 0;
        //遍历链表
        while( p != null)
        {
            if( p.element == element){
                return i;
            }
            p = p.next;
            i ++;
        }
        return ELEMENT_NOT_FOUND ;
    }

接口与抽象类

单链表和双链表的接口和抽象类都是一样的,如下

List接口



package DoubleList;

interface List<E> {
    /**
     * 公共接口,将共有的且每个子类具体实现都不一样的地方抽象出来放在接口处.
     * 接口本身就是交由子类实现的,因此其权限本身就是public的,无需写
     */

    static final int ELEMENT_NOT_FOUND = - 1;
    /**
     * 清除所有元素
     */
     void clear();

    /**
     * 返回长度
     */
     int size();

    /**
     * 判断是否为空
     */
     boolean isEmpty();

    /**
     * 判断是否包含该元素
     */
     boolean  contains(E element);

    /**
     * 在index位置插入一个元素
     */
     void add(int index, E e);

     /**
      * 按索引获取节点元素值
      *
      * @param i
      */
     E get(int i);

     /**
      * 设置index位置的元素 返回原来元素
      */
     E set(int index, E element);

    /**
     * 按索引删除元素
     */
     E remove(int index);

    /**
     * 返回给定元素在的位置
     */
     int indexOf(E element);

     void add(E i);
 }

AbstractList

package DoubleList;

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

    /**
     * 该类实现子类共有的方法
     */
    /**
     * Constructs a new object.
     */
    public AbstractList() {
        super();
    }

    /**
     * 元素的数量
     */
    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
     */
    @Override
    public void add(E element) {
        add(size, element);
    }




    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);
        }
    }
}

SingleList

package SingleList;

public class SingleList<E> extends AbstractList<E> {
    /**
     * 内部类且声明为私有(只在链表LinkedList内部使用 对外部是不可见的)和静态
     */


    /**
     * 头结点:是对第一个节点的引用
     */
    private Node<E> head;

    /**
     * 构造方法
     * @param <E>
     */
    private static class Node<E>{
        E element;
        Node<E> next;
        public Node(E element, Node<E>  next) {
            this.element = element;
            this.next = next;
        }

    }
    /**
     * 清除所有节点
     */
    @Override
    public void clear() {
        //数量置为0 同时头结点也要指向空
        size = 0;
        head = null;
    }


    /**
     * 在index位置插入一个节点
     * @param index
     * @param e
     */
    @Override
    public void add(int index, E e) {
        System.out.println("SingleList.LinkedList……");
        //先判断索引是否合法
        rangeCheckForAdd(index);
        //特判往头结点后插入节点的情况
        if( index == 0){
            //新节点
            Node<E> node = new Node(e, head);
            head = node;
        }else{
            //获取前驱节点
            Node<E> pre = get_Node(index - 1);
            //新节点
            Node<E> node = new Node(e,pre.next);
            pre.next = node;
        }
        //长度加1
        size ++;
    }


    /**
     * 按索引获取节点值
     *
     * @param i
     */
    @Override
    public E get(int i) {
        return get_Node(i).element;
    }

    /**
     * 设置index位置的节点,返回原来节点的元素值
     * @param index
     * @param element
     */
    @Override
    public E set(int index, E element) {
        Node<E> node = get_Node(index);
        E val = node.element;
        node.element = element;
        return val;
    }

    /**
     * 按索引删除节点,返回被删除节点的元素值
     *
     * @param index
     */
    @Override
    public E remove(int index) {
        Node<E> node = head;
        if( index == 0){
            head = head.next;
        }else{
            Node<E> pre = get_Node(index - 1);
            node = pre.next;
            pre.next = node.next;
        }
        //size要减1
        size --;
        return node.element;
    }

    /**
     *  返回元素所在节点下标
     *
     * @param element
     */
    @Override
    public int indexOf(E element) {
        Node<E> p = head;
        //i记录下标
        int i = 0;
        //遍历链表
        while( p != null)
        {
            if( p.element == element){
                return i;
            }
            p = p.next;
            i ++;
        }
        return List.ELEMENT_NOT_FOUND ;
    }

    /**
     * 按索引获取某个节点
     * @param index
     * @return
     */
    public Node<E> get_Node(int index){
        rangeCheck(index);
        Node<E> node = head;
        for(int i = 0; i < index; i ++) node = node.next;
        return node;
    }

    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = head;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                string.append(", ");
            }

            string.append(node.element);

            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}

测试:

public class Main {
    public static void main(String[] args) {
        //父类引用指向子类对象 add方法也要在List接口中声明
        //如果是只在LinkedList子类中定义 是无法通过父类引用来调用子类特有的方法的
        List<Integer> list = new LinkedList<>();
        list.add(20);
        list.add(0,10);
        list.add(30);
        list.add(list.size() , 40);
        [10, 20, 30, 40]
        list.remove(0);
        list.remove(list.size() - 1);
        System.out.println(list);
        System.out.println( list.get(1)  );
        System.out.println( list.contains(20) );




    }
}

复杂度分析

最好平均最坏
add(int index, Element e)O(1)O(n)O(n)
remove(int index)O(1)O(n)O(n)
set(int index, Element e)O(1)O(n)O(n)
get(int index)O(1)O(n)O(n)

双向链表

在这里插入图片描述

LinkedList类方法及属性

 /**
     * 头结点(head):是对第一个节点的引用
     * 尾结点(tail): 对最后一个节点的引用
     */
    private Node<E> head;
    private Node<E> tail;

    /**
     * 构造方法
     * @param <E>
     */
    private static class Node<E>{
        E element;
        Node<E> prev;  //前驱
        Node<E> next;  //后继
        public Node(Node<E>  prev, E element, Node<E>  next) {
            this.prev = prev;
            this.element = element;
            this.next = next;
        }

    }

clear

    /**
     * 清除所有节点
     */
    @Override
    public void clear() {
        //数量置为0 同时头结点、尾结点也要指向空
        size = 0;
        tail = null;
        head = null;
    }

tail = null;head = null;之后,虽然每个节点存在相互引用,但是根据GC root是否可达,JVM中的GC会自动释放一连串的Node对象。
GC root对象是:

  1. 被局部变量所指向的对象

Node get_Node(int index)

待查找元素在左半边 从前往后查找。待查找元素在右半边 从后往前查找


    /**
     * 按索引获取某个节点
     * @param index
     * @return
     */
    public Node<E> get_Node(int index){
        rangeCheck(index);
        if( index < (size >> 1) ){//待查找元素在左半边 从前往后查找
            Node<E> node = head;
            for(int i = 0; i < index; i ++) node = node.next;
            return node;

        }else{//待查找元素在右半边 从后往前查找
            Node<E> node = tail;
            for(int i = size - 1; i > index; i --) node = node.prev;
            return node;
        }
    }

add(int index, E e)

【思路】
插入的位置可能在
1、尾部插入:涉及到tail尾结点。尾部插入时,也有特殊情况。即 index== size == 0,此时oldTail 为null,前一个节点应该为null,head 和tail都指向该新节点。
2、头部及中间位置插入:涉及到head头结点。头部插入(index==0且size > 0时),头结点head指向该节点。其他情况都是prev.next = current;

/**
     * 在index位置插入一个节点
     * @param index
     * @param e
     */
    @Override
    public void add(int index, E e) {
        //先判断索引是否合法
        rangeCheckForAdd(index);

        //往尾部添加节点 涉及到尾结点tail
        if( index == size){
            Node oldTail = tail;
            tail = new  Node(oldTail,e ,null);
            //oldTail可能为null:当index == 0且size == 0时
            if( oldTail == null ) head = tail;
            else  oldTail.next = tail;
        }else{
            //往[0,size - 1)位置添加节点
            //且往0添加时 不是第一个节点 其后已经有节点存在了
            Node next = get_Node(index);
            Node prev = next.prev;
            Node current =  new Node(prev, e, next);
            next.prev = current;
            
            //插入位置是0  则head = current
            if(prev == null) head = current;
            //插入位置是除0和尾结点外的位置
            else prev.next = current;

        }
        //长度加1
        size ++;
    }

E remove(int index)

  /**
     * 按索引删除节点,返回被删除节点的元素值
     *
     * @param index
     */
    @Override
    public E remove(int index) {
        //检查索引是否合法
        rangeCheck(index);
        
        Node<E> node = get_Node(index);
        Node prev = node.prev;
        Node next = node.next;

        //头结点
        if( prev == null) head = next;
        else  prev.next = next;
        
        //尾结点
        if( next == null) tail = prev;
        else next.prev = prev;
        
        //size --
        size --;
        
        return  node.element;
     
    }

DoubleList.LinkedList

package DoubleList;

public class LinkedList<E> extends AbstractList<E> {
    /**
     * 内部类且声明为私有(只在链表LinkedList内部使用 对外部是不可见的)和静态
     */


    /**
     * 头结点(head):是对第一个节点的引用
     * 尾结点(tail): 对最后一个节点的引用
     */
    private Node<E> head;
    private Node<E> tail;


    /**
     * 构造方法
     * @param <E>
     */
    private static class Node<E>{
        E element;
        Node<E> prev;  //前驱
        Node<E> next;  //后继
        public Node(Node<E>  prev, E element, Node<E>  next) {
            this.prev = prev;
            this.element = element;
            this.next = next;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            if( this.prev != null) sb.append( this.prev.element);
            else sb.append("null");
            sb.append( "__").append( this.element ).append( "__");

            if( this.next != null) sb.append( this.next.element);
            else sb.append("null");


            return sb.toString();
        }
    }
    /**
     * 清除所有节点
     */
    @Override
    public void clear() {
        //数量置为0 同时头结点、尾结点也要指向空
        size = 0;
        tail = null;
        head = null;
    }


    /**
     * 在index位置插入一个节点
     * @param index
     * @param e
     */
    @Override
    public void add(int index, E e) {
        //先判断索引是否合法
        rangeCheckForAdd(index);

        //往尾部添加节点 涉及到尾结点tail
        if( index == size){
            Node oldTail = tail;
            tail = new  Node(oldTail,e ,null);
            //oldTail可能为null:当index == 0且size == 0时
            if( oldTail == null ) head = tail;
            else  oldTail.next = tail;
        }else{
            //往[0,size - 1)位置添加节点
            //且往0添加时 不是第一个节点 其后已经有节点存在了
            Node next = get_Node(index);
            Node prev = next.prev;
            Node current =  new Node(prev, e, next);
            next.prev = current;

            //插入位置是0  则head = current
            if(prev == null) head = current;
            //插入位置是除0和尾结点外的位置
            else prev.next = current;

        }
        //长度加1
        size ++;
    }


    /**
     * 按索引获取节点值
     *
     * @param i
     */
    @Override
    public E get(int i) {
        return get_Node(i).element;
    }

    /**
     * 设置index位置的节点,返回原来节点的元素值
     * @param index
     * @param element
     */
    @Override
    public E set(int index, E element) {
        Node<E> node = get_Node(index);
        E val = node.element;
        node.element = element;
        return val;
    }

    /**
     * 按索引删除节点,返回被删除节点的元素值
     *
     * @param index
     */
    @Override
    public E remove(int index) {
        //检查索引是否合法
        rangeCheck(index);

        Node<E> node = get_Node(index);
        Node prev = node.prev;
        Node next = node.next;

        //头结点
        if( prev == null) head = next;
        else  prev.next = next;

        //尾结点
        if( next == null) tail = prev;
        else next.prev = prev;

        //size --
        size --;

        return  node.element;

    }

    /**
     *  返回元素所在节点下标
     *
     * @param element
     */
    @Override
    public int indexOf(E element) {
        Node<E> p = head;
        //i记录下标
        int i = 0;
        //遍历链表
        while( p != null)
        {
            if( p.element == element){
                return i;
            }
            p = p.next;
            i ++;
        }
        return List.ELEMENT_NOT_FOUND ;
    }

    /**
     * 按索引获取某个节点
     * @param index
     * @return
     */
    public Node<E> get_Node(int index){
        rangeCheck(index);
        if( index < (size >> 1) ){//带查找元素在左半边 从前往后查找
            Node<E> node = head;
            for(int i = 0; i < index; i ++) node = node.next;
            return node;

        }else{
            Node<E> node = tail;
            for(int i = size - 1; i > index; i --) node = node.prev;
            return node;
        }
    }

    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = head;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                string.append(", ");
            }

            string.append(node);

            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}

单链表与双链表的比较

在这里插入图片描述

双向链表与动态数组

在这里插入图片描述

单向循环链表

在这里插入图片描述

add

注意只有一个节点时的插入

在这里插入图片描述

/**
     * 在index位置插入一个节点
     * @param index
     * @param e
     */
    @Override
    public void add(int index, E e) {
        //先判断索引是否合法
        rangeCheckForAdd(index);
        //特判往头结点后插入节点的情况
        if( index == 0){
            //新节点
            Node<E> node = new Node(e, head);
            //size == 0时,最后一个节点即为 node
            //在修改线之前去查找
            Node<E> last= size == 0 ? node : get_Node(size - 1);
            head = node; //头结点指向新节点
            last.next = node;


        }else{
            //获取前驱节点
            Node<E> pre = get_Node(index - 1);
            //新节点
            Node<E> node = new Node(e,pre.next);
            pre.next = node;
        }
        //长度加1
        size ++;
    }

remove

/**
     * 按索引删除节点,返回被删除节点的元素值
     *
     * @param index
     */
    @Override
    public E remove(int index) {
        Node<E> node = head;

        if( index == 0){
            //index == 0 &&size == 1 头结点指向null
            if( size == 1) head = null;
            else{
                Node last = get_Node(size - 1); //最后一个节点
                head = head.next;
                last.next = head;
            }
        }else{
            Node<E> pre = get_Node(index - 1);
            node = pre.next;
            pre.next = node.next;
        }
        //size要减1
        size --;
        return node.element;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值