1.常见8种排序算法分析笔记之-空间O(1)三种(冒泡、选择、插入)

本文详细介绍了冒泡排序、选择排序和插入排序三种经典排序算法的实现思想、复杂度分析及代码实现。尽管它们的时间复杂度都是O(N^2),但通过优化,如冒泡排序中的lastSwap变量,可以减少不必要的循环。文章还提供了性能测试,展示了在不同数据集上的表现,选择排序在无序数组中因较少交换而表现出较好的性能,而插入排序在有序数组中更为高效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

空间复杂度为O(1)的三种排序法

一、冒泡排序法

1.代码实现思想

参考文章

慕课网数据结构与算法课程
  • 冒泡,即大的数据下层,小的数据上浮。

  • 代码实现:每一轮,指定数组中的一个位置,放置该位置前所有元素中的最大值,即通过数据两两比较获取

  • 要将N-1个位置放置好数据,需要N-1轮

  • 每轮,指针j从0位置依次遍历,遍历到指定位置的前一个位置,比较当前位置j与下个位置j+1位置数值大小,是否交换

  • 代码优化对于大量有序的数列,可能要指定的位置已经排好序,无需再循环判断,即,为了减少循环,用变量lastSwap记录已经排号序的位置,下次循环指定的位置是标志位前一个无序位置

2.复杂度分析

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2),即需要 O ( N ) O(N) O(N)轮,每轮 O ( N ) O(N) O(N)的操作

  • 空间复杂度: O ( 1 ) O(1) O(1),即需要常数级的变量空间,无需开辟新的空间

  • 稳定性:指的是相同元素保存相对位置不变的特性,可以设计成稳定排序,即相等元素比较时不交换

3.代码实现

public class BubbleSort {
	private BubbleSort() {}
	
	public static <E extends Comparable<E>> void sort(E[] arr) {
		// 先处理特殊情况
		if(arr == null || arr.length <= 2)  return;
		
		for(int i = arr.length -1; i > 0;) { // 指定要处理的位置,N-1轮,后面数先排好序
            int lastSwap = 0;		// 从后往前看,标记已经排好序的最后的位置
		  	for(int j = 0; j < i; j ++) { // 遍历比较,注意循环终止条件j + 1 <= i
				if(arr[j].compareTo(arr[j + 1]) > 0) { // 注意数组越界
					swap(arr, j, j + 1);
                   	  lastSwap = j + 1;	
				}
			}
            i = lastSwap - 1;	// i 不是每次减一,而是直接跳到标记没有排好序的最后一个位置,减少循环
		}
	}

	private static <E> void swap(E[] arr, int j, int i) {
		E t = arr[j];
		arr[j] = arr[i];
		arr[i] = t;	
	}
    
    // 代码测试
    public static void main(String[] args) {
		int n = 10000;
		Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
		SortingHelper.sortTime("BubbleSort", arr);
	}
}

4.代码测试

设计两个辅助测试类:

  • 数组生成器(ArrayGenerator.class):提供两个接口,生成有序数组和无序数组

    • 有序数组,传入数组大小,按照数组索引依次遍历填入有序数值
    • 无序数组,借助JAVA自带Random类,传入数组大小和Random边界,将边界内随机生成的数值填入数组
  • 排序算法校验和性能测试器(SortingHelper.class),提供两个接口,排序校验,性能时间打印

    • 排序校验,默认是从小到大,两两比较判断是否有没有排序好的情况
    • 性能测试,传入排序算法名,可以扩展为多个排序算法调用,打印测试时间前进行排序校验
  • 代码实现:

import java.util.Random;

public class ArrayGenerator {
	private ArrayGenerator() {}
	
	// 正序排列的数组
	public static Integer[] generateOrderedArray(int n) {
		Integer[] arr = new Integer[n];
		for(int i = 0; i < n; i++) {
			arr[i] = i;
		}
		return arr;
	}

	// 乱序排列的数组
	public static Integer[] generateRandomArray(int n, int border) {
		Integer[] arr = new Integer[n];
		Random rdm = new Random();
		for(int i = 0; i < n ; i++) {
			arr[i] = rdm.nextInt(border);
		}
		return arr;
	}
}
public class SortingHelper {
	private SortingHelper() {}
	
	public static <E extends Comparable<E>> boolean isSorted(E[] arr) {
		for(int i = 1; i < arr.length; i++) {
			if(arr[i-1].compareTo(arr[i]) > 0) {
				return false;
			}
		}
		return true;
	}
	
	public static <E extends Comparable<E>> void sortTime(String sortName, E[] arr) {
		long startTime = System.nanoTime();
		
		if(sortName.equals("BubbleSort")) {
			BubbleSort.sort(arr);
		} else if(sortName.equals("InsertionSort")) {
			InsertionSort.sort(arr);
		} else if(sortName.equals("SelectionSort")) {
			SelectionSort.sort(arr);
		} else if(sortName.equals("MergeSort")) {
			MergeSort.sort(arr);
		}
		long lastTime = System.nanoTime();
		double time = (lastTime - startTime) / 1000000000.0;
		
		if(!SortingHelper.isSorted(arr)) {
			throw new RuntimeException("Sort false!");
		}		
	
		System.out.println(String.format(
				"%s, n = %d : %f s", sortName, arr.length, time));
	}
}
输出结果:
BubbleSort, n = 10000 : 0.417135 s
选择排序法

二、选择排序法

