排序-图解算法【简单选择排序、冒泡排序、插入排序、希尔排序、快速排序、归并排序】

本文详细介绍了几种基本的排序算法,包括简单选择排序、冒泡排序、插入排序、希尔排序、快速排序和归并排序。每种排序算法都有其独特的工作原理和时间复杂度,并通过图解和实现代码进行了清晰的解释。冒泡排序可以通过优化减少循环次数,快速排序利用了分治策略,而归并排序则采用递归的合并过程来实现排序。

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

各个排序的时间复杂度

在这里插入图片描述

简单选择排序

  • 每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止,简单选择排序是不稳定排序;在每次比较时将最小的数的下标保存起来,这个一次循环完成后,再进行交换顺序
  • 数组分成有序区和无序区,初始时整个数组都是无序区,然后每次从无序区选一个最小的元素直接放到有序区的最后,直到整个数组变有序区。
  • 图解
    在这里插入图片描述
  • 实现
	public void simpleChoiceSort(int[] data) {
		for (int i = 0; i < data.length - 1; i++) {
			// 每一趟循环比较时,tempMinIndex 用于存放较小元素的数组下标,这样当前批次比较完毕最终存放的就是此趟内最小的元素的下标,避免每次遇到较小元素都要进行交换。
			int tempMinIndex = i;
			// 默认与i相等的一位作为基数,然后跟它的下一位比较。。。
			for (int j = i + 1; j <= data.length - 1; j++) {
				// 从大到小,是【<】;从小到大,是【>】
				if (data[tempMinIndex] > data[j]) {
					tempMinIndex = j;
				}
			}
			// 进行交换,如果tempMinIndex 发生变化,则进行交换
			if (tempMinIndex != i) {
				int tempData = data[i];
				data[i] = data[tempMinIndex];
				data[tempMinIndex] = tempData;
			}
		}
	}

冒泡排序

  • 冒泡排序分从大到小和从小到大两种排序方式。它们的唯一区别就是两个数交换的条件不同,从大到小排序是前面的数比后面的小的时候交换,而从小到大排序是前面的数比后面的数大的时候交换。
  • 从第一个数开始,依次往后比较,如果前面的数比后面的数大就交换,否则不作处理。这就类似烧开水时,壶底的水泡往上冒的过程。
  • 图解
    在这里插入图片描述
  • 实现
	public void bubbleSort(int[] data) {
		for (int i = 0; i < data.length; i++) {
			// 外层循环增加一次,对应内层循环就 减少一次;内层循环【data.length - 1 - i】外层循环【data.length - 1】
			for (int j = 0; j < data.length - 1 - i; j++) {
				// 将整个队列中相邻的两个元素做比较,将较小的元素和较大的元素交换位置
				// 从大到小,是【<】;从小到大,是【>】
				if (data[j] > data[j + 1]) {
					int temp = data[j];
					data[j] = data[j + 1];
					data[j + 1] = temp;
				}
			}
		}
	}
  • 冒泡排序优化
  • 减少外层循环次数的优化【如果该次循环没有发生一次数的交换,就说明数组已经排好序了,那么后面的循环比较就可以停止了】
	public void bubbleSortPlus(int[] data) {
		// flag: 在一次循环中是否发生过数据交换。true:表示发生过交换,false:表示未发生过交换
		boolean flag = true;
		for (int i = 0; i < data.length; i++) {
			// 外层循环增加一次,对应内层循环就 减少一次;内层循环【data.length - 1 - i】外层循环【data.length - 1】
			// 如果未发生交换,则break
			if (!flag) {
				break;
			}
			// 每次循环都先置为false,即认为不会发生交换
			flag = false;
			for (int j = 0; j < data.length - 1 - i; j++) {
				if (data[j] < data[j + 1]) {
					// 发生了交换
					flag = true;
					int temp = data[j];
					data[j] = data[j + 1];
					data[j + 1] = temp;
				}
			}
		}
	}
  • 减少内层循环次数的优化【如果我们将第一次出现该次不再交换数据时的位置下标找到,然后把这个值作为内层循环j的上限值。这样内层循环就会减少一些循环。所以内层循环的 j 的上限值是动态值】
	public void bubbleSortPlusPlus(int[] data) {
		// flag: 在一次循环中是否发生过数据交换。true:表示发生过交换,false:表示未发生过交换
		boolean flag = true;
		// 上一次内层循环上界值,在第一次循环时与arr.length-1-i相等,即:arr.length-1。也就是说默认是最后一个
		int k = data.length - 1;
		for (int i = 0; i < data.length - 1; i++) {
			// 如果未发生交换,则break
			if (!flag) {
				break;
			}
			// 每次循环都先置为false,即认为不会发生交换
			flag = false;
			// 记录上一次内层循环时最后一次交换的位置
			int last = 0;
			for (int j = 0; j < k; j++) {
				if (data[j] > data[j + 1]) {
					// 发生了交换
					flag = true;
					// 记录每次交换的位置
					last = j;
					int temp = data[j];
					data[j] = data[j + 1];
					data[j + 1] = temp;
				}
			}
			// 让下一次循环的上界值变成此次循环的最后一次交换的位置
			k = last;
		}
	}

