数据结构与算法(一)-顺序表与链表

一 .前言

 1.什么是数据结构

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

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

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

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

  2.为什么要学数据结构

 1. 数据结构+算法 = 程序

 2. 大厂的面试基本都会面试到算法.

下面正式开始................................................................................................

二 线性表

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

数据结构与算法(一)-顺序表与链表_第1张图片

 

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

数据结构与算法(一)-顺序表与链表_第2张图片

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

三 顺序表

基本思想:元素的存储空间是连续的.在内存中是以顺序存储,内存划分的区域是连续的,顺序表是顺序存储的线性表

代表: ArrayList,Vector,数组,Student[] stus = new Student(){new Student(),new Student(),........}

在某个索引位置添加元素

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

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

所以,arraylist的优缺点:

优点:

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

缺点:

  • 前面增删效率低

四.链表

链表是链式存储的线性表.包括单链表.单链循环表,双链表,双链循环表

1.单链表

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

单链表的应用:MessageQueue。

2.单链循环表

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

3.双链表

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

双链表的应用:LinkedList。

4.双链循环表

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

五、单链表

1.单链表的实现

单链表是由一系列节点组成,节点有数据域和指针域 ,例如Message就是一个节点,MessageQueue就是单链表

数据结构与算法(一)-顺序表与链表_第3张图片

MessageQueue插入一个节点,主要是

boolean enqueueMessage(Message msg, long when) {

      .....

Message prev;
//for 循环是找到message要插入的位置
for (;;) {
    prev = p;
    p = p.next;
    if (p == null || when < p.when) {
        break;
    }
    if (needWake && p.isAsynchronous()) {
        needWake = false;
    }
}
//将message插入到messageQueque中
msg.next = p; // invariant: p == prev.next
prev.next = msg;

  }

MessageQueue删除一个节点

Message next(){
   .....
   
synchronized (this) {
    // Try to retrieve the next message.  Return if found.
    final long now = SystemClock.uptimeMillis();
    Message prevMsg = null;
    Message msg = mMessages;
    if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }
    if (msg != null) {
        if (now < msg.when) {
            // Next message is not ready.  Set a timeout to wake up when it is ready.
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
            // Got a message.
            mBlocked = false;
            if (prevMsg != null) {
                prevMsg.next = msg.next;
            } else {
                mMessages = msg.next;
            }
            msg.next = null;
            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
            msg.markInUse();
            return msg;
        }
    } else {
        // No more messages.
        nextPollTimeoutMillis = -1;
    }
}
 

自己手写实现

public class SingleLinkedList {


    //头节点
    private Node first;
    //尾节点
    private Node last;
    //单链表长度
    private int size;
    //查询
    private Node node(int index){
        Node resultNode = first;
        for (int i = 0; i < index; i++) {
            resultNode = first.next;
        }
        return resultNode;
    }
    //增加在尾部
    private void add(E e){
        Node newNode = new Node<>(e,null);
        Node temp = last;
        if (temp==null){
            first = newNode;
            last = newNode;
        }else {
            temp.next = newNode;
        }
        size++;
    }
    //增加在某个位置
    private void add(E e,int index){

        if (index<0||index>size){
            throw new IndexOutOfBoundsException();
        }
        if (index==size){
            add(e);
        }else {
            Node nextNode = node(index);
            Node preNode = node(index - 1);
            Node newNode = new Node<>(e,nextNode);
            preNode.next = newNode;
        }
        size++;
    }

    //删除索引处的元素
    private void remove(int index){
        if (index<0||index>size){
            throw new IndexOutOfBoundsException();
        }
        Node target = node(index);
        Node next = target.next;

        if (index==0||index==size){
           if (index==0){
               //删除的元素刚好是第0个元素
               first = target.next;
           }else {
               //集合中只有一个元素
               if (node(index-1)==null){
                   first=null;
                   last=null;
                   size=0;
               }else {
                   //将最后一个元素置为null
                   node(index-1).next=null;
                   size--;
               }
           }
        }else {
            node(index-1).next = target.next;
            size--;
        }
    }
    class Node {

        private E item;
        private Node next;


        public Node(E item, Node next) {
            this.item = item;
            this.next = next;
        }
    }


}

2.单链表的优缺点

优点:

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

缺点:

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

3.MessageQueue为什么使用单链表

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

 

4.单链表的倒序

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

//循环倒序
public void reset() {
    Node curr = first;
    Node last = null;

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

//递归倒序
public Node reset(Node curr) {
    //结束条件
    if (curr == null || curr.next == null) {
        return curr;
    }
    //循环条件
    Node tail = reset(curr.next);
    //循环内容
    curr.next.next = curr;
    curr.next = null;
    return tail;
}

六、双链表

 

1.双链表的实现

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

public class LinkedList {
    /**
     * 结点
     */
    private static class Node {
        E item;
        Node prev;
        Node next;

        public Node(Node prev, E item, Node next) {
            this.item = item;
            this.prev = prev;
            this.next = next;
        }
    }

    public LinkedList() {

    }

    //头节点
    Node first;
    //尾节点
    Node last;
    //大小
    int size;

    /**
     * 添加数据在最后
     */
    public void add(E e) {
        linkLast(e);
    }

    /**
     * 添加到最后
     * @param e
     */
    private void linkLast(E e) {
        Node newNode = new Node(last, e, null);
        Node l = last;
        last=newNode;

        if(l==null){
            first=newNode;
        }else {
            l.next = newNode;
        }
        size++;
    }
    /**
     * 查找位置
     */
    public E get(int index){
        if(index<0 || index>size){
            return null;
        }
        return node(index).item;
    }
    /**
     * 获取index位置上的节点
     */
    private Node node(int index){

        //如果index在整个链表的前半部分
        if(index<(size>>1)){   //1000 100   10
            Node node=first;
            for (int i = 0; i < index; i++) {
                node=node.next;
            }
            return node;
        }else{
            Node node=last;
            for (int i = size-1; i > index; i--) {
                node=node.prev;
            }
            return node;
        }


    }

    /**
     * 添加数据在index位置
     */
    public void add(int index,E e) {
        if(index<0 || index>size){
            return ;
        }
        if(index==size){
            linkLast(e);
        }else{
            Node target=node(index);//  index=2
            Node pre=target.prev;
            Node newNode=new Node(pre,e,target);

            if(pre==null){
                first=newNode;
                target.prev = newNode;//4
            }else {
                pre.next = newNode;//3
                target.prev = newNode;//4
            }
            size++;
        }

    }

    /**
     * 删除元素
     */
    public void remove(int index){
        Node target=node(index);
        unlinkNode(target);
    }

    private void unlinkNode(Node p) {//index=2
        Node pre=p.prev;
        Node next=p.next;
        if(pre==null){
            first=p.next;
        }else{
            pre.next=p.next;
        }
        if(next==null){
            last=p.prev;
        }else{
            next.prev=p.prev;
        }
        size--;
    }

}

2.双链表的优缺点

优点:

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

缺点:

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

你可能感兴趣的:(数据结构与算法,数据结构与算法)