安卓数据结构01-顺序表与链表

数据结构01-顺序表与链表

一、前言

1.什么是数据结构

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后关系,而与他们在计算机中的存储位置无关。逻辑结构包括:

  • 集合:数据结构中的元素之间除了“同属一个集合” 的相互关系外,别无其他关系;
  • 线性结构:数据结构中的元素存在一对一的相互关系;
  • 树形结构:数据结构中的元素存在一对多的相互关系;
  • 图形结构:数据结构中的元素存在多对多的相互关系。

数据的物理结构:指数据的逻辑结构在计算机存储空间的存放形式。

2.为什么要学数据结构

作为一个安卓程序员,为什么学习数据结构呢?

理由有二:

  1. 提高程序性能。虽然提升的不多,但是性能就是一点点提升起来的
  2. 进名企。去名企面试,数据结构是必问的,到时候不会就尴尬了。

二、线性表

线性表是最简单、最基本、也是最常用的一种线性结构。 线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,通常记为: (a1,a2,… ai-1,ai,ai+1,…an) ,其中n为表长, n=0 时称为空表。 它有两种存储方法:顺序存储和链式存储,它的主要基本操作是插入、删除和检索等。

顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现,由此得到的存储表示称为顺序存储结构。顺序存储结构是一种最基本的存储表示方法,通常借助于程序设计语言中的数组来实现。

链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构,链式存储结构通常借助于程序设计语言中的指针类型来实现

三、顺序表

顺序表是顺序存储的线性表。

顺序表的应用:ArrayList。

1.ArrayList的实现

ArrayList是通过数组实现的。它的查询和修改很简单,我们看下它的增加和删除方法。

指定位置插入:

public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    //动态扩容:当数组元素要超出时,增加0.5倍的大小
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将数组中的元素从指定位置后移一位
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    //在指定位置插入元素                  
    elementData[index] = element;
    size++;
}

删除元素:

    public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        //ArrayList修改的次数
        modCount++;
        E oldValue = (E) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved > 0)
            //将数组中的元素从指定位置前移一位
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        //回收最后一个元素                     
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

2.ArrayList的优缺点

优点:

  • 查改效率高:直接通过下标获取元素,直接通过下标设置元素。因为数组元素在物理上是连续的,知道其中一个的内存地址,就可以推算出其他元素的内存地址。
  • 后面增删效率高:每次增加或删除,都需要移动指定位置后面的元素,所以增删的位置越靠前,需要移动的元素就越多,效率就越低;反之,效率就越高。

缺点:

  • 前面增删效率低。

四、链表

链表是链式存储的线性表。包括单链表、单链循环表、双链表、双链循环表。

1.单链表

链表的逻辑关系是由节点的指针决定的,节点的指针只指向下一个节点的链表就是单链表。

单链表的应用:MessageQueue。

2.单链循环表

将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相连的单链表称为单循环链表,简称循环链表。

3.双链表

双链表有2个指针,next指针指向上一个节点,pre指针指向下一个节点。

双链表的应用:LinkedList。

4.双链循环表

双链循环表是闭环的双链表,它的尾节点的next指针指向头节点,它的头结点的pre指针指向尾节点。

五、单链表

1.单链表的实现

这里我们手写实现一个单链表。

查询:

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

指定位置添加:

