06-数组

158 阅读14分钟

引言

现在目前我们的一个变量,最多就保存一个数据,显然不科学。目前需要一个方案来解决这个问题,可以保存多个数据的容器。在几乎所有的编程语言中都提供了数组这么一个概念**,数组它是一种基础的数据结构**(列表数据结构)。

一. 数组概念

1.1 基本概念

数组它是存放 一组类型相同,个数有限的数据的容器。数组也是变量,由于它的数据结构的特点可以保存多个数据,数组在内存中,往往是块连续有限的空间。

1.2 图形理解

image.png

二 . 数组的声明与赋值

数组它是一种引用数据类型,它的存储需要使用到两块内存空间(堆和栈),基本数据类型只需要栈空间。

2.1 数组声明语法

// 声明一个数组(声明与初始化)
数据类型[]  数组名 = new 数据类型[ 长度 ];

int scores[] = new int[5] ; //  c 系语言的风格。 
int[] scores = new int[5] ; //  java 的风格。

// 声明一个数组(先声明后初始化)
int[] arr ;
arr = new int[5]

// 声明一个数组(静态初始化)初始化的时候就为每个元素赋值。
int[] brr = new int[]{1,2,3,4,5}
int[] brr = { 1,2,3,4,5};

这里的数据类型 可以是全体Java数据类型(自定义类) Dog User int double 所有引用变量的默认值都是null 区别:null 和 "" 有什么区别呢

1 null 代表是我当前这个 变量是没有被初始化,就是说 在内存里面还没有分配空间
2 "" 这个是空字符串,它是分配了存储空间,只是当前这个空间的值为空字符串
如:
String str = null; _//可能随时会被 gc给回收
_String str2 = ""; //它是不会被回收;

 //1 第一种定义方式
        int [] arr = new int[2]; //数组 定义 数据类型 [] 变量名 = new 数据类型[长度]; 先定义 后使用
                                 // 每个数组 都有一个下标,标识我元素的所在位置;
                                //我们可以通过下标 对数组进行操作
        // 如:取值:变量名[index]--取值  add:变量名[index]=值
        arr[0]= 10;
        arr[1]= 20;
        //2 第二种定义方式
        int [] arr2 = new int[]{1,2,3,4,5};
        
        //3 第三种定义方式
        int [] arr3 = {1,2, 3,4};

        /**
         * 第一个 直接创建内存空间;第二种需要计算数据长度,在去创建空间;三种先创建对象,计算长度,创建内存空间;
         */

2.2 数组内存分配

image.png

数组初始化后,会为元素赋予初始值,比如整型数组默认为0 浮点数数组默认为0.0 引用数据类型数组为 null。

三 . 数组的组成

3.1 数组元素寻址

数组由各个元素构成,各个元素依次排列,每个元素都有一个寻址下标,选址下标自动生成,从0开始。首地址结合下标通过偏移就可以寻址到每个元素,这里有一个问题就是,如果偏移的过程中,超过了下标,会造成数组下标越界问题。最大下标为数组长度-1。
元素的访问: 对元素的赋值和取值,数组名[ 下标 ]

3.2 获得数组长度

数组的长度:** 数组名.length **;

3.3 数组下标越界

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 这个异常就是数组下标超出了范围。

    public static void main(String[] args) {
        int[] scores = new int[5];
        //存数据:赋值
        scores[0] = 1;
        scores[1] = 2;
        scores[2] = 3;
        scores[3] = 4;
        scores[4] = 5;
        //取数据:
        System.out.println( scores[0] );
        System.out.println( scores[1] );
        System.out.println( scores[2] );
        System.out.println( scores[3] );
        System.out.println( scores[4] );
    }

四 . 数组的遍历

数组遍历,指的是将元素依次访问一遍,通常这个过程需要借助循环实现,这里介绍两种常见遍历数组的方式

4.1 通过普通for循环

