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