Java笔记——数组

1.前言:

数组相当于程序中的容器,是一种重要的基础数据结构,用于存储一系列相同类型的数据。

1.1 数组(Array)概念

用于存储固定大小的同类型元素的集合。每个数组都有一个固定的长度 (一旦创建,长度不可更改),并通过一个索引(从0开始)来访问数组中的元素。

1.2 构成

  • 数组名
  • 索引、index、下标(找到指定数组元素所使用的编号)
  • 元素
  • 数组长度(存储数组容器中的存储的元素的个数)

图解:

图源来自网络
图源来自网络

1.3 数组的特点

  • 数组本身是 引用数据类型 ,数组中的元素可以是 任何数据类型
  • 创建数组对象会在内存中开辟一整块“连续的空间”,占用内存空间大大小,取决于数组的长度和数组中元素的类型。
  • 数组中的元素在内存中是依次紧密排列的,有序的。
  • 数组的初始化完成,长度就确定,无法更改。 
  • 数组名中引用的是这块连续空间的首地址。

1.4 为什么要使用数组

  1. 存储多个值:数组能够在单一变量中存储多个数据值,简化了数据的管理和操作。

  2. 快速访问:通过索引访问数组中的元素非常快速,时间复杂度为O(1)。这使得数组特别适合需要频繁访问和操作数据的场景。

  3. 内存管理:数组允许在内存中连续存储数据,提高了内存的使用效率。

  4. 数据结构基础:数组是实现其他复杂数据结构(如列表、栈、队列等)和算法(如排序、查找等)的基础。

1.5 数组的分类

数组维数分类:

  • 一维数组:存储一系列同类型的元素,类似于线性列表。
  • 多维数组:由多个一维数组组成,通常用于表示矩阵或表格数据。最常用的是二维数组。

2.一维数组

2.1 声明和初始化,索引

/*数组的声明和初始化

声明数组*/
int[] numbers;
/*初始化数组:
使用 new 关键字:*/
// 创建一个长度为5的整数数组
numbers = new int[5];

//在声明时直接初始化:
int[] numbers = {1, 2, 3, 4, 5};

/*2. 调用数组的指定元素
要访问数组中的特定元素,可以使用索引,索引从0开始。例如:*/
int firstElement = numbers[0]; // 访问数组中的第一个元素
int secondElement = numbers[1]; // 访问数组中的第二个元素
//可以通过索引来修改数组的元素:
numbers[0] = 10; // 将第一个元素的值修改为10

2.2 长度与遍历

/*数组的长度,用来描述数组容器中容量的大小
使用length属性表示:
*/
System.out.printlt(数组名.length);
// 使用传统的 for 循环
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// 使用增强的 for 循环
for (int item : arr) {
    System.out.println(item);
}

2.3 数组元素的默认初始化值

数组的元素在创建时会被自动初始化为其默认值。默认值取决于数据类型:

  • 对于整数类型(如 intlongshortbyte),默认值为 0
  • 对于浮点类型(如 floatdouble),默认值为 0.0
  • 对于字符类型(char),默认值为 '\u0000'(空字符)。
  • 对于布尔类型(boolean),默认值为 false
  • 对于引用数据类型,默认值为 null
public class Person {
    public static void main(String[] args) {
        //采用int类型
        int[] arr = new int[5];
        System.out.println("这是int类型的默认初始化值: " + arr[0]);

        short[] arr2 = new short[4];
        System.out.println("这是short类型的默认初始化值: " + arr2[0]);
        //采用浮点类型
        double[] arr3 = new double[3];
        System.out.println("这是double类型的默认初始化值: " + arr3[0]);
        //采用字符类型
        char[] arr4 = new char[3];
        System.out.println("这是char类型的默认初始化值: " + arr4[0]);
        //采用布尔类型
        boolean[] arr5 = new boolean[2];
        System.out.println("这是boolean类型的默认初始化值: " + arr5[0]);
        //采用引用数据类型
        String[] arr6 = new String[5];
        for (int i = 0; i < arr6.length; i++) {
            System.out.println("这是String类型的默认初始化值: " + arr6[i]);
        }

        }
    }

结果:

2.4 内存解析

一维数组的内存分配是动态的。当你使用 new 关键字创建数组时,Java虚拟机(JVM)在堆内存中分配相应大小的内存空间。数组的长度在创建时被固定,数组中的每个元素都会占用相同大小的内存块。

内存空间可以分为5个部分:程序计数器、虚拟机栈、本地方法栈、堆、方法区。

