数据结构与算法(java版) 第一季 - 03 单向链表

目录

01 简介

02 接口设计

03 clear

04 add

05 remove

06 indexOf_toString

07 删除节点

08 反转链表( 递归)

09 反转链表(迭代)

10 反转链表(环形链表)

11 补充

12 虚拟头节点

13 复杂度分析(ArrayList)

14 均摊度分析

15 ArrayList的缩容

16 复杂度震荡


01 简介

  • 简介是如下所示:
  • 链表的设计方案是如下所示:
  •  链表的设计代码是如下所示:
    public class LinkedList {
        private int size;
        private Node firstNode;//由于是只是在链表之中进行使用,因此我们设计成内部类
    
    
        private static class Node<E>//这里的节点内部类要进行设计成为相应的私有静态的,不要问为什么.
        {
            E element;//内部类是一般不写访问的权限的
            Node<E> next;
    
            //构造函数
            public Node(E element, Node<E> next) {
                this.element = element;
                this.next = next;
            }
        }
    }

02 接口设计

  • 链表的接口大部分和动态数组是一致的.因此,创建一个父类List,只是数组和链表的实现方式是不一样的.

          但是这里二者数组和链表内容是完全不一样的,使用父类的方式并不是最佳的方式进行,可以使用接口进行创建.

  •  ArrayList和LinkedList还是存在一定的公共的相同的东西,因此,这里还是可以使用公共的线型表进行表示.
  •  经过上述处理之后List接口的代码是:
    /*
    
     */
    public interface List<E> {
    
       static final int ELEMENT_NOT_FOUND = -1;
    
    
        void clear();
    
        int size();
    
        boolean isEmpty();
    
        boolean contains(E var1);
    
        void add(E var1);
    
        E get(int var1);
    
        E set(int var1, E var2);
    
        void add(int var1, E var2);
    
        E remove(int var1);
    
        int indexOf(E var1);
    
    }
    

    AbstractList之中的代码是:

    public abstract class AbstractList<E> implements List<E>{//这里是只是实现一些公共的方法,因此,这里是使用abstract方式,对于外界是不可见的.
    
    
    
        protected int size;
    
        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) {
            if(element == null) return;
    
            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);
            }
        }
    }
    

    ArrayList之中的代码是:

    
    
    public class ArrayList<E> extends AbstractList<E> {
        /**
         * 元素的数量
         */
    
        /**
         * 所有的元素
         */
        private E[] elements;
    
        private static final int DEFAULT_CAPACITY = 10;
    
    
        public ArrayList(int capaticy) {
            capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
            elements = (E[]) new Object[capaticy];
        }
    
        public ArrayList() {
            this(DEFAULT_CAPACITY);
        }
    
        /**
         * 清除所有元素
         */
        public void clear() {
            /*
            这样的含义就是数组之中都是null,但是数组内存是存在的,不能够进行销毁,
            不能够使用element=null,相当于第一根线断了,不能够继续使用.
            能循环利用的留下,不能够循环利用的滚蛋.
             */
            for (int i = 0; i < size; i++) {
                elements[i] = null;
            }
            size = 0;
        }
    
    
        /**
         * 获取index位置的元素
         * @param index
         * @return
         */
        public E get(int index) {
            rangeCheck(index);
            return elements[index];
        }
    
        /**
         * 设置index位置的元素
         * @param index
         * @param element
         * @return 原来的元素ֵ
         */
        public E set(int index, E element) {
            rangeCheck(index);
    
            E old = elements[index];
            elements[index] = element;
            return old;
        }
    
        /**
         * 在index位置插入一个元素
         * @param index
         * @param element
         */
        public void add(int index, E element) {
            rangeCheckForAdd(index);
    
            ensureCapacity(size + 1);
    
            for (int i = size; i > index; i--) {
                elements[i] = elements[i - 1];
            }
            elements[index] = element;
            size++;
        }
    
        /**
         * 删除index位置的元素
         * @param index
         * @return
         */
        public E remove(int index) {
            rangeCheck(index);
    
            E old = elements[index];
            for (int i = index + 1; i < size; i++) {
                elements[i - 1] = elements[i];
            }
            elements[--size] = null;
            return old;
        }
        public  void  remove(E element)
        {
            remove(indexOf(element));
        }
    
        /**
         * 查看元素的索引
         * @param element
         * @return
         */
        public int indexOf(E element) {
            if (element == null) {  // 1
                for (int i = 0; i < size; i++) {
                    if (elements[i] == null) return i;
                }
            } else {
                for (int i = 0; i < size; i++) {
                    if (element.equals(elements[i])) return i; // n
                }
            }
            return ELEMENT_NOT_FOUND;
        }
    
    //	public int indexOf2(E element) {
    //		for (int i = 0; i < size; i++) {
    //			if (valEquals(element, elements[i])) return i; // 2n
    //		}
    //		return ELEMENT_NOT_FOUND;
    //	}
    //
    //	private boolean valEquals(Object v1, Object v2) {
    //		return v1 == null ? v2 == null : v1.equals(v2);
    //	}
    
        /**
         * 保证要有capacity的容量
         * @param capacity
         */
        private void ensureCapacity(int capacity) {
            int oldCapacity = elements.length;
            if (oldCapacity >= capacity) return;
    
            // 新容量为旧容量的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            E[] newElements = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                newElements[i] = elements[i];
            }
            elements = newElements;
    
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
    
    
        @Override
        public String toString() {
            // size=3, [99, 88, 77]
            StringBuilder string = new StringBuilder();
            string.append("size=").append(size).append(", [");
            for (int i = 0; i < size; i++) {
                if (i != 0) {
                    string.append(", ");
                }
    
                string.append(elements[i]);
    
    //			if (i != size - 1) {
    //				string.append(", ");
    //			}
            }
            string.append("]");
            return string.toString();
        }
    }
    

    LinkedList之中的代码是:

    
    public class LinkedList<E> extends AbstractList<E>{
        int ELEMENT_NOT_FOUND = -1;
        private int size;
        private Node firstNode;//由于是只是在链表之中进行使用,因此我们设计成内部类
    
        private static class Node<E>//这里的节点内部类要进行设计成为相应的私有静态的,不要问为什么.
        {
    
    
            E element;//内部类是一般不写访问的权限的
            Node<E> next;
    
            //构造函数
            public Node(E element, Node<E> next) {
                this.element = element;
                this.next = next;
            }
        }
    
        public void clear() {
    
    
    
        }
    
        public int size() {
            return size;
        }
    
        public boolean isEmpty() {
            return size == 0;
        }
    
        public boolean contains(E element) {
            return indexOf(element) !=  ELEMENT_NOT_FOUND ;
        }
    
        public void add(E element) {
            add(size,element);
    
        }
    
        public E get(int var1) {
            return null;
        }
    
        public E set(int var1, E var2) {
            return null;
        }
    
        public void add(int var1, E var2) {
    
        }
    
        public E remove(int var1) {
            return null;
        }
    
        public int indexOf(E var1) {
            return 0;
        }
    
    
    
    
    }
    

