• 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);
    
            }
    
        }
    
    }
    
    
  • 相关阅读:
    Leetcode & CTCI ---Day 4
    Leetcode & CTCI ---Day 3
    Leetcode & CTCI ---Day 2
    Leetcode & CTCI ---Day 1
    编码格式坑之UTF-8
    15. 3Sum
    第十六周助教总结-第二组
    第十五周助教总结-第二组
    第十四周助教总结-第二组
    第十三周助教总结-第二组
  • 原文地址:https://www.cnblogs.com/bianzhuo/p/13453559.html
Copyright © 2020-2023  润新知