与目前数组相关的内存结构:

  • 虚拟机栈:用于存放方法中的声明的变量。如:arr。
  • 堆:用于存放数组的实体(数组中的所有元素)如:1.2.3

3.二维数组

理解:可以堪称是一维数组arr1又作为另一个一维数组arr2的元素而存在。即嵌套一维数组

3.1 声明和初始化,索引

public class TwoDimensionalArrayExample {
    public static void main(String[] args) {
        //静态声明并初始化二维数组
        int[][] array = new int[][]{
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };
        //动态初始化
        String[][] arr2 = new String[3][4]
        // 访问第二行第三列的元素,值为 6
        int value = array[1][2]; 
        /*对于内层元素的调用,要将元素的行列写清楚
          对于外层元素的调用,只要写清楚行*/
        System.out.println(array[0][1]);//内层元素
        System.out.println(array[0]);//外层元素
    }
}

3.2 长度与遍历

//通过 length 属性获取二维数组的行数和列数:
int rows = array.length; // 行数
int cols = array[0].length; // 列数(假设所有行列数相同)
//使用嵌套的 for 循环遍历二维数组:
for (int i = 0; i < array.length; i++) { // 遍历行
    for (int j = 0; j < array[i].length; j++) { // 遍历列
        System.out.print(array[i][j] + " ");
    }
    System.out.println(); // 换行
}

3.3 默认初始化值

二维数组的元素会被默认初始化为以下值:

外层:

  • 表示地址值
System.out.println(arr[3]);//@32674235

内层:

  • 对于 int 类型:0
  • 对于 boolean 类型:false
  • 对于 char 类型:'\u0000'(空字符)
  • 对于引用数据类型:null
System.out.println(arr3[0][1]);//值为null

3.4 二维内存解析

Java 中的二维数组实际上是一个数组的数组。在内存中,它的分配是非连续的,每个内层数组的内存地址可能不在同一位置。这种结构使得可以有不规则的数组(即每一行的列数可以不同)。

解释:

       先开辟了arr1的三行两列的空间,arr2的空间则是4行n列,arr2的第二行有5列,由于是int类型默认为0,再将arr2的第二行第二列从0变1。由于arr2的第三行没有说明列数,所以arr的第三行第三列不存在,报错。

解释:

       先给arr1的4行n列开辟空间,再给arr1的第一行开辟了三列的空间,int类型默认值为0。arr1的第二行也开辟了三列的空间,但有赋值1,2,3。将arr1的第一行第三列的值改为5。最后相当于初始化了一个新的arr1。

4.数组的常见算法操作

4.1 特征值计算

示例:

import java.util.Random;

public class ArrayTest {
    public static void main(String[] args) {
        // 创建一个大小为10的int型数组
        int[] numbers = new int[10];
        
        // 创建一个Random对象用于生成随机数
        Random random = new Random();
        
        // 使用循环为数组赋随机值
        for (int i = 0; i < numbers.length; i++) {
            // 生成0到100之间的随机整数
            numbers[i] = random.nextInt(101); // 包含0到100的随机数
        }

        // 初始化最大值、最小值和总和
        int max = numbers[0];
        int min = numbers[0];
        int sum = 0;

        // 遍历数组,计算最大值、最小值和总和
        for (int number : numbers) {
            if (number > max) {
                max = number; // 更新最大值
            }
            if (number < min) {
                min = number; // 更新最小值
            }
            sum += number; // 累加到总和
        }

        // 计算平均值
        double average = (double) sum / numbers.length;

        // 输出结果
        System.out.println("数组元素: ");
        for (int number : numbers) {
            System.out.print(number + " ");
        }
        System.out.println("\n最大值: " + max);
        System.out.println("最小值: " + min);
        System.out.println("总和: " + sum);
        System.out.println("平均值: " + average);
    }
}

结果:

4.2 数组元素的赋值与反转

赋值示例:杨辉三角,10行

public class YangHuiTriangle {
    public static void main(String[] args) {
        // 定义二维数组,用于存储杨辉三角的10行
        int[][] triangle = new int[10][];
        
        // 构造杨辉三角
        for (int i = 0; i < 10; i++) {
            // 每一行的长度为i + 1
            triangle[i] = new int[i + 1];
            
            // 第一列和最后一列的元素为1
            triangle[i][0] = 1;
            triangle[i][i] = 1;
            
            // 计算每一行的其他元素
            if (i > 1) {  // 从第三行开始
                for (int j = 1; j < i; j++) {
                    triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
                }
            }
        }

        // 打印杨辉三角
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < triangle[i].length; j++) {
                System.out.print(triangle[i][j] + " ");
            }
            System.out.println(); // 每行输出后换行
        }
    }
}

