java-数组


前言

本文主要介绍java语言中的数组类型

1 数组

1.1 数组的概述

数组(Array):多个相同类型数据按一定顺序排列 的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。
说明:

  • 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
  • 创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。
  • 数组的长度一旦确定,就不能修改
  • 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
  • 数组的分类:
    ① 按照维度:一维数组、二维数组、三维数组、…
    ② 按照元素的数据类型分:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)

1.2 一维数组的使用

1.2.1 声明

一维数组的声明方式:

type var[] 或 type[] var(推荐);

注:Java语言中声明数组时不能指定其长度(数组中元素的数)例如: int a[5]; //非法

1.2.2 数组的初始化

动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行

int[] arr = new int[3];
arr[0] = 3;
arr[1] = 9;
arr[2] = 8;
//---------------------
String names[];
names = new String[3]; names[0] = “钱学森”;
names[1] = “邓稼先”;
names[2] = “袁隆平”;

静态初始化:在定义数组的同时就为数组元素分配空间并赋值。

int arr[] = new int[]{ 3, 9, 8};或int[] arr = {3,9,8};
String names[] = {“李四光”,“茅以升”,“华罗庚”}

说明:

  • 可以使用var来定义变量—只要在定义该变量时为其指定初始值即可,这样编译器就可推断出该变量的类型。

// 编译器推断names变量的类型是String[]
var names = new String[] {“Hello”, “World”};

  • 使用静态初始化简化语法执行初始化的数组不能使用var定义数组变量。

//‘var’ is not allowed as an element type of an array
//var[] a = {5, 6, 7, 9};

  • 不要同时使用静态初始化和动态初始化,也就是说,不要在进行数组初始化时,既指定数组的长度,也为每个数组元素分配初始值。

int arr[] = new int[3]{ 3, 9, 8};//非法

  • 执行动态初始化时,程序员只需指定数组的长度,即为每个数组元素指定所需的内存空间,系统将负责为这些数组元素分配初始值。
  • 对于基本数据类型而言,默认初始化值各有不同
  • 对于引用数据类型而言,默认初始化值为null(注意与0不同!)
    在这里插入图片描述

1.2.3 数组的引用

数组的引用:

  • 定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;
  • 数组元素的引用方式:数组名[数组元素下标]
    ①数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];
    ②数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 —>n-1;如int a[]=new int[3]; 可引用的数组元素为a[0]、a[1]、a[2]
    ③如果访问数组元素时指定的索引值小于0,或者大于等于数组的长度,编译程序不会出现任何错误,但运行时出现异常:java.lang.ArrayIndexOutOfBoundsException:N(数组索引越界异常)
  • 每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长
    度(元素个数)
    ①数组一旦初始化,其长度是不可变的

数组的遍历:

  • 普通for循环遍历:
@Test
public void arrayTest() {
	//数组的定义和初始化同时完成,使用动态初始化语法
	int[] prices = new int[5];
	// 使用普通for循环输出prices数组的每个数组元素的值
	for (var i = 0; i < prices.length; i++)
	{
		System.out.println(prices[i]);
	}
}
  • foreach循环(java5以及以后)
    foreach循环:循环遍历数组和集合,更加简洁。无须获得数组和集合长度,无须根据索引来访问数组元素和集合元素,foreach循环自动遍历数组和集合的每个元素。

foreach 循环的语法格式如下∶
for(type variableName : array l collection){
// variableName 自动迭代访问每个元素…
}

@Test
public void arrayTest1() {
	//数组的定义和初始化同时完成,使用动态初始化语法
	int[] prices = new int[5];
	// 使用 foreach循环输出prices数组的每个数组元素的值
	for (int i : prices) {
		System.out.println(prices[i]);
	}
}

1.2.4 数组的内存解析

内存的简化结构:
在这里插入图片描述
在这里插入图片描述

1.3 多维数组的使用

本节主要介绍二维数组

说明:

  • Java 语言里提供了支持多维数组的语法。
  • 如果说可以把一维数组当成几何中的线性图形, 那么二维数组就相当于是一个表格。
  • 对于二维数组的理解,我们可以看成是一维数组 array1又作为另一个一维数组array2的元素而存 在。其实,从数组底层的运行机制来看,其实没 有多维数组。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

1.4 数组经典练习题以及常用算法

1.4.1 练习题

