(全)数据结构各种排序的总结(Java实现)

这篇博客总结了数据结构中的各种排序算法,包括冒泡排序、插入排序、选择排序、堆排序、快速排序、归并排序、桶排序、基数排序和希尔排序。详细介绍了每个排序算法的思想,并指出稳定性和适用场景。对于稳定排序方法,如冒泡排序、插入排序、归并排序、基数排序和桶排序进行了特别说明。

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

(全)数据结构各种排序的总结(Java实现)

今天整理了一下数据结构课上所学的各种排序,先写一下今天刚敲完的代码(都只写了升序),也是对自己学习的总结,希望能帮到大家。才疏学浅,若有错误希望大家多多指出,一定虚心接受。


首先贴上代码中用到的简单交换函数swap的代码,作用就是交换数组中两个不同位置的数。

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

1.冒泡排序及其优化

思想:冒泡排序是将数组中相邻的两个元素不断比较,第一个比第二个大就交换,因此会将较大的数一直换到最后,然后再缩小比较范围,直至为1排序完成,是一个稳定的排序方法。优化的冒泡排序是增加了一个用于判断数组此时是否有序的变量,有序之后则停止循环。

	/*
	 * 冒泡排序及其优化版本 O(n*n)
	 */
	public static void bubbleSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		for (int end = length - 1; end > 0; end--) { // 将最大的数依次交换到end位置
			for (int j = 0; j < end; j++) {
				if (arr[j] > arr[j + 1]) {
					swap(arr, j, j + 1);
				}
			}
		}
	}

	public static void bubbleSortPro(int[] arr) { // 时间复杂度与数据状态有关
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		boolean isRandom = true;// 记录此时的数组是否仍是无序的
		for (int end = length - 1; end > 0 && isRandom; end--) { // 已经有序后就退出
			isRandom = false;
			for (int j = 0; j < end; j++) {
				if (arr[j] > arr[j + 1]) {
					swap(arr, j, j + 1);
					isRandom = true;
				}
			}
		}
	}

2.插入排序

思想:每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止,是一个稳定的排序方法。

	/*
	 * 插入排序 O(n*n)
	 */
	public static void insertionSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		for (int i = 1; i < length; i++) {// 将第i位上的数当作要插入的数
			for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {// 依次向前比较 找到大于前面数且小于后面数的位置停下
				swap(arr, j, j + 1);
			}
		}
	}

3.选择排序

思想:每一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,然后起始位置向后移,直至待排序列只有一个,这是一个不稳定的排序。

public static void selectionSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		int minIndex = 0;
		for (int i = 0; i < length - 1; i++) {
			minIndex = i;
			for (int j = i + 1; j < length; j++) {
				minIndex = arr[j] < arr[minIndex] ? j : minIndex; // 依次找出最小值放在i位置上
			}
			swap(arr, minIndex, i);
		}
	}

4.堆排序

思想:堆是一个近似完全二叉树的结构,这里用到的是大根堆,即子结点的键值或索引总是小于它的父节点,再不断取出大根堆的根节点(最大值)就可以实现排序,这是个不稳定的排序。

	/*
	 * 堆排序 O(nlogn) 首先实现两个功能 heapInsert 和 heapify
	 */
	public static void heapInsert(int[] arr, int index) {
		while (arr[index] > arr[(index - 1) / 2]) {
			swap(arr, index, (index - 1) / 2); // 如果插入的节点的值大于父节点,就向上交换
			index = (index - 1) / 2;
		}
	}

	public static void heapify(int[] arr, int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left; // 右孩子不越界的情况下找到左右孩子较大的那个的下标
			largest = arr[index] > arr[largest] ? index : largest; // 找出父亲节点和左右孩子中最大的节点的下标
			if (index == largest) {
				break; // 如果此时父亲节点最大,不用再调整
			}
			swap(arr, index, largest); // 父亲节点不是最大的话就往下沉,与最大的孩子节点交换
			index = largest;
			left = index * 2 + 1; // 继续下沉,直至成为大根堆
		}
	}

	public static void heapSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		for (int i = 0; i < length; i++) {
			heapInsert(arr, i); // 形成大根堆
		}
		swap(arr, 0, --length); // 将最大的数交换到最后
		while (length > 0) {
			heapify(arr, 0, length);// 再次形成大根堆,将此时最大的数取出交换到--length处
			swap(arr, 0, --length);
		}
	}

5.快速排序

思想:通过一趟排序将要排序的数据分割成独立的三部分,小于最后一个数的部分,等于最后一个数的部分,大于最后一个数的部分(参考荷兰国旗问题),然后再按此方法对不等于的两部分进行排序,不是稳定的排序方法。


	/*
	 * 快速排序 O(nlogn) 这里写的是随机快排
	 */
	public static void quickSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		quickSort(arr, 0, length - 1);
	}

	public static void quickSort(int[] arr, int l, int r) {
		if (l < r) {
			swap(arr, l + (int) (Math.random() * (r - l + 1)), r); // 随机快排比经典快排只多这一步
			int[] p = partition(arr, l, r);
			quickSort(arr, l, p[0] - 1);
			quickSort(arr, p[1] + 1, r);
		}
	}

	public static int[] partition(int[] arr, int l, int r) {
		int less = l - 1;
		int more = r;
		int index = l;
		while (index < more) {
			if (arr[index] < arr[r]) {
				swap(arr, index++, ++less); // 遇到比最后一个数小的数就放在less区域后的第一个位置(即等于区域的第一个),less再++扩充范围,此时index也++比较下一个
			} else if (arr[index] > arr[r]) {
				swap(arr, index, --more);// 遇到比最后一个数大的数就放在more区域,此时交换过来的数并没有参与比较,所以index不++
			} else {
				index++;// 相等的话index++
			}
		}
		swap(arr, more, r);// 将最后一个的位置调整好
		return new int[] { less + 1, more }; // 返回相等区间的范围
	}