借助循环的连续的循环变量作为下标。

 public static void main(String[] args) {
        int[] scores = new int[5];
        //存数据:赋值
        scores[0] = 1;
        scores[1] = 2;
        scores[2] = 3;
        scores[3] = 4;
        scores[4] = 5;
        //取数据:
        System.out.println( scores[0] );
        System.out.println( scores[1] );
        System.out.println( scores[2] );
        System.out.println( scores[3] );
        System.out.println( scores[4] );

        //数组遍历(把每个元素都访问一遍 != 输出 )
        System.out.println("---遍历方式1--"); //天意:循环计数器可以是连续的,恰好用在这里当下标
        for( int i =0 ; i< scores.length  ; i++  ){
            System.out.println( scores[i] );
        }
       Arrays.sort()
       Scnnar sc = new Scanner()
       sc.nextInt();    
 }

4.2 通过增强for循环foreach

此方法是一种语法糖,也就是编写上的形式方便,底层依然采用普通for实现

 public static void main(String[] args) {
        int[] scores = new int[5];
        //存数据:赋值
        scores[0] = 1;
        scores[1] = 2;
        scores[2] = 3;
        scores[3] = 4;
        scores[4] = 5;
        //取数据:
        System.out.println( scores[0] );
        System.out.println( scores[1] );
        System.out.println( scores[2] );
        System.out.println( scores[3] );
        System.out.println( scores[4] );
        //数组遍历 (增强for循环 语法糖= 编译的时候会过程叫 解语法糖,会采用基础原生的方式实现 )
        System.out.println("---遍历方式2--");
        for( int element  : scores ){
            System.out.println(element);
        }
 }

五. 数组应用

5.1 基本使用

    public static void main(String[] args) {
        //定义数组
        String[] names = new String[5];
        names[0] = "马云";
        names[1] = "成龙";
        names[2] = "波多野结衣";
        names[3] = "王健林";
        names[4] = "黄家驹";
         //{ "马云","成龙","波多野结衣","王健林" };
        //修改元素值
        names[2] = "武老师";
        //遍历
        for( int i =0; i< names.length ; i++ ){
            System.out.println(names[i]);
        }
    }

5.2 数组作为参数

/**
 * 数组作为方法的参数类型
 */
public class ArrayUsedArgs {
    public static void main(String[] args) {
        int[] arr = {1,23,4,5,6,67,7,8,8};
        System.out.println("arr的地址"+arr);
        printArray( arr ); // 数组传参传的是什么? 传递的是地址

    }
    //定义一个函数,这个函数的作用就是来输出一个数组的全部元素
    public static void printArray( int[]  crr  ){ // int[] crr = arr;
        System.out.println("crr的地址"+crr);
        for( int i=0; i< crr.length; i++ ){
            System.out.print(crr[i]+"\t");
        }
        System.out.println();
    }
}

5.3 数组作为返回值

/**
  数组作为返回值类型
 */
public class ArrayUesdReturn {
    //编写一个程序,生成一个数字验证码 (4位)
    public static int[] createCode(){

        int[] codeArray = new int[4];
        Random rd = new Random();

        for(int i=0;i<4;i++){
            int n   = rd.nextInt(10);
            codeArray[i]=n;
        }
        System.out.println("codeArray的地址"+codeArray);
        return codeArray;
    }

    public static void main(String[] args) {

        int[]  result =   createCode(); // ctrl

        System.out.println("result的地址"+result);

        ArrayUsedArgs.printArray(result);
    }

}

5.4 值传递与地址传递

  • 对于基本数据类型,传参为值的拷贝,当然返回的也是值的拷贝。
  • 对于引用数据类型, 传参实际是地址传递,当然返回的也是地址。
public class ValueAndRefrence {


    public static void main(String[] args) {
        int  age = 10;
        change(age);
        System.out.println(age); // 10

        int[] brr = {10,20};
        change2(brr);
        System.out.println(brr[0]+" "+brr[1]);// 100 20
    }

