数据结构与算法:数组的操作

简介: 数据结构与算法:数组的操作

📖 前言

在学习数据结构与算法之前, 我们要知道什么是数据结构?为什么要学数据结构与算法?


• 数据结构就是研究数据如何在计算机中进行组织和存储, 使我们可以高效的获取数据和修改数据.


• 要写出好的程序一定要学好数据结构与算法, 我们脑海里要时刻有如下这个公式:


                                  数据结构 + 算法 = 程序

 在学习数据结构过程中,我们要形成一种意识:并不是简单的实现,而是强调比较和优化。


数据结构可以分为三类:

线性结构数组、队列、栈、链表、哈希表...

树型结构二叉树、二分搜索树、AVL树、红黑树、堆、Trie、线段树、并查集...

 图结构   邻接矩阵、邻接表



🎀 数组


概念:

✎ 一组相同数据类型的数或相同类型数的集合.

数组在内存中如何分配:

✎ 分配的是连续的空间, 因此在创建数组时要指定数组的大小, 数组一旦创建就不能修改.

如何声明和定义数组:

✎ int [] arr = new int[10] ;

数组操作:

✎ 增删改查 (crud)     【C:Create(创建)   Retrieve(查询)   Update(更新)   Delete(删除)】

如何使用数组:

✎ 通过数组的下标(索引)   下标是从0开始的,至数组中元素的个数(数组的长度)-1  arr.length

常见问题:

✎ 空数组---[ ]    空对象---null

   NullPointException 空指针异常(要知道谁为空)

   ArraysIndexOutOfBoundsException 下标越界

数组优缺点:

✎ 优:快速查询!   缺:删除/添加元素慢!

常见数组有:

✎ 字符串、哈希表、对象数组.

🎀 自建数组 MyArray

创建属于自己的数组 ----- 注意:是基于java中的数组进行二次封装.


 定义属性 (数据容器、实际存放元素个数、容积)

public class MyArray<T> {
    //定义数据容器
    private T[] data;
    //实际存放元素的个数
    private int size;
    //容积 capacity
    private int capacity;

 创建构造方法 (无参构造方法和有参构造方法)

public MyArray() {
        this(100);
    }
 
    public MyArray(int capacity) {
        //先判断容积是否合法,不合法默认容积为一个值
        if (capacity <= 0) {
            capacity = 100;
        }
        this.capacity = capacity;
        this.size = 0;
        this.data = (T[]) new Object[this.capacity];
    }

 实现MyArray类的功能 (常用方法)

 //获取数组容积
    public int getCapacity() {
        return this.capacity;
    }
 
    //判断是否为空
    public boolean isEmpty() {
        return this.size == 0;
    }
 