public void add(int index, E e) {
    if (index < 0 || index > size) {
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //这里可以跳过
    if (index == size) {
        add(e);
        return;
    }
    //当前位置的上一个节点
    Node<E> prevNode = node(index - 1);
    //当前位置的节点
    Node<E> nowNode = prevNode == null ? node(index) : prevNode.next;
    //生成要添加的节点
    Node<E> newNode = new Node<>(e, nowNode);
    //改变新节点的指针
    if (prevNode != null) {
        prevNode.next = newNode;
    } else {
        first = newNode;
    }
    //单链表的大小
    size++;
}

指定位置删除:

public void remove(int index) {
    if (index < 0 || index >= size) {
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //当前位置的上一个节点
    Node<E> prevNode = node(index - 1);
    //当前位置的节点
    Node<E> nowNode = prevNode == null ? node(index) : prevNode.next;;
    if (prevNode == null) {
        //删除首节点
        removeFirst(nowNode);
    } else {
        //将上一个节点的指针指向下一个节点
        removeNext(prevNode);
    }
}

2.单链表的优缺点

优点:

  • 前面查改效率高:每次查询和修改都要遍历整个链表,所以查改位置越靠前,效率越高;反之,效率越低。
  • 前面增删效率高:增加和删除时,只需要改变上一个节点的指针。但是获取上一个节点,要遍历整个链表。所以增删的位置越靠前,效率越高;反之,效率越低。

缺点:

  • 后面查改效率低;
  • 后面增删效率低。

3.MessageQueue为什么使用单链表

MessageQueue是用来存放延迟消息的,最先发送的消息需要放在链表的最前面,每次取消息就只需要去最前面的数据。一般新插入的消息,都是放在链表的前面。所以查询和插入操作的位置都很靠前,刚好符合单链表的优点。

4.单链表的倒序

单链表由于只有首节点,所以倒序比较麻烦。这里有两种方法:

循环倒序:

public void reset() {
    Node<E> curr = first;
    Node<E> last = null;

    while (curr != null) {
        Node<E> temp = curr.next;
        curr.next = last;
        last = curr;
        curr = temp;
    }
    first = last;
}

递归倒序:

public Node<E> reset(Node<E> curr) {
    //结束条件
    if (curr == null || curr.next == null) {
        return curr;
    }
    //循环条件
    Node<E> tail = reset(curr.next);
    //循环内容
    curr.next.next = curr;
    curr.next = null;
    return tail;
}

六、双链表

1.双链表的实现

这里我们手写实现一个双链表。

查询:

private Node<E> node(int index) {
    Node<E> resuleNode = null;
    if (index <= size / 2) {
        for (int i = 0; i <= index; i++) {
            if (i == 0) {
                resuleNode = first;
            } else {
                resuleNode = resuleNode.next;
            }
        }
    } else {
        for (int i = size - 1; i >= index; i--) {
            if (i == size - 1) {
                resuleNode = last;
            } else {
                resuleNode = resuleNode.prev;
            }
        }
    }
    return resuleNode;
}

指定位置添加:

public boolean add(int index, E e) {
    if (index < 0 || index > size) {
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    if (index == size) {
        addLast(e);
        return true;
    }

    Node<E> nowNode = node(index);
    Node<E> newNode = new Node<>(e, nowNode.prev, nowNode);
    nowNode.prev = newNode;
    if (newNode.prev != null) {
        newNode.prev.next = newNode;
    } else {
        first = newNode;
    }
    size++;
    return true;
}

直接删除节点:

public boolean remove(Node<E> nowNode) {
    boolean result = false;
    Node<E> prevNode = nowNode.prev;
    Node<E> nextNode = nowNode.next;
    if (prevNode == null) {
        first = nextNode;
    } else {
        prevNode.next = nextNode;
    }
    if (nextNode == null) {
        last = prevNode;
    } else {
        nextNode.prev = prevNode;
    }
    size--;
    return result;
}

2.双链表的优缺点

优点:

  • 两端查改效率高:双链表遍历时,是从两端开始,所以查改位置越靠两端,效率越高;反之,效率越低。
  • 两端增删效率高:双链表增删时,只需改变前一个节点和后一个节点的指针。但是获取上一个节点,要遍历整个链表。所以增删的位置越靠两端,效率越高;反之,效率越低。
  • 直接删除节点效率:如果删除的时节点,那么就可知直接其前后节点,所以需改变前一个节点和后一个节点的指针。

缺点:

  • 中间查改效率低;
  • 中间增删效率低;
  • 内存比单链表高:因为有2个指针。

最后

代码地址:https://gitee.com/yanhuo2008/Common/blob/master/Tool/src/main/java/gsw/tool/datastructure/table/LinkedList.java

数据结构与算法专题:https://www.jianshu.com/nb/25128590

喜欢请点赞,谢谢!

    原文作者:最爱的火
    原文地址: https://www.jianshu.com/p/5c811081f7ed
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