    public static void change(int num ){
         num = 100;
    }

    public static void change2(int[] arr){
        arr[0] = 100;
    }
}

image.png

5.5 数组的扩容

  1. **什么是扩容? **

扩容指的是扩展存储空间容量,

  1. 为什么要扩容?

数组是的容量是声明是就确定了的(定死了)后续不可以被修改,但是我们实际情况又需要更多的容量。这里的"扩容"是一种通过其他手段实现的。因为数组本身长度声明后是不可改变的。

  1. 扩容的关键技术,元素拷贝
/**
 *  数组的扩容问题
 */
public class ArraysExtend {

    public static void main(String[] args) {

    } 

    /**
     *  扩容方式3
     */
    private static void copy03() {
        //原数组
        int[] data = {1,2,3, 4,5};

        //Arrays.copyOf(T[] ,int length  )
        /*
           1. T[] 表示原数组,这里使用了泛型,任意类型数组
           2. length 新数组的长度, 底层创建一个新数组,并且返回这个新数组的地址。
         */
        data =  Arrays.copyOf( data, data.length*2);// 把新数组的地址赋值给原来数组

        ArrayUsedArgs.printArray(data);
    }


    /**
     * 扩容方式2 : System.arraycopy()
     */
    private static void copy02() {
        //原数组
        int[] data  = {1,2,3,4,5};

        int[] more = new int[10];


        //public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos, int length);
        //1. src 指定的原数组
        //2. srcPos 指的是从原数组哪个位置开始拷贝,拷贝起始下标
        //3. dest 指的是目标数组 ,新数组
        //4  destPos 指的的是 从新数组的哪个位置开始存放
        //5. length 指的是考被拷贝的元素个数
        System.arraycopy( data, 0,more, 0, data.length );
        // 把新数组的地址赋值给原来数组
        data= more;
        ArrayUsedArgs.printArray(data);
    }

    /**
     * 扩容方式 1: 手写循环
     */
    private static void copyOne() {
        //原数组
        int[] data = { 1,2,3,4,5};

        //1. 创建一个更大的数组
        int[] more = new int[10];

        //2. 通过循环赋值,对应下标对应赋值,以原数组为基础
        for(int i=0;i<data.length;i++){
            //为新数组赋值
            more[i] =  data[i];
        }
        // 3. 把新数组的地址 赋值给原来的数组名
        data = more;
        // 4. 可以继续存放更多的数据
        data[5]=6;
        for (int element:data)
        {
            System.out.print(element+" ");
        }
    }
}

5.6 数组元素删除

package com.qfedu.array.use;

import java.util.Arrays;

/** 元素增减 */
public class TestArrayElementInsertDelete {

    public static void main(String[] args) {

        int[] arr = {1,2,3,4,5};

        //arr = delete(4,arr);
        arr = insert(3, 100, arr);
        
        //修改
        update(arr,4,10);

        System.out.println(Arrays.toString(arr));

    }


    public static int[] delete( int i , int[] arr ){ //2
        int[] newArray = new int[ arr.length-1 ];
        System.arraycopy( arr,0, newArray,0,i);//拷贝前段
        System.arraycopy(arr,i+1, newArray,i, arr.length-i-1);//拷贝后段
        return newArray;
    }

    public static int[] insert( int i, int value, int[] arr ){
        int[] newArray = new int[arr.length+1]; //2
        System.arraycopy( arr,0,newArray,0,i);
        newArray [i] = value;
        System.arraycopy( arr, i, newArray,i+1, newArray.length-i-1);
        return  newArray;
    }
    
    /**
     * 修改的数组
     * @param oldArr
     * @param index
     * @param data
     */
    public static void update(int [] oldArr,int index,int data){
        oldArr[index] = data;
    }  
}

5.7 可变参数

