Java基础知识5
数组
定义:数组是一个变量,存储相同数据类型的一组数据,声明数组,就是在内存中划分一串连续的空间
-
注意:
- 数组一经定义,大小就确定了,不可以在此基础上再增加空间(重新创建除外),即不可以动态扩展空间
- 数组长度固定,应避免地址下标越界获取元素
-
基本要素
- 数据类型:指定数组中存放的数据类型
- 标识符:数组名称
- 数组元素:存放在数组中的数据(初始化)
- 下标:从0开始,最大不能大于数组的长度-1
一维数组
声明数组
方式1:数据类型[] 数组名;(推荐写法)
方式2:数据类型 数组名[];
// 方法一
int[] score;
// 方法二
String name[];
分配空间
语法:数组名 = new 数据类型[数组长度];
String[] name = new String[5];
- 注意:长度必须要定义,代表数组可以存放的元素个数
- 备注:
- 根据不同的数据类型,会有不同的初始值
- 数组定义后,不初始化赋值,仍然可以直接访问元素,不会报错
- int:0
- String:null
赋值
语法:数组名[下标] = 定义数据类型的值;
方式1:数据类型[] 数组名 = {元素1,元素2…};
方式2:数据类型[] 数组名 = new 数据类型[]{元素1,元素2…}
// 方法1
int[] scores = {1,2,56,88,94};
// 方法2
scores = new int[]{70,20,30,40,50,60,10};
- 注意:
- 实际是向分配的内存空间存放数据(地址引用)
- 由于初始化值,已经确定了数组的元素个数,所以,不可以再指定长度
- 边声明边赋值:必须定义在一条语句内,不能分开
- 数组元素赋值之后,可以重复获取,并使用
长度
语法:数组名.length
scores = new int[]{70,20,30,40,50,60,10};
System.out.println(scores[0]);
int sum = 0;
// scores.length scores数组长度
for (int i = 0; i < scores.length; i++) {
sum += scores[i];
}
- 注意:数组长度使用的是length数组,不同于字符串,字符串长度使用的是length()方法
比较大小
循环数组,借助中间变量,依次比较
int[] prives = new int[4];
Scanner scanner = new Scanner(System.in);
for (int i = 0; i < 4; i++) {
System.out.print("请输入第" + (i + 1) + "家店的手机价格:");
prives [i] = scanner.nextInt();
}
int min = prives[0];
int index = 0;
for (int i = 0; i < prives.length; i++) {
if(prives[i] < min){
min = prives[i];
index = i;
}
}
System.out.println("第" + (index + 1) + "家店手机价格最低,价格为:" + min);
scanner.close();
遍历数组
方式一:普通循环
int[] scores = {1,2,56,88,94};
double sum = 0;
for(int i = 0; i < scores.length; i++){
sum += scores[i];
}
System.out.println("平均数为:" + (sum/scores.length));
方式二:增强for循环,只适合循环遍历
int[] scores = {1,2,56,88,94};
double sum = 0;
for(int score : scores){
sum += score;
}
System.out.println("平均数为:" + (sum/scores.length));
方式三:lambda表达式(JDK 8)
import java.util.Arrays;
int[] scores = {1,2,56,88,94};
double sum = 0;
Arrays.stream(scores).forEach(score ->{
sum += score;
})
System.out.println("平均数为:" + (sum/scores.length));
Arrays工具类
导包:import java.util.Arrays;
查看数组所有元素
使用方法:Arrays.toString(数组名)
int[] scores = {1,2,56,88,94};
// 输出结果:[1,2,56,88,94]
System.out.println(Arrays.toString(scores));
拷贝数组
使用方法:Arrays.copyOf(需拷贝的数组名,拷贝后的数组大小)
备注:此方法可以达到扩容效果,但实质是创建了一个新数组,复制了原数组的数据而已,多出的长度部分,使用类型的默认值填充。
int[] scores = {1,2,56,88,94};
int[] scores2 = Arrays.copyOf(scores, scores.length + 1);
// 输出结果:[1,2,56,88,94,0]
System.out.println(Arrays.toString(scores2));
指定范围拷贝
使用方法:Arrays.copyOfRange(需拷贝的数组名,起始下标,结束下标)
注意:包含起始下标,不包含结束下标
备注:结束下标可以超过原数组的长度,多余位置自动填充数据类型的默认值
int[] scores = {1,2,56,88,94};
int[] scores2 = Arrays.copyOfRange(scores,2,8);
// 输出结果:[56,88,94,0,0,0]
System.out.println(Arrays.toString(scores2));
升序排序(串行)
使用方法:Arrays.sort(数组名)
备注:可以结合比较器Comparator,完成更加复杂的排序。
比较器方法:Arrays.sort(数组,比较器Comparator);
注意:
数字默认排序方法即为大小
字符默认排序按照ASCII码排序
字符串依次按照ASCII码排序,若首字母相同则比较下一位
int[] scores = {1,2,56,88,94,66};
Arrays.sort(scores);
// 输出结果:[1,2,56,66,88,94]
System.out.println(Arrays.toString(scores));
指定范围排序(串行)
使用方法:Arrays.sort(数组名, 开始下标, 结束下标)
注意:包含起始下标,不包含结束下标
备注:可以结合比较器Comparator,完成更加复杂的排序。
比较器方法:Arrays.parallelSort(数组名,比较器Comparator);
int[] scores = {1,6,2,56,88,94,66};
Arrays.sort(scores,0,4);
// 输出结果:[1,2,6,56,88,94,66]
System.out.println(Arrays.toString(scores));
升序排序(并行)
使用方法:Arrays.parallelSort(数组名)
备注:
此方法为并行,数据量很大时推荐使用,效率较高
可以结合比较器Comparator,完成更加复杂的排序。
比较器方法:Arrays.parallelSort(数组名,比较器Comparator);
int[] scores = {1,2,56,88,94,66};
Arrays.parallelSort(scores);
// 输出结果:[1,2,56,66,88,94]
System.out.println(Arrays.toString(scores));
指定范围排序(并行)
使用方法:Arrays.parallelSort(数组名, 开始下标, 结束下标)
注意:包含起始下标,不包含结束下标
备注:
此方法为并行,数据量很大时推荐使用,效率较高
可以结合比较器Comparator,完成更加复杂的排序。
比较器方法:Arrays.parallelSort(数组名,开始下标,结束下标,比较器Comparator);、
int[] scores = {1,6,2,56,88,94,66};
Arrays.sort(scores,0,4);
// 输出结果:[1,2,6,56,88,94,66]
System.out.println(Arrays.toString(scores));
为数组元素填充相同值
使用方法:fill(数组名,要填充的值)
int[] scores = {1,2,56,88,94,66};
Arrays.fill(scores,1);
// 输出结果:[1,1,1,1,1,1]
System.out.println(Arrays.toString(scores));
为数组部分元素填充相同值
使用方法:fill(数组名,开始下标, 结束下标,要填充的值)
注意:包含起始下标,不包含结束下标
int[] scores = {1,2,56,88,94,66};
Arrays.fill(scores,0,3,1);
// 输出结果:[1,1,1,88,94,66]
System.out.println(Arrays.toString(scores));
查看多维数组
使用方法:Arrays.deepToString(多维数组名)
int[][] nums = {{10,20},{30,40}};
Arrays.deepToString()
// 输出结果:[[10, 20],[30, 40]]
System.out.println(Arrays.toString(scores));
比较两个数组是否相等
使用方法:Arrays.equals(数组名1,数组名2)
注意:数组元素为基本数据类型时,直接比较;为引用数据类型时,依次调用元素的 equals()方法比较
int[] num1 = {1,3,5,7,9};
int[] num2 = {1,3,5,7,9};
int[] num3 = {1,2,3,4,5};
// 输出结果:true
System.out.println(Arrays.equals(num1, num2));
// 输出结果:false
System.out.println(Arrays.equals(num1, num3));
比较多维数组是否相等
使用方法:Arrays.deepEquals(二维数组名1,二维数组名2)
注意:数组元素为基本数据类型时,直接比较;为引用数据类型时,依次调用元素的 equals()方法比较
int[][] nums1 = {{10,20},{30,40}};
int[][] nums2 = {{20,20},{30,40}};
// 输出结果:false
System.out.println(Arrays.deepEquals(nums1,nums2));
数组转换List
使用方法:Arrays.asList(数组名/数组)
注意:基本数据类型的数组转换为List,需要声明为包装类类型
// 数组转换1
Integer[] num1 = {1,3,5,7,9};
List<Integer> list = Arrays.asList(num1);
// 输出结果:[1, 3, 5, 7, 9]
System.out.println(list);
// 数组转换2
List<String> list = Arrays.asList("num","abc");
// 输出结果:[num, abc]
System.out.println(list);
数组元素查找
使用方法:binarySearch(数组名,查找元素)
注意:数组必须已升序排列
备注:
搜索值不是数组元素,且在数组范围内,从1开始计数,返回 “- 可插入点元素索引值”
搜索值是数组元素,返回 “此值下标”
搜索值不是数组元素,大于数组内元素,返回 “– (length + 1)”
搜索值不是数组元素,小于数组内元素,返回 “–1”
有多个相同的数时返回值不确定(本质是二分查找)
int[] num8 = {5,5,8,4,2,1,5,6,9,9,10};
// [1, 2, 4, 5, 5, 5, 6, 8, 9, 9, 10]
Arrays.sort(num8);
// 存在
int index = Arrays.binarySearch(num8, 8);
// 结果输出:7
System.out.println("8位于数组的下标:" + index);
// 不存在,小于数组内元素
int index1 = Arrays.binarySearch(num8, 0);
// 结果输出:-1
System.out.println("0位于数组的下标:" + index1);
// 不存在,大于数组内元素
int index2 = Arrays.binarySearch(num8, 15);
// 结果输出:-12
System.out.println("15位于数组的下标:" + index2);
// 不存在,在数组范围内
int index3 = Arrays.binarySearch(num8, 3);
// 结果输出:-3
System.out.println("3位于数组的下标:" + index3);
指定范围内查找元素
使用方法:binarySearch(数组名,起始下标,结束下标,查找元素值)
注意:数组必须已升序排列
备注:
搜索值不是数组元素,且在数组范围内,从1开始计数,返回 “- 可插入点元素索引值”
搜索值是数组元素,返回 “此值下标”
搜索值不是数组元素,大于数组内元素,返回 “– (结束下标 + 1)”
搜索值不是数组元素,小于数组内元素,返回 “– (起始下标 + 1)”
有多个相同的数时返回值不确定(本质是二分查找)
int[] num8 = {5,5,8,4,2,1,5,6,9,9,10};
// [1, 2, 4, 5, 5, 5, 6, 8, 9, 9, 10] length = 11
Arrays.sort(num8);
// 存在
int index = Arrays.binarySearch(num8,3,9,8);
// 结果输出:7
System.out.println("8位于数组的下标:" + index);
// 不存在,小于数组内元素
int index1 = Arrays.binarySearch(num8,3,9,0);
// 结果输出:-4
System.out.println("0位于数组的下标:" + index1);
// 不存在,大于数组内元素
int index2 = Arrays.binarySearch(num8,3,9,15);
// 结果输出:-10
System.out.println("15位于数组的下标:" + index2);
// 不存在,在数组范围内
int index3 = Arrays.binarySearch(num8,3,9,7);
// 结果输出:-8
System.out.println("7位于数组的下标:" + index3);
对数组中的所有元素进行相同操作(串行)
使用方法:Arrays.setAll(数组,进行的操作)
源方法:public static void setAll(T[] array, IntFunction<? extends T> generator)
备注:
T是泛型,指代任何类型
IntFunction是用于便捷地创建泛型数组的,一般使用lambda表达式
此方法为串行,数据量很大时效率会低一些
// 源码
public static <T> void setAll(T[] array, IntFunction<? extends T> generator) {
// 判断操作是否为null,为null则抛出NullPointerException空指针异常
Objects.requireNonNull(generator);
for (int i = 0; i < array.length; i++)
// 对数组每个元素执行操作
array[i] = generator.apply(i);
}
}
// 示例
Integer[] arrayInt = {5,6,9,8};
// i为索引值
Arrays.setAll(arrayInt, i -> arrayInt[i] + 10);
// 输出结果:[15, 16, 19, 18]
System.out.println(Arrays.toString(arrayInt));
// 收藏例子
int[] arr = new int[10000000];
Random r = new Random();
// 串行给数组中每一个元素都附上一个随机值
Arrays.setAll(arr, (i)->r.nextInt());
对数组中的所有元素进行相同操作(并行)
使用方法:Arrays.parallelSetAll(数组,进行的操作)
源方法:public static void parallelSetAll(T[] array,IntFunction<? extends T> generator)
备注:此方法为并行,数据量很大时推荐使用,效率较高
// 源码
public static <T> void parallelSetAll(T[] array, IntFunction<? extends T> generator) {
// 判断操作是否为null,为null则抛出NullPointerException空指针异常
Objects.requireNonNull(generator);
IntStream.range(0, array.length).parallel().forEach(i -> {
// 对数组每个元素执行操作
array[i] = generator.apply(i);
});
}
// 示例
Integer[] arrayInt = {5,6,9,8};
// i为索引值
Arrays.parallelSetAll(arrayInt, i -> arrayInt[i] + 10);
// 输出结果:[15, 16, 19, 18]
System.out.println(Arrays.toString(arrayInt));
// 收藏例子
int[] arr = new int[10000000];
Random r = new Random();
// 并行给数组中每一个元素都附上一个随机值
Arrays. parallelSetAll (arr, (i)->r.nextInt())
以流的格式返回数组
使用方法::Arrays.stream(数组名)
备注:转换后即可使用Stream的方法
Integer[] arrayInt = {5,6,9,8};
Stream<Integer> stream = Arrays.stream(arrayInt);
迭代
使用方法:Arrays.spliterator(数组名)
备注:
spliterator为JDK8提供的分片迭代器,是为了并行遍历数据源中的元素而设计的。
与顺序遍历迭代器不同,顺序遍历迭代器Iterator是顺序遍历的,大量数据情况下效率较低,而分片迭代器spliterator是并行遍历的,大量数据情况下效率较高。
import java.util.Arrays;
import java.util.Spliterator;
import java.util.stream.IntStream;
public class Test {
private String proName;
private Integer peoNo;
public Test(String proName, Integer peoNo) {
super();
this.proName = proName;
this.peoNo = peoNo;
}
public String getProName() {
return proName;
}
public void setProName(String proName) {
this.proName = proName;
}
public Integer getPeoNo() {
return peoNo;
}
public void setPeoNo(Integer peoNo) {
this.peoNo = peoNo;
}
public static void main(String[] args) {
Test[] tests = new Test[5];
// IntStream.range(0, 5) 生成一个0-5的数字流 即[0,1,2,3,4] 给对象循环赋值
IntStream.range(0, 5).forEach(i -> tests[i] = new Test("商品" + (i+1) + "号", i));
// 返回分片迭代器
Spliterator<Test> spliterator = Arrays.spliterator(tests);
// 迭代遍历数组
spliterator.forEachRemaining(pro -> {
// 输出结果:商品1号 商品2号 商品3号 商品4号 商品5号
System.out.println(pro.getProName());
});
}
}
迭代部分元素
使用方法:Arrays.spliterator(数组名,起始下标,结束下标)
注意:包含起始下标,不包含结束下标
// 其他代码与上述迭代部分代码相同
Spliterator<Test> spliterator = Arrays.spliterator(tests,1,4);
// 迭代遍历数组
spliterator.forEachRemaining(pro -> {
// 输出结果:商品2号 商品3号 商品4号
System.out.println(pro.getProName());
});
利用数组中的多个元素进行操作生成新数组
使用方法:Arrays.parallelPrefix(数组名,要进行的操作)
源方法:public static void parallelPrefix(T[] array,BinaryOperator op)
注意:操作一般为lambda表达式,又称生成器函数
备注:
此方法为并行,数据量很大时推荐使用,效率较高
BinaryOperator接口用于执行lambda表达式并返回一个T类型的返回值
// 源码
public static <T> void parallelPrefix(T[] array, BinaryOperator<T> op) {
// 判断操作是否为null,为null则抛出NullPointerException空指针异常
Objects.requireNonNull(op);
if (array.length > 0){
// 根据生成器函数op对数组进行相应的操作
new ArrayPrefixHelpers.CumulateTask<>(null, op, array, 0, array.length).invoke();
}
}
// 示例
Integer[] data = {2, 3, 4, 5};
// 第一个元素2不变(作为参数x),将其与第二个元素3(作为参数y)一起作为参数传入,得到和5
// 再将5作为数组新的参数x,与第四个参数(作为参数y)进行运算,以此类推
Arrays.parallelPrefix(data, (x, y) -> x + y);
// 输出结果:[2, 5, 9, 14]
System.out.println(Arrays.toString(data));
插入元素
分析
1. 对数组进行排序
2. 拷贝原数组并扩容
3. 从后向前,依次移动(因为新数组最后以0补位)
4. 在插入点插入数据(赋值)
int score = 55;
int[] nums = {88, 77, 99, 66, 22, 11, 44, 33};
// 排序
Arrays.sort(nums);
// 拷贝并扩容
int[] scores = Arrays.copyOf(nums, nums.length+1);
// 确定插入位置方法1(binarySearch返回值Arrays工具类中有讲,下方有方法2):
int index = Arrays.binarySearch(nums, score);
index = (index == -1) ? 0 : (index < 0) ? -index - 1 : index;
// 从后向前,依次移动
for (int i = scores.length - 1; i > index; i--) {
scores[i] = scores[i - 1];
}
// 在插入点插入数据(赋值)
scores[index] = score;
// 输出结果:[11, 22, 33, 44, 55, 66, 77, 88, 99]
System.out.println(Arrays.toString(scores));
// 确定插入位置方法2:
for (int i = 0; i < scores.length; i++) {
if(score > scores[i]){
index = i;
// 要立刻结束,否则,index会得到最后的值
break;
}
}
优化方法:直接在扩容位置赋值再排序(仅实现业务,不太符合插入这一事件的要求)
int score = 55;
int[] nums = {88, 77, 99, 66, 22, 11, 44, 33};
int[] scores = Arrays.copyOf(nums, nums.length+1);
scores[scores.length - 1] = score;
Arrays.sort(scores);
// 输出结果:[11, 22, 33, 44, 55, 66, 77, 88, 99]
System.out.println(Arrays.toString(scores));
二维数组
声明数组
语法1:<数据类型>[][] 数组名;
语法2:<数据类型> 数组名[][];
// 方式1
int[][] scores;
// 方式2
int scores[][];
分配空间
语法1:<数据类型>[][] 数组名 = new 数据类型[数组二维长度][数组一维长度];
语法2:<数据类型> 数组名[][] = new 数据类型[数组二维长度][数组一维长度];
注意:
数组二维长度:即数组有几行
数组一维长度:即数组一行有几个元素
备注:
定义时,必须定义最大二维维数,一维维度可以不定义,且长度可以不一致
二维数组实际上是一个以一维数组做为元素的一维数组
// 正确
int[][] scores;
scores = new int[5][];
// 错误
int[][] scores = new int[][];
// 正确
int[][] scores = new int[5][6];
赋值
语法1:<数据类型>[][] 数组名 = new 数据类型[][]{{…},{…},…};
语法2:<数据类型> 数组名[][] = {{…},{…},…};
方法3:可以每行分开赋值,赋值方法同一维数组
注意:scores[1] = {1,2,3,6,5}; 此种赋值方法错误
// 方式1
int[][] nums = new int[][]{ { 90, 85, 92, 78, 54 }, { 76, 63, 80 }, { 87 }};
// 方式2
int[][] nums = {{ 90, 85, 92, 78, 54 }, { 76, 63, 80 }, { 87 } };
// 方法3
int[][] scores;
scores = new int[2][];
scores[0] = new int[2];
scores[1] = new int[]{1,2,3,6,8};
// 注意:下列方式错误
scores[1] = {1,2,3,6,5};
长度
二位数组长度:
语法:数组名.length
本质:通过二维数组名获取长度,即有几个一维数组(有几行)
int[][] scores = new int[5][6];
// 输出结果:5
System.out.println(scores.length);
二位数组中一维数组长度:
语法:数组名[二维下标].length
int[][] scores = new int[5][6];
// 输出结果:6
System.out.println(scores[0].length);
比较大小
方法:借助Arrays.deepEquals比较
注意:数组元素为基本数据类型时,直接比较;为引用数据类型时,依次调用元素的 equals()方法比较
int[][] nums1 = {{10,20},{30,40}};
int[][] nums2 = {{20,20},{30,40}};
// 输出结果:false
System.out.println(Arrays.deepEquals(nums1,nums2));
遍历数组
方式一:嵌套for循环
int[][] nums = {{20,20},{30,40}};
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums[i].length; j++) {
System.out.print(nums[i][j] + " ");
}
/*
输出结果:
20 20
30 40
*/
System.out.println();
}
方式二:增强for循环,只适合循环遍历
int[][] nums = {{20,20},{30,40}};
for(int[] num : nums) {
//遍历一维数组中每一个元素
for(int cell : num) {
System.out.print(cell + " ");
}
/*
输出结果:
20 20
30 40
*/
System.out.println();
}
方式三:lambda表达式(JDK 8),本质上还是嵌套for循环
int[][] nums = {{20,20},{30,40}};
Arrays.stream(nums).forEach(num ->{
Arrays.stream(num).forEach(cell ->{
System.out.print( cell + " ");
});
System.out.println();
});
排序算法
时间复杂度:对排序过程中总的操作次数。
空间复杂度:是指算法在计算机运行需要多大的内存空间。
时间复杂度:
1.对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度。
2.对于一个循环,假设循环体的时间复杂度为O(n),循环次数为m,则这个循环的时间复杂度为 O(n×m)。
3.对于多个循环,假设循环体的时间复杂度为O(n),各个循环的循环次数分别是m1, m2, m3…,则这个循环的时间复杂度为O(n×m1×m2×m3…)。
4.对于条件判断语句,总的时间复杂度等于其中时间复杂度最大的路径的时间复杂度。
大O推导法:
1.用常数1取代运行时间中的所有加法常数。
2.在修改后的运行次数函数中,只保留最高阶项。
3.如果最高阶项存在且不是1,则去除与这个项相乘的常数。
示例:(n2 / 2 + n / 2) ——> n2
空间复杂度:
1.如果程序所占用的存储空间和输入值无关,则该程序的空间复杂度就为O(1)。
2.如果随着输入值的增大,程序申请的临时空间成线性增长,则程序的空间复杂度用O(n)表示。
3.如果随着输入值n的增大,程序申请的临时空间成n2关系增长,则程序的空间复杂度用O(n2)表示。
4.如果随着输入值n的增大,程序申请的临时空间成n3关系增长,则程序的空间复杂度用O(n3)表示。
5.以此类推…
稳定性:
稳定:如果a原本在b前面,两值相等,排序之后两者顺序不变。
不稳定:如果a原本在b的前面,两值相等,排序后顺序可能调换。
备注:算法的稳定性与实际实现有关,并非绝对固定。
冒泡排序
冒泡排序:每次循环,将前后两个数进行比较,若前后顺序与要求顺序不一致,则将两值进行交换直至最后一位,以此类推。
时间复杂度:
最差: O(n2)
最优: O(n)
平均: O(n2)
空间复杂度:O(1)
稳定性:稳定(示例中是稳定的,若比较判断改为>=则会不稳定)
升序为例:
1.前后两两比较,大的后移。
2.外层循环n-1次(一次循环中,n个数比较大小需要比较n-1次)
3.内层循环n-1-i次(每循环一次最后会确定一个值,因此内层循环次数,即比较次数递减)
int[] array = {1,5,6,8,9,2};
int tmp = 0;
for(int i = 0; i < array.length - 1;i++ ){
for(int j = 0; j < array.length - 1 - i;j++ ){
if(array[j] > array[j+1]){
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
// 输出结果:[1, 2, 5, 6, 8, 9]
System.out.println(Arrays.toString(array));
直接选择排序
直接选择排序:
又称选择排序,每次循环将当前值下标i作为最小值下标min,循环与后面的值进行比较,若循环中的值小于min的值则将此值作为min,将其与后续值如上进行比较,本次循环结束后将i下标的值与min下标的值互换,以此类推。
时间复杂度:
最差: O(n2)
最优: O(n2)
平均: O(n2)
空间复杂度:O(1)
稳定性:不稳定(示例中是稳定的,若比较判断改为<=则会不稳定)
降序为例:
1.每个位置寻找当前未排序的最大值,储存下标,内层循环结束交换当前位置和最大值位置元素。
2.外层循环n-1次。
3.内层从i+1元素进行循环,直至数组最后一位。
备注:使用此方法数据量越少越好,因为时间复杂度始终为n2
int[] array = {1,5,6,8,9,2};
int tmp = 0;
for(int i = 0;i< array.length - 1;i++){
int max = i;
for(int j = i + 1;j < array.length;j++){
if(array[max] < array[j]){
max = j;
}
}
tmp = array[i];
array[i] = array[max];
array[max] = tmp;
}
// 输出结果:[9, 8, 6, 5, 2, 1]
System.out.println(Arrays.toString(array));
直接插入排序
直接插入排序:
对除了第一个值的每个值进行循环,从后向前比较,一次与当前位置前的值进行比较,若可以插在比较的值之前则将此值后移,以此类推,直至不满足插入的位置,将它插入在此值后面。
时间复杂度:
最差: O(n2)
最优: O(n)
平均: O(n2)
空间复杂度:O(1)
稳定性:稳定
升序为例:
1.储存当前循环值,依次比较前面的值是否大于此值,大于则后移,直至找到第一个小于此值的数,将此值插入到这个值后面。
2.从下标1开始,外层循环n-1次。
3.内层循环次数不确定,根据是否可插入判断是否继续循环(可插入则继续)
4.注意内层循环要添加>=0的判断,防止下标小于0
5.循环过程中的赋值均是将前面的赋给后面的,因为后面多一个位置,依次向后赋值不会造成数据丢失
int[] array = {15,95,6,25,56,45,25,26};
// 注意此处从1开始,因为第一个值默认它已经排序
for (int i = 1; i < array.length; i++) {
// 设置初始前一个值的下标,不能直接使用i-1,pre后续会动态改变,i-1在一次循环中不会改变
int pre = i - 1;
// 注意:需要储存当前值,array[i]后续会被前面较大值替换掉
int curr = array[i];
while (pre >= 0 && curr < array[pre]) {
// 达到后移效果,最初的pre+1是储存的curr值的位置,所以不会丢失数据,直接替换即可
array[pre + 1] = array[pre];
// 依次向前比较,若大于curr值则后移,直至小于curr值
pre--;
}
// 此处需要使用pre+1,因为此时的pre已不满足>=0或者小于curr的条件,要将curr插在它后面
array[pre + 1] = curr;
}
// 输出结果:[6, 15, 25, 25, 26, 45, 56, 95]
System.out.println(Arrays.toString(array));
希尔排序
希尔排序:
先比较指定间隔的数之间的大小,若当前值小于比较值,则交换数值(仅先给后面的值赋值),再向前进行相同间隔的比较,至符合顺序要求后跳出二层循环,进行下一个数的比较,本轮比较结束后,更改间隔重复上述操作。当间隔值为1时进行插入排序,此使数据已基本排好序,效率会提升。
时间复杂度:
最差: O(n2)
最优: O(n)
平均: O(nlogn)
空间复杂度:O(1)
稳定性:不稳定
升序为例:
1.设置间隔以及间隔更改的规律。
2.从间隔值gap开始循环,循环n-gap次。
3.内层循环次数不确定,根据此值前同间隔的数有几个并且是否大于它决定。
4.注意内层循环若已满足顺序要求一定要break,否则最后赋值会多向前数一个间隔。
5.循环过程中的赋值均是将前面的赋给后面的,因为后面多一个位置,依次向后赋值不会造成数据丢失
public static void shell(int[] array){
// 设置间隔
int gap = array.length;
while(gap > 1){
sort(array,gap);
// 逐渐缩小间隔
gap = (gap / 3) + 1;
}
// 接近1时已经基本有序了,最后使用插入排序
sort(array, 1);
}
public static void sort(int[] array,int gap){
// 内层比外层的值位置较前
for (int i = gap; i < array.length; i++) {
// 储存当前值
int curr = array[i];
// 相隔给定间隔的数,j需要在循环外使用定义到循环外
int j = i - gap;
// 希尔排序为倒序,递减的所以判定条件为>=0
for (; j >= 0; j = j - gap) {
/* 若当前值小于间隔gap之前的值,更改值后在此与前面相同间隔的比较,否则跳出循环。
* 比较是从gap开始的,前面相同间隔只有一轮,后面超过2gap的值,若顺序正确,无需再次向前比较,因为前面的已经比较过了
* 顺序不正确仍需比较
*/
if(array[j] > curr){
// 给后面的位置赋值
array[j + gap] = array[j];
}else{
// 不写break,会执行j - gap后结束程序,后续的给j + gap赋值会赋给前一个间隔的值
break;
}
}
array[j + gap] = curr;
}
}
public static void main(String[] args) {
int[] array = {15,95,6,25,56,45,25,26,1,158,30,15,6,9,22,69};
shell(array);
// 输出结果:[1, 6, 6, 9, 15, 15, 22, 25, 25, 26, 30, 45, 56, 69, 95, 158]
System.out.println(Arrays.toString(array));
}