1.4.1.1 使用二维数组打印杨辉三角
@Test
public void arrayTest2() {
	/*
	 * 提示:
	 * 1.第一行有1个元素,第n行有n个元素
	 * 2.每一行的第一个元素和最后一个元素都是1
	 * 3.从第3行开始,对于非第一个元素和最后一个元素。即:
	 * arr[i][j]=arr[i-1][j-1]+arr[i-1][j];
	 */
	//1.声明并初始化二维数组
	Scanner sc =new Scanner(System.in);
	System.out.println("请输入要打印的行数:");
	int nextInt = sc.nextInt();
	int[][] arr=new int[nextInt][];
	//2.给数组元素赋值
	for (int i = 0; i < arr.length; i++) {
		//初始化列数
		arr[i]=new int[i+1];
		//给首末元素赋值
		arr[i][0]=arr[i][i]=1;
		//给每行的非首末元素赋值(第3行开始)
		if(i>1) {
			for (int j = 1; j < arr[i].length-1; j++) {
				arr[i][j]=arr[i-1][j-1]+arr[i-1][j];
			}
		}
		
	}
	//3.遍历二维数组
	for (int i = 0; i < arr.length; i++) {
		for (int j = 0; j < arr[i].length; j++) {
			System.out.print(arr[i][j]+"  ");
		}
		System.out.println();
	}
}
1.4.1.2 随机赋值
@Test
public void arrayTest2() {
	/*
	 * 创建一个长度为6的int型数组,要求取值为1-30,同时元素值各不相同
	 */
	//初始化数组
	int[] arr = new int[6];
	for (int i = 0; i < arr.length; i++) {
		//数组随机赋值
		// [0,1) [0,30) [1,31)
		arr[i] = (int) (Math.random() * 30) + 1;
			//与前面元素作比较,相同重新赋值
			for (int j = 0; j < i; j++) {
				if (arr[i] == arr[j]) {
					i--;
					break;
				}
			}
		}
	//循环遍历数组
	for (int i = 0; i < arr.length; i++) {
		System.out.println(arr[i]);
	}
}
1.4.1.3 回形数格式方阵的实现
@Test
public void arrayTest2() {
	/*
	 * 从键盘输入一个整数(1~20) 
	则以该数字为矩阵的大小,把1,2,3…n*n 的数字按照顺时针螺旋的形式填入其中。例如: 输入数字2,则程序输出: 
	1 2 
	4 3 
	输入数字3,则程序输出: 
	1 2 3 
	8 9 4 
	7 6 5 
	输入数字4, 则程序输出: 
	1   2   3   4 
	12  13  14  5 
	11  16  15  6 
	10   9  8   7
	 */
	Scanner sc=new Scanner(System.in);
	System.out.println("请输入一个数字:");
	int n = sc.nextInt();
	int[][] arr=new int[n][n];
	int num=0;
	//列
	int c1=0;int c2=n-1;
	//行
	int r1=0;int r2=n-1;
	while(c1<=c2) {
		//第r1行
		for (int i = c1; i <=c2; i++) {
			arr[r1][i]=++num;
		}
		//第c2列
		for (int i = r1+1; i <=r2; i++) {
			arr[i][c2]=++num;
		}
		//第r2行
		for (int i = c2 - 1; i >= c1 ; i--) {
			arr[r2][i] = ++num;
		}
		//第c1列
		for (int i = r2 - 1; i > r1; i--) {
			arr[i][c1] = ++num;
		}
        c1++;
        c2--;
        r1++;
        r2--;
       
	}
	 for (int i = 0; i < arr.length; i++) {
         for (int j = 0; j < arr[0].length; j++) {
             System.out.print(arr[i][j] + "\t");
         }
         System.out.println();
     }
}

1.4.2 常用算法

数组中涉及到的常见算法:排序算法
排序:假设含有n个记录的序列为{R1,R2,…,Rn},其相应的关键字序列为{K1,K2,…,Kn}。将这些记录重新排序为{Ri1,Ri2,…,Rin},使得相应的关键字值满足条Ki1<=Ki2<=…<=Kin,这样的一种操作称为排序。

  • 通常来说,排序的目的是快速查找

衡量排序算法的优劣:

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

排序算法分类:内部排序和外部排序。

  • 内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
  • 外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。

算法的5大特征:
在这里插入图片描述
说明:
满足确定性的算法也称为:确定性算法。现在人们也关注更广泛的概念,例如:考虑各种非确定性的算法,如并行算法、概率算法等。另外,人们也关注并不要求终止的计算描述,这种描述有时被称为过程(procedure)。