结果:

数组反转的示例:反转这个一维数组

public class Person {
    public static void main(String[] args) {
        int[] fanzhuan = new int[]{1,2,3,4,5};
        //遍历原本的顺寻
        for (int i = 0; i < fanzhuan.length; i++) {
                System.out.print(fanzhuan[i]+ " ");
        }
        System.out.println(); // 每行输出后换行
        //反转操作
        for (int i = 0; i < fanzhuan.length/2; i++) {
            int temp = fanzhuan[i];
            fanzhuan[i] = fanzhuan[fanzhuan.length-1-i];
            fanzhuan[fanzhuan.length-1-i] = temp;
        }
        //
        for (int i = 0; i < fanzhuan.length; i++) {
            System.out.print(fanzhuan[i]+ " ");
        }
        System.out.println(); // 每行输出后换行

    }
}

结果:

4.3 数组的扩容和缩容

示例:int[] arr = new int[] {1,2,3,4,5};将数组扩容一倍,并插入6,7,8三个元素到数组中

public class Person {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};

        int[] newArr = new int[arr.length << 1];

        for (int i = 0; i < arr.length; i++) {
            newArr[i] = arr[i];
        }

        newArr[arr.length] = 6;
        newArr[arr.length+1] = 7;
        newArr[arr.length+2] = 8;

        arr = newArr;

        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] +"\t");
        }
    }
}

结果:

示例:int[] arr = {1,2,3,4,5,6},现需删除数组中索引为3的元素,也就是删除4,即缩容一列。 


public class Person {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5,6,7};
        int index1 = 3;
        /*不使用新数组
        for (int i = index1; i < arr.length-1; i++) {
            arr[i] = arr[i+1];
        }
        arr[arr.length-1] = 0;
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]);
        }
        */
        //使用新数组
        int[] newArr = new int[arr.length - 1];
        for (int i = 0; i < index1; i++) {
            newArr[i] = arr[i];
        }
        for (int i = index1; i < arr.length - 1; i++) {
            newArr[i] = arr[i+1];
        }
        for (int i = 0; i < newArr.length; i++) {
            System.out.print(newArr[i]);
        }

    }
}

结果:

4.4 查找数组元素

4.4.1 线性查找

示例:int[] arr1 = new int[]{55,66,77,1,2,3,4,5,76,34,12};查找元素77是否出现过,出现求索引值


public class Person {
    public static void main(String[] args) {
        int[] arr1 = new int[]{55,66,77,1,2,3,4,5,76,34,12};
        int target = 77;
        //线性查找
        boolean isFlag = true;
        for (int i = 0; i < arr1.length; i++) {
            if (target == arr1[i]){
                System.out.println("找到啦"+ target + ",对应位置为:"+ i);
                isFlag = false;
                break;
            }
        }
        if (isFlag) {
            System.out.println("没找到");
        }
        
    }
}

结果:

4.4.2 二分法查找

说明:适用于在有序数组中查找特定元素。其基本思路是将数组分成两半,逐步缩小查找范围。

步骤:

  1. 初始化:设置两个指针,low指向数组的开始,high指向数组的结束。
  2. 查找
    • 计算中间索引 mid = (low + high) / 2
    • 比较 mid 位置的元素与目标值:
      • 如果相等,返回 mid(找到了目标值)。
      • 如果目标值小于 mid 位置的元素,则在左半部分继续查找,将 high 更新为 mid - 1
      • 如果目标值大于 mid 位置的元素,则在右半部分继续查找,将 low 更新为 mid + 1
  3. 结束:如果 low 大于 high,说明目标值不在数组中,返回一个表示未找到的值(例如 -1)。

图解:

示例:int[] arr2 = new int[]{55,66,77,1,98,45,69,2,3,4,5,76,34,12};查找元素55是否出现过,出现求索引值


public class Person {
    public static void main(String[] args) {
        int[] arr2 = new int[]{55,66,77,1,98,45,69,2,3,4,5,76,34,12};
        int target = 77;
        int low = 0;//首索引
        int high = arr2.length - 1;//默认的尾索引
        int mid;
        boolean isFlag = false;
        while(low <= high) {
            mid = (high + low) / 2;

            if (target == arr2[mid]) {
                System.out.println("找到了 " + target + ",对应位置为:" + mid);
                isFlag = true;
                break;
            }else if(target > arr2[mid]){
                high = mid +1;
            }else {
                low = mid - 1;
            }
        }
        if (!isFlag){
            System.out.println("没找到");
        }

    }
}

结果:

4.4.3 优缺点

线性查找:

  • 优点:算法简单
  • 缺点:执行效率低,执行的时间复杂度0(N)

二分法查找:

  • 优点:执行效率高,执行时间复杂度0(logN)
  • 缺点:偏难,前提:数组必须有序

4.5 数组元素排序

算法概述:用于将一组数据按照特定顺序排列。

目的;为了快速查找;

衡量排序算法的优劣:

  • 时间复杂度:关键字的比较次数和记录的移动次数。
  • 空间复杂度:分析排序算法中需要多少辅助内存。
  • 稳定性:若A与B的关键字值相等,但排序后A、B的先后次序保持不变。

4.5.1 冒泡排序

说明:它通过重复遍历待排序的数列,比较相邻的元素,并在顺序错误的情况下交换它们。这个过程持续进行,直到没有需要交换的元素为止,即数列已经有序。因为每次比较后,较大的元素会“冒泡”到数列的末尾。

冒泡排序的工作原理

  1. 比较相邻元素:从数组的第一个元素开始,比较当前元素与下一个元素的大小。
  2. 交换:如果当前元素大于下一个元素,则交换它们。
  3. 重复过程:对每一对相邻元素重复以上过程,直到数组的末尾。
  4. 缩小范围:每一趟结束后,最后一个元素是当前的最大值,下次比较时可以忽略它。
  5. 终止条件:如果在某一趟中没有进行任何交换,表示数组已经有序,可以提前终止算法。

示例:

public class BubbleSort {
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        boolean swapped;

        // 外层循环控制趟数
        for (int i = 0; i < n - 1; i++) {
            swapped = false; // 每趟开始前重置交换标志

            // 内层循环进行相邻元素比较
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换 arr[j] 和 arr[j + 1]
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swapped = true; // 标记发生了交换
                }
            }

            // 如果没有发生任何交换,提前结束
            if (!swapped) {
                break;
            }
        }
    }

    // 测试冒泡排序
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        System.out.println("原始数组:");
        printArray(arr);

        bubbleSort(arr);

        System.out.println("排序后的数组:");
        printArray(arr);
    }

    // 辅助方法,打印数组
    public static void printArray(int[] arr) {
        for (int value : arr) {
            System.out.print(value + " ");
        }
        System.out.println();
    }
}

结果:

4.5.2 快速排序

说明:通过选择一个“基准”元素(pivot),将数组分成两个子数组,其中一个子数组的所有元素都小于基准元素,另一个子数组的所有元素都大于基准元素。然后,递归地对这两个子数组进行排序,最终实现整个数组的排序。

快速排序的基本步骤

  1. 选择基准元素:从数组中选择一个元素作为基准。
  2. 分区操作:重排数组,将比基准元素小的元素放在其左边,比基准元素大的元素放在其右边。此时,基准元素的最终位置已确定。
  3. 递归排序:对基准元素左边和右边的子数组分别进行快速排序。

示例:

public class QuickSort {
    
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 获取分区索引
            int pi = partition(arr, low, high);
            
            // 递归排序分区
            quickSort(arr, low, pi - 1);  // 左子数组
            quickSort(arr, pi + 1, high); // 右子数组
        }
    }

    private static int partition(int[] arr, int low, int high) {
        // 选择最后一个元素作为基准
        int pivot = arr[high];
        int i = (low - 1); // 小于基准的元素索引
        
        for (int j = low; j < high; j++) {
            // 如果当前元素小于或等于基准
            if (arr[j] <= pivot) {
                i++; // 增加小于基准的元素索引
                swap(arr, i, j); // 交换元素
            }
        }
        
        // 将基准元素放到正确的位置
        swap(arr, i + 1, high);
        return i + 1; // 返回基准元素的索引
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        int n = arr.length;
        
        quickSort(arr, 0, n - 1);
        
        System.out.println("排序后的数组: ");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}

结果:

4.5.3 小结

排序的分类:内部排序(内存中排序),外部排序(外部存储设备+内存)

关注:

  • 冒泡排序:最简单,要求会敲。
  • 快速排序:最快的,开发中的默认排序方式,要掌握其实现思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值