选择排序 Java 带有示例的程序

⚡ 智能摘要

选择排序 Java 反复扫描数组中未排序的部分,找到剩余的最小值,并将其交换到相应位置,无论输入顺序如何,最多只需 n-1 次交换即可完成工作。

  • 🔘 定义: 选择排序在每次遍历时都会将数组分成已排序区域和未排序区域。
  • ☑️ 过程: 每次遍历都会在未排序区域中搜索最底层的元素,并将其向前交换。
  • 程序: 此 Java 例如,对 {860, 8, 200, 9} 进行排序,并打印每次比较和交换。
  • 🧪 复杂: 最佳情况、平均情况和最差情况的运行时间均为 O(n²),因为比较次数永远不会减少。
  • 🛠️ 记忆: 交换发生在原始数组内部,因此辅助空间保持在 O(1)。
  • 📊 行为: 经典版本不稳定,但它是所有二次排序算法中写入次数最少的。

选择排序 Java 带有示例的程序

选择排序如何工作?

选择排序实现了一个简单的排序算法,如下所示:

  • 算法重复搜索最低元素。
  • 将当前元素与具有最低值的元素交换
  • 每次选择排序的迭代/传递都会交换元素。

因此,每一次传递都会处理 排列 数据被分为两个区域:一个从左侧增长的有序块和一个从右侧缩小的无序块。算法遍历无序块,记住遇到的最小值的索引,并将该值与第一个无序位置交换。

由于每次循环只进行一次交换,因此最多经过 n-1 次交换,n 个元素的数组即可排序。正是这一特性使该例程区别于其他入门级例程。 Java 排序算法会更频繁地移动数据。

此 trac下面 e 遵循示例数组 {860, 8, 200, 9},与下一节中的程序在运行时打印它的方式完全相同。

通过 印刷对比图 找到的最小值 交换后的数组
开始 - - 860 8 200 9
1 860 和 8,8 和 200,8 和 9 8 8 860 200 9
2 860 和 200,200 和 9 9 8 9 200 860
3 200 860和 200 8 9 200 860

其中有两个细节 trac值得仔细观察。首先,即使顺序没有改变,第三次迭代仍然报告了交换操作,因为剩余元素中最小的值已经位于当前索引处,程序会将该元素与自身交换。其次,每次迭代的比较次数都减少一次(三次,两次,一次),这与页面下方复杂度数据的规律相符。

Java 实现选择排序的程序

下面这个类名为 SelectionSortAlgo,位于 com.guru99 包中。`main()` 方法声明了一个示例数组,打印出来,然后将其传递给 `selection()` 函数进行排序,最后再次打印出来。辅助函数 `printArray()` 将所有元素写入一行,从而生成易于阅读的逐行日志。

在 selection() 函数内部,外层循环标记了已排序区域和未排序区域之间的边界,变量 index 保存了迄今为止看到的最小值的位置,每次循环结束时的三个赋值操作执行交换。

package com.guru99;
 
public class SelectionSortAlgo {
 
	public static void main(String a[])
	{  
		int[] myArray = {860,8,200,9}; 
		
		System.out.println("------Before Selection Sort-----");
 
		printArray(myArray);
 
 
		selection(myArray);//sorting array using selection sort  
 
		System.out.println("-----After Selection Sort-----");  
 
		printArray(myArray); 
	} 
	
		public static void selection(int[] array)
	{  
		for (int i = 0; i < array.length - 1; i++)  
		{  System.out.println("Sort Pass Number "+(i+1));
			int index = i;  
			for (int j = i + 1; j < array.length; j++)
			{   
			    System.out.println("Comparing "+ array[index]  + " and " + array[j]);  
				if (array[j] < array[index]){ 
				System.out.println(array[index]  + " is greater than " + array[j] );
					index = j;
				
				
				}  
			}  
 
			int smallerNumber = array[index];   
			array[index] = array[i];  
			array[i] = smallerNumber;  
			System.out.println("Swapping Elements: New Array After Swap");
			printArray(array);
		}  
	}  
	static void printArray(int[] array){
	    
	    for(int i=0; i < array.length; i++)
		{  
			System.out.print(array[i] + " ");  
		} 
	    System.out.println();
	    
	}
 
}

输出:

编译并运行该类后,会生成以下控制台日志,每次运行输出一个块。

------Before Selection Sort-----
860 8 200 9 
Sort Pass Number 1
Comparing 860 and 8
860 is greater than 8
Comparing 8 and 200
Comparing 8 and 9
Swapping Elements: New Array After Swap
8 860 200 9 
Sort Pass Number 2
Comparing 860 and 200
860 is greater than 200
Comparing 200 and 9
200 is greater than 9
Swapping Elements: New Array After Swap
8 9 200 860 
Sort Pass Number 3
Comparing 200 and 860
Swapping Elements: New Array After Swap
8 9 200 860 
-----After Selection Sort-----
8 9 200 860

初学者在首次运行此示例时会遇到两个问题。因为该文件声明了 package com.guru99;源必须位于匹配的 com/guru99 否则,编译器会报告包名或类名不匹配。此时,必须使用类的完全限定名来启动该类。 java com.guru99.SelectionSortAlgo因为朴素 java SelectionSortAlgo 引发 NoClassDefFoundError 错误。