可变参数,指得是在函数/方法定义时,可以通过可变参数实现,动态参数个数,他的本质依然是数组作为参数,函数内部通过数组的方式处理参数。如果一个函数设计可变参数,那么这参数必须是最后一个参数。语法定义 (类型 ... 参数名)

//可变参数
public class ArrayVarArgsDemo {


    public static void main(String[] args) {
        System.out.println("-------------------单个参数传参 ---------------------------");
        readBook( "三国演义" );

        System.out.println("-------------------- 数组参数传参 ---------------------------");
        String[] mybooks = { "小明与小红的爱情故事","小时代","大秦帝国之崛起"  }; 
        readBooks(  mybooks ); //提前准备一个数组
        
        readBooks(  new String[]{"奥特曼","铁甲小宝","海尔兄弟" } ); // 现写现用一个数组
        
      
        System.out.println("-------------------- 可变参数传参 ---------------------------");
        readBookPlus("小明与小红的爱情故事","小时代","大秦帝国之崛起");
    }

    //编写一个函数,读书函数 单个参数
    public static void readBook( String book ){
        System.out.println("我读"+book);
    }

    //编写一个函数,读书函数 传数组
    public static void readBooks( String[] books ){
        for(String book : books){
            System.out.println("我读"+book);
        }
    }

    //编写一个函数,读书函数 传可变参数(本质也就是一个数组)
    public static void readBookPlus(  String ... books  ){ //可变参数必须是整个函数参数的最后一个参数
        for(String book : books){
            System.out.println("我读"+book);
        }
    }
}

六 . 数组排序与查找

数组排序指的是采用某种算法来将元素按一定次序排列整齐,数组的查找,根据指的元素判断其在数组中的位置。

冒泡 选择 插入 快速 堆 希尔 归并 统计 基数 计数

排序的区别: 1. 思路上的区别 2. 时间复杂度(考虑) 3. 空间复杂度(忽略)。

常见的时间复杂度曲线:
T=f(n)=O(n)

O(1) :
O(n) :
O( n^2 ):
O( logN):
O(nlogN):
O(2^n):
OIP-C.png

6.1 冒泡排序


 取相邻的两个元素比较如果前者大于后则交换,一趟的结果为找出了这趟的最大值,且交换到最最后 ,一致重复趟数直到完成。

/**
 * 冒泡排序
 */
public class BubbleSort {

    public static void main(String[] args) {
        int[] data = {9,8,7,6,5,4,3,2,1};
        /**
         *   趟:  取相邻的两个元素比较如果前者大于后则交换,一趟的结果为找出了这趟的最大值,且交换到最最后 ,一致重复趟数直到完成
         */
        for( int i=0;i<data.length;i++ ){ // 控制趟数
            //每趟需要做的事情就是 取 相邻两个元素比较
            for(int j=1; j<data.length-i;j++){
                if( data[j-1] > data[j]  ){
                    int temp = data[j-1];
                    data[j-1] = data[j];
                    data[j] = temp;
                }
            }
            System.out.println( "第"+i+"趟"+Arrays.toString(data) );
        }
        //  Arrays.toString(T []) 作用是接收一个数组,把数组元素通过 逗号 连接成一个字符串
        System.out.println( Arrays.toString(data) );
    }
}

6.2 选择排序

/**
 * 选择排序
 */
public class ChooseSort {
    public static void main(String[] args) {

        int[] data = {12,34,22,15,1,56,33};

        //依次假设出每个当前最小的数字
        for( int i=0 ;  i<data.length  ; i++ ){
            //假设当前最小值的下标为min
            int min = i;
            for( int j=i+1; j<data.length;j++  ){
                //如果 假设最小值并非最小
                if( data[min]> data[j] ){
                    min = j; //记录真正最小值的下标
                }
            }
            //验证 看我们假设最小值的下标是否和最初假设一致。
            if( min != i ){
               int temp = data[i];
               data[i] = data[min];
               data[min] = temp;
            }
        }
        System.out.println(Arrays.toString(data));
    }
}

