常见排序算法及其优化(Java实现)

所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。
在这里插入图片描述

一. 冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1. 算法步骤
(1) 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
(2) 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
(3) 针对所有的元素重复以上的步骤,除了最后一个。
(4) 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

2. 动图演示
在这里插入图片描述
3. 举例演示

待排数组:{21,10,52,4,9,2}


第一趟排序过程:

  第一次比较:21和10比较,21比10大,交换位置,得:{10,21,52,4,9,2}
  第二次比较:21和52比较,21比52小,不用交换位置,得:{10,21,52,4,9,2}
  第三次比较:52和4比较,52比4大,交换位置,得:{10,21,4,52,9,2}
  第四次比较:52和9比较,52比9大,交换位置,得:{10,21,4,9,52,2}
  第五次比较:52和2比较,52比2大,交换位置,得:{10,21,4,9,2,52}

第一趟排序结束,排序结果:{10,21,4,9,2,52}

依照上面的操作方法,可得到:

  第二趟排序结果:{10,4,9,2,21,52}
  第三趟排序结果:{4,9,2,10,21,52}
  第四趟排序结果:{4,2,9,10,21,52}
  第五趟排序结果:{2,4,9,10,21,52}

最终排序结果:{2,4,9,10,21,52}

4. 什么时候最快
当输入的数据已经是正序时(都已经是正序了,当然不需要排序了。在走访一遍数列后发现没有一对数字需要交换,就可以结束了)。

5. 什么时候最慢
当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢?不过计算机可不知道你输入的是不是反序哈哈哈,所以它会走访n - 1遍数列后使得数列有序)。

6. Java实现冒泡排序

public void bubbleSort(int[] a) {
	int n = a.length;
	// 一共n-1趟排序
	for (int i = 1; i < n; i++) {
		// 第i趟排序从第0个元素到第n-i个元素
		for (int j = 0; j < n - i; j++) {
			if (a[j] > a[j + 1]) {
				int temp;
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

7. 优化一
对于一些连片有序而整体无序的序列,如{2,4,6,8,7},按照上面的排序方式,第一趟排序后将8和7交换后,该数组已经有序,接下来的3趟排序就是多余的。因此我们可以增加一个标志,如果某一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去,就可以结束排序。具体的实现代码请看下面:

public void bubbleSort(int[] a) {
	int n = a.length;
	// 第一次判断时,要将flag置为true,使得后续排序可以进行
	boolean flag = true;
	for (int i = 1; i < n && flag; i++) {
		// 每趟排序前先将flag置为false,因为这一趟排序还没开始,当然没有元素进行交换了
		flag = false;
		for (int j = 0; j < n - i; j++) {
			if (a[j] > a[j + 1]) {
				int temp;
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
				// 有数据交换了,falg设置为true
				flag = true;  
			}
		}
	}
}

8. 优化二
优化一仅仅适用于连片有序而整体无序的数据(例如:1, 2,3 ,4 ,7,6,5)。但是对于前面大部分是无序而后边小半部分有序的数据(3,2,4,7,4,3,7,8,9,10)排序效率也不可观,对于种类型数据,我们可以继续优化。怎么优化呢?我们可以记下最后一次交换的位置,在最后一次交换的位置后面,必然是有序的,然后下一趟排序从第一个比较到上次记录的位置结束即可,具体的实现代码请看下面:

public void bubbleSort(int[] a) {
	int n = a.length;
	boolean flag = true;
	// 最后一次发生交换的位置
	int rightPos = n;
	int right = n - 1;
	for (int i = 1; i < n && flag; i++) {
		flag = false;
		for (int j = 0; j < right; j++) {
			if (a[j] > a[j + 1]) {
				int temp;
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
				flag = true;
				// 记录发生元素交换的位置
				rightPos = j;
			}
		}
		right = rightPos;  // 下一次只需比较到rightPos这个位置即可 
	}
}

二. 选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

1. 算法步骤
(1)首先在未排序序列中找到最小元素,存放到排序序列的起始位置。
(2)再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
(3)重复第二步,直到所有元素均排序完毕。

2. 动图演示
在这里插入图片描述

3. 举例演示

待排数组:{5, 2, 8, 4, 9, 1}


第一趟排序:
从第一个位置开始,找出数组中最小的元素,将最小元素与第一个元素交换位置。
第一趟排序结果:{1,2,8,4,9,5}
第二趟排序:
从第二个位置开始,找出数组中最小的元素,将最小元素与第二个元素交换位置。
第二趟排序结果:{1,2,8,4,9,5}
第三趟排序:
从第三个位置开始,找出数组中最小的元素,将最小元素与第三个元素交换位置。
第三趟排序结果:{1,2,4,8,9,5}
第四趟排序:
从第四个位置开始,找出数组中最小的元素,将最小元素与第四个元素交换位置。
第四趟排序结果:{1,2,4,5,9,8}
第五趟排序:
从第五个位置开始,找出数组中最小的元素,将最小元素与第五个元素交换位置。
第五趟排序结果:{1,2,4,5,8,9}

排序结束,排序结果:{1,2,4,5,8,9}

4. Java实现选择排序

public static void selectionSort(int[] arr) {
	int len = arr.length;
	// 长度为len的数组,需要len - 1次排序
	for (int i = 0; i < len - 1; i++) {
		// min:用来记录这趟遍历中,最小数的位置
		int min = i;
		// 从i位置向后开始找有没有小于arr[min]的元素
		for (int j = i + 1; j < len; j++) {
			if (arr[j] < arr[min])
				min = j;
		}
		// 内层循环结束,找到本次循环的最小数,进行交换
		if (i != min) {
			int temp = arr[i];
			arr[i] = arr[min];
			arr[min] = temp;
		}
	}
}

5. 优化
选择排序的思想是在要排序的一组数中,选出最小的那个数,然后与第一位置的数进行交换;然后再剩下的数中,找出最小的那个数与第二个数进行交换,直到第 n-1 个数与第 n 个数比较为止。而二元选择排序,顾名思义,从待排序的数组中找出一个最大值和一个最小值,分别与第一个数、最后一个数进行交换,这样可以使选择排序的时间复杂度降低,所以外层循环从 n 次变为了 n/2 次。代码实现如下:

public void selectionSort(int[] arr) {
	int len = arr.length;
	int rightPos = arr.length - 1;
	// 每次都选出了最大和最小的元素,则一共需要len / 2次排序
	for (int i = 0; i < len / 2; i++, rightPos--) {
		int max = i;
		int min = i;
		for (int j = i + 1; j <= rightPos; j++) {
			if (arr[j] > arr[max])
				max = j;
			if (arr[j] < arr[min])
				min = j;
		}
		// 将这一趟遍历中最大的元素放到rightPos这个位置上
		int temp1 = arr[rightPos];
		arr[rightPos] = arr[max];
		arr[max] = temp1;

		if (i != min) {
			int temp = arr[i];
			// 有一种特殊情况就是,这一趟遍历中,最小元素刚好在rightPos这个位置上
			// 经过上面放置最大元素的操作,最小元素的位置已经变为max了
			if (min == rightPos) {
				arr[i] = arr[max];
				arr[max] = temp;
			} else {
				arr[i] = arr[min];
				arr[min] = temp;
			}
		}
	}
}

三. 插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

1. 算法步骤
(1)将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
(2)从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

2. 动图演示
在这里插入图片描述
3.Java代码实现

public void insertSort(int[] arr) {
	int len = arr.length;
	for (int i = 1; i < len; i++) {
		int temp = arr[i];
		int j = i;
		// 将比i位置上的元素大的元素往后移动一个位置
		while (j > 0 && temp < arr[j - 1]) {
			arr[j] = arr[j - 1];
			j--;
		}
		if (j != i)
			arr[j] = temp;
	}
}

4. 优化
在上面介绍的插入排序算法的思想,它是从有序序列的最后一个元素开依次向前找出待排元素的位置。其实,为了找出待排元素的位置,我们可以用二分查找算法来找,这样可以减少查找的次数。具体代码实现如下:

public static void insertSort(int[] arr) {
	int len = arr.length;
	for (int i = 1; i < len; i++) {
		int temp = arr[i];
		// 使用二分查找算法找出元素arr[i]该插入的位置
		int low = 0;
		int high = i - 1;
		while (low <= high) {
			int mid = (low + high) / 2;
			if (arr[mid] > temp)
				high = mid - 1;
			else
				low = mid + 1;
		}
		// 元素后移,为插入temp腾出空间
		for (int j = i - 1; j >= low; j--) {
			arr[j + 1] = arr[j];
		}
		arr[low] = temp;
	}
}

四、希尔排序

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值