03 clear

  • 清空元素
  •  代码块如下所示:
    public void clear() {
        size = 0;
        first = null;
    }

04 add

  • 添加元素的结构示意图:
  •  通过index获取相应的节点的方式是如下所示:
   /*
    获取index对应位置的节点对象
     */
    private Node<E> node(int index)//这里的含义就是传入一个索引,返回当下的节点
    {
        //检查索引是否存在
        rangeCheck(index);

        //比如,想要找到索引为2的位置,需要索引进行next两次
        Node<E> node = first;
        for (int i = 0;i < index;i++)
        {
            node = node.next;
        }
        return node;
    }
  • 由上所示,可以得知set和get方法的使用:

    public E get(int index) {
        return node(index).element;
    }

    public E set(int index, E element) {
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }
  • 添加元素的代码是如下所示:
        public void add(int index, E element) {
            //如果要是传入的index是0
            if(index == 0)//添加到最前面的位置
            {
            first = new Node<>(element,first);
            }
            else
            {    Node<E> prev = node(index-1);
                prev.next = new Node<E>(element,prev.next); //这是
    
            }
    
            size++;
    
        }

     注意:临界和边界位置处理

05 remove

  • 删除元素
    public E remove(int index) {
        
        //被删除的节点
        Node<E> node = first;
        
       if(index == 0)
       {
           first = first.next;
       }
       else
       {
           Node<E> prev = node(index-1);
           node = prev.next;
           prev.next = prev.next.next;
       }
size--;
       return node.element;
    }