6.3 插入排序

 public static void insert( int[] data){
        //根据元素个数确定趟数 为什么是1 开始,假设第一个是排好
        for( int i=1; i< data.length;i++ ){
            //从未排序区取一个元素data[i+1]  和 已经排序区 比较寻找和位置
            for( int j=i ; j>0;j-- ){
                // 1 ,2 3 4 8  3|
               //如果我们选择的这个数 小于当前元素则交换
                if( data[j] <  data[j-1] ){
                    int t = data[j];
                    data[j]= data[j-1];   
                    data[j-1]=t;
                }
            }
        }
}             

6.4 使用JDK排序

利用JDK提供的排序方法,而这个排序方法采用的是快速排序算法, Arrays.sort(data);

    public static void main(String[] args) {

        int[] data = { 12,33,44,6,58,24,23,10};

        Arrays.sort(data);

        System.out.println(Arrays.toString(data));


    }

6.5 快速排序原理

  1. 快速排序,特点就是块, 一般的情况 O(n*lgN ) 最坏情况是n^2

  2. 它是一种分治的思想。分区( 把数据 分为两个子区, 小于基准数的排列到 左边 大于基准数的排列在右边。 )

  3. 重复重复上述动作分区( 引入递归 )

  4. 确定一个基准数。(左边第一个数)

  5. 从右向左扫,如果大于基准数着继续前行,直到找到第一个小于基准数的值,则保存这个数到左边。

  6. 从左往右扫,如果小于基准数则继续前行,直到找到第一个大于基准数的值。则保存这个数到右边。

  7. 重复上诉过程,即可把数据分为两区。

    public static void main(String[] args) {

        int[] data = {38,22,44,23,14,32,28,66,25,88,77,44};

        quickSort(data, 0, data.length - 1);
        System.out.println(Arrays.toString(data));

    }
    public static void quickSort( int data[] , int left, int right ){

         if( left<right ){
             int index = division(data,left,right);
             quickSort(data,left,index-1);
             quickSort(data,index+1,right);      
         }
    }
    public static int division(int[] data , int left, int right){
        int pivot = data[left];
        while (left < right){
               while ( left < right &&  data[right] >= pivot    )
                   --right;
                   data[left] = data[right];

               while (left<right && data[left] <= pivot )
                   ++left;
                   data[right]=data[left];
        }
        data[left]=pivot;
        return left;
    }

从数组中找到目标数据,如果寻找,又涉及到相关的算法,典型查找方法,比如线性 查找,或者折半查找。

6.6 线性查找

就是从头到尾查找,如果元素非常多,效率就不高。(全文检索)

package find;

import java.util.Arrays;

//查找
public class FindDemo {
    public static void main(String[] args) {

        String[] team = { "诸葛亮","刘备","关羽","大乔","小乔","曹操" };
        int index = lineFind(team,"吕布");
        System.out.println(index);
        //原数组
        int[] data = {1,3,46,367,312,11,567,44,22,56};
        //排序
        Arrays.sort(data);
        System.out.println(Arrays.toString(data));

        int index2 = binarySerach(data, 56);
        //int index2 = Arrays.binarySearch(data, 56);
        System.out.println(index2);
    }
    // 线性查找:更通用,可适用于各种数据类型
    public static int lineFind(String[] data, String key){
        for(int i=0; i<data.length; i++){
            if( data[i].equals(key)      ){
                return  i;
            }
        }
        return -1;
    }
}

6.7 折半查找(二分法查询)[重要]

就是 通过已经排好序的数组,先判断中间值,如果相等则返回,否则不断缩小查找范围
注意:二分法查询 你是数组必须有序的