6.归并排序

思想:采用分治的一个非常典型的应用。首先将所要排序的序列一分为二递归进行,再将已有序的子序列合并,得到完全有序的序列,是个稳定的排序方法。

	/*
	 * 归并排序 O(nlogn)
	 */

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

	public static void mergeSort(int[] arr, int l, int r) {
		if (l == r) {
			return;
		}
		int mid = l + (r - l) / 2;
		mergeSort(arr, l, mid);
		mergeSort(arr, mid + 1, r);
		merge(arr, l, mid, r);
	}

	public static void merge(int[] arr, int l, int mid, int r) {
		int[] help = new int[r - l + 1];
		int p1 = l;
		int p2 = mid + 1;
		int index = 0;
		while (p1 <= mid && p2 <= r) {
			help[index++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		while (p1 <= mid) {
			help[index++] = arr[p1++];
		}
		while (p2 <= r) {
			help[index++] = arr[p2++];
		}
		for (int i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}
	}

7.桶排序

思想:将数组分到有限数量的桶里,再按顺序将每个桶中的数据取出,所以桶排序不是基于比较的排序。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n)),是一种稳定的排序方法,但是当数据量较大时会使用较大的空间,因此更适用于数据量小的时候。

	/*
	 * 桶排序 
	 */
	public static void bucketSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		int maxValue = Integer.MIN_VALUE;
		for (int i = 0; i < length; i++) {
			maxValue = arr[i] > maxValue ? arr[i] : maxValue; // 找出数组中的最大值
		}
		int bucket[] = new int[maxValue + 1];
		for (int i = 0; i < length; i++) {
			bucket[arr[i]]++; // 依次放进对应的桶中
		}
		int index = 0;
		for (int i = 0; i <= maxValue; i++) {
			while (bucket[i]-- > 0) {
				arr[index++] = i;// 从桶中取出
			}
		}
	}

8.基数排序

思想:属于“分配式排序”,通俗来讲就是先按照个位数值项数组排好序,在个位排好序的基础上之后再通过比较十位数值排序,以此类推直到把最高位的顺序也排好,是一种稳定的排序方法。

	/*
	 * 基数排序 (写了两种方法,个人感觉可能第二种更好理解)
	 */
	public static void radixSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		final int radix = 10;
		int bucket[] = new int[length];
		int maxDigit = getMaxDigit(arr);
		for (int i = 1; i <= maxDigit; i++) {
			int count[] = new int[radix];
			for (int j = 0; j < length; j++) {
				count[getRemainder(arr[j], i)]++; // 记录各个位上数值为0~9的个数有多少
			}
			for (int j = 1; j < 10; j++) {
				count[j] = count[j - 1] + count[j]; // 将count[i]的值转变为在bucket数组中的最大位置的数值
			}
			for (int j = length - 1; j >= 0; j--) { // 因为count[remainder]是从大到小往bucket数组里放,为了保留上一次排序的顺序,需要倒序遍历
				int remainder = getRemainder(arr[j], i);
				bucket[--count[remainder]] = arr[j];
			}
			for (int j = 0; j < length; j++) {
				arr[j] = bucket[j]; // 将排序的结果放回数组
			}
		}
	}

	public static int getRemainder(int x, int y) { // 返回X在个位、十位...上的数值
		return ((x / ((int) Math.pow(10, y - 1))) % 10);
	}

	public static int getMaxDigit(int[] arr) {// 返回数组中最大数的位数
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < arr.length; i++) {
			max = Math.max(max, arr[i]);
		}
		int res = 0;
		while (max != 0) {
			res++;
			max /= 10;
		}
		return res;
	}

第二种方法使用了二维数组来存储bucket的值,额外空间复杂度更高。

public static void radixSortDMArray(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		final int radix = 10;
		int maxDigit = getMaxDigit(arr);
		for (int i = 1; i <= maxDigit; i++) {
			int[] count = new int[radix];
			int[][] bucket = new int[10][length];
			for (int j = 0; j < length; j++) {
				int remainder = getRemainder(arr[j], i);
				bucket[remainder][count[remainder]++] = arr[j];
			}
			int index = 0;
			for (int j = 0; j < 10; j++) {
				if (count[j] != 0) {
					for (int k = 0; k < count[j]; k++) {
						arr[index++] = bucket[j][k];
					}
				}
			}
		}
	}

9.希尔排序

思想:是直接插入排序算法的一种更高效的改进版本,主要体现在使用增量来分组排好序,再不断缩小增量,最后增量为1时,分组为1,整个数组有序,是个非稳定的排序方法。

	/*
	 * 希尔排序
	 */

	public static void shellSort(int[] arr) {
		int length = arr.length;
		if (arr == null || length < 2) {
			return;
		}
		length /= 2; // 增量
		while (length >= 1) {

			for (int i = length; i < arr.length; i++) {
				int temp = arr[i];
				int index;
				for (index = i - length; index >= 0 && arr[index] > temp; index = index - length) { // 把arr[i]的值在分組中向前直接插入
					arr[index + length] = arr[index];
				}
				arr[index + length] = temp; // index在第一个小于temp的地方停下,所以在index后面的位置插入
			}
			length /= 2;
		}

	}

有关稳定性的总结

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。以上的排序中稳定的排序方法有:冒泡排序插入排序归并排序基数排序桶排序;非稳定的排序方法有:选择排序快速排序堆排序希尔排序。 详细解释请见排序算法稳定性。

才疏学浅,若有错误希望大家多多指出,一定虚心接受。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值