算法之数据结构基础(1)

一、数组介绍

1、数组是什么?

  • 数组(Array),是有限个相同类型的变量所组成的有序集合,数组中每一个变量被称为元素。数组是最简单、常用的数据结构。
  • 特点:在内存中顺序存储,实现逻辑上的顺序表。数组的每个元素,都存储在各自的内存单元中,元素之间紧密排列,不能打乱元素的存储顺序,也不能跳过某个存储单元进行存储。
    在这里插入图片描述

2、如何操作数组?

从增删改查四个角度介绍

(1)查

对于数组来说,读取最为简单。因为数组中的元素在内存中顺序存储,故只需要指定数组下标即可获取相对应的元素。根据下标读取元素的方式又叫做随机读取

int[] arr = new int[]{5, 2, 3, 1};
System.out.println(arr[2]);//输出:3

读取元素时间复杂度为O(1)

(2)改

对于数组来说,要改一个数组中的元素也很简单。只需要给指定的元素赋新值即可。

int[] arr = new int[]{5, 2, 3, 1};
System.out.println(arr[2]);//输出3
arr[2] = 100;
System.out.println(arr[2]);//输出100

更新元素的时间复杂度为O(1)

(3)删

对于数组来说,删除一个位于中间索引上的元素,这个索引就变成了空,需要将此索引后的元素全部前移一位,保证数组的顺序连续性。
在这里插入图片描述

看代码实现。

/**
 * @author chengyanqi
 * @date 2020/7/23 21:45
 */
public class ArrayDemo {
    private static Integer[] arr;
    private static int size;
    public static void main(String[] args) throws Exception {
        size = 5;
        arr = new Integer[size];
        arr[0] = 0;
        arr[1] = 1;
        arr[2] = 2;
        arr[3] = 3;
        arr[4] = 4;
        size = arr.length;
        System.out.println(Arrays.toString(arr));
        int element = deleteElementByIndex(3);
        System.out.println("删除元素的索引:" + element);
        System.out.println(Arrays.toString(arr));
    }
    /**
     * 删除数组中的元素
     * @param index 删除的元素的索引
     */
    public static int deleteElementByIndex(int index) throws Exception {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("数组越界异常");
        }
        int deleteElement = arr[index];
        for (int i = index; i < size - 1; i++) {
            arr[i] = arr[i + 1];
        }
        arr[size - 1] = null;
        size--;
        return deleteElement;
    }
}
[0, 1, 2, 3, 4]
删除元素的索引:3
[0, 1, 2, 4, null]

删除操作,涉及到元素的移动,遍历。故时间复杂度为O(n)

(4)增

对于数组来说,插入元素较为复杂。
首先针对数组元素少于数组长度的情况。分为了两种插入:1、尾部插入。2、中间插入(包括头部插入)

  • 尾部插入。和更新类似,直接放到尾部空闲位置即可。
    在这里插入图片描述
  • 中间插入。需要考虑到插入索引位置后的元素移动。插入索引的元素后,后面的元素都要后移一位。
    在这里插入图片描述
/**
 * @author chengyanqi
 * @date 2020/7/23 22:33
 */
public class ArrayDemo2 {
    private static int[] arr;
    private static int size;
    public static void main(String[] args) throws Exception {
        initArr(10);
        insert(0,5);
        insert(1,5);
        insert(2,5);
        insert(3,5);
        insert(4,5);
        System.out.println(Arrays.toString(arr));
    }
    //初始化数组
    public static void initArr(int size) {
        arr = new int[size];
        ArrayDemo2.size = 0;
    }
    /**
     * 数组插入元素
     * @param index 索引
     * @param element 元素
     */
    public static void insert(int index, int element) throws Exception{
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("数组越界异常");
        }
        //由于起始数组为空,不能从左向右遍历移动。否则抛出数组越界异常。
        //从右向左循环,大于index的元素向右移动一位
        for (int i = size - 1; i >= index; i--) {
            arr[i + 1] = arr[i];
        }
        arr[index] = element;
        size++;
    }
}
[5, 5, 5, 5, 5, 0, 0, 0, 0, 0]

插入需要移动、遍历。时间复杂度为O(n)

(5)扩容

如果考虑到,插入元素超过数组长度。这时候就涉及到数组的扩容了。我们可以新定义一个数组长度为原数组两倍的新数组,将旧数组内的元素移动到新数组中,这样就实现了数组的扩容。
在这里插入图片描述
修改上面插入元素的代码。

/**
 * @author chengyanqi
 * @date 2020/7/23 22:33
 */