插入排序

  • 类似于打扑克牌。每次将一个待排序的数据,跟前面已经有序的序列的数字一一比较找到自己合适的位置,插入到序列中,直到全部数据插入完成。
  • 图解
    在这里插入图片描述
  • 实现
	public void insertSort(int[] data) {
		for (int i = 1; i < data.length; i++) {
			// 将data[i]作为基数
			int temp = data[i];
			for (int j = i - 1; j >= 0; j--) {
				if (temp < data[j]) {
					data[j + 1] = data[j];
					data[j] = temp;
				} else {
					break;
				}
			}
		}
	}

希尔排序

  • 先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。由于希尔排序是对相隔若干距离的数据进行直接插入排序,因此可以形象的称希尔排序为“跳着插”
  • 实现
	// 希尔排序
	public void shellsSort(int[] data) {
		for (int incr = data.length / 2; incr > 0; incr = incr / 2) {
			for (int i = incr; i < data.length; i = i + incr) {
				int temp = data[i];
				for (int j = i - incr; j >= 0; j = j - incr) {
					if (temp < data[j]) {
						data[j + incr] = data[j];
						data[j] = temp;
					} else {
						// data[j + incr] = temp;
						break;
					}
				}
			}
		}
	}

快速排序

  • “挖坑填数+分治法”,首先令i =L; j = R; 将a[i]挖出形成第一个坑,称a[i]为基准数。然后j–由后向前找比基准数小的数,找到后挖出此数填入前一个坑a[i]中,再i++由前向后找比基准数大的数,找到后也挖出此数填到前一个坑a[j]中。重复进行这种“挖坑填数”直到i==j。再将基准数填入a[i]中,这样i之前的数都比基准数小,i之后的数都比基准数大。因此将数组分成二部分再分别重复上述步骤就完成了排序。

  • 图解
    在这里插入图片描述

  • 实现

	public static void quickSort1(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process1(arr, 0, arr.length - 1);
	}

	public static void process1(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		// L..R partition arr[R]  [   <=arr[R]   arr[R]    >arr[R]  ]
		int M = partition(arr, L, R);
		process1(arr, L, M - 1);
		process1(arr, M + 1, R);
	}

归并排序

  • 归并排序主要分为两步:分数列(divide),每次把数列一分为二,然后分到只有两个元素的小数列;合数列(Merge),合并两个已经内部有序的子序列,直至所有数字有序。用递归可以实现。

  • 图解
    在这里插入图片描述

  • 实现

	private static void mergeSort(int[] data) {
		// 传入的是索引值
		fun(0, data.length - 1, data);
	}

	private static void fun(int left, int right, int[] data) {
		if (left >= right) {
			return;
		}
		int mid = (left + right) / 2;
		fun(left, mid, data);// 左边归并排序
		fun(mid + 1, right, data);// 右边归并排序
		// 归并
		merge(left, right, data, mid);
	}

	private static void merge(int left, int right, int[] data, int mid) {
		// new 一个相同长度的临时数组
		int[] tempData = new int[right - left + 1];
		int leftPos = left;// 左序列指针
		int rightPos = mid + 1;// 右序列指针
		int tempPos = 0;// 临时数组指针
		while (leftPos <= mid && rightPos <= right) {
			if (data[leftPos] <= data[rightPos]) {
				tempData[tempPos++] = data[leftPos++];
			} else {
				tempData[tempPos++] = data[rightPos++];
			}
		}
		while (leftPos <= mid) {// 将左边剩余元素填充进temp中
			tempData[tempPos++] = data[leftPos++];

		}
		while (rightPos <= right) {// 将右序列剩余元素填充进temp中
			tempData[tempPos++] = data[rightPos++];
		}
		tempPos = 0;
		// 将temp中的元素全部拷贝到原数组中
		while (left <= right) {
			data[left++] = tempData[tempPos++];
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值