📌一篇文章带你真正读懂排序算法

107 阅读6分钟

今天我们要介绍的算法是——选择排序

定义

选择排序(Selection Sort)是一种简单的排序算法,在数组中依次选择未排序部分中的最小值,并将其放置在已排序部分的末尾。该过程会不断重复,直到所有元素都在已排序部分。

步骤

  • 找到数组中最小元素,并将其放置在数组的起始位置。
  • 从剩余未排序的元素中找到最小元素,将其放置在已排序部分的末尾。
  • 重复步骤上述步骤,直到所有元素都在已排序部分。

时间复杂度

O(n^2)

算法实现步骤

交换数组元素

public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

这段代码是一个用于交换数组中的两个元素的静态方法,使用了三个传入参数:一个整型数组 arr,和两个整型参数ij。首先我们创建一个tmp变量并将其设置为arr[i],然后将arr[i]的值设置为arr[j]的值,最后将arr[j]的值设置为tmp的值。

主要步骤

if (arr == null || arr.length < 2) {
			return;
		}

如果传入的数组arrnull或长度小于2,则直接结束该方法的执行。

for (int i = 0; i < arr.length - 1; i++) {
			int minIndex = i;
			for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标 
				minIndex = arr[j] < arr[minIndex] ? j : minIndex;
			}
			swap(arr, i, minIndex);
		}

整体浏览

  1. 对于长度为N的数组,从0N-1依次遍历。
  2. 对于每个下标,从当前下标+1N-1范围内找到最小值的下标,并记录下来。
  3. 将当前下标和最小值的下标所对应的元素进行交换。
  4. 重复执行1~3步骤,直到整个数组排好序。

具体逻辑

for循环的条件是i < arr.length - 1,表示从数组下标为0的位置开始,依次遍历到倒数第二个位置。

这时候很多同学就有疑惑了,为啥不是遍历到arr.length-2呢?

因为选择排序需要在内层循环中找到当前未排序部分中的最小值并交换到前面,所以外层循环的最后一个位置不需要遍历到,因为前面已经排好序了。

在外层循环中,首先将**i赋值给minIndex ,表示把当前的i下标位置作为最小值的下标。接下来内层循环使用j遍历从i+1arr.length-1的未排序部分,寻找未排序部分中的最小值。如果找到了某个小于当前最小值下标minIndex对应的元素值,则将该元素的下标j更新给minIndex**。

经过这样一次内层循环,就能得到当前未排序部分中的最小值位置,然后将该最小值和第**i个位置的值进行交换。这里需要注意的是,如果minIndex**没有更新,说明最小值就是当前位置,就不需要进行交换操作。

选择排序Demo

题目

比如,假设有一个长度为5的数组**arr**,初始值为[5, 3, 6, 1, 4],经过选择排序后,应有如下操作:

  • 第一轮排序:外层循环**i = 0 ,内层循环从j = 1j = 4找到最小值下标minIndex = 3**,交换arr[0]arr[3]的值,得到[1, 3, 6, 5, 4]
  • 第二轮排序:外层循环**i = 1 ,内层循环从j = 2j = 4找到最小值下标minIndex = 4**,交换arr[1]arr[4]的值,得到[1, 4, 6, 5, 3]
  • 第三轮排序:外层循环**i = 2 ,内层循环从j = 3j = 4找到最小值下标minIndex = 4**,交换arr[2]arr[4]的值,得到[1, 4, 3, 5, 6]
  • 第四轮排序:外层循环**i = 3**,内层循环只遍历了一次,发现最小值就是arr[3],不需要进行交换操作。
  • 最后就排好啦!

生成随机数组用于测试算法

int[] arr = new int[(int) ((maxSize + 1) * Math.random())];

这一行代码主要是生成了一个长度为随机数字的整型数组arr,具体实现如下

  1. 生成一个[0,1)之间的随机小数,通过Math.random()方法实现。
  2. 将这个随机小数乘以(maxSize + 1),得到一个[0, maxSize + 1)的随机小数。
  3. 将这个随机小数强制转换为整型,得到[0, maxSize]区间内的整数值,作为数组的长度。
for (int i = 0; i < arr.length; i++) {
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;

这段代码中的for循环,用于给数组arr中的每一个元素赋值。具体实现如下

  1. 初始化变量i为0,表示从数组的第一个元素开始遍历。
  2. 执行循环,当i小于数组长度时,进入循环体。
  3. 循环体内,为数组中的当前元素生成随机值。生成的随机值范围是[-maxValue,maxValue]
  4. 首先生成[0, maxValue+1)之间的随机数;然后再生成[0, maxValue)之间的随机数。这两个值相减,得到的就是[-maxValue, maxValue]之间的随机值。
  5. 循环结束后,返回数组arr,其中的每一个元素都被随机赋值过。

数组复制

public static int[] copyArray(int[] arr) {
		if (arr == null) {
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}
  • 这段代码是一个静态方法,主要是用于复制一个整型数组,返回一个新的数组副本。该方法包含一个参数,即待复制的原数组arr
  • 方法首先检测arr是否为null,如果为null,则返回null。如果arr不为null,则创建一个和arr具有相同长度的新数组res
  • 接下来,使用一个for循环将arr中的每个元素复制到res数组相应的位置。这里采用基本的遍历方式,即通过数组下标来遍历。
  • 最后,返回复制完成的新数组res。由于数组是引用类型,因此返回的是新的数组副本,与原数组arr是独立的。

打印数组

public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
  • 这段代码是一个静态方法,主要是用于打印一个整型数组。该方法包含一个参数,即待打印的数组arr
  • 方法首先检测arr是否为null,如果为null,则直接返回,不做任何处理。
  • 接下来,使用一个for循环遍历arr中的每个元素并输出,其中间隔一个空格。最后在循环结束后输出一个换行符,使输出的结果在控制台中单独占一行。
  • 该方法并不返回任何值,而只是向控制台输出数组的内容。