public class ArrayDemo2 {
    private static int[] arr;
    private static int size;
    public static void main(String[] args) throws Exception {
        initArr(3);
        insert(0,5);
        insert(1,5);
        insert(2,5);
        insert(3,5);
        insert(4,5);
        System.out.println(Arrays.toString(arr));
    }
    //初始化数组
    public static void initArr(int size) {
        arr = new int[size];
        ArrayDemo2.size = 0;
    }
    /**
     * 数组插入元素
     * @param index 索引
     * @param element 元素
     */
    public static void insert(int index, int element) throws Exception{
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("数组越界异常");
        }
        //如果插入元素index达到数组容量上限,则进行扩容
        if (index >= arr.length) {
            System.out.println("扩容前数组:" + Arrays.toString(arr));
            expansion();
            System.out.println("扩容后数组:" + Arrays.toString(arr));
        }
        //由于起始数组为空,不能从左向右遍历移动。否则抛出数组越界异常。
        //从右向左循环,大于index的元素向右移动一位
        for (int i = size - 1; i >= index; i--) {
            arr[i + 1] = arr[i];
        }
        arr[index] = element;
        size++;
    }
    public static void expansion() {
        int[] newArr = new int[arr.length * 2];
        //赋值老数组中的元素进入新数组
        System.arraycopy(arr, 0, newArr, 0, arr.length);
        arr = newArr;
    }
}
扩容前数组:[5, 5, 5]
扩容后数组:[5, 5, 5, 0, 0, 0]
[5, 5, 5, 5, 5, 0]

插入需要移动、遍历,时间复杂度为O(n)。扩容时间复杂度为O(n)。综合来说,插入并扩容时间复杂度为O(n)

(6)数组小结

数组具有高效的随机访问。对于查找、更新而言,只要根据索引下标,就可以用O(1)的时间复杂度找到元素。
数组的插入、删除操作涉及到元素的被迫移动,影响效率。
总结:读操作时间复杂度为O(1),写操作时间复杂度为O(n)。数组适合于读多写少的场景。

二、链表介绍

1、链表是什么?

链表(linked list)是一种在物理上非连续、非顺序的数据结构。由若干个节点node组成。第一个节点又被称为头节点,最后一个节点被称为尾节点。整体是头节点的next指向下一个节点,下一个节点的next指向下下一个节点,直到指向尾部节点。
单向链表,每个节点包含两个部分,一个是存储数据的变量data,一个是指向下一个节点的next。

public class Node {
 	Node next;
  	int data;
}

双向连边,每个节点由三部分组成,指向上个节点的pre,存储变量的data,指向下个节点的next。

public class Node {
	Node pre;
	Node next;
	int data;
}

2、如何操作链表?

(1)查

查找链表不像数组那样通过下标快速查找到需要的元素,只能从头部开始next、next、next…直到找到所需要的节点。
在这里插入图片描述

private Node head;
private Node last;
private int size;
public Node get(int index) throws Exception {
    if (index < 0 || index >= size) {
        throw new Exception("超出链表范围");
    }
    Node node = head;
    for (int i = 0; i < index; i++) {
        node = node.next;
    }
    return node;
}

查找链表的时间复杂度,考虑最坏情况为O(n)

(2)改

如果不考虑查找链表的过程,更新链表比较简单,和数组一样,直接由新数据替换旧数据即可。

(3)增

新增节点分为三种情况,1、尾部插入。2、头部插入。3、中间插入
尾部插入:把最后一个节点的next指向新插入的节点即可。
在这里插入图片描述
头部插入:将新节点的next指向原本的头部节点,再将新节点变为链表的头部节点
在这里插入图片描述
中间插入:新节点的next指向插入位置的节点,插入位置前的节点的next指向新插入的节点。
在这里插入图片描述

private Node head;
private Node last;
private int size;
public void insert(int data,int index) throws Exception{
    if (index < 0 || index > size) {
        throw new Exception("超出链表范围");
    }
    Node insertNode = new Node(data);
    if (size == 0) {
        head = insertNode;
        last = insertNode;
    } else if (index == 0) {
        insertNode.next = head;
        head = insertNode;
    } else if (index == size) {
        last.next = insertNode;
        last = insertNode;
    } else {
        Node preNode = get(index-1);
        insertNode.next = preNode.next;
        preNode.next = insertNode;
    }
    size++;
}

链表的插入,不需要考虑节点的扩容问题。不考虑插入前的查找过程,时间复杂度为O(1)

(4)删

删除节点同样分为三种情况,1、尾部删除。2、头部删除。3、中间删除。
尾部删除:直接把到数第二个节点指向null即可。
在这里插入图片描述
头部删除:将头节点head设置为原先头节点的next节点即可。
在这里插入图片描述
中间删除:将被删除节点的前一个节点的next指向被删除节点的后一个节点即可。
在这里插入图片描述

private Node head;
private Node last;
private int size;
public Node remove(int index) throws Exception {
    if (index < 0 || index >= size) {
        throw new Exception("超出链表范围");
    }
    Node removeNode = null;
    if (index == 0) {
        removeNode = head;
        head = head.next;
    } else if (index == size - 1) {
        Node preLastNode = get(index - 1);
        removeNode = preLastNode.next.next;
        preLastNode.next = null;
        last = preLastNode;
    }else {
        Node preNode = get(index - 1);
        Node lastNode = preNode.next.next;
        removeNode = preNode.next;
        preNode.next = lastNode;
    }
    size--;
    return removeNode;
}

链表删除节点的时间复杂度同样为O(1)。Java中有垃圾回收机制,不被引用的节点会自动被回收。

(5)小结

链表更适用于读少写多的场景。读O(n),增删改O(1)

三、总结

数据结构
数组O(1)O(1)O(n)O(n)
链表O(n)O(1)O(1)O(1)

由表格不难看出,数组和链表各有优势
数组更适用于查找的场景,链表更适用于插入和删除操作多的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程大帅气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值