算法的性能对比:
在这里插入图片描述
各种内部排序方法性能比较:

  1. 从平均时间而言:快速排序最佳。但在最坏情况下时间性能不如堆排序和归并排序。
  2. 从算法简单性看:由于直接选择排序、直接插入排序和冒泡排序的算法比较简单,将其认为是简单算法。对于Shell排序、堆排序、快速排序和归并排序算法,其算法比较复杂,认为是复杂排序。
  3. 从稳定性看:直接插入排序、冒泡排序和归并排序时稳定的;而直接选择排序、快速排序、 Shell排序和堆排序是不稳定排序
  4. 从待排序的记录数n的大小看,n较小时,宜采用简单排序;而n较大时宜采用改进排序

排序算法的选择 :

  1. 若n较小(如n≤50),可采用直接插入或直接选择排序。当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插入,应选直接选择排序为宜。
  2. 若文件初始状态基本有序(指正序),则应选用直接插入、冒泡或随机的快速排序为宜;
  3. 若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。

十大内部排序算法:
选择排序:

  • 直接选择排序(手写)、堆排序

交换排序 :

  • 冒泡排序、快速排序 (手写)

插入排序 :

  • 直接插入排序、折半插入排序、Shell排序

归并排序
桶式排序

1.4.2.1 直接选择排序
public static void selectSort(int[] data) {
	System.out.println("开始排序");
	int arrayLength = data.length;
	for (int i = 0; i < arrayLength - 1; i++) {
		for (int j = i + 1; j < arrayLength; j++) {
			if (data[i] - data[j] > 0) {
				int temp = data[i];
				data[i] = data[j];
				data[j] = temp;
			}
		}
		System.out.println(java.util.Arrays.toString(data));
	}
}
1.4.2.2 冒泡排序(手写)

介绍:
冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
排序思想:

  1. 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较为止。
public static void selectSort(int[] arr) {
		System.out.println("开始排序");
		for (int i = 0; i < arr.length-1; i++) {
			for (int j = 0; j < arr.length-1-i; j++) {
				if (arr[j]>arr[j+1]) {
					int temp=arr[j+1];
					arr[j]=arr[j+1];
					arr[j+1]=temp;
				}
			}
		}
		for(int i = 0;i < arr.length;i++){
			System.out.print(arr[i] + "\t");
		}
	}
1.4.2.3 快速排序(手写)

介绍:
快速排序通常明显比同为O(nlogn)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多笔试面试中能经常看到快排的影子。可见掌握快排的重要性。快速排序(Quick Sort)由图灵奖获得者Tony Hoare发明,被列为20世纪十大算法之一,是迄今为止所有内排序算法中速度最快的一种。冒泡排序的升级版,交换排序的一种。快速排序的时间复杂度为O(nlog(n))。

排序思想:

  1. 从数列中挑出一个元素,称为"基准"(pivot),
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
  4. 递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

一次划分的具体过程:
1.low指向待划分区域首元素,high指向待划分区域尾元素;
2.R【0】=R【low】(为了减少数据的移动,将作为标准的元素暂有到R【0】中,最后再放入最终位置);
3.high从后往前移动直到R【high】.key<R【0】.key;
4. R[low]]=R[high], low++;
5.low从前往后移动直到R【low】.key>=R【0】.key;
6. R[high]=R[low], high–;
7. goto 3;
8.直到low==high时,R【low】=R【0】(即将作为标准的元素放到
其最终位置)。
概括地说,一次划分就是从表的两端交替地向中间进行扫描,将小的放到左边,大的放到右边,作为标准的元素放到中间。

public static void quikSort(int[] data, int start, int end) {
		if (start < end) {
			// R【0】=R【low】
			int base = data[start];
			int low = start;
			int high = end;
			while (low < high) {
				// high从后往前移动直到R【high】.key<R【0】.key;
				while (low < high && data[high] >= base) {
					high--;
				}
				if (low < high && data[high] < base) {
					data[low] = data[high];
					low++;
				}
				// low从前往后移动直到R【low】.key>=R【0】.key;
				while (low < high && data[low] < base) {
					low++;
				}
				if (low < high && data[low] >= base) {
					data[high] = data[low];
					high--;
				}
			}
			// 直到low==high时,R【low】=R【0】(即将作为标准的元素放到其最终位置)。
			if (low == high) {
				data[low] = base;
			}
			quikSort(data, start, high - 1);
			quikSort(data, high + 1, end);
		}
	}

1.5 和数组相关的异常

①ArrayIndexOutOfBoundsException数组索引越界异常。访问到了不存在的位置(编译不会报错)。
②NegativeArraySizeException分配了负长度异常(编译不会报错)。
③NullPointerException空指针异常,当null调用了任何的方法或者是任何的属性,就会触发该异常(编译不会报错)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值