package find;
import java.util.Arrays;
//查找
public class FindDemo {
    public static void main(String[] args) {
        //原数组
        int[] data = {1,3,46,367,312,11,567,44,22,56};
        //排序
        Arrays.sort(data);
        System.out.println(Arrays.toString(data));
        int index2 = binarySerach(data, 56);
        //int index2 = Arrays.binarySearch(data, 56);
        System.out.println(index2);
    }
    // 二分查找:针对的是数字查找,且要求数字必须先排序。
    public static int binarySerach( int[] data  , int key ){
        int left = 0; //左起点
        int right = data.length-1;//右终点
        //只要 左起点下标未超过右终点 继续,否则说明 移动交叉了不存在查找的数据 停止查找。
        while( left<=right ){
            //计算中间值下标
            int min =  (left+right)/2;
            // 判断与中间值的大小
            if( key == data[min]){      //  查到了
                return min;
            }else if( key < data[min] ){ // 在左半轴查找
                right = min-1;
            }else{                       // 在右半轴查找
                left = min+1;
            }
        }
        //没有找到
        return -1;
    }
}

七 . 二维数组(了解)

7.1 概念

二维数组从空间上看是有行列组成的结构,但是实际在计算机中,二维数组本质上还是一个一维数组,只是这个一维数组的每个元素,都是一个数组。

7.2 内存理解

image.png

数组的每个元素保存的右是 另一个数组的地址。

7.3 创建和声明二维数组

// 语法
数据类型[][] 数组名 = new 数据类型[ 低维度 ][ 高维度 ]  
int[][] data = new int[5][3]; 

数据类型[][] 数组名 
int[][] data ;
data = new int[5][3];

//数据类型[][] 数组名 = { {元素,...,元素 } ,{元素,...,元素  ,{元素,...,元素  } ; 
int[][] data  = new int[][]{   {1,2,3},{4,5,6},{7,8,9} }
int[][] data ={ {1,2,3},{4,5,6},{7,8,9} };

image.png

7.4 二维数组的元素访问

    public static void main(String[] args) {
        int[][] data = new int[3][3];
        //元素的访问:赋值
        data[0][0] = 1;
        data[0][1] = 2;
        data[0][2] = 3;

        data[1][0] = 4;
        data[1][1] = 5;
        data[1][2] = 6;

        data[2][0] = 7;
        data[2][1] = 8;
        data[2][2] = 9;
    }

使用 双下标访问

7.5 二维数组遍历

    public static void main(String[] args) {
        int[][] data = new int[3][3];
        //元素的访问:赋值
        data[0][0] = 1;
        data[0][1] = 2;
        data[0][2] = 3;

        data[1][0] = 4;
        data[1][1] = 5;
        data[1][2] = 6;

        data[2][0] = 7;
        data[2][1] = 8;
        data[2][2] = 9;
        
        // 普通for
        for(int i=0;i<data.length;i++){

            for(int j=0;j< data[i].length;j++ ){

                System.out.print(data[i][j]+"\t");
            }
            System.out.println();
        }
        // 增强for
        for(  int[] arr   : data ){
            for( int  e  :arr ){
                System.out.print(e+"\t");
            }
            System.out.println();
        }
    }

7.6 二维数组运用

public class Pratice {
    public static void main(String[] args) {
        int[][] map = new int[10][10];
        Random random = new Random();
        for(int i=0;i<map.length;i++){
            for(int j=0;j<map[i].length;j++){
                int n=  1+random.nextInt(100);
                if( !contains( map, n ) ){
                    map[i][j]=n;
                }else{
                    j--;
                }
            }
        }
        for(int[] arr: map ){
            System.out.println(Arrays.toString(arr));
        }
    }
    //编写一个函数 检查二维数组中是否存在该元素
    public static boolean contains(int[][] map, int num    ){
        for(int i=0; i< map.length;i++ ){
            for(int j=0;j<map[i].length;j++){
                if( num == map[i][j] ){
                    return true;
                }
            }
        }
        return false;
    }
}