JavaSE - 数组的相关算法
本节学习目标:
- 了解并掌握Java中随机数的生成方式;
- 了解常用的数组赋值算法;
- 了解并掌握浅拷贝与深拷贝的区别与理解
- 了解并掌握常用的数组查找算法;
- 了解并掌握常用的数组排序算法。
1. 算法简述
算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
算法 - 百度百科
算法的特征:
- 有穷性(Finiteness):算法的有穷性是指算法必须能在执行有限个步骤之后终止;
- 确切性(Definiteness):算法的每一个步骤必须有确切的定义;
- 输入项(Input):一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
- 输出项(Output):一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
- 可行性(Effectiveness):算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性)。
算法的指标:
-
时间复杂度:
算法的时间复杂度是指执行算法所需要的计算工作量。一般来说,计算机算法是问题规模的函数,算法的时间复杂度也因此记做:
因此,问题的规模越大,算法执行的时间的增长率与的增长率正相关,称作渐进时间复杂度(Asymptotic Time Complexity)。 -
空间复杂度:算法的空间复杂度是指算法需要消耗的内存空间。其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。同时间复杂度相比,空间复杂度的分析要简单得多。
-
正确性:算法的正确性是评价一个算法优劣的最重要的标准。
-
可读性:算法的可读性是指一个算法可供人们阅读的容易程度。
-
鲁棒性:鲁棒性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。
2. 随机数
Java一共有三种方式生成随机数:
- 使用
Math.random()
方法; - 使用
Random
类; - 使用
System.currentTimeMillis()
方法。
2.1 Math.random()方法
位于java.lang
包下的Math
类提供的random()
方法产生的是范围在[0.0, 1.0)内的double
型数值,由于double
类型的精度很高,可以在一定程度下看做随机数。可以强制类型转换为int
类型。
获取任意区间的的随机数:
public class JavaRandom {
public static void main(String[] args) {
int x = (int) (Math.random() * (75 - 25) + 25); // 生成区间[25, 75)内的随机整数
System.out.println(x);
}
}
2.2 Random类
位于java.util
包下的Random
类提供了更多产生随机数的方法。它有两个构造方法:
Random()
:此方法使用当前时间为默认种子生成随机数;Random(long seed)
:此方法使用提供的种子生成随机数。
种子是产生随机数时的第一次使用值,一般计算机的随机数都是伪随机数,以一个真随机数(种子)作为初始条件,然后用一定的算法不停迭代产生随机数。
以相同的种子使用相同的方法产生的随机数是相同的。
获取任意区间的整数(只提供了int
型和long
型随机数生成方法):
import java.util.Random;
public class JavaRandom {
public static void main(String[] args) {
Random random = new Random(); // 使用当前时间作为种子
int y = random.nextInt(50 - 25) + 25; // 产生区间[25, 50)内的随机整数
System.out.println(y);
}
}
获取任意区间的其他类型数据(boolean
型、float
型和double
型):
import java.util.Random;
public class JavaRandom {
public static void main(String[] args) {
Random random = new Random(12345L); // 使用12345作为种子
double d = random.nextDouble() * (98 - 57) + 57; // 生成区间[57.0, 98.0)内的随机数
System.out.println(d);
}
}
2.3 System.currentTimeMillis()方法
位于java.lang
包下的System
类提供的currentTimeMillis()
方法可以获取系统当前时间戳(从1970年1月1日0时0点0分到现在所经过的毫秒数)。
用法和Math.random()
方法相同,但一般不用currentTimeMillis()
方法产生随机数,推荐使用前两种方法。
3. 数组赋值算法
3.1 数组的复制
Object
类的clone()
方法可以执行特定的克隆(复制)操作。
- clone()方法是面向引用数据类型(对象)变量的,基本数据类型变量无法使用。
- 使用
clone()
方法的对象的类必须实现了Cloneable
接口,否则这个类的对象调用clone()
方法会抛出CloneNotSupportedException
异常; Object
类虽然提供了clone
方法,但自身并未实现Cloneable
接口。所以尝试在类为Object
的对象上调用clone()
方法会抛出异常。- 所有的数组默认实现了
Cloneable
接口。
以数组的克隆操作为例,理解浅拷贝与深拷贝。
编写代码:
import java.util.Arrays;
public class ArrayCopy {
public static void main(String[] args) {
int[][] arr1 = new int[][]{
{
87, 62, -39}, {
75, 69}};
int[][] arr2 = arr1.clone();
arr2[0][1] = 0;
System.out.println(Arrays.toString(arr1[0]));
System.out.println(arr1 + " " + arr2);
System.out.println(arr1[0] + " " + arr2[0]);
}
}
运行结果:
[87, 0, -39]
[[I@1b6d3586 [[I@4554617c
[I@74a14482 [I@74a14482
可以看到我们修改了arr2
数组的内容,但是输出arr1
时却发现也被修改了;arr1
和arr2
引用的数组对象不同,但arr1[0]
和arr2[0]
引用的数组对象却相同。
- 浅拷贝:
- 对于成员变量的数据类型为基本数据类型的对象(比如一维数组),浅拷贝为直接进行值传递,拷贝得到的副本和原变量的引用的对象是不同的。
- 当对象中有引用数据类型的成员变量(比如二维数组),浅拷贝只会拷贝对象本身,副本和原对象的成员变量引用的对象是同一个。
- 深拷贝:
- 深拷贝是相对于浅拷贝来说的,深拷贝就是将对象完全拷贝为一个副本对象,这个对象引用的所有数据都将拷贝一份副本给副本对象引用。
如何实现深拷贝?
- 对于我们编写的类,只需要重写
clone()
方法,指明要拷贝所有成员变量即可。 - 对于Java已经定义了的类和数组,将所有引用数据类型的成员变量(二维数组的第二维)全部进行拷贝即可。
浅拷贝只拷贝地址,深拷贝则拷贝数据。
对于引用数据类型变量,类似A = B;
这样的操作只会把B所引用的地址赋给A,两者指向的是同一个引用,和拷贝没有关系。
3.2 数组的反转
将一个数组的数据顺序先后颠倒(比如{1, 2, 3}
反转为{3, 2, 1}
)。
示例代码:
import java.util.Arrays;
import java.util.Random;
public class ArrayReverse {
public static void main(String[] args)