06 indexOf_toString

    private Node<E> node(int index)//这里的含义就是传入一个索引,返回当下的节点
    {
        //检查索引是否存在
        rangeCheck(index);

        //比如,想要找到索引为2的位置,需要索引进行next两次
        Node<E> node = first;
        for (int i = 0;i < index;i++)
        {
            node = node.next;
        }
        return node;
    }

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

07 删除节点

08 反转链表( 递归)

刷题:
  • 代码段如下所示:

    package 链表;
    
    public class _206_反转链表 {
        /**
         * Definition for singly-linked list.
         * public class ListNode {
         * int val;
         * ListNode next;
         * ListNode() {}
         * ListNode(int val) { this.val = val; }
         * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
         * }
         */
        class Solution {
            public class ListNode {
                int val;
                ListNode next;
    
                ListNode() {
                }
    
                ListNode(int val) {
                    this.val = val;
                }
    
                ListNode(int val, ListNode next) {
                    this.val = val;
                    this.next = next;
                }
            }
    
            public ListNode reverseList(ListNode head) {
                if(head == null)
                    return null;
    
                if(head.next == null) return head;
    
                ListNode newHead = reverseList(head.next);
    
                head.next.next = head;//4的next指向5
    
                head.next = null;//5的next指向null
    
                return newHead;
    
    
            }
        }
    }

09 反转链表(迭代)

  • 代码思路:重复上述操作,将上面的东西进行串联.
  •  代码块是如下所示:
      public ListNode reverseList2(ListNode head) {
                if(head == null)
                    return null;
    
                if(head.next == null) return head;
    
               ListNode newHead = null;
               while(head != null) {
                   ListNode tmp = head.next;
                   head.next = newHead;
                   newHead = head;
                   head = tmp;
    
               }
                return newHead;
    
            }

10 反转链表(环形链表)

  • 刷题141. 环形链表 - 力扣(LeetCode)https://leetcode.cn/problems/linked-list-cycle/
  • 使用快慢指针思想进行解决.这里快慢指针的含义就是,比如说慢指针走一个,快指针走两个,如果要是又环的话,二者就是一定会相遇的.
  • 判断是否有环的过程之中,代码实现.
    package 链表;
    
    public class _3_判断一个链表是否有环 {//141题
    
            public boolean hasCycle(ListNode head) {
                if (head == null && head.next == null) return false;
    
                ListNode slow = head;
                ListNode fast = head.next;
                while (fast != null && fast.next != null) //这里是防止空指针异常的情况出现
                {
                    slow = slow.next;
                    fast = fast.next.next;
    
                    if(fast == slow) return true;
                    
                }
                return false;
            }
    
    }

11 补充

  • 这里需要进行注意的是remove方法之中存在首指针为0的情况,add方法之中也是需要进行注意:

12 虚拟头节点

  • 使得代码更加的精简
  •  虚拟头节点的存在使得相应的add和remove以及toString方式存在差异.
  • ArrayList2代码:
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.mj;
    
    public class ArrayList2<E> extends AbstractList<E> {
        private E[] elements;
        private static final int DEFAULT_CAPACITY = 10;
    
        public ArrayList2(int capaticy) {
            capaticy = capaticy < 10 ? 10 : capaticy;
            this.elements = new Object[capaticy];
        }
    
        public ArrayList2() {
            this(10);
        }
    
        public void clear() {
            for(int i = 0; i < this.size; ++i) {
                this.elements[i] = null;
            }
    
            this.size = 0;
            if (this.elements != null && this.elements.length > 10) {
                this.elements = new Object[10];
            }
    
        }
    
        public E get(int index) {
            this.rangeCheck(index);
            return this.elements[index];
        }
    
        public E set(int index, E element) {
            this.rangeCheck(index);
            E old = this.elements[index];
            this.elements[index] = element;
            return old;
        }
    
        public void add(int index, E element) {
            this.rangeCheckForAdd(index);
            this.ensureCapacity(this.size + 1);
    
            for(int i = this.size; i > index; --i) {
                this.elements[i] = this.elements[i - 1];
            }
    
            this.elements[index] = element;
            ++this.size;
        }
    
        public E remove(int index) {
            this.rangeCheck(index);
            E old = this.elements[index];
    
            for(int i = index + 1; i < this.size; ++i) {
                this.elements[i - 1] = this.elements[i];
            }
    
            this.elements[--this.size] = null;
            this.trim();
            return old;
        }
    
        public int indexOf(E element) {
            int i;
            if (element == null) {
                for(i = 0; i < this.size; ++i) {
                    if (this.elements[i] == null) {
                        return i;
                    }
                }
            } else {
                for(i = 0; i < this.size; ++i) {
                    if (element.equals(this.elements[i])) {
                        return i;
                    }
                }
            }
    
            return -1;
        }
    
        private void ensureCapacity(int capacity) {
            int oldCapacity = this.elements.length;
            if (oldCapacity < capacity) {
                int newCapacity = oldCapacity + (oldCapacity >> 1);
                Object[] newElements = new Object[newCapacity];
    
                for(int i = 0; i < this.size; ++i) {
                    newElements[i] = this.elements[i];
                }
    
                this.elements = newElements;
                System.out.println(oldCapacity + "扩容为" + newCapacity);
            }
        }
    
        private void trim() {
            int oldCapacity = this.elements.length;
            int newCapacity = oldCapacity >> 1;
            if (this.size <= newCapacity && oldCapacity > 10) {
                Object[] newElements = new Object[newCapacity];
    
                for(int i = 0; i < this.size; ++i) {
                    newElements[i] = this.elements[i];
                }
    
                this.elements = newElements;
                System.out.println(oldCapacity + "缩容为" + newCapacity);
            }
        }
    
        public String toString() {
            StringBuilder string = new StringBuilder();
            string.append("size=").append(this.size).append(", [");
    
            for(int i = 0; i < this.size; ++i) {
                if (i != 0) {
                    string.append(", ");
                }
    
                string.append(this.elements[i]);
            }
    
            string.append("]");
            return string.toString();
        }
    }
    

    一般不建议是使用添加虚拟头节点的方式进行.

13 复杂度分析(ArrayList)

  • 复杂度的三种情况:

  • 数组访问一个元素,速度是一致的,随机访问是非常快的. 数据规模是指元素的数量,O(n).
  • 只有和数据规模相关的时候,才会分析相应的三种复杂度的情况.拿add方式进行分析,如下所示(remove方式是与add方式相同的):最好的情况是:O(1),最坏情况是O(n),平均是O(n).
  • 链表的添加 删除的复杂度是O(1),是指添加 删除那一刻.
  • 动态数组 链表复杂度分析

14 均摊度分析

  •  适用情况:经过连续的多次复杂度比较低的情况后,出现个别复杂度比较高的情况

15 ArrayList的缩容

  • 当动态数组存在较多的容量的时候,就是需要进行一个缩容的操作.一般是存在相应的删除的过程.

    private void trim() {
        int oldCapacity = this.elements.length;
        int newCapacity = oldCapacity >> 1;//首先是将原始的容量变成原始的一半,
        if (this.size <= newCapacity && oldCapacity > 10) { //如果剩余的空间是原来的容量的一半,
            Object[] newElements = new Object[newCapacity];//声明一个新的容量与数组
            for(int i = 0; i < this.size; ++i) {
                newElements[i] = this.elements[i];
            }
            this.elements = newElements;
            System.out.println(oldCapacity + "缩容为" + newCapacity);
        }
    }

16 复杂度震荡

  • 如果要是扩容倍数和缩容时机设计不得当,会导致复杂度震荡.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值