极客时间-算法训练营 学习笔记 1. 数组、链表、跳表的基本实现和特性

一序

  本文属于极客时间-算法训练营 学习笔记。

二 数组

   Java:new int[100];

  • 可以直接进行初始化

  • 高级语言,对于数组中的元素类型没有限制--->泛型

    计算机底层:内存空间开辟一块连续的地址。

  • 每一个地址直接可以通过内存管理器进行访问

  • 无论访问哪个元素,时间复杂度是一样的,常数O(1)的

    特点:可以随机的访问。

数组的问题,关键在于要增加、删除数组元素的时候,会进行相应的操作,
如下图: 要在index=3的位置插入“D”,则需要移动“E”“F”“G”,此时源码中会涉及非常多的arraycopy操作,时间复杂度由常数级的复杂度--->O(n),(平均移动移动一半的数据)删除同理,将最后的元素设置为空,调用垃圾回收size-1。

Java的arrayList源码就是这样实现的。

    //追加到末尾
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    //指定位置插入
   public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //复制
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //插入
        elementData[index] = element;
        size++;
    }

注意: arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

是吧源目标位置拷贝到目标位置,

 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }    
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

关于扩容ensureCapacityInternal:

首先对旧容量oldCapacity进行1.5倍的扩容,之后如果newCapacity仍然小于最小要求容量minCapacity,就把minCapacity的值给到newCapacity。如果newCapacity的值大于最大要求容量,则会走hugeCapacity()方法,进行一个最终扩容后的大小确认,最后调用Arrays.copyof()方法,在原数组的基础上进行复制,容量变为newCapacity的大小。

由此看来,对于数组扩容涉及到很多array.copy.效率很低。

三 链表

为了弥补数组的插入、删除带来的复杂度上升的缺点

  • 一般用class来定义,每个元素有Value、Next,next指向下一个元素。
  • 只有一个next指针叫单链表
    有时候往前一个指叫它的先前指针(previous),是双向链表
  • 头指针用head表示,尾指针用tail表示,
  • 最后一个元素next指向空。
  • 如果tail指针可以指向head指针,就叫循环链表

java的源码:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }
   。。。
  private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

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

它是双向链表。first 、last分别起到了双向指针的作用。

增加节点

删除节点

  • 增加或删除节点的话,没有引起整个链表的群移操作,也不需要复制元素,挪动元素到新的位置,所以它移动的效率和修改的操作效率非常高,复杂度为O(1)

这个结构导致了访问链表中的任何一个位置,(除了头尾节点),复杂度为O(n)

链表操作时间复杂度:
prepend ---> O(1)
append ---> O(1)
lookup ---> O(n)
insert ---> O(1)
delete ---> O(1)
Array操作复杂度:
prepend ---> O(1)
append ---> O(1)
lookup ---> O(1)
insert ---> O(n)
delete ---> O(n)

四 跳表

4.1问题由来:

针对链表有序的情况下,O(n)的时间复杂度已经太慢了。

4.2优化的思路:

升维,将一维结构变为二维结构,不仅仅增加一个头指针和尾指针,还有中间节点。起到加速的作用。

空间换时间,贯穿于整个数据结构和算法.

以此类推,通过增加索引级数来提高查询速度。

4.3、跳表查询的时间复杂度分析

n/2、n/4、n/8、第k级索引结点的个数就是n/(2^k)

假设索引有h级,最高级的索引有2个结点。n/(2^h)=2,从而求得h=log2(n)-1

索引的高度:logn,每层索引遍历的结点个数:3

在跳表中查询任意数据的时间复杂度就是O(logn),也就是从最朴素的原始链表的O(n)的时间复杂度降到了log2n的时间复杂度。

现实中的跳表:维护成本相对较高,每增加或删除一个元素的时候,需要把它的索引都更新一遍。(时间复杂度为logn)

4.4 跳表的空间复杂度

5、工程中的应用

真的是干货慢慢,因为每一块知识点展开都是长篇文章的。

猜你喜欢

转载自blog.csdn.net/bohu83/article/details/107028881