1.代码实现思想

  • 与冒泡思想类似,每轮选择最小元素(最大元素)放在指定位置,一般从左往右依次放置
  • 选择过程也是比较交换过程,为了减少交换操作,设计一个变量minIndex储存当前轮最小值索引,遍历比较
  • 最后将minIndex指向的数值与指定位置的数值进行交换,得到指定位置应该放的最小元素

在这里插入图片描述

2.复杂度分析

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2),即需要 O ( N ) O(N) O(N)轮,每轮 O ( N ) O(N) O(N)的操作

  • 空间复杂度: O ( 1 ) O(1) O(1),即需要常数级的变量空间,无需开辟新的空间

  • 稳定性:指的是相同元素保存相对位置不变的特性,正常算法无法保持稳定性,因为交换后会打乱顺序

3.代码实现

public class SelectionSort {
	private SelectionSort() {}
	
	public static <E extends Comparable<E>> void sort2(E[] arr) {
		if(arr == null || arr.length < 2) return;
		
		for(int i = 0; i < arr.length - 1; i ++) { // 最后一个元素自动确认,减少循环次数
			int minIndex = i; // 目标要放置位置的元素 = minIndex = i
			for(int j = i + 1; j < arr.length; j ++) {
				minIndex = arr[j].compareTo(arr[minIndex]) < 0 ? j : minIndex;
			}
			swap(arr, minIndex, i);
		}
	}
	
	private static <E>void swap(E[] arr, int j, int i) {
		E t = arr[j];
		arr[j] = arr[i];
		arr[i] = t;
	}
}

4.代码测试

public static void main(String[] args) {
		int n = 10000;
		System.out.println("Random Array:");
		Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
		Integer[] arr1 = Arrays.copyOf(arr, arr.length);
		
		SortingHelper.sortTime("BubbleSort", arr);
		SortingHelper.sortTime("SelectionSort", arr1); // 无序选择最好,减少交换次数
	}
Random Array:
BubbleSort, n = 10000 : 0.410318 s
SelectionSort, n = 10000 : 0.058414 s
  • 通过测试知,选择排序法实现正确,且时间性能方面,由于减少了交换次数,会相对比冒泡排序法更好。
插入排序法

三、插入排序法

1.代码实现思想

  • 每一轮,对指定元素,找到它要插入的位置,保证插入位置之前的元素已经排好序

  • 通过两两比较的方式判断插入位置,保证插入的位置前所有元素比指定元素小,后所有元素比指定元素大

  • 减少比较次数先找位置,不急着交换,只是将非插入位置元素往后挪,复杂度会低些,找到位置后,再将指定元素放到插入位置上

2.复杂度分析

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2),即需要 O ( N ) O(N) O(N)轮,每轮 O ( N ) O(N) O(N)的操作
    • 有序数组的排序, O ( 1 ) O(1) O(1),需要比较n-1次,无需交换元素
  • 空间复杂度: O ( 1 ) O(1) O(1),即需要常数级的变量空间,无需开辟新的空间
  • 稳定性:指的是相同元素保存相对位置不变的特性,因为位置是相对挪动,保证相同元素不挪动,可以保证稳定性

3.代码实现

import java.util.Arrays;

import bubbleSort.ArrayGenerator;
import bubbleSort.SortingHelper;

public class InsertionSort {
	private InsertionSort() {};
	
	public static <E extends Comparable<E>> void sort(E[] arr) {
		// 精髓是两两比较,如果已经排好序,就不再交换,与冒泡比较,减少交换
		// 找到要处理的数要插到的位置,该位置后的元素依次挪位,该位置前的元素已经排好序
		for(int i = 1; i < arr.length; i ++) {
			E target = arr[i]; // 要处理的目标元素
			int j;
			for(j = i; j > 0 && arr[j - 1].compareTo(target) > 0; j --) {
				arr[j] = arr[j - 1];
			}
			arr[j] = target;
		}
	}
}

4.代码测试

public static void main(String[] args) {
		int n = 10000;
    	// 无序数组的测试
		System.out.println("Random Array:");
		Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
		Integer[] arr1 = Arrays.copyOf(arr, arr.length);
		Integer[] arr2 = Arrays.copyOf(arr, arr.length);
		
		SortingHelper.sortTime("BubbleSort", arr);
		SortingHelper.sortTime("SelectionSort", arr1); // 无序选择最好,减少交换次数
		SortingHelper.sortTime("InsertionSort", arr2);
		System.out.println();
		
    	// 有序数组的测试
		System.out.println("Order Array:");
		arr = ArrayGenerator.generateOrderedArray(n);
		arr1 = Arrays.copyOf(arr, arr.length);
		arr2 = Arrays.copyOf(arr, arr.length);
		SortingHelper.sortTime("BubbleSort", arr);
		SortingHelper.sortTime("SelectionSort", arr1);
		SortingHelper.sortTime("InsertionSort", arr2); // 有序,插入最好,已经排好序的无序操作
	}
Random Array:
BubbleSort, n = 10000 : 0.430529 s
SelectionSort, n = 10000 : 0.057633 s
InsertionSort, n = 10000 : 0.153631 s

Order Array:
BubbleSort, n = 10000 : 0.106295 s
SelectionSort, n = 10000 : 0.046629 s
InsertionSort, n = 10000 : 0.000241 s
  • 测试结果显示:排序算法正确,无序数组,性能方面,选择排序法由于交换次数较少,较其他两种较优
    • 有序数组插入排序法,对已经排好序的数组操作较简单,性能较其他两种较优
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值