Java基础知识5【数组与Arrays工具类与排序算法】

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));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值