    //获取实际存放元素的大小
    public int getSize() {
        return this.size;
    }

✰ 数组操作(增删改查)


📌 添加元素                                                                                                                                  


//添加元素
    public void add(int index, T ele) {
        if (index < 0 || index > this.size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        //判断是否扩容
        if (index == this.capacity) {
            resize(this.capacity * 2);
        }
        //判断哪些元素后移
        for (int i = this.size; i >= index; i--) {
            this.data[i + 1] = this.data[i];
        }
        this.data[index] = ele;
        this.size += 1;
    }
 
    //在头部添加
    public void addHead(T ele) {
        add(0, ele);
    }
 
    //在尾部添加
    public void addTail(T ele) {
        add(this.size, ele);
    }

📌 删除元素                                                                                                                                  


 //删除操作
    public T remove(int index) {
        if (index < 0 || index > this.size) {//先判断索引是否合法
            throw new IllegalArgumentException("index is invalid!");
        }
        //保存删除元素
        T delVal = this.data[index];
        for (int i = index + 1; i < this.size; i++) {
            this.data[i - 1] = data[i];
        }
        this.size--;
        if (this.size <= (this.capacity / 4) && capacity / 2 > 1) {
            resize(this.capacity / 2);
        }
        return delVal;
    }
 
    public T removeHead() {//删除头部元素
        return remove(0);
    }

修改元素                                                                                                                                      


 //修改操作
    public void updateByIndex(int index, T ele) {
        if (index < 0 || index > this.size) {//先判断索引是否合法
            throw new IllegalArgumentException("index is invalid!");
        }
        this.data[index] = ele;
    }

      查询元素                

//查询某个元素是否存在
    public boolean contains(T ele) {
        for (int i = 0; i < this.size; i++) {
            if (this.data[i].equals(ele)) {
                return true;
            }
        }
        return false;
    }
 
    //查询元素在数组中是否存在,如果存在,返回索引,否则返回-1
    public int findIndex(T ele) {
        for (int i = 0; i < this.size; i++) {
            if (this.data[i].equals(ele)) {
                return i;
            }
        }
        return -1;
    }

完整代码

package com.learn.study1;
 
public class MyArray<T> {
    //定义数据容器
    private T[] data;
    //实际存放元素的个数
    private int size;
    //容积 capacity
    private int capacity;
 
    public MyArray() {
        this(100);
    }
 
    public MyArray(int capacity) {
        if (capacity <= 0) {
            capacity = 100;
        }
        this.capacity = capacity;
        this.size = 0;
        this.data = (T[]) new Object[this.capacity];
    }
 
    //获取数组容积
    public int getCapacity() {
        return this.capacity;
    }
 
    //判断是否为空
    public boolean isEmpty() {
        return this.size == 0;
    }
 
    //获取实际存放元素的大小
    public int getSize() {
        return this.size;
    }
 
    //添加元素
    public void add(int index, T ele) {
        if (index < 0 || index > this.size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        //判断是否扩容
        if (index == this.capacity) {
            resize(this.capacity * 2);
        }
        //判断哪些元素后移
        for (int i = this.size; i >= index; i--) {
            this.data[i + 1] = this.data[i];
        }
        this.data[index] = ele;
        this.size += 1;
    }
 
    //在头部添加
    public void addHead(T ele) {
        add(0, ele);
    }
 
    //在尾部添加
    public void addTail(T ele) {
        add(this.size, ele);
    }
 
    //修改操作
    public void updateByIndex(int index, T ele) {
        if (index < 0 || index > this.size) {//先判断索引是否合法
            throw new IllegalArgumentException("index is invalid!");
        }
        this.data[index] = ele;
    }
 
    //查询某个元素是否存在
    public boolean contains(T ele) {
        for (int i = 0; i < this.size; i++) {
            if (this.data[i].equals(ele)) {
                return true;
            }
        }
        return false;
    }
 
    //查询元素在数组中是否存在,如果存在,返回索引,否则返回-1
    public int findIndex(T ele) {
        for (int i = 0; i < this.size; i++) {
            if (this.data[i].equals(ele)) {
                return i;
            }
        }
        return -1;
    }
 
    //删除操作
    public T remove(int index) {
        if (index < 0 || index > this.size) {//先判断索引是否合法
            throw new IllegalArgumentException("index is invalid!");
        }
        //保存删除元素
        T delVal = this.data[index];
        for (int i = index + 1; i < this.size; i++) {
            this.data[i - 1] = data[i];
        }
        this.size--;
        if (this.size <= (this.capacity / 4) && capacity / 2 > 1) {
            resize(this.capacity / 2);
        }
        return delVal;
    }
 
    public T removeHead() {//删除头部元素
        return remove(0);
    }
 
    //重写toString
    @Override
    public String toString() {
        //[1,2,3,4]
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < this.size; i++) {
            sb.append(this.data[i].toString());
            if (i != this.size - 1) {
                sb.append(",");
            }
        }
        sb.append("]");
        return super.toString();
    }
 
    private void resize(int newCapacity) {
        System.out.println("resize操作,新的容积:" + newCapacity);
        T[] newData = (T[]) new Object[newCapacity];
        //空间复杂度 O(n)
        // 将原数组中的数组复制到新数组中
        // 时间复杂度O(n)
        for (int i = 0; i < this.size; i++) {
            newData[i] = this.data[i];
        }
        // 更新容器
        this.data = newData;
        // 更新容积
        this.capacity = newCapacity;
    }
    
    public static void main(String[] args) {
 
    }
 
}

🎀 复杂度分析和均摊复杂度


在算法领域,使用复杂度分析的内容来评价写的代码性能如何。

📌时间复杂度

  • 通常情况下我们只需要简单的时间复杂度分析就OK!
  • 大O描述的是算法的运行时间和输入数据之间的关系 (数据量要足够的大,趋于无穷)
  • 通常情况有:O(1),O(n),O(Ign),O(nIogn),O(n^2)

•  在计算时需要忽略常数,如:


T = 2*n+2                  -------- O(n)


T = 20000*n+50000  -------- O(n)

T = 5 * n * n + 5         -------- O(n^2)

这时很多小伙伴就问了:比如n取10, T2明显时间大于T3啊注意:此时我们要考虑n趋于无穷的情况  (而n趋于无穷时常数就可以忽略)

综合来看:

  • 增:O(n)
  • 删:O(n)
  • 改:已知索引O(1);未知索引O(n)
  • 查:已知索引O(1);未知索引O(n)

📌均摊时间复杂度

resize O(n)

假设capacity = n , n+1次addLast ,触发resize(扩容),一共进行了2n+1次操作,平均每次addLast操作进行两次操作,这样均摊计算:时间复杂度为O(1)

注意:

当我们同时进行 addLast 和 removelast 操作时,如果过于着急会出现均摊度的震荡问题,此时我们可以在缩容的时候缓一点。


相关文章
|
15天前
|
存储 算法 Go
算法学习:数组 vs 链表
算法学习:数组 vs 链表
22 0
|
7天前
|
存储 算法 调度
【数据结构与算法】详解循环队列:基于数组实现高效存储与访问
【数据结构与算法】详解循环队列:基于数组实现高效存储与访问
|
11天前
|
存储 JavaScript 前端开发
JavaScript中的数组是核心数据结构,用于存储和操作序列数据
【6月更文挑战第22天】JavaScript中的数组是核心数据结构,用于存储和操作序列数据。创建数组可以使用字面量`[]`或`new Array()`。访问元素通过索引,如`myArray[0]`,修改同样如此。常见方法包括:`push()`添加元素至末尾,`pop()`移除末尾元素,`shift()`移除首元素,`unshift()`添加到开头,`join()`连接为字符串,`slice()`提取子数组,`splice()`进行删除、替换,`indexOf()`查找元素位置,`sort()`排序数组。还有其他如`reverse()`、`concat()`等方法。
41 2
|
18天前
|
算法 索引 Python
数据结构 数组部分小结
数据结构 数组部分小结
|
7天前
|
存储 算法 编译器
【数据结构与算法】使用数组实现栈:原理、步骤与应用
【数据结构与算法】使用数组实现栈:原理、步骤与应用
|
8天前
|
算法 Java
Java数据结构与算法:位运算之位移操作
Java数据结构与算法:位运算之位移操作
|
8天前
|
存储 算法 Java
Java数据结构与算法:线性数据结构之数组
Java数据结构与算法:线性数据结构之数组
|
12天前
|
算法
【洛谷 P1003】[NOIP2011 提高组] 铺地毯 题解(数组+贪心算法)
**NOIP2011 提高组问题:铺地毯** 在第一象限的颁奖典礼场地,有$n$张地毯按编号顺序铺设。需找出覆盖特定点$(x, y)$的最上方地毯编号。输入包括地毯坐标和点坐标,输出地毯编号或-1表示未覆盖。 样例:给定3张地毯,点$(2,2)$被第3张地毯覆盖,输出3;另一样例点$(4,5)$未被覆盖,输出-1。 $30\%$数据$n\leq2$,$50\%$数据坐标$\leq100$,$100\%$数据$n\leq10^4$,坐标$\leq10^5$。 解决方法:从下到上遍历地毯,更新覆盖点的地毯编号。 AC代码略。
7 0
|
13天前
|
索引
栈的数组实现
栈的数组实现
7 0