循环边界是另一个常见的陷阱。外层循环会在以下位置停止: array.length - 1 内循环从 i + 1更改任一边界都会导致额外的空传递或 ArrayIndexOutOfBoundsException 异常。

选择排序的时间和空间复杂度

程序中的内层循环总是运行到数组末尾,因此无论数据如何变化,算法执行的比较次数都相同。对于一个包含 n 个元素的数组,比较次数总共为 n(n-1)/2,对于包含四个元素的样本,则比较次数为 6 次,而上面的输出也确实打印了 6 行比较结果。

案例 比较 互换 时间复杂度 辅助空间
最佳(数组已排序) n(n-1)/ 2 正1 O(n²) O(1)
平均值(随机排序) n(n-1)/ 2 正1 O(n²) O(1)
最差(逆序排序) n(n-1)/ 2 正1 O(n²) O(1)

由这组均匀分布的数字可以得出三个结论:

  • 选择排序不具备自适应性。已排序的输入和已逆序输入的成本完全相同,因此不存在提前退出的捷径。 气泡排序 提供。
  • 交换次数是该算法的优势所在。最多只发生 n-1 次交换,远少于其他简单排序算法可能需要进行的二次方级的移动次数。
  • 内存使用量是恒定的。只需要循环计数器和两个临时变量 index 和 smallerNumber,因此辅助空间为 O(1),排序操作直接在原地进行。

二次增长是实际应用中的极限。数组大小翻倍,比较工作量大致翻四倍,因此选择排序更适合教学、小型数组和嵌入式代码,而非生产数据集,在生产数据集中,O(n log n) 算法才是正确的选择。

选择排序的优点和缺点

了解算法在哪些方面有所帮助,在哪些方面会造成损害,可以更容易地决定何时使用算法才是合理的。

优势

  • 逻辑简洁易懂,因此它是一种标准的初级排序练习。 插入排序.
  • 它进行原地排序,因此不会分配第二个数组,内存使用量也不会随着输入而增长。
  • 它最多对数组执行 n-1 次写入操作,这对于写入速度慢或会磨损介质的存储设备来说很重要。
  • 它的运行时间完全可预测,因为比较次数仅取决于数组长度。

缺点

  • 每个案例的时间复杂度都是 O(n²),因此该算法无法扩展到大型数据集。
  • 它无法检测到已排序的数组,因此永远不会提前完成。
  • 上面所示的经典形式是不稳定的,因此两个相等的值最终可能会以相反的顺序排列。
  • 在近乎有序的数据上,插入排序的比较频率比插入排序更高,而插入排序的时间接近线性时间。

简而言之,当数组较小且每次写入成本较高时,选择选择排序;而当数据集较大或已接近排序时,则避免选择排序。

选择排序与 Bubble排序与插入排序

这三种算法都是二次原地比较排序算法,但一旦输入的形状发生变化,它们的行为就会有所不同。

标准 选择排序 Bubbl排序 插入排序
最佳情况时间 O(n²) O(N) O(N)
平均时间和最坏情况时间 O(n²) O(n²) O(n²)
最坏情况下,可能会出现换班或调动。 n-1 次交换 n(n-1)/2 次交换 最多 n(n-1)/2 次移位
稳定 没有
适应已排序的输入 没有
辅助空间 O(1) O(1) O(1)
典型用途 所需写入次数最少 教学和识别已分类数据 小型或近乎有序的数组

表格解释了一个常见的面试答案。选择排序的优势在于交换次数少,冒泡排序的优势在于能够识别已排序的输入,而插入排序在实践中通常是最快的,因为实际数据往往是部分有序的。一旦数组元素超过几十个,它们都无法与归并排序或快速排序相媲美。

常见问题

经过 n-1 次循环后,未排序区域只剩下一个元素,而这个元素已经位于正确的位置。再进行一次循环将不会进行任何比较,因此循环界限避免了一次无效的迭代。

AI助手可以用文字描述每次迭代过程,构建额外的测试数组,并统计给定输入下的比较次数。请将此解释作为学习辅助工具,并在引用任何关于复杂性的说法之前,务必参考教科书进行核实。

是的。 GitHub 副驾驶 根据签名或注释完成方法。请自行检查内部循环的起始位置和交换语句,因为生成的版本有时会使用索引 i 而不是存储的最小索引进行交换。

这里显示的版本不稳定,因为长距离交换可能会导致一个相等的值跳过另一个相等的值。 Shift替换元素块而不是交换ping 保留相同键的原始顺序,但会增加写入次数。

Reverse 内层循环中的比较。测试 array[j] 是否大于 array[index]。 tracks 是剩余值中的最大值,因此每次遍历都会将最大值向前移动,最终数组从高到低排列。

是的。递归方法会找到当前子数组的最小值,将其移到数组首,然后对剩余部分调用自身。比较次数不变,但调用栈会增加 O(n) 的空间,因此循环形式更可取。

常见错误包括:忘记在每次循环开始时将索引重置为 i,内部循环的起始位置从 i 而不是 i + 1,以及交换操作。ping 使用 array[j] 而不是 array[index],会丢失信息。 track 值最小。

不。`Arrays.sort()` 对基本类型应用双枢轴快速排序,对对象应用 TimSort 排序,对小分区则应用插入式排序。选择排序更多地出现在教学材料和手写代码中,而不是标准库中。

总结一下这篇文章: