【数据结构基础】之数组介绍,生动形象,通俗易懂,算法入门必看

205 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

本文为数据结构基础数组相关知识,本文将对数组的定义性质及结构数组的各种玩法循环遍历数组查找数组最大值数组元素的位移等,二维数组的定义及用法等进行详尽介绍~

Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~ 算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~


一、数组的定义

1.基本概念和内存图

首先来了解一下数组的定义以及它在内存中的结构是怎么样的。

数组可以存放多个同一类型的数据。 数组也是一种数据类型, 是引用类型,数组在内存中是按顺序存放的,它在内存里的结构如下:

3.png

数组的定义方式如下:

// 定义:
int[] nums;
// 初始化:
nums = new int[3];
// 赋值:
nums[0] = 1;   
nums[1] = 4;
nums[2] = 3;

// 直接定义初始化并赋值
int[] nums = {1,2,3};

// 这样写也行
int nums[] = new int[5];
int nums[] = new int[]{1,2,4,5};

// 数组有一个属性,可以获得数组的长度
nums.length 

类型[] 名字 = new 类型[长度];

数组的定义过程实际上经历了三个步骤,如下图:

4.png

对于数组的三个问题值得我们注意:

  • 1、数组不初始化能赋值吗?不可以,数组要提前定义好大小才能使用(会分配固定大小的内存空间)。
  • 2、初始化之后,如果不赋值,会不会有默认值?只要给你开辟了空间就一定会有默认值。
  • 3、数组能不能越界赋值,比如长度为三,非要给第五个赋值?肯定是 不可以的,数组的大小是固定的,在后边的使用过程中是不可变的。

2.数组的性质

数组具有以下性质:

  • 数组一旦建立,长度不能改变。
  • 每个位置只能存一个值,多了会覆盖。
  • 数组创建后会有默认值:int 0, short 0, byte 0, long 0, float 0.0,double 0.0, char \u0000, boolean false, String null
  • 编号从0开始,下标必须在指定范围内使用, 否则报: 下标越界异常。
  • 他有个长度的属性,最后一个位置的编号是 长度-1,即 0 - length -1
  • 数组里边可以是基本类型,也可以是引用类型。

3.数据结构

数组是一种最基本的数据结构,是表的一种,是一种二维的线性表,下期还会为大家介绍链表,还有hash表等都是线性结构。

在百度百科中是这样解释线性表的:

  • 线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
  • 线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。

二、玩转数组

下面将通过一些代码示例为大家介绍对于数组的使用以及一些操作:包括循环遍历一个数组并打印数组中的每一个值,查找一个数组里存在的值,打擂台的形式找最大值,元素的位移等。

1.循环遍历打印数组的每一个值

对于遍历数组,for循环正好能从0循环到某一个值,而这刚好可以对应我们数据的下标,因此可以写一个优雅的循环来遍历数组的元素。其中我们要明确的是对于数组遍历的范围是 0 ~ length -1,length是数组的大小,那么数组的下标范围就是0 ~ length -1。

以下是使用for循环遍历数组的代码示例:

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

使用while循环也可以对数组进行遍历,代码示例如下:

int i = 0;
while (i < nums.length){
    System.out.println(nums[i]);
    i++;
}

2.查找一个数组里存在的值

有时候我们需要查找一个数组里存在的某一个值,实现思路很简单,就是遍历每一个值,然后比较,直到我们遇到这个值为止。

实现代码如下:

// 定义一个变量,存放找到的目标值
int target ;
for (int i = 0; i < salary.length; i++) {
    if(salary[i] == 5){
        res = salary[i];
    }
}
System.out.println(target);

你会发现这么写是错的,编译可能出问题:Error:(21, 28) java: 可能尚未初始化变量res; 那我们target初始化一个几呢?0吗? 万一人家最后的就是要找0,怎么办,其实巧用数组下标是个好办法。

这其实很简单,我们的目标其实是查找一下这个数字在第几个格子而已。这样的好处就是,格子不可能有负数。 我们不妨这样设计,如果找到了就获得他的下标,如果找不到就得到-1,代码可以优化如下:

int targetIndex = -1;
for (int i = 0; i < salary.length; i++) {
    if(salary[i] == 9){
        targetIndex = i;
    }
    break;
}

3.打擂台的形式找最大值

我们要想 找出一个数组中 存在的最大值可以通过打擂台的思想进行查找。

打擂台:今天要比武决胜负,找出一个武功最高的当武林盟主: 1、黄蓉在台上大喊,今天我们要比武决胜负,选出一位丐帮帮主,哪位英雄好汉愿意第一个上。 2、丁春秋第一个上来,大吼,谁敢上来和我比试比试? 3、郭芙蓉上台一个排山倒海干掉了丁春秋,于是丁春秋下台,台上站的是郭芙蓉。 4、大家你来我往,一个一个上,最后站在台上的是张无忌,顺利当选。

这种打擂台的思想其实就是定义一个变量来保存最大值,然后就遍历数组元素,一个一个来与这个变量中保存的值进行比较,如果遍历到的值更大,那么就拿它来替换原来的最大值,它就成为新的最大值,直到最后遍历完数组,这个变量中保存的值就是数组中元素的最大值。那么需要注意的是起始时,这个变量中应该先保存一个较小的值。 我们的代码如下:

// 定义一个数组,存放各路英雄豪杰
int[] salary = {4,5,0,6,7,8};
// 搞一个擂台,让第0个人上来
int maxIndex = 0 ;
// 然后从第一个开始打
for (int i = 1; i < salary.length; i++) {
    // 谁赢了,谁继续在台上站着
    if(salary[i] > salary[maxIndex]){
        maxIndex = i;
    }
}
System.out.println("经过了n轮比赛得到的最大值得下标是:"+maxIndex+"。值是:"+salary[maxIndex]);

4.元素的位移

在数组中我们可能会涉及到要对数组中的元素进行换位,即位移的操作。 假如我们计划将数组中的两个元素交换位置,刚开始不熟悉程序的运行原理的小伙伴可能会这样写:

int[] salary = {4,5,0,6,7,8};

salary[0] = salary[1];
salary[1] = salary[0];

salary[0] = salary[1]; 这句话执行完了之后数组中 就出现了两个salary[1]了。 所以正确的方法是需要另外找一个空间,临时存放salary[0]。

int[] salary = {4,5,0,6,7,8};

int temp = salary[0];
salary[0] = salary[1];
salary[1] = temp;

5.数组的扩容

前边介绍数组的性质的时候已经知道数组本质上不能改变大小,实质上的扩容肯定是不行的。但是在实际使用过程中肯定会涉及到需要我们 改变数组大小的需求,这个时候就需要使用一些手段。先画一个图描述一下吧:

5.png

由上图可以看出其实就是重新定义一个新的数组,再是原来的数组的指针指向新的数组的地址空间,因为我们知道数组实质上也是一种引用类型。

// 定义原始数组
int[] nums = new int[]{3,4,6};
// 定义一个新的临时数组
int[] temp = new int[6];
// 讲原始数组的数据全部拷贝到临时数组
for (int i = 0; i < nums.length; i++) {
	temp[i] = nums[i];
}
// 让原始数组的引用指向临时数组,感觉上就像原始数组被扩容了
nums = temp;
for (int i = 0; i < nums.length; i++) {
System.out.println(temp[i]);
}

【问题】使用temp修改数组的值,会不会影响nums?答案是会,因为我们知道数组是一种引用类型。

6.数组的反转

下面来看看数组的反转操作是如何实现的,数组反转就是使得数组中的元素头尾倒置。对于数组的反转操作可以有两种方式来实现。

  • 思路一:

创建一个等长的数组,反向放入,最后改变引用即可

代码实现如下:

int[] nums = new int[]{3,4,6};
int[] temp = new int[nums.length];
for (int i = nums.length - 1; i >= 0; i--) {
    temp[i] = nums[nums.length - 1 - i];
}
nums = temp;
for (int i = 0; i < nums.length; i++) {
    System.out.println(temp[i]);
}
  • 思路二

利用交换的方式

代码实现如下: 13.png

// 定义原始数组
int[] nums = new int[]{3,4,6};
// 交换反转
for (int i = 0; i < nums.length/2; i++) {
    int temp = nums[nums.length - 1 - i];//保存
    nums[nums.length - 1 - i] = nums[i];
    nums[i] = temp;
}
for (int i = 0; i < nums.length; i++) {
    System.out.println(nums[i]);
}

三、二维数组

二维数组使用的频率比较少,但是也有一些类似的需求,比如我们要开发一个围棋游戏, 棋盘就可以用二维数组来表示。 如图:

14.png 那么怎么定义一个二维数组呢?

// 比如: 
int nums[][]=new int[2][3]

二维数组在内存中的存储结构是这样的: 6.png

二维数组里的每一个数组的长度一定要一样吗,如下图可以吗?

7.png

答案是可以的:

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

我们还可以这样对二维数组进行赋值:

// 这个初始化做了什么工作,这样就可以开辟空间初识化了,只不过每一个都是null
int[][] nums = new int[3][];
// 赋值
nums[0] = new int[5];
nums[1] = new int[3];
nums[2] = new int[7];

由以上操作我们可以明白一个道理,初识化只要知道我需要多少空间,然后在堆内存给它分配就好了。

关于二维数组的一个题目 :

写一个杨辉三角,他是这么一个三角: 每个数等于它上方两数之和。 每行数字左右对称,由1开始逐渐变大。 第n行的数字有n项。 前n行共[(1+n)n]/2 个数。

2.png 输出目标:

使用二维数组打印一个 10 行杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

代码实现如下:

public class YangHui {
    public static void main(String[] args) {

        int max = 10;

        // 分配三角形的数组
        int[][] nums = new int[max + 1][];
        for (int n = 0; n <= max; n++)
            nums[n] = new int[n + 1];

        // 填充杨辉三角
        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < nums[i].length; j++) {
                // 边边上全是1,每一行的0和length-1都是1
                if (j == 0 || j == nums[i].length - 1) {
                    nums[i][j] = 1;
                } else {
                    // 剩余的部分按照规律来
                    nums[i][j] = nums[i-1][j-1] +nums[i-1][j];
                }
            }
        }

        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < nums[i].length; j++) {
                System.out.print(nums[i][j]+" ");
            }
            System.out.println();
        }
    }
}

后记

以上呢对数组的定义性质及结构数组的各种玩法循环遍历数组查找数组最大值数组元素的位移等,二维数组的定义及用法等进行了详细的介绍,如果小伙伴想深入学习JAVA开发技术与知识,或者想进一步提高算法能力可参考